sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/secretsmanager/secret.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 secretsmanager 18 19 import ( 20 "fmt" 21 "path" 22 23 "github.com/aws/aws-sdk-go/aws" 24 "github.com/aws/aws-sdk-go/service/secretsmanager" 25 kerrors "k8s.io/apimachinery/pkg/util/errors" 26 "k8s.io/apimachinery/pkg/util/uuid" 27 28 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 29 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors" 30 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/converters" 31 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" 32 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/wait" 33 "sigs.k8s.io/cluster-api-provider-aws/pkg/internal/bytes" 34 ) 35 36 const ( 37 entryPrefix = "aws.cluster.x-k8s.io" 38 39 // we set the max secret size to well below the 10240 byte limit, because this is limit after base64 encoding, 40 // but the aws sdk handles encoding for us, so we can't send a full 10240. 41 maxSecretSizeBytes = 7000 42 ) 43 44 var retryableErrors = []string{ 45 // Returned when the secret is scheduled for deletion 46 secretsmanager.ErrCodeInvalidRequestException, 47 // Returned during retries of deletes prior to recreation 48 secretsmanager.ErrCodeResourceNotFoundException, 49 } 50 51 // Create stores data in AWS Secrets Manager for a given machine, chunking at 10kb per secret. The prefix of the secret 52 // ARN and the number of chunks are returned. 53 func (s *Service) Create(m *scope.MachineScope, data []byte) (string, int32, error) { 54 // Build the tags to apply to the secret. 55 additionalTags := m.AdditionalTags() 56 additionalTags[infrav1.ClusterAWSCloudProviderTagKey(s.scope.Name())] = string(infrav1.ResourceLifecycleOwned) 57 tags := infrav1.Build(infrav1.BuildParams{ 58 ClusterName: s.scope.Name(), 59 Lifecycle: infrav1.ResourceLifecycleOwned, 60 Name: aws.String(m.Name()), 61 Role: aws.String(m.Role()), 62 Additional: additionalTags, 63 }) 64 65 // Build the prefix. 66 prefix := m.GetSecretPrefix() 67 if prefix == "" { 68 prefix = path.Join(entryPrefix, string(uuid.NewUUID())) 69 } 70 // Split the data into chunks and create the secrets on demand. 71 chunks := int32(0) 72 var err error 73 bytes.Split(data, false, maxSecretSizeBytes, func(chunk []byte) { 74 name := fmt.Sprintf("%s-%d", prefix, chunks) 75 retryFunc := func() (bool, error) { return s.retryableCreateSecret(name, chunk, tags) } 76 // Default timeout is 5 mins, but if Secrets Manager has got to the state where the timeout is reached, 77 // makes sense to slow down machine creation until AWS weather improves. 78 if err = wait.WaitForWithRetryable(wait.NewBackoff(), retryFunc, retryableErrors...); err != nil { 79 return 80 } 81 chunks++ 82 }) 83 84 return prefix, chunks, err 85 } 86 87 // retryableCreateSecret is a function to be passed into a waiter. In a separate function for ease of reading. 88 func (s *Service) retryableCreateSecret(name string, chunk []byte, tags infrav1.Tags) (bool, error) { 89 _, err := s.SecretsManagerClient.CreateSecret(&secretsmanager.CreateSecretInput{ 90 Name: aws.String(name), 91 SecretBinary: chunk, 92 Tags: converters.MapToSecretsManagerTags(tags), 93 }) 94 // If the secret already exists, delete it, return request to retry, as deletes are eventually consistent 95 if awserrors.IsResourceExists(err) { 96 return false, s.forceDeleteSecretEntry(name) 97 } 98 if err != nil { 99 return false, err 100 } 101 return true, err 102 } 103 104 // forceDeleteSecretEntry deletes a single secret, ignoring if it is absent. 105 func (s *Service) forceDeleteSecretEntry(name string) error { 106 _, err := s.SecretsManagerClient.DeleteSecret(&secretsmanager.DeleteSecretInput{ 107 SecretId: aws.String(name), 108 ForceDeleteWithoutRecovery: aws.Bool(true), 109 }) 110 if awserrors.IsNotFound(err) { 111 return nil 112 } 113 return err 114 } 115 116 // Delete the secret belonging to a machine from AWS Secrets Manager. 117 func (s *Service) Delete(m *scope.MachineScope) error { 118 var errs []error 119 for i := int32(0); i < m.GetSecretCount(); i++ { 120 if err := s.forceDeleteSecretEntry(fmt.Sprintf("%s-%d", m.GetSecretPrefix(), i)); err != nil { 121 errs = append(errs, err) 122 } 123 } 124 125 return kerrors.NewAggregate(errs) 126 }