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  }