diff --git a/deploy/crds/crd-challenges.yaml b/deploy/crds/crd-challenges.yaml index d89fc4597..5db7f43c4 100644 --- a/deploy/crds/crd-challenges.yaml +++ b/deploy/crds/crd-challenges.yaml @@ -492,6 +492,41 @@ spec: Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names type: string + auth: + description: Auth configures how cert-manager authenticates. + type: object + required: + - kubernetes + properties: + kubernetes: + description: |- + Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + by passing a bound ServiceAccount token. + type: object + required: + - serviceAccountRef + properties: + serviceAccountRef: + description: |- + A reference to a service account that will be used to request a bound + token (also known as "projected token"). To use this field, you must + configure an RBAC rule to let cert-manager request a token. + type: object + required: + - name + properties: + audiences: + description: |- + TokenAudiences is an optional list of audiences to include in the + token passed to AWS. The default token consisting of the issuer's namespace + and name is always included. + If unset the audience defaults to `sts.amazonaws.com`. + type: array + items: + type: string + name: + description: Name of the ServiceAccount used to request a token. + type: string hostedZoneID: description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. type: string diff --git a/deploy/crds/crd-clusterissuers.yaml b/deploy/crds/crd-clusterissuers.yaml index f73392551..c0effdb0b 100644 --- a/deploy/crds/crd-clusterissuers.yaml +++ b/deploy/crds/crd-clusterissuers.yaml @@ -599,6 +599,41 @@ spec: Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names type: string + auth: + description: Auth configures how cert-manager authenticates. + type: object + required: + - kubernetes + properties: + kubernetes: + description: |- + Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + by passing a bound ServiceAccount token. + type: object + required: + - serviceAccountRef + properties: + serviceAccountRef: + description: |- + A reference to a service account that will be used to request a bound + token (also known as "projected token"). To use this field, you must + configure an RBAC rule to let cert-manager request a token. + type: object + required: + - name + properties: + audiences: + description: |- + TokenAudiences is an optional list of audiences to include in the + token passed to AWS. The default token consisting of the issuer's namespace + and name is always included. + If unset the audience defaults to `sts.amazonaws.com`. + type: array + items: + type: string + name: + description: Name of the ServiceAccount used to request a token. + type: string hostedZoneID: description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. type: string diff --git a/deploy/crds/crd-issuers.yaml b/deploy/crds/crd-issuers.yaml index 8bd4d281d..12a291b6d 100644 --- a/deploy/crds/crd-issuers.yaml +++ b/deploy/crds/crd-issuers.yaml @@ -599,6 +599,41 @@ spec: Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names type: string + auth: + description: Auth configures how cert-manager authenticates. + type: object + required: + - kubernetes + properties: + kubernetes: + description: |- + Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + by passing a bound ServiceAccount token. + type: object + required: + - serviceAccountRef + properties: + serviceAccountRef: + description: |- + A reference to a service account that will be used to request a bound + token (also known as "projected token"). To use this field, you must + configure an RBAC rule to let cert-manager request a token. + type: object + required: + - name + properties: + audiences: + description: |- + TokenAudiences is an optional list of audiences to include in the + token passed to AWS. The default token consisting of the issuer's namespace + and name is always included. + If unset the audience defaults to `sts.amazonaws.com`. + type: array + items: + type: string + name: + description: Name of the ServiceAccount used to request a token. + type: string hostedZoneID: description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. type: string diff --git a/internal/apis/acme/types_issuer.go b/internal/apis/acme/types_issuer.go index daa80b2b2..545040791 100644 --- a/internal/apis/acme/types_issuer.go +++ b/internal/apis/acme/types_issuer.go @@ -421,6 +421,9 @@ type ACMEIssuerDNS01ProviderDigitalOcean struct { // ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 // configuration for AWS type ACMEIssuerDNS01ProviderRoute53 struct { + // Auth configures how cert-manager authenticates. + Auth *Route53Auth + // The AccessKeyID is used for authentication. // Cannot be set when SecretAccessKeyID is set. // If neither the Access Key nor Key ID are set, we fall-back to using env @@ -453,6 +456,35 @@ type ACMEIssuerDNS01ProviderRoute53 struct { Region string } +// Route53Auth is configuration used to authenticate with a Route53. +type Route53Auth struct { + // Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + // by passing a bound ServiceAccount token. + Kubernetes *Route53KubernetesAuth +} + +// Route53KubernetesAuth is a configuration to authenticate against Route53 +// using a bound Kubernetes ServiceAccount token. +type Route53KubernetesAuth struct { + // A reference to a service account that will be used to request a bound + // token (also known as "projected token"). To use this field, you must + // configure an RBAC rule to let cert-manager request a token. + ServiceAccountRef *ServiceAccountRef +} + +// ServiceAccountRef is a service account used by cert-manager to request a +// token. The expiration of the token is also set by cert-manager to 10 minutes. +type ServiceAccountRef struct { + // Name of the ServiceAccount used to request a token. + Name string + + // TokenAudiences is an optional list of audiences to include in the + // token passed to AWS. The default token consisting of the issuer's namespace + // and name is always included. + // If unset the audience defaults to `sts.amazonaws.com`. + TokenAudiences []string +} + // ACMEIssuerDNS01ProviderAzureDNS is a structure containing the // configuration for Azure DNS type ACMEIssuerDNS01ProviderAzureDNS struct { diff --git a/internal/apis/acme/v1/zz_generated.conversion.go b/internal/apis/acme/v1/zz_generated.conversion.go index 468def2a9..b49ce9928 100644 --- a/internal/apis/acme/v1/zz_generated.conversion.go +++ b/internal/apis/acme/v1/zz_generated.conversion.go @@ -374,6 +374,36 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*v1.Route53Auth)(nil), (*acme.Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Route53Auth_To_acme_Route53Auth(a.(*v1.Route53Auth), b.(*acme.Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53Auth)(nil), (*v1.Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53Auth_To_v1_Route53Auth(a.(*acme.Route53Auth), b.(*v1.Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1.Route53KubernetesAuth)(nil), (*acme.Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(a.(*v1.Route53KubernetesAuth), b.(*acme.Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53KubernetesAuth)(nil), (*v1.Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth(a.(*acme.Route53KubernetesAuth), b.(*v1.Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1.ServiceAccountRef)(nil), (*acme.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ServiceAccountRef_To_acme_ServiceAccountRef(a.(*v1.ServiceAccountRef), b.(*acme.ServiceAccountRef), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.ServiceAccountRef)(nil), (*v1.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ServiceAccountRef_To_v1_ServiceAccountRef(a.(*acme.ServiceAccountRef), b.(*v1.ServiceAccountRef), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*acme.ACMEIssuer)(nil), (*v1.ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_acme_ACMEIssuer_To_v1_ACMEIssuer(a.(*acme.ACMEIssuer), b.(*v1.ACMEIssuer), scope) }); err != nil { @@ -1210,6 +1240,7 @@ func Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRF } func autoConvert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *v1.ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*acme.Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1235,6 +1266,7 @@ func Convert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRo } func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *v1.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*v1.Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1636,3 +1668,65 @@ func autoConvert_acme_OrderStatus_To_v1_OrderStatus(in *acme.OrderStatus, out *v func Convert_acme_OrderStatus_To_v1_OrderStatus(in *acme.OrderStatus, out *v1.OrderStatus, s conversion.Scope) error { return autoConvert_acme_OrderStatus_To_v1_OrderStatus(in, out, s) } + +func autoConvert_v1_Route53Auth_To_acme_Route53Auth(in *v1.Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*acme.Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_v1_Route53Auth_To_acme_Route53Auth is an autogenerated conversion function. +func Convert_v1_Route53Auth_To_acme_Route53Auth(in *v1.Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + return autoConvert_v1_Route53Auth_To_acme_Route53Auth(in, out, s) +} + +func autoConvert_acme_Route53Auth_To_v1_Route53Auth(in *acme.Route53Auth, out *v1.Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*v1.Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_acme_Route53Auth_To_v1_Route53Auth is an autogenerated conversion function. +func Convert_acme_Route53Auth_To_v1_Route53Auth(in *acme.Route53Auth, out *v1.Route53Auth, s conversion.Scope) error { + return autoConvert_acme_Route53Auth_To_v1_Route53Auth(in, out, s) +} + +func autoConvert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *v1.Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*acme.ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *v1.Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *v1.Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*v1.ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *v1.Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_v1_ServiceAccountRef_To_acme_ServiceAccountRef(in *v1.ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_v1_ServiceAccountRef_To_acme_ServiceAccountRef is an autogenerated conversion function. +func Convert_v1_ServiceAccountRef_To_acme_ServiceAccountRef(in *v1.ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + return autoConvert_v1_ServiceAccountRef_To_acme_ServiceAccountRef(in, out, s) +} + +func autoConvert_acme_ServiceAccountRef_To_v1_ServiceAccountRef(in *acme.ServiceAccountRef, out *v1.ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_acme_ServiceAccountRef_To_v1_ServiceAccountRef is an autogenerated conversion function. +func Convert_acme_ServiceAccountRef_To_v1_ServiceAccountRef(in *acme.ServiceAccountRef, out *v1.ServiceAccountRef, s conversion.Scope) error { + return autoConvert_acme_ServiceAccountRef_To_v1_ServiceAccountRef(in, out, s) +} diff --git a/internal/apis/acme/v1alpha2/types_issuer.go b/internal/apis/acme/v1alpha2/types_issuer.go index dc02f0eb6..6c9459d5f 100644 --- a/internal/apis/acme/v1alpha2/types_issuer.go +++ b/internal/apis/acme/v1alpha2/types_issuer.go @@ -475,6 +475,10 @@ type ACMEIssuerDNS01ProviderDigitalOcean struct { // ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 // configuration for AWS type ACMEIssuerDNS01ProviderRoute53 struct { + // Auth configures how cert-manager authenticates. + // +optional + Auth *Route53Auth `json:"auth,omitempty"` + // The AccessKeyID is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata // see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials // +optional @@ -503,6 +507,36 @@ type ACMEIssuerDNS01ProviderRoute53 struct { Region string `json:"region"` } +// Route53Auth is configuration used to authenticate with a Route53. +type Route53Auth struct { + // Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + // by passing a bound ServiceAccount token. + Kubernetes *Route53KubernetesAuth `json:"kubernetes"` +} + +// Route53KubernetesAuth is a configuration to authenticate against Route53 +// using a bound Kubernetes ServiceAccount token. +type Route53KubernetesAuth struct { + // A reference to a service account that will be used to request a bound + // token (also known as "projected token"). To use this field, you must + // configure an RBAC rule to let cert-manager request a token. + ServiceAccountRef *ServiceAccountRef `json:"serviceAccountRef"` +} + +// ServiceAccountRef is a service account used by cert-manager to request a +// token. The expiration of the token is also set by cert-manager to 10 minutes. +type ServiceAccountRef struct { + // Name of the ServiceAccount used to request a token. + Name string `json:"name"` + + // TokenAudiences is an optional list of audiences to include in the + // token passed to AWS. The default token consisting of the issuer's namespace + // and name is always included. + // If unset the audience defaults to `sts.amazonaws.com`. + // +optional + TokenAudiences []string `json:"audiences,omitempty"` +} + // ACMEIssuerDNS01ProviderAzureDNS is a structure containing the // configuration for Azure DNS type ACMEIssuerDNS01ProviderAzureDNS struct { diff --git a/internal/apis/acme/v1alpha2/zz_generated.conversion.go b/internal/apis/acme/v1alpha2/zz_generated.conversion.go index b61022286..22d2d5833 100644 --- a/internal/apis/acme/v1alpha2/zz_generated.conversion.go +++ b/internal/apis/acme/v1alpha2/zz_generated.conversion.go @@ -353,6 +353,36 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*Route53Auth)(nil), (*acme.Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_Route53Auth_To_acme_Route53Auth(a.(*Route53Auth), b.(*acme.Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53Auth)(nil), (*Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53Auth_To_v1alpha2_Route53Auth(a.(*acme.Route53Auth), b.(*Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*Route53KubernetesAuth)(nil), (*acme.Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(a.(*Route53KubernetesAuth), b.(*acme.Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53KubernetesAuth)(nil), (*Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53KubernetesAuth_To_v1alpha2_Route53KubernetesAuth(a.(*acme.Route53KubernetesAuth), b.(*Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ServiceAccountRef)(nil), (*acme.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_ServiceAccountRef_To_acme_ServiceAccountRef(a.(*ServiceAccountRef), b.(*acme.ServiceAccountRef), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.ServiceAccountRef)(nil), (*ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ServiceAccountRef_To_v1alpha2_ServiceAccountRef(a.(*acme.ServiceAccountRef), b.(*ServiceAccountRef), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*acme.ACMEIssuer)(nil), (*ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_acme_ACMEIssuer_To_v1alpha2_ACMEIssuer(a.(*acme.ACMEIssuer), b.(*ACMEIssuer), scope) }); err != nil { @@ -1209,6 +1239,7 @@ func Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha2_ACMEIssuerDNS01Prov } func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*acme.Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1234,6 +1265,7 @@ func Convert_v1alpha2_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01Prov } func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha2_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1615,3 +1647,65 @@ func autoConvert_acme_OrderStatus_To_v1alpha2_OrderStatus(in *acme.OrderStatus, func Convert_acme_OrderStatus_To_v1alpha2_OrderStatus(in *acme.OrderStatus, out *OrderStatus, s conversion.Scope) error { return autoConvert_acme_OrderStatus_To_v1alpha2_OrderStatus(in, out, s) } + +func autoConvert_v1alpha2_Route53Auth_To_acme_Route53Auth(in *Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*acme.Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_v1alpha2_Route53Auth_To_acme_Route53Auth is an autogenerated conversion function. +func Convert_v1alpha2_Route53Auth_To_acme_Route53Auth(in *Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + return autoConvert_v1alpha2_Route53Auth_To_acme_Route53Auth(in, out, s) +} + +func autoConvert_acme_Route53Auth_To_v1alpha2_Route53Auth(in *acme.Route53Auth, out *Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_acme_Route53Auth_To_v1alpha2_Route53Auth is an autogenerated conversion function. +func Convert_acme_Route53Auth_To_v1alpha2_Route53Auth(in *acme.Route53Auth, out *Route53Auth, s conversion.Scope) error { + return autoConvert_acme_Route53Auth_To_v1alpha2_Route53Auth(in, out, s) +} + +func autoConvert_v1alpha2_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*acme.ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_v1alpha2_Route53KubernetesAuth_To_acme_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_v1alpha2_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_v1alpha2_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_acme_Route53KubernetesAuth_To_v1alpha2_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_acme_Route53KubernetesAuth_To_v1alpha2_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_acme_Route53KubernetesAuth_To_v1alpha2_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_acme_Route53KubernetesAuth_To_v1alpha2_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_v1alpha2_ServiceAccountRef_To_acme_ServiceAccountRef(in *ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_v1alpha2_ServiceAccountRef_To_acme_ServiceAccountRef is an autogenerated conversion function. +func Convert_v1alpha2_ServiceAccountRef_To_acme_ServiceAccountRef(in *ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + return autoConvert_v1alpha2_ServiceAccountRef_To_acme_ServiceAccountRef(in, out, s) +} + +func autoConvert_acme_ServiceAccountRef_To_v1alpha2_ServiceAccountRef(in *acme.ServiceAccountRef, out *ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_acme_ServiceAccountRef_To_v1alpha2_ServiceAccountRef is an autogenerated conversion function. +func Convert_acme_ServiceAccountRef_To_v1alpha2_ServiceAccountRef(in *acme.ServiceAccountRef, out *ServiceAccountRef, s conversion.Scope) error { + return autoConvert_acme_ServiceAccountRef_To_v1alpha2_ServiceAccountRef(in, out, s) +} diff --git a/internal/apis/acme/v1alpha2/zz_generated.deepcopy.go b/internal/apis/acme/v1alpha2/zz_generated.deepcopy.go index a8b7073f1..da7fcb702 100644 --- a/internal/apis/acme/v1alpha2/zz_generated.deepcopy.go +++ b/internal/apis/acme/v1alpha2/zz_generated.deepcopy.go @@ -588,6 +588,11 @@ func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { *out = *in + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(Route53Auth) + (*in).DeepCopyInto(*out) + } if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID *out = new(metav1.SecretKeySelector) @@ -917,3 +922,66 @@ func (in *OrderStatus) DeepCopy() *OrderStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53Auth) DeepCopyInto(out *Route53Auth) { + *out = *in + if in.Kubernetes != nil { + in, out := &in.Kubernetes, &out.Kubernetes + *out = new(Route53KubernetesAuth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53Auth. +func (in *Route53Auth) DeepCopy() *Route53Auth { + if in == nil { + return nil + } + out := new(Route53Auth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53KubernetesAuth) DeepCopyInto(out *Route53KubernetesAuth) { + *out = *in + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(ServiceAccountRef) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53KubernetesAuth. +func (in *Route53KubernetesAuth) DeepCopy() *Route53KubernetesAuth { + if in == nil { + return nil + } + out := new(Route53KubernetesAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in + if in.TokenAudiences != nil { + in, out := &in.TokenAudiences, &out.TokenAudiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} diff --git a/internal/apis/acme/v1alpha3/types_issuer.go b/internal/apis/acme/v1alpha3/types_issuer.go index 40c775049..b92222e27 100644 --- a/internal/apis/acme/v1alpha3/types_issuer.go +++ b/internal/apis/acme/v1alpha3/types_issuer.go @@ -475,6 +475,10 @@ type ACMEIssuerDNS01ProviderDigitalOcean struct { // ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 // configuration for AWS type ACMEIssuerDNS01ProviderRoute53 struct { + // Auth configures how cert-manager authenticates. + // +optional + Auth *Route53Auth `json:"auth,omitempty"` + // The AccessKeyID is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata // see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials // +optional @@ -503,6 +507,36 @@ type ACMEIssuerDNS01ProviderRoute53 struct { Region string `json:"region"` } +// Route53Auth is configuration used to authenticate with a Route53. +type Route53Auth struct { + // Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + // by passing a bound ServiceAccount token. + Kubernetes *Route53KubernetesAuth `json:"kubernetes"` +} + +// Route53KubernetesAuth is a configuration to authenticate against Route53 +// using a bound Kubernetes ServiceAccount token. +type Route53KubernetesAuth struct { + // A reference to a service account that will be used to request a bound + // token (also known as "projected token"). To use this field, you must + // configure an RBAC rule to let cert-manager request a token. + ServiceAccountRef *ServiceAccountRef `json:"serviceAccountRef"` +} + +// ServiceAccountRef is a service account used by cert-manager to request a +// token. The expiration of the token is also set by cert-manager to 10 minutes. +type ServiceAccountRef struct { + // Name of the ServiceAccount used to request a token. + Name string `json:"name"` + + // TokenAudiences is an optional list of audiences to include in the + // token passed to AWS. The default token consisting of the issuer's namespace + // and name is always included. + // If unset the audience defaults to `sts.amazonaws.com`. + // +optional + TokenAudiences []string `json:"audiences,omitempty"` +} + // ACMEIssuerDNS01ProviderAzureDNS is a structure containing the // configuration for Azure DNS type ACMEIssuerDNS01ProviderAzureDNS struct { diff --git a/internal/apis/acme/v1alpha3/zz_generated.conversion.go b/internal/apis/acme/v1alpha3/zz_generated.conversion.go index 9bec71598..e3525c8a9 100644 --- a/internal/apis/acme/v1alpha3/zz_generated.conversion.go +++ b/internal/apis/acme/v1alpha3/zz_generated.conversion.go @@ -353,6 +353,36 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*Route53Auth)(nil), (*acme.Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_Route53Auth_To_acme_Route53Auth(a.(*Route53Auth), b.(*acme.Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53Auth)(nil), (*Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53Auth_To_v1alpha3_Route53Auth(a.(*acme.Route53Auth), b.(*Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*Route53KubernetesAuth)(nil), (*acme.Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(a.(*Route53KubernetesAuth), b.(*acme.Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53KubernetesAuth)(nil), (*Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53KubernetesAuth_To_v1alpha3_Route53KubernetesAuth(a.(*acme.Route53KubernetesAuth), b.(*Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ServiceAccountRef)(nil), (*acme.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_ServiceAccountRef_To_acme_ServiceAccountRef(a.(*ServiceAccountRef), b.(*acme.ServiceAccountRef), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.ServiceAccountRef)(nil), (*ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ServiceAccountRef_To_v1alpha3_ServiceAccountRef(a.(*acme.ServiceAccountRef), b.(*ServiceAccountRef), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*acme.ACMEIssuer)(nil), (*ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_acme_ACMEIssuer_To_v1alpha3_ACMEIssuer(a.(*acme.ACMEIssuer), b.(*ACMEIssuer), scope) }); err != nil { @@ -1209,6 +1239,7 @@ func Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha3_ACMEIssuerDNS01Prov } func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*acme.Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1234,6 +1265,7 @@ func Convert_v1alpha3_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01Prov } func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha3_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1615,3 +1647,65 @@ func autoConvert_acme_OrderStatus_To_v1alpha3_OrderStatus(in *acme.OrderStatus, func Convert_acme_OrderStatus_To_v1alpha3_OrderStatus(in *acme.OrderStatus, out *OrderStatus, s conversion.Scope) error { return autoConvert_acme_OrderStatus_To_v1alpha3_OrderStatus(in, out, s) } + +func autoConvert_v1alpha3_Route53Auth_To_acme_Route53Auth(in *Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*acme.Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_v1alpha3_Route53Auth_To_acme_Route53Auth is an autogenerated conversion function. +func Convert_v1alpha3_Route53Auth_To_acme_Route53Auth(in *Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + return autoConvert_v1alpha3_Route53Auth_To_acme_Route53Auth(in, out, s) +} + +func autoConvert_acme_Route53Auth_To_v1alpha3_Route53Auth(in *acme.Route53Auth, out *Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_acme_Route53Auth_To_v1alpha3_Route53Auth is an autogenerated conversion function. +func Convert_acme_Route53Auth_To_v1alpha3_Route53Auth(in *acme.Route53Auth, out *Route53Auth, s conversion.Scope) error { + return autoConvert_acme_Route53Auth_To_v1alpha3_Route53Auth(in, out, s) +} + +func autoConvert_v1alpha3_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*acme.ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_v1alpha3_Route53KubernetesAuth_To_acme_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_v1alpha3_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_v1alpha3_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_acme_Route53KubernetesAuth_To_v1alpha3_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_acme_Route53KubernetesAuth_To_v1alpha3_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_acme_Route53KubernetesAuth_To_v1alpha3_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_acme_Route53KubernetesAuth_To_v1alpha3_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_v1alpha3_ServiceAccountRef_To_acme_ServiceAccountRef(in *ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_v1alpha3_ServiceAccountRef_To_acme_ServiceAccountRef is an autogenerated conversion function. +func Convert_v1alpha3_ServiceAccountRef_To_acme_ServiceAccountRef(in *ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + return autoConvert_v1alpha3_ServiceAccountRef_To_acme_ServiceAccountRef(in, out, s) +} + +func autoConvert_acme_ServiceAccountRef_To_v1alpha3_ServiceAccountRef(in *acme.ServiceAccountRef, out *ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_acme_ServiceAccountRef_To_v1alpha3_ServiceAccountRef is an autogenerated conversion function. +func Convert_acme_ServiceAccountRef_To_v1alpha3_ServiceAccountRef(in *acme.ServiceAccountRef, out *ServiceAccountRef, s conversion.Scope) error { + return autoConvert_acme_ServiceAccountRef_To_v1alpha3_ServiceAccountRef(in, out, s) +} diff --git a/internal/apis/acme/v1alpha3/zz_generated.deepcopy.go b/internal/apis/acme/v1alpha3/zz_generated.deepcopy.go index ab0decbcc..80ef90d67 100644 --- a/internal/apis/acme/v1alpha3/zz_generated.deepcopy.go +++ b/internal/apis/acme/v1alpha3/zz_generated.deepcopy.go @@ -588,6 +588,11 @@ func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { *out = *in + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(Route53Auth) + (*in).DeepCopyInto(*out) + } if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID *out = new(metav1.SecretKeySelector) @@ -917,3 +922,66 @@ func (in *OrderStatus) DeepCopy() *OrderStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53Auth) DeepCopyInto(out *Route53Auth) { + *out = *in + if in.Kubernetes != nil { + in, out := &in.Kubernetes, &out.Kubernetes + *out = new(Route53KubernetesAuth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53Auth. +func (in *Route53Auth) DeepCopy() *Route53Auth { + if in == nil { + return nil + } + out := new(Route53Auth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53KubernetesAuth) DeepCopyInto(out *Route53KubernetesAuth) { + *out = *in + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(ServiceAccountRef) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53KubernetesAuth. +func (in *Route53KubernetesAuth) DeepCopy() *Route53KubernetesAuth { + if in == nil { + return nil + } + out := new(Route53KubernetesAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in + if in.TokenAudiences != nil { + in, out := &in.TokenAudiences, &out.TokenAudiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} diff --git a/internal/apis/acme/v1beta1/types_issuer.go b/internal/apis/acme/v1beta1/types_issuer.go index 7ffaa9fc6..4c164a25c 100644 --- a/internal/apis/acme/v1beta1/types_issuer.go +++ b/internal/apis/acme/v1beta1/types_issuer.go @@ -474,6 +474,10 @@ type ACMEIssuerDNS01ProviderDigitalOcean struct { // ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 // configuration for AWS type ACMEIssuerDNS01ProviderRoute53 struct { + // Auth configures how cert-manager authenticates. + // +optional + Auth *Route53Auth `json:"auth,omitempty"` + // The AccessKeyID is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata // see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials // +optional @@ -502,6 +506,36 @@ type ACMEIssuerDNS01ProviderRoute53 struct { Region string `json:"region"` } +// Route53Auth is configuration used to authenticate with a Route53. +type Route53Auth struct { + // Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + // by passing a bound ServiceAccount token. + Kubernetes *Route53KubernetesAuth `json:"kubernetes"` +} + +// Route53KubernetesAuth is a configuration to authenticate against Route53 +// using a bound Kubernetes ServiceAccount token. +type Route53KubernetesAuth struct { + // A reference to a service account that will be used to request a bound + // token (also known as "projected token"). To use this field, you must + // configure an RBAC rule to let cert-manager request a token. + ServiceAccountRef *ServiceAccountRef `json:"serviceAccountRef"` +} + +// ServiceAccountRef is a service account used by cert-manager to request a +// token. The expiration of the token is also set by cert-manager to 10 minutes. +type ServiceAccountRef struct { + // Name of the ServiceAccount used to request a token. + Name string `json:"name"` + + // TokenAudiences is an optional list of audiences to include in the + // token passed to AWS. The default token consisting of the issuer's namespace + // and name is always included. + // If unset the audience defaults to `sts.amazonaws.com`. + // +optional + TokenAudiences []string `json:"audiences,omitempty"` +} + // ACMEIssuerDNS01ProviderAzureDNS is a structure containing the // configuration for Azure DNS type ACMEIssuerDNS01ProviderAzureDNS struct { diff --git a/internal/apis/acme/v1beta1/zz_generated.conversion.go b/internal/apis/acme/v1beta1/zz_generated.conversion.go index 9e9248f98..172237648 100644 --- a/internal/apis/acme/v1beta1/zz_generated.conversion.go +++ b/internal/apis/acme/v1beta1/zz_generated.conversion.go @@ -373,6 +373,36 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*Route53Auth)(nil), (*acme.Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Route53Auth_To_acme_Route53Auth(a.(*Route53Auth), b.(*acme.Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53Auth)(nil), (*Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53Auth_To_v1beta1_Route53Auth(a.(*acme.Route53Auth), b.(*Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*Route53KubernetesAuth)(nil), (*acme.Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(a.(*Route53KubernetesAuth), b.(*acme.Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53KubernetesAuth)(nil), (*Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53KubernetesAuth_To_v1beta1_Route53KubernetesAuth(a.(*acme.Route53KubernetesAuth), b.(*Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ServiceAccountRef)(nil), (*acme.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_ServiceAccountRef_To_acme_ServiceAccountRef(a.(*ServiceAccountRef), b.(*acme.ServiceAccountRef), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.ServiceAccountRef)(nil), (*ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ServiceAccountRef_To_v1beta1_ServiceAccountRef(a.(*acme.ServiceAccountRef), b.(*ServiceAccountRef), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*acme.ACMEIssuer)(nil), (*ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_acme_ACMEIssuer_To_v1beta1_ACMEIssuer(a.(*acme.ACMEIssuer), b.(*ACMEIssuer), scope) }); err != nil { @@ -1209,6 +1239,7 @@ func Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1beta1_ACMEIssuerDNS01Provi } func autoConvert_v1beta1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*acme.Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1234,6 +1265,7 @@ func Convert_v1beta1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01Provi } func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1beta1_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1635,3 +1667,65 @@ func autoConvert_acme_OrderStatus_To_v1beta1_OrderStatus(in *acme.OrderStatus, o func Convert_acme_OrderStatus_To_v1beta1_OrderStatus(in *acme.OrderStatus, out *OrderStatus, s conversion.Scope) error { return autoConvert_acme_OrderStatus_To_v1beta1_OrderStatus(in, out, s) } + +func autoConvert_v1beta1_Route53Auth_To_acme_Route53Auth(in *Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*acme.Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_v1beta1_Route53Auth_To_acme_Route53Auth is an autogenerated conversion function. +func Convert_v1beta1_Route53Auth_To_acme_Route53Auth(in *Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + return autoConvert_v1beta1_Route53Auth_To_acme_Route53Auth(in, out, s) +} + +func autoConvert_acme_Route53Auth_To_v1beta1_Route53Auth(in *acme.Route53Auth, out *Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_acme_Route53Auth_To_v1beta1_Route53Auth is an autogenerated conversion function. +func Convert_acme_Route53Auth_To_v1beta1_Route53Auth(in *acme.Route53Auth, out *Route53Auth, s conversion.Scope) error { + return autoConvert_acme_Route53Auth_To_v1beta1_Route53Auth(in, out, s) +} + +func autoConvert_v1beta1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*acme.ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_v1beta1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_v1beta1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_v1beta1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_acme_Route53KubernetesAuth_To_v1beta1_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_acme_Route53KubernetesAuth_To_v1beta1_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_acme_Route53KubernetesAuth_To_v1beta1_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_acme_Route53KubernetesAuth_To_v1beta1_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_v1beta1_ServiceAccountRef_To_acme_ServiceAccountRef(in *ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_v1beta1_ServiceAccountRef_To_acme_ServiceAccountRef is an autogenerated conversion function. +func Convert_v1beta1_ServiceAccountRef_To_acme_ServiceAccountRef(in *ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + return autoConvert_v1beta1_ServiceAccountRef_To_acme_ServiceAccountRef(in, out, s) +} + +func autoConvert_acme_ServiceAccountRef_To_v1beta1_ServiceAccountRef(in *acme.ServiceAccountRef, out *ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_acme_ServiceAccountRef_To_v1beta1_ServiceAccountRef is an autogenerated conversion function. +func Convert_acme_ServiceAccountRef_To_v1beta1_ServiceAccountRef(in *acme.ServiceAccountRef, out *ServiceAccountRef, s conversion.Scope) error { + return autoConvert_acme_ServiceAccountRef_To_v1beta1_ServiceAccountRef(in, out, s) +} diff --git a/internal/apis/acme/v1beta1/zz_generated.deepcopy.go b/internal/apis/acme/v1beta1/zz_generated.deepcopy.go index a1aaba007..07e73a076 100644 --- a/internal/apis/acme/v1beta1/zz_generated.deepcopy.go +++ b/internal/apis/acme/v1beta1/zz_generated.deepcopy.go @@ -588,6 +588,11 @@ func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { *out = *in + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(Route53Auth) + (*in).DeepCopyInto(*out) + } if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID *out = new(metav1.SecretKeySelector) @@ -917,3 +922,66 @@ func (in *OrderStatus) DeepCopy() *OrderStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53Auth) DeepCopyInto(out *Route53Auth) { + *out = *in + if in.Kubernetes != nil { + in, out := &in.Kubernetes, &out.Kubernetes + *out = new(Route53KubernetesAuth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53Auth. +func (in *Route53Auth) DeepCopy() *Route53Auth { + if in == nil { + return nil + } + out := new(Route53Auth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53KubernetesAuth) DeepCopyInto(out *Route53KubernetesAuth) { + *out = *in + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(ServiceAccountRef) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53KubernetesAuth. +func (in *Route53KubernetesAuth) DeepCopy() *Route53KubernetesAuth { + if in == nil { + return nil + } + out := new(Route53KubernetesAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in + if in.TokenAudiences != nil { + in, out := &in.TokenAudiences, &out.TokenAudiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} diff --git a/internal/apis/acme/zz_generated.deepcopy.go b/internal/apis/acme/zz_generated.deepcopy.go index d0598cf13..c798aecce 100644 --- a/internal/apis/acme/zz_generated.deepcopy.go +++ b/internal/apis/acme/zz_generated.deepcopy.go @@ -588,6 +588,11 @@ func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { *out = *in + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(Route53Auth) + (*in).DeepCopyInto(*out) + } if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID *out = new(meta.SecretKeySelector) @@ -917,3 +922,66 @@ func (in *OrderStatus) DeepCopy() *OrderStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53Auth) DeepCopyInto(out *Route53Auth) { + *out = *in + if in.Kubernetes != nil { + in, out := &in.Kubernetes, &out.Kubernetes + *out = new(Route53KubernetesAuth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53Auth. +func (in *Route53Auth) DeepCopy() *Route53Auth { + if in == nil { + return nil + } + out := new(Route53Auth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53KubernetesAuth) DeepCopyInto(out *Route53KubernetesAuth) { + *out = *in + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(ServiceAccountRef) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53KubernetesAuth. +func (in *Route53KubernetesAuth) DeepCopy() *Route53KubernetesAuth { + if in == nil { + return nil + } + out := new(Route53KubernetesAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in + if in.TokenAudiences != nil { + in, out := &in.TokenAudiences, &out.TokenAudiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/acme/v1/types_issuer.go b/pkg/apis/acme/v1/types_issuer.go index 9e4676fae..0da9444f4 100644 --- a/pkg/apis/acme/v1/types_issuer.go +++ b/pkg/apis/acme/v1/types_issuer.go @@ -479,6 +479,10 @@ type ACMEIssuerDNS01ProviderDigitalOcean struct { // ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 // configuration for AWS type ACMEIssuerDNS01ProviderRoute53 struct { + // Auth configures how cert-manager authenticates. + // +optional + Auth *Route53Auth `json:"auth,omitempty"` + // The AccessKeyID is used for authentication. // Cannot be set when SecretAccessKeyID is set. // If neither the Access Key nor Key ID are set, we fall-back to using env @@ -516,6 +520,36 @@ type ACMEIssuerDNS01ProviderRoute53 struct { Region string `json:"region"` } +// Route53Auth is configuration used to authenticate with a Route53. +type Route53Auth struct { + // Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + // by passing a bound ServiceAccount token. + Kubernetes *Route53KubernetesAuth `json:"kubernetes"` +} + +// Route53KubernetesAuth is a configuration to authenticate against Route53 +// using a bound Kubernetes ServiceAccount token. +type Route53KubernetesAuth struct { + // A reference to a service account that will be used to request a bound + // token (also known as "projected token"). To use this field, you must + // configure an RBAC rule to let cert-manager request a token. + ServiceAccountRef *ServiceAccountRef `json:"serviceAccountRef"` +} + +// ServiceAccountRef is a service account used by cert-manager to request a +// token. The expiration of the token is also set by cert-manager to 10 minutes. +type ServiceAccountRef struct { + // Name of the ServiceAccount used to request a token. + Name string `json:"name"` + + // TokenAudiences is an optional list of audiences to include in the + // token passed to AWS. The default token consisting of the issuer's namespace + // and name is always included. + // If unset the audience defaults to `sts.amazonaws.com`. + // +optional + TokenAudiences []string `json:"audiences,omitempty"` +} + // ACMEIssuerDNS01ProviderAzureDNS is a structure containing the // configuration for Azure DNS type ACMEIssuerDNS01ProviderAzureDNS struct { diff --git a/pkg/apis/acme/v1/zz_generated.deepcopy.go b/pkg/apis/acme/v1/zz_generated.deepcopy.go index 86e91f7b3..655be302a 100644 --- a/pkg/apis/acme/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acme/v1/zz_generated.deepcopy.go @@ -588,6 +588,11 @@ func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { *out = *in + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(Route53Auth) + (*in).DeepCopyInto(*out) + } if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID *out = new(metav1.SecretKeySelector) @@ -917,3 +922,66 @@ func (in *OrderStatus) DeepCopy() *OrderStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53Auth) DeepCopyInto(out *Route53Auth) { + *out = *in + if in.Kubernetes != nil { + in, out := &in.Kubernetes, &out.Kubernetes + *out = new(Route53KubernetesAuth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53Auth. +func (in *Route53Auth) DeepCopy() *Route53Auth { + if in == nil { + return nil + } + out := new(Route53Auth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53KubernetesAuth) DeepCopyInto(out *Route53KubernetesAuth) { + *out = *in + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(ServiceAccountRef) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53KubernetesAuth. +func (in *Route53KubernetesAuth) DeepCopy() *Route53KubernetesAuth { + if in == nil { + return nil + } + out := new(Route53KubernetesAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in + if in.TokenAudiences != nil { + in, out := &in.TokenAudiences, &out.TokenAudiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/issuer/acme/dns/dns.go b/pkg/issuer/acme/dns/dns.go index 2a1dd9294..239a7302e 100644 --- a/pkg/issuer/acme/dns/dns.go +++ b/pkg/issuer/acme/dns/dns.go @@ -23,7 +23,10 @@ import ( "strings" "time" + authv1 "k8s.io/api/authentication/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" internalinformers "github.com/cert-manager/cert-manager/internal/informers" "github.com/cert-manager/cert-manager/pkg/acme/webhook" @@ -58,7 +61,7 @@ type solver interface { type dnsProviderConstructors struct { cloudDNS func(ctx context.Context, project string, serviceAccount []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*clouddns.DNSProvider, error) cloudFlare func(email, apikey, apiToken string, dns01Nameservers []string, userAgent string) (*cloudflare.DNSProvider, error) - route53 func(ctx context.Context, accessKey, secretKey, hostedZoneID, region, role string, ambient bool, dns01Nameservers []string, userAgent string) (*route53.DNSProvider, error) + route53 func(ctx context.Context, accessKey, secretKey, hostedZoneID, region, role, webIdentityToken string, ambient bool, dns01Nameservers []string, userAgent string) (*route53.DNSProvider, error) azureDNS func(environment, clientID, clientSecret, subscriptionID, tenantID, resourceGroupName, hostedZoneName string, dns01Nameservers []string, ambient bool, managedIdentity *cmacme.AzureManagedIdentity) (*azuredns.DNSProvider, error) acmeDNS func(host string, accountJson []byte, dns01Nameservers []string) (*acmedns.DNSProvider, error) digitalOcean func(token string, dns01Nameservers []string, userAgent string) (*digitalocean.DNSProvider, error) @@ -343,6 +346,25 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1.GenericIssuer secretAccessKey = string(secretAccessKeyBytes) } + webIdentityToken := "" + if providerConfig.Route53.Auth != nil && providerConfig.Route53.Auth.Kubernetes != nil && providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef != nil { + if providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef.Name == "" { + return nil, nil, fmt.Errorf("service account name is required for Kubernetes auth") + } + + audiences := []string{"sts.amazonaws.com"} + if len(providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef.TokenAudiences) != 0 { + audiences = providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef.TokenAudiences + } + + jwt, err := s.createToken(ctx, resourceNamespace, providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef.Name, audiences) + if err != nil { + return nil, nil, fmt.Errorf("error getting service account token: %w", err) + } + + webIdentityToken = jwt + } + impl, err = s.dnsProviderConstructors.route53( ctx, secretAccessKeyID, @@ -350,6 +372,7 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1.GenericIssuer providerConfig.Route53.HostedZoneID, providerConfig.Route53.Region, providerConfig.Route53.Role, + webIdentityToken, canUseAmbientCredentials, s.DNS01Nameservers, s.RESTConfig.UserAgent, @@ -536,3 +559,17 @@ func (s *Solver) loadSecretData(selector *cmmeta.SecretKeySelector, ns string) ( return nil, fmt.Errorf("no key %q in secret %q", selector.Key, ns+"/"+selector.Name) } + +func (s *Solver) createToken(ctx context.Context, ns, serviceAccount string, audiences []string) (string, error) { + tokenrequest, err := s.Client.CoreV1().ServiceAccounts(ns).CreateToken(ctx, serviceAccount, &authv1.TokenRequest{ + Spec: authv1.TokenRequestSpec{ + Audiences: audiences, + ExpirationSeconds: ptr.To(int64(600)), + }, + }, metav1.CreateOptions{}) + if err != nil { + return "", fmt.Errorf("failed to request token for %s/%s: %w", ns, serviceAccount, err) + } + + return tokenrequest.Status.Token, nil +} diff --git a/pkg/issuer/acme/dns/dns_test.go b/pkg/issuer/acme/dns/dns_test.go index 48562e535..81aba1513 100644 --- a/pkg/issuer/acme/dns/dns_test.go +++ b/pkg/issuer/acme/dns/dns_test.go @@ -395,7 +395,7 @@ func TestRoute53TrimCreds(t *testing.T) { expectedR53Call := []fakeDNSProviderCall{ { name: "route53", - args: []interface{}{"test_with_spaces", "AKIENDINNEWLINE", "", "us-west-2", "", false, util.RecursiveNameservers}, + args: []interface{}{"test_with_spaces", "AKIENDINNEWLINE", "", "us-west-2", "", "", false, util.RecursiveNameservers}, }, } @@ -453,7 +453,7 @@ func TestRoute53SecretAccessKey(t *testing.T) { expectedR53Call := []fakeDNSProviderCall{ { name: "route53", - args: []interface{}{"AWSACCESSKEYID", "AKIENDINNEWLINE", "", "us-west-2", "", false, util.RecursiveNameservers}, + args: []interface{}{"AWSACCESSKEYID", "AKIENDINNEWLINE", "", "us-west-2", "", "", false, util.RecursiveNameservers}, }, } @@ -501,7 +501,7 @@ func TestRoute53AmbientCreds(t *testing.T) { result{ expectedCall: &fakeDNSProviderCall{ name: "route53", - args: []interface{}{"", "", "", "us-west-2", "", true, util.RecursiveNameservers}, + args: []interface{}{"", "", "", "us-west-2", "", "", true, util.RecursiveNameservers}, }, }, }, @@ -534,7 +534,7 @@ func TestRoute53AmbientCreds(t *testing.T) { result{ expectedCall: &fakeDNSProviderCall{ name: "route53", - args: []interface{}{"", "", "", "us-west-2", "", false, util.RecursiveNameservers}, + args: []interface{}{"", "", "", "us-west-2", "", "", false, util.RecursiveNameservers}, }, }, }, @@ -598,7 +598,7 @@ func TestRoute53AssumeRole(t *testing.T) { result{ expectedCall: &fakeDNSProviderCall{ name: "route53", - args: []interface{}{"", "", "", "us-west-2", "my-role", true, util.RecursiveNameservers}, + args: []interface{}{"", "", "", "us-west-2", "my-role", "", true, util.RecursiveNameservers}, }, }, }, @@ -632,7 +632,7 @@ func TestRoute53AssumeRole(t *testing.T) { result{ expectedCall: &fakeDNSProviderCall{ name: "route53", - args: []interface{}{"", "", "", "us-west-2", "my-other-role", false, util.RecursiveNameservers}, + args: []interface{}{"", "", "", "us-west-2", "my-other-role", "", false, util.RecursiveNameservers}, }, }, }, diff --git a/pkg/issuer/acme/dns/route53/route53.go b/pkg/issuer/acme/dns/route53/route53.go index 17cca403a..36e375af4 100644 --- a/pkg/issuer/acme/dns/route53/route53.go +++ b/pkg/issuer/acme/dns/route53/route53.go @@ -47,40 +47,48 @@ type DNSProvider struct { } type sessionProvider struct { - AccessKeyID string - SecretAccessKey string - Ambient bool - Region string - Role string - StsProvider func(aws.Config) StsClient - log logr.Logger - userAgent string + AccessKeyID string + SecretAccessKey string + Ambient bool + Region string + Role string + WebIdentityToken string + StsProvider func(aws.Config) StsClient + log logr.Logger + userAgent string } type StsClient interface { AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) + AssumeRoleWithWebIdentity(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) } func (d *sessionProvider) GetSession(ctx context.Context) (aws.Config, error) { - if d.AccessKeyID == "" && d.SecretAccessKey == "" { - if !d.Ambient { + switch { + case d.Role == "" && d.WebIdentityToken != "": + return aws.Config{}, fmt.Errorf("unable to construct route53 provider: role must be set when web identity token is set") + case d.AccessKeyID == "" && d.SecretAccessKey == "": + if !d.Ambient && d.WebIdentityToken == "" { return aws.Config{}, fmt.Errorf("unable to construct route53 provider: empty credentials; perhaps you meant to enable ambient credentials?") } - } else if d.AccessKeyID == "" || d.SecretAccessKey == "" { + case d.AccessKeyID == "" || d.SecretAccessKey == "": // It's always an error to set one of those but not the other return aws.Config{}, fmt.Errorf("unable to construct route53 provider: only one of access and secret key was provided") } - useAmbientCredentials := d.Ambient && (d.AccessKeyID == "" && d.SecretAccessKey == "") + useAmbientCredentials := d.Ambient && (d.AccessKeyID == "" && d.SecretAccessKey == "") && d.WebIdentityToken == "" var optFns []func(*config.LoadOptions) error - - if useAmbientCredentials { + switch { + case d.Role != "" && d.WebIdentityToken != "": + d.log.V(logf.DebugLevel).Info("using assume role with web identity") + optFns = append(optFns, config.WithRegion(d.Region)) + case useAmbientCredentials: d.log.V(logf.DebugLevel).Info("using ambient credentials") // Leaving credentials unset results in a default credential chain being // used; this chain is a reasonable default for getting ambient creds. // https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials - } else { + default: d.log.V(logf.DebugLevel).Info("not using ambient credentials") optFns = append(optFns, config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(d.AccessKeyID, d.SecretAccessKey, ""))) } @@ -90,7 +98,7 @@ func (d *sessionProvider) GetSession(ctx context.Context) (aws.Config, error) { return aws.Config{}, fmt.Errorf("unable to create aws config: %s", err) } - if d.Role != "" { + if d.Role != "" && d.WebIdentityToken == "" { d.log.V(logf.DebugLevel).WithValues("role", d.Role).Info("assuming role") stsSvc := d.StsProvider(cfg) result, err := stsSvc.AssumeRole(ctx, &sts.AssumeRoleInput{ @@ -108,6 +116,26 @@ func (d *sessionProvider) GetSession(ctx context.Context) (aws.Config, error) { ) } + if d.Role != "" && d.WebIdentityToken != "" { + d.log.V(logf.DebugLevel).WithValues("role", d.Role).Info("assuming role with web identity") + + stsSvc := d.StsProvider(cfg) + result, err := stsSvc.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityInput{ + RoleArn: aws.String(d.Role), + RoleSessionName: aws.String("cert-manager"), + WebIdentityToken: aws.String(d.WebIdentityToken), + }) + if err != nil { + return aws.Config{}, fmt.Errorf("unable to assume role with web identity: %s", err) + } + + cfg.Credentials = credentials.NewStaticCredentialsProvider( + *result.Credentials.AccessKeyId, + *result.Credentials.SecretAccessKey, + *result.Credentials.SessionToken, + ) + } + // If ambient credentials aren't permitted, always set the region, even if to // empty string, to avoid it falling back on the environment. // this has to be set after session is constructed @@ -122,16 +150,17 @@ func (d *sessionProvider) GetSession(ctx context.Context) (aws.Config, error) { return cfg, nil } -func newSessionProvider(accessKeyID, secretAccessKey, region, role string, ambient bool, userAgent string) *sessionProvider { +func newSessionProvider(accessKeyID, secretAccessKey, region, role string, webIdentityToken string, ambient bool, userAgent string) *sessionProvider { return &sessionProvider{ - AccessKeyID: accessKeyID, - SecretAccessKey: secretAccessKey, - Ambient: ambient, - Region: region, - Role: role, - StsProvider: defaultSTSProvider, - log: logf.Log.WithName("route53-session-provider"), - userAgent: userAgent, + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + Ambient: ambient, + Region: region, + Role: role, + WebIdentityToken: webIdentityToken, + StsProvider: defaultSTSProvider, + log: logf.Log.WithName("route53-session-provider"), + userAgent: userAgent, } } @@ -144,12 +173,12 @@ func defaultSTSProvider(cfg aws.Config) StsClient { // unset and the 'ambient' option is set, credentials from the environment. func NewDNSProvider( ctx context.Context, - accessKeyID, secretAccessKey, hostedZoneID, region, role string, + accessKeyID, secretAccessKey, hostedZoneID, region, role, webIdentityToken string, ambient bool, dns01Nameservers []string, userAgent string, ) (*DNSProvider, error) { - provider := newSessionProvider(accessKeyID, secretAccessKey, region, role, ambient, userAgent) + provider := newSessionProvider(accessKeyID, secretAccessKey, region, role, webIdentityToken, ambient, userAgent) cfg, err := provider.GetSession(ctx) if err != nil { diff --git a/pkg/issuer/acme/dns/route53/route53_test.go b/pkg/issuer/acme/dns/route53/route53_test.go index 232987cfb..7f563dbe5 100644 --- a/pkg/issuer/acme/dns/route53/route53_test.go +++ b/pkg/issuer/acme/dns/route53/route53_test.go @@ -32,6 +32,8 @@ import ( logf "github.com/cert-manager/cert-manager/pkg/logs" ) +const jwt string = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJzdHMuYW1hem9uYXdzLmNvbSIsImV4cCI6MTc0MTg4NzYwOCwiaWF0IjoxNzEwMzUxNjM4LCJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwibmFtZSI6IkpvaG4gRG9lIiwic3ViIjoiMTIzNDU2Nzg5MCJ9.SfuV3SW-vEdV-tLFIr2PK2DnN6QYmozygav5OeoH36Q" + func makeRoute53Provider(ts *httptest.Server) (*DNSProvider, error) { cfg, err := config.LoadDefaultConfig( context.TODO(), @@ -58,7 +60,7 @@ func TestAmbientCredentialsFromEnv(t *testing.T) { t.Setenv("AWS_SECRET_ACCESS_KEY", "123") t.Setenv("AWS_REGION", "us-east-1") - provider, err := NewDNSProvider(context.TODO(), "", "", "", "", "", true, util.RecursiveNameservers, "cert-manager-test") + provider, err := NewDNSProvider(context.TODO(), "", "", "", "", "", "", true, util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err, "Expected no error constructing DNSProvider") _, err = provider.client.Options().Credentials.Retrieve(context.TODO()) @@ -72,14 +74,14 @@ func TestNoCredentialsFromEnv(t *testing.T) { t.Setenv("AWS_SECRET_ACCESS_KEY", "123") t.Setenv("AWS_REGION", "us-east-1") - _, err := NewDNSProvider(context.TODO(), "", "", "", "", "", false, util.RecursiveNameservers, "cert-manager-test") + _, err := NewDNSProvider(context.TODO(), "", "", "", "", "", "", false, util.RecursiveNameservers, "cert-manager-test") assert.Error(t, err, "Expected error constructing DNSProvider with no credentials and not ambient") } func TestAmbientRegionFromEnv(t *testing.T) { t.Setenv("AWS_REGION", "us-east-1") - provider, err := NewDNSProvider(context.TODO(), "", "", "", "", "", true, util.RecursiveNameservers, "cert-manager-test") + provider, err := NewDNSProvider(context.TODO(), "", "", "", "", "", "", true, util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err, "Expected no error constructing DNSProvider") assert.Equal(t, "us-east-1", provider.client.Options().Region, "Expected Region to be set from environment") @@ -88,7 +90,7 @@ func TestAmbientRegionFromEnv(t *testing.T) { func TestNoRegionFromEnv(t *testing.T) { t.Setenv("AWS_REGION", "us-east-1") - provider, err := NewDNSProvider(context.TODO(), "marx", "swordfish", "", "", "", false, util.RecursiveNameservers, "cert-manager-test") + provider, err := NewDNSProvider(context.TODO(), "marx", "swordfish", "", "", "", "", false, util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err, "Expected no error constructing DNSProvider") assert.Equal(t, "", provider.client.Options().Region, "Expected Region to not be set from environment") @@ -142,16 +144,17 @@ func TestAssumeRole(t *testing.T) { SessionToken: aws.String("my-token"), } cases := []struct { - name string - ambient bool - role string - expErr bool - expCreds *ststypes.Credentials - expRegion string - key string - secret string - region string - mockSTS *mockSTS + name string + ambient bool + role string + webIdentityToken string + expErr bool + expCreds *ststypes.Credentials + expRegion string + key string + secret string + region string + mockSTS *mockSTS }{ { name: "should assume role w/ ambient creds", @@ -224,17 +227,43 @@ func TestAssumeRole(t *testing.T) { }, }, }, + { + name: "should assume role with web identity", + role: "my-role", + webIdentityToken: jwt, + expErr: false, + expCreds: creds, + mockSTS: &mockSTS{ + AssumeRoleWithWebIdentityFn: func(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) { + return &sts.AssumeRoleWithWebIdentityOutput{ + Credentials: creds, + }, nil + }, + }, + }, + { + name: "require role when using assume role with web identity", + webIdentityToken: jwt, + expErr: true, + expCreds: nil, + mockSTS: &mockSTS{ + AssumeRoleWithWebIdentityFn: func(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) { + return nil, fmt.Errorf("error assuming mock role with web identity") + }, + }, + }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { provider := makeMockSessionProvider(func(aws.Config) StsClient { return c.mockSTS - }, c.key, c.secret, c.region, c.role, c.ambient) + }, c.key, c.secret, c.region, c.role, c.webIdentityToken, c.ambient) cfg, err := provider.GetSession(context.TODO()) if c.expErr { assert.NotNil(t, err) } else { + assert.Nil(t, err) sessCreds, _ := cfg.Credentials.Retrieve(context.TODO()) assert.Equal(t, c.mockSTS.assumedRole, c.role) assert.Equal(t, *c.expCreds.SecretAccessKey, sessCreds.SecretAccessKey) @@ -246,8 +275,9 @@ func TestAssumeRole(t *testing.T) { } type mockSTS struct { - AssumeRoleFn func(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) - assumedRole string + AssumeRoleFn func(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) + AssumeRoleWithWebIdentityFn func(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) + assumedRole string } func (m *mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { @@ -259,19 +289,29 @@ func (m *mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, o return nil, nil } +func (m *mockSTS) AssumeRoleWithWebIdentity(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) { + if m.AssumeRoleWithWebIdentityFn != nil { + m.assumedRole = *params.RoleArn + return m.AssumeRoleWithWebIdentityFn(ctx, params, optFns...) + } + + return nil, nil +} + func makeMockSessionProvider( defaultSTSProvider func(aws.Config) StsClient, - accessKeyID, secretAccessKey, region, role string, + accessKeyID, secretAccessKey, region, role, webIdentityToken string, ambient bool, ) *sessionProvider { return &sessionProvider{ - AccessKeyID: accessKeyID, - SecretAccessKey: secretAccessKey, - Ambient: ambient, - Region: region, - Role: role, - StsProvider: defaultSTSProvider, - log: logf.Log.WithName("route53-session"), + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + Ambient: ambient, + Region: region, + Role: role, + WebIdentityToken: webIdentityToken, + StsProvider: defaultSTSProvider, + log: logf.Log.WithName("route53-session"), } } diff --git a/pkg/issuer/acme/dns/util_test.go b/pkg/issuer/acme/dns/util_test.go index 4f142526a..32872c157 100644 --- a/pkg/issuer/acme/dns/util_test.go +++ b/pkg/issuer/acme/dns/util_test.go @@ -140,8 +140,8 @@ func newFakeDNSProviders() *fakeDNSProviders { } return nil, nil }, - route53: func(ctx context.Context, accessKey, secretKey, hostedZoneID, region, role string, ambient bool, dns01Nameservers []string, userAgent string) (*route53.DNSProvider, error) { - f.call("route53", accessKey, secretKey, hostedZoneID, region, role, ambient, util.RecursiveNameservers) + route53: func(ctx context.Context, accessKey, secretKey, hostedZoneID, region, role, webIdentityToken string, ambient bool, dns01Nameservers []string, userAgent string) (*route53.DNSProvider, error) { + f.call("route53", accessKey, secretKey, hostedZoneID, region, role, webIdentityToken, ambient, util.RecursiveNameservers) return nil, nil }, azureDNS: func(environment, clientID, clientSecret, subscriptionID, tenantID, resourceGroupName, hostedZoneName string, dns01Nameservers []string, ambient bool, managedIdentity *cmacme.AzureManagedIdentity) (*azuredns.DNSProvider, error) {