sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/iamauth/reconcile.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 iamauth
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/aws/aws-sdk-go/aws"
    24  	"github.com/aws/aws-sdk-go/service/iam"
    25  	"github.com/pkg/errors"
    26  	corev1 "k8s.io/api/core/v1"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    30  	ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1"
    31  	expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
    32  	iamv1 "sigs.k8s.io/cluster-api-provider-aws/iam/api/v1beta1"
    33  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    34  	expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    35  )
    36  
    37  // ReconcileIAMAuthenticator is used to create the aws-iam-authenticator in a cluster.
    38  func (s *Service) ReconcileIAMAuthenticator(ctx context.Context) error {
    39  	s.scope.Info("Reconciling aws-iam-authenticator configuration", "cluster-name", s.scope.Name())
    40  
    41  	remoteClient, err := s.scope.RemoteClient()
    42  	if err != nil {
    43  		s.scope.Error(err, "getting client for remote cluster")
    44  		return fmt.Errorf("getting client for remote cluster: %w", err)
    45  	}
    46  
    47  	authBackend, err := NewBackend(s.backend, remoteClient)
    48  	if err != nil {
    49  		return fmt.Errorf("getting aws-iam-authenticator backend: %w", err)
    50  	}
    51  	nodeRoles, err := s.getRolesForWorkers(ctx)
    52  	if err != nil {
    53  		s.scope.Error(err, "getting roles for remote workers")
    54  		return fmt.Errorf("getting roles for remote workers: %w", err)
    55  	}
    56  	for roleName := range nodeRoles {
    57  		roleARN, err := s.getARNForRole(roleName)
    58  		if err != nil {
    59  			return fmt.Errorf("failed to get ARN for role %s: %w", roleARN, err)
    60  		}
    61  		nodesRoleMapping := ekscontrolplanev1.RoleMapping{
    62  			RoleARN: roleARN,
    63  			KubernetesMapping: ekscontrolplanev1.KubernetesMapping{
    64  				UserName: EC2NodeUserName,
    65  				Groups:   NodeGroups,
    66  			},
    67  		}
    68  		s.scope.V(2).Info("Mapping node IAM role", "iam-role", nodesRoleMapping.RoleARN, "user", nodesRoleMapping.UserName)
    69  		if err := authBackend.MapRole(nodesRoleMapping); err != nil {
    70  			return fmt.Errorf("mapping iam node role: %w", err)
    71  		}
    72  	}
    73  
    74  	s.scope.V(2).Info("Mapping additional IAM roles and users")
    75  	iamCfg := s.scope.IAMAuthConfig()
    76  	for _, roleMapping := range iamCfg.RoleMappings {
    77  		s.scope.V(2).Info("Mapping IAM role", "iam-role", roleMapping.RoleARN, "user", roleMapping.UserName)
    78  		if err := authBackend.MapRole(roleMapping); err != nil {
    79  			return fmt.Errorf("mapping iam role: %w", err)
    80  		}
    81  	}
    82  
    83  	for _, userMapping := range iamCfg.UserMappings {
    84  		s.scope.V(2).Info("Mapping IAM user", "iam-user", userMapping.UserARN, "user", userMapping.UserName)
    85  		if err := authBackend.MapUser(userMapping); err != nil {
    86  			return fmt.Errorf("mapping iam user: %w", err)
    87  		}
    88  	}
    89  
    90  	s.scope.Info("Reconciled aws-iam-authenticator configuration", "cluster-name", s.scope.KubernetesClusterName())
    91  
    92  	return nil
    93  }
    94  
    95  func (s *Service) getARNForRole(role string) (string, error) {
    96  	input := &iam.GetRoleInput{
    97  		RoleName: aws.String(role),
    98  	}
    99  	out, err := s.IAMClient.GetRole(input)
   100  	if err != nil {
   101  		return "", errors.Wrap(err, "unable to get role")
   102  	}
   103  	return aws.StringValue(out.Role.Arn), nil
   104  }
   105  
   106  func (s *Service) getRolesForWorkers(ctx context.Context) (map[string]struct{}, error) {
   107  	// previously this was the default role always added to the IAM authenticator config
   108  	// we'll keep this to not break existing behavior for users
   109  	allRoles := map[string]struct{}{
   110  		fmt.Sprintf("nodes%s", iamv1.DefaultNameSuffix): {},
   111  	}
   112  	if err := s.getRolesForMachineDeployments(ctx, allRoles); err != nil {
   113  		return nil, fmt.Errorf("failed to get roles from machine deployments %w", err)
   114  	}
   115  	if err := s.getRolesForMachinePools(ctx, allRoles); err != nil {
   116  		return nil, fmt.Errorf("failed to get roles from machine pools %w", err)
   117  	}
   118  	return allRoles, nil
   119  }
   120  
   121  func (s *Service) getRolesForMachineDeployments(ctx context.Context, allRoles map[string]struct{}) error {
   122  	deploymentList := &clusterv1.MachineDeploymentList{}
   123  	selectors := []client.ListOption{
   124  		client.InNamespace(s.scope.Namespace()),
   125  		client.MatchingLabels{
   126  			clusterv1.ClusterLabelName: s.scope.Name(),
   127  		},
   128  	}
   129  	err := s.client.List(ctx, deploymentList, selectors...)
   130  	if err != nil {
   131  		return fmt.Errorf("failed to list machine deployments for cluster %s/%s: %w", s.scope.Namespace(), s.scope.Name(), err)
   132  	}
   133  
   134  	for _, deployment := range deploymentList.Items {
   135  		ref := deployment.Spec.Template.Spec.InfrastructureRef
   136  		if ref.Kind != "AWSMachineTemplate" {
   137  			continue
   138  		}
   139  		awsMachineTemplate := &infrav1.AWSMachineTemplate{}
   140  		err := s.client.Get(ctx, client.ObjectKey{
   141  			Name:      ref.Name,
   142  			Namespace: s.scope.Namespace(),
   143  		}, awsMachineTemplate)
   144  		if err != nil {
   145  			return fmt.Errorf("failed to get AWSMachine %s/%s: %w", ref.Namespace, ref.Name, err)
   146  		}
   147  		instanceProfile := awsMachineTemplate.Spec.Template.Spec.IAMInstanceProfile
   148  		if _, ok := allRoles[instanceProfile]; !ok && instanceProfile != "" {
   149  			allRoles[instanceProfile] = struct{}{}
   150  		}
   151  	}
   152  	return nil
   153  }
   154  
   155  func (s *Service) getRolesForMachinePools(ctx context.Context, allRoles map[string]struct{}) error {
   156  	machinePoolList := &expclusterv1.MachinePoolList{}
   157  	selectors := []client.ListOption{
   158  		client.InNamespace(s.scope.Namespace()),
   159  		client.MatchingLabels{
   160  			clusterv1.ClusterLabelName: s.scope.Name(),
   161  		},
   162  	}
   163  	err := s.client.List(ctx, machinePoolList, selectors...)
   164  	if err != nil {
   165  		return fmt.Errorf("failed to list machine pools for cluster %s/%s: %w", s.scope.Namespace(), s.scope.Name(), err)
   166  	}
   167  	for _, pool := range machinePoolList.Items {
   168  		ref := pool.Spec.Template.Spec.InfrastructureRef
   169  		switch ref.Kind {
   170  		case "AWSMachinePool":
   171  			if err := s.getRolesForAWSMachinePool(ctx, ref, allRoles); err != nil {
   172  				return err
   173  			}
   174  		case "AWSManagedMachinePool":
   175  			if err := s.getRolesForAWSManagedMachinePool(ctx, ref, allRoles); err != nil {
   176  				return err
   177  			}
   178  		default:
   179  		}
   180  	}
   181  	return nil
   182  }
   183  
   184  func (s *Service) getRolesForAWSMachinePool(ctx context.Context, ref corev1.ObjectReference, allRoles map[string]struct{}) error {
   185  	awsMachinePool := &expinfrav1.AWSMachinePool{}
   186  	err := s.client.Get(ctx, client.ObjectKey{
   187  		Name:      ref.Name,
   188  		Namespace: s.scope.Namespace(),
   189  	}, awsMachinePool)
   190  	if err != nil {
   191  		return fmt.Errorf("failed to get AWSMachine %s/%s: %w", ref.Namespace, ref.Name, err)
   192  	}
   193  	instanceProfile := awsMachinePool.Spec.AWSLaunchTemplate.IamInstanceProfile
   194  	if _, ok := allRoles[instanceProfile]; !ok && instanceProfile != "" {
   195  		allRoles[instanceProfile] = struct{}{}
   196  	}
   197  	return nil
   198  }
   199  
   200  func (s *Service) getRolesForAWSManagedMachinePool(ctx context.Context, ref corev1.ObjectReference, allRoles map[string]struct{}) error {
   201  	awsManagedMachinePool := &expinfrav1.AWSManagedMachinePool{}
   202  	err := s.client.Get(ctx, client.ObjectKey{
   203  		Name:      ref.Name,
   204  		Namespace: s.scope.Namespace(),
   205  	}, awsManagedMachinePool)
   206  	if err != nil {
   207  		return fmt.Errorf("failed to get AWSMachine %s/%s: %w", ref.Namespace, ref.Name, err)
   208  	}
   209  	instanceProfile := awsManagedMachinePool.Spec.RoleName
   210  	if _, ok := allRoles[instanceProfile]; !ok && instanceProfile != "" {
   211  		allRoles[instanceProfile] = struct{}{}
   212  	}
   213  	return nil
   214  }