-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for resolving dynamic ssm values
This adds support for resolving the two types of SSM dynamic references: 1. [SSM PlainText](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references-ssm.html) 2. [SSM SecureString](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references-ssm-secure-strings.html) closes #200
- Loading branch information
Showing
11 changed files
with
576 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
name: pulumi-aws-ssm-dynamic | ||
runtime: nodejs | ||
description: ssm-dynamic integration test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as pulumi from '@pulumi/pulumi'; | ||
import * as ssm from 'aws-cdk-lib/aws-ssm'; | ||
import * as pulumicdk from '@pulumi/cdk'; | ||
|
||
const config = new pulumi.Config(); | ||
const prefix = config.get('prefix') ?? pulumi.getStack(); | ||
class SsmDynamicStack extends pulumicdk.Stack { | ||
public readonly stringValue: pulumi.Output<string>; | ||
public readonly stringListValue: pulumi.Output<string[]>; | ||
constructor(app: pulumicdk.App, id: string, options?: pulumicdk.StackOptions) { | ||
super(app, id, options); | ||
|
||
const stringParam = new ssm.StringParameter(this, 'testparam', { | ||
parameterName: `${prefix}-param`, | ||
stringValue: 'testvalue', | ||
}); | ||
this.stringValue = this.asOutput(stringParam.stringValue); | ||
|
||
const listParam = new ssm.StringListParameter(this, 'testparamlist', { | ||
parameterName: `${prefix}-listparam`, | ||
stringListValue: ['abcd', 'xyz'], | ||
}); | ||
this.stringListValue = this.asOutput(listParam.stringListValue); | ||
} | ||
} | ||
|
||
const app = new pulumicdk.App('app', (scope: pulumicdk.App) => { | ||
const stack = new SsmDynamicStack(scope, `${prefix}-misc`); | ||
return { | ||
stringValue: stack.stringValue, | ||
stringListValue: stack.stringListValue, | ||
}; | ||
}); | ||
export const stringValue = app.outputs['stringValue']; | ||
export const stringListValue = app.outputs['stringListValue']; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "pulumi-aws-cdk", | ||
"devDependencies": { | ||
"@types/node": "^10.0.0" | ||
}, | ||
"dependencies": { | ||
"@pulumi/aws": "^6.0.0", | ||
"@pulumi/aws-native": "^1.11.0", | ||
"@pulumi/cdk": "^0.5.0", | ||
"@pulumi/pulumi": "^3.0.0", | ||
"aws-cdk-lib": "2.149.0", | ||
"constructs": "10.3.0", | ||
"esbuild": "^0.24.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import * as pulumi from '@pulumi/pulumi'; | ||
import * as ssm from 'aws-cdk-lib/aws-ssm'; | ||
import * as pulumicdk from '@pulumi/cdk'; | ||
import { CfnDynamicReference, CfnDynamicReferenceService } from 'aws-cdk-lib'; | ||
|
||
const config = new pulumi.Config(); | ||
const prefix = config.get('prefix') ?? pulumi.getStack(); | ||
class SsmDynamicStack extends pulumicdk.Stack { | ||
public readonly stringValue: pulumi.Output<string>; | ||
public readonly stringListValue: pulumi.Output<string[]>; | ||
public readonly dynamicStringValue: pulumi.Output<string>; | ||
public readonly dynamicStringListValue: pulumi.Output<string[]>; | ||
constructor(app: pulumicdk.App, id: string, options?: pulumicdk.StackOptions) { | ||
super(app, id, options); | ||
|
||
const stringParam = new ssm.StringParameter(this, 'testparam', { | ||
parameterName: `${prefix}-param`, | ||
stringValue: 'testvalue', | ||
}); | ||
this.stringValue = this.asOutput(stringParam.stringValue); | ||
|
||
const listParam = new ssm.StringListParameter(this, 'testparamlist', { | ||
parameterName: `${prefix}-listparam`, | ||
stringListValue: ['abcd', 'xyz'], | ||
}); | ||
this.stringListValue = this.asOutput(listParam.stringListValue); | ||
|
||
const stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, `${prefix}-param`).toString(); | ||
const stringDynamicParam = new ssm.StringParameter(this, 'stringDynamicParam', { | ||
stringValue: stringValue, | ||
}); | ||
this.dynamicStringValue = this.asOutput(stringDynamicParam.stringValue); | ||
|
||
const stringListValue = new CfnDynamicReference( | ||
CfnDynamicReferenceService.SSM, | ||
`${prefix}-listparam`, | ||
).toString(); | ||
const stringListDynamicParam = new ssm.StringParameter(this, 'stringListDynamicParam', { | ||
stringValue: stringListValue, | ||
}); | ||
this.dynamicStringListValue = this.asOutput(stringListDynamicParam.stringValue).apply((v) => v.split(',')); | ||
} | ||
} | ||
|
||
const app = new pulumicdk.App('app', (scope: pulumicdk.App) => { | ||
const stack = new SsmDynamicStack(scope, `${prefix}-misc`); | ||
return { | ||
stringValue: stack.stringValue, | ||
stringListValue: stack.stringListValue, | ||
dynamicStringValue: stack.dynamicStringValue, | ||
dynamicStringListValue: stack.dynamicStringListValue, | ||
}; | ||
}); | ||
export const stringValue = app.outputs['stringValue']; | ||
export const stringListValue = app.outputs['stringListValue']; | ||
export const dynamicStringValue = app.outputs['dynamicStringValue']; | ||
export const dynamicStringListValue = app.outputs['dynamicStringListValue']; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"compilerOptions": { | ||
"strict": true, | ||
"outDir": "bin", | ||
"target": "es2019", | ||
"module": "commonjs", | ||
"moduleResolution": "node", | ||
"sourceMap": true, | ||
"experimentalDecorators": true, | ||
"pretty": true, | ||
"noFallthroughCasesInSwitch": true, | ||
"noImplicitReturns": true, | ||
"forceConsistentCasingInFileNames": true | ||
}, | ||
"include": [ | ||
"./*.ts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import * as aws from '@pulumi/aws'; | ||
import * as pulumi from '@pulumi/pulumi'; | ||
import { containsEventuals } from '../types'; | ||
|
||
/** | ||
* The regular expression used to match an SSM plaintext dynamic reference. | ||
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references-ssm.html#dynamic-references-ssm-pattern | ||
*/ | ||
const SSM_PLAINTEXT_DYNAMIC_REGEX = /{{resolve:ssm:([a-zA-Z0-9_.\-/]+(?::\d+)?)}}/; | ||
|
||
/** | ||
* The regular expression used to match an SSM SecureString dynamic reference. | ||
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references-ssm-secure-strings.html#dynamic-references-ssm-secure-pattern | ||
*/ | ||
const SSM_SECURE_DYNAMIC_REGEX = /{{resolve:ssm-secure:([a-zA-Z0-9_.\-/]+(?::\d+)?)}}/; | ||
|
||
export interface SSMDynamicReference { | ||
/** | ||
* The name of the parameter you want to reference. | ||
* This will also include the version if specified. | ||
*/ | ||
parameterName: string; | ||
} | ||
|
||
/** | ||
* Parses an SSM plaintext dynamic reference and returns the parameter name. | ||
* | ||
* @param value - The value which contains the SSM plaintext dynamic reference | ||
* @returns The parameter name | ||
*/ | ||
export function parseSSMDynamicSecureStringReference(value: string): SSMDynamicReference { | ||
const match = value.match(SSM_SECURE_DYNAMIC_REGEX); | ||
if (!match) { | ||
throw new Error(`Failed to parse SSM SecureString dynamic reference: ${value}`); | ||
} | ||
|
||
const [_, parameterName] = match; | ||
return { | ||
parameterName, | ||
}; | ||
} | ||
|
||
/** | ||
* Parses an SSM SecureString dynamic reference and returns the parameter name. | ||
* | ||
* @param value - The value which contains the SSM SecureString dynamic reference | ||
* @returns The parameter name | ||
*/ | ||
export function parseSSMDynamicPlaintextReference(value: string): SSMDynamicReference { | ||
const match = value.match(SSM_PLAINTEXT_DYNAMIC_REGEX); | ||
if (!match) { | ||
throw new Error(`Failed to parse SSM plaintext dynamic reference: ${value}`); | ||
} | ||
|
||
const [_, parameterName] = match; | ||
return { | ||
parameterName, | ||
}; | ||
} | ||
|
||
/** | ||
* Resolves an SSM plaintext dynamic reference | ||
* | ||
* @param parent - The parent resource for the SSM parameter function | ||
* @param value - The value which contains the SSM plaintext dynamic reference | ||
* @returns The parameter value as a pulumi output | ||
*/ | ||
export function resolveSSMDynamicPlaintextReference( | ||
parent: pulumi.Resource, | ||
value: string, | ||
): pulumi.Output<string | string[]> { | ||
// This shouldn't happen because we currently only call this where we know we have a string | ||
// but adding this for completeness | ||
if (containsEventuals(value)) { | ||
throw new Error('SSM dynamic references cannot contain unresolved values'); | ||
} | ||
|
||
const parts = parseSSMDynamicPlaintextReference(value); | ||
return aws.ssm | ||
.getParameterOutput( | ||
{ | ||
name: parts.parameterName, | ||
// we don't want to return a decrypted SecureString value | ||
// SecureString types are handled elsewhere | ||
withDecryption: false, | ||
}, | ||
{ parent }, | ||
) | ||
.apply((v) => { | ||
switch (v.type) { | ||
// CDK/CloudFormation will return a string for both String and StringList types | ||
case 'String': | ||
case 'StringList': | ||
return v.value; | ||
default: | ||
throw new Error(`Unsupported SSM parameter type: ${v.type}`); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Resolves an SSM SecureString dynamic reference | ||
* | ||
* @param parent - The parent resource for the SSM parameter function | ||
* @param value - The value which contains the SSM SecureString dynamic reference | ||
* @returns The parameter value as a pulumi secret output | ||
*/ | ||
export function resolveSSMDynamicSecureStringReference(parent: pulumi.Resource, value: string): pulumi.Output<string> { | ||
// This shouldn't happen because we currently only call this where we know we have a string | ||
// but adding this for completeness | ||
if (containsEventuals(value)) { | ||
throw new Error('SSM dynamic references cannot contain unresolved values'); | ||
} | ||
|
||
const parts = parseSSMDynamicSecureStringReference(value); | ||
return aws.ssm | ||
.getParameterOutput( | ||
{ | ||
name: parts.parameterName, | ||
withDecryption: true, | ||
}, | ||
{ parent }, | ||
) | ||
.apply((v) => { | ||
switch (v.type) { | ||
case 'SecureString': | ||
return pulumi.secret(v.value); | ||
default: | ||
throw new Error(`Unsupported SSM parameter type: ${v.type}`); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Used to process a value that may contain a ssm dynamic reference | ||
* | ||
* The value may be a pulumi output (typically if the value contains resource references) or a string. | ||
* | ||
* @param parent - The parent resource | ||
* @param value - A fully resolved value that may contain a ssm dynamic reference | ||
* @returns A secret output if the value is a ssm dynamic reference, otherwise the original value | ||
*/ | ||
export function processSSMReferenceValue(parent: pulumi.Resource, value: any): any { | ||
let returnValue = value; | ||
if (pulumi.Output.isInstance(value)) { | ||
returnValue = value.apply((v) => { | ||
if (typeof v === 'string' && v.startsWith('{{resolve:ssm:')) { | ||
return resolveSSMDynamicPlaintextReference(parent, v); | ||
} else if (typeof v === 'string' && v.startsWith('{{resolve:ssm-secure:')) { | ||
return resolveSSMDynamicSecureStringReference(parent, v); | ||
} | ||
return v; | ||
}); | ||
} else if (typeof value === 'string' && value.startsWith('{{resolve:ssm-secure:')) { | ||
returnValue = resolveSSMDynamicSecureStringReference(parent, value); | ||
} else if (typeof value === 'string' && value.startsWith('{{resolve:ssm:')) { | ||
returnValue = resolveSSMDynamicPlaintextReference(parent, value); | ||
} | ||
return returnValue; | ||
} |
Oops, something went wrong.