diff --git a/action.yml b/action.yml index 9219a5131..654a4ab42 100644 --- a/action.yml +++ b/action.yml @@ -100,6 +100,18 @@ inputs: upload-plan-destination-s3-bucket: description: Name of the destination bucket for AWS S3. Should be provided if destination == aws required: false + upload-plan-destination-s3-encryption-enabled: + description: If encryption is to be enabled for s3 bucket + required: false + default: "false" + upload-plan-destination-s3-encryption-type: + description: the type of encryption to use for the S3 bucket, either AES256 or KMS + required: false + default: "AES256" + upload-plan-destination-s3-encryption-kms-key-id: + description: for encryption of type KMS you need to specify the KMS key ID to use + required: false + upload-plan-destination-gcp-bucket: description: Name of the destination bucket for a GCP bucket. Should be provided if destination == gcp required: false @@ -367,6 +379,9 @@ runs: shell: bash env: PLAN_UPLOAD_DESTINATION: ${{ inputs.upload-plan-destination }} + PLAN_UPLOAD_S3_ENCRYPTION_ENABLED: ${{ inputs.upload-plan-destination-s3-encryption-enabled }} + PLAN_UPLOAD_S3_ENCRYPTION_TYPE: ${{ inputs.upload-plan-destination-s3-encryption-type }} + PLAN_UPLOAD_S3_ENCRYPTION_KMS_ID: ${{ inputs.upload-plan-destination-s3-encryption-kms-key-id }} GOOGLE_STORAGE_LOCK_BUCKET: ${{ inputs.google-lock-bucket }} GOOGLE_STORAGE_PLAN_ARTEFACT_BUCKET: ${{ inputs.upload-plan-destination-gcp-bucket }} AWS_S3_BUCKET: ${{ inputs.upload-plan-destination-s3-bucket }} @@ -404,6 +419,9 @@ runs: env: actionref: ${{ github.action_ref }} PLAN_UPLOAD_DESTINATION: ${{ inputs.upload-plan-destination }} + PLAN_UPLOAD_S3_ENCRYPTION_ENABLED: ${{ inputs.upload-plan-destination-s3-encryption-enabled }} + PLAN_UPLOAD_S3_ENCRYPTION_TYPE: ${{ inputs.upload-plan-destination-s3-encryption-type }} + PLAN_UPLOAD_S3_ENCRYPTION_KMS_ID: ${{ inputs.upload-plan-destination-s3-encryption-kms-key-id }} GOOGLE_STORAGE_LOCK_BUCKET: ${{ inputs.google-lock-bucket }} GOOGLE_STORAGE_PLAN_ARTEFACT_BUCKET: ${{ inputs.upload-plan-destination-gcp-bucket }} AWS_S3_BUCKET: ${{ inputs.upload-plan-destination-s3-bucket }} diff --git a/libs/storage/aws_plan_storage.go b/libs/storage/aws_plan_storage.go index 32cd65a55..a58d3cd68 100644 --- a/libs/storage/aws_plan_storage.go +++ b/libs/storage/aws_plan_storage.go @@ -24,12 +24,53 @@ type S3Client interface { DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) } +type AwsS3EncryptionType string + +const ( + ServerSideEncryptionAes256 AwsS3EncryptionType = "AES256" + ServerSideEncryptionAwsKms AwsS3EncryptionType = "aws:kms" +) + type PlanStorageAWS struct { - Client S3Client - Bucket string - Context context.Context + Client S3Client + Bucket string + Context context.Context + EncryptionEnabled bool + EncryptionType AwsS3EncryptionType + KMSEncryptionId string } +func NewAWSPlanStorage(bucketName string, encryptionEnabled bool, encryptionType string, KMSEncryptionId string) (*PlanStorageAWS, error) { + if bucketName == "" { + return nil, fmt.Errorf("AWS_S3_BUCKET is not defined") + } + ctx, client, err := GetAWSStorageClient() + if err != nil { + return nil, fmt.Errorf("could not retrieve aws storage client") + } + planStorage := &PlanStorageAWS{ + Context: ctx, + Client: client, + Bucket: bucketName, + } + if encryptionEnabled { + planStorage.EncryptionEnabled = true + if encryptionType == "AES256" { + planStorage.EncryptionType = ServerSideEncryptionAes256 + } else if encryptionType == "KMS" { + if KMSEncryptionId == "" { + return nil, fmt.Errorf("KMS encryption requested but no KMS key specified") + } + planStorage.EncryptionType = ServerSideEncryptionAwsKms + planStorage.KMSEncryptionId = KMSEncryptionId + } else { + return nil, fmt.Errorf("unknown encryption type specified for aws plan bucket: %v", encryptionType) + } + } + + return planStorage, nil + +} func (psa *PlanStorageAWS) PlanExists(artifactName, storedPlanFilePath string) (bool, error) { input := &s3.HeadObjectInput{ Bucket: aws.String(psa.Bucket), @@ -59,6 +100,15 @@ func (psa *PlanStorageAWS) StorePlanFile(fileContents []byte, artifactName, file Bucket: aws.String(psa.Bucket), Key: aws.String(fileName), } + + // support for encryption + if psa.EncryptionEnabled { + input.ServerSideEncryption = types.ServerSideEncryption(psa.EncryptionType) + if psa.EncryptionType == ServerSideEncryptionAwsKms { + input.SSEKMSKeyId = aws.String(psa.KMSEncryptionId) + } + } + _, err := psa.Client.PutObject(psa.Context, input) if err != nil { log.Printf("Failed to write file to bucket: %v", err) diff --git a/libs/storage/plan_storage.go b/libs/storage/plan_storage.go index 90a296e4f..ac84dd9b5 100644 --- a/libs/storage/plan_storage.go +++ b/libs/storage/plan_storage.go @@ -235,18 +235,14 @@ func NewPlanStorage(ghToken string, ghRepoOwner string, ghRepositoryName string, Context: ctx, } case uploadDestination == "aws": - ctx, client, err := GetAWSStorageClient() - if err != nil { - return nil, fmt.Errorf(fmt.Sprintf("Failed to create AWS storage client: %s", err)) - } bucketName := strings.ToLower(os.Getenv("AWS_S3_BUCKET")) - if bucketName == "" { - return nil, fmt.Errorf("AWS_S3_BUCKET is not defined") - } - planStorage = &PlanStorageAWS{ - Context: ctx, - Client: client, - Bucket: bucketName, + encryptionEnabled := os.Getenv("PLAN_UPLOAD_S3_ENCRYPTION_ENABLED") == "true" + encryptionType := os.Getenv("PLAN_UPLOAD_S3_ENCRYPTION_TYPE") + encryptionKmsId := os.Getenv("PLAN_UPLOAD_S3_ENCRYPTION_KMS_ID") + var err error + planStorage, err = NewAWSPlanStorage(bucketName, encryptionEnabled, encryptionType, encryptionKmsId) + if err != nil { + return nil, fmt.Errorf("error while creating AWS plan storage: %v", err) } case uploadDestination == "gitlab": //TODO implement me