cdklabs / cdk-nag

Check CDK applications for best practices using a combination of available rule packs
Apache License 2.0
745 stars 55 forks source link

bug: CDK Stages not linted by Nag rules #1726

Open Clebiez opened 1 week ago

Clebiez commented 1 week ago

What is the problem?

By using a CDK Stage and using the command cdk synth '**', annotations are correctly generated by cdk but cdk-nag doesn't lint anything from this stack. Seems to be related to #637

Reproduction Steps

// ./lib/cdktest-stack.ts
import * as cdk from "aws-cdk-lib";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";

export class CdktestStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const bucket = new Bucket(this, "bucket-test");
  }
}

// ./lib/cdktest-stage.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { CdktestStack } from "./cdktest-stack";

export class CdkTestStage extends cdk.Stage {
  constructor(scope: Construct, id: string, props: cdk.StageProps) {
    super(scope, id, props);
    new CdktestStack(this, "cdk-test-stack");
  }
}

// ./lib/pipeline.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as pipeline from "aws-cdk-lib/pipelines";
import * as codecommit from "aws-cdk-lib/aws-codecommit";
import { CdkTestStage } from "./cdktest-stage";
import { Bucket, BucketEncryption } from "aws-cdk-lib/aws-s3";

export class PipelineStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: cdk.StackProps) {
    super(scope, id, props);
    const arnCodeCommit = `arn:aws:codecommit:us-east-1:00000000:testRepo`;
    const artifactBucket = new Bucket(this, "artifact", {
      encryption: BucketEncryption.KMS,
      bucketName: "djslkfsdlkf-artifact",
    });
    // On importe le code-commit dgeq-devops-pes-planb-gep, on doit passer par l'Arn car il est dans un autre compte
    const testRepo = codecommit.Repository.fromRepositoryArn(
      this,
      "testRepo",
      arnCodeCommit
    );

    const sourceCodeCommit = pipeline.CodePipelineSource.codeCommit(
      testRepo,
      "main"
    );

    // Création de l'étape pour CDK Synth
    const synthBuildStep = new pipeline.CodeBuildStep("Synth", {
      input: sourceCodeCommit,
      installCommands: ["npm install -g aws-cdk"],
      commands: [],
      primaryOutputDirectory: "pipeline/cdk.out",
    });

    //Pipeline de base
    const pipelineInfra = new pipeline.CodePipeline(this, "pipelineInfra", {
      synth: synthBuildStep,
      artifactBucket,
    });

    pipelineInfra.addStage(new CdkTestStage(this, "cdk-test-stage", props));

    pipelineInfra.buildPipeline();
  }
}

// bin/main.ts
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { AwsSolutionsChecks } from "cdk-nag";
import { PipelineStack } from "../lib/pipeline";
const app = new cdk.App();
new PipelineStack(app, "PipelineStack", {
  env: {
    region: "us-east-1",
    account: "111111111",
  },
});
cdk.Aspects.of(app).add(new AwsSolutionsChecks());

Rules regarding to the pipeline will be correctly reported here but the test-bucket inside the CdkTestStack seems to be ignored.

Directly instanciating the CdkTestStack inside the bin/main.ts will correctly report S3 rules.

What did you expect to happen?

Trying to lint both of the pipeline and stage rules.

What actually happened?

Only pipeline rules are displayed.

cdk-nag version

2.28.144

Language

Typescript

Other information

No response

dontirun commented 4 days ago

This seems to be an issue with cdk Aspects not getting applied on all child scopes and should be raised on the CDK repo. I can reproduce the issue with a normal Aspect

Given the following bin/main.ts

import * as cdk from 'aws-cdk-lib';
import * as codecommit from 'aws-cdk-lib/aws-codecommit';
import { Bucket, BucketEncryption } from 'aws-cdk-lib/aws-s3';
import * as pipeline from 'aws-cdk-lib/pipelines';
import { Construct } from 'constructs';

export class CdktestStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    new Bucket(this, 'bucket-test');
  }
}

export class CdkTestStage extends cdk.Stage {
  constructor(scope: Construct, id: string, props: cdk.StageProps) {
    super(scope, id, props);
    new CdktestStack(this, 'cdk-test-stack');
  }
}

export class PipelineStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: cdk.StackProps) {
    super(scope, id, props);
    const arnCodeCommit = 'arn:aws:codecommit:us-east-1:00000000:testRepo';
    const artifactBucket = new Bucket(this, 'artifact', {
      encryption: BucketEncryption.KMS,
      bucketName: 'djslkfsdlkf-artifact',
    });
    // On importe le code-commit dgeq-devops-pes-planb-gep, on doit passer par l'Arn car il est dans un autre compte
    const testRepo = codecommit.Repository.fromRepositoryArn(
      this,
      'testRepo',
      arnCodeCommit,
    );

    const sourceCodeCommit = pipeline.CodePipelineSource.codeCommit(
      testRepo,
      'main',
    );

    // Création de l'étape pour CDK Synth
    const synthBuildStep = new pipeline.CodeBuildStep('Synth', {
      input: sourceCodeCommit,
      installCommands: ['npm install -g aws-cdk'],
      commands: [],
      primaryOutputDirectory: 'pipeline/cdk.out',
    });

    //Pipeline de base
    const pipelineInfra = new pipeline.CodePipeline(this, 'pipelineInfra', {
      synth: synthBuildStep,
      artifactBucket,
    });

    pipelineInfra.addStage(new CdkTestStage(this, 'cdk-test-stage', props));

    pipelineInfra.buildPipeline();
  }
}

export class TestAspect implements cdk.IAspect {
  public visit(node: Construct): void {
    if (node instanceof cdk.CfnResource) {
      console.log(`${node.node.path} : ${node.cfnResourceType}`);
    }
  }
}

const app = new cdk.App();
new PipelineStack(app, 'PipelineStack', {
  env: {
    region: 'us-east-1',
    account: '111111111',
  },
});

cdk.Aspects.of(app).add(new TestAspect);

I run cdk synth '**' and get the following output

PipelineStack/artifact/Key/Resource : AWS::KMS::Key
PipelineStack/artifact/Resource : AWS::S3::Bucket
PipelineStack/artifact/Policy/Resource : AWS::S3::BucketPolicy
PipelineStack/testRepo/PipelineStackpipelineInfraPipeline62852AFC-main-EventRule/Resource : AWS::Events::Rule
PipelineStack/pipelineInfra/Pipeline/Role/Resource : AWS::IAM::Role
PipelineStack/pipelineInfra/Pipeline/Role/DefaultPolicy/Resource : AWS::IAM::Policy
PipelineStack/pipelineInfra/Pipeline/Resource : AWS::CodePipeline::Pipeline
PipelineStack/pipelineInfra/Pipeline/EventsRole/Resource : AWS::IAM::Role
PipelineStack/pipelineInfra/Pipeline/EventsRole/DefaultPolicy/Resource : AWS::IAM::Policy
PipelineStack/pipelineInfra/Pipeline/Build/Synth/CdkBuildProject/Role/Resource : AWS::IAM::Role
PipelineStack/pipelineInfra/Pipeline/Build/Synth/CdkBuildProject/Role/DefaultPolicy/Resource : AWS::IAM::Policy
PipelineStack/pipelineInfra/Pipeline/Build/Synth/CdkBuildProject/Resource : AWS::CodeBuild::Project
PipelineStack/pipelineInfra/CodeBuildActionRole/Resource : AWS::IAM::Role
PipelineStack/pipelineInfra/CodeBuildActionRole/DefaultPolicy/Resource : AWS::IAM::Policy
PipelineStack/pipelineInfra/UpdatePipeline/SelfMutation/Role/Resource : AWS::IAM::Role
PipelineStack/pipelineInfra/UpdatePipeline/SelfMutation/Role/DefaultPolicy/Resource : AWS::IAM::Policy
PipelineStack/pipelineInfra/UpdatePipeline/SelfMutation/Resource : AWS::CodeBuild::Project
cross-account-support-stack-00000000/PipelineStackpipelineInfraPipeline62852AFC-Source-testRepo-ActionRole/Resource : AWS::IAM::Role
cross-account-support-stack-00000000/PipelineStackpipelineInfraPipeline62852AFC-Source-testRepo-ActionRole/DefaultPolicy/Resource : AWS::IAM::Policy