sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/scope/session.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 scope
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  
    24  	"github.com/aws/aws-sdk-go/aws"
    25  	"github.com/aws/aws-sdk-go/aws/credentials"
    26  	"github.com/aws/aws-sdk-go/aws/endpoints"
    27  	"github.com/aws/aws-sdk-go/aws/session"
    28  	"github.com/aws/aws-sdk-go/service/ec2"
    29  	"github.com/aws/aws-sdk-go/service/elb"
    30  	"github.com/aws/aws-sdk-go/service/elbv2"
    31  	"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
    32  	"github.com/aws/aws-sdk-go/service/secretsmanager"
    33  	"github.com/go-logr/logr"
    34  	"github.com/google/go-cmp/cmp"
    35  	"github.com/pkg/errors"
    36  	corev1 "k8s.io/api/core/v1"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	"sigs.k8s.io/controller-runtime/pkg/client"
    39  
    40  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    41  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud"
    42  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/identity"
    43  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/throttle"
    44  	"sigs.k8s.io/cluster-api-provider-aws/util/system"
    45  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    46  	"sigs.k8s.io/cluster-api/util"
    47  	"sigs.k8s.io/cluster-api/util/conditions"
    48  	"sigs.k8s.io/cluster-api/util/patch"
    49  )
    50  
    51  const (
    52  	notPermittedError = "Namespace is not permitted to use %s: %s"
    53  )
    54  
    55  // ServiceEndpoint defines a tuple containing AWS Service resolution information.
    56  type ServiceEndpoint struct {
    57  	ServiceID     string
    58  	URL           string
    59  	SigningRegion string
    60  }
    61  
    62  var sessionCache sync.Map
    63  var providerCache sync.Map
    64  
    65  type sessionCacheEntry struct {
    66  	session         *session.Session
    67  	serviceLimiters throttle.ServiceLimiters
    68  }
    69  
    70  // SessionInterface is the interface for AWSCluster and ManagedCluster to be used to get session using identityRef.
    71  var SessionInterface interface {
    72  }
    73  
    74  func sessionForRegion(region string, endpoint []ServiceEndpoint) (*session.Session, throttle.ServiceLimiters, error) {
    75  	if s, ok := sessionCache.Load(region); ok {
    76  		entry := s.(*sessionCacheEntry)
    77  		return entry.session, entry.serviceLimiters, nil
    78  	}
    79  
    80  	resolver := func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
    81  		for _, s := range endpoint {
    82  			if service == s.ServiceID {
    83  				return endpoints.ResolvedEndpoint{
    84  					URL:           s.URL,
    85  					SigningRegion: s.SigningRegion,
    86  				}, nil
    87  			}
    88  		}
    89  		return endpoints.DefaultResolver().EndpointFor(service, region, optFns...)
    90  	}
    91  	ns, err := session.NewSession(&aws.Config{
    92  		Region:           aws.String(region),
    93  		EndpointResolver: endpoints.ResolverFunc(resolver),
    94  	})
    95  	if err != nil {
    96  		return nil, nil, err
    97  	}
    98  
    99  	sl := newServiceLimiters()
   100  	sessionCache.Store(region, &sessionCacheEntry{
   101  		session:         ns,
   102  		serviceLimiters: sl,
   103  	})
   104  	return ns, sl, nil
   105  }
   106  
   107  func sessionForClusterWithRegion(k8sClient client.Client, clusterScoper cloud.ClusterScoper, region string, endpoint []ServiceEndpoint, logger logr.Logger) (*session.Session, throttle.ServiceLimiters, error) {
   108  	log := logger.WithName("identity")
   109  	log.V(4).Info("Creating an AWS Session")
   110  
   111  	resolver := func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
   112  		for _, s := range endpoint {
   113  			if service == s.ServiceID {
   114  				return endpoints.ResolvedEndpoint{
   115  					URL:           s.URL,
   116  					SigningRegion: s.SigningRegion,
   117  				}, nil
   118  			}
   119  		}
   120  		return endpoints.DefaultResolver().EndpointFor(service, region, optFns...)
   121  	}
   122  
   123  	providers, err := getProvidersForCluster(context.Background(), k8sClient, clusterScoper, log)
   124  	if err != nil {
   125  		// could not get providers and retrieve the credentials
   126  		conditions.MarkFalse(clusterScoper.InfraCluster(), infrav1.PrincipalCredentialRetrievedCondition, infrav1.PrincipalCredentialRetrievalFailedReason, clusterv1.ConditionSeverityError, err.Error())
   127  		return nil, nil, errors.Wrap(err, "Failed to get providers for cluster")
   128  	}
   129  
   130  	isChanged := false
   131  	awsProviders := make([]credentials.Provider, len(providers))
   132  	for i, provider := range providers {
   133  		// load an existing matching providers from the cache if such a providers exists
   134  		providerHash, err := provider.Hash()
   135  		if err != nil {
   136  			return nil, nil, errors.Wrap(err, "Failed to calculate provider hash")
   137  		}
   138  		cachedProvider, ok := providerCache.Load(providerHash)
   139  		if ok {
   140  			provider = cachedProvider.(identity.AWSPrincipalTypeProvider)
   141  		} else {
   142  			isChanged = true
   143  			// add this providers to the cache
   144  			providerCache.Store(providerHash, provider)
   145  		}
   146  		awsProviders[i] = provider.(credentials.Provider)
   147  	}
   148  
   149  	if !isChanged {
   150  		if s, ok := sessionCache.Load(getSessionName(region, clusterScoper)); ok {
   151  			entry := s.(*sessionCacheEntry)
   152  			return entry.session, entry.serviceLimiters, nil
   153  		}
   154  	}
   155  	awsConfig := &aws.Config{
   156  		Region:           aws.String(region),
   157  		EndpointResolver: endpoints.ResolverFunc(resolver),
   158  	}
   159  
   160  	if len(providers) > 0 {
   161  		// Check if identity credentials can be retrieved. One reason this will fail is that source identity is not authorized for assume role.
   162  		_, err := providers[0].Retrieve()
   163  		if err != nil {
   164  			conditions.MarkUnknown(clusterScoper.InfraCluster(), infrav1.PrincipalCredentialRetrievedCondition, infrav1.CredentialProviderBuildFailedReason, err.Error())
   165  			return nil, nil, errors.Wrap(err, "Failed to retrieve identity credentials")
   166  		}
   167  		awsConfig = awsConfig.WithCredentials(credentials.NewChainCredentials(awsProviders))
   168  	}
   169  
   170  	conditions.MarkTrue(clusterScoper.InfraCluster(), infrav1.PrincipalCredentialRetrievedCondition)
   171  
   172  	ns, err := session.NewSession(awsConfig)
   173  	if err != nil {
   174  		return nil, nil, errors.Wrap(err, "Failed to create a new AWS session")
   175  	}
   176  	sl := newServiceLimiters()
   177  	sessionCache.Store(getSessionName(region, clusterScoper), &sessionCacheEntry{
   178  		session:         ns,
   179  		serviceLimiters: sl,
   180  	})
   181  
   182  	return ns, sl, nil
   183  }
   184  
   185  func getSessionName(region string, clusterScoper cloud.ClusterScoper) string {
   186  	return fmt.Sprintf("%s-%s-%s", region, clusterScoper.InfraClusterName(), clusterScoper.Namespace())
   187  }
   188  
   189  func newServiceLimiters() throttle.ServiceLimiters {
   190  	return throttle.ServiceLimiters{
   191  		ec2.ServiceID:                      newEC2ServiceLimiter(),
   192  		elb.ServiceID:                      newGenericServiceLimiter(),
   193  		elbv2.ServiceID:                    newGenericServiceLimiter(),
   194  		resourcegroupstaggingapi.ServiceID: newGenericServiceLimiter(),
   195  		secretsmanager.ServiceID:           newGenericServiceLimiter(),
   196  	}
   197  }
   198  
   199  func newGenericServiceLimiter() *throttle.ServiceLimiter {
   200  	return &throttle.ServiceLimiter{
   201  		{
   202  			Operation:  throttle.NewMultiOperationMatch("Describe", "Get", "List"),
   203  			RefillRate: 20.0,
   204  			Burst:      100,
   205  		},
   206  		{
   207  			Operation:  ".*",
   208  			RefillRate: 5.0,
   209  			Burst:      200,
   210  		},
   211  	}
   212  }
   213  
   214  func newEC2ServiceLimiter() *throttle.ServiceLimiter {
   215  	return &throttle.ServiceLimiter{
   216  		{
   217  			Operation:  throttle.NewMultiOperationMatch("Describe", "Get"),
   218  			RefillRate: 20.0,
   219  			Burst:      100,
   220  		},
   221  		{
   222  			Operation: throttle.NewMultiOperationMatch(
   223  				"AuthorizeSecurityGroupIngress",
   224  				"CancelSpotInstanceRequests",
   225  				"CreateKeyPair",
   226  				"RequestSpotInstances",
   227  			),
   228  			RefillRate: 20.0,
   229  			Burst:      100,
   230  		},
   231  		{
   232  			Operation:  "RunInstances",
   233  			RefillRate: 2.0,
   234  			Burst:      5,
   235  		},
   236  		{
   237  			Operation:  "StartInstances",
   238  			RefillRate: 2.0,
   239  			Burst:      5,
   240  		},
   241  		{
   242  			Operation:  ".*",
   243  			RefillRate: 5.0,
   244  			Burst:      200,
   245  		},
   246  	}
   247  }
   248  
   249  func buildProvidersForRef(
   250  	ctx context.Context,
   251  	providers []identity.AWSPrincipalTypeProvider,
   252  	k8sClient client.Client,
   253  	clusterScoper cloud.ClusterScoper,
   254  	ref *infrav1.AWSIdentityReference,
   255  	log logr.Logger) ([]identity.AWSPrincipalTypeProvider, error) {
   256  	if ref == nil {
   257  		log.V(4).Info("AWSCluster does not have a IdentityRef specified")
   258  		return providers, nil
   259  	}
   260  
   261  	var provider identity.AWSPrincipalTypeProvider
   262  	identityObjectKey := client.ObjectKey{Name: ref.Name}
   263  	log = log.WithValues("identityKey", identityObjectKey)
   264  	log.V(4).Info("Getting identity")
   265  
   266  	switch ref.Kind {
   267  	case infrav1.ControllerIdentityKind:
   268  		err := buildAWSClusterControllerIdentity(ctx, identityObjectKey, k8sClient, clusterScoper)
   269  		if err != nil {
   270  			return providers, err
   271  		}
   272  		// returning empty provider list to default to Controller Principal.
   273  		return []identity.AWSPrincipalTypeProvider{}, nil
   274  	case infrav1.ClusterStaticIdentityKind:
   275  		provider, err := buildAWSClusterStaticIdentity(ctx, identityObjectKey, k8sClient, clusterScoper)
   276  		if err != nil {
   277  			return providers, err
   278  		}
   279  		providers = append(providers, provider)
   280  	case infrav1.ClusterRoleIdentityKind:
   281  		roleIdentity := &infrav1.AWSClusterRoleIdentity{}
   282  		err := k8sClient.Get(ctx, identityObjectKey, roleIdentity)
   283  		if err != nil {
   284  			return providers, err
   285  		}
   286  		log.V(4).Info("Principal retrieved")
   287  		canUse, err := isClusterPermittedToUsePrincipal(k8sClient, roleIdentity.Spec.AllowedNamespaces, clusterScoper.Namespace())
   288  		if err != nil {
   289  			return providers, err
   290  		}
   291  		if !canUse {
   292  			setPrincipalUsageNotAllowedCondition(infrav1.ClusterRoleIdentityKind, identityObjectKey, clusterScoper)
   293  			return providers, errors.Errorf(notPermittedError, infrav1.ClusterRoleIdentityKind, roleIdentity.Name)
   294  		}
   295  		setPrincipalUsageAllowedCondition(clusterScoper)
   296  
   297  		if roleIdentity.Spec.SourceIdentityRef != nil {
   298  			providers, err = buildProvidersForRef(ctx, providers, k8sClient, clusterScoper, roleIdentity.Spec.SourceIdentityRef, log)
   299  			if err != nil {
   300  				return providers, err
   301  			}
   302  		}
   303  		var sourceProvider identity.AWSPrincipalTypeProvider
   304  		if len(providers) > 0 {
   305  			sourceProvider = providers[len(providers)-1]
   306  			// Remove last provider
   307  			if len(providers) > 0 {
   308  				providers = providers[:len(providers)-1]
   309  			}
   310  		}
   311  
   312  		if sourceProvider != nil {
   313  			provider = identity.NewAWSRolePrincipalTypeProvider(roleIdentity, &sourceProvider, log)
   314  		} else {
   315  			provider = identity.NewAWSRolePrincipalTypeProvider(roleIdentity, nil, log)
   316  		}
   317  		providers = append(providers, provider)
   318  	default:
   319  		return providers, errors.Errorf("No such provider known: '%s'", ref.Kind)
   320  	}
   321  	conditions.MarkTrue(clusterScoper.InfraCluster(), infrav1.PrincipalUsageAllowedCondition)
   322  	return providers, nil
   323  }
   324  
   325  func setPrincipalUsageAllowedCondition(clusterScoper cloud.ClusterScoper) {
   326  	conditions.MarkTrue(clusterScoper.InfraCluster(), infrav1.PrincipalUsageAllowedCondition)
   327  }
   328  
   329  func setPrincipalUsageNotAllowedCondition(kind infrav1.AWSIdentityKind, identityObjectKey client.ObjectKey, clusterScoper cloud.ClusterScoper) {
   330  	errMsg := fmt.Sprintf(notPermittedError, kind, identityObjectKey.Name)
   331  
   332  	if clusterScoper.IdentityRef().Name == identityObjectKey.Name {
   333  		conditions.MarkFalse(clusterScoper.InfraCluster(), infrav1.PrincipalUsageAllowedCondition, infrav1.PrincipalUsageUnauthorizedReason, clusterv1.ConditionSeverityError, errMsg)
   334  	} else {
   335  		conditions.MarkFalse(clusterScoper.InfraCluster(), infrav1.PrincipalUsageAllowedCondition, infrav1.SourcePrincipalUsageUnauthorizedReason, clusterv1.ConditionSeverityError, errMsg)
   336  	}
   337  }
   338  
   339  func buildAWSClusterStaticIdentity(ctx context.Context, identityObjectKey client.ObjectKey, k8sClient client.Client, clusterScoper cloud.ClusterScoper) (*identity.AWSStaticPrincipalTypeProvider, error) {
   340  	staticPrincipal := &infrav1.AWSClusterStaticIdentity{}
   341  	err := k8sClient.Get(ctx, identityObjectKey, staticPrincipal)
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  	secret := &corev1.Secret{}
   346  	err = k8sClient.Get(ctx, client.ObjectKey{Name: staticPrincipal.Spec.SecretRef, Namespace: system.GetManagerNamespace()}, secret)
   347  	if err != nil {
   348  		return nil, err
   349  	}
   350  
   351  	// Set ClusterStaticPrincipal as Secret's owner reference for 'clusterctl move'.
   352  	patchHelper, err := patch.NewHelper(secret, k8sClient)
   353  	if err != nil {
   354  		return nil, errors.Wrapf(err, "failed to init patch helper for secret name:%s namespace:%s", secret.Name, secret.Namespace)
   355  	}
   356  
   357  	secret.OwnerReferences = util.EnsureOwnerRef(secret.OwnerReferences, metav1.OwnerReference{
   358  		APIVersion: infrav1.GroupVersion.String(),
   359  		Kind:       string(infrav1.ClusterStaticIdentityKind),
   360  		Name:       staticPrincipal.Name,
   361  		UID:        staticPrincipal.UID,
   362  	})
   363  
   364  	if err := patchHelper.Patch(ctx, secret); err != nil {
   365  		return nil, errors.Wrapf(err, "failed to patch secret name:%s namespace:%s", secret.Name, secret.Namespace)
   366  	}
   367  
   368  	canUse, err := isClusterPermittedToUsePrincipal(k8sClient, staticPrincipal.Spec.AllowedNamespaces, clusterScoper.Namespace())
   369  	if err != nil {
   370  		return nil, err
   371  	}
   372  	if !canUse {
   373  		setPrincipalUsageNotAllowedCondition(infrav1.ClusterStaticIdentityKind, identityObjectKey, clusterScoper)
   374  		return nil, errors.Errorf(notPermittedError, infrav1.ClusterStaticIdentityKind, identityObjectKey.Name)
   375  	}
   376  	setPrincipalUsageAllowedCondition(clusterScoper)
   377  
   378  	return identity.NewAWSStaticPrincipalTypeProvider(staticPrincipal, secret), nil
   379  }
   380  
   381  func buildAWSClusterControllerIdentity(ctx context.Context, identityObjectKey client.ObjectKey, k8sClient client.Client, clusterScoper cloud.ClusterScoper) error {
   382  	controllerIdentity := &infrav1.AWSClusterControllerIdentity{}
   383  	controllerIdentity.Kind = string(infrav1.ControllerIdentityKind)
   384  
   385  	// Enforce the singleton again for depth
   386  	if identityObjectKey.Name != infrav1.AWSClusterControllerIdentityName {
   387  		return errors.Errorf("Expected AWSClusterControllerIdentity of name %s, got %s", infrav1.AWSClusterControllerIdentityName, identityObjectKey.Name)
   388  	}
   389  
   390  	err := k8sClient.Get(ctx, client.ObjectKey{Name: identityObjectKey.Name}, controllerIdentity)
   391  	if err != nil {
   392  		return err
   393  	}
   394  
   395  	canUse, err := isClusterPermittedToUsePrincipal(k8sClient, controllerIdentity.Spec.AllowedNamespaces, clusterScoper.Namespace())
   396  	if err != nil {
   397  		return err
   398  	}
   399  	if !canUse {
   400  		setPrincipalUsageNotAllowedCondition(infrav1.ControllerIdentityKind, identityObjectKey, clusterScoper)
   401  		return errors.Errorf(notPermittedError, infrav1.ControllerIdentityKind, controllerIdentity.Name)
   402  	}
   403  	setPrincipalUsageAllowedCondition(clusterScoper)
   404  	return nil
   405  }
   406  
   407  func getProvidersForCluster(ctx context.Context, k8sClient client.Client, clusterScoper cloud.ClusterScoper, log logr.Logger) ([]identity.AWSPrincipalTypeProvider, error) {
   408  	providers := make([]identity.AWSPrincipalTypeProvider, 0)
   409  	providers, err := buildProvidersForRef(ctx, providers, k8sClient, clusterScoper, clusterScoper.IdentityRef(), log)
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  
   414  	return providers, nil
   415  }
   416  
   417  func isClusterPermittedToUsePrincipal(k8sClient client.Client, allowedNs *infrav1.AllowedNamespaces, clusterNamespace string) (bool, error) {
   418  	// nil value does not match with any namespaces
   419  	if allowedNs == nil {
   420  		return false, nil
   421  	}
   422  
   423  	// empty value matches with all namespaces
   424  	if cmp.Equal(*allowedNs, infrav1.AllowedNamespaces{}) {
   425  		return true, nil
   426  	}
   427  
   428  	for _, v := range allowedNs.NamespaceList {
   429  		if v == clusterNamespace {
   430  			return true, nil
   431  		}
   432  	}
   433  
   434  	// Check if clusterNamespace is in the namespaces selected by the identity's allowedNamespaces selector.
   435  	namespaces := &corev1.NamespaceList{}
   436  	selector, err := metav1.LabelSelectorAsSelector(&allowedNs.Selector)
   437  	if err != nil {
   438  		return false, errors.Wrap(err, "failed to get label selector from spec selector")
   439  	}
   440  
   441  	// If a Selector has a nil or empty selector, it should match nothing, not everything.
   442  	if selector.Empty() {
   443  		return false, nil
   444  	}
   445  
   446  	if err := k8sClient.List(context.Background(), namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil {
   447  		return false, errors.Wrap(err, "failed to list namespaces")
   448  	}
   449  
   450  	for i := range namespaces.Items {
   451  		n := &namespaces.Items[i]
   452  		if n.Name == clusterNamespace {
   453  			return true, nil
   454  		}
   455  	}
   456  	return false, nil
   457  }