Skip to content

Commit a10fc5d

Browse files
stefanmcshanek0da
authored andcommitted
Add eks pod identities
1 parent 867499a commit a10fc5d

19 files changed

+778
-5
lines changed

config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,6 +2949,35 @@ spec:
29492949
description: Partition is the AWS security partition being used. Defaults
29502950
to "aws"
29512951
type: string
2952+
podIdentityAssociations:
2953+
description: |-
2954+
PodIdentityAssociations represent Kubernetes Service Accounts mapping to AWS IAM Roles without IRSA, using EKS Pod Identity.
2955+
This requires using the AWS EKS Addon for Pod Identity.
2956+
items:
2957+
description: PodIdentityAssociation represents an association between
2958+
a Kubernetes Service Account in a namespace, and an AWS IAM role
2959+
which allows the service principal `pods.eks.amazonaws.com` in
2960+
its trust policy.
2961+
properties:
2962+
serviceAccountName:
2963+
description: ServiceAccountName is the name of the kubernetes
2964+
Service Account within the namespace
2965+
type: string
2966+
serviceAccountNamespace:
2967+
default: default
2968+
description: ServiceAccountNamespace is the kubernetes namespace,
2969+
which the kubernetes Service Account resides in. Defaults
2970+
to "default" namespace.
2971+
type: string
2972+
serviceAccountRoleARN:
2973+
description: RoleARN is the ARN of an IAM role which the Service
2974+
Account can assume.
2975+
type: string
2976+
required:
2977+
- serviceAccountName
2978+
- serviceAccountNamespace
2979+
type: object
2980+
type: array
29522981
region:
29532982
description: The AWS Region the cluster lives in.
29542983
type: string

config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanetemplates.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,35 @@ spec:
882882
description: Partition is the AWS security partition being
883883
used. Defaults to "aws"
884884
type: string
885+
podIdentityAssociations:
886+
description: |-
887+
PodIdentityAssociations represent Kubernetes Service Accounts mapping to AWS IAM Roles without IRSA, using EKS Pod Identity.
888+
This requires using the AWS EKS Addon for Pod Identity.
889+
items:
890+
description: PodIdentityAssociation represents an association
891+
between a Kubernetes Service Account in a namespace, and
892+
an AWS IAM role which allows the service principal `pods.eks.amazonaws.com`
893+
in its trust policy.
894+
properties:
895+
serviceAccountName:
896+
description: ServiceAccountName is the name of the kubernetes
897+
Service Account within the namespace
898+
type: string
899+
serviceAccountNamespace:
900+
default: default
901+
description: ServiceAccountNamespace is the kubernetes
902+
namespace, which the kubernetes Service Account resides
903+
in. Defaults to "default" namespace.
904+
type: string
905+
serviceAccountRoleARN:
906+
description: RoleARN is the ARN of an IAM role which
907+
the Service Account can assume.
908+
type: string
909+
required:
910+
- serviceAccountName
911+
- serviceAccountNamespace
912+
type: object
913+
type: array
885914
region:
886915
description: The AWS Region the cluster lives in.
887916
type: string

controlplane/eks/api/v1beta1/conversion.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func (r *AWSManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error {
4141
dst.Spec.VpcCni.Disable = r.Spec.DisableVPCCNI
4242
dst.Spec.Partition = restored.Spec.Partition
4343
dst.Spec.RestrictPrivateSubnets = restored.Spec.RestrictPrivateSubnets
44+
dst.Spec.PodIdentityAssociations = restored.Spec.PodIdentityAssociations
4445

4546
return nil
4647
}

controlplane/eks/api/v1beta1/zz_generated.conversion.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ type AWSManagedControlPlaneSpec struct { //nolint: maligned
164164
// +optional
165165
Addons *[]Addon `json:"addons,omitempty"`
166166

167+
// PodIdentityAssociations represent Kubernetes Service Accounts mapping to AWS IAM Roles without IRSA, using EKS Pod Identity.
168+
// This requires using the AWS EKS Addon for Pod Identity.
169+
// +optional
170+
PodIdentityAssociations []PodIdentityAssociation `json:"podIdentityAssociations,omitempty"`
171+
167172
// OIDCIdentityProviderConfig is used to specify the oidc provider config
168173
// to be attached with this eks cluster
169174
// +optional

controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/pkg/errors"
2525
apierrors "k8s.io/apimachinery/pkg/api/errors"
2626
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/apimachinery/pkg/util/validation"
2728
"k8s.io/apimachinery/pkg/util/validation/field"
2829
"k8s.io/apimachinery/pkg/util/version"
2930
"k8s.io/klog/v2"
@@ -63,8 +64,10 @@ func (r *AWSManagedControlPlane) SetupWebhookWithManager(mgr ctrl.Manager) error
6364
// +kubebuilder:webhook:verbs=create;update,path=/validate-controlplane-cluster-x-k8s-io-v1beta2-awsmanagedcontrolplane,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=controlplane.cluster.x-k8s.io,resources=awsmanagedcontrolplanes,versions=v1beta2,name=validation.awsmanagedcontrolplanes.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
6465
// +kubebuilder:webhook:verbs=create;update,path=/mutate-controlplane-cluster-x-k8s-io-v1beta2-awsmanagedcontrolplane,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=controlplane.cluster.x-k8s.io,resources=awsmanagedcontrolplanes,versions=v1beta2,name=default.awsmanagedcontrolplanes.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
6566

66-
var _ webhook.Defaulter = &AWSManagedControlPlane{}
67-
var _ webhook.Validator = &AWSManagedControlPlane{}
67+
var (
68+
_ webhook.Defaulter = &AWSManagedControlPlane{}
69+
_ webhook.Validator = &AWSManagedControlPlane{}
70+
)
6871

6972
func parseEKSVersion(raw string) (*version.Version, error) {
7073
v, err := version.ParseGeneric(raw)
@@ -96,6 +99,7 @@ func (r *AWSManagedControlPlane) ValidateCreate() (admission.Warnings, error) {
9699
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
97100
allErrs = append(allErrs, validateNetwork(r.Spec)...)
98101
allErrs = append(allErrs, validatePrivateDNSHostnameTypeOnLaunch(r.Spec)...)
102+
allErrs = append(allErrs, r.validServiceAccountName()...)
99103

100104
if len(allErrs) == 0 {
101105
return nil, nil
@@ -132,6 +136,7 @@ func (r *AWSManagedControlPlane) ValidateUpdate(old runtime.Object) (admission.W
132136
allErrs = append(allErrs, validateKubeProxy(r.Spec)...)
133137
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
134138
allErrs = append(allErrs, validatePrivateDNSHostnameTypeOnLaunch(r.Spec)...)
139+
allErrs = append(allErrs, r.validServiceAccountName()...)
135140

136141
if r.Spec.Region != oldAWSManagedControlplane.Spec.Region {
137142
allErrs = append(allErrs,
@@ -444,6 +449,28 @@ func validatePrivateDNSHostnameTypeOnLaunch(r AWSManagedControlPlaneSpec) field.
444449
return allErrs
445450
}
446451

452+
func (r *AWSManagedControlPlane) validServiceAccountName() field.ErrorList {
453+
var allErrs field.ErrorList
454+
455+
if r.Spec.PodIdentityAssociations != nil {
456+
for i, association := range r.Spec.PodIdentityAssociations {
457+
associationPath := field.NewPath("spec", "podIdentityAssociations").Index(i)
458+
if association.ServiceAccountName == "" {
459+
allErrs = append(allErrs, field.Required(associationPath.Child("serviceAccountName"), "serviceAccountName is required"))
460+
}
461+
462+
// kubernetes uses ValidateServiceAccountName internally, which maps to IsDNS1123Subdomain
463+
// https://github.com/kubernetes/apimachinery/blob/d794766488ac2892197a7cc8d0b4b46b0edbda80/pkg/api/validation/generic.go#L68
464+
465+
if validationErrs := validation.IsDNS1123Subdomain(association.ServiceAccountName); len(validationErrs) > 0 {
466+
allErrs = append(allErrs, field.Invalid(associationPath.Child("serviceAccountName"), association.ServiceAccountName, fmt.Sprintf("serviceAccountName is invalid: %v", validationErrs)))
467+
}
468+
}
469+
}
470+
471+
return allErrs
472+
}
473+
447474
func validateNetwork(r AWSManagedControlPlaneSpec) field.ErrorList {
448475
var allErrs field.ErrorList
449476

controlplane/eks/api/v1beta2/conditions_consts.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ const (
5252
EKSAddonsConfiguredFailedReason = "EKSAddonsConfiguredFailed"
5353
)
5454

55+
const (
56+
// EKSPodIdentityAssociationConfiguredCondition condition reports on the successful reconciliation of EKS pod identity associations.
57+
EKSPodIdentityAssociationConfiguredCondition clusterv1.ConditionType = "EKSPodIdentityAssociationConfigured"
58+
// EKSPodIdentityAssociationFailedReason is used to report failures while reconciling the EKS pod identity associations.
59+
EKSPodIdentityAssociationFailedReason = "EKSPodIdentityAssociationConfigurationFailed"
60+
)
61+
5562
const (
5663
// EKSIdentityProviderConfiguredCondition condition reports on the successful association of identity provider config.
5764
EKSIdentityProviderConfiguredCondition clusterv1.ConditionType = "EKSIdentityProviderConfigured"

controlplane/eks/api/v1beta2/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,17 @@ type OIDCIdentityProviderConfig struct {
294294
// +optional
295295
Tags infrav1.Tags `json:"tags,omitempty"`
296296
}
297+
298+
// PodIdentityAssociation represents an association between a Kubernetes Service Account in a namespace, and an AWS IAM role which allows the service principal `pods.eks.amazonaws.com` in its trust policy.
299+
type PodIdentityAssociation struct {
300+
// ServiceAccountName is the name of the kubernetes Service Account within the namespace
301+
// +kubebuilder:validation:Required
302+
ServiceAccountName string `json:"serviceAccountName"`
303+
// ServiceAccountNamespace is the kubernetes namespace, which the kubernetes Service Account resides in. Defaults to "default" namespace.
304+
// +kubebuilder:validation:Required
305+
// +kubebuilder:default=default
306+
ServiceAccountNamespace string `json:"serviceAccountNamespace"`
307+
// RoleARN is the ARN of an IAM role which the Service Account can assume.
308+
// +kubebuilder:validation:Required
309+
RoleARN string `json:"serviceAccountRoleARN,omitempty"`
310+
}

controlplane/eks/api/v1beta2/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/eks/controllers/awsmanagedcontrolplane_controller_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,8 @@ func mockedCallsForMissingEverything(ec2Rec *mocks.MockEC2APIMockRecorder, subne
432432
Name: aws.String("tag-key"),
433433
Values: aws.StringSlice([]string{"sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"}),
434434
},
435-
}})).Return(&ec2.DescribeRouteTablesOutput{
435+
},
436+
})).Return(&ec2.DescribeRouteTablesOutput{
436437
RouteTables: []*ec2.RouteTable{
437438
{
438439
Routes: []*ec2.Route{
@@ -492,7 +493,8 @@ func mockedCallsForMissingEverything(ec2Rec *mocks.MockEC2APIMockRecorder, subne
492493
Name: aws.String("state"),
493494
Values: aws.StringSlice([]string{ec2.VpcStatePending, ec2.VpcStateAvailable}),
494495
},
495-
}}), gomock.Any()).Return(nil).MinTimes(1).MaxTimes(2)
496+
},
497+
}), gomock.Any()).Return(nil).MinTimes(1).MaxTimes(2)
496498

497499
ec2Rec.DescribeAddressesWithContext(context.TODO(), gomock.Eq(&ec2.DescribeAddressesInput{
498500
Filters: []*ec2.Filter{
@@ -924,6 +926,12 @@ func mockedEKSCluster(g *WithT, eksRec *mock_eksiface.MockEKSAPIMockRecorder, ia
924926
ClusterName: aws.String("test-cluster"),
925927
}).Return(&eks.ListAddonsOutput{}, nil)
926928

929+
eksRec.ListPodIdentityAssociationsWithContext(context.TODO(), gomock.Eq(&eks.ListPodIdentityAssociationsInput{
930+
ClusterName: aws.String("test-cluster"),
931+
})).Return(&eks.ListPodIdentityAssociationsOutput{
932+
Associations: []*eks.PodIdentityAssociationSummary{},
933+
}, nil)
934+
927935
awsNodeRec.ReconcileCNI(gomock.Any()).Return(nil)
928936
kubeProxyRec.ReconcileKubeProxy(gomock.Any()).Return(nil)
929937
iamAuthenticatorRec.ReconcileIAMAuthenticator(gomock.Any()).Return(nil)

0 commit comments

Comments
 (0)