S3 Bucket Data Encrypted at Rest

Violation ID: BC_AWS_S3_14

Ensure all Data Stored in the S3 Bucket is Securely Encrypted At Rest

Description

SSE helps prevent unauthorized access to S3 buckets.

Rationale

Encrypting and decrypting data at the S3 bucket level is transparent to users when accessing data.

Automated Remediation

Runtime Resource

The description below describes the steps taken by the Playbook for automated remediation of a violation of this policy.

s3Encrypt
Adds policies to S3 buckets to restrict uploads using AES-256 encryption-at-rest.

const AWS = require('aws-sdk');
const path = require('path');

const fileName = path.basename(__filename);

const ENCRYPTION_CONFIGURATION = { Rules: [{ ApplyServerSideEncryptionByDefault: { SSEAlgorithm: 'AES256' } }] };

const transformResources = (buckets) => buckets.map((bucket) => (bucket.includes(':') ? bucket.split(':').pop() : bucket));

const doRunRemediation = async (event) => {
    const buckets = transformResources(event.resources);
    if (!buckets) {
        console.log('No buckets found in params');
        return Promise.reject({ code: 400, message: 's3Encrypt requires a "buckets" param' });
    }

    const S3 = event.sts ? new AWS.S3(event.sts) : new AWS.S3();

    return Promise.all(buckets.map((bucket) => S3.getBucketEncryption({ Bucket: bucket }).promise().then(async (response) => {
        // eslint-disable-next-line no-restricted-syntax
        for (const rule of response.ServerSideEncryptionConfiguration.Rules) {
            if (rule.ApplyServerSideEncryptionByDefault) {
                return {
                    bucket,
                    result: `Default encryption is already set to ${rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm}`,
                    isSuccess: true,
                    undoable: false
                };
            }
        }

        return {
            bucket,
            result: `Unhandled default encryption policy: ${JSON.stringify(response)}`,
            isSuccess: false,
            undoable: false
        };
    }).catch(async (error) => {
        if (error.code === 'ServerSideEncryptionConfigurationNotFoundError') {
            return S3.putBucketEncryption({ Bucket: bucket, ServerSideEncryptionConfiguration: ENCRYPTION_CONFIGURATION }).promise().then(() => ({
                bucket,
                result: 'Set the bucket\'s default encryption policy to AES256',
                startState: '',
                endState: ENCRYPTION_CONFIGURATION,
                undoable: true,
                isSuccess: true
            })).catch((err) => ({ bucket, result: `Couldn't create set a default bucket encryption, error: ${err.message}` }));
        }
        if (error.code === 'NoSuchBucket') {
            return Promise.reject({
                bucket,
                result: 'The bucket does not exist. Perhaps it has been removed.',
                isSuccess: false,
                undoable: false
            });
        }
        console.log(error);
        return Promise.reject({ bucket, result: `Encountered the error ${error.code}` });
    })));
};

const getCli = (resources) => `npm i aws-sdk && node ${fileName} -b "${resources.join(',')}"`;

const undo = async (sts, results) => {
    const S3 = sts ? new AWS.S3(sts) : new AWS.S3();

    // eslint-disable-next-line no-param-reassign
    results = results.filter((result) => result.isSuccess);
    return Promise.all(results.map(async (bucketResult) => {
        if (bucketResult.endState) {
            // eslint-disable-next-line consistent-return
            return S3.getBucketEncryption({ Bucket: bucketResult.bucket }).promise().then(async (bucketEncryption) => {
                if (JSON.stringify(bucketEncryption.ServerSideEncryptionConfiguration.Rules) === JSON.stringify(bucketResult.endState.Rules)) {
                    if (bucketResult.startState === '') {
                        return S3.deleteBucketEncryption({ Bucket: bucketResult.bucket }).promise().then(() => ({ Bucket: bucketResult.bucket, result: 'Success' }));
                    }
                    throw new Error(`Unknown startState: ${JSON.stringify(bucketResult.startState)}`);
                }
            }).catch((error) => {
                if (error.code === 'ServerSideEncryptionConfigurationNotFoundError') {
                    return { Bucket: bucketResult.bucket, result: 'Nothing to undo' };
                }
                return Promise.reject({ bucket: bucketResult.bucket, result: `Encountered the error ${error.code}` });
            });
        }
        return { bucket: bucketResult.bucket, result: 'Nothing to undo' };
    })).then(() => 'Success');
};

// Main endpoint
if (process.env.IS_LAMBDA) {
    console.log('Waiting for lambda trigger');
} else {
    const args = process.argv.slice(2, process.argv.length);
    let profile;
    let bucketsString;
    for (let i = 0; i < args.length; i++) {
        switch (args[i]) {
            case '-b':
            case '-buckets':
                i++;
                bucketsString = args[i];
                break;
            case '-p':
            case '-profile':
                i++;
                profile = args[i];
                break;
            case '-h':
            case 'help':
            case '-help':
            default:
                console.error('Bad params\n');
                console.log('Synopsis:\n\nThis script adds encryption settings to a list of buckets.\n'
                + 'To launch it, supply the bucket names and the profile, as set in your AWS credentials file, as such:\n'
                + `node ${fileName}\n`
                + 'Parameters: \n-p / -profile\tThe AWS profile to be used, as defined in the AWS credentials file\n'
                + '-b / -buckets\tA list of bucket names, separated by commas, to be configured.\n\n\n'
                + `Example:\n node ${fileName} -p dev -b "bucket-name1,bucket-2.yourcompany.com,another-bucket.number-3"\n`);
                process.exit(1);
        }
    }

    if (bucketsString) {
        if (profile) {
            console.log(`using profile: ${profile}`);
            AWS.config.credentials = new AWS.SharedIniFileCredentials({ profile });
        }
        doRunRemediation({ resources: bucketsString.split(',') }, AWS).then(console.log).catch(console.log);
    } else {
        console.error('\nMissing the buckets parameter. Please add '
            + '-b YOUR_BUCKETS_STRING and try again\n'
            + `For example: node ${fileName} -b "mybucket1,org.mybucket2.site,tests-bucket.bridgecrew" -p prod\n\n`
            + `For help, type "node ${fileName} -h"\n`);
    }
}

const description = 'Adds policies to the buckets which restricts all uploads to use AES-256 encryption-at-rest';

const params = [];

const example = `npm i aws-sdk && node ${fileName} -b "BUCKET1,BUCKET2,BUCKET17" -p dev`;

module.exports = {
    params,
    example,
    description,
    doRunRemediation,
    getCli,
    undo
};

Buildtime Resource

Add the following block to a Terraform S3 resource to add AES-256 encryption:

server_side_encryption_configuration {
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

Manual Remediation

Runtime Resource

Procedure

To change the policy using the AWS Console, follow these steps:

  1. Login to the AWS Management Console at https://console.aws.amazon.com/.
  2. Open the S3 console.
  3. Select the name of the bucket that you want from the Bucket name list.
  4. Select Properties.
  5. Select Default encryption.
  6. To use keys that are managed by Amazon S3 for default encryption, select AES-256, then select Save.
  7. If you want to use CMKs that are stored in AWS KMS for default encryption, follow these steps:
    1. Select AWS-KMS.
    2. Select a customer-managed AWS KMS CMK that you have created, using one of these methods:
      a) In the list that appears, select the AWS KMS CMK.
      b) In the list that appears, select Custom KMS ARN, and then enter the Amazon Resource Name of the AWS KMS CMK.
    3. Select Save.

The steps above will encrypt all new files going forward. To encrypt all existing files, follow the steps below. Note that this will appear as an object modification, which will be logged if access logging is configured, and will count as a bucket write operation for billing purposes. Be mindful of applying these steps on large buckets.

  1. Navigate to the bucket Overview tab.
  2. Select the objects to encrypt.
  3. From the Actions dropdown, select Change encryption.
  4. Select the desired encryption method, and click Save.
  5. Note the progress bar for the background job at the bottom of the screen.

CLI Command

To set encryption at the bucket level for all new objects, use the following command:

aws s3api put-bucket-encryption --bucket awsexamplebucket --server-side-encryption-configuration '{"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]}'

The command above will not encrypt existing objects. To do so, you must re-add each file with encryption. You can copy a single object back to itself encrypted with SSE-S3 (server-side encryption with Amazon S3-managed keys), using the following S3 Encrypt command:

aws s3 cp s3://awsexamplebucket/myfile s3://awsexamplebucket/myfile --sse AES256

Buildtime Resource

Procedure

Add the following block to a Terraform S3 resource to add AES-256 encryption:

server_side_encryption_configuration {
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

For CloudFormation, use the following block under Properties:

JSON:

"BucketEncryption": {
  "ServerSideEncryptionConfiguration": [
    {
      "ServerSideEncryptionByDefault": {
        "SSEAlgorithm": "AES256"
      }
    }
  ]
}

YAML:

BucketEncryption: 
  ServerSideEncryptionConfiguration: 
  - ServerSideEncryptionByDefault:
      SSEAlgorithm: AES256

Updated about a month ago


S3 Bucket Data Encrypted at Rest


Violation ID: BC_AWS_S3_14

Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.