skip to Main Content

I understand that sometimes order makes or breaks CloudFormation templates, but this one shouldn’t be a problem, or at least it’s not obvious to me why I’m getting this error:

Error: ‘DataPipelinePrototypeSecretStack’ depends on
‘DataPipelinePrototypeDatabaseStack’ (DataPipelinePrototypeSecretStack
-> DataPipelinePrototypeDatabaseStack/PrototypeDb1/Resource.Ref). Adding this dependency (DataPipelinePrototypeDatabaseStack ->
DataPipelinePrototypeSecretStack/DataPipelinePrototypeMasterUserSecret/Resource.Ref)
would create a cyclic reference.

I’m simply creating a secret in the SecretStack, entirely independent of RDS:

export class SecretStack extends Stack {
    public readonly databaseMasterUserSecret: Secret;

    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);
        this.databaseMasterUserSecret = new Secret(this, "MasterUserSecret", {
            secretName: "pg-master-user-secret",
            generateSecretString: {
                secretStringTemplate: JSON.stringify({ username: "postgres" }),
                generateStringKey: "password",
                passwordLength: 16,
                excludePunctuation: true,
            },
        });
    }
}

Then, in the DatabaseStack, creating the RDS instance:

interface DatabaseStackProps extends StackProps {
    vpc: Vpc,
    securityGroup: SecurityGroup,
    secret: Secret
}

export class DatabaseStack extends Stack {
    constructor(scope: Construct, id: string, props: DatabaseStackProps) {
        super(scope, id, props);

        const engine = DatabaseInstanceEngine.postgres({ version: PostgresEngineVersion.VER_15_4 });
        const instanceType = InstanceType.of(InstanceClass.T3, InstanceSize.MICRO);
        const port = 5432;
        const dbName = "dbname";

        const dbInstance = new DatabaseInstance(this, "TestDb1", {
            vpc: props.vpc,
            vpcSubnets: { subnetType: SubnetType.PRIVATE_ISOLATED },
            instanceType,
            engine,
            port,
            securityGroups: [props.securityGroup],
            databaseName: dbName,
            credentials: Credentials.fromSecret(props.secret), //HERE...
            backupRetention: Duration.days(0),
            deleteAutomatedBackups: true,
            removalPolicy: RemovalPolicy.DESTROY,
        });
    }
}

Here’s how they’re called in the entry point:

const vpcStack = new VpcStack(app, "DataPipelinePrototypeVpc");
const secretStack = new SecretStack(app, "DataPipelinePrototypeSecretStack");
new DataBaseStack(app, "DataPipelinePrototypeDatabaseStack", {
    vpc: vpcStack.vpc,
    securityGroup: vpcStack.securityGroup,
    secret: secretStack.databaseMasterUserSecret
});
const lambdaStack = new LambdaStack(app, "DataPipelinePrototypeLambdaStack", {
    vpc: vpcStack.vpc,
    securityGroup: vpcStack.securityGroup,
    secret: secretStack.databaseMasterUserSecret
});

Flipping lambdaStack and DatabaseStack order didn’t work.

When the secret was nested inside of the DatabaseStack like so, it worked just fine:

export class DatabaseStack extends Stack {
    constructor(scope: Construct, id: string, props: DatabaseStackProps) {
        super(scope, id, props);

        const engine = DatabaseInstanceEngine.postgres({ version: PostgresEngineVersion.VER_15_4 });
        const instanceType = InstanceType.of(InstanceClass.T3, InstanceSize.MICRO);
        const port = 5432;
        const dbName = "dbname";
        
        const secret = new Secret(this, "MasterUserSecret", {
            secretName: "pg-master-user-secret",
            description: "Database master user credentials",
            generateSecretString: {
                secretStringTemplate: JSON.stringify({ username: "postgres" }),
                generateStringKey: "password",
                passwordLength: 16,
                excludePunctuation: true,
            },
        });        

        const dbInstance = new DatabaseInstance(this, "TestDb1", {
            vpc: props.vpc,
            vpcSubnets: { subnetType: SubnetType.PRIVATE_ISOLATED },
            instanceType,
            engine,
            port,
            securityGroups: [props.securityGroup],
            databaseName: dbName,
            credentials: Credentials.fromSecret(secret), //HERE...
            backupRetention: Duration.days(0),
            deleteAutomatedBackups: true,
            removalPolicy: RemovalPolicy.DESTROY,
        });
    }
}

However, I need to add permissions to the Lambda (see entry point above), so I also need to pass the secret into the LambdaStack, so I’m able to retrieve the secret from within the Lambda code:

interface LambdaStackProps extends StackProps {
    vpc: Vpc,
    securityGroup: SecurityGroup,
    secret: Secret
}

export class LambdaStack extends Stack {
    public readonly dataPipelineLambda: NodejsFunction;

    constructor(scope: Construct, id: string, props: LambdaStackProps) {
        super(scope, id, props);
        this.dataPipelineLambda = new NodejsFunction(this, "Lambda", {
            functionName: "dataPipelineHandler",
            runtime: Runtime.NODEJS_18_X,
            handler: "handler",
            entry: "./service/handler.ts"
        });
        this.dataPipelineLambda.addToRolePolicy(new PolicyStatement({
            effect: Effect.ALLOW,
            resources: [props.secret.secretArn],
            actions: [
                "ssm:Get*"
            ]
        }));
    }
}

2

Answers


  1. Chosen as BEST ANSWER

    Unless someone has a better answer, I was able to manually extract the credentials values in the DatabaseInstance to make it work:

    new DatabaseInstance(this, "PrototypeDb1", {
        ///...etc...
        credentials: {
            username: props.secret.secretValueFromJson("username").unsafeUnwrap().toString(),
            password: props.secret.secretValueFromJson("password")
        }
    });
    

  2. Try to create the secret in the parent stack.

    const vpcStack = new VpcStack(app, "DataPipelinePrototypeVpc");
    
    const secret = new Secret(this, "MasterUserSecret", {
        secretName: "pg-master-user-secret",
        generateSecretString: {
            secretStringTemplate: JSON.stringify({ username: "postgres" }),
            generateStringKey: "password",
            passwordLength: 16,
            excludePunctuation: true,
        },
    });
    
    new DataBaseStack(app, "DataPipelinePrototypeDatabaseStack", {
        vpc: vpcStack.vpc,
        securityGroup: vpcStack.securityGroup,
        secret
    });
    const lambdaStack = new LambdaStack(app, "DataPipelinePrototypeLambdaStack", {
        vpc: vpcStack.vpc,
        securityGroup: vpcStack.securityGroup,
        secret
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search