-
Notifications
You must be signed in to change notification settings - Fork 1
New: [AEA-6258] - Add SSM Parameter construct #619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
02b0af2
455b657
26fbada
db95a9f
d3594ca
f3a9207
c7dbb39
f7e421d
2afccf1
dc6f2d3
bb83c29
74d0de2
c7d714d
f2e9325
0ac46b1
b500519
734254b
e26e480
50ebe0a
bd610d0
4912c82
a784142
7b17511
503d44c
0ae7177
2b92465
70e7f20
a5a01ac
3176a52
87cba70
dcab80b
1f2297d
f5a4f8d
d15b6f7
92376db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import {CfnOutput} from "aws-cdk-lib" | ||
| import {Effect, ManagedPolicy, PolicyStatement} from "aws-cdk-lib/aws-iam" | ||
| import {StringParameter} from "aws-cdk-lib/aws-ssm" | ||
| import {Construct} from "constructs" | ||
|
|
||
| export interface SsmParameterDefinition { | ||
| /** | ||
| * Unique identifier used for construct and output logical IDs. | ||
| */ | ||
| readonly id: string | ||
| /** | ||
| * Suffix appended to stackName to create the parameter name. | ||
| * The final SSM parameter name is `${stackName}-${nameSuffix}`. | ||
| */ | ||
| readonly nameSuffix: string | ||
| /** | ||
| * Description stored with the SSM parameter. | ||
| */ | ||
| readonly description: string | ||
| /** | ||
| * Value stored in the SSM parameter. | ||
| */ | ||
| readonly value: string | ||
| /** | ||
| * Optional export suffix for the output containing the parameter name. | ||
| * Defaults to `${nameSuffix}Parameter`. | ||
| */ | ||
| readonly outputExportSuffix?: string | ||
| /** | ||
| * Optional output description. | ||
| */ | ||
| readonly outputDescription?: string | ||
| } | ||
|
|
||
| export interface SsmParametersConstructProps { | ||
| /** | ||
| * Prefix used in SSM parameter names and CloudFormation export names. | ||
| */ | ||
| readonly stackName: string | ||
| /** | ||
| * List of SSM parameters to create. | ||
| */ | ||
| readonly parameters: Array<SsmParameterDefinition> | ||
| /** | ||
| * Description for the managed policy that grants read access. | ||
| * @default "Allows reading SSM parameters" | ||
| */ | ||
| readonly readPolicyDescription?: string | ||
| /** | ||
| * Description for the output exporting the managed policy ARN. | ||
| * @default "Access to the parameters used by the integration" | ||
| */ | ||
| readonly readPolicyOutputDescription?: string | ||
| /** | ||
| * Export suffix for the output exporting the managed policy ARN. | ||
| * @default "GetParametersPolicy" | ||
| */ | ||
| readonly readPolicyExportSuffix?: string | ||
| } | ||
|
|
||
| /** | ||
| * Creates a bundle of SSM String parameters, a managed policy to read them, | ||
| * and CloudFormation outputs to export parameter names and policy ARN. | ||
| */ | ||
| export class SsmParametersConstruct extends Construct { | ||
| public readonly parameters: Record<string, StringParameter> | ||
| public readonly readParametersPolicy: ManagedPolicy | ||
|
|
||
| public constructor(scope: Construct, id: string, props: SsmParametersConstructProps) { | ||
| super(scope, id) | ||
|
|
||
| const { | ||
| stackName, | ||
| parameters, | ||
| readPolicyDescription = "Allows reading SSM parameters", | ||
| readPolicyOutputDescription = "Access to the parameters used by the integration", | ||
| readPolicyExportSuffix = "GetParametersPolicy" | ||
| } = props | ||
|
|
||
| if (parameters.length === 0) { | ||
| throw new Error("SsmParametersConstruct requires at least one parameter definition") | ||
| } | ||
|
|
||
| const createdParameters: Record<string, StringParameter> = {} | ||
|
|
||
| for (const parameter of parameters) { | ||
| const ssmParameter = new StringParameter(this, `${parameter.id}Parameter`, { | ||
| parameterName: `${stackName}-${parameter.nameSuffix}`, | ||
| description: parameter.description, | ||
| stringValue: parameter.value | ||
| }) | ||
|
|
||
| createdParameters[parameter.id] = ssmParameter | ||
|
|
||
| new CfnOutput(this, `${parameter.id}ParameterNameOutput`, { | ||
| description: parameter.outputDescription ?? `Name of the SSM parameter holding ${parameter.nameSuffix}`, | ||
| value: ssmParameter.parameterName, | ||
| exportName: `${stackName}-${parameter.outputExportSuffix ?? `${parameter.nameSuffix}Parameter`}` | ||
|
Check warning on line 98 in packages/cdkConstructs/src/constructs/SsmParametersConstruct.ts
|
||
| }) | ||
| } | ||
|
wildjames marked this conversation as resolved.
|
||
|
|
||
| const readParametersPolicy = new ManagedPolicy(this, "GetParametersPolicy", { | ||
| description: readPolicyDescription, | ||
| statements: [ | ||
| new PolicyStatement({ | ||
| effect: Effect.ALLOW, | ||
| actions: ["ssm:GetParameter", "ssm:GetParameters"], | ||
| resources: Object.values(createdParameters).map((parameter) => parameter.parameterArn) | ||
| }) | ||
| ] | ||
| }) | ||
|
|
||
| new CfnOutput(this, "ReadParametersPolicyOutput", { | ||
| description: readPolicyOutputDescription, | ||
| value: readParametersPolicy.managedPolicyArn, | ||
| exportName: `${stackName}-${readPolicyExportSuffix}` | ||
| }) | ||
|
|
||
| this.parameters = createdParameters | ||
| this.readParametersPolicy = readParametersPolicy | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| import {App, Stack} from "aws-cdk-lib" | ||
| import {Template} from "aws-cdk-lib/assertions" | ||
| import { | ||
| beforeAll, | ||
| describe, | ||
| expect, | ||
| test | ||
| } from "vitest" | ||
|
|
||
| import {SsmParametersConstruct} from "../../src/constructs/SsmParametersConstruct" | ||
|
|
||
| describe("SsmParametersConstruct", () => { | ||
| let template: Template | ||
|
|
||
| beforeAll(() => { | ||
| const app = new App() | ||
| const stack = new Stack(app, "parameterStack") | ||
|
|
||
| new SsmParametersConstruct(stack, "TestingParameters", { | ||
|
Check warning on line 19 in packages/cdkConstructs/tests/constructs/ssmParametersConstruct.test.ts
|
||
| stackName: "mock-stack", | ||
| parameters: [ | ||
| { | ||
| id: "MockParam1", | ||
| nameSuffix: "MockParam1", | ||
| description: "Description for mock parameter 1", | ||
| value: "mock-value-1", | ||
| outputExportSuffix: "MockParam1Parameter", | ||
| outputDescription: "Name of the SSM parameter holding MockParam1" | ||
| }, | ||
| { | ||
| id: "MockParam2", | ||
| nameSuffix: "MockParam2", | ||
| description: "Description for mock parameter 2", | ||
| value: "mock-value-2", | ||
| outputExportSuffix: "MockParam2Parameter", | ||
| outputDescription: "Name of the SSM parameter holding MockParam2" | ||
| }, | ||
| { | ||
| id: "MockParam3", | ||
| nameSuffix: "MockParam3", | ||
| description: "Description for mock parameter 3", | ||
| value: "mock-value-3", | ||
| outputExportSuffix: "MockParam3Parameter", | ||
| outputDescription: "Name of the SSM parameter holding MockParam3" | ||
| } | ||
| ], | ||
| readPolicyDescription: "Mock policy description", | ||
| readPolicyOutputDescription: "Mock read policy output description", | ||
| readPolicyExportSuffix: "MockGetParametersPolicy" | ||
| }) | ||
|
|
||
|
wildjames marked this conversation as resolved.
|
||
| template = Template.fromStack(stack) | ||
| }) | ||
|
wildjames marked this conversation as resolved.
|
||
|
|
||
| test("creates all SSM parameters", () => { | ||
| template.hasResourceProperties("AWS::SSM::Parameter", { | ||
| Name: "mock-stack-MockParam1", | ||
| Type: "String", | ||
| Value: "mock-value-1" | ||
| }) | ||
|
|
||
| template.hasResourceProperties("AWS::SSM::Parameter", { | ||
| Name: "mock-stack-MockParam2", | ||
| Type: "String", | ||
| Value: "mock-value-2" | ||
| }) | ||
|
|
||
| template.hasResourceProperties("AWS::SSM::Parameter", { | ||
| Name: "mock-stack-MockParam3", | ||
| Type: "String", | ||
| Value: "mock-value-3" | ||
| }) | ||
| }) | ||
|
|
||
| test("creates read policy with GetParameter actions for all parameters", () => { | ||
| const policies = template.findResources("AWS::IAM::ManagedPolicy", { | ||
| Properties: { | ||
| Description: "Mock policy description" | ||
| } | ||
| }) | ||
|
|
||
| expect(Object.keys(policies)).toHaveLength(1) | ||
|
|
||
| const policy = Object.values(policies)[0] as { | ||
| Properties: { | ||
| PolicyDocument: { | ||
| Statement: Array<{ | ||
| Action: Array<string> | ||
| Resource: Array<unknown> | ||
| }> | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const statement = policy.Properties.PolicyDocument.Statement[0] | ||
| expect(statement.Action).toEqual(["ssm:GetParameter", "ssm:GetParameters"]) | ||
| expect(statement.Resource).toHaveLength(3) | ||
| }) | ||
|
|
||
| test("exports parameter names and policy ARN", () => { | ||
| const outputs = template.toJSON().Outputs as Record<string, { | ||
| Description?: string | ||
| Export?: { | ||
| Name?: string | ||
| } | ||
| }> | ||
|
|
||
| const exportedNames = Object.values(outputs) | ||
| .map((output) => output.Export?.Name) | ||
| .filter((name): name is string => name !== undefined) | ||
|
|
||
| const descriptions = Object.values(outputs) | ||
| .map((output) => output.Description) | ||
| .filter((description): description is string => description !== undefined) | ||
|
|
||
| expect(exportedNames).toContain("mock-stack-MockParam1Parameter") | ||
| expect(exportedNames).toContain("mock-stack-MockParam2Parameter") | ||
| expect(exportedNames).toContain("mock-stack-MockParam3Parameter") | ||
| expect(exportedNames).toContain("mock-stack-MockGetParametersPolicy") | ||
|
|
||
| expect(descriptions).toContain("Name of the SSM parameter holding MockParam1") | ||
| expect(descriptions).toContain("Name of the SSM parameter holding MockParam2") | ||
| expect(descriptions).toContain("Name of the SSM parameter holding MockParam3") | ||
| expect(descriptions).toContain("Mock read policy output description") | ||
| }) | ||
| }) | ||
Uh oh!
There was an error while loading. Please reload this page.