(aws-ssm): stringListValue returns unsplit list as string instead of list of strings
See original GitHub issueWhat 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}}\"]}" β
β β β β β β } β
βββββ΄βββββββββββββββββββββ΄βββββββββ΄βββββββββββββββββββββ΄ββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Issue Analytics
- State:
- Created 2 years ago
- Reactions:5
- Comments:10 (6 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