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  }