Repository: mmuller88/cdk-prowler (language: python)

Issue:
No way to provide allow-lists.
The Prowler docs allow for specific resources to be ignored in the compliance checks:

https://github.com/toniblyx/prowler#whitelist-or-allowlist-or-remove-a-fail-from-resources

There is currently no way to provide this file and option.

Proposal:

A new property added to the construct, called 'allowlist', that is a Code object. If provided, the buildspec file will be updated to read the file from s3 and provide it as a `-w` option during runtime.



Discussion / hints:

Fix patch:
diff --git a/.projen/deps.json b/.projen/deps.json
index 8f3802b..0e5374c 100644
--- a/.projen/deps.json
+++ b/.projen/deps.json
@@ -40,6 +40,11 @@
       "version": "1.120.0",
       "type": "build"
     },
+    {
+      "name": "@aws-cdk/aws-s3-assets",
+      "version": "1.120.0",
+      "type": "build"
+    },
     {
       "name": "@aws-cdk/aws-s3",
       "version": "1.120.0",
@@ -178,6 +183,11 @@
       "version": "1.120.0",
       "type": "peer"
     },
+    {
+      "name": "@aws-cdk/aws-s3-assets",
+      "version": "1.120.0",
+      "type": "peer"
+    },
     {
       "name": "@aws-cdk/aws-s3",
       "version": "1.120.0",
diff --git a/.projenrc.js b/.projenrc.js
index 6acade9..909633c 100644
--- a/.projenrc.js
+++ b/.projenrc.js
@@ -17,6 +17,7 @@ const cdkDependencies = [
   '@aws-cdk/aws-lambda',
   '@aws-cdk/aws-logs',
   '@aws-cdk/aws-s3',
+  '@aws-cdk/aws-s3-assets',
   '@aws-cdk/aws-iam',
   '@aws-cdk/aws-events',
   '@aws-cdk/aws-events-targets',
@@ -129,6 +130,8 @@ project.setScript('deploy', './node_modules/.bin/cdk deploy');
 project.setScript('destroy', './node_modules/.bin/cdk destroy');
 project.setScript('synth', './node_modules/.bin/cdk synth');
 
+project.setScript('integ:allowlist', 'cdk synth --app \'ts-node -P tsconfig.jest.json test/integ.allowlist.ts\'');
+
 const common_exclude = ['cdk.out'];
 project.npmignore.exclude(...common_exclude);
 project.gitignore.exclude(...common_exclude);
diff --git a/API.md b/API.md
index 3b371b1..dfc8161 100644
--- a/API.md
+++ b/API.md
@@ -101,6 +101,18 @@ An optional parameter to add to the S3 bucket copy command.
 
 ---
 
+##### `allowlist`<sup>Optional</sup> <a name="cdk-prowler.ProwlerAuditProps.property.allowlist"></a>
+
+- *Type:* [`@aws-cdk/aws-s3-assets.Asset`](#@aws-cdk/aws-s3-assets.Asset)
+- *Default:* undefined
+
+An Prowler-specific Allowlist file.
+
+If a value is provided then this is passed to Prowler on runs using the '-w' flag.
+If no value is provided, the -w parameter is not used.
+
+---
+
 ##### `enableScheduler`<sup>Optional</sup> <a name="cdk-prowler.ProwlerAuditProps.property.enableScheduler"></a>
 
 - *Type:* `boolean`
diff --git a/package.json b/package.json
index 3a19a0b..aa5b703 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,8 @@
     "projen": "npx projen",
     "deploy": "./node_modules/.bin/cdk deploy",
     "destroy": "./node_modules/.bin/cdk destroy",
-    "synth": "./node_modules/.bin/cdk synth"
+    "synth": "./node_modules/.bin/cdk synth",
+    "integ:allowlist": "cdk synth --app 'ts-node -P tsconfig.jest.json test/integ.allowlist.ts'"
   },
   "author": {
     "name": "Martin Mueller",
@@ -46,6 +47,7 @@
     "@aws-cdk/aws-lambda": "1.120.0",
     "@aws-cdk/aws-logs": "1.120.0",
     "@aws-cdk/aws-s3": "1.120.0",
+    "@aws-cdk/aws-s3-assets": "1.120.0",
     "@aws-cdk/core": "1.120.0",
     "@aws-cdk/custom-resources": "1.120.0",
     "@types/jest": "^26.0.24",
@@ -79,6 +81,7 @@
     "@aws-cdk/aws-lambda": "1.120.0",
     "@aws-cdk/aws-logs": "1.120.0",
     "@aws-cdk/aws-s3": "1.120.0",
+    "@aws-cdk/aws-s3-assets": "1.120.0",
     "@aws-cdk/core": "1.120.0",
     "@aws-cdk/custom-resources": "1.120.0",
     "cdk-iam-floyd": "^0.229.0",
diff --git a/src/index.ts b/src/index.ts
index 4c1370c..09c776c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,6 +11,7 @@ import * as logs from '@aws-cdk/aws-logs';
 // eslint-disable-next-line import/no-extraneous-dependencies
 import * as s3 from '@aws-cdk/aws-s3';
 import { IBucket } from '@aws-cdk/aws-s3';
+import { Asset } from '@aws-cdk/aws-s3-assets';
 import { Construct, CustomResource, Duration, RemovalPolicy, Stack } from '@aws-cdk/core';
 import * as cr from '@aws-cdk/custom-resources';
 import * as statement from 'cdk-iam-floyd';
@@ -68,6 +69,15 @@ export interface ProwlerAuditProps {
    * @example --acl bucket-owner-full-control
    */
   readonly additionalS3CopyArgs?: string;
+
+  /**
+   * An Prowler-specific Allowlist file. If a value is provided then this is passed to Prowler on runs using the '-w' flag.
+   * If no value is provided, the -w parameter is not used.
+   *
+   * @example new Asset(this, 'AllowList', { path: path.join(__dirname, 'allowlist.txt') })
+   * @default undefined
+   */
+  readonly allowlist?: Asset;
 }
 
 /**
@@ -101,6 +111,20 @@ export class ProwlerAudit extends Construct {
 
     const reportGroup = new codebuild.ReportGroup(this, 'reportGroup', { /**reportGroupName: 'testReportGroup', */removalPolicy: RemovalPolicy.DESTROY });
 
+    const preBuildCommands: string[] = [];
+    if (!!props?.allowlist) {
+      const prowlerFilename = 'prowler-exemptions.txt';
+
+      preBuildCommands.push(`aws s3 cp ${props.allowlist.s3ObjectUrl} prowler/${prowlerFilename}`);
+      if (props.allowlist.isZipArchive) {
+        // this isn't as simple as just unzipping.. we need to know the filename(s?) that are in there
+        preBuildCommands.push(`unzip ${props.allowlist.s3ObjectKey} -d prowler`);
+      }
+      preBuildCommands.push('ls prowler'); // remove this after testing
+
+      this.prowlerOptions = this.prowlerOptions + ` -w ${prowlerFilename}`;
+    }
+
     const prowlerBuild = this.codebuildProject = new codebuild.Project(this, 'prowlerBuild', {
       description: 'Run Prowler assessment',
       timeout: Duration.hours(5),
@@ -130,6 +154,9 @@ export class ProwlerAudit extends Construct {
               `git clone -b ${this.prowlerVersion} https://github.com/toniblyx/prowler`,
             ],
           },
+          pre_build: {
+            commands: preBuildCommands,
+          },
           build: {
             commands: [
               `echo "Running Prowler as ./prowler ${this.prowlerOptions} && echo OK || echo FAILED"`,
@@ -154,6 +181,11 @@ export class ProwlerAudit extends Construct {
         },
       }),
     });
+
+    if (!!props?.allowlist) {
+      props.allowlist.bucket.grantRead(prowlerBuild);
+    }
+
     prowlerBuild.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('SecurityAudit'));
     prowlerBuild.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('job-function/ViewOnlyAccess'));
     // prowlerBuild.addToRolePolicy(new statement.Dax().allow().to());


Test patch:
diff --git a/test/allowlist.txt b/test/allowlist.txt
new file mode 100644
index 0000000..e413548
--- /dev/null
+++ b/test/allowlist.txt
@@ -0,0 +1,1 @@
+check114:MFA
\ No newline at end of file
diff --git a/test/integ.allowlist.ts b/test/integ.allowlist.ts
new file mode 100644
index 0000000..bd8bb9a
--- /dev/null
+++ b/test/integ.allowlist.ts
@@ -0,0 +1,16 @@
+// import { App, Stack } from 'aws-cdk-lib';
+import * as path from 'path';
+import { Asset } from '@aws-cdk/aws-s3-assets';
+import { App, Stack } from '@aws-cdk/core';
+
+import { ProwlerAudit } from '../src/index';
+
+const app = new App();
+
+const stack = new Stack(app, 'ProwlerAudit-allowlist-test');
+
+new ProwlerAudit(stack, 'ProwlerAudit', {
+  allowlist: new Asset(stack, 'AllowList', { path: path.join(__dirname, 'allowlist.txt') }),
+});
+
+app.synth();
\ No newline at end of file
diff --git a/test/prowler.test.ts b/test/prowler.test.ts
index e768cae..d904fff 100644
--- a/test/prowler.test.ts
+++ b/test/prowler.test.ts
@@ -1,5 +1,7 @@
+import * as path from 'path';
 import { Template } from '@aws-cdk/assertions';
 import { Bucket } from '@aws-cdk/aws-s3';
+import { Asset } from '@aws-cdk/aws-s3-assets';
 import { App, Stack } from '@aws-cdk/core';
 import { ProwlerAudit, ProwlerAuditProps } from '../src';
 
@@ -62,7 +64,7 @@ describe('Prowler Construct', () => {
           'Fn::Join': [
             '',
             [
-              '{\n  "version": "0.2",\n  "phases": {\n    "install": {\n      "runtime-versions": {\n        "python": 3.8\n      },\n      "commands": [\n        "echo \\"Installing Prowler and dependencies...\\"",\n        "pip3 install detect-secrets",\n        "yum -y install jq",\n        "curl \\"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip\\" -o \\"awscliv2.zip\\"",\n        "unzip awscliv2.zip",\n        "./aws/install",\n        "git clone -b 2.5.0 https://github.com/toniblyx/prowler"\n      ]\n    },\n    "build": {\n      "commands": [\n        "echo \\"Running Prowler as ./prowler -M text,junit-xml,html,csv,json && echo OK || echo FAILED\\"",\n        "cd prowler",\n        "./prowler -M text,junit-xml,html,csv,json && echo OK || echo FAILED"\n      ]\n    },\n    "post_build": {\n      "commands": [\n        "echo \\"Uploading reports to S3...\\" ",\n        "aws s3 cp --sse AES256 output/ s3://$BUCKET_REPORT/$BUCKET_PREFIX --recursive $ADDITIONAL_S3_ARGS",\n        "echo \\"Done!\\""\n      ]\n    }\n  },\n  "reports": {\n    "',
+              '{\n  "version": "0.2",\n  "phases": {\n    "install": {\n      "runtime-versions": {\n        "python": 3.8\n      },\n      "commands": [\n        "echo \\"Installing Prowler and dependencies...\\"",\n        "pip3 install detect-secrets",\n        "yum -y install jq",\n        "curl \\"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip\\" -o \\"awscliv2.zip\\"",\n        "unzip awscliv2.zip",\n        "./aws/install",\n        "git clone -b 2.5.0 https://github.com/toniblyx/prowler"\n      ]\n    },\n    "pre_build": {\n      "commands": []\n    },\n    "build": {\n      "commands": [\n        "echo \\"Running Prowler as ./prowler -M text,junit-xml,html,csv,json && echo OK || echo FAILED\\"",\n        "cd prowler",\n        "./prowler -M text,junit-xml,html,csv,json && echo OK || echo FAILED"\n      ]\n    },\n    "post_build": {\n      "commands": [\n        "echo \\"Uploading reports to S3...\\" ",\n        "aws s3 cp --sse AES256 output/ s3://$BUCKET_REPORT/$BUCKET_PREFIX --recursive $ADDITIONAL_S3_ARGS",\n        "echo \\"Done!\\""\n      ]\n    }\n  },\n  "reports": {\n    "',
               {
                 'Fn::Select': [
                   1,
@@ -121,7 +123,7 @@ describe('Prowler Construct', () => {
           'Fn::Join': [
             '',
             [
-              '{\n  "version": "0.2",\n  "phases": {\n    "install": {\n      "runtime-versions": {\n        "python": 3.8\n      },\n      "commands": [\n        "echo \\"Installing Prowler and dependencies...\\"",\n        "pip3 install detect-secrets",\n        "yum -y install jq",\n        "curl \\"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip\\" -o \\"awscliv2.zip\\"",\n        "unzip awscliv2.zip",\n        "./aws/install",\n        "git clone -b 2.5.0 https://github.com/toniblyx/prowler"\n      ]\n    },\n    "build": {\n      "commands": [\n        "echo \\"Running Prowler as ./prowler -M text,junit-xml,html,csv,json && echo OK || echo FAILED\\"",\n        "cd prowler",\n        "./prowler -M text,junit-xml,html,csv,json && echo OK || echo FAILED"\n      ]\n    },\n    "post_build": {\n      "commands": [\n        "echo \\"Uploading reports to S3...\\" ",\n        "aws s3 cp --sse AES256 output/ s3://$BUCKET_REPORT/$BUCKET_PREFIX --recursive $ADDITIONAL_S3_ARGS",\n        "echo \\"Done!\\""\n      ]\n    }\n  },\n  "reports": {\n    "',
+              '{\n  "version": "0.2",\n  "phases": {\n    "install": {\n      "runtime-versions": {\n        "python": 3.8\n      },\n      "commands": [\n        "echo \\"Installing Prowler and dependencies...\\"",\n        "pip3 install detect-secrets",\n        "yum -y install jq",\n        "curl \\"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip\\" -o \\"awscliv2.zip\\"",\n        "unzip awscliv2.zip",\n        "./aws/install",\n        "git clone -b 2.5.0 https://github.com/toniblyx/prowler"\n      ]\n    },\n    "pre_build": {\n      "commands": []\n    },\n    "build": {\n      "commands": [\n        "echo \\"Running Prowler as ./prowler -M text,junit-xml,html,csv,json && echo OK || echo FAILED\\"",\n        "cd prowler",\n        "./prowler -M text,junit-xml,html,csv,json && echo OK || echo FAILED"\n      ]\n    },\n    "post_build": {\n      "commands": [\n        "echo \\"Uploading reports to S3...\\" ",\n        "aws s3 cp --sse AES256 output/ s3://$BUCKET_REPORT/$BUCKET_PREFIX --recursive $ADDITIONAL_S3_ARGS",\n        "echo \\"Done!\\""\n      ]\n    }\n  },\n  "reports": {\n    "',
               {
                 'Fn::Select': [
                   1,
@@ -144,5 +146,101 @@ describe('Prowler Construct', () => {
     });
   });
 
+  test('is allowlist setup correctly', () => {
+
+    const app = new App();
+    const stack = new Stack(app, 'TestStack');
+    new ProwlerAudit(stack, 'TestAudit', {
+      allowlist: new Asset(stack, 'AllowList', {
+        path: path.join(__dirname, 'allowlist.txt'),
+      }),
+    });
+    const assert = Template.fromStack(stack);
+
+    assert.hasResourceProperties('AWS::CodeBuild::Project', {
+      Environment: {
+        EnvironmentVariables: [
+          {
+            Name: 'BUCKET_REPORT',
+            Type: 'PLAINTEXT',
+            Value: {
+              Ref: 'TestAuditReportBucket865A4698',
+            },
+          },
+          {
+            Name: 'BUCKET_PREFIX',
+            Type: 'PLAINTEXT',
+            Value: '',
+          },
+          {
+            Name: 'ADDITIONAL_S3_ARGS',
+            Type: 'PLAINTEXT',
+            Value: '',
+          },
+          {
+            Name: 'PROWLER_OPTIONS',
+            Type: 'PLAINTEXT',
+            Value: '-M text,junit-xml,html,csv,json -w prowler-exemptions.txt',
+          },
+        ],
+      },
+      Source: {
+        BuildSpec: {
+          'Fn::Join': [
+            '',
+            [
+              '{\n  "version": "0.2",\n  "phases": {\n    "install": {\n      "runtime-versions": {\n        "python": 3.8\n      },\n      "commands": [\n        "echo \\"Installing Prowler and dependencies...\\"",\n        "pip3 install detect-secrets",\n        "yum -y install jq",\n        "curl \\"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip\\" -o \\"awscliv2.zip\\"",\n        "unzip awscliv2.zip",\n        "./aws/install",\n        "git clone -b 2.5.0 https://github.com/toniblyx/prowler"\n      ]\n    },\n    "pre_build": {\n      "commands": [\n        "aws s3 cp s3://',
+              {
+                Ref: 'AssetParameters9aea54d9a7efe166d507f4871a7a1a483e26e0735cb063ce42afd545ce703601S3Bucket5E97AAE6',
+              },
+              '/',
+              {
+                'Fn::Select': [
+                  0,
+                  {
+                    'Fn::Split': [
+                      '||',
+                      {
+                        Ref: 'AssetParameters9aea54d9a7efe166d507f4871a7a1a483e26e0735cb063ce42afd545ce703601S3VersionKey1A9EEC90',
+                      },
+                    ],
+                  },
+                ],
+              },
+              {
+                'Fn::Select': [
+                  1,
+                  {
+                    'Fn::Split': [
+                      '||',
+                      {
+                        Ref: 'AssetParameters9aea54d9a7efe166d507f4871a7a1a483e26e0735cb063ce42afd545ce703601S3VersionKey1A9EEC90',
+                      },
+                    ],
+                  },
+                ],
+              },
+              ' prowler/prowler-exemptions.txt",\n        "ls prowler"\n      ]\n    },\n    "build": {\n      "commands": [\n        "echo \\"Running Prowler as ./prowler -M text,junit-xml,html,csv,json -w prowler-exemptions.txt && echo OK || echo FAILED\\"",\n        "cd prowler",\n        "./prowler -M text,junit-xml,html,csv,json -w prowler-exemptions.txt && echo OK || echo FAILED"\n      ]\n    },\n    "post_build": {\n      "commands": [\n        "echo \\"Uploading reports to S3...\\" ",\n        "aws s3 cp --sse AES256 output/ s3://$BUCKET_REPORT/$BUCKET_PREFIX --recursive $ADDITIONAL_S3_ARGS",\n        "echo \\"Done!\\""\n      ]\n    }\n  },\n  "reports": {\n    "',
+              {
+                'Fn::Select': [
+                  1,
+                  {
+                    'Fn::Split': [
+                      '/',
+                      {
+                        Ref: 'TestAuditreportGroup202AA555',
+                      },
+                    ],
+                  },
+                ],
+              },
+              '": {\n      "files": [\n        "**/*"\n      ],\n      "base-directory": "prowler/junit-reports",\n      "file-format": "JunitXml"\n    }\n  }\n}',
+            ],
+          ],
+        },
+        Type: 'NO_SOURCE',
+      },
+    });
+  });
 
 });
\ No newline at end of file