├── .npmrc
├── images
└── how-it-works.png
├── .gitignore
├── .eslintrc.json
├── runners
├── AutoRemediateOrganizations-002.runner.js
├── AutoRemediateS3-012.runner.js
├── AutoRemediateIAM-001.runner.js
├── AutoRemediateIAM-038.runner.js
├── AutoRemediateEBS-009.runner.js
├── AutoRemediateKMS-002.runner.js
├── AutoRemediateKMS-004.runner.js
├── AutoRemediateRS-019.runner.js
├── AutoRemediateS3-016.runner.js
├── AutoRemediateRDS-023.runner.js
├── AutoRemediateS3-014.runner.js
├── AutoRemediateEC2-002.runner.js
├── AutoRemediateEC2-005.runner.js
├── AutoRemediateEC2-019.runner.js
├── AutoRemediateGD-001.runner.js
├── AutoRemediateRS-023.runner.js
├── AutoRemediatLamda-003.runner.js
├── AutoRemediateVPC-001.runner.js
├── AutoRemediateKinesis-001.runner.js
├── AutoRemediateRDS-006.runner.js
├── AutoRemediateCT-003.runner.js
├── AutoRemediateSQS-004.runner.js
├── AutoRemediateTrustedAdvisor-003.runner.js
└── AutoRemediateS3-001.runner.js
├── functions
├── Utils.js
├── AutoRemediateKMS-002.js
├── AutoRemediateTrustedAdvisor-003.js
├── AutoRemediateOrganizations-002.js
├── AutoRemediateCT-003.js
├── AutoRemediateS3-012.js
├── AutoRemediateEC2-040.js
├── AutoRemediateKMS-004.js
├── AutoRemediateEC2-039.js
├── AutoRemediateEC2-043.js
├── AutoRemediateEC2-002.js
├── AutoRemediateEC2-038.js
├── AutoRemediateEC2-003.js
├── AutoRemediateEC2-004.js
├── AutoRemediateEC2-045.js
├── AutoRemediateGD-001.js
├── AutoRemediateRDS-006.js
├── AutoRemediateRDS-008.js
├── AutoRemediateEC2-005.js
├── AutoRemediateEC2-008.js
├── AutoRemediateS3-002.js
├── AutoRemediateS3-003.js
├── AutoRemediateEC2-006.js
├── AutoRemediateS3-004.js
├── AutoRemediateS3-005.js
├── AutoRemediateRS-001.js
├── AutoRemediateS3-006.js
├── AutoRemediateS3-008.js
├── AutoRemediateS3-007.js
├── AutoRemediateEBS-009.js
├── AutoRemediateRDS-023.js
├── AutoRemediateS3-009.js
├── AutoRemediateCFM-005.js
├── AutoRemediateRS-019.js
├── AutoRemediateS3-010.js
├── AutoRemediateEC2-019.js
├── AutoRemediateRS-023.js
├── AutoRemediateIAM-001.js
├── AutoRemediateIAM-038.js
├── AutoRemediateCT-001.js
├── AutoRemediateSQS-004.js
├── AutoRemediateKinesis-001.js
├── AutoRemediateOrchestrator.js
├── AutoRemediateS3-001.js
├── AutoRemediateLambda-003.js
├── AutoRemediateIAM-029.js
├── AutoRemediateS3-014.js
├── AutoRemediateS3-016.js
├── config.json
├── AutoRemediateVPC-001.js
└── AutoRemediateConfig-001.js
├── jest.config.js
├── LICENSE.md
├── utils
├── S3_utils.js
└── security_group_access_revoker.js
├── .github
└── workflows
│ ├── pull-request.yml
│ └── codeql-analysis.yml
├── package.json
├── test
├── utils
│ └── S3_utils.test.js
├── AutoRemediateS3-003.test.js
├── AutoRemediateS3-002.test.js
├── AutoRemediateS3-004.test.js
├── AutoRemediateS3-005.test.js
├── AutoRemediateS3-006.test.js
├── AutoRemediateS3-008.test.js
├── AutoRemediateS3-009.test.js
├── AutoRemediateS3-010.test.js
└── AutoRemediateS3-007.test.js
├── exclude-rules.json
└── README.md
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
2 | always-auth=false
3 |
--------------------------------------------------------------------------------
/images/how-it-works.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudconformity/auto-remediate/HEAD/images/how-it-works.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | .env
4 | *.env
5 | .serverless
6 | .idea
7 | coverage
8 | *.swp
9 | yarn.lock
10 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "jest/globals": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "standard",
9 | "plugin:jest/recommended",
10 | "plugin:jest/style"
11 | ],
12 | "plugins": [
13 | "standard",
14 | "jest"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/runners/AutoRemediateOrganizations-002.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 |
3 | }
4 |
5 | const AutoRemediate = require('../functions/AutoRemediateOrganizations-002')
6 |
7 | AutoRemediate.handler(event, {}, function (err, data) {
8 | console.log(err)
9 | console.log('data', JSON.stringify(data, null, 2))
10 | })
11 |
--------------------------------------------------------------------------------
/runners/AutoRemediateS3-012.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'YOUR_BUCKET_NAME'
3 | }
4 |
5 | const AutoRemediate = require('../functions/AutoRemediateS3-012')
6 |
7 | AutoRemediate.handler(event, {}, function (err, data) {
8 | console.log(err)
9 | console.log('data', JSON.stringify(data, null, 2))
10 | })
11 |
--------------------------------------------------------------------------------
/runners/AutoRemediateIAM-001.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'AKIA----------------'
3 | }
4 |
5 | const AutoRemediate = require('../functions/AutoRemediateIAM-001')
6 |
7 | AutoRemediate.handler(event, {}, function (err, data) {
8 | console.log(err)
9 | console.log('data', JSON.stringify(data, null, 2))
10 | })
11 |
--------------------------------------------------------------------------------
/runners/AutoRemediateIAM-038.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'AKIA----------------'
3 | }
4 |
5 | const AutoRemediate = require('../functions/AutoRemediateIAM-038')
6 |
7 | AutoRemediate.handler(event, {}, function (err, data) {
8 | console.log(err)
9 | console.log('data', JSON.stringify(data, null, 2))
10 | })
11 |
--------------------------------------------------------------------------------
/runners/AutoRemediateEBS-009.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'snap-41df37a1',
3 | region: 'us-east-1'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateEBS-009')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateKMS-002.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'your_kms_key_id',
3 | region: 'us-east-1'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateKMS-002')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateKMS-004.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'your_kms_key_id',
3 | region: 'us-east-1'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateKMS-004')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateRS-019.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'YOUR_RS_CLUSTER_ID',
3 | region: 'us-east-1'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateRS-019')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateS3-016.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'privatetest2',
3 | region: 'ap-southeast-2'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateS3-016')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateRDS-023.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'my-db-snapshot',
3 | region: 'ap-southeast-2b'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateRDS-023')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateS3-014.runner.js:
--------------------------------------------------------------------------------
1 |
2 | const event = {
3 | resource: 'privatetest2',
4 | region: 'ap-southeast-2'
5 | }
6 |
7 | const AutoRemediate = require('../functions/AutoRemediateS3-014')
8 |
9 | AutoRemediate.handler(event, {}, function (err, data) {
10 | console.log(err)
11 | console.log('data', JSON.stringify(data, null, 2))
12 | })
13 |
--------------------------------------------------------------------------------
/runners/AutoRemediateEC2-002.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'YOUR_SECURITY_GROUP_NAME',
3 | region: 'us-east-1'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateEC2-002')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateEC2-005.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'YOUR_SECURITY_GROUP_NAME',
3 | region: 'us-east-1'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateEC2-005')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateEC2-019.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'ami-0afd9aa15c54e1210',
3 | region: 'ap-southeast-2'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateEC2-019')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateGD-001.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | ruleId: 'GD-001',
3 | region: 'us-east-1',
4 | status: 'FAILURE'
5 | }
6 |
7 | const AutoRemediate = require('../functions/AutoRemediateGD-001')
8 |
9 | AutoRemediate.handler(event, {}, function (err, data) {
10 | console.log(err)
11 | console.log('data', JSON.stringify(data, null, 2))
12 | })
13 |
--------------------------------------------------------------------------------
/runners/AutoRemediateRS-023.runner.js:
--------------------------------------------------------------------------------
1 |
2 | const event = {
3 | resource: 'your_redshift_parmatet_group',
4 | region: 'us-east-1'
5 | }
6 |
7 | const AutoRemediate = require('../functions/AutoRemediateRS-023')
8 |
9 | AutoRemediate.handler(event, {}, function (err, data) {
10 | console.log(err)
11 | console.log('data', JSON.stringify(data, null, 2))
12 | })
13 |
--------------------------------------------------------------------------------
/runners/AutoRemediatLamda-003.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | resource: 'auto-remediate-v1-AutoRemediateIAM-038',
3 | region: 'ap-southeast-2'
4 | }
5 |
6 | const AutoRemediate = require('../functions/AutoRemediateLambda-003')
7 |
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateVPC-001.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | ruleId: 'VPC-001',
3 | resource: 'vpc-68c3000d',
4 | region: 'ap-southeast-2',
5 | status: 'FAILURE'
6 | }
7 | const AutoRemediate = require('../functions/AutoRemediateVPC-001')
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateKinesis-001.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | ruleId: 'Kinesis-001',
3 | resource: 'mystream',
4 | region: 'ap-southeast-2',
5 | status: 'FAILURE'
6 | }
7 | const AutoRemediate = require('../functions/AutoRemediateKinesis-001')
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateRDS-006.runner.js:
--------------------------------------------------------------------------------
1 |
2 | const event = {
3 | ruleId: 'RDS-006',
4 | resource: 'MY-RDS-INSTANCE',
5 | region: 'ap-southeast-2',
6 | status: 'FAILURE'
7 | }
8 | const AutoRemediate = require('../functions/AutoRemediateRDS-006')
9 | AutoRemediate.handler(event, {}, function (err, data) {
10 | console.log(err)
11 | console.log('data', JSON.stringify(data, null, 2))
12 | })
13 |
--------------------------------------------------------------------------------
/runners/AutoRemediateCT-003.runner.js:
--------------------------------------------------------------------------------
1 |
2 | const event = {
3 | ruleId: 'CT-003',
4 | resource: 'cloudtrail-global-logging-s3-bucket',
5 | region: 'ap-southeast-2',
6 | status: 'FAILURE'
7 | }
8 | const AutoRemediate = require('../functions/AutoRemediateCT-003')
9 | AutoRemediate.handler(event, {}, function (err, data) {
10 | console.log(err)
11 | console.log('data', JSON.stringify(data, null, 2))
12 | })
13 |
--------------------------------------------------------------------------------
/runners/AutoRemediateSQS-004.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | ruleId: 'SQS-004',
3 | resource: 'https://sqs.ap-southeast-2.amazonaws.com/12345678/mysqs',
4 | region: 'ap-southeast-2',
5 | status: 'FAILURE'
6 | }
7 | const AutoRemediate = require('../functions/AutoRemediateSQS-004')
8 | AutoRemediate.handler(event, {}, function (err, data) {
9 | console.log(err)
10 | console.log('data', JSON.stringify(data, null, 2))
11 | })
12 |
--------------------------------------------------------------------------------
/runners/AutoRemediateTrustedAdvisor-003.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | ruleId: 'TrustedAdvisor-003',
3 | resource: 'your_aws_key_id',
4 | region: 'ap-southeast-2',
5 | status: 'FAILURE'
6 | }
7 |
8 | const AutoRemediate = require('../functions/AutoRemediateTrustedAdvisor-003')
9 |
10 | AutoRemediate.handler(event, {}, function (err, data) {
11 | console.log(err)
12 | console.log('data', JSON.stringify(data, null, 2))
13 | })
14 |
--------------------------------------------------------------------------------
/functions/Utils.js:
--------------------------------------------------------------------------------
1 | const { STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts')
2 |
3 | module.exports = {
4 | /**
5 | * Returns AWS Account ID whose credentials are used to call the API
6 | *
7 | */
8 | getAccountId: async function () {
9 | const STS = new STSClient({ apiVersion: '2011-06-15' })
10 |
11 | const { Account: account } = await STS.send(new GetCallerIdentityCommand({}))
12 | return account
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/runners/AutoRemediateS3-001.runner.js:
--------------------------------------------------------------------------------
1 | const event = {
2 | ruleId: 'S3-001',
3 | ruleTitle: "S3 Bucket Public 'READ' Access",
4 | service: 'S3',
5 | region: 'us-east-1',
6 | riskLevel: 'VERY_HIGH',
7 | resource: 'open-bucket-created-by-mike',
8 | status: 'FAILURE'
9 | }
10 |
11 | const AutoRemediate = require('../functions/AutoRemediateS3-001')
12 |
13 | AutoRemediate.handler(event, {}, function (err, data) {
14 | console.log(err)
15 | console.log('data', JSON.stringify(data, null, 2))
16 | })
17 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | clearMocks: true,
3 | restoreMocks: true,
4 | resetModules: true,
5 | testEnvironment: 'node',
6 | testPathIgnorePatterns: ['/node_modules', '/runners'],
7 | collectCoverageFrom: ['functions/**/*.js', 'utils/**/*.js'],
8 | coverageReporters: ['json', 'lcov', 'json-summary', 'text'],
9 | coverageThreshold: {
10 | global: {
11 | statements: 15,
12 | branches: 10,
13 | functions: 15,
14 | lines: 15
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/functions/AutoRemediateKMS-002.js:
--------------------------------------------------------------------------------
1 |
2 | const { KMSClient, EnableKeyRotationCommand } = require('@aws-sdk/client-kms')
3 |
4 | /**
5 | * Lambda function to Enabled KMS Key Rotation
6 | */
7 |
8 | const handler = async (event) => {
9 | console.log('Enable AWS KMS Key Rotation - Received event:', JSON.stringify(event, null, 2))
10 |
11 | if (!event || !event.resource) {
12 | return handleError('Invalid event')
13 | }
14 |
15 | const params = {
16 | KeyId: event.resource
17 | }
18 |
19 | const KMS = new KMSClient({ region: event.region })
20 |
21 | try {
22 | const result = await KMS.send(new EnableKeyRotationCommand(params))
23 | console.log('Result', result)
24 | return 'Successfully processed event'
25 | } catch (err) {
26 | console.log('Error', err)
27 | return handleError(err.message ? err.message : 'Enable Key Rotation failed')
28 | }
29 |
30 | function handleError (message) {
31 | message = message || 'Failed to process request.'
32 | throw new Error(message)
33 | }
34 | }
35 |
36 | module.exports = { handler }
37 |
--------------------------------------------------------------------------------
/functions/AutoRemediateTrustedAdvisor-003.js:
--------------------------------------------------------------------------------
1 |
2 | const { IAMClient, UpdateAccessKeyCommand } = require('@aws-sdk/client-iam')
3 |
4 | /**
5 | * Lambda function disable exposed Amazon IAM access keys
6 | *
7 | */
8 |
9 | const handler = async (event) => {
10 | console.log('Exposed IAM Access Keys - Received event:', JSON.stringify(event, null, 2))
11 |
12 | if (!event || !event.resource) {
13 | return handleError('Invalid event')
14 | }
15 |
16 | const params = {
17 | AccessKeyId: event.resource,
18 | Status: 'Inactive'
19 | }
20 |
21 | const IAM = new IAMClient()
22 |
23 | try {
24 | const result = await IAM.send(new UpdateAccessKeyCommand(params))
25 | console.log('Result', result)
26 | return 'Successfully processed event'
27 | } catch (err) {
28 | console.log('Error', err)
29 | return handleError(err.message ? err.message : 'update Access Key failed')
30 | }
31 |
32 | function handleError (message) {
33 | message = message || 'Failed to process request.'
34 | throw new Error(message)
35 | }
36 | }
37 |
38 | module.exports = { handler }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateOrganizations-002.js:
--------------------------------------------------------------------------------
1 |
2 | const { OrganizationsClient, EnableAllFeaturesCommand } = require('@aws-sdk/client-organizations')
3 |
4 | /**
5 | * Lambda function to enable All Features for Organizations
6 | */
7 |
8 | const handler = async (event) => {
9 | console.log('Enable All Features - Received event:', JSON.stringify(event, null, 2))
10 |
11 | if (!event || !event.region) {
12 | return handleError('Invalid event')
13 | }
14 |
15 | const params = {
16 |
17 | }
18 |
19 | const Organizations = new OrganizationsClient({ region: event.region })
20 |
21 | try {
22 | const result = await Organizations.send(new EnableAllFeaturesCommand(params))
23 | console.log('Result', result)
24 | return 'Successfully processed event'
25 | } catch (err) {
26 | console.log('Error', err)
27 | return handleError(err.message ? err.message : 'Enable All Features failed')
28 | }
29 |
30 | function handleError (message) {
31 | message = message || 'Failed to process request.'
32 | throw new Error(message)
33 | }
34 | }
35 |
36 | module.exports = { handler }
37 |
--------------------------------------------------------------------------------
/functions/AutoRemediateCT-003.js:
--------------------------------------------------------------------------------
1 | const { S3Client, PutBucketAclCommand } = require('@aws-sdk/client-s3')
2 | /**
3 | * Lambda function to disable access to any AWS CloudTrail logging buckets that are publicly accessible
4 | *
5 | */
6 | module.exports.handler = async (event) => {
7 | console.log(' Publicly Shared AWS CloudTrail Logging Buckets - Received event:', JSON.stringify(event, null, 2))
8 | if (!event || !event.resource || !event.region) {
9 | return handleError('Invalid event')
10 | }
11 |
12 | const params = {
13 | Bucket: event.resource,
14 | ACL: 'private'
15 | }
16 |
17 | const S3 = new S3Client({ region: event.region })
18 |
19 | try {
20 | const result = await S3.send(new PutBucketAclCommand(params))
21 | console.log('Result', result)
22 | return 'Successfully processed event'
23 | } catch (err) {
24 | console.log('Error', err)
25 | return handleError(err.message ? err.message : 'Failed to putBucketAcl')
26 | }
27 |
28 | function handleError (message) {
29 | message = message || 'Failed to process request.'
30 | throw new Error(message)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-012.js:
--------------------------------------------------------------------------------
1 |
2 | const { S3Client, PutBucketVersioningCommand } = require('@aws-sdk/client-s3')
3 |
4 | /**
5 | * Lambda function to enable versioning on an s3 bucket
6 | */
7 |
8 | const handler = async (event) => {
9 | console.log('S3BucketVersioning - Received event:', JSON.stringify(event, null, 2))
10 |
11 | if (!event || !event.resource) {
12 | return handleError('Invalid event')
13 | }
14 |
15 | const params = {
16 | Bucket: event.resource,
17 | VersioningConfiguration: {
18 | Status: 'Enabled'
19 | }
20 | }
21 |
22 | const S3 = new S3Client()
23 |
24 | try {
25 | const result = await S3.send(new PutBucketVersioningCommand(params))
26 | console.log('Result', result)
27 | return 'Successfully processed event'
28 | } catch (err) {
29 | console.log('Error', err)
30 | return handleError(err.message ? err.message : 'enabling bucket versioning failed')
31 | }
32 |
33 | function handleError (message) {
34 | message = message || 'Failed to process request.'
35 | throw new Error(message)
36 | }
37 | }
38 |
39 | module.exports = { handler }
40 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-040.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_RPC_PORT = 135
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 135 from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedRPCAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_RPC_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing RPC access on port 135 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateKMS-004.js:
--------------------------------------------------------------------------------
1 |
2 | const { KMSClient, CancelKeyDeletionCommand } = require('@aws-sdk/client-kms')
3 |
4 | /**
5 | * Lambda function to make AWS KMS Customer Master Keys (CMK) that has been scheduled for deletion canceled
6 | */
7 |
8 | const handler = async (event) => {
9 | console.log('Recover KMS Customer Master Keys - Received event:', JSON.stringify(event, null, 2))
10 |
11 | if (!event || !event.resource) {
12 | return handleError('Invalid event')
13 | }
14 |
15 | const params = {
16 | KeyId: event.resource
17 | }
18 |
19 | const KMS = new KMSClient({ region: event.region })
20 |
21 | try {
22 | const result = await KMS.send(new CancelKeyDeletionCommand(params))
23 | console.log('Result', result)
24 | return 'Successfully processed event'
25 | } catch (err) {
26 | console.log('Error', err)
27 | return handleError(err.message ? err.message : 'Cancel Key Deletion failed')
28 | }
29 |
30 | function handleError (message) {
31 | message = message || 'Failed to process request.'
32 | throw new Error(message)
33 | }
34 | }
35 |
36 | module.exports = { handler }
37 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-039.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_SMTP_PORT = 25
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 25 from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedSMTPAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_SMTP_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing smtp access on port 25 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-043.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_CIFS_PORT = 445
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 445 from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedCIFSAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_CIFS_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing CIFS access on port 445 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-002.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_SSH_PORT = 22
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 22 from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedSSHAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_SSH_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing SSH database access on port 22 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-038.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_TELNET_PORT = 23
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 23 from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedTELNETAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_TELNET_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing telnet access on port 23 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Cloud Conformity
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-003.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_RDP_PORT = 3389
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 3389 from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedRDPAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_RDP_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing RDP database access on port RDP failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-004.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_ORACLE_PORT = 1521
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 1521 from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedORACLEAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_ORACLE_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing Oracle database access on port 1521 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-045.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_MONGO_PORT = 27017
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 27017 from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedMONGOAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_MONGO_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing MONGO database access on port 27017 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateGD-001.js:
--------------------------------------------------------------------------------
1 |
2 | const { GuardDutyClient, CreateDetectorCommand } = require('@aws-sdk/client-guardduty')
3 |
4 | /**
5 | * Lambda function to make Amazon GuardDuty service is currently enabled in order to protect your AWS environment and infrastructure
6 | *
7 | */
8 |
9 | const handler = async (event) => {
10 | console.log('GuardDuty In Use - Received event:', JSON.stringify(event, null, 2))
11 |
12 | if (!event || !event.region) {
13 | return handleError('Invalid event')
14 | }
15 |
16 | const params = {
17 | Enable: true
18 | }
19 |
20 | const GuardDuty = new GuardDutyClient({ region: event.region })
21 |
22 | try {
23 | const result = await GuardDuty.send(new CreateDetectorCommand(params))
24 | console.log('Result', result)
25 | return 'Successfully processed event'
26 | } catch (err) {
27 | console.log('Error', err)
28 | return handleError(err.message ? err.message : 'Failed to enable GuardDuty')
29 | }
30 |
31 | function handleError (message) {
32 | message = message || 'Failed to process request.'
33 | throw new Error(message)
34 | }
35 | }
36 |
37 | module.exports = { handler }
38 |
--------------------------------------------------------------------------------
/utils/S3_utils.js:
--------------------------------------------------------------------------------
1 | const { GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
2 | const isEqual = require('lodash.isequal')
3 | const filterAclGrants = function (acl, grantToRemove) {
4 | return {
5 | Owner: acl.Owner,
6 | Grants: acl.Grants.filter(grant => !isEqual(grant, grantToRemove))
7 | }
8 | }
9 |
10 | const getAcl = async (s3, bucketName) => {
11 | var getAclParams = {
12 | Bucket: bucketName
13 | }
14 | return s3.send(new GetBucketAclCommand(getAclParams))
15 | }
16 |
17 | const putAcl = async (s3, bucketName, acl) => {
18 | const putAclParams = {
19 | Bucket: bucketName,
20 | AccessControlPolicy: acl
21 | }
22 | return s3.send(new PutBucketAclCommand(putAclParams))
23 | }
24 |
25 | const filterAcl = async (s3, bucketName, grantToRemove) => {
26 | const acl = await getAcl(s3, bucketName)
27 | const filteredAcl = await filterAclGrants(acl, grantToRemove)
28 | const putAclResponse = await putAcl(s3, bucketName, filteredAcl)
29 |
30 | console.log('result>' + JSON.stringify(putAclResponse))
31 | }
32 |
33 | module.exports = {
34 | filterAclGrants: filterAclGrants,
35 | filterAcl: filterAcl
36 | }
37 |
--------------------------------------------------------------------------------
/functions/AutoRemediateRDS-006.js:
--------------------------------------------------------------------------------
1 | const { RDSClient, ModifyDBInstanceCommand } = require('@aws-sdk/client-rds')
2 | /**
3 | * Lambda function to enable Update Minor Version flag for AWS RDS
4 | *
5 | */
6 | const handler = async (event) => {
7 | console.log(' Update Minor Version for RDS - Received event:', JSON.stringify(event, null, 2))
8 | if (!event || !event.resource || !event.region) {
9 | return handleError('Invalid event')
10 | }
11 |
12 | const params = {
13 | DBInstanceIdentifier: event.resource,
14 | AutoMinorVersionUpgrade: true,
15 | ApplyImmediately: true
16 |
17 | }
18 |
19 | const RDS = new RDSClient({ region: event.region })
20 |
21 | try {
22 | const result = await RDS.send(new ModifyDBInstanceCommand(params))
23 | console.log('Result', result)
24 | return 'Successfully processed event'
25 | } catch (err) {
26 | console.log('Error', err)
27 | return handleError(err.message ? err.message : 'Failed to modify DB Instance')
28 | }
29 |
30 | function handleError (message) {
31 | message = message || 'Failed to process request.'
32 | throw new Error(message)
33 | }
34 | }
35 |
36 | module.exports = { handler }
37 |
--------------------------------------------------------------------------------
/functions/AutoRemediateRDS-008.js:
--------------------------------------------------------------------------------
1 |
2 | const { RDSClient, ModifyDBInstanceCommand } = require('@aws-sdk/client-rds')
3 |
4 | /**
5 | * Lambda function to automatically remediate publicly accessible RDS instance
6 | *
7 | */
8 |
9 | const handler = async (event) => {
10 | console.log('PubliclyAccessible - Received event:', JSON.stringify(event, null, 2))
11 |
12 | if (!event || !event.ccrn || !event.resource || !event.region) {
13 | return handleError('Invalid event')
14 | }
15 |
16 | const params = {
17 | DBInstanceIdentifier: event.resource,
18 | PubliclyAccessible: false
19 | }
20 |
21 | const RDS = new RDSClient({ region: event.region })
22 |
23 | try {
24 | const result = await RDS.send(new ModifyDBInstanceCommand(params))
25 | console.log('Result', result)
26 | return 'Successfully processed event'
27 | } catch (err) {
28 | console.log('Error', err)
29 | return handleError(err.message ? err.message : 'Failed to modify DB Instance')
30 | }
31 |
32 | function handleError (message) {
33 | message = message || 'Failed to process request.'
34 | throw new Error(message)
35 | }
36 | }
37 |
38 | module.exports = { handler }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-005.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_MYSQL_PORT = 3306
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 3306 (used by MYSQL Database Server) from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedMYSQLAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_MYSQL_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing mysql database access on port 3306 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-008.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_MSSQL_PORT = 1433
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 1433 (used by MSSQL Database Server) from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedMSSQLAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_MSSQL_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing mssql database access on port 1433 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-002.js:
--------------------------------------------------------------------------------
1 | const utils = require('../utils/S3_utils')
2 | const { S3Client } = require('@aws-sdk/client-s3')
3 |
4 | const CCRuleCode = 'S3-002'
5 | const CCRuleName = 'BucketPublicReadAcpAccess'
6 |
7 | const readAcpAllUsers = {
8 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
9 | }
10 |
11 | function handleError (message) {
12 | message = message || 'Failed to process request.'
13 | throw new Error(message)
14 | }
15 |
16 | // look for and remove S3BucketPublicReadAccess
17 | const handler = async (event) => {
18 | console.log('S3', CCRuleName, ' - Received event:', JSON.stringify(event, null, 2))
19 |
20 | if (!event || !event.resource || event.ruleId !== CCRuleCode) {
21 | return handleError('Invalid event')
22 | }
23 |
24 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
25 | try {
26 | await utils.filterAcl(s3, event.resource, readAcpAllUsers)
27 | return 'Success'
28 | } catch (err) {
29 | console.log(err, err.stack)
30 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
31 | }
32 | }
33 |
34 | module.exports = {
35 | handler: handler
36 | }
37 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-003.js:
--------------------------------------------------------------------------------
1 |
2 | const utils = require('../utils/S3_utils')
3 | const { S3Client } = require('@aws-sdk/client-s3')
4 |
5 | const CCRuleCode = 'S3-003'
6 | const CCRuleName = 'BucketPublicWriteAccess'
7 |
8 | const writeAllUsers = {
9 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE'
10 | }
11 |
12 | function handleError (message) {
13 | message = message || 'Failed to process request.'
14 | throw new Error(message)
15 | }
16 |
17 | // look for and remove S3BucketPublicReadAccess
18 | const handler = async (event) => {
19 | console.log('S3', CCRuleName, ' - Received event:', JSON.stringify(event, null, 2))
20 |
21 | if (!event || !event.resource || event.ruleId !== CCRuleCode) {
22 | return handleError('Invalid event')
23 | }
24 |
25 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
26 |
27 | try {
28 | await utils.filterAcl(s3, event.resource, writeAllUsers)
29 | return 'Success'
30 | } catch (err) {
31 | console.log(err, err.stack)
32 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
33 | }
34 | }
35 |
36 | module.exports = {
37 | handler: handler
38 | }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-006.js:
--------------------------------------------------------------------------------
1 | const AccessRevoker = require('../utils/security_group_access_revoker')
2 |
3 | const TCP_PSS_PORT = 5432
4 | const PROTOCOL = 'tcp'
5 |
6 | /**
7 | * Lambda function to remove the inbound rule that allow unrestricted access through TCP port 5432 (used by PostgreSQL Database Server) from the selected EC2 security group.
8 | */
9 |
10 | module.exports.handler = (event, context, callback) => {
11 | console.log('UnrestrictedPOSTGRESAccess - Received event:', JSON.stringify(event, null, 2))
12 |
13 | if (!event || !event.resource || !event.region) {
14 | return handleError('Invalid event')
15 | }
16 |
17 | AccessRevoker.revoke(PROTOCOL, TCP_PSS_PORT, event.resource, event.region, function (err, result) {
18 | if (err) {
19 | console.log('Error', err)
20 | return handleError(err.message ? err.message : 'removing postgresql database access on port 5432 failed')
21 | }
22 | console.log('Result', result)
23 | return callback(null, 'Successfully processed event')
24 | })
25 |
26 | function handleError (message) {
27 | message = message || 'Failed to process request.'
28 | return callback(new Error(message))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-004.js:
--------------------------------------------------------------------------------
1 |
2 | const utils = require('../utils/S3_utils')
3 | const { S3Client } = require('@aws-sdk/client-s3')
4 |
5 | const CCRuleCode = 'S3-004'
6 | const CCRuleName = 'BucketPublicWriteAcpAccess'
7 |
8 | const writeAllUsers = {
9 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE_ACP'
10 | }
11 |
12 | function handleError (message) {
13 | message = message || 'Failed to process request.'
14 | throw new Error(message)
15 | }
16 |
17 | // look for and remove S3BucketPublicReadAccess
18 | const handler = async (event) => {
19 | console.log('S3', CCRuleName, ' - Received event:', JSON.stringify(event, null, 2))
20 |
21 | if (!event || !event.resource || event.ruleId !== CCRuleCode) {
22 | return handleError('Invalid event')
23 | }
24 |
25 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
26 |
27 | try {
28 | await utils.filterAcl(s3, event.resource, writeAllUsers)
29 | return 'Success'
30 | } catch (err) {
31 | console.log(err, err.stack)
32 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
33 | }
34 | }
35 |
36 | module.exports = {
37 | handler: handler
38 | }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-005.js:
--------------------------------------------------------------------------------
1 |
2 | const utils = require('../utils/S3_utils')
3 | const { S3Client } = require('@aws-sdk/client-s3')
4 |
5 | const CCRuleCode = 'S3-005'
6 | const CCRuleName = 'BucketPublicReadAcpAccess'
7 |
8 | const readAcpAllUsers = {
9 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'FULL_CONTROL'
10 | }
11 |
12 | function handleError (message) {
13 | message = message || 'Failed to process request.'
14 | throw new Error(message)
15 | }
16 |
17 | // look for and remove S3BucketPublicReadAccess
18 | const handler = async (event) => {
19 | console.log('S3', CCRuleName, ' - Received event:', JSON.stringify(event, null, 2))
20 |
21 | if (!event || !event.resource || event.ruleId !== CCRuleCode) {
22 | return handleError('Invalid event')
23 | }
24 |
25 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
26 |
27 | try {
28 | await utils.filterAcl(s3, event.resource, readAcpAllUsers)
29 | return 'Success'
30 | } catch (err) {
31 | console.log(err, err.stack)
32 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
33 | }
34 | }
35 |
36 | module.exports = {
37 | handler: handler
38 | }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateRS-001.js:
--------------------------------------------------------------------------------
1 |
2 | const { RedshiftClient, ModifyClusterCommand } = require('@aws-sdk/client-redshift')
3 |
4 | /**
5 | * Lambda function to make Redshift Cluster private to minimise security risks
6 | *
7 | */
8 |
9 | const handler = async (event) => {
10 | console.log('ClusterPubliclyAccessible - Received event:', JSON.stringify(event, null, 2))
11 |
12 | if (!event || !event.ccrn || !event.resource || !event.region) {
13 | return handleError('Invalid event')
14 | }
15 |
16 | const params = {
17 | ClusterIdentifier: event.resource,
18 | PubliclyAccessible: false
19 | }
20 |
21 | const Redshift = new RedshiftClient({ region: event.region })
22 |
23 | try {
24 | const result = await Redshift.send(new ModifyClusterCommand(params))
25 | console.log('Result', result)
26 | return 'Successfully processed event'
27 | } catch (err) {
28 | console.log('Error', err)
29 | return handleError(err.message ? err.message : 'Failed to make Redshift cluster private')
30 | }
31 |
32 | function handleError (message) {
33 | message = message || 'Failed to process request.'
34 | throw new Error(message)
35 | }
36 | }
37 |
38 | module.exports = { handler }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-006.js:
--------------------------------------------------------------------------------
1 |
2 | const utils = require('../utils/S3_utils')
3 | const { S3Client } = require('@aws-sdk/client-s3')
4 |
5 | const CCRuleCode = 'S3-006'
6 | const CCRuleName = 'BucketAuthenticatedUsersReadAccess'
7 |
8 | const readAuthenticatedUsers = {
9 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ'
10 | }
11 |
12 | function handleError (message) {
13 | message = message || 'Failed to process request.'
14 | throw new Error(message)
15 | }
16 |
17 | // look for and remove S3 BucketAuthenticatedUsersReadAccess
18 | const handler = async (event) => {
19 | console.log('S3', CCRuleName, ' - Received event:', JSON.stringify(event, null, 2))
20 |
21 | if (!event || !event.resource || event.ruleId !== CCRuleCode) {
22 | return handleError('Invalid event')
23 | }
24 |
25 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
26 |
27 | try {
28 | await utils.filterAcl(s3, event.resource, readAuthenticatedUsers)
29 | return 'Success'
30 | } catch (err) {
31 | console.log(err, err.stack)
32 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
33 | }
34 | }
35 |
36 | module.exports = {
37 | handler: handler
38 | }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-008.js:
--------------------------------------------------------------------------------
1 |
2 | const utils = require('../utils/S3_utils')
3 | const { S3Client } = require('@aws-sdk/client-s3')
4 |
5 | const CCRuleCode = 'S3-008'
6 | const CCRuleName = 'BucketAuthenticatedUsersWriteAccess'
7 |
8 | const writeAuthenticatedUsers = {
9 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE'
10 | }
11 |
12 | function handleError (message) {
13 | message = message || 'Failed to process request.'
14 | throw new Error(message)
15 | }
16 |
17 | // look for and remove S3 BucketAuthenticatedUsersWriteAccess
18 | const handler = async (event) => {
19 | console.log('S3', CCRuleName, ' - Received event:', JSON.stringify(event, null, 2))
20 |
21 | if (!event || !event.resource || event.ruleId !== CCRuleCode) {
22 | return handleError('Invalid event')
23 | }
24 |
25 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
26 |
27 | try {
28 | await utils.filterAcl(s3, event.resource, writeAuthenticatedUsers)
29 | return 'Success'
30 | } catch (err) {
31 | console.log(err, err.stack)
32 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
33 | }
34 | }
35 |
36 | module.exports = {
37 | handler: handler
38 | }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-007.js:
--------------------------------------------------------------------------------
1 |
2 | const utils = require('../utils/S3_utils')
3 | const { S3Client } = require('@aws-sdk/client-s3')
4 |
5 | const CCRuleCode = 'S3-007'
6 | const CCRuleName = 'BucketAuthenticatedUsersReadAcpAccess'
7 |
8 | const readAcpAuthenticatedUsers = {
9 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ_ACP'
10 | }
11 |
12 | function handleError (message) {
13 | message = message || 'Failed to process request.'
14 | throw new Error(message)
15 | }
16 |
17 | // look for and remove S3 BucketAuthenticatedUsersReadAcpAccess
18 | const handler = async (event) => {
19 | console.log('S3', CCRuleName, ' - Received event:', JSON.stringify(event, null, 2))
20 |
21 | if (!event || !event.resource || event.ruleId !== CCRuleCode) {
22 | return handleError('Invalid event')
23 | }
24 |
25 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
26 |
27 | try {
28 | await utils.filterAcl(s3, event.resource, readAcpAuthenticatedUsers)
29 | return 'Success'
30 | } catch (err) {
31 | console.log(err, err.stack)
32 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
33 | }
34 | }
35 |
36 | module.exports = {
37 | handler: handler
38 | }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEBS-009.js:
--------------------------------------------------------------------------------
1 | const { EC2Client, ModifySnapshotAttributeCommand } = require('@aws-sdk/client-ec2')
2 | /**
3 | * Lambda function enforce Elastic Block Store (EBS) volume snapshots not to be public
4 | *
5 | */
6 | const handler = async (event) => {
7 | console.log('Publicly Accessible EBS Snapshot - Received event:', JSON.stringify(event, null, 2))
8 | if (!event || !event.resource || !event.region) {
9 | return handleError('Invalid event')
10 | }
11 |
12 | const params = {
13 | SnapshotId: event.resource,
14 | Attribute: 'createVolumePermission',
15 | OperationType: 'remove',
16 | GroupNames: [
17 | 'all'
18 | ]
19 |
20 | }
21 |
22 | const EC2 = new EC2Client({ region: event.region })
23 |
24 | try {
25 | const result = await EC2.send(new ModifySnapshotAttributeCommand(params))
26 | console.log('Result', result)
27 | return 'Successfully processed event'
28 | } catch (err) {
29 | console.log('Error', err)
30 | return handleError(err.message ? err.message : 'Failed to modify DB Instance')
31 | }
32 |
33 | function handleError (message) {
34 | message = message || 'Failed to process request.'
35 | throw new Error(message)
36 | }
37 | }
38 |
39 | module.exports = { handler }
40 |
--------------------------------------------------------------------------------
/functions/AutoRemediateRDS-023.js:
--------------------------------------------------------------------------------
1 |
2 | const { RDSClient, ModifyDBSnapshotAttributeCommand } = require('@aws-sdk/client-rds')
3 |
4 | /**
5 | * Lambda function to enforce AWS Relational Database Service (RDS) database snapshots not to be shared publicly
6 | */
7 |
8 | const handler = async (event) => {
9 | console.log('RDS Snapshot Publicly Accessible - Received event:', JSON.stringify(event, null, 2))
10 |
11 | if (!event || !event.resource) {
12 | return handleError('Invalid event')
13 | }
14 |
15 | const params = {
16 | DBSnapshotIdentifier: event.resource,
17 | AttributeName: 'restore',
18 | ValuesToRemove: [
19 | 'all'
20 | ]
21 |
22 | }
23 |
24 | const RDS = new RDSClient({ region: event.region })
25 |
26 | try {
27 | const result = await RDS.send(new ModifyDBSnapshotAttributeCommand(params))
28 | console.log('Result', result)
29 | return 'Successfully processed event'
30 | } catch (err) {
31 | console.log('Error', err)
32 | return handleError(err.message ? err.message : 'Modify Db Snapshot failed')
33 | }
34 |
35 | function handleError (message) {
36 | message = message || 'Failed to process request.'
37 | throw new Error(message)
38 | }
39 | }
40 |
41 | module.exports = { handler }
42 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-009.js:
--------------------------------------------------------------------------------
1 |
2 | const utils = require('../utils/S3_utils')
3 | const { S3Client } = require('@aws-sdk/client-s3')
4 |
5 | const CCRuleCode = 'S3-009'
6 | const CCRuleName = 'BucketAuthenticatedUsersWriteAcpAccess'
7 |
8 | const authenticatedUsersWriteAcpAccess = {
9 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE_ACP'
10 | }
11 |
12 | function handleError (message) {
13 | message = message || 'Failed to process request.'
14 | throw new Error(message)
15 | }
16 |
17 | // look for and remove S3 BucketAuthenticatedUsersWriteAcpAccess
18 | const handler = async (event) => {
19 | console.log('S3', CCRuleName, ' - Received event:', JSON.stringify(event, null, 2))
20 |
21 | if (!event || !event.resource || event.ruleId !== CCRuleCode) {
22 | return handleError('Invalid event')
23 | }
24 |
25 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
26 |
27 | try {
28 | await utils.filterAcl(s3, event.resource, authenticatedUsersWriteAcpAccess)
29 | return 'Success'
30 | } catch (err) {
31 | console.log(err, err.stack)
32 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
33 | }
34 | }
35 |
36 | module.exports = {
37 | handler: handler
38 | }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateCFM-005.js:
--------------------------------------------------------------------------------
1 |
2 | const { CloudFormationClient, UpdateTerminationProtectionCommand } = require('@aws-sdk/client-cloudformation')
3 |
4 | /**
5 | * Lambda function to enable CloudFormation Stack Termination Protection
6 | *
7 | */
8 |
9 | const handler = async (event) => {
10 | console.log('StackTerminationProtection - Received event:', JSON.stringify(event, null, 2))
11 |
12 | if (!event || !event.ccrn || !event.resource || !event.region) {
13 | return handleError('Invalid event')
14 | }
15 |
16 | const params = {
17 | EnableTerminationProtection: true,
18 | StackName: event.resource
19 | }
20 |
21 | const CloudFormation = new CloudFormationClient({ region: event.region })
22 |
23 | try {
24 | const result = await CloudFormation.send(new UpdateTerminationProtectionCommand(params))
25 | console.log('Result', result)
26 | return 'Successfully processed event'
27 | } catch (err) {
28 | console.log('Error', err)
29 | return handleError(err.message ? err.message : 'Failed to enable CloudFormation Stack Termination Protection')
30 | }
31 |
32 | function handleError (message) {
33 | message = message || 'Failed to process request.'
34 | throw new Error(message)
35 | }
36 | }
37 |
38 | module.exports = { handler }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateRS-019.js:
--------------------------------------------------------------------------------
1 |
2 | const { RedshiftClient, ModifyClusterCommand } = require('@aws-sdk/client-redshift')
3 | const CONFIG = require('./config')['AutoRemediateRS-019']
4 |
5 | /**
6 | * Lambda function to Enable Automated Snapshot Retention Period for AWS Redshift
7 | */
8 |
9 | const handler = async (event) => {
10 | console.log('AWS Redshift Automated Snapshot Retention Period - Received event:', JSON.stringify(event, null, 2))
11 |
12 | if (!event || !event.resource) {
13 | return handleError('Invalid event')
14 | }
15 |
16 | const params = {
17 | ClusterIdentifier: event.resource,
18 | AutomatedSnapshotRetentionPeriod: CONFIG['RetentionPeriod']
19 |
20 | }
21 |
22 | const Redshift = new RedshiftClient({ region: event.region })
23 |
24 | try {
25 | const result = await Redshift.send(new ModifyClusterCommand(params))
26 | console.log('Result', result)
27 | return 'Successfully processed event'
28 | } catch (err) {
29 | console.log('Error', err)
30 | return handleError(err.message ? err.message : 'Modify Cluster failed')
31 | }
32 |
33 | function handleError (message) {
34 | message = message || 'Failed to process request.'
35 | throw new Error(message)
36 | }
37 | }
38 |
39 | module.exports = { handler }
40 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-010.js:
--------------------------------------------------------------------------------
1 |
2 | const utils = require('../utils/S3_utils')
3 | const { S3Client } = require('@aws-sdk/client-s3')
4 |
5 | const CCRuleCode = 'S3-010'
6 | const CCRuleName = 'BucketAuthenticatedUsersFullControlAccess'
7 |
8 | const authenticatedUsersFullControlAccess = {
9 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'FULL_CONTROL'
10 | }
11 |
12 | function handleError (message) {
13 | message = message || 'Failed to process request.'
14 | throw new Error(message)
15 | }
16 |
17 | // look for and remove S3 BucketAuthenticatedUsersFullControlAccess
18 | const handler = async (event) => {
19 | console.log('S3', CCRuleName, ' - Received event:', JSON.stringify(event, null, 2))
20 |
21 | if (!event || !event.resource || event.ruleId !== CCRuleCode) {
22 | return handleError('Invalid event')
23 | }
24 |
25 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
26 |
27 | try {
28 | await utils.filterAcl(s3, event.resource, authenticatedUsersFullControlAccess)
29 | return 'Success'
30 | } catch (err) {
31 | console.log(err, err.stack)
32 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
33 | }
34 | }
35 |
36 | module.exports = {
37 | handler: handler
38 | }
39 |
--------------------------------------------------------------------------------
/functions/AutoRemediateEC2-019.js:
--------------------------------------------------------------------------------
1 |
2 | const { EC2Client, ModifyImageAttributeCommand } = require('@aws-sdk/client-ec2')
3 |
4 | /**
5 | * Lambda function to disable access to EC2 AMI that publicly shared with other AWS accounts
6 | *
7 | */
8 |
9 | const handler = async (event) => {
10 | console.log('Publicly Shared AMI - Received event:', JSON.stringify(event, null, 2))
11 |
12 | if (!event || !event.resource || !event.region) {
13 | return handleError('Invalid event')
14 | }
15 |
16 | const params = {
17 | ImageId: event.resource,
18 | LaunchPermission: {
19 |
20 | Remove: [
21 | {
22 | Group: 'all'
23 | }
24 |
25 | ]
26 | }
27 | }
28 |
29 | const Ec2 = new EC2Client({ region: event.region })
30 |
31 | try {
32 | const result = await Ec2.send(new ModifyImageAttributeCommand(params))
33 | console.log('Result', result)
34 | return 'Successfully processed event'
35 | } catch (err) {
36 | console.log('Error', err)
37 | return handleError(err.message ? err.message : 'Failed to update ImageAttribute')
38 | }
39 |
40 | function handleError (message) {
41 | message = message || 'Failed to process request.'
42 | throw new Error(message)
43 | }
44 | }
45 |
46 | module.exports = { handler }
47 |
--------------------------------------------------------------------------------
/functions/AutoRemediateRS-023.js:
--------------------------------------------------------------------------------
1 |
2 | const { RedshiftClient, ModifyClusterParameterGroupCommand } = require('@aws-sdk/client-redshift')
3 |
4 | /**
5 | * Lambda function to enable Redshift User Activity Logging
6 | */
7 |
8 | const handler = async (event) => {
9 | console.log('Enable Redshift User Activity Logging - Received event:', JSON.stringify(event, null, 2))
10 |
11 | if (!event || !event.resource) {
12 | return handleError('Invalid event')
13 | }
14 |
15 | const params = {
16 | ParameterGroupName: event.resource,
17 | Parameters: [{
18 | ParameterName: 'enable_user_activity_logging',
19 | ParameterValue: 'true'
20 | }
21 | ]
22 | }
23 |
24 | const Redshift = new RedshiftClient({ region: event.region })
25 |
26 | try {
27 | const result = await Redshift.send(new ModifyClusterParameterGroupCommand(params))
28 | console.log('Result', result)
29 | return 'Successfully processed event'
30 | } catch (err) {
31 | console.log('Error', err)
32 | return handleError(err.message ? err.message : 'modify cluster parameter group failed')
33 | }
34 |
35 | function handleError (message) {
36 | message = message || 'Failed to process request.'
37 | throw new Error(message)
38 | }
39 | }
40 |
41 | module.exports = { handler }
42 |
--------------------------------------------------------------------------------
/functions/AutoRemediateIAM-001.js:
--------------------------------------------------------------------------------
1 | const { ClientIam, UpdateAccessKeyCommand } = require('@aws-sdk/client-iam')
2 |
3 | /**
4 | * Lambda function to deactivate access keys older than 30 days
5 | */
6 |
7 | const handler = async (event) => {
8 | console.log('AccessKeysRotated30Days - Received event:', JSON.stringify(event, null, 2))
9 | if (!event || !event.resource || !event.extradata) {
10 | return handleError('Invalid event')
11 | }
12 |
13 | const userName = (event.extradata || []).find(data => data.name === 'UserName')
14 |
15 | if (!userName || !userName.value) {
16 | return handleError('Cannot find IAM Username')
17 | }
18 |
19 | const params = {
20 | AccessKeyId: event.resource,
21 | Status: 'Inactive',
22 | UserName: userName.value
23 | }
24 |
25 | const IAM = new ClientIam()
26 |
27 | try {
28 | const result = await IAM.send(new UpdateAccessKeyCommand(params))
29 | console.log('Result', result)
30 | return 'Successfully processed event'
31 | } catch (err) {
32 | console.log('Error', err)
33 | return handleError(err.message ? err.message : 'update Access Key failed')
34 | }
35 |
36 | function handleError (message) {
37 | message = message || 'Failed to process request.'
38 | throw new Error(message)
39 | }
40 | }
41 |
42 | module.exports = { handler }
43 |
--------------------------------------------------------------------------------
/utils/security_group_access_revoker.js:
--------------------------------------------------------------------------------
1 | const { EC2Client, RevokeSecurityGroupIngressCommand } = require('@aws-sdk/client-ec2')
2 |
3 | const revokeSecurityGroupAccess = async (protocol, port, resource, region, callback) => {
4 | const ec2 = new EC2Client({ region: region })
5 |
6 | const params = {
7 | GroupId: resource,
8 | IpPermissions: [
9 | {
10 | FromPort: port,
11 | ToPort: port,
12 | IpProtocol: protocol,
13 | IpRanges: [{ CidrIp: '0.0.0.0/0' }]
14 | }
15 | ]
16 | }
17 | try {
18 | const revokeIngressResponse = await ec2.send(new RevokeSecurityGroupIngressCommand(params))
19 | console.log('Revoking %s, %s on 0.0.0.0/0 succeeded')
20 | console.log(revokeIngressResponse)
21 | } catch (err) {
22 | console.log('Revoking %s, %s on 0.0.0.0/0 failed')
23 | console.error(err)
24 | }
25 |
26 | try {
27 | params.IpPermissions[0].IpRanges[0].CidrIp = '::/0'
28 | const revokeIngressResponse = await ec2.send(new RevokeSecurityGroupIngressCommand(params))
29 | console.log('Revoking %s, %s on ::/0 succeeded')
30 | console.log(revokeIngressResponse)
31 | } catch (err) {
32 | console.log('Revoking %s, %s on ::/0 failed')
33 | console.error(err)
34 | }
35 |
36 | return callback(null, 'done')
37 | }
38 |
39 | exports.revoke = revokeSecurityGroupAccess
40 |
--------------------------------------------------------------------------------
/functions/AutoRemediateIAM-038.js:
--------------------------------------------------------------------------------
1 |
2 | const { IAMClient, UpdateAccessKeyCommand } = require('@aws-sdk/client-iam')
3 |
4 | /**
5 | * Lambda function to deactivate access keys older than 90 days
6 | */
7 |
8 | const handler = async (event) => {
9 | console.log('AWS IAM Access Keys Rotation- 90 Days - Received event:', JSON.stringify(event, null, 2))
10 | if (!event || !event.resource || !event.extradata) {
11 | return handleError('Invalid event')
12 | }
13 |
14 | const UserName = (event.extradata || []).filter(data => data.name === 'UserName')
15 |
16 | if (UserName.length !== 1 && !UserName[0].value) {
17 | return handleError('Cannot find IAM Username')
18 | }
19 |
20 | const params = {
21 | AccessKeyId: event.resource,
22 | Status: 'Inactive',
23 | UserName: UserName[0].value
24 | }
25 |
26 | const IAM = new IAMClient()
27 |
28 | try {
29 | const result = await IAM.send(new UpdateAccessKeyCommand(params))
30 | console.log('Result', result)
31 | return 'Successfully processed event'
32 | } catch (err) {
33 | console.log('Error', err)
34 | return handleError(err.message ? err.message : 'update Access Key failed')
35 | }
36 |
37 | function handleError (message) {
38 | message = message || 'Failed to process request.'
39 | throw new Error(message)
40 | }
41 | }
42 |
43 | module.exports = { handler }
44 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yml:
--------------------------------------------------------------------------------
1 | name: Lint and test
2 |
3 | on:
4 | pull_request
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | env:
10 | ARTIFACTORY_TOKEN: ${{ secrets.ARTIFACTORY_TOKEN }}
11 | ARTIFACTORY_TOKEN_EMAIL: ${{ secrets.ARTIFACTORY_TOKEN_EMAIL }}
12 | ARTIFACTORY_URL: ${{ secrets.ARTIFACTORY_URL_VIRTUAL }}
13 | steps:
14 | - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # refers to actions/checkout@v3.1.0
15 | with:
16 | persist-credentials: false
17 |
18 | # Install Node.js
19 | - uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # refers to actions/setup-node@v3.5.1
20 | with:
21 | node-version: 18
22 |
23 | - run: npm i npm@9 -g
24 | - run: npm config set registry https://$ARTIFACTORY_URL
25 | - run: npm config set replace-registry-host npm.pkg.github.com
26 | - run: npm config set //$ARTIFACTORY_URL:_auth "$(echo -n $ARTIFACTORY_TOKEN_EMAIL:$ARTIFACTORY_TOKEN | base64 -w 0)"
27 | - run: npm config set //$ARTIFACTORY_URL:email "$ARTIFACTORY_TOKEN_EMAIL"
28 |
29 | # Install your dependencies
30 | - name: Installing package.json...
31 | run: npm ci
32 |
33 | # Run ESLint
34 | - name: Running ESLint...
35 | run: npm run lint
36 |
37 | # Run Tests
38 | - name: Running tests...
39 | run: npm run test
--------------------------------------------------------------------------------
/functions/AutoRemediateCT-001.js:
--------------------------------------------------------------------------------
1 |
2 | const { CloudTrailClient, UpdateTrailCommand } = require('@aws-sdk/client-cloudtrail')
3 |
4 | const CONFIG = require('./config')['AutoRemediateCT-001']
5 |
6 | /**
7 | * Lambda function to automatically remediate CloudTrail not Global
8 | */
9 | const handler = async (event) => {
10 | console.log('CloudTrail not Global - Received event:', JSON.stringify(event, null, 2))
11 |
12 | if (!event || !event.region || event.ruleId !== 'CT-001') {
13 | return handleError('Invalid event')
14 | }
15 |
16 | const params = {
17 | Name: CONFIG['Name'],
18 | S3BucketName: CONFIG['S3BucketName'],
19 | IncludeGlobalServiceEvents: CONFIG['IncludeGlobalServiceEvents'],
20 | IsMultiRegionTrail: CONFIG['IsMultiRegionTrail'],
21 | S3KeyPrefix: CONFIG['S3KeyPrefix']
22 | }
23 |
24 | const CloudTrail = new CloudTrailClient({ apiVersion: '2013-11-01' })
25 |
26 | try {
27 | const result = await CloudTrail.send(new UpdateTrailCommand(params))
28 | console.log('Result', result)
29 | return 'Successfully processed event'
30 | } catch (err) {
31 | console.log('Error' + err)
32 | return handleError(err.message ? err.message : 'Make CloudTrail global in account failed')
33 | }
34 |
35 | function handleError (message) {
36 | message = message || 'Failed to process request.'
37 | throw new Error(message)
38 | }
39 | }
40 |
41 | module.exports = { handler }
42 |
--------------------------------------------------------------------------------
/functions/AutoRemediateSQS-004.js:
--------------------------------------------------------------------------------
1 | const { SQSClient, SetQueueAttributesCommand } = require('@aws-sdk/client-sqs')
2 | const CONFIG = require('./config')
3 |
4 | /**
5 | * Lambda function to enable Amazon Simple Queue Service (SQS) queues' message encryption using Server-Side Encryption (SSE)
6 | *
7 | */
8 |
9 | const handler = async (event) => {
10 | console.log(' Enable AWS SQS Server-Side Encryption for messages - Received event:', JSON.stringify(event, null, 2))
11 | if (!event || !event.resource || !event.region) {
12 | return handleError('Invalid event')
13 | }
14 | if (!CONFIG['AutoRemediateSQS-004']['KmsKeyId']) {
15 | return handleError('Missing KmsKeyId value')
16 | }
17 | const params = {
18 | Attributes: {
19 | KmsMasterKeyId: CONFIG['AutoRemediateSQS-004']['KmsKeyId']
20 | },
21 | QueueUrl: event.resource
22 | }
23 | const Sqs = new SQSClient({ region: event.region, apiVersion: '2012-11-05' })
24 | try {
25 | const result = await Sqs.send(new SetQueueAttributesCommand(params))
26 | console.log('Result', result)
27 | return 'Successfully processed event'
28 | } catch (err) {
29 | console.log('Error', err)
30 | return handleError(err.message ? err.message : 'Failed to setQueueAttributes ,KmsMasterKeyId ')
31 | }
32 |
33 | function handleError (message) {
34 | message = message || 'Failed to process request.'
35 | throw new Error(message)
36 | }
37 | }
38 |
39 | module.exports = { handler }
40 |
--------------------------------------------------------------------------------
/functions/AutoRemediateKinesis-001.js:
--------------------------------------------------------------------------------
1 | const CONFIG = require('./config')
2 | const { KinesisClient, StartStreamEncryptionCommand } = require('@aws-sdk/client-kinesis')
3 |
4 | /**
5 | * Lambda function to enable AWS Kinesis streams encryption using Server-Side Encryption (SSE)
6 | *
7 | */
8 |
9 | const handler = async (event) => {
10 | console.log(' Enable Kinesis Server-Side Encryption - Received event:', JSON.stringify(event, null, 2))
11 |
12 | if (!event || !event.resource || !event.region) {
13 | return handleError('Invalid event')
14 | }
15 |
16 | if (!CONFIG['AutoRemediateKinesis-001']['KmsKeyId']) {
17 | return handleError('Missing KmsKeyId value')
18 | }
19 |
20 | const params = {
21 | EncryptionType: 'KMS',
22 | KeyId: CONFIG['AutoRemediateKinesis-001']['KmsKeyId'],
23 | StreamName: event.resource
24 | }
25 |
26 | const Kinesis = new KinesisClient({ region: event.region, apiVersion: '2012-11-05' })
27 |
28 | try {
29 | const result = await Kinesis.send(new StartStreamEncryptionCommand(params))
30 | console.log('Result', result)
31 | return 'Successfully processed event'
32 | } catch (err) {
33 | console.log('Error', err)
34 | return handleError(err.message ? err.message : 'Failed to enable Kinesis Server Side Encryption')
35 | }
36 |
37 | function handleError (message) {
38 | message = message || 'Failed to process request.'
39 | throw new Error(message)
40 | }
41 | }
42 |
43 | module.exports = { handler }
44 |
--------------------------------------------------------------------------------
/functions/AutoRemediateOrchestrator.js:
--------------------------------------------------------------------------------
1 | const CONFIG = require('./config')
2 | const { LambdaClient, InvokeCommand } = require('@aws-sdk/client-lambda')
3 |
4 | const handler = async (event) => {
5 | console.log('Received event: ', JSON.stringify(event, null, 2))
6 | console.log('Config settings: ', JSON.stringify(CONFIG, null, 2))
7 | if (!event || !event.Records[0] || !event.Records[0].body) {
8 | throw new Error('No event specified')
9 | }
10 | const message = JSON.parse(event.Records[0].body)
11 | const AutoRemediate = 'AutoRemediate' + message.ruleId
12 | if (!CONFIG[`${AutoRemediate}`]) {
13 | console.log(
14 | 'The %s is not supported. Exiting gracefully ...',
15 | AutoRemediate
16 | )
17 | return
18 | }
19 | if (!CONFIG[`${AutoRemediate}`]['enabled']) {
20 | console.log('The %s is not enabled. Exiting gracefully ...', AutoRemediate)
21 | return
22 | }
23 | const FunctionName =
24 | process.env['AWS_LAMBDA_FUNCTION_NAME'].substring(
25 | 0,
26 | process.env['AWS_LAMBDA_FUNCTION_NAME'].lastIndexOf('-') + 1
27 | ) + AutoRemediate
28 | console.log(`Invoking ${FunctionName} ...`)
29 | const Lambda = new LambdaClient({
30 | region: process.env['AWS_REGION'],
31 | apiVersion: '2015-03-31'
32 | })
33 |
34 | try {
35 | const result = await Lambda.send(new InvokeCommand({
36 | FunctionName,
37 | Payload: JSON.stringify(message, null, 2) }
38 | ))
39 |
40 | return `Successfully invoked ${FunctionName} with result ${result}`
41 | } catch (error) {
42 | console.log(`Error occurred while invoking ${FunctionName}`)
43 | console.log(error)
44 | throw error
45 | }
46 | }
47 |
48 | module.exports = { handler }
49 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-001.js:
--------------------------------------------------------------------------------
1 |
2 | const { S3Client, GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
3 | const allUsersURI = 'http://acs.amazonaws.com/groups/global/AllUsers'
4 | const readPermission = 'READ'
5 |
6 | const CCRuleCode = 'S3-001'
7 |
8 | const aclNew = {
9 | Owner: '',
10 | Grants: []
11 | }
12 |
13 | function remediateAllUsers (thisGrant, newAcl) {
14 | if (thisGrant.Permission !== readPermission) { // any besides READ are passed through
15 | newAcl['Grants'].push(thisGrant)
16 | }
17 |
18 | return newAcl
19 | }
20 |
21 | // look for and remove S3BucketPublicReadAccess
22 | const handler = async (event) => {
23 | console.log(
24 | 'S3 BucketPublicReadAccess - Received event:',
25 | JSON.stringify(event, null, 2)
26 | )
27 |
28 | if (!event || !event.resource || event.ruleId !== 'S3-001') {
29 | throw new Error('Invalid event')
30 | }
31 |
32 | var s3 = new S3Client({ apiVersion: '2006-03-01' })
33 |
34 | var getAclParams = {
35 | Bucket: event.resource
36 | }
37 |
38 | try {
39 | const aclWas = await s3.send(new GetBucketAclCommand(getAclParams))
40 |
41 | aclNew.Owner = aclWas.Owner // transfer the existing bucket owner
42 | aclWas.Grants.forEach((grant, _) => {
43 | if (grant.Grantee.URI === allUsersURI) {
44 | remediateAllUsers(grant, aclNew)
45 | } else {
46 | const found = aclNew['Grants'].some(
47 | ({ Permission, Grantee: { ID, DisplayName, Type } }) => {
48 | return (
49 | grant.Permission === Permission &&
50 | grant.Grantee.DisplayName === DisplayName &&
51 | grant.Grantee.ID === ID &&
52 | grant.Grantee.Type === Type
53 | )
54 | }
55 | )
56 | if (!found) {
57 | aclNew['Grants'].push(grant)
58 | }
59 | }
60 | })
61 |
62 | var putAclParams = {
63 | Bucket: event.resource,
64 | AccessControlPolicy: aclNew
65 | }
66 | const result = await s3.send(new PutBucketAclCommand(putAclParams))
67 | console.log('result>' + JSON.stringify(result))
68 |
69 | return 'Success'
70 | } catch (err) {
71 | console.log(err, err.stack)
72 | throw new Error(`failed to auto-remediate ${CCRuleCode}: ${err}`)
73 | }
74 | }
75 |
76 | module.exports = { handler }
77 |
--------------------------------------------------------------------------------
/functions/AutoRemediateLambda-003.js:
--------------------------------------------------------------------------------
1 |
2 | const { IAMClient, AttachRolePolicyCommand } = require('@aws-sdk/client-iam')
3 | const { LambdaClient, GetFunctionConfigurationCommand, UpdateFunctionConfigurationCommand } = require('@aws-sdk/client-lambda')
4 | const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry')
5 |
6 | const { setTimeout } = require('timers/promises')
7 |
8 | // retry all requests with a 5 sec delay (if they are retry-able), up to 10 times
9 | const retryStrategy = new ConfiguredRetryStrategy(10, 5000)
10 |
11 | /**
12 | * Enable tracing for your AWS Lambda functions
13 | */
14 | const handler = async (event) => {
15 | console.log('Lambda Function Tracing Enabled - Received event:', JSON.stringify(event, null, 2))
16 |
17 | if (!event || !event.region) {
18 | return handleError('Invalid event')
19 | }
20 |
21 | const FunctionName = event.resource
22 |
23 | const Lambda = new LambdaClient()
24 | const IAM = new IAMClient({ retryStrategy })
25 |
26 | try {
27 | const lambdaConfig = await Lambda.send(new GetFunctionConfigurationCommand({ FunctionName }))
28 | console.log('Role ARN:', lambdaConfig.Role)
29 |
30 | const FunctionRoleName = lambdaConfig.Role.split('/')[1]
31 |
32 | const AttachRolePolicyParams = {
33 | PolicyArn: 'arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess',
34 | RoleName: FunctionRoleName
35 | }
36 |
37 | await IAM.send(new AttachRolePolicyCommand(AttachRolePolicyParams))
38 | console.log('Successfully attached AWSXray managed policy to the function role')
39 |
40 | // Wait 10 seconds to avoid Error: The provided execution role does not have permissions to call PutTraceSegments on XRAY
41 | await setTimeout(10000)
42 |
43 | const FunctionConfigurationParams = {
44 | FunctionName: FunctionName,
45 | TracingConfig: {
46 | Mode: 'Active'
47 | }
48 | }
49 |
50 | await Lambda.send(new UpdateFunctionConfigurationCommand(FunctionConfigurationParams))
51 | return 'Successfully updated function configuration'
52 | } catch (err) {
53 | console.log('Error', err)
54 | return handleError(err.message ? err.message : 'Failed to enable tracing for the function')
55 | }
56 |
57 | function handleError (message) {
58 | message = message || 'Failed to process request.'
59 | throw new Error(message)
60 | }
61 | }
62 |
63 | module.exports = { handler }
64 |
--------------------------------------------------------------------------------
/functions/AutoRemediateIAM-029.js:
--------------------------------------------------------------------------------
1 | const {
2 | IAMClient,
3 | DeleteUserCommand,
4 | DeleteUserPolicyCommand,
5 | DeleteLoginProfileCommand,
6 | ListAttachedUserPoliciesCommand,
7 | ListUserPoliciesCommand,
8 | DetachUserPolicyCommand,
9 | ListGroupsForUserCommand,
10 | RemoveUserFromGroupCommand
11 | } = require('@aws-sdk/client-iam')
12 |
13 | const handler = async ({ region, resource }) => {
14 | const client = new IAMClient({ region })
15 | resource = resource.replace('users-', '')
16 |
17 | await removeUserRelatedResources(client, resource)
18 |
19 | const deleteUser = new DeleteUserCommand({
20 | UserName: resource
21 | })
22 | await client.send(deleteUser)
23 | }
24 |
25 | // Before deleting an IAM user we must remove the related resources.
26 | // https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_remove.html
27 | const removeUserRelatedResources = async (client, username) => {
28 | const deleteLoginProfile = new DeleteLoginProfileCommand({
29 | UserName: username
30 | })
31 | await client.send(deleteLoginProfile)
32 |
33 | const listUserPolicies = new ListUserPoliciesCommand({
34 | UserName: username
35 | })
36 | const listUserPoliciesResponse = await client.send(listUserPolicies)
37 |
38 | for (const policyName of listUserPoliciesResponse.PolicyNames) {
39 | const deleteUserPolicy = new DeleteUserPolicyCommand({
40 | UserName: username,
41 | PolicyName: policyName
42 | })
43 | await client.send(deleteUserPolicy)
44 | }
45 |
46 | const listAttachedPolicies = new ListAttachedUserPoliciesCommand({
47 | UserName: username
48 | })
49 | const listAttachedPoliciesResponse = await client.send(listAttachedPolicies)
50 |
51 | for (const policy of listAttachedPoliciesResponse.AttachedPolicies) {
52 | const detachUserPolicy = new DetachUserPolicyCommand({
53 | UserName: username,
54 | PolicyArn: policy.PolicyArn
55 | })
56 | await client.send(detachUserPolicy)
57 | }
58 |
59 | const listGroups = new ListGroupsForUserCommand({
60 | UserName: username
61 | })
62 | const listGroupsResponse = await client.send(listGroups)
63 |
64 | for (const group of listGroupsResponse.Groups) {
65 | const removeUserFromGroup = new RemoveUserFromGroupCommand({
66 | UserName: username,
67 | GroupName: group.GroupName
68 | })
69 | await client.send(removeUserFromGroup)
70 | }
71 | }
72 |
73 | module.exports = {
74 | handler
75 | }
76 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auto-remediate",
3 | "version": "2.0.5",
4 | "description": "Auto Remediate",
5 | "main": "Orchestrator.js",
6 | "scripts": {
7 | "test": "jest",
8 | "test:watch": "npm test -- --watch",
9 | "coverage": "npm run test -- --coverage --silent",
10 | "lint": "eslint '**/*.js' --ignore-path .gitignore",
11 | "lint:fix": "npm run lint -- --fix",
12 | "sls:deploy": "npm prune --production && serverless deploy --force"
13 | },
14 | "author": "Mike Rahmati",
15 | "contributors": [
16 | "Edmund Cong"
17 | ],
18 | "repository": {
19 | "type": "git",
20 | "url": "ssh://git@github.com:cloudconformity/auto-remediate"
21 | },
22 | "license": "MIT",
23 | "dependencies": {
24 | "@aws-sdk/client-cloudformation": "^3.490.0",
25 | "@aws-sdk/client-cloudtrail": "^3.490.0",
26 | "@aws-sdk/client-config-service": "^3.490.0",
27 | "@aws-sdk/client-ec2": "^3.496.0",
28 | "@aws-sdk/client-guardduty": "^3.496.0",
29 | "@aws-sdk/client-iam": "^3.490.0",
30 | "@aws-sdk/client-kinesis": "^3.496.0",
31 | "@aws-sdk/client-kms": "^3.496.0",
32 | "@aws-sdk/client-lambda": "^3.496.0",
33 | "@aws-sdk/client-organizations": "^3.497.0",
34 | "@aws-sdk/client-rds": "^3.497.0",
35 | "@aws-sdk/client-redshift": "^3.496.0",
36 | "@aws-sdk/client-s3": "^3.490.0",
37 | "@aws-sdk/client-sqs": "^3.496.0",
38 | "@aws-sdk/client-sts": "^3.496.0",
39 | "@aws-sdk/util-retry": "^3.374.0",
40 | "lodash.isequal": "^4.5.0"
41 | },
42 | "devDependencies": {
43 | "aws-sdk-client-mock": "^3.0.1",
44 | "aws-sdk-client-mock-jest": "^3.0.1",
45 | "dotenv": "^10.0.0",
46 | "eslint": "^7.11.0",
47 | "eslint-config-standard": "^13.0.1",
48 | "eslint-plugin-import": "^2.23.3",
49 | "eslint-plugin-jest": "^22.13.6",
50 | "eslint-plugin-node": "^9.1.0",
51 | "eslint-plugin-promise": "^4.2.1",
52 | "eslint-plugin-standard": "^4.0.0",
53 | "husky": "^4.3.0",
54 | "jest": "^26.6.3",
55 | "lint-staged": "^11.0.0",
56 | "lodash.clonedeep": "^4.5.0",
57 | "serverless": "^3.27.0",
58 | "serverless-plugin-split-stacks": "^1.11.3",
59 | "serverless-stack-termination-protection": "^2.0.2"
60 | },
61 | "engines": {
62 | "node": "^18",
63 | "npm": "^9"
64 | },
65 | "lint-staged": {
66 | "**/*.js": [
67 | "eslint --fix"
68 | ]
69 | },
70 | "husky": {
71 | "hooks": {
72 | "pre-commit": "lint-staged",
73 | "pre-push": "npm run test"
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/test/utils/S3_utils.test.js:
--------------------------------------------------------------------------------
1 |
2 | const utils = require('../../utils/S3_utils')
3 | const clonedeep = require('lodash.clonedeep')
4 |
5 | describe('S3_utils', () => {
6 | const grantReadAcpAllUsers = {
7 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
8 | }
9 | const grantReadAllUsers = {
10 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ'
11 | }
12 | const grantReadAcpAuthenticatedUsers = {
13 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ_ACP'
14 | }
15 |
16 | describe('#filterAclGrants', () => {
17 | let sampleAcl
18 | beforeEach(() => {
19 | sampleAcl = {
20 | Owner: { DisplayName: 'user_name', ID: '2ce976687c4d75ad5a026cfc3c1f0397e39a0df116faf88c1fd90f2faa291c8b' },
21 | Grants: [grantReadAcpAllUsers, grantReadAllUsers]
22 | }
23 | })
24 | it('should not mutate the input param', () => {
25 | const original = clonedeep(sampleAcl)
26 | utils.filterAclGrants(sampleAcl, grantReadAcpAllUsers)
27 | expect(sampleAcl).toEqual(original)
28 | })
29 |
30 | it('should return a copy when grant is undefined', () => {
31 | const result = utils.filterAclGrants(sampleAcl)
32 | expect(result).toEqual(sampleAcl)
33 | })
34 |
35 | it('should return a copy of the acl when no match exists', () => {
36 | const result = utils.filterAclGrants(sampleAcl, grantReadAcpAuthenticatedUsers)
37 | expect(result).toEqual(sampleAcl)
38 | })
39 |
40 | it('should remove a single match', () => {
41 | sampleAcl.Grants = clonedeep([grantReadAcpAllUsers, grantReadAllUsers])
42 | const result = utils.filterAclGrants(sampleAcl, grantReadAcpAllUsers)
43 | expect(result.Grants).toEqual([grantReadAllUsers])
44 | })
45 |
46 | it('should remove multiple matches', () => {
47 | sampleAcl.Grants = clonedeep([grantReadAcpAllUsers, grantReadAllUsers, grantReadAcpAllUsers])
48 | const result = utils.filterAclGrants(sampleAcl, grantReadAcpAllUsers)
49 | expect(result.Grants).toEqual([grantReadAllUsers])
50 | })
51 |
52 | it('should fail when acl is undefined', () => {
53 | const invalidCall = () => utils.filterAclGrants(undefined, grantReadAcpAllUsers)
54 | expect(invalidCall).toThrow(expect.anything())
55 | })
56 |
57 | it('should not match when Grantee differs', () => {
58 | sampleAcl.Grants = clonedeep([grantReadAcpAllUsers])
59 | const result = utils.filterAclGrants(sampleAcl, grantReadAcpAuthenticatedUsers)
60 | expect(result.Grants).toEqual([grantReadAcpAllUsers])
61 | })
62 | })
63 | })
64 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | name: "CodeQL"
7 |
8 | on:
9 | push:
10 | branches: [master]
11 | pull_request:
12 | # The branches below must be a subset of the branches above
13 | branches: [master]
14 | schedule:
15 | - cron: '0 7 * * 3'
16 |
17 | jobs:
18 | analyze:
19 | name: Analyze
20 | runs-on: ubuntu-latest
21 |
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | # Override automatic language detection by changing the below list
26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
27 | language: ['javascript']
28 | # Learn more...
29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
30 |
31 | steps:
32 | - name: Checkout repository
33 | uses: actions/checkout@v2
34 | with:
35 | # We must fetch at least the immediate parents so that if this is
36 | # a pull request then we can checkout the head.
37 | fetch-depth: 2
38 |
39 | # If this run was triggered by a pull request event, then checkout
40 | # the head of the pull request instead of the merge commit.
41 | - run: git checkout HEAD^2
42 | if: ${{ github.event_name == 'pull_request' }}
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | #- name: Autobuild
57 | # uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-014.js:
--------------------------------------------------------------------------------
1 |
2 | const { S3Client, GetBucketPolicyCommand, PutBucketPolicyCommand } = require('@aws-sdk/client-s3')
3 | const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry')
4 |
5 | const Utils = require('./Utils.js')
6 |
7 | const retryStrategy = new ConfiguredRetryStrategy(10, 5000)
8 |
9 | /**
10 | * This function Alter Bucket policy to enforce AWS S3 bucket are not publicly accessible
11 | * * Note: Here we assume that the S3 Bucket has a policy with the
12 | * "Effect" : "Allow" and "Principal" : "*"
13 | * Which cause S3-014 rule to fail.
14 | *
15 | * This Function replace Principal in the policy with account root user
16 | */
17 | const handler = async (event) => {
18 | console.log('S3 Bucket Public Access Via Policy - Received event:', JSON.stringify(event, null, 2))
19 |
20 | if (!event || !event.region) {
21 | return handleError('Invalid event')
22 | }
23 |
24 | const S3Bucket = event.resource
25 |
26 | try {
27 | const accountId = await Utils.getAccountId()
28 | console.log('AWS Account ID:', accountId)
29 | await AlterBucketPolicyPrincipal(S3Bucket, accountId)
30 | console.log('SES is Enabled for bucket ', event.resource)
31 | return 'Successfully processed event'
32 | } catch (err) {
33 | console.log('Error', err)
34 | return handleError(err.message ? err.message : 'Failed to enable AWS Config')
35 | }
36 |
37 | async function AlterBucketPolicyPrincipal (S3Bucket, accountId) {
38 | const S3 = new S3Client({ retryStrategy })
39 |
40 | try {
41 | const data = await S3.send(new GetBucketPolicyCommand({ Bucket: S3Bucket }))
42 | console.log('Retrieved Bucket Policy:', JSON.stringify(JSON.parse(data.Policy), undefined, 2))
43 | const BucketPolicy = JSON.parse(data.Policy)
44 | var statements = BucketPolicy.Statement
45 |
46 | for (var i = 0; i < statements.length; i++) {
47 | var currentStatement = statements[i]
48 | console.log(`Processing statement ${i}...`)
49 | for (var key in currentStatement) {
50 | // eslint-disable-next-line no-prototype-builtins
51 | if (currentStatement.hasOwnProperty(key) && currentStatement[key] === 'Allow') {
52 | if (currentStatement['Principal'] === '*') {
53 | currentStatement['Principal'] = { AWS: 'arn:aws:iam::' + accountId + ':root' }
54 | console.log('Altered Principal to the root user')
55 | console.log(currentStatement)
56 | }
57 | }
58 | }
59 | }
60 |
61 | const PutBucketPolicyParams = {
62 | Policy: JSON.stringify(BucketPolicy),
63 | Bucket: S3Bucket
64 | }
65 |
66 | const putBucketPolicyResult = await S3.send(new PutBucketPolicyCommand(PutBucketPolicyParams))
67 | console.log('Successfully put bucket policy')
68 | return putBucketPolicyResult.Policy
69 | } catch (err) {
70 | console.log(err.message)
71 | throw err
72 | }
73 | }
74 |
75 | function handleError (message) {
76 | message = message || 'Failed to process request.'
77 | throw new Error(message)
78 | }
79 | }
80 |
81 | module.exports = { handler }
82 |
--------------------------------------------------------------------------------
/functions/AutoRemediateS3-016.js:
--------------------------------------------------------------------------------
1 |
2 | const { S3Client, GetBucketPolicyCommand, PutBucketPolicyCommand } = require('@aws-sdk/client-s3')
3 |
4 | const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry')
5 |
6 | const retryStrategy = new ConfiguredRetryStrategy(10, (attempt) => 5000 + attempt * 1000)
7 |
8 | /**
9 | * Enable Server-Side Encryption for AWS S3 buckets
10 | */
11 | const handler = async (event) => {
12 | console.log('S3 Server Side Encryption - Received event:', JSON.stringify(event, null, 2))
13 |
14 | if (!event || !event.region) {
15 | return handleError('Invalid event')
16 | }
17 |
18 | const S3Bucket = event.resource
19 |
20 | await UpdateBucketPolicy(S3Bucket)
21 |
22 | console.log('SES is Enabled for bucket ', event.resource)
23 | return 'Successfully processed event'
24 |
25 | async function UpdateBucketPolicy (S3Bucket) {
26 | const S3 = new S3Client({ retryStrategy })
27 |
28 | const SseStatement1 = JSON.stringify({
29 | Sid: 'DenyIncorrectEncryptionHeader',
30 | Effect: 'Deny',
31 | Principal: '*',
32 | Action: 's3:PutObject',
33 | Resource: 'arn:aws:s3:::' + S3Bucket + '/*',
34 | Condition: {
35 | StringNotEquals: {
36 | 's3:x-amz-server-side-encryption': 'AES256'
37 | }
38 | }
39 | })
40 |
41 | const SseStatement2 = JSON.stringify({
42 | Sid: 'DenyUnEncryptedObjectUploads',
43 | Effect: 'Deny',
44 | Principal: '*',
45 | Action: 's3:PutObject',
46 | Resource: 'arn:aws:s3:::' + S3Bucket + '/*',
47 | Condition: {
48 | Null: {
49 | 's3:x-amz-server-side-encryption': 'true'
50 | }
51 | }
52 | }
53 | )
54 |
55 | try {
56 | const currentPolicy = await S3.send(new GetBucketPolicyCommand({ Bucket: S3Bucket }))
57 | console.log('Retrieved Bucket Policy:', JSON.stringify(JSON.parse(currentPolicy.Policy), undefined, 2))
58 | const BucketPolicy = JSON.parse(currentPolicy.Policy)
59 |
60 | if ((currentPolicy.Policy).includes('s3:x-amz-server-side-encryption')) {
61 | console.log('Bucket already has SSE policy in place')
62 | return BucketPolicy
63 | } else {
64 | BucketPolicy.Statement.push(JSON.parse(SseStatement1))
65 | BucketPolicy.Statement.push(JSON.parse(SseStatement2))
66 |
67 | const PutBucketPolicyParams = {
68 | Policy: JSON.stringify(BucketPolicy),
69 | Bucket: S3Bucket
70 | }
71 |
72 | return putBucketPolicy(S3, PutBucketPolicyParams)
73 | }
74 | } catch (err) {
75 | console.log(err.message)
76 |
77 | if (err.code !== 'NoSuchBucketPolicy') {
78 | throw err
79 | }
80 |
81 | const NewSsePolicy = JSON.stringify({
82 | Version: '2012-10-17',
83 | Id: 'PutSSEObjPolicy',
84 | Statement: []
85 | })
86 |
87 | const NewSsePolicyObj = JSON.parse(NewSsePolicy)
88 |
89 | NewSsePolicyObj.Statement.push(JSON.parse(SseStatement1))
90 | NewSsePolicyObj.Statement.push(JSON.parse(SseStatement2))
91 |
92 | const PutBucketPolicyParams = {
93 | Policy: JSON.stringify(NewSsePolicyObj),
94 | Bucket: S3Bucket
95 | }
96 |
97 | return putBucketPolicy(S3, PutBucketPolicyParams)
98 | }
99 | }
100 |
101 | async function putBucketPolicy (S3, PutBucketPolicyParams) {
102 | const result = await S3.send(new PutBucketPolicyCommand(PutBucketPolicyParams))
103 | console.log('Successfully put bucket policy')
104 | return result.Policy
105 | }
106 |
107 | function handleError (message) {
108 | message = message || 'Failed to process request.'
109 | throw new Error(message)
110 | }
111 | }
112 |
113 | module.exports = { handler }
114 |
--------------------------------------------------------------------------------
/functions/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "AutoRemediateRDS-008": {
3 | "enabled": false
4 | },
5 |
6 | "AutoRemediateS3-001": {
7 | "enabled": false
8 | },
9 |
10 | "AutoRemediateS3-002": {
11 | "enabled": false
12 | },
13 |
14 | "AutoRemediateS3-003": {
15 | "enabled": false
16 | },
17 |
18 | "AutoRemediateS3-004": {
19 | "enabled": false
20 | },
21 |
22 | "AutoRemediateS3-005": {
23 | "enabled": false
24 | },
25 |
26 | "AutoRemediateS3-006": {
27 | "enabled": false
28 | },
29 |
30 | "AutoRemediateS3-007": {
31 | "enabled": false
32 | },
33 |
34 | "AutoRemediateS3-008": {
35 | "enabled": false
36 | },
37 |
38 | "AutoRemediateS3-009": {
39 | "enabled": false
40 | },
41 |
42 | "AutoRemediateS3-010": {
43 | "enabled": false
44 | },
45 |
46 | "AutoRemediateS3-012": {
47 | "enabled": false
48 | },
49 |
50 | "AutoRemediateConfig-001": {
51 | "enabled": false,
52 | "S3BucketName": ""
53 | },
54 |
55 | "AutoRemediateCFM-005": {
56 | "enabled": false
57 | },
58 |
59 | "AutoRemediateCT-001": {
60 | "enabled": false,
61 | "Name": "GlobalTrail",
62 | "S3BucketName": "",
63 | "IncludeGlobalServiceEvents": true,
64 | "IsMultiRegionTrail": true,
65 | "S3KeyPrefix": ""
66 | },
67 |
68 | "AutoRemediateRS-001": {
69 | "enabled": false
70 | },
71 |
72 | "AutoRemediateIAM-001": {
73 | "enabled": false
74 | },
75 |
76 | "AutoRemediateIAM-029": {
77 | "enabled": false
78 | },
79 |
80 | "AutoRemediateEC2-002": {
81 | "enabled": false
82 | },
83 |
84 | "AutoRemediateEC2-003": {
85 | "enabled": false
86 | },
87 |
88 | "AutoRemediateEC2-004": {
89 | "enabled": false
90 | },
91 |
92 | "AutoRemediateEC2-005": {
93 | "enabled": false
94 | },
95 |
96 | "AutoRemediateEC2-006": {
97 | "enabled": false
98 | },
99 |
100 | "AutoRemediateEC2-008": {
101 | "enabled": false
102 | },
103 |
104 | "AutoRemediateEC2-038": {
105 | "enabled": false
106 | },
107 |
108 | "AutoRemediateEC2-039": {
109 | "enabled": false
110 | },
111 |
112 | "AutoRemediateEC2-040": {
113 | "enabled": false
114 | },
115 |
116 | "AutoRemediateEC2-043": {
117 | "enabled": false
118 | },
119 |
120 | "AutoRemediateEC2-045": {
121 | "enabled": false
122 | },
123 |
124 | "AutoRemediateIAM-038": {
125 | "enabled": false
126 | },
127 |
128 | "AutoRemediateKinesis-001": {
129 | "enabled": false,
130 | "KmsKeyId": "alias/aws/kinesis"
131 | },
132 |
133 | "AutoRemediateVPC-001": {
134 | "enabled": false
135 | },
136 |
137 | "AutoRemediateEC2-019": {
138 | "enabled": false
139 | },
140 |
141 | "AutoRemediateCT-003": {
142 | "enabled": false
143 | },
144 |
145 | "AutoRemediateRds-006": {
146 | "enabled": false
147 | },
148 |
149 | "AutoRemediateRDS-006": {
150 | "enabled": false
151 | },
152 |
153 | "AutoRemediateRDS-023": {
154 | "enabled": false
155 | },
156 |
157 | "AutoRemediateKMS-004": {
158 | "enabled": false
159 | },
160 |
161 | "AutoRemediateRS-023": {
162 | "enabled": false
163 | },
164 |
165 | "AutoRemediateGD-001": {
166 | "enabled": false
167 | },
168 |
169 | "AutoRemediateOrganizations-002": {
170 | "enabled": false
171 | },
172 |
173 | "AutoRemediateLambda-003": {
174 | "enabled": false
175 | },
176 |
177 | "AutoRemediateS3-016": {
178 | "enabled": false
179 | },
180 |
181 | "AutoRemediateS3-014": {
182 | "enabled": false
183 | },
184 |
185 | "AutoRemediateTrustedAdvisor-003": {
186 | "enabled": false
187 | },
188 |
189 | "AutoRemediateKMS-002": {
190 | "enabled": false
191 | },
192 |
193 | "AutoRemediateRS-019": {
194 | "enabled": false,
195 | "RetentionPeriod": 7
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/functions/AutoRemediateVPC-001.js:
--------------------------------------------------------------------------------
1 |
2 | const { IAMClient, GetRoleCommand, CreateRoleCommand, PutRolePolicyCommand } = require('@aws-sdk/client-iam')
3 | const { EC2Client, DescribeFlowLogsCommand, CreateFlowLogsCommand } = require(`@aws-sdk/client-ec2`)
4 | const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry')
5 |
6 | // retry all requests with a 5 sec delay (if they are retry-able), up to 10 times
7 | const retryStrategy = new ConfiguredRetryStrategy(10, 5000)
8 |
9 | /**
10 | * Ensure that Flow Logs feature is Enabled for your account
11 | */
12 | const handler = async (event) => {
13 | console.log('Enable Flow Logs - Received event:', JSON.stringify(event, null, 2))
14 |
15 | function handleError (message) {
16 | message = message || 'Failed to process request.'
17 | throw new Error(message)
18 | }
19 |
20 | if (!event || !event.resource || !event.region) {
21 | return handleError('Invalid event')
22 | }
23 | console.log(event.resource)
24 |
25 | const roleARN = await CreateVPCFlowLogRole()
26 | console.log('Role ARN:', roleARN)
27 |
28 | const describeParam = {
29 | Filter: [
30 | {
31 | Name: 'resource-id',
32 | Values: [event.resource]
33 | }
34 | ]
35 | }
36 | try {
37 | const EC2 = new EC2Client({ region: event.region, retryStrategy })
38 | const { FlowLogs: flowLogs } = await EC2.send(new DescribeFlowLogsCommand(describeParam))
39 | if (!flowLogs.length) {
40 | const params = {
41 | ResourceIds: [event.resource],
42 | ResourceType: 'VPC',
43 | TrafficType: 'ALL',
44 | DeliverLogsPermissionArn: roleARN,
45 | LogGroupName: 'VPCFlowLogs'
46 | }
47 | const result = await EC2.send(new CreateFlowLogsCommand(params))
48 | console.log('Result', result)
49 | }
50 | return 'Successfully processed event'
51 | } catch (err) {
52 | console.log('Error', err)
53 | const message = err.message ? err.message : 'Failed to createFlowLogs'
54 | throw new Error(message)
55 | }
56 |
57 | async function putRolePolicy (IAM, roleName) {
58 | const PutRolePolicyParams = {
59 | PolicyDocument: JSON.stringify({
60 | Version: '2012-10-17',
61 | Statement: [
62 | {
63 | Action: [
64 | 'logs:CreateLogGroup',
65 | 'logs:CreateLogStream',
66 | 'logs:PutLogEvents',
67 | 'logs:DescribeLogGroups',
68 | 'logs:DescribeLogStreams'
69 | ],
70 | Effect: 'Allow',
71 | Resource: '*'
72 | }
73 | ]
74 | }),
75 | PolicyName: 'VPCFlowLogRolePolicy',
76 | RoleName: roleName
77 | }
78 | await IAM.send(new PutRolePolicyCommand(PutRolePolicyParams))
79 | console.log('Successfully put role policy')
80 |
81 | const { Role: role } = IAM.send(new GetRoleCommand({ RoleName: roleName }))
82 | return role.Arn
83 | }
84 |
85 | async function CreateVPCFlowLogRole () {
86 | const VPCFlowLogRole = 'VPCFlowLogRole'
87 |
88 | const IAM = new IAMClient({ retryStrategy })
89 |
90 | try {
91 | await IAM.send(new GetRoleCommand({ RoleName: VPCFlowLogRole }))
92 |
93 | return putRolePolicy(IAM)
94 | } catch (err) {
95 | console.log(err.message)
96 | if (err.code !== 'NoSuchEntity') {
97 | throw err
98 | }
99 |
100 | const CreateRoleParams = {
101 | AssumeRolePolicyDocument: JSON.stringify({
102 | Version: '2012-10-17',
103 | Statement: [
104 | {
105 | Effect: 'Allow',
106 | Principal: {
107 | Service: 'vpc-flow-logs.amazonaws.com'
108 | },
109 | Action: 'sts:AssumeRole'
110 | }
111 | ]
112 | }
113 | ),
114 | RoleName: VPCFlowLogRole,
115 | Description: 'AWS VPC FlowLog Role Created By Cloud Conformity AutoRemediateVPC-001'
116 | }
117 |
118 | await IAM.send(new CreateRoleCommand(CreateRoleParams))
119 |
120 | return putRolePolicy(IAM)
121 | }
122 | }
123 | }
124 |
125 | module.exports = { handler }
126 |
--------------------------------------------------------------------------------
/exclude-rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "schema": "https://cloudconformity.com/external/profile.schema.json",
3 | "version": "1.1",
4 | "name": "exclude list of rules triggered by auto-remediate resources",
5 | "description": "This is a Conformity Profile used to add tag exceptions to rules that triggered by auto-remediate resources. The excluded rules are either low risk rules or rules that require extra resources or configurations",
6 | "ruleSettings": [
7 | {
8 | "id": "CFM-001",
9 | "enabled": true,
10 | "provider": "aws",
11 | "riskLevel": "MEDIUM",
12 | "exceptions": {
13 | "tags": [
14 | "service::auto-remediate"
15 | ]
16 | }
17 | },
18 | {
19 | "id": "CFM-002",
20 | "enabled": true,
21 | "provider": "aws",
22 | "riskLevel": "MEDIUM",
23 | "exceptions": {
24 | "tags": [
25 | "service::auto-remediate"
26 | ]
27 | }
28 | },
29 | {
30 | "id": "SNS-002",
31 | "enabled": true,
32 | "provider": "aws",
33 | "riskLevel": "HIGH",
34 | "exceptions": {
35 | "tags": [
36 | "service::auto-remediate"
37 | ]
38 | }
39 | },
40 | {
41 | "id": "Lambda-007",
42 | "enabled": true,
43 | "provider": "aws",
44 | "riskLevel": "MEDIUM",
45 | "exceptions": {
46 | "tags": [
47 | "service::auto-remediate"
48 | ]
49 | }
50 | },
51 | {
52 | "id": "S3-011",
53 | "enabled": true,
54 | "provider": "aws",
55 | "riskLevel": "MEDIUM",
56 | "exceptions": {
57 | "tags": [
58 | "service::auto-remediate"
59 | ]
60 | }
61 | },
62 | {
63 | "id": "S3-018",
64 | "enabled": true,
65 | "provider": "aws",
66 | "riskLevel": "LOW",
67 | "exceptions": {
68 | "tags": [
69 | "service::auto-remediate"
70 | ]
71 | }
72 | },
73 | {
74 | "id": "S3-012",
75 | "enabled": true,
76 | "provider": "aws",
77 | "riskLevel": "LOW",
78 | "exceptions": {
79 | "tags": [
80 | "service::auto-remediate"
81 | ]
82 | }
83 | },
84 | {
85 | "id": "S3-013",
86 | "enabled": true,
87 | "provider": "aws",
88 | "riskLevel": "LOW",
89 | "exceptions": {
90 | "tags": [
91 | "service::auto-remediate"
92 | ]
93 | }
94 | },
95 | {
96 | "id": "S3-020",
97 | "enabled": true,
98 | "provider": "aws",
99 | "riskLevel": "LOW",
100 | "exceptions": {
101 | "tags": [
102 | "service::auto-remediate"
103 | ]
104 | }
105 | },
106 | {
107 | "id": "S3-023",
108 | "enabled": true,
109 | "provider": "aws",
110 | "riskLevel": "LOW",
111 | "exceptions": {
112 | "tags": [
113 | "service::auto-remediate"
114 | ]
115 | }
116 | },
117 | {
118 | "id": "S3-024",
119 | "enabled": true,
120 | "provider": "aws",
121 | "riskLevel": "LOW",
122 | "exceptions": {
123 | "tags": [
124 | "service::auto-remediate"
125 | ]
126 | }
127 | },
128 | {
129 | "id": "SQS-006",
130 | "enabled": true,
131 | "provider": "aws",
132 | "riskLevel": "LOW",
133 | "exceptions": {
134 | "tags": [
135 | "service::auto-remediate"
136 | ]
137 | }
138 | }
139 | ]
140 | }
141 |
--------------------------------------------------------------------------------
/functions/AutoRemediateConfig-001.js:
--------------------------------------------------------------------------------
1 |
2 | const CONFIG = require('./config')
3 | const { IAMClient, GetRoleCommand, CreateRoleCommand, AttachRolePolicyCommand, PutRolePolicyCommand } = require('@aws-sdk/client-iam')
4 | const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry')
5 | const { ConfigServiceClient } = require('@aws-sdk/client-config-service ')
6 | const { setTimeout } = require('timers/promises')
7 |
8 | // retry all requests with a 5 sec delay (if they are retry-able), up to 10 times
9 | const retryStrategy = new ConfiguredRetryStrategy(10, 5000)
10 |
11 | const Utils = require('./Utils.js')
12 | const { PutConfigurationRecorderCommand, PutDeliveryChannelCommand, StartConfigurationRecorderCommand } = require('@aws-sdk/client-config-service')
13 |
14 | /**
15 | * Ensure AWS Config is enabled in all regions
16 | *
17 | * @param {string} event.region
18 | *
19 | * @param context Lambda Context
20 | * @param callback Lambda Callback
21 | */
22 | const handler = async (event) => {
23 | console.log('Config AutoRemediateConfig-001 - Received event:', JSON.stringify(event, null, 2))
24 |
25 | if (!event || !event.region) {
26 | return handleError('Invalid event')
27 | }
28 |
29 | if (!CONFIG['AutoRemediateConfig-001']['S3BucketName']) {
30 | return handleError('Missing CONFIG_S3_BUCKET configuration')
31 | }
32 |
33 | try {
34 | const accountId = await Utils.getAccountId()
35 | console.log('AWS Account ID:', accountId)
36 |
37 | const roleARN = await getRole(accountId)
38 | console.log('Role ARN:', roleARN)
39 |
40 | await subscribe(roleARN)
41 | console.log('Successfully enabled AWS config in', event.region)
42 |
43 | return 'Successfully processed event'
44 | } catch (err) {
45 | console.log('Error', err)
46 | return handleError(err.message ? err.message : 'Failed to enable AWS Config')
47 | }
48 |
49 | function handleError (message) {
50 | message = message || 'Failed to process request.'
51 | throw new Error(message)
52 | }
53 |
54 | async function getRole (accountId) {
55 | const ConfigRoleName = 'AWSConfigRole'
56 |
57 | const IAM = new IAMClient({ retryStrategy })
58 |
59 | try {
60 | const role = await IAM.send(new GetRoleCommand({ RoleName: ConfigRoleName }))
61 | return role.Role.Arn
62 | } catch (err) {
63 | console.log(err.message)
64 |
65 | if (err.code !== 'NoSuchEntity') {
66 | throw err
67 | }
68 |
69 | const CreateRoleParams = {
70 | AssumeRolePolicyDocument: JSON.stringify({
71 | Version: '2012-10-17',
72 | Statement: [
73 | {
74 | Sid: '',
75 | Effect: 'Allow',
76 | Principal: {
77 | Service: 'config.amazonaws.com'
78 | },
79 | Action: 'sts:AssumeRole'
80 | }
81 | ]
82 | }),
83 | RoleName: ConfigRoleName,
84 | Description: 'AWS Config Role Created By Cloud Conformity AutoRemediateConfig-001'
85 | }
86 | const role = await IAM.send(new CreateRoleCommand(CreateRoleParams))
87 | console.log('Successfully created the role')
88 | const AttachRolePolicyParams = {
89 | PolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSConfigRole',
90 | RoleName: ConfigRoleName
91 | }
92 |
93 | await IAM.send(new AttachRolePolicyCommand(AttachRolePolicyParams))
94 | console.log('Successfully attach AWSConfigRole managed policy to role')
95 |
96 | const PutRolePolicyParams = {
97 | PolicyDocument: JSON.stringify({
98 | Version: '2012-10-17',
99 | Statement: [
100 | {
101 | Effect: 'Allow',
102 | Action: [
103 | 's3:PutObject'
104 | ],
105 | Resource: 'arn:aws:s3:::' + CONFIG['AutoRemediateConfig-001']['S3BucketName'] + '/AWSLogs/' + accountId + '/Config/*',
106 | Condition: {
107 | StringLike: {
108 | 's3:x-amz-acl': 'bucket-owner-full-control'
109 | }
110 | }
111 | },
112 | {
113 | Effect: 'Allow',
114 | Action: [
115 | 's3:GetBucketAcl'
116 | ],
117 | Resource: 'arn:aws:s3:::' + CONFIG['AutoRemediateConfig-001']['S3BucketName']
118 | }
119 | ]
120 | }),
121 | PolicyName: 'AWSConfigRolePolicy',
122 | RoleName: ConfigRoleName
123 | }
124 |
125 | await IAM.send(new PutRolePolicyCommand(PutRolePolicyParams))
126 | console.log('Successfully put role policy')
127 |
128 | // Wait 15 seconds to avoid InsufficientDeliveryPolicyException: Insufficient delivery policy to s3 bucket:
129 | await setTimeout(15000)
130 | return role.Role.Arn
131 | }
132 | }
133 |
134 | async function subscribe (roleARN) {
135 | const ConfigService = new ConfigServiceClient({ region: event.region })
136 |
137 | const ConfigurationRecorderParams = {
138 | ConfigurationRecorder: {
139 | name: 'default',
140 | recordingGroup: {
141 | allSupported: true,
142 | includeGlobalResourceTypes: (event.region === 'us-east-1')
143 | },
144 | roleARN: roleARN
145 | }
146 | }
147 |
148 | await ConfigService.send(new PutConfigurationRecorderCommand(ConfigurationRecorderParams))
149 | console.log('Successfully put configuration recorder')
150 |
151 | const PutDeliveryChannelParams = {
152 | DeliveryChannel: {
153 | name: 'default',
154 | s3BucketName: CONFIG['AutoRemediateConfig-001']['S3BucketName']
155 | }
156 | }
157 |
158 | await ConfigService.send(new PutDeliveryChannelCommand(PutDeliveryChannelParams))
159 | console.log('Successfully put delivery channel')
160 |
161 | const StartConfigurationRecorderParams = {
162 | ConfigurationRecorderName: 'default'
163 | }
164 |
165 | const startConfigurationRecorderResult = await ConfigService.send(new StartConfigurationRecorderCommand(StartConfigurationRecorderParams))
166 | console.log('Successfully start configuration recorder')
167 | return startConfigurationRecorderResult
168 | }
169 | }
170 |
171 | module.exports = { handler }
172 |
--------------------------------------------------------------------------------
/test/AutoRemediateS3-003.test.js:
--------------------------------------------------------------------------------
1 |
2 | const source = require('../functions/AutoRemediateS3-003')
3 | const { S3Client, GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
4 | const { mockClient } = require('aws-sdk-client-mock')
5 | // eslint-disable-next-line node/no-extraneous-require
6 | require('aws-sdk-client-mock-jest')
7 |
8 | const sampleEvent = {
9 | id: 'ccc:HJzFMHchx:S3-003:S3:ap-southeast-2:sample-bucket',
10 | organisationId: 'some-organisation',
11 | accountId: 'abcdef',
12 | ruleId: 'S3-003',
13 | ruleTitle: "S3 Bucket Public 'WRITE' Access",
14 | service: 'S3',
15 | region: 'ap-southeast-2',
16 | riskLevel: 'VERY_HIGH',
17 | categories: [
18 | 'security'
19 | ],
20 | compliances: [
21 | 'AWAF'
22 | ],
23 | message: "Bucket sample-bucket allows public 'WRITE' access.",
24 | resource: 'sample-bucket',
25 | status: 'FAILURE',
26 | statusRiskLevel: 'FAILURE:1',
27 | lastUpdatedDate: null,
28 | lastUpdatedBy: 'SYSTEM',
29 | resolvedBy: 'SYSTEM',
30 | eventId: 'Skzp7ra1WW',
31 | ccrn: 'ccrn:aws:HJzFMHchx:S3:global:sample-bucket',
32 | tags: [],
33 | cost: '0',
34 | waste: '0',
35 | lastModifiedDate: '1511060191925',
36 | lastModifiedBy: 'SYSTEM'
37 | }
38 |
39 | describe('S3-003 AutoRemediation', () => {
40 | const grantReadAcpAllUsers = {
41 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
42 | }
43 | const grantReadAllUsers = {
44 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ'
45 | }
46 | const grantWriteAcpAllUsers = {
47 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE_ACP'
48 | }
49 | const grantWriteAllUsers = {
50 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE'
51 | }
52 | const grantWriteAuthenticatedUsers = {
53 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE'
54 | }
55 | const grantFullControlCanonicalUser = {
56 | Grantee: { DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser' }, Permission: 'FULL_CONTROL'
57 | }
58 |
59 | const mockS3 = mockClient(S3Client)
60 |
61 | afterEach(() => {
62 | mockS3.reset()
63 | })
64 |
65 | describe('valid invocation', () => {
66 | beforeEach(async () => {
67 | mockS3.on(GetBucketAclCommand).resolves({
68 | Owner: {
69 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef'
70 | },
71 | Grants: [
72 | {
73 | Grantee: {
74 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser'
75 | },
76 | Permission: 'FULL_CONTROL'
77 | },
78 | grantReadAllUsers,
79 | grantReadAcpAllUsers,
80 | grantWriteAllUsers,
81 | grantWriteAcpAllUsers,
82 | grantWriteAuthenticatedUsers,
83 | grantFullControlCanonicalUser
84 | ]
85 | })
86 |
87 | await source.handler(sampleEvent)
88 | })
89 |
90 | it('should get the correct bucket ACL from S3', () => {
91 | const expectedParams = {
92 | Bucket: 'sample-bucket'
93 | }
94 | expect(mockS3).toHaveReceivedCommandWith(GetBucketAclCommand, expectedParams)
95 | })
96 |
97 | it('should set a new ACL on the affected bucket', () => {
98 | const expectedParams = {
99 | Bucket: 'sample-bucket',
100 | AccessControlPolicy: expect.any(Object)
101 | }
102 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedParams)
103 | })
104 |
105 | it('should remove WRITE grants for AllUsers', () => {
106 | expect(mockS3).not.toHaveReceivedCommandWith(PutBucketAclCommand, grantWriteAllUsers)
107 | })
108 |
109 | it('should keep WRITE grants for other users', () => {
110 | const expectedGrant = {
111 | AccessControlPolicy: {
112 | Grants: expect.arrayContaining([grantWriteAuthenticatedUsers]),
113 | Owner: expect.any(Object)
114 | }
115 | }
116 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
117 | })
118 |
119 | it('should keep READ grants for All Users', () => {
120 | const expectedGrant = {
121 | AccessControlPolicy: {
122 | Grants: expect.arrayContaining([grantReadAllUsers]),
123 | Owner: expect.any(Object)
124 | }
125 | }
126 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
127 | })
128 |
129 | it('should keep READ_ACP grants for All Users', () => {
130 | const expectedGrant = {
131 | AccessControlPolicy: {
132 | Grants: expect.arrayContaining([grantReadAcpAllUsers]),
133 | Owner: expect.any(Object)
134 | }
135 | }
136 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
137 | })
138 |
139 | it('should keep WRITE_ACP grants for All Users', () => {
140 | const expectedGrant = {
141 | AccessControlPolicy: {
142 | Grants: expect.arrayContaining([grantWriteAcpAllUsers]),
143 | Owner: expect.any(Object)
144 | }
145 | }
146 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
147 | })
148 |
149 | it('should keep FULL_CONTROL grants for Canonical User', () => {
150 | const expectedGrant = {
151 | AccessControlPolicy: {
152 | Grants: expect.arrayContaining([grantFullControlCanonicalUser]),
153 | Owner: expect.any(Object)
154 | }
155 | }
156 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
157 | })
158 | })
159 |
160 | describe('invalid invocation', () => {
161 | it('should fail when event is undefined', async () => {
162 | await expect(source.handler(undefined))
163 | .rejects
164 | .toThrow('Invalid event')
165 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
166 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
167 | })
168 |
169 | it('should fail when "resource" missing from the event', async () => {
170 | const malformedEvent = {
171 | ruleId: 'S3-003'
172 | }
173 | await expect(source.handler(malformedEvent))
174 | .rejects
175 | .toThrow('Invalid event')
176 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
177 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
178 | })
179 |
180 | it('should fail when the incorrect rule is received', async () => {
181 | const malformedEvent = {
182 | resource: 'sample-bucket',
183 | ruleId: 'S3-00x'
184 | }
185 | await expect(source.handler(malformedEvent))
186 | .rejects
187 | .toThrow('Invalid event')
188 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
189 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
190 | })
191 | })
192 | })
193 |
--------------------------------------------------------------------------------
/test/AutoRemediateS3-002.test.js:
--------------------------------------------------------------------------------
1 |
2 | const source = require('../functions/AutoRemediateS3-002')
3 | const { S3Client, GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
4 | const { mockClient } = require('aws-sdk-client-mock')
5 | // eslint-disable-next-line node/no-extraneous-require
6 | require('aws-sdk-client-mock-jest')
7 |
8 | const sampleEvent = {
9 | id: 'ccc:HJzFMHchx:S3-001:S3:ap-southeast-2:sample-bucket',
10 | organisationId: 'some-organisation',
11 | accountId: 'abcdef',
12 | ruleId: 'S3-002',
13 | ruleTitle: "S3 Bucket Public 'READ_ACP' Access",
14 | service: 'S3',
15 | region: 'ap-southeast-2',
16 | riskLevel: 'VERY_HIGH',
17 | categories: [
18 | 'security'
19 | ],
20 | compliances: [
21 | 'AWAF'
22 | ],
23 | message: "Bucket sample-bucket allows public 'READ_ACP' access.",
24 | resource: 'sample-bucket',
25 | status: 'FAILURE',
26 | statusRiskLevel: 'FAILURE:1',
27 | lastUpdatedDate: null,
28 | lastUpdatedBy: 'SYSTEM',
29 | resolvedBy: 'SYSTEM',
30 | eventId: 'Skzp7ra1WW',
31 | ccrn: 'ccrn:aws:HJzFMHchx:S3:global:sample-bucket',
32 | tags: [],
33 | cost: '0',
34 | waste: '0',
35 | lastModifiedDate: '1511060191925',
36 | lastModifiedBy: 'SYSTEM'
37 | }
38 |
39 | describe('S3-002 AutoRemediation', () => {
40 | const mockS3 = mockClient(S3Client)
41 | const grantReadAcpAllUsers = {
42 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
43 | }
44 | const grantReadAllUsers = {
45 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ'
46 | }
47 | const grantWriteAcpAllUsers = {
48 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE_ACP'
49 | }
50 | const grantWriteAllUsers = {
51 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE'
52 | }
53 | const grantReadAcpAuthenticatedUsers = {
54 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ_ACP'
55 | }
56 | const grantFullControlCanonicalUser = {
57 | Grantee: { DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser' }, Permission: 'FULL_CONTROL'
58 | }
59 |
60 | afterEach(() => {
61 | mockS3.reset()
62 | })
63 |
64 | describe('valid invocation', () => {
65 | beforeEach(async () => {
66 | mockS3.on(GetBucketAclCommand).resolves({
67 | Owner: {
68 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef'
69 | },
70 | Grants: [
71 | {
72 | Grantee: {
73 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser'
74 | },
75 | Permission: 'FULL_CONTROL'
76 | },
77 | grantReadAllUsers,
78 | grantReadAcpAllUsers,
79 | grantWriteAllUsers,
80 | grantWriteAcpAllUsers,
81 | grantReadAcpAuthenticatedUsers,
82 | grantFullControlCanonicalUser
83 | ]
84 | })
85 |
86 | await source.handler(sampleEvent)
87 | })
88 |
89 | it('should get the correct bucket ACL from S3', () => {
90 | const expectedParams = {
91 | Bucket: 'sample-bucket'
92 | }
93 | expect(mockS3).toHaveReceivedCommandWith(GetBucketAclCommand, expectedParams)
94 | })
95 |
96 | it('should set a new ACL on the affected bucket', () => {
97 | const expectedParams = {
98 | Bucket: 'sample-bucket',
99 | AccessControlPolicy: expect.any(Object)
100 | }
101 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedParams)
102 | })
103 |
104 | it('should remove READ_ACP grants for AllUsers', () => {
105 | expect(mockS3).not.toHaveReceivedCommandWith(PutBucketAclCommand, grantReadAcpAllUsers)
106 | })
107 |
108 | it('should keep READ_ACP grants for other users', () => {
109 | const expectedGrant = {
110 | AccessControlPolicy: {
111 | Grants: expect.arrayContaining([grantReadAcpAuthenticatedUsers]),
112 | Owner: expect.any(Object)
113 | }
114 | }
115 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
116 | })
117 |
118 | it('should keep READ grants for All Users', () => {
119 | const expectedGrant = {
120 | AccessControlPolicy: {
121 | Grants: expect.arrayContaining([grantReadAllUsers]),
122 | Owner: expect.any(Object)
123 | }
124 | }
125 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
126 | })
127 |
128 | it('should keep WRITE grants for All Users', () => {
129 | const expectedGrant = {
130 | AccessControlPolicy: {
131 | Grants: expect.arrayContaining([grantWriteAllUsers]),
132 | Owner: expect.any(Object)
133 | }
134 | }
135 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
136 | })
137 |
138 | it('should keep WRITE_ACP grants for All Users', () => {
139 | const expectedGrant = {
140 | AccessControlPolicy: {
141 | Grants: expect.arrayContaining([grantWriteAcpAllUsers]),
142 | Owner: expect.any(Object)
143 | }
144 | }
145 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
146 | })
147 |
148 | it('should keep FULL_CONTROL grants for Canonical User', () => {
149 | const expectedGrant = {
150 | AccessControlPolicy: {
151 | Grants: expect.arrayContaining([grantFullControlCanonicalUser]),
152 | Owner: expect.any(Object)
153 | }
154 | }
155 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
156 | })
157 | })
158 |
159 | describe('invalid invocation', () => {
160 | it('should fail when event is undefined', async () => {
161 | await expect(source.handler(undefined))
162 | .rejects
163 | .toThrow('Invalid event')
164 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
165 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
166 | })
167 |
168 | it('should fail when "resource" missing from the event', async () => {
169 | const malformedEvent = {
170 | ruleId: 'S3-002'
171 | }
172 | await expect(source.handler(malformedEvent))
173 | .rejects
174 | .toThrow('Invalid event')
175 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
176 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
177 | })
178 |
179 | it('should fail when the incorrect rule is received', async () => {
180 | const malformedEvent = {
181 | resource: 'sample-bucket',
182 | ruleId: 'S3-001'
183 | }
184 | await expect(source.handler(malformedEvent))
185 | .rejects
186 | .toThrow('Invalid event')
187 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
188 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
189 | })
190 | })
191 | })
192 |
--------------------------------------------------------------------------------
/test/AutoRemediateS3-004.test.js:
--------------------------------------------------------------------------------
1 |
2 | const source = require('../functions/AutoRemediateS3-004')
3 | const { S3Client, GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
4 | const { mockClient } = require('aws-sdk-client-mock')
5 | // eslint-disable-next-line node/no-extraneous-require
6 | require('aws-sdk-client-mock-jest')
7 |
8 | const sampleEvent = {
9 | id: 'ccc:HJzFMHchx:S3-004:S3:ap-southeast-2:sample-bucket',
10 | organisationId: 'some-organisation',
11 | accountId: 'abcdef',
12 | ruleId: 'S3-004',
13 | ruleTitle: "S3 Bucket Public 'WRITE_ACP' Access",
14 | service: 'S3',
15 | region: 'ap-southeast-2',
16 | riskLevel: 'VERY_HIGH',
17 | categories: [
18 | 'security'
19 | ],
20 | compliances: [
21 | 'AWAF'
22 | ],
23 | message: "Bucket sample-bucket allows public 'WRITE_ACP' access.",
24 | resource: 'sample-bucket',
25 | status: 'FAILURE',
26 | statusRiskLevel: 'FAILURE:1',
27 | lastUpdatedDate: null,
28 | lastUpdatedBy: 'SYSTEM',
29 | resolvedBy: 'SYSTEM',
30 | eventId: 'Skzp7ra1WW',
31 | ccrn: 'ccrn:aws:HJzFMHchx:S3:global:sample-bucket',
32 | tags: [],
33 | cost: '0',
34 | waste: '0',
35 | lastModifiedDate: '1511060191925',
36 | lastModifiedBy: 'SYSTEM'
37 | }
38 |
39 | describe('S3-004 AutoRemediation', () => {
40 | const grantReadAcpAllUsers = {
41 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
42 | }
43 | const grantReadAllUsers = {
44 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ'
45 | }
46 | const grantWriteAcpAllUsers = {
47 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE_ACP'
48 | }
49 | const grantWriteAllUsers = {
50 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE'
51 | }
52 | const grantWriteAcpAuthenticatedUsers = {
53 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE_ACP'
54 | }
55 | const grantFullControlCanonicalUser = {
56 | Grantee: { DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser' }, Permission: 'FULL_CONTROL'
57 | }
58 |
59 | const mockS3 = mockClient(S3Client)
60 |
61 | afterEach(() => {
62 | mockS3.reset()
63 | })
64 |
65 | describe('valid invocation', () => {
66 | beforeEach(async () => {
67 | mockS3.on(GetBucketAclCommand).resolves({
68 | Owner: {
69 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef'
70 | },
71 | Grants: [
72 | {
73 | Grantee: {
74 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser'
75 | },
76 | Permission: 'FULL_CONTROL'
77 | },
78 | grantReadAllUsers,
79 | grantReadAcpAllUsers,
80 | grantWriteAllUsers,
81 | grantWriteAcpAllUsers,
82 | grantWriteAcpAuthenticatedUsers,
83 | grantFullControlCanonicalUser
84 | ]
85 | })
86 |
87 | await source.handler(sampleEvent)
88 | })
89 |
90 | it('should get the correct bucket ACL from S3', () => {
91 | const expectedParams = {
92 | Bucket: 'sample-bucket'
93 | }
94 | expect(mockS3).toHaveReceivedCommandWith(GetBucketAclCommand, expectedParams)
95 | })
96 |
97 | it('should set a new ACL on the affected bucket', () => {
98 | const expectedParams = {
99 | Bucket: 'sample-bucket',
100 | AccessControlPolicy: expect.any(Object)
101 | }
102 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedParams)
103 | })
104 |
105 | it('should remove WRITE_ACP grants for AllUsers', () => {
106 | expect(mockS3).not.toHaveReceivedCommandWith(PutBucketAclCommand, grantWriteAcpAllUsers)
107 | })
108 |
109 | it('should keep WRITE_ACP grants for other users', () => {
110 | const expectedGrant = {
111 | AccessControlPolicy: {
112 | Grants: expect.arrayContaining([grantWriteAcpAuthenticatedUsers]),
113 | Owner: expect.any(Object)
114 | }
115 | }
116 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
117 | })
118 |
119 | it('should keep READ grants for All Users', () => {
120 | const expectedGrant = {
121 | AccessControlPolicy: {
122 | Grants: expect.arrayContaining([grantReadAllUsers]),
123 | Owner: expect.any(Object)
124 | }
125 | }
126 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
127 | })
128 |
129 | it('should keep READ_ACP grants for All Users', () => {
130 | const expectedGrant = {
131 | AccessControlPolicy: {
132 | Grants: expect.arrayContaining([grantReadAcpAllUsers]),
133 | Owner: expect.any(Object)
134 | }
135 | }
136 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
137 | })
138 |
139 | it('should keep WRITE grants for All Users', () => {
140 | const expectedGrant = {
141 | AccessControlPolicy: {
142 | Grants: expect.arrayContaining([grantWriteAllUsers]),
143 | Owner: expect.any(Object)
144 | }
145 | }
146 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
147 | })
148 |
149 | it('should keep FULL_CONTROL grants for Canonical User', () => {
150 | const expectedGrant = {
151 | AccessControlPolicy: {
152 | Grants: expect.arrayContaining([grantFullControlCanonicalUser]),
153 | Owner: expect.any(Object)
154 | }
155 | }
156 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
157 | })
158 | })
159 |
160 | describe('invalid invocation', () => {
161 | it('should fail when event is undefined', async () => {
162 | await expect(source.handler(undefined))
163 | .rejects
164 | .toThrow('Invalid event')
165 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
166 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
167 | })
168 |
169 | it('should fail when "resource" missing from the event', async () => {
170 | const malformedEvent = {
171 | ruleId: 'S3-004'
172 | }
173 | await expect(source.handler(malformedEvent))
174 | .rejects
175 | .toThrow('Invalid event')
176 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
177 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
178 | })
179 |
180 | it('should fail when the incorrect rule is received', async () => {
181 | const malformedEvent = {
182 | resource: 'sample-bucket',
183 | ruleId: 'S3-00x'
184 | }
185 | await expect(source.handler(malformedEvent))
186 | .rejects
187 | .toThrow('Invalid event')
188 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
189 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
190 | })
191 | })
192 | })
193 |
--------------------------------------------------------------------------------
/test/AutoRemediateS3-005.test.js:
--------------------------------------------------------------------------------
1 |
2 | const source = require('../functions/AutoRemediateS3-005')
3 | const { S3Client, GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
4 | const { mockClient } = require('aws-sdk-client-mock')
5 | // eslint-disable-next-line node/no-extraneous-require
6 | require('aws-sdk-client-mock-jest')
7 |
8 | const sampleEvent = {
9 | id: 'ccc:HJzFMHchx:S3-005:S3:ap-southeast-2:sample-bucket',
10 | organisationId: 'some-organisation',
11 | accountId: 'abcdef',
12 | ruleId: 'S3-005',
13 | ruleTitle: "S3 Bucket Public 'FULL_CONTROL' Access",
14 | service: 'S3',
15 | region: 'ap-southeast-2',
16 | riskLevel: 'VERY_HIGH',
17 | categories: [
18 | 'security'
19 | ],
20 | compliances: [
21 | 'AWAF'
22 | ],
23 | message: 'Bucket sample-bucket allows public FULL_CONTROL access.',
24 | resource: 'sample-bucket',
25 | status: 'FAILURE',
26 | statusRiskLevel: 'FAILURE:1',
27 | lastUpdatedDate: null,
28 | lastUpdatedBy: 'SYSTEM',
29 | resolvedBy: 'SYSTEM',
30 | eventId: 'Skzp7ra1WW',
31 | ccrn: 'ccrn:aws:HJzFMHchx:S3:global:sample-bucket',
32 | tags: [],
33 | cost: '0',
34 | waste: '0',
35 | lastModifiedDate: '1511060191925',
36 | lastModifiedBy: 'SYSTEM'
37 | }
38 |
39 | describe('S3-005 AutoRemediation', () => {
40 | const grantReadAcpAllUsers = {
41 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
42 | }
43 | const grantReadAllUsers = {
44 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ'
45 | }
46 | const grantWriteAcpAllUsers = {
47 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE_ACP'
48 | }
49 | const grantWriteAllUsers = {
50 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE'
51 | }
52 | const grantFullControlAlllUser = {
53 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'FULL_CONTROL'
54 | }
55 | const grantReadAcpAuthenticatedUsers = {
56 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ_ACP'
57 | }
58 | const grantFullControlCanonicalUser = {
59 | Grantee: { DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser' }, Permission: 'FULL_CONTROL'
60 | }
61 |
62 | const mockS3 = mockClient(S3Client)
63 |
64 | afterEach(() => {
65 | mockS3.reset()
66 | })
67 |
68 | describe('valid invocation', () => {
69 | beforeEach(async () => {
70 | mockS3.on(GetBucketAclCommand).resolves({
71 | Owner: {
72 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef'
73 | },
74 | Grants: [
75 | {
76 | Grantee: {
77 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser'
78 | },
79 | Permission: 'FULL_CONTROL'
80 | },
81 | grantReadAllUsers,
82 | grantReadAcpAllUsers,
83 | grantWriteAllUsers,
84 | grantWriteAcpAllUsers,
85 | grantFullControlAlllUser,
86 | grantReadAcpAuthenticatedUsers,
87 | grantFullControlCanonicalUser
88 | ]
89 | })
90 |
91 | await source.handler(sampleEvent)
92 | })
93 |
94 | it('should get the correct bucket ACL from S3', () => {
95 | const expectedParams = {
96 | Bucket: 'sample-bucket'
97 | }
98 | expect(mockS3).toHaveReceivedCommandWith(GetBucketAclCommand, expectedParams)
99 | })
100 |
101 | it('should set a new ACL on the affected bucket', () => {
102 | const expectedParams = {
103 | Bucket: 'sample-bucket',
104 | AccessControlPolicy: expect.any(Object)
105 | }
106 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedParams)
107 | })
108 |
109 | it('should remove READ_ACP grants for AllUsers', () => {
110 | expect(mockS3).not.toHaveReceivedCommandWith(PutBucketAclCommand, grantReadAcpAllUsers)
111 | })
112 |
113 | it('should keep READ_ACP grants for other users', () => {
114 | const expectedGrant = {
115 | AccessControlPolicy: {
116 | Grants: expect.arrayContaining([grantReadAcpAuthenticatedUsers]),
117 | Owner: expect.any(Object)
118 | }
119 | }
120 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
121 | })
122 |
123 | it('should keep READ grants for All Users', () => {
124 | const expectedGrant = {
125 | AccessControlPolicy: {
126 | Grants: expect.arrayContaining([grantReadAllUsers]),
127 | Owner: expect.any(Object)
128 | }
129 | }
130 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
131 | })
132 |
133 | it('should keep WRITE grants for All Users', () => {
134 | const expectedGrant = {
135 | AccessControlPolicy: {
136 | Grants: expect.arrayContaining([grantWriteAllUsers]),
137 | Owner: expect.any(Object)
138 | }
139 | }
140 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
141 | })
142 |
143 | it('should keep WRITE_ACP grants for All Users', () => {
144 | const expectedGrant = {
145 | AccessControlPolicy: {
146 | Grants: expect.arrayContaining([grantWriteAcpAllUsers]),
147 | Owner: expect.any(Object)
148 | }
149 | }
150 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
151 | })
152 |
153 | it('should keep FULL_CONTROL grants for Canonical User', () => {
154 | const expectedGrant = {
155 | AccessControlPolicy: {
156 | Grants: expect.arrayContaining([grantFullControlCanonicalUser]),
157 | Owner: expect.any(Object)
158 | }
159 | }
160 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
161 | })
162 | })
163 |
164 | describe('invalid invocation', () => {
165 | it('should fail when event is undefined', async () => {
166 | await expect(source.handler(undefined))
167 | .rejects
168 | .toThrow('Invalid event')
169 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
170 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
171 | })
172 |
173 | it('should fail when "resource" missing from the event', async () => {
174 | const malformedEvent = {
175 | ruleId: 'S3-00x'
176 | }
177 | await expect(source.handler(malformedEvent))
178 | .rejects
179 | .toThrow('Invalid event')
180 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
181 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
182 | })
183 |
184 | it('should fail when the incorrect rule is received', async () => {
185 | const malformedEvent = {
186 | resource: 'sample-bucket',
187 | ruleId: 'S3-00x'
188 | }
189 | await expect(source.handler(malformedEvent))
190 | .rejects
191 | .toThrow('Invalid event')
192 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
193 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
194 | })
195 | })
196 | })
197 |
--------------------------------------------------------------------------------
/test/AutoRemediateS3-006.test.js:
--------------------------------------------------------------------------------
1 |
2 | const source = require('../functions/AutoRemediateS3-006')
3 | const { S3Client, GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
4 | const { mockClient } = require('aws-sdk-client-mock')
5 | // eslint-disable-next-line node/no-extraneous-require
6 | require('aws-sdk-client-mock-jest')
7 |
8 | const sampleEvent = {
9 | id: 'ccc:HJzFMHchx:S3-006:S3:ap-southeast-2:sample-bucket',
10 | organisationId: 'some-organisation',
11 | accountId: 'abcdef',
12 | ruleId: 'S3-006',
13 | ruleTitle: "S3 Bucket Authenticated Users 'READ' Access",
14 | service: 'S3',
15 | region: 'ap-southeast-2',
16 | riskLevel: 'VERY_HIGH',
17 | categories: [
18 | 'security'
19 | ],
20 | compliances: [
21 | 'AWAF'
22 | ],
23 | message: 'Bucket sample-bucket allows authenticated users READ access.',
24 | resource: 'sample-bucket',
25 | status: 'FAILURE',
26 | statusRiskLevel: 'FAILURE:1',
27 | lastUpdatedDate: null,
28 | lastUpdatedBy: 'SYSTEM',
29 | resolvedBy: 'SYSTEM',
30 | eventId: 'Skzp7ra1WW',
31 | ccrn: 'ccrn:aws:HJzFMHchx:S3:global:sample-bucket',
32 | tags: [],
33 | cost: '0',
34 | waste: '0',
35 | lastModifiedDate: '1511060191925',
36 | lastModifiedBy: 'SYSTEM'
37 | }
38 |
39 | describe('S3-006 AutoRemediation', () => {
40 | const grantReadAcpAllUsers = {
41 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
42 | }
43 | const grantReadAllUsers = {
44 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ'
45 | }
46 | const grantWriteAcpAllUsers = {
47 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE_ACP'
48 | }
49 | const grantWriteAllUsers = {
50 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE'
51 | }
52 | const grantReadAcpAuthenticatedUsers = {
53 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ_ACP'
54 | }
55 | const grantReadAuthenticatedUsers = {
56 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ'
57 | }
58 | const grantFullControlCanonicalUser = {
59 | Grantee: { DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser' }, Permission: 'FULL_CONTROL'
60 | }
61 |
62 | const mockS3 = mockClient(S3Client)
63 |
64 | afterEach(() => {
65 | mockS3.reset()
66 | })
67 |
68 | describe('valid invocation', () => {
69 | beforeEach(async () => {
70 | mockS3.on(GetBucketAclCommand).resolves({
71 | Owner: {
72 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef'
73 | },
74 | Grants: [
75 | {
76 | Grantee: {
77 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser'
78 | },
79 | Permission: 'FULL_CONTROL'
80 | },
81 | grantReadAllUsers,
82 | grantReadAcpAllUsers,
83 | grantWriteAllUsers,
84 | grantWriteAcpAllUsers,
85 | grantReadAuthenticatedUsers,
86 | grantReadAcpAuthenticatedUsers,
87 | grantFullControlCanonicalUser
88 | ]
89 | })
90 |
91 | await source.handler(sampleEvent)
92 | })
93 |
94 | it('should get the correct bucket ACL from S3', () => {
95 | const expectedParams = {
96 | Bucket: 'sample-bucket'
97 | }
98 | expect(mockS3).toHaveReceivedCommandWith(GetBucketAclCommand, expectedParams)
99 | })
100 |
101 | it('should set a new ACL on the affected bucket', () => {
102 | const expectedParams = {
103 | Bucket: 'sample-bucket',
104 | AccessControlPolicy: expect.any(Object)
105 | }
106 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedParams)
107 | })
108 |
109 | it('should remove READ grants for Authenticated Users', () => {
110 | expect(mockS3).not.toHaveReceivedCommandWith(PutBucketAclCommand, grantReadAuthenticatedUsers)
111 | })
112 |
113 | it('should keep READ_ACP grants for other users', () => {
114 | const expectedGrant = {
115 | AccessControlPolicy: {
116 | Grants: expect.arrayContaining([grantReadAcpAuthenticatedUsers]),
117 | Owner: expect.any(Object)
118 | }
119 | }
120 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
121 | })
122 |
123 | it('should keep READ grants for All Users', () => {
124 | const expectedGrant = {
125 | AccessControlPolicy: {
126 | Grants: expect.arrayContaining([grantReadAllUsers]),
127 | Owner: expect.any(Object)
128 | }
129 | }
130 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
131 | })
132 |
133 | it('should keep READ_ACP grants for All Users', () => {
134 | const expectedGrant = {
135 | AccessControlPolicy: {
136 | Grants: expect.arrayContaining([grantReadAcpAllUsers]),
137 | Owner: expect.any(Object)
138 | }
139 | }
140 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
141 | })
142 |
143 | it('should keep WRITE grants for All Users', () => {
144 | const expectedGrant = {
145 | AccessControlPolicy: {
146 | Grants: expect.arrayContaining([grantWriteAllUsers]),
147 | Owner: expect.any(Object)
148 | }
149 | }
150 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
151 | })
152 |
153 | it('should keep WRITE_ACP grants for All Users', () => {
154 | const expectedGrant = {
155 | AccessControlPolicy: {
156 | Grants: expect.arrayContaining([grantWriteAcpAllUsers]),
157 | Owner: expect.any(Object)
158 | }
159 | }
160 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
161 | })
162 |
163 | it('should keep FULL_CONTROL grants for Canonical User', () => {
164 | const expectedGrant = {
165 | AccessControlPolicy: {
166 | Grants: expect.arrayContaining([grantFullControlCanonicalUser]),
167 | Owner: expect.any(Object)
168 | }
169 | }
170 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
171 | })
172 | })
173 |
174 | describe('invalid invocation', () => {
175 | it('should fail when event is undefined', async () => {
176 | await expect(source.handler(undefined))
177 | .rejects
178 | .toThrow('Invalid event')
179 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
180 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
181 | })
182 |
183 | it('should fail when "resource" missing from the event', async () => {
184 | const malformedEvent = {
185 | ruleId: 'S3-006'
186 | }
187 | await expect(source.handler(malformedEvent))
188 | .rejects
189 | .toThrow('Invalid event')
190 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
191 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
192 | })
193 |
194 | it('should fail when the incorrect rule is received', async () => {
195 | const malformedEvent = {
196 | resource: 'sample-bucket',
197 | ruleId: 'S3-00x'
198 | }
199 | await expect(source.handler(malformedEvent))
200 | .rejects
201 | .toThrow('Invalid event')
202 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
203 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
204 | })
205 | })
206 | })
207 |
--------------------------------------------------------------------------------
/test/AutoRemediateS3-008.test.js:
--------------------------------------------------------------------------------
1 |
2 | const source = require('../functions/AutoRemediateS3-008')
3 | const { S3Client, GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
4 | const { mockClient } = require('aws-sdk-client-mock')
5 | // eslint-disable-next-line node/no-extraneous-require
6 | require('aws-sdk-client-mock-jest')
7 |
8 | const sampleEvent = {
9 | id: 'ccc:HJzFMHchx:S3-008:S3:ap-southeast-2:sample-bucket',
10 | organisationId: 'some-organisation',
11 | accountId: 'abcdef',
12 | ruleId: 'S3-008',
13 | ruleTitle: "S3 Bucket Authenticated Users 'WRITE' Access",
14 | service: 'S3',
15 | region: 'ap-southeast-2',
16 | riskLevel: 'VERY_HIGH',
17 | categories: [
18 | 'security'
19 | ],
20 | compliances: [
21 | 'AWAF'
22 | ],
23 | message: 'Bucket sample-bucket allows authenticated users WRITE access.',
24 | resource: 'sample-bucket',
25 | status: 'FAILURE',
26 | statusRiskLevel: 'FAILURE:1',
27 | lastUpdatedDate: null,
28 | lastUpdatedBy: 'SYSTEM',
29 | resolvedBy: 'SYSTEM',
30 | eventId: 'Skzp7ra1WW',
31 | ccrn: 'ccrn:aws:HJzFMHchx:S3:global:sample-bucket',
32 | tags: [],
33 | cost: '0',
34 | waste: '0',
35 | lastModifiedDate: '1511060191925',
36 | lastModifiedBy: 'SYSTEM'
37 | }
38 |
39 | describe('S3-008 AutoRemediation', () => {
40 | const grantReadAcpAllUsers = {
41 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
42 | }
43 | const grantReadAllUsers = {
44 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ'
45 | }
46 | const grantWriteAcpAllUsers = {
47 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE_ACP'
48 | }
49 | const grantWriteAllUsers = {
50 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE'
51 | }
52 | const grantFullControlAllUsers = {
53 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'FULL_CONTROL'
54 | }
55 | const grantReadAcpAuthenticatedUsers = {
56 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ_ACP'
57 | }
58 | const grantReadAuthenticatedUsers = {
59 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ'
60 | }
61 | const grantWriteAcpAuthenticatedUsers = {
62 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE_ACP'
63 | }
64 | const grantWriteAuthenticatedUsers = {
65 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE'
66 | }
67 | const grantFullControlAuthenticatedUsers = {
68 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'FULL_CONTROL'
69 | }
70 | const grantFullControlCanonicalUser = {
71 | Grantee: { DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser' }, Permission: 'FULL_CONTROL'
72 | }
73 |
74 | const mockS3 = mockClient(S3Client)
75 |
76 | afterEach(() => {
77 | mockS3.reset()
78 | })
79 |
80 | describe('valid invocation', () => {
81 | beforeEach(async () => {
82 | mockS3.on(GetBucketAclCommand).resolves({
83 | Owner: {
84 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef'
85 | },
86 | Grants: [
87 | {
88 | Grantee: {
89 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser'
90 | },
91 | Permission: 'FULL_CONTROL'
92 | },
93 | grantReadAllUsers,
94 | grantReadAcpAllUsers,
95 | grantWriteAllUsers,
96 | grantWriteAcpAllUsers,
97 | grantFullControlAllUsers,
98 | grantReadAuthenticatedUsers,
99 | grantReadAcpAuthenticatedUsers,
100 | grantWriteAuthenticatedUsers,
101 | grantWriteAcpAuthenticatedUsers,
102 | grantFullControlAuthenticatedUsers,
103 | grantFullControlCanonicalUser
104 | ]
105 | })
106 |
107 | await source.handler(sampleEvent)
108 | })
109 |
110 | it('should get the correct bucket ACL from S3', () => {
111 | const expectedParams = {
112 | Bucket: 'sample-bucket'
113 | }
114 | expect(mockS3).toHaveReceivedCommandWith(GetBucketAclCommand, expectedParams)
115 | })
116 |
117 | it('should set a new ACL on the affected bucket', () => {
118 | const expectedParams = {
119 | Bucket: 'sample-bucket',
120 | AccessControlPolicy: expect.any(Object)
121 | }
122 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedParams)
123 | })
124 |
125 | it('should remove WRITE grants for Authenticated Users', () => {
126 | expect(mockS3).not.toHaveReceivedCommandWith(PutBucketAclCommand, grantWriteAuthenticatedUsers)
127 | })
128 |
129 | it('should keep READ_ACP grants for Authenticated Users', () => {
130 | const expectedGrant = {
131 | AccessControlPolicy: {
132 | Grants: expect.arrayContaining([grantReadAcpAuthenticatedUsers]),
133 | Owner: expect.any(Object)
134 | }
135 | }
136 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
137 | })
138 |
139 | it('should keep READ grants for Authenticated Users', () => {
140 | const expectedGrant = {
141 | AccessControlPolicy: {
142 | Grants: expect.arrayContaining([grantReadAuthenticatedUsers]),
143 | Owner: expect.any(Object)
144 | }
145 | }
146 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
147 | })
148 |
149 | it('should keep WRITE_ACP grants for Authenticated Users', () => {
150 | const expectedGrant = {
151 | AccessControlPolicy: {
152 | Grants: expect.arrayContaining([grantWriteAcpAuthenticatedUsers]),
153 | Owner: expect.any(Object)
154 | }
155 | }
156 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
157 | })
158 |
159 | it('should keep FULL_CONTROL grants for Authenticated Users', () => {
160 | const expectedGrant = {
161 | AccessControlPolicy: {
162 | Grants: expect.arrayContaining([grantFullControlAuthenticatedUsers]),
163 | Owner: expect.any(Object)
164 | }
165 | }
166 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
167 | })
168 |
169 | it('should keep READ grants for All Users', () => {
170 | const expectedGrant = {
171 | AccessControlPolicy: {
172 | Grants: expect.arrayContaining([grantReadAllUsers]),
173 | Owner: expect.any(Object)
174 | }
175 | }
176 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
177 | })
178 |
179 | it('should keep READ_ACP grants for All Users', () => {
180 | const expectedGrant = {
181 | AccessControlPolicy: {
182 | Grants: expect.arrayContaining([grantReadAcpAllUsers]),
183 | Owner: expect.any(Object)
184 | }
185 | }
186 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
187 | })
188 |
189 | it('should keep WRITE grants for All Users', () => {
190 | const expectedGrant = {
191 | AccessControlPolicy: {
192 | Grants: expect.arrayContaining([grantWriteAllUsers]),
193 | Owner: expect.any(Object)
194 | }
195 | }
196 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
197 | })
198 |
199 | it('should keep WRITE_ACP grants for All Users', () => {
200 | const expectedGrant = {
201 | AccessControlPolicy: {
202 | Grants: expect.arrayContaining([grantWriteAcpAllUsers]),
203 | Owner: expect.any(Object)
204 | }
205 | }
206 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
207 | })
208 |
209 | it('should keep FULL_CONTROL grants for All Users', () => {
210 | const expectedGrant = {
211 | AccessControlPolicy: {
212 | Grants: expect.arrayContaining([grantFullControlAllUsers]),
213 | Owner: expect.any(Object)
214 | }
215 | }
216 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
217 | })
218 |
219 | it('should keep FULL_CONTROL grants for Canonical User', () => {
220 | const expectedGrant = {
221 | AccessControlPolicy: {
222 | Grants: expect.arrayContaining([grantFullControlCanonicalUser]),
223 | Owner: expect.any(Object)
224 | }
225 | }
226 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
227 | })
228 | })
229 |
230 | describe('invalid invocation', () => {
231 | it('should fail when event is undefined', async () => {
232 | await expect(source.handler(undefined))
233 | .rejects
234 | .toThrow('Invalid event')
235 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
236 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
237 | })
238 |
239 | it('should fail when "resource" missing from the event', async () => {
240 | const malformedEvent = {
241 | ruleId: 'S3-008'
242 | }
243 | await expect(source.handler(malformedEvent))
244 | .rejects
245 | .toThrow('Invalid event')
246 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
247 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
248 | })
249 |
250 | it('should fail when the incorrect rule is received', async () => {
251 | const malformedEvent = {
252 | resource: 'sample-bucket',
253 | ruleId: 'S3-00x'
254 | }
255 | await expect(source.handler(malformedEvent))
256 | .rejects
257 | .toThrow('Invalid event')
258 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
259 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
260 | })
261 | })
262 | })
263 |
--------------------------------------------------------------------------------
/test/AutoRemediateS3-009.test.js:
--------------------------------------------------------------------------------
1 |
2 | const source = require('../functions/AutoRemediateS3-009')
3 | const { S3Client, GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
4 | const { mockClient } = require('aws-sdk-client-mock')
5 | // eslint-disable-next-line node/no-extraneous-require
6 | require('aws-sdk-client-mock-jest')
7 |
8 | const sampleEvent = {
9 | id: 'ccc:HJzFMHchx:S3-009:S3:ap-southeast-2:sample-bucket',
10 | organisationId: 'some-organisation',
11 | accountId: 'abcdef',
12 | ruleId: 'S3-009',
13 | ruleTitle: "S3 Bucket Authenticated Users 'WRITE_ACP' Access",
14 | service: 'S3',
15 | region: 'ap-southeast-2',
16 | riskLevel: 'VERY_HIGH',
17 | categories: [
18 | 'security'
19 | ],
20 | compliances: [
21 | 'AWAF'
22 | ],
23 | message: 'Bucket sample-bucket allows authenticated users WRITE_ACP access.',
24 | resource: 'sample-bucket',
25 | status: 'FAILURE',
26 | statusRiskLevel: 'FAILURE:1',
27 | lastUpdatedDate: null,
28 | lastUpdatedBy: 'SYSTEM',
29 | resolvedBy: 'SYSTEM',
30 | eventId: 'Skzp7ra1WW',
31 | ccrn: 'ccrn:aws:HJzFMHchx:S3:global:sample-bucket',
32 | tags: [],
33 | cost: '0',
34 | waste: '0',
35 | lastModifiedDate: '1511060191925',
36 | lastModifiedBy: 'SYSTEM'
37 | }
38 |
39 | describe('S3-009 AutoRemediation', () => {
40 | const grantReadAcpAllUsers = {
41 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
42 | }
43 | const grantReadAllUsers = {
44 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ'
45 | }
46 | const grantWriteAcpAllUsers = {
47 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE_ACP'
48 | }
49 | const grantWriteAllUsers = {
50 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE'
51 | }
52 | const grantFullControlAllUsers = {
53 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'FULL_CONTROL'
54 | }
55 | const grantReadAcpAuthenticatedUsers = {
56 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ_ACP'
57 | }
58 | const grantReadAuthenticatedUsers = {
59 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ'
60 | }
61 | const grantWriteAcpAuthenticatedUsers = {
62 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE_ACP'
63 | }
64 | const grantWriteAuthenticatedUsers = {
65 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE'
66 | }
67 | const grantFullControlAuthenticatedUsers = {
68 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'FULL_CONTROL'
69 | }
70 | const grantFullControlCanonicalUser = {
71 | Grantee: { DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser' }, Permission: 'FULL_CONTROL'
72 | }
73 |
74 | const mockS3 = mockClient(S3Client)
75 |
76 | afterEach(() => {
77 | mockS3.reset()
78 | })
79 |
80 | describe('valid invocation', () => {
81 | beforeEach(async () => {
82 | mockS3.on(GetBucketAclCommand).resolves({
83 | Owner: {
84 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef'
85 | },
86 | Grants: [
87 | {
88 | Grantee: {
89 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser'
90 | },
91 | Permission: 'FULL_CONTROL'
92 | },
93 | grantReadAllUsers,
94 | grantReadAcpAllUsers,
95 | grantWriteAllUsers,
96 | grantWriteAcpAllUsers,
97 | grantFullControlAllUsers,
98 | grantReadAuthenticatedUsers,
99 | grantReadAcpAuthenticatedUsers,
100 | grantWriteAuthenticatedUsers,
101 | grantWriteAcpAuthenticatedUsers,
102 | grantFullControlAuthenticatedUsers,
103 | grantFullControlCanonicalUser
104 | ]
105 | })
106 |
107 | await source.handler(sampleEvent)
108 | })
109 |
110 | it('should get the correct bucket ACL from S3', () => {
111 | const expectedParams = {
112 | Bucket: 'sample-bucket'
113 | }
114 | expect(mockS3).toHaveReceivedCommandWith(GetBucketAclCommand, expectedParams)
115 | })
116 |
117 | it('should set a new ACL on the affected bucket', () => {
118 | const expectedParams = {
119 | Bucket: 'sample-bucket',
120 | AccessControlPolicy: expect.any(Object)
121 | }
122 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedParams)
123 | })
124 |
125 | it('should remove WRITE_ACP grants for Authenticated Users', () => {
126 | expect(mockS3).not.toHaveReceivedCommandWith(PutBucketAclCommand, grantWriteAcpAuthenticatedUsers)
127 | })
128 |
129 | it('should keep READ_ACP grants for Authenticated Users', () => {
130 | const expectedGrant = {
131 | AccessControlPolicy: {
132 | Grants: expect.arrayContaining([grantReadAcpAuthenticatedUsers]),
133 | Owner: expect.any(Object)
134 | }
135 | }
136 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
137 | })
138 |
139 | it('should keep READ grants for Authenticated Users', () => {
140 | const expectedGrant = {
141 | AccessControlPolicy: {
142 | Grants: expect.arrayContaining([grantReadAuthenticatedUsers]),
143 | Owner: expect.any(Object)
144 | }
145 | }
146 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
147 | })
148 |
149 | it('should keep WRITE grants for Authenticated Users', () => {
150 | const expectedGrant = {
151 | AccessControlPolicy: {
152 | Grants: expect.arrayContaining([grantWriteAuthenticatedUsers]),
153 | Owner: expect.any(Object)
154 | }
155 | }
156 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
157 | })
158 |
159 | it('should keep FULL_CONTROL grants for Authenticated Users', () => {
160 | const expectedGrant = {
161 | AccessControlPolicy: {
162 | Grants: expect.arrayContaining([grantFullControlAuthenticatedUsers]),
163 | Owner: expect.any(Object)
164 | }
165 | }
166 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
167 | })
168 |
169 | it('should keep READ grants for All Users', () => {
170 | const expectedGrant = {
171 | AccessControlPolicy: {
172 | Grants: expect.arrayContaining([grantReadAllUsers]),
173 | Owner: expect.any(Object)
174 | }
175 | }
176 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
177 | })
178 |
179 | it('should keep READ_ACP grants for All Users', () => {
180 | const expectedGrant = {
181 | AccessControlPolicy: {
182 | Grants: expect.arrayContaining([grantReadAcpAllUsers]),
183 | Owner: expect.any(Object)
184 | }
185 | }
186 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
187 | })
188 |
189 | it('should keep WRITE grants for All Users', () => {
190 | const expectedGrant = {
191 | AccessControlPolicy: {
192 | Grants: expect.arrayContaining([grantWriteAllUsers]),
193 | Owner: expect.any(Object)
194 | }
195 | }
196 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
197 | })
198 |
199 | it('should keep WRITE_ACP grants for All Users', () => {
200 | const expectedGrant = {
201 | AccessControlPolicy: {
202 | Grants: expect.arrayContaining([grantWriteAcpAllUsers]),
203 | Owner: expect.any(Object)
204 | }
205 | }
206 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
207 | })
208 |
209 | it('should keep FULL_CONTROL grants for All Users', () => {
210 | const expectedGrant = {
211 | AccessControlPolicy: {
212 | Grants: expect.arrayContaining([grantFullControlAllUsers]),
213 | Owner: expect.any(Object)
214 | }
215 | }
216 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
217 | })
218 |
219 | it('should keep FULL_CONTROL grants for Canonical User', () => {
220 | const expectedGrant = {
221 | AccessControlPolicy: {
222 | Grants: expect.arrayContaining([grantFullControlCanonicalUser]),
223 | Owner: expect.any(Object)
224 | }
225 | }
226 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
227 | })
228 | })
229 |
230 | describe('invalid invocation', () => {
231 | it('should fail when event is undefined', async () => {
232 | await expect(source.handler(undefined))
233 | .rejects
234 | .toThrow('Invalid event')
235 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
236 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
237 | })
238 |
239 | it('should fail when "resource" missing from the event', async () => {
240 | const malformedEvent = {
241 | ruleId: 'S3-006'
242 | }
243 | await expect(source.handler(malformedEvent))
244 | .rejects
245 | .toThrow('Invalid event')
246 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
247 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
248 | })
249 |
250 | it('should fail when the incorrect rule is received', async () => {
251 | const malformedEvent = {
252 | resource: 'sample-bucket',
253 | ruleId: 'S3-00x'
254 | }
255 | await expect(source.handler(malformedEvent))
256 | .rejects
257 | .toThrow('Invalid event')
258 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
259 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
260 | })
261 | })
262 | })
263 |
--------------------------------------------------------------------------------
/test/AutoRemediateS3-010.test.js:
--------------------------------------------------------------------------------
1 |
2 | const source = require('../functions/AutoRemediateS3-010')
3 | const { S3Client, GetBucketAclCommand, PutBucketAclCommand } = require('@aws-sdk/client-s3')
4 | const { mockClient } = require('aws-sdk-client-mock')
5 | // eslint-disable-next-line node/no-extraneous-require
6 | require('aws-sdk-client-mock-jest')
7 |
8 | const sampleEvent = {
9 | id: 'ccc:HJzFMHchx:S3-010:S3:ap-southeast-2:sample-bucket',
10 | organisationId: 'some-organisation',
11 | accountId: 'abcdef',
12 | ruleId: 'S3-010',
13 | ruleTitle: "S3 Bucket Authenticated Users 'FULL_CONTROL' Access",
14 | service: 'S3',
15 | region: 'ap-southeast-2',
16 | riskLevel: 'VERY_HIGH',
17 | categories: [
18 | 'security'
19 | ],
20 | compliances: [
21 | 'AWAF'
22 | ],
23 | message: 'Bucket sample-bucket allows authenticated users FULL_CONTROL access.',
24 | resource: 'sample-bucket',
25 | status: 'FAILURE',
26 | statusRiskLevel: 'FAILURE:1',
27 | lastUpdatedDate: null,
28 | lastUpdatedBy: 'SYSTEM',
29 | resolvedBy: 'SYSTEM',
30 | eventId: 'Skzp7ra1WW',
31 | ccrn: 'ccrn:aws:HJzFMHchx:S3:global:sample-bucket',
32 | tags: [],
33 | cost: '0',
34 | waste: '0',
35 | lastModifiedDate: '1511060191925',
36 | lastModifiedBy: 'SYSTEM'
37 | }
38 |
39 | describe('S3-010 AutoRemediation', () => {
40 | const grantReadAcpAllUsers = {
41 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ_ACP'
42 | }
43 | const grantReadAllUsers = {
44 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'READ'
45 | }
46 | const grantWriteAcpAllUsers = {
47 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE_ACP'
48 | }
49 | const grantWriteAllUsers = {
50 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'WRITE'
51 | }
52 | const grantFullControlAllUsers = {
53 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AllUsers' }, Permission: 'FULL_CONTROL'
54 | }
55 | const grantReadAcpAuthenticatedUsers = {
56 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ_ACP'
57 | }
58 | const grantReadAuthenticatedUsers = {
59 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'READ'
60 | }
61 | const grantWriteAcpAuthenticatedUsers = {
62 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE_ACP'
63 | }
64 | const grantWriteAuthenticatedUsers = {
65 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'WRITE'
66 | }
67 | const grantFullControlAuthenticatedUsers = {
68 | Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' }, Permission: 'FULL_CONTROL'
69 | }
70 | const grantFullControlCanonicalUser = {
71 | Grantee: { DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser' }, Permission: 'FULL_CONTROL'
72 | }
73 |
74 | const mockS3 = mockClient(S3Client)
75 |
76 | afterEach(() => {
77 | mockS3.reset()
78 | })
79 |
80 | describe('valid invocation', () => {
81 | beforeEach(async () => {
82 | mockS3.on(GetBucketAclCommand).resolves({
83 | Owner: {
84 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef'
85 | },
86 | Grants: [
87 | {
88 | Grantee: {
89 | DisplayName: 'user_name', ID: 'account_user_id123455667890abcdef', Type: 'CanonicalUser'
90 | },
91 | Permission: 'FULL_CONTROL'
92 | },
93 | grantReadAllUsers,
94 | grantReadAcpAllUsers,
95 | grantWriteAllUsers,
96 | grantWriteAcpAllUsers,
97 | grantFullControlAllUsers,
98 | grantReadAuthenticatedUsers,
99 | grantReadAcpAuthenticatedUsers,
100 | grantWriteAuthenticatedUsers,
101 | grantWriteAcpAuthenticatedUsers,
102 | grantFullControlAuthenticatedUsers,
103 | grantFullControlCanonicalUser
104 | ]
105 | })
106 | await source.handler(sampleEvent)
107 | })
108 |
109 | it('should get the correct bucket ACL from S3', () => {
110 | const expectedParams = {
111 | Bucket: 'sample-bucket'
112 | }
113 | expect(mockS3).toHaveReceivedCommandWith(GetBucketAclCommand, expectedParams)
114 | })
115 |
116 | it('should set a new ACL on the affected bucket', () => {
117 | const expectedParams = {
118 | Bucket: 'sample-bucket',
119 | AccessControlPolicy: expect.any(Object)
120 | }
121 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedParams)
122 | })
123 |
124 | it('should remove FULL_CONTROL grants for Authenticated Users', () => {
125 | expect(mockS3).not.toHaveReceivedCommandWith(PutBucketAclCommand, grantFullControlAuthenticatedUsers)
126 | })
127 |
128 | it('should keep READ_ACP grants for Authenticated Users', () => {
129 | const expectedGrant = {
130 | AccessControlPolicy: {
131 | Grants: expect.arrayContaining([grantReadAcpAuthenticatedUsers]),
132 | Owner: expect.any(Object)
133 | }
134 | }
135 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
136 | })
137 |
138 | it('should keep READ grants for Authenticated Users', () => {
139 | const expectedGrant = {
140 | AccessControlPolicy: {
141 | Grants: expect.arrayContaining([grantReadAuthenticatedUsers]),
142 | Owner: expect.any(Object)
143 | }
144 | }
145 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
146 | })
147 |
148 | it('should keep WRITE grants for Authenticated Users', () => {
149 | const expectedGrant = {
150 | AccessControlPolicy: {
151 | Grants: expect.arrayContaining([grantWriteAuthenticatedUsers]),
152 | Owner: expect.any(Object)
153 | }
154 | }
155 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
156 | })
157 |
158 | it('should keep WRITE_ACP grants for Authenticated Users', () => {
159 | const expectedGrant = {
160 | AccessControlPolicy: {
161 | Grants: expect.arrayContaining([grantWriteAcpAuthenticatedUsers]),
162 | Owner: expect.any(Object)
163 | }
164 | }
165 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
166 | })
167 |
168 | it('should keep READ grants for All Users', () => {
169 | const expectedGrant = {
170 | AccessControlPolicy: {
171 | Grants: expect.arrayContaining([grantReadAllUsers]),
172 | Owner: expect.any(Object)
173 | }
174 | }
175 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
176 | })
177 |
178 | it('should keep READ_ACP grants for All Users', () => {
179 | const expectedGrant = {
180 | AccessControlPolicy: {
181 | Grants: expect.arrayContaining([grantReadAcpAllUsers]),
182 | Owner: expect.any(Object)
183 | }
184 | }
185 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
186 | })
187 |
188 | it('should keep WRITE grants for All Users', () => {
189 | const expectedGrant = {
190 | AccessControlPolicy: {
191 | Grants: expect.arrayContaining([grantWriteAllUsers]),
192 | Owner: expect.any(Object)
193 | }
194 | }
195 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
196 | })
197 |
198 | it('should keep WRITE_ACP grants for All Users', () => {
199 | const expectedGrant = {
200 | AccessControlPolicy: {
201 | Grants: expect.arrayContaining([grantWriteAcpAllUsers]),
202 | Owner: expect.any(Object)
203 | }
204 | }
205 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
206 | })
207 |
208 | it('should keep FULL_CONTROL grants for All Users', () => {
209 | const expectedGrant = {
210 | AccessControlPolicy: {
211 | Grants: expect.arrayContaining([grantFullControlAllUsers]),
212 | Owner: expect.any(Object)
213 | }
214 | }
215 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
216 | })
217 |
218 | it('should keep FULL_CONTROL grants for Canonical User', () => {
219 | const expectedGrant = {
220 | AccessControlPolicy: {
221 | Grants: expect.arrayContaining([grantFullControlCanonicalUser]),
222 | Owner: expect.any(Object)
223 | }
224 | }
225 | expect(mockS3).toHaveReceivedCommandWith(PutBucketAclCommand, expectedGrant)
226 | })
227 | })
228 |
229 | describe('invalid invocation', () => {
230 | it('should fail when event is undefined', async () => {
231 | await expect(source.handler(undefined))
232 | .rejects
233 | .toThrow('Invalid event')
234 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
235 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
236 | })
237 |
238 | it('should fail when "resource" missing from the event', async () => {
239 | const malformedEvent = {
240 | ruleId: 'S3-006'
241 | }
242 | await expect(source.handler(malformedEvent))
243 | .rejects
244 | .toThrow('Invalid event')
245 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
246 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
247 | })
248 |
249 | it('should fail when the incorrect rule is received', async () => {
250 | const malformedEvent = {
251 | resource: 'sample-bucket',
252 | ruleId: 'S3-00x'
253 | }
254 | await expect(source.handler(malformedEvent))
255 | .rejects
256 | .toThrow('Invalid event')
257 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
258 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
259 | })
260 | })
261 | })
262 |
--------------------------------------------------------------------------------
/test/AutoRemediateS3-007.test.js:
--------------------------------------------------------------------------------
1 | const source = require('../functions/AutoRemediateS3-007')
2 | const {
3 | S3Client,
4 | GetBucketAclCommand,
5 | PutBucketAclCommand
6 | } = require('@aws-sdk/client-s3')
7 | const { mockClient } = require('aws-sdk-client-mock')
8 | // eslint-disable-next-line node/no-extraneous-require
9 | require('aws-sdk-client-mock-jest')
10 |
11 | const sampleEvent = {
12 | id: 'ccc:HJzFMHchx:S3-007:S3:ap-southeast-2:sample-bucket',
13 | organisationId: 'some-organisation',
14 | accountId: 'abcdef',
15 | ruleId: 'S3-007',
16 | ruleTitle: "S3 Bucket Authenticated Users 'READ_ACP' Access",
17 | service: 'S3',
18 | region: 'ap-southeast-2',
19 | riskLevel: 'VERY_HIGH',
20 | categories: ['security'],
21 | compliances: ['AWAF'],
22 | message: 'Bucket sample-bucket allows authenticated users READ_ACP access.',
23 | resource: 'sample-bucket',
24 | status: 'FAILURE',
25 | statusRiskLevel: 'FAILURE:1',
26 | lastUpdatedDate: null,
27 | lastUpdatedBy: 'SYSTEM',
28 | resolvedBy: 'SYSTEM',
29 | eventId: 'Skzp7ra1WW',
30 | ccrn: 'ccrn:aws:HJzFMHchx:S3:global:sample-bucket',
31 | tags: [],
32 | cost: '0',
33 | waste: '0',
34 | lastModifiedDate: '1511060191925',
35 | lastModifiedBy: 'SYSTEM'
36 | }
37 |
38 | describe('S3-007 AutoRemediation', () => {
39 | const grantReadAcpAllUsers = {
40 | Grantee: {
41 | Type: 'Group',
42 | URI: 'http://acs.amazonaws.com/groups/global/AllUsers'
43 | },
44 | Permission: 'READ_ACP'
45 | }
46 | const grantReadAllUsers = {
47 | Grantee: {
48 | Type: 'Group',
49 | URI: 'http://acs.amazonaws.com/groups/global/AllUsers'
50 | },
51 | Permission: 'READ'
52 | }
53 | const grantWriteAcpAllUsers = {
54 | Grantee: {
55 | Type: 'Group',
56 | URI: 'http://acs.amazonaws.com/groups/global/AllUsers'
57 | },
58 | Permission: 'WRITE_ACP'
59 | }
60 | const grantWriteAllUsers = {
61 | Grantee: {
62 | Type: 'Group',
63 | URI: 'http://acs.amazonaws.com/groups/global/AllUsers'
64 | },
65 | Permission: 'WRITE'
66 | }
67 | const grantFullControlAllUsers = {
68 | Grantee: {
69 | Type: 'Group',
70 | URI: 'http://acs.amazonaws.com/groups/global/AllUsers'
71 | },
72 | Permission: 'FULL_CONTROL'
73 | }
74 | const grantReadAcpAuthenticatedUsers = {
75 | Grantee: {
76 | Type: 'Group',
77 | URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers'
78 | },
79 | Permission: 'READ_ACP'
80 | }
81 | const grantReadAuthenticatedUsers = {
82 | Grantee: {
83 | Type: 'Group',
84 | URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers'
85 | },
86 | Permission: 'READ'
87 | }
88 | const grantWriteAcpAuthenticatedUsers = {
89 | Grantee: {
90 | Type: 'Group',
91 | URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers'
92 | },
93 | Permission: 'WRITE_ACP'
94 | }
95 | const grantWriteAuthenticatedUsers = {
96 | Grantee: {
97 | Type: 'Group',
98 | URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers'
99 | },
100 | Permission: 'WRITE'
101 | }
102 | const grantFullControlAuthenticatedUsers = {
103 | Grantee: {
104 | Type: 'Group',
105 | URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers'
106 | },
107 | Permission: 'FULL_CONTROL'
108 | }
109 | const grantFullControlCanonicalUser = {
110 | Grantee: {
111 | DisplayName: 'user_name',
112 | ID: 'account_user_id123455667890abcdef',
113 | Type: 'CanonicalUser'
114 | },
115 | Permission: 'FULL_CONTROL'
116 | }
117 |
118 | const mockS3 = mockClient(S3Client)
119 |
120 | afterEach(() => {
121 | mockS3.reset()
122 | })
123 |
124 | describe('valid invocation', () => {
125 | beforeEach(async () => {
126 | mockS3.on(GetBucketAclCommand).resolves({
127 | Owner: {
128 | DisplayName: 'user_name',
129 | ID: 'account_user_id123455667890abcdef'
130 | },
131 | Grants: [
132 | {
133 | Grantee: {
134 | DisplayName: 'user_name',
135 | ID: 'account_user_id123455667890abcdef',
136 | Type: 'CanonicalUser'
137 | },
138 | Permission: 'FULL_CONTROL'
139 | },
140 | grantReadAllUsers,
141 | grantReadAcpAllUsers,
142 | grantWriteAllUsers,
143 | grantWriteAcpAllUsers,
144 | grantFullControlAllUsers,
145 | grantReadAuthenticatedUsers,
146 | grantReadAcpAuthenticatedUsers,
147 | grantWriteAuthenticatedUsers,
148 | grantWriteAcpAuthenticatedUsers,
149 | grantFullControlAuthenticatedUsers,
150 | grantFullControlCanonicalUser
151 | ]
152 | })
153 |
154 | await source.handler(sampleEvent)
155 | })
156 |
157 | it('should get the correct bucket ACL from S3', () => {
158 | const expectedParams = {
159 | Bucket: 'sample-bucket'
160 | }
161 | expect(mockS3).toHaveReceivedCommandWith(
162 | GetBucketAclCommand,
163 | expectedParams
164 | )
165 | })
166 |
167 | it('should set a new ACL on the affected bucket', () => {
168 | const expectedParams = {
169 | Bucket: 'sample-bucket',
170 | AccessControlPolicy: expect.any(Object)
171 | }
172 | expect(mockS3).toHaveReceivedCommandWith(
173 | PutBucketAclCommand,
174 | expectedParams
175 | )
176 | })
177 |
178 | it('should remove READ_ACP grants for Authenticated Users', () => {
179 | expect(mockS3).not.toHaveReceivedCommandWith(
180 | PutBucketAclCommand,
181 | grantReadAcpAuthenticatedUsers
182 | )
183 | })
184 |
185 | it('should keep READ grants for Authenticated Users', () => {
186 | const expectedGrant = {
187 | AccessControlPolicy: {
188 | Grants: expect.arrayContaining([grantReadAuthenticatedUsers]),
189 | Owner: expect.any(Object)
190 | }
191 | }
192 | expect(mockS3).toHaveReceivedCommandWith(
193 | PutBucketAclCommand,
194 | expectedGrant
195 | )
196 | })
197 |
198 | it('should keep WRITE grants for Authenticated Users', () => {
199 | const expectedGrant = {
200 | AccessControlPolicy: {
201 | Grants: expect.arrayContaining([grantWriteAuthenticatedUsers]),
202 | Owner: expect.any(Object)
203 | }
204 | }
205 | expect(mockS3).toHaveReceivedCommandWith(
206 | PutBucketAclCommand,
207 | expectedGrant
208 | )
209 | })
210 |
211 | it('should keep WRITE_ACP grants for Authenticated Users', () => {
212 | const expectedGrant = {
213 | AccessControlPolicy: {
214 | Grants: expect.arrayContaining([grantWriteAcpAuthenticatedUsers]),
215 | Owner: expect.any(Object)
216 | }
217 | }
218 | expect(mockS3).toHaveReceivedCommandWith(
219 | PutBucketAclCommand,
220 | expectedGrant
221 | )
222 | })
223 |
224 | it('should keep FULL_CONTROL grants for Authenticated Users', () => {
225 | const expectedGrant = {
226 | AccessControlPolicy: {
227 | Grants: expect.arrayContaining([grantFullControlAuthenticatedUsers]),
228 | Owner: expect.any(Object)
229 | }
230 | }
231 | expect(mockS3).toHaveReceivedCommandWith(
232 | PutBucketAclCommand,
233 | expectedGrant
234 | )
235 | })
236 |
237 | it('should keep READ grants for All Users', () => {
238 | const expectedGrant = {
239 | AccessControlPolicy: {
240 | Grants: expect.arrayContaining([grantReadAllUsers]),
241 | Owner: expect.any(Object)
242 | }
243 | }
244 | expect(mockS3).toHaveReceivedCommandWith(
245 | PutBucketAclCommand,
246 | expectedGrant
247 | )
248 | })
249 |
250 | it('should keep READ_ACP grants for All Users', () => {
251 | const expectedGrant = {
252 | AccessControlPolicy: {
253 | Grants: expect.arrayContaining([grantReadAcpAllUsers]),
254 | Owner: expect.any(Object)
255 | }
256 | }
257 | expect(mockS3).toHaveReceivedCommandWith(
258 | PutBucketAclCommand,
259 | expectedGrant
260 | )
261 | })
262 |
263 | it('should keep WRITE grants for All Users', () => {
264 | const expectedGrant = {
265 | AccessControlPolicy: {
266 | Grants: expect.arrayContaining([grantWriteAllUsers]),
267 | Owner: expect.any(Object)
268 | }
269 | }
270 | expect(mockS3).toHaveReceivedCommandWith(
271 | PutBucketAclCommand,
272 | expectedGrant
273 | )
274 | })
275 |
276 | it('should keep WRITE_ACP grants for All Users', () => {
277 | const expectedGrant = {
278 | AccessControlPolicy: {
279 | Grants: expect.arrayContaining([grantWriteAcpAllUsers]),
280 | Owner: expect.any(Object)
281 | }
282 | }
283 | expect(mockS3).toHaveReceivedCommandWith(
284 | PutBucketAclCommand,
285 | expectedGrant
286 | )
287 | })
288 |
289 | it('should keep FULL_CONTROL grants for All Users', () => {
290 | const expectedGrant = {
291 | AccessControlPolicy: {
292 | Grants: expect.arrayContaining([grantFullControlAllUsers]),
293 | Owner: expect.any(Object)
294 | }
295 | }
296 | expect(mockS3).toHaveReceivedCommandWith(
297 | PutBucketAclCommand,
298 | expectedGrant
299 | )
300 | })
301 |
302 | it('should keep FULL_CONTROL grants for Canonical User', () => {
303 | const expectedGrant = {
304 | AccessControlPolicy: {
305 | Grants: expect.arrayContaining([grantFullControlCanonicalUser]),
306 | Owner: expect.any(Object)
307 | }
308 | }
309 | expect(mockS3).toHaveReceivedCommandWith(
310 | PutBucketAclCommand,
311 | expectedGrant
312 | )
313 | })
314 | })
315 |
316 | describe('invalid invocation', () => {
317 | it('should fail when event is undefined', async () => {
318 | await expect(source.handler(undefined))
319 | .rejects
320 | .toThrow('Invalid event')
321 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
322 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
323 | })
324 |
325 | it('should fail when "resource" missing from the event', async () => {
326 | const malformedEvent = {
327 | ruleId: 'S3-007'
328 | }
329 | await expect(source.handler(malformedEvent))
330 | .rejects
331 | .toThrow('Invalid event')
332 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
333 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
334 | })
335 |
336 | it('should fail when the incorrect rule is received', async () => {
337 | const malformedEvent = {
338 | resource: 'sample-bucket',
339 | ruleId: 'S3-00x'
340 | }
341 | await expect(source.handler(malformedEvent))
342 | .rejects
343 | .toThrow('Invalid event')
344 | expect(mockS3).not.toHaveReceivedCommand(GetBucketAclCommand)
345 | expect(mockS3).not.toHaveReceivedCommand(PutBucketAclCommand)
346 | })
347 | })
348 | })
349 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Cloud Conformity](https://www.cloudconformity.com/?utm_source=github) Auto Remediation
2 |
3 | ## Disclaimer
4 | This early "Auto Remediation" is subject to change. Cloud Conformity will use commercially reasonable efforts to support the previous version of the project.
5 | This project is provided on an ‘AS IS’ and ‘WHEN AVAILABLE’ basis. Cloud Conformity has no liability to user as a result of any changes made to their AWS infrastructure by installing this project.
6 |
7 | Auto Remediation is an MIT open-source project, actively maintained by Cloud Conformity team.
8 |
9 | ## How it works
10 |
11 | The following image shows how Cloud Conformity Auto Remediation works:
12 |
13 | 
14 |
15 | Here's an example:
16 |
17 | 1. A user makes an S3 bucket publicly readable via S3 Access Control Lists (ACLs)
18 | 2. Cloud Conformity identifies the risk in real-time
19 | 3. Cloud Conformity publishes a message to the specified SNS Topic
20 | 3. SNS topic triggers the Orchestrator lambda function which in turns calls S3 bucket auto-remediate function
21 | 4. S3 BucketPublicReadAccess Auto Remediate Function (AutoRemediateS3-001) updates the S3 bucket ACL and closes the security gap
22 |
23 |
24 | ## Prerequisites
25 | 1. Install [Node.js](https://nodejs.org/en/) v18 or later.
26 |
27 | ## Installation
28 | > Note that you need to follow the [Deleting a stack on the AWS CloudFormation console](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-console-delete-stack.html) to delete the current stack if you plan to update to the latest version.
29 |
30 |
31 | 1. Create a working copy of "Cloud Conformity Auto Remediation" repository by running the following command:
32 | ```bash
33 | git clone https://github.com/cloudconformity/auto-remediate.git
34 | ```
35 | 2. Change directory to auto-remediation:
36 |
37 | ```bash
38 | cd auto-remediate
39 | ```
40 | 3. `IMPORTANT` Update `functions/config.json` with required configurations. Please note that all the rules in config file are disabled by default to prevent unwanted changes. User needs to enable the ones they need manually.
41 | 4. `IMPORTANT` Make any other necessary adjustments before deployment.
42 | 5. Run `npm install` before deploying so that the node_modules folder would be available to AWS
43 |
44 | ```bash
45 | npm install
46 | ```
47 | 6. Finally deploy
48 |
49 | ```bash
50 | npx serverless deploy --region us-west-2
51 | ```
52 |
53 | ## Message Format
54 |
55 | The table below gives more information about SNS Message format:
56 |
57 | | Name | Values |
58 | | ------------- | ------------- |
59 | | organisationId | Your Cloud Conformity Organisation Id |
60 | | accountId | Your Cloud Conformity Account Id |
61 | | accountName | Your Cloud Conformity Account Name |
62 | | service | AutoScaling \| CloudFormation \| CloudFront \| CloudTrail \| CloudWatch \| CloudWatchEvents \|
CloudWatchLogs \| Config \| DynamoDB \| EBS \| EC2 \| ElastiCache \| Elasticsearch \| ELB \| IAM \|
KMS \| RDS \| Redshift \| ResourceGroup \| Route53 \| S3 \| SES \| SNS \| SQS \| VPC \| WAF \|
ACM \| Inspector \| TrustedAdvisor \| Shield \| EMR \| Lambda \| Support \| Organizations \|
Kinesis \| EFS
For more information about services, please refer to [Cloud Conformity Services Endpoint](https://us-west-2.cloudconformity.com/v1/services) |
63 | | region | global \| us-east-2 \| us-east-1 \| us-west-1 \| us-west-2 \| ap-south-1 \| ap-northeast-2 \|
ap-southeast-1 \| ap-southeast-2 \| ap-northeast-1 \| ca-central-1 \| eu-central-1 \| eu-west-1 \|
eu-west-2 \| sa-east-1
For more information about regions, please refer to [Cloud Conformity Region Endpoint](https://us-west-2.cloudconformity.com/v1/regions) |
64 | | id | Check Id |
65 | | resource | AWS Resource |
66 | | ccrn | Cloud Conformity Resource Name |
67 | | ruleId | e.g. S3-001
For more information about rules, please refer to [Cloud Conformity Services Endpoint](https://us-west-2.cloudconformity.com/v1/services) |
68 | | ruleTitle | e.g. BucketPublicReadAccess
For more information about rules, please refer to [Cloud Conformity Services Endpoint](https://us-west-2.cloudconformity.com/v1/services) |
69 | | statuses | SUCCESS \| FAILURE |
70 | | categories | security \| cost-optimisation \| reliability \| performance-efficiency \| operational-excellence
For more information about categories, please refer to [Cloud Conformity Services Endpoint](https://us-west-2.cloudconformity.com/v1/services) |
71 | | riskLevels | LOW\| MEDIUM \| HIGH \| VERY_HIGH \| EXTREME
For more information about risk levels, please refer to [Cloud Conformity Services Endpoint](https://us-west-2.cloudconformity.com/v1/services) |
72 | | message | e.g. Bucket my-bucket-name allows public 'READ' access |
73 | | createdDate | The date when the check was created
The numeric value of the specified date as the number of milliseconds since January 1, 1970, 00:00:00 UTC |
74 | | failureDiscoveryDate | The date when the check the failure was discovered
The numeric value of the specified date as the number of milliseconds since January 1, 1970, 00:00:00 UTC |
75 | | tags | Any assigned metadata tags to your AWS resources |
76 |
77 |
78 | ## Auto Remediate Functions
79 |
80 | The table below lists the supported auto auto-remediate functions:
81 |
82 | | Service | Rule Id | Description |
83 | | ------------- | ------------- | ------------- |
84 | | CloudFormation | [CFM-005](https://www.cloudconformity.com/knowledge-base/aws/CloudFormation/stack-termination-protection.html) | Ensure Termination Protection feature is enabled for your AWS CloudFormation stacks |
85 | | Config | [Config-001](https://www.cloudconformity.com/knowledge-base/aws/Config/aws-config-enabled.html) | Ensure AWS Config is enabled in all regions |
86 | | S3 | [S3-001](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-public-read-access.html) | Ensure S3 buckets do not allow public READ access |
87 | | S3 | [S3-002](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-public-read-acp-access.html) | Ensure S3 buckets do not allow public READ_ACP access |
88 | | S3 | [S3-003](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-public-write-access.html) | Ensure S3 buckets do not allow public WRITE access |
89 | | S3 | [S3-004](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-public-write-acp-access.html) | Ensure S3 buckets do not allow public WRITE_ACP access |
90 | | S3 | [S3-005](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-public-full-control-access.html) | Ensure S3 buckets do not allow public FULL_CONTROL access |
91 | | S3 | [S3-006](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-authenticated-users-read-access.html) | Ensure S3 buckets do not allow authenticated users READ access |
92 | | S3 | [S3-007](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-authenticated-users-read-acp-access.html) | Ensure S3 buckets do not allow authenticated users READ_ACP access |
93 | | S3 | [S3-008](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-authenticated-users-write-access.html) | Ensure S3 buckets do not allow authenticated users WRITE access |
94 | | S3 | [S3-009](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-authenticated-users-write-acp-access.html) | Ensure S3 buckets do not allow authenticated users WRITE_ACP access |
95 | | S3 | [S3-010](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-authenticated-users-full-control-access.html) | Ensure S3 buckets do not allow authenticated users FULL_CONTROL access |
96 | | S3 | [S3-012](https://www.cloudconformity.com/knowledge-base/aws/S3/s3-bucket-versioning-enabled.html) | Enable versioning for AWS S3 buckets |
97 | | RDS | [RDS-008](https://www.cloudconformity.com/knowledge-base/aws/RDS/rds-publicly-accessible.html) | Ensure RDS instances are not public facing to minimise security risks |
98 | | CloudTrail | [CT-001](https://www.cloudconformity.com/knowledge-base/aws/CloudTrail/cloudtrail-enabled.html) | Ensure CloudTrail API logging is activated for all Regions |
99 | | Redshift | [RS-001](https://www.cloudconformity.com/knowledge-base/aws/Redshift/redshift-cluster-publicly-accessible.html) | Ensure Redshift clusters are not publicly accessible to minimise security risks |
100 | | IAM | [IAM-001](https://www.cloudconformity.com/knowledge-base/aws/IAM/access-keys-rotated-30-days.html) | Ensure that all your IAM user access keys are rotated every month |
101 | | IAM | [IAM-029](https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/IAM/unused-iam-user.html) | Identify and remove any unused AWS IAM users |
102 | | EC2 | [EC2-002](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-ssh-access.html) | Ensure that there is no unrestricted access through TCP port 22 from the selected EC2 security group |
103 | | EC2 | [EC2-003](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-rdp-access.html) | Ensure that there is no unrestricted access through TCP port 3389 (which is used for MS Remote Desktop Protocol) from the selected EC2 security group |
104 | | EC2 | [EC2-004](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-oracle-access.html) | Ensure that there is no unrestricted access through TCP port 1521 (which is used by Oracle Database Server) from the selected EC2 security group |
105 | | EC2 | [EC2-005](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-mysql-access.html) | Ensure that there is no unrestricted access through TCP port 3306 (which is used by MYSQL Database Server) from the selected EC2 security group |
106 | | EC2 | [EC2-006](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-postgresql-access.html) | Ensure that there is no unrestricted access through TCP port 5432 (which is used by PostgreSQL Database Server) from the selected EC2 security group |
107 | | EC2 | [EC2-008](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-mssql-access.html) | Ensure that there is no unrestricted access through TCP port 1433 (which is used by Microsoft MYSQL Database Server) from the selected EC2 security group |
108 | | EC2 | [EC2-038](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-telnet-access.html) | Ensure that there is no unrestricted access through TCP port 23 (which is used by Telnet) from the selected EC2 security group |
109 | | EC2 | [EC2-039](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-smtp-access.html) | Ensure that there is no unrestricted access through TCP port 25 (which is used by SMTP) from the selected EC2 security group |
110 | | EC2 | [EC2-040](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-rcp-access.html) | Ensure that there is no unrestricted access through TCP port 135 (which is used client/server communication by Microsoft Message Queuing, as well as other Microsoft Windows/Windows Server software.) from the selected EC2 security group |
111 | | EC2 | [EC2-043](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-cifs-access.html) | Ensure that there is no unrestricted access through TCP port 445 (which is used by CIFS for file/printer sharing and other network communications) from the selected EC2 security group |
112 | | EC2 | [EC2-045](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-mongodb-access.html) | Ensure that there is no unrestricted access through TCP port 27017 (which is used by MongoDB database) from the selected EC2 security group |
113 | | Kinesis | [Kinesis-001](https://www.cloudconformity.com/knowledge-base/aws/Kinesis/server-side-encryption.html) | Ensure that your AWS Kinesis streams are encrypted using Server-Side Encryption (SSE) in order to meet strict regulatory requirements and improve the security of your data at rest. |
114 | | SQS | [SQS-004](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-mongodb-access.html) | Ensure that your Amazon Simple Queue Service (SQS) queues are protecting the contents of their messages using Server-Side Encryption (SSE). |
115 | | EC2 | [EC2-045](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-mongodb-access.html) | Ensure that there is no unrestricted access through TCP port 27017 (which is used by MongoDB database) from the selected EC2 security group |
116 | | IAM | [IAM-038](https://www.cloudconformity.com/knowledge-base/aws/IAM/access-keys-rotated-90-days.html) | Ensure that all your IAM user access keys are rotated every month in order to decrease the likelihood of accidental exposures and protect your AWS resources against unauthorized access |
117 | | VPC | [VPC-001](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-cifs-access.html) | Ensure that Flow Logs feature is Enabled for your account to capture network traffic data to and from your Virtual Private Cloud (VPC) |
118 | | EC2 | [EC2-019](https://www.cloudconformity.com/knowledge-base/aws/EC2/publicly-shared-ami.html) | Ensure that your AWS AMIs are not publicly shared with the other AWS accounts in order to avoid exposing sensitive data. |
119 | | CloudTrail | [CT-003](https://www.cloudconformity.com/knowledge-base/aws/CloudTrail/cloudtrail-bucket-publicly-accessible.html) | Ensure there are not any AWS CloudTrail logging buckets are publicly accessible, in order to determine if your AWS account could be at risk. |
120 | | RDS | [RDS-006](https://www.cloudconformity.com/knowledge-base/aws/RDS/rds-auto-minor-version-upgrade.html) | Ensure that your RDS database instances have the Auto Minor Version Upgrade flag enabled in order to receive automatically minor engine upgrades during the specified maintenance window |
121 | | EBS | [EBS-009](https://www.cloudconformity.com/knowledge-base/aws/EBS/public-snapshots.html) | Ensure that your AWS Elastic Block Store (EBS) volume snapshots are not public (i.e. publicly shared with other AWS accounts) in order to avoid exposing personal and sensitive data. |
122 | | RDS | [RDS-023](https://www.cloudconformity.com/knowledge-base/aws/RDS/public-snapshots.html) | Ensure that your AWS Relational Database Service (RDS) database snapshots are not publicly accessible (i.e. shared with all AWS accounts and users) in order to avoid exposing your private data. |
123 | | KMS | [KMS-004](https://www.cloudconformity.com/knowledge-base/aws/KMS/kms-customer-master-key-pending-deletion.html) | Identify any disabled AWS KMS Customer Master Keys (CMK) that have been accidentally or intentionally scheduled for deletion in order to prevent losing any data encrypted with these keys. |
124 | | RedShift | [RS-023](https://www.cloudconformity.com/knowledge-base/aws/Redshift/user-activity-log.html) |Ensure that user activity logging is enabled for your AWS Redshift clusters in order to log each query before it is performed on the clusters database. |
125 | | GuardDuty | [GD-001](https://www.cloudconformity.com/knowledge-base/aws/GuardDuty/guardduty-enabled.html) | Ensure that Amazon GuardDuty service is currently enabled in order to protect your AWS environment and infrastructure |
126 | | Organizations | [Organizations-002](https://www.cloudconformity.com/knowledge-base/aws/Organizations/all-features.html) | Ensure that All Features is enabled within your Amazon Organizations to achieve full control over the use of AWS services and actions across multiple AWS accounts using Service Control Policies (SCPs). |
127 | | Lambda | [Lambda-003](https://www.cloudconformity.com/knowledge-base/aws/Lambda/tracing.html) | Ensure that tracing is enabled for your AWS Lambda functions in order to gain visibility into the functions execution and performance. |
128 | | S3 | [S3-016](https://www.cloudconformity.com/knowledge-base/aws/S3/server-side-encryption.html) | Ensure that your AWS S3 buckets are protecting their sensitive data at rest by enforcing Server-Side Encryption |
129 | | S3 | [S3-014](https://www.cloudconformity.com/knowledge-base/aws/EC2/unrestricted-mongodb-access.html) | Ensure that your AWS S3 buckets are not publicly accessible via bucket policies in order to protect against unauthorized access. |
130 | | TrustedAdvisor | [TrustedAdvisor-003](https://www.cloudconformity.com/knowledge-base/aws/TrustedAdvisor/exposed-access-keys.html) | Ensure that there are not any exposed Amazon IAM access keys in order to protect your AWS resources against unapproved access |
131 | | KMS | [KMS-002](https://www.cloudconformity.com/knowledge-base/aws/KMS/key-rotation-enabled.html) | Ensure that the KMS Key Rotation is Enabled which allows you to set an yearly rotation schedule for your CMK |
132 | | RedShift | [RS-019](https://www.cloudconformity.com/knowledge-base/aws/Redshift/automated-snapshot-retention-period.html) | Ensure that the automated snapshot retention period set for your AWS Redshift clusters is a positive number, meaning that automated backups are enabled for the clusters |
133 |
134 |
135 |
136 | For more information about `Rule Id`, please refer to [Cloud Conformity Services Endpoint](https://us-west-2.cloudconformity.com/v1/services)
137 |
138 | Note: if you want to exclude auto-remediate resources for certain rules, you can use Conformity Profile, for example, `exclude-rules.json`. The example profile provides a list of rules to exlude low risk rules or rules that require extra resources or configurations.
139 |
140 | ## How to contribute
141 |
142 | You are welcome to contribute to "Cloud Conformity Auto Remediation"
143 |
144 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) the project
145 | 2. Make a well commented and clean commit to the repository
146 | 3. Create a [pull request](https://help.github.com/articles/about-pull-requests/)
147 |
148 | ## Styleguide
149 |
150 | The styleguide used for this project is
151 | [Standard](https://standardjs.com/) forced by [eslint](https://github.com/eslint/eslint).
152 | To run eslint, you only need to run `npm run lint [filename]` or to
153 | run for the whole project, run `npm run lint '**/*.js'`.
154 | Refer to package.json for more info.
155 |
156 | The main rules are as follows:
157 | * 2 spaces – for indentation
158 | * Single quotes for strings – except to avoid escaping
159 | * No unused variables – this one catches tons of bugs!
160 | * No semicolons – It's fine. Really!
161 | * Never start a line with (, [, or `
162 | * Space after keywords if (condition) { ... }
163 | * Space after function name function name (arg) { ... }
164 | * Always use === instead of == – but obj == null is allowed to check null || undefined.
165 | * Always handle the node.js err function parameter
166 | * Always prefix browser globals with window – except document and navigator are okay
167 |
168 | ## About
169 |
170 | Protect, Detect, Correct. The most complete solution to avoid critical threats and vulnerabilities in your AWS environments. Awarded both AWS Cloud Management Tools Competency and Security Partner Competency, Cloud Conformity’s security and optimization platform delivers continuous assurance that your infrastructure is risk-free and compliant as your cloud presence grows.
171 |
--------------------------------------------------------------------------------