I have a CDK construct to create "SecureString" type SSM parameters, defined at the bottom of the question.
When I call it to create two parameters, like this:
const secureParamA = new SecureStringSsmParam(
this, 'testParamA', '/testSecureStringA');
const secureParamB = new SecureStringSsmParam(
this, 'testParamB', '/testSecureString/nameB');
The AWS console shows the two params being named:
testSecureStringA
/testSecureString/nameB
It all seems to work properly, but notice the missing /
char on the "A" param.
What is causing that?
My workaround is just going to be to document the weird behavior with leading slash and call it done.
/**
* The construct intentionally doesn't take a value to store in the param,
* because I don't want to accidentally be slinging around secrets. The
* intended use case for this construct is that you `deploy` the code, then
* update the param manually in the console.
* Yes, that means your infra that depends on these values (DB connections,
* API keys, etc.) won't work until you take that manual action.
* Usually, when I first create the dependant services (ECS etc.),
* I configure the CDK to "create" the service but not run it (i.e. the ECS task
* definition desiredCount is initially "0") until I have a chance to go and
* manually populate the secrets.
*
* Note that this intentionally fails if a param with that name already exists.
* This means that the param creation may fail if you just change the id without
* changing the name, depending on the order that CDK does the operations in.
* Also note that the AwsCustomResource uses the parameter name in the
* security policy, so if you change the param name - you might run into
* permission errors. Best to just delete then re-create.
*/
export class SecureStringSsmParam extends Construct {
readonly parameterName: string;
readonly resource: AwsCustomResource;
readonly arn: string;
constructor(scope: Construct, id: string, parameterName: string) {
super(scope, id);
validateParameterName(parameterName);
this.parameterName = parameterName;
this.arn = Arn.format({
service: 'ssm',
resource: 'parameter',
// the leading slash seems to cause policy problems
resourceName: this.parameterName.substring(1),
// this is the format used by policy-statement-resources
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
}, Stack.of(this));
this.resource = new AwsCustomResource(this, 'CustomResource', {
// functionName
onCreate: {
service: 'SSM',
action: 'putParameter',
parameters: {
Name: this.parameterName,
Type: 'SecureString',
Value: 'set me via console',
Overwrite: false,
},
physicalResourceId: PhysicalResourceId.of(this.parameterName),
},
onDelete: {
service: 'SSM',
action: 'deleteParameter',
parameters: {
Name: this.parameterName,
},
},
policy: AwsCustomResourcePolicy.fromStatements([
new PolicyStatement({
actions: ['ssm:PutParameter', 'ssm:DeleteParameter'],
resources: [this.arn],
}),
]),
});
}
}
function validateParameterName(parameterName: string): boolean {
if (!parameterName) {
throw new Error('Parameter name cannot be empty.');
}
// not strictly necessary
if (!parameterName.startsWith('/')) {
throw new Error('Parameter name must start with a forward slash.');
}
if (parameterName.length > 256) {
throw new Error('Parameter name cannot be longer than 256 characters.');
}
return true;
}
It's a historical thing, Originally there only used to be single parameter value allowed then AWS added the slash option for hierarchies.
From the docs:
"You aren't required to specify a parameter hierarchy. You can create parameters at level one. These are called root parameters. For backward compatibility, all parameters created in Parameter Store before hierarchies were released are root parameters. The systems treats both of the following parameters as root parameters."