// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, // either express or implied. See the License for the specific language governing // permissions and limitations under the License. // Package s3util contains methods for interacting with S3. package s3util import ( "os" "github.com/aws/amazon-ssm-agent/agent/appconfig" "github.com/aws/amazon-ssm-agent/agent/backoffconfig" "github.com/aws/amazon-ssm-agent/agent/context" "github.com/aws/amazon-ssm-agent/agent/log" "github.com/aws/amazon-ssm-agent/agent/sdkutil" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/cenkalti/backoff/v4" ) var makeAwsConfig = sdkutil.AwsConfigForRegion var getS3Endpoint = GetS3Endpoint var getFallbackS3EndpointFunc = getFallbackS3Endpoint var backoffRetry = backoff.Retry var getBucketRegionFromSignedHeadBucketRequestFunc = getBucketRegionFromSignedHeadBucketRequest var getHttpProvider = func(logger log.T, appConfig appconfig.SsmagentConfig) HttpProvider { return HttpProviderImpl{ logger: logger, appConfig: appConfig, } } type IAmazonS3Util interface { S3Upload(log log.T, bucketName string, objectKey string, filePath string) error IsBucketEncrypted(log log.T, bucketName string) (bool, error) } type AmazonS3Util struct { myUploader *s3manager.Uploader } func shouldRetryS3Upload(err error) bool { // Don't retry if no error if err == nil { return false } if awsErr, ok := err.(awserr.Error); ok { code := awsErr.Code() if _, ok := awsErr.(s3manager.MultiUploadFailure); ok { return true } else if code == "ChecksumValidationError" || code == "InvalidChecksum" || code == "ReadRequestBody" || code == "BodyHashError" || code == "SerializationError" || code == "ReadError" || code == "ResponseTimeout" || code == "InternalError" || code == "SlowDown" { return true } return false } // Retry for any non-aws errors return true } func NewAmazonS3Util(context context.T, bucketName string) (res *AmazonS3Util, err error) { log := context.Log() sess, err := GetS3CrossRegionCapableSession(context, bucketName) if err == nil { res = &AmazonS3Util{ myUploader: s3manager.NewUploader(sess), } } else { log.Errorf("failed to create AmazonS3Util: %v", err) } return } // S3Upload uploads a file to s3. func (u *AmazonS3Util) S3Upload(log log.T, bucketName string, objectKey string, filePath string) (err error) { file, err := os.Open(filePath) if err != nil { log.Errorf("Failed to open file %v", err) return err } defer file.Close() log.Infof("Uploading %v to s3://%v/%v", filePath, bucketName, objectKey) params := &s3manager.UploadInput{ Bucket: aws.String(bucketName), Key: aws.String(objectKey), Body: file, ContentType: aws.String("text/plain"), ACL: aws.String("bucket-owner-full-control"), } if bucketEncrypted, sseAlgortihm, encryptionKey := getSSEAlgorithm(log, u, bucketName); bucketEncrypted == true { switch sseAlgortihm { case s3.ServerSideEncryptionAes256: params.ServerSideEncryption = aws.String(sseAlgortihm) case s3.ServerSideEncryptionAwsKms: params.ServerSideEncryption = aws.String(sseAlgortihm) if encryptionKey != "" { params.SSEKMSKeyId = aws.String(encryptionKey) } } } exponentialBackoff, err := backoffconfig.GetDefaultExponentialBackoff() if err != nil { log.Warnf("Failed to create backoff config with error: %v", err) return err } var result *s3manager.UploadOutput _ = backoffRetry(func() error { result, err = u.myUploader.Upload(params) if shouldRetryS3Upload(err) { log.Warnf("Failed uploading %v to s3://%v/%v err:%v - retrying", filePath, bucketName, objectKey, err) return err } return nil }, exponentialBackoff) if err != nil { log.Errorf("Failed to upload %v to s3://%v/%v err:%v", filePath, bucketName, objectKey, err) return err } log.Infof("Successfully uploaded file to %s", result.Location) return nil } // IsBucketEncrypted checks if the bucket is encrypted func (u *AmazonS3Util) IsBucketEncrypted(log log.T, bucketName string) (bool, error) { input := &s3.GetBucketEncryptionInput{ Bucket: aws.String(bucketName), } output, err := u.myUploader.S3.GetBucketEncryption(input) if err != nil { log.Errorf("Encountered an error while calling S3 API GetBucketEncryption %s", err) return false, err } bucketEncryption := *output.ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.SSEAlgorithm if bucketEncryption == s3.ServerSideEncryptionAwsKms || bucketEncryption == s3.ServerSideEncryptionAes256 { return true, nil } log.Errorf("S3 bucket %s is not encrypted", bucketName) return false, nil } func getSSEAlgorithm(log log.T, u *AmazonS3Util, bucketName string) (bucketEncrypted bool, sseAlgortihm string, encryptionKey string) { defer func() { if msg := recover(); msg != nil { log.Errorf("S3Upload panic: %v", msg) } }() input := &s3.GetBucketEncryptionInput{ Bucket: aws.String(bucketName), } output, err := u.myUploader.S3.GetBucketEncryption(input) if err != nil { log.Infof("Bucket is not encrypted") return false, "", "" } bucketEncryption := *output.ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault switch bucketEncryptionType := *bucketEncryption.SSEAlgorithm; bucketEncryptionType { case s3.ServerSideEncryptionAwsKms: // If bucket is KMS encrypted log.Infof("Bucket %v has been encrypted with KMS", bucketName) if bucketEncryption.KMSMasterKeyID != nil { return true, s3.ServerSideEncryptionAwsKms, *bucketEncryption.KMSMasterKeyID } else { return true, s3.ServerSideEncryptionAwsKms, "" } case s3.ServerSideEncryptionAes256: // If bucket is Aes256 encrypted log.Infof("Bucket %v has been encrypted with AES256", bucketName) return true, s3.ServerSideEncryptionAes256, "" default: log.Infof("Bucket is not encrypted") return false, "", "" } }