aws-cdk: (aws-ssm): stringListValue returns unsplit list as string instead of list of strings
What is the problem?
When using a StringListParameter imported from an existing string list parameter with StringListParameter.fromStringListParameterName, the resulting value from ipListParam.stringListValue ends up being a single string with the unsplit list in it, instead of the expected list of strings.
My use-case is using the StringListParameter to store a list of IP addresses to be used in a ResourcePolicy, like so:
const ipListParam = new StringListParameter(this, "ip-list", {
stringListValue: ["x.x.x.1", "x.x.x.2"],
parameterName: "ipList",
});
const apiResourcePolicy = new PolicyDocument({
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
principals: [new AnyPrincipal()],
resources: ["execute-api:/*/*/*"],
actions: ["execute-api:Invoke"],
conditions: {
IpAddress: {
"aws:SourceIp": ipListParam.stringListValue,
},
},
}),
],
});
Reproduction Steps
I created a new Typescript CDK project using aws-cdk-lib=2.15.0, and deployed the following stack to create the parameter, REST API Gateway, and resource policy:
Sample 1 (working):
import { aws_apigateway, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib";
import { EndpointType } from "aws-cdk-lib/aws-apigateway";
import {
AnyPrincipal,
Effect,
PolicyDocument,
PolicyStatement,
} from "aws-cdk-lib/aws-iam";
import { StringListParameter } from "aws-cdk-lib/aws-ssm";
import { Construct } from "constructs";
export class StringlistTestStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const ipListParam = new StringListParameter(this, "ip-list", {
stringListValue: ["x.x.x.1", "x.x.x.2"],
parameterName: "ipList",
});
ipListParam.applyRemovalPolicy(RemovalPolicy.RETAIN);
const apiResourcePolicy = new PolicyDocument({
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
principals: [new AnyPrincipal()],
resources: ["execute-api:/*/*/*"],
actions: ["execute-api:Invoke"],
conditions: {
IpAddress: {
"aws:SourceIp": ipListParam.stringListValue,
},
},
}),
],
});
const api = new aws_apigateway.RestApi(this, "pudim-api", {
policy: apiResourcePolicy,
endpointTypes: [EndpointType.REGIONAL],
});
const item = api.root.addResource("item");
item.addMethod(
"GET",
new aws_apigateway.HttpIntegration("http://www.pudim.com.br")
);
}
}
Then, I removed ipList from the stack while retaining the parameter, and imported it by its name again.
Sample 2 (not working):
import { aws_apigateway, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib";
import { EndpointType } from "aws-cdk-lib/aws-apigateway";
import {
AnyPrincipal,
Effect,
PolicyDocument,
PolicyStatement,
} from "aws-cdk-lib/aws-iam";
import { StringListParameter } from "aws-cdk-lib/aws-ssm";
import { Construct } from "constructs";
export class StringlistTestStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const ipListParam = StringListParameter.fromStringListParameterName(
this,
"ip-list",
"ipList"
);
const apiResourcePolicy = new PolicyDocument({
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
principals: [new AnyPrincipal()],
resources: ["execute-api:/*/*/*"],
actions: ["execute-api:Invoke"],
conditions: {
IpAddress: {
"aws:SourceIp": ipListParam.stringListValue,
},
},
}),
],
});
const api = new aws_apigateway.RestApi(this, "pudim-api", {
policy: apiResourcePolicy,
endpointTypes: [EndpointType.REGIONAL],
});
const item = api.root.addResource("item");
item.addMethod(
"GET",
new aws_apigateway.HttpIntegration("http://www.pudim.com.br")
);
}
}
What did you expect to happen?
The API should be accessible for the IP addresses in the StringListParameter ipList.
I deployed and tested the endpoint resulting from Sample 1, and it worked as expected. The generated resource policy associated with the REST API Gateway was also correct (note the list in aws:SourceIp:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:eu-central-1:<account>:<api-id>/*/*/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"x.x.x.1",
"x.x.x.2"
]
}
}
}
]
}
What actually happened?
The API was not accessible, and the generated resource policy now contained a comma-separated string of IP addresses.
Generated resource policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:eu-central-1:<account>:<api-id>/*/*/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "x.x.x.1,x.x.x.2"
}
}
}
]
}
I had the same problem with CDK 1.147.0.
CDK CLI Version
2.15.0 (build 151055e)
Framework Version
?
Node.js Version
v16.14.0
OS
macOS 12.2.1 (21D62)
Language
Typescript
Language Version
Typescript 3.9.7
Other information
When deploying, this is the change to the policy as presented by CDK:
IAM Statement Changes
┌───┬────────────────────┬────────┬────────────────────┬───────────┬──────────────────────────────────────────────────────────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼────────────────────┼────────┼────────────────────┼───────────┼──────────────────────────────────────────────────────────────┤
│ - │ execute-api:/*/*/* │ Allow │ execute-api:Invoke │ AWS:* │ "IpAddress": { │
│ │ │ │ │ │ "aws:SourceIp": "{\"Fn::Split\":[\",\",\"${iplist29786949. │
│ │ │ │ │ │ Value}\"]}" │
│ │ │ │ │ │ } │
├───┼────────────────────┼────────┼────────────────────┼───────────┼──────────────────────────────────────────────────────────────┤
│ + │ execute-api:/*/*/* │ Allow │ execute-api:Invoke │ AWS:* │ "IpAddress": { │
│ │ │ │ │ │ "aws:SourceIp": "{\"Fn::Split\":[\",\",\"{{resolve:ssm:ipL │
│ │ │ │ │ │ ist}}\"]}" │
│ │ │ │ │ │ } │
└───┴────────────────────┴────────┴────────────────────┴───────────┴──────────────────────────────────────────────────────────────┘
About this issue
- Original URL
- State: open
- Created 2 years ago
- Reactions: 10
- Comments: 15 (7 by maintainers)
@iwt-kschoenrock I was able to reproduce the issue, on a smaller example.
Here’s what I did:
Wrote this CDK code:
Ran
cdk deploy.Changed the code to:
Ran
cdk deploy.And here’s what I see in the AWS Console for S3, in the “Cross-origin resource sharing (CORS)” section:
Clearly, it should be
"AllowedHeaders": ["val1", "val2"]instead.So it does look like there’s some issue with this, either in the CDK, or in the CloudFormation support.
The relevant part of the generated template can be seen here:
We would expect the dynamic reference to be resolved, then have the split be applied. However, this is not what happens
If you test this with splitting by
:instead of,, this is what the allowed headers will resolve to in the S3 console:This confirms that the split is occurring before the dynamic value is resolved. Since we rely on CloudFormation to keep these values secure, I’m not aware of any workarounds here. I’ve reported this to CloudFormation to see what they say P68236683
Hi guys,
I stumbled upon this issue in my own implementation and after a bit of research, I found a workaround:
To use it as a list:
Thanks for responding @ShubhamJainSJ, I neglected on posting an update here once one became available. My apologies.
This is part of the response I got:
They dove into a deeper explanation of CloudFormation architecture - Essentially, this is necessary to keep dynamic reference values secure.
As for a workaround…
Could get pretty sloppy.
Is there any progress on this issue? Still running into this on
aws-cdk-lib@2.31.1– this is very easy to reproduce. The split string is not correctly added to the template