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 }