sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/eks/oidc.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package eks 18 19 import ( 20 "context" 21 "fmt" 22 "regexp" 23 "strings" 24 25 "github.com/aws/aws-sdk-go/service/eks" 26 "github.com/pkg/errors" 27 corev1 "k8s.io/api/core/v1" 28 apierrors "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/types" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 "sigs.k8s.io/cluster-api-provider-aws/cmd/clusterawsadm/converters" 34 iamv1 "sigs.k8s.io/cluster-api-provider-aws/iam/api/v1beta1" 35 "sigs.k8s.io/cluster-api/controllers/remote" 36 ) 37 38 var ( 39 trustPolicyConfigMapName = "boilerplate-oidc-trust-policy" 40 trustPolicyConfigMapNamespace = metav1.NamespaceDefault 41 42 whitespaceRe = regexp.MustCompile(`(?m)[\t\n]`) 43 ) 44 45 func (s *Service) reconcileOIDCProvider(cluster *eks.Cluster) error { 46 if !s.scope.ControlPlane.Spec.AssociateOIDCProvider || s.scope.ControlPlane.Status.OIDCProvider.ARN != "" { 47 return nil 48 } 49 50 if !s.scope.EnableIAM() { 51 return errors.New("'AssociateOIDCProvider' provided without enabling the 'EKSEnableIAM' feature flag") 52 } 53 54 s.scope.Info("Reconciling EKS OIDC Provider", "cluster-name", cluster.Name) 55 oidcProvider, err := s.CreateOIDCProvider(cluster) 56 if err != nil { 57 return errors.Wrap(err, "failed to create OIDC provider") 58 } 59 s.scope.ControlPlane.Status.OIDCProvider.ARN = oidcProvider 60 61 policy, err := converters.IAMPolicyDocumentToJSON(s.buildOIDCTrustPolicy()) 62 if err != nil { 63 return errors.Wrap(err, "failed to parse IAM policy") 64 } 65 s.scope.ControlPlane.Status.OIDCProvider.TrustPolicy = whitespaceRe.ReplaceAllString(policy, "") 66 if err := s.scope.PatchObject(); err != nil { 67 return errors.Wrap(err, "failed to update control plane with OIDC provider ARN") 68 } 69 70 if err := s.reconcileTrustPolicy(); err != nil { 71 return errors.Wrap(err, "failed to reconcile trust policy in workload cluster") 72 } 73 74 return nil 75 } 76 77 func (s *Service) reconcileTrustPolicy() error { 78 ctx := context.Background() 79 80 clusterKey := client.ObjectKey{ 81 Name: s.scope.Name(), 82 Namespace: s.scope.Namespace(), 83 } 84 85 restConfig, err := remote.RESTConfig(ctx, s.scope.ControlPlane.Name, s.scope.Client, clusterKey) 86 if err != nil { 87 return fmt.Errorf("getting remote client for %s/%s: %w", s.scope.Namespace(), s.scope.Name(), err) 88 } 89 90 remoteClient, err := client.New(restConfig, client.Options{}) 91 if err != nil { 92 return fmt.Errorf("getting client for remote cluster: %w", err) 93 } 94 95 configMapRef := types.NamespacedName{ 96 Name: trustPolicyConfigMapName, 97 Namespace: trustPolicyConfigMapNamespace, 98 } 99 100 trustPolicyConfigMap := &corev1.ConfigMap{} 101 102 err = remoteClient.Get(ctx, configMapRef, trustPolicyConfigMap) 103 if err != nil && !apierrors.IsNotFound(err) { 104 return fmt.Errorf("getting %s/%s config map: %w", trustPolicyConfigMapNamespace, trustPolicyConfigMapName, err) 105 } 106 107 policy, err := converters.IAMPolicyDocumentToJSON(s.buildOIDCTrustPolicy()) 108 if err != nil { 109 return errors.Wrap(err, "failed to parse IAM policy") 110 } 111 112 trustPolicyConfigMap.Data = map[string]string{ 113 "trust-policy.json": policy, 114 } 115 116 if trustPolicyConfigMap.UID == "" { 117 trustPolicyConfigMap.Name = trustPolicyConfigMapName 118 trustPolicyConfigMap.Namespace = trustPolicyConfigMapNamespace 119 s.V(2).Info("Creating new Trust Policy ConfigMap", "cluster", s.scope.Name(), "configmap", trustPolicyConfigMapName) 120 return remoteClient.Create(ctx, trustPolicyConfigMap) 121 } 122 123 s.V(2).Info("Updating existing Trust Policy ConfigMap", "cluster", s.scope.Name(), "configmap", trustPolicyConfigMapName) 124 return remoteClient.Update(ctx, trustPolicyConfigMap) 125 } 126 127 func (s *Service) deleteOIDCProvider() error { 128 if !s.scope.ControlPlane.Spec.AssociateOIDCProvider || s.scope.ControlPlane.Status.OIDCProvider.ARN == "" { 129 return nil 130 } 131 132 providerARN := s.scope.ControlPlane.Status.OIDCProvider.ARN 133 if err := s.DeleteOIDCProvider(&providerARN); err != nil { 134 return errors.Wrap(err, "failed to delete OIDC provider") 135 } 136 137 s.scope.ControlPlane.Status.OIDCProvider.ARN = "" 138 if err := s.scope.PatchObject(); err != nil { 139 return errors.Wrap(err, "failed to update control plane with OIDC provider ARN") 140 } 141 142 return nil 143 } 144 145 func (s *Service) buildOIDCTrustPolicy() iamv1.PolicyDocument { 146 providerARN := s.scope.ControlPlane.Status.OIDCProvider.ARN 147 conditionValue := providerARN[strings.Index(providerARN, "/")+1:] + ":sub" 148 149 return iamv1.PolicyDocument{ 150 Version: "2012-10-17", 151 Statement: iamv1.Statements{ 152 iamv1.StatementEntry{ 153 Sid: "", 154 Effect: "Allow", 155 Principal: iamv1.Principals{ 156 iamv1.PrincipalFederated: iamv1.PrincipalID{providerARN}, 157 }, 158 Action: iamv1.Actions{"sts:AssumeRoleWithWebIdentity"}, 159 Condition: iamv1.Conditions{ 160 "ForAnyValue:StringLike": map[string][]string{ 161 conditionValue: {"system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}"}, 162 }, 163 }, 164 }, 165 }, 166 } 167 }