sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/ssm/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 ssm 18 19 import ( 20 "fmt" 21 "path" 22 "regexp" 23 24 "github.com/aws/aws-sdk-go/aws" 25 "github.com/aws/aws-sdk-go/service/ssm" 26 kerrors "k8s.io/apimachinery/pkg/util/errors" 27 "k8s.io/apimachinery/pkg/util/uuid" 28 29 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 30 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors" 31 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/converters" 32 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" 33 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/wait" 34 "sigs.k8s.io/cluster-api-provider-aws/pkg/internal/bytes" 35 ) 36 37 const ( 38 entryPrefix = "aws.cluster.x-k8s.io" 39 40 // max byte size for ssm is 4KB else we cross into the advanced-parameter tier. 41 maxSecretSizeBytes = 4000 42 ) 43 44 var ( 45 prefixRe = regexp.MustCompile(`(?i)^[\/]?(aws|ssm)[.]?`) 46 retryableErrors = []string{ 47 ssm.ErrCodeParameterLimitExceeded, 48 } 49 ) 50 51 // Create stores data in AWS SSM for a given machine, chunking at 4kb 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 // SSM Validation does not allow (/)aws|ssm in the beginning of the string 71 prefix = prefixRe.ReplaceAllString(prefix, "") 72 // Because the secret name has a slash in it, whole name must validate as a full path 73 if prefix[0] != byte('/') { 74 prefix = "/" + prefix 75 } 76 77 // Split the data into chunks and create the secrets on demand. 78 chunks := int32(0) 79 var err error 80 bytes.Split(data, true, maxSecretSizeBytes, func(chunk []byte) { 81 name := fmt.Sprintf("%s/%d", prefix, chunks) 82 retryFunc := func() (bool, error) { return s.retryableCreateSecret(name, chunk, tags) } 83 // Default timeout is 5 mins, but if SSM has got to the state where the timeout is reached, 84 // makes sense to slow down machine creation until AWS weather improves. 85 if err = wait.WaitForWithRetryable(wait.NewBackoff(), retryFunc, retryableErrors...); err != nil { 86 return 87 } 88 chunks++ 89 }) 90 91 return prefix, chunks, err 92 } 93 94 // retryableCreateSecret is a function to be passed into a waiter. In a separate function for ease of reading. 95 func (s *Service) retryableCreateSecret(name string, chunk []byte, tags infrav1.Tags) (bool, error) { 96 _, err := s.SSMClient.PutParameter(&ssm.PutParameterInput{ 97 Name: aws.String(name), 98 Value: aws.String(string(chunk)), 99 Tags: converters.MapToSSMTags(tags), 100 Type: aws.String("SecureString"), 101 }) 102 if err != nil { 103 return false, err 104 } 105 return true, err 106 } 107 108 // forceDeleteSecretEntry deletes a single secret, ignoring if it is absent. 109 func (s *Service) forceDeleteSecretEntry(name string) error { 110 _, err := s.SSMClient.DeleteParameter(&ssm.DeleteParameterInput{ 111 Name: aws.String(name), 112 }) 113 if awserrors.IsNotFound(err) { 114 return nil 115 } 116 return err 117 } 118 119 // Delete the secret belonging to a machine from AWS SSM. 120 func (s *Service) Delete(m *scope.MachineScope) error { 121 var errs []error 122 for i := int32(0); i < m.GetSecretCount(); i++ { 123 if err := s.forceDeleteSecretEntry(fmt.Sprintf("%s/%d", m.GetSecretPrefix(), i)); err != nil { 124 errs = append(errs, err) 125 } 126 } 127 128 return kerrors.NewAggregate(errs) 129 }