diff --git a/.vscode/settings.json b/.vscode/settings.json index 5a2d9bf..1087b08 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,4 +4,10 @@ }, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, -} \ No newline at end of file + "search.exclude": { + "**/node_modules/**": true, + "**/cdk.out/**": true, + "**/.next/**": true, + "**/pruned/**": true + } +} diff --git a/examples/cdk-pipelines/app.ts b/examples/cdk-pipelines/app.ts new file mode 100644 index 0000000..53ed443 --- /dev/null +++ b/examples/cdk-pipelines/app.ts @@ -0,0 +1,62 @@ +import { Aspects, Stack, StackProps, Stage, StageProps } from "aws-cdk-lib"; +import { Construct } from "constructs"; +import { App } from "aws-cdk-lib"; +import { AwsSolutionsChecks } from "cdk-nag"; +import { + CodePipeline, + CodePipelineSource, + ShellStep, +} from "aws-cdk-lib/pipelines"; +import { GlobalFunctionsStack } from "../global-functions/app"; +import { + suppressCommonNags, + suppressGlobalNags, + suppressFunctionsNags, +} from "../shared/suppress-nags"; + +const app = new App(); + +const env = { + account: process.env["CDK_DEFAULT_ACCOUNT"], + region: process.env["CDK_DEFAULT_REGION"], +}; + +class MyStage extends Stage { + constructor(scope: Construct, id: string, props: StageProps) { + super(scope, id, props); + const stack = new GlobalFunctionsStack(this, "glbl-fns", { + env, + }); + suppressCommonNags(stack); + suppressGlobalNags(stack); + suppressFunctionsNags(stack); + } +} + +class CdkPipelinesStack extends Stack { + constructor(scope: Construct, id: string, props: StackProps) { + super(scope, id, props); + const pipeline = new CodePipeline(this, "Pipeline", { + synth: new ShellStep("ShellStep", { + installCommands: ["cd examples", "pnpm i"], + commands: ["cd examples/cdk-pipelines", "pnpm cdk synth"], + // NOTE: you must manually create this in AWS Console. + input: CodePipelineSource.connection("cdk-labs/cdk-nextjs", "main", { + connectionArn: this.formatArn({ + service: "codestar-connections", + resource: "connection", + resourceName: process.env["CODESTAR_CONNECTION_ID"], + }), + }), + primaryOutputDirectory: "examples/cdk-pipelines/cdk.out", + }), + }); + pipeline.addStage(new MyStage(this, "prod", { env })); + } +} + +export const stack = new CdkPipelinesStack(app, "cdk-pplns", { + env, +}); + +Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })); diff --git a/examples/cdk-pipelines/cdk.json b/examples/cdk-pipelines/cdk.json new file mode 100644 index 0000000..d2eb7aa --- /dev/null +++ b/examples/cdk-pipelines/cdk.json @@ -0,0 +1,58 @@ +{ + "app": "pnpm tsx app.ts", + "watch": { + "include": ["src/**"], + "exclude": ["node_modules", "cdk.out"] + }, + "context": { + "see": "https://github.com/aws/aws-cdk/blob/main/packages/%40aws-cdk/cx-api/FEATURE_FLAGS.md", + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws", "aws-cn"], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-stepfunctions-tasks:ecsReduceRunTaskPermissions": true + } +} diff --git a/examples/cdk-pipelines/package.json b/examples/cdk-pipelines/package.json new file mode 100644 index 0000000..6e82e84 --- /dev/null +++ b/examples/cdk-pipelines/package.json @@ -0,0 +1,21 @@ +{ + "name": "cdk-pipelines", + "type": "module", + "private": true, + "scripts": { + "deploy:local": "dotenv -- cdk deploy --require-approval never", + "synth:local": "dotenv -- cdk synth", + "destroy:local": "dotenv -- cdk destroy --force", + "deploy": "cdk deploy --require-approval never" + }, + "dependencies": { + "aws-cdk": "^2.1005.0", + "aws-cdk-lib": "2.186.0", + "cdk-nextjs": "link:../..", + "constructs": "10.0.5", + "tsx": "^4.19.3" + }, + "devDependencies": { + "@types/node": "^22.13.13" + } +} diff --git a/examples/cdk-pipelines/tsconfig.json b/examples/cdk-pipelines/tsconfig.json new file mode 100644 index 0000000..ffe273f --- /dev/null +++ b/examples/cdk-pipelines/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": ["../tsconfig.json"] +} \ No newline at end of file diff --git a/examples/global-functions/app.ts b/examples/global-functions/app.ts index 306b302..b5af700 100644 --- a/examples/global-functions/app.ts +++ b/examples/global-functions/app.ts @@ -7,14 +7,14 @@ import { AwsSolutionsChecks, NagSuppressions } from "cdk-nag"; import { suppressCommonNags, suppressGlobalNags, - suppressLambdaNags, + suppressFunctionsNags, } from "../shared/suppress-nags"; import { FlowLogDestination } from "aws-cdk-lib/aws-ec2"; import { Bucket, ObjectOwnership } from "aws-cdk-lib/aws-s3"; const app = new App(); -class GlobalFunctionsStack extends Stack { +export class GlobalFunctionsStack extends Stack { constructor(scope: Construct, id: string, props: StackProps) { super(scope, id, props); const logsBucket = this.#getLogsBucket(); @@ -90,28 +90,11 @@ export const stack = new GlobalFunctionsStack(app, "glbl-fns", { region: process.env["CDK_DEFAULT_REGION"], }, }); -suppressCommonNags(stack); -suppressGlobalNags(stack); -suppressLambdaNags(stack); -NagSuppressions.addResourceSuppressionsByPath( - stack, - `/${stack.stackName}/Nextjs/NextjsRevalidation/Queue/Resource`, - [ - { - id: "AwsSolutions-SQS3", - reason: "DLQ not required for example app", - }, - ], -); -NagSuppressions.addResourceSuppressionsByPath( - stack, - `/${stack.stackName}/Nextjs/NextjsRevalidation/Fn/ServiceRole/Resource`, - [ - { - id: "AwsSolutions-IAM4", - reason: "AWSLambdaBasicExecutionRole is not overly permissive", - }, - ], -); + +export function suppressGlobalFunctionsStackNags() { + suppressCommonNags(stack); + suppressGlobalNags(stack); + suppressFunctionsNags(stack); +} Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })); diff --git a/examples/package.json b/examples/package.json index 2dab917..118c685 100644 --- a/examples/package.json +++ b/examples/package.json @@ -5,7 +5,7 @@ "scripts": { "prune": "turbo prune app-playground --docker --out-dir pruned/app-playground" }, - "packageManager": "pnpm@9.1.4+sha256.30a1801ac4e723779efed13a21f4c39f9eb6c9fbb4ced101bce06b422593d7c9", + "packageManager": "pnpm@10.11.0", "devDependencies": { "@tsconfig/node20": "^20.1.5", "@tsconfig/strictest": "^2.0.5", diff --git a/examples/shared/suppress-nags.ts b/examples/shared/suppress-nags.ts index 616523d..17f483a 100644 --- a/examples/shared/suppress-nags.ts +++ b/examples/shared/suppress-nags.ts @@ -26,7 +26,7 @@ export function suppressCommonNags(stack: Stack) { ); } -export function suppressLambdaNags(stack: Stack) { +export function suppressFunctionsNags(stack: Stack) { NagSuppressions.addResourceSuppressionsByPath( stack, `/${stack.stackName}/Nextjs/NextjsFunctions/Functions/ServiceRole/Resource`, @@ -48,6 +48,26 @@ export function suppressLambdaNags(stack: Stack) { }, ], ); + NagSuppressions.addResourceSuppressionsByPath( + stack, + `/${stack.stackName}/Nextjs/NextjsRevalidation/Queue/Resource`, + [ + { + id: "AwsSolutions-SQS3", + reason: "DLQ not required for example app", + }, + ], + ); + NagSuppressions.addResourceSuppressionsByPath( + stack, + `/${stack.stackName}/Nextjs/NextjsRevalidation/Fn/ServiceRole/Resource`, + [ + { + id: "AwsSolutions-IAM4", + reason: "AWSLambdaBasicExecutionRole is not overly permissive", + }, + ], + ); } export function suppressContainerNags(stack: Stack) {