I want to allow Lambda service to create a deployment inside my VPC, thus I have subnet ids array of type Output<string>[] that I want to put into role policy as follows:
export const createNetworkInterfacePolicy = new aws.iam.RolePolicy(
"network-interface-policy-2",
{
policy: pulumi.interpolate `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ec2:CreateNetworkInterfacePermission"],
"Resource": [
"arn:aws:ec2:${region}:${callerIdentity.accountId}:network-interface/*"
],
"Condition": {
"StringEquals": {
"ec2:Subnet": ${JSON.stringify(vpc.vpcPrivateSubnetIds.map(item => item.apply(JSON.stringify)))},
"ec2:AuthorizedService": "lambda.amazonaws.com"
}
}
}
]
}`,
role: deploymentRole
}
);
Unfortunately what I end up with is:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterfacePermission"
],
"Resource": [
"arn:aws:ec2:us-east-2:removedAccountId:network-interface/*"
],
"Condition": {
"StringEquals": {
"ec2:Subnet": [
"Calling [toJSON] on an [Output<T>] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://pulumi.io/help/outputs for more details.\nThis function may throw in a future version of #pulumi/pulumi.",
"Calling [toJSON] on an [Output<T>] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://pulumi.io/help/outputs for more details.\nThis function may throw in a future version of #pulumi/pulumi."
],
"ec2:AuthorizedService": "lambda.amazonaws.com"
}
}
}
]
}
I tried many combinations but none of them work. How do I generate JSON array from Output<string>[]?
Sometimes it's easiest to wrap an apply around the entire creation of another resource. In this case appTaskPolicy becomes an OutputInstance<aws.iam.Policy> which you can then feed into other parts of your program using it's own Outputs.
You'll need to import * as pulumi from '#pulumi/pulumi'; if you haven't already for this to work
const vpc = awsx.Network.getDefault();
const appTaskPolicyName = named('app-task-policy');
const appTaskPolicy = pulumi.all(vpc.publicSubnetIds).apply(([...subnetIds]) => {
return new aws.iam.Policy(appTaskPolicyName, {
policy: {
Version: '2012-10-17',
Statement: [
{
Action: ['sqs:GetQueueUrl', 'sqs:SendMessage'],
Resource: [
'someresourcearn'
],
Effect: 'Allow',
Condition: {
StringEquals: {
'ec2:Subnet': subnetIds,
'ec2:AuthorizedService': 'lambda.amazonaws.com'
}
}
}
]
}
});
});
Related
I am creating a role using the CDK and I need to add sts:SetSourceIdentity to the AssumeRolePolicyDocument.
My code looks like this currently:
new Role(this, 'MyRole', {
assumedBy: new AccountPrincipal(Stack.of(this).account),
...
});
This results in an AssumeRolePolicyDocument that looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::0123456789012:root"
},
"Action": "sts:AssumeRole"
}
]
}
I need it to look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::0123456789012:root"
},
"Action": ["sts:AssumeRole", "sts:SetSourceIdentity"]
}
]
}
The generate CloudFormation from the CDK code above ends up like this:
"MyRoleCF2E104D": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"AWS": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::0123456789012:root"
]
]
}
}
}
],
"Version": "2012-10-17"
},
...
},
I can't figure out how to get the sts:SetSourceIdentity added to the Action in the CloudFormation. Any ideas? Do I need to eject to the L1 construct?
addStatements adds new actions to the role's assume role policy document:
role.assumeRolePolicy?.addStatements(
new iam.PolicyStatement({
actions: ['sts:SetSourceIdentity'],
principals: [new iam.AccountPrincipal(this.account)],
})
);
I am trying to explore if there is a better way. I just define the IAM policy using policy generator and then use the following --
const policyDocument = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FirstStatement",
"Effect": "Allow",
"Action": ["iam:ChangePassword"],
"Resource": "*"
},
{
"Sid": "SecondStatement",
"Effect": "Allow",
"Action": [
"s3:List*",
"s3:Get*"
],
"Resource": [
"arn:aws:s3:::confidential-data",
"arn:aws:s3:::confidential-data/*"
],
"Condition": {"Bool": {"aws:MultiFactorAuthPresent": "true"}}
}
]
};
Then following needs to defined --
const customPolicyDocument = iam.PolicyDocument.fromJson(policyDocument);
const newManagedPolicy = new ManagedPolicy(stack, 'MyNewManagedPolicy', {
document: customPolicyDocument
});
const newPolicy = new Policy(stack, 'MyNewPolicy', {
document: customPolicyDocument
});
Finally, I create a role and attach the policy -
const TestBucketRole = new iam.Role(this, 'TestBucketRole', {
assumedBy: new iam.ArnPrincipal('arn:aws:iam::123456789012:user/user1'),
roleName: "test-role-cdk"
})
TestBucketRole.attachInlinePolicy(newPolicy);
Is there a better way of doing this ?
you can use CDK constructs to iam.PolicyDocument and iam.PolicyStatement to achieve the same thing:
import * as iam from "#aws-cdk/aws-iam";
let policy = new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["iam:ChangePassword"],
resources: ["*"],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["iam:ChangePassword"],
resources: ["*"],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["s3:List*", "s3:Get*"],
resources: [
"arn:aws:s3:::confidential-data",
"arn:aws:s3:::confidential-data/*",
],
conditions: {
Bool: { "aws:MultiFactorAuthPresent": "true" },
},
}),
],
});
what I like about using CDK constructs instead of JSON is the TypeScript property/type checking and autocomplete.
but in the end, they are interchangeable!
I am trying to unit-test my CDK application. I have a role created and I want to assure that it has all the policies assigned. As roles and policies are different resources, policies are not available from Cloud Formation Role resource. Role only has a reference to the policy:
"MyRole4CBCE4C9": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
...
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Ref": "MyPolicyC18AB378"
}
]
},
In the test I have:
expectCDK(stack).to(haveResource("AWS::IAM::Role", {
AssumeRolePolicyDocument: {
Statement: [
...
],
Version: "2012-10-17",
},
}
));
How can I validate that this exact role has correct policy? Steps I have in my head are as follows:
Get "Ref" from the Role properties
Find Policy by this reference
Assert all the necessary data in Policy
However, it seems that CDK does not provide functions to get the element by its logical id and to get resource from haveResource as an object.
What is CDK way to approach this kind of testing?
UPD: seems like I can approach it with StackInspector, though I still wonder, what is the true way for this.
Scenario 1: you're creating a role and a policy in the same stack, and the policy will not be reused in any other stack.
In this case use iam.Policy and attach it in-line:
export class CdkGetLogicalIdExampleStack extends cdk.Stack {
role: iam.IRole;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const policy = new iam.Policy(this, 'policy', {
policyName: 'policy',
statements: [
new iam.PolicyStatement({
actions: ["s3:*"],
resources: ['*'],
effect: iam.Effect.ALLOW
})
]
});
this.role = new iam.Role(this, 'my-role', {
roleName: 'my-role',
assumedBy: new iam.AccountPrincipal(this.account),
});
// Alternatively: this.role.attachInlinePolicy(policy);
policy.attachToRole(this.role);
}
}
The role is kept as stack's attribute, so it's easy to refer to it in tests:
test('Empty Stack', () => {
const app = new cdk.App();
const stack = new CdkGetLogicalIdExample.CdkGetLogicalIdExampleStack(app, 'MyTestStack');
const roleId = stack.getLogicalId(stack.role.node.findChild('Resource') as cdk.CfnElement);
expectCDK(stack).to(haveResource("AWS::IAM::Policy", {
"PolicyDocument": {
"Statement": [
{
"Action": "s3:*",
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"PolicyName": "policy",
"Roles": [
{
"Ref": roleId
}
]
}));
});
Scenario 2: you're creating a policy that can be used in various stacks, so you want to make it a managed policy.
Testing this scenario is a little bit more complex because iam.ManagedPolicy implements the IManagedPolicy interface, which only provides the managedPolicyArn attribute (I am using CDK 1.124.0).
Nevertheless, the iam.ManagedPolicy extends the cdk.Resource, so we can trick the casting mechanism of TypeScript in the following way:
export class CdkGetLogicalIdExampleStack extends cdk.Stack {
// note the type here
managedPolicy: cdk.Resource | iam.IManagedPolicy;
role: iam.IRole;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.managedPolicy = new iam.ManagedPolicy(this, 'managed-policy', {
managedPolicyName: 'managed-policy',
document: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: ["dynamodb:*"],
resources: ["*"],
effect: iam.Effect.ALLOW
})
]
})
});
this.role = new iam.Role(this, 'my-role', {
roleName: 'my-role',
assumedBy: new iam.AccountPrincipal(this.account),
});
// here we need to cast to iam.IManagedPolicy
this.role.addManagedPolicy(this.managedPolicy as iam.IManagedPolicy);
}
}
Now, testing it is possible because we can access managedPolicy attribute as cdk.Resource:
const roleId = stack.getLogicalId(stack.role.node.findChild('Resource') as cdk.CfnElement);
// here we do the casting to cdk.Resource
const managedPolicyResource = stack.managedPolicy as cdk.Resource;
const managedPolicyId = stack.getLogicalId(managedPolicyResource.node.findChild('Resource') as cdk.CfnElement);
expectCDK(stack).to(haveResource("AWS::IAM::ManagedPolicy", {
"PolicyDocument": {
"Statement": [
{
"Action": "dynamodb:*",
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"Description": "",
"ManagedPolicyName": "managed-policy",
"Path": "/"
}));
expectCDK(stack).to(haveResource("AWS::IAM::Role", {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"AWS": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::",
{
"Ref": "AWS::AccountId"
},
":root"
]
]
}
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Ref": managedPolicyId
}
],
"RoleName": "my-role"
}));
In this way you can test that your policies have all the necessary data and the relations between roles and policies are correct.
You can access a full working example here.
i want to know how i can set an assume role policy document to something more complex than a service...
this is what i found till now and maybe this will work:
this.TestRole = new iam.Role(this, "Test", {
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
roleName: "TestRole"
})
But i want to add something like this:
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Principal": {
"AWS": [
"arn:aws:iam::account1:role/Role1",
"arn:aws:iam::account2:role/Role2"
]
}
},
{
"Effect": "Allow",
"Action": [
"sts:AssumeRoleWithSAML"
],
"Principal": {
"Federated": {
some sub and so on
}
},
"Condition": {
"StringEquals": {
"SAML:aud": some saml stuff
}
}
}
]
},
I have no clue how to achieve this... can you help me?
Ok, its possible to do something like this:
this.TestRole = new iam.Role(this, "Test", {
assumedBy: new iam.FederatedPrincipal(new cdk.FnSub("arn:aws:iam::${AWS::AccountId}:saml-provider/SAMLIDP"), {
"StringEquals": {
"SAML:aud": "https://signin.aws.amazon.com/saml"
}
}, "sts:AssumeRoleWithSAML"),
roleName: parent.getApplicationName().charAt(0).toUpperCase() + parent.getApplicationName().slice(1)
})
that was easy :-/ But now i want to add the two roles with action sts:AssumeRole - i don't know how to add another principal...
Fortunately, https://github.com/aws/aws-cdk/pull/1377 delivered the fix we need. You can now use aws_iam.CompositePrincipal to add multiple Principle including service Principles.
For example, in Python for a Data Pipeline Role:
pipeline_role = aws_iam.Role(
scope=self, id='pipeline-role',
role_name='pipeline',
assumed_by=aws_iam.CompositePrincipal(
aws_iam.ServicePrincipal('datapipeline.amazonaws.com'),
aws_iam.ServicePrincipal('elasticmapreduce.amazonaws.com')
)
)
The documentation for iam.RoleProps#assumedBy mentions that you can access the assume policy using the iam.Role#assumeRolePolicy attribute. You could try something like the following:
this.TestRole = new iam.Role(this, 'Test', {
assumedBy: new iam.FederatedPrincipal(/*...*/)
/* ... */
});
this.TestRole.assumeRolePolicy.addStatement(
new iam.PolicyStatement().allow()
.addAction('sts:AssumeRole')
.addAwsPrincipal('arn:aws:iam::account1:role/Role1')
.addAwsPrincipal('arn:aws:iam::account2:role/Role2')
);
I am trying to insert to my DynamoDB table using Cognito user Id and I am getting always "AccessDeniedException". I followed documentation and created table and policy for it as below. What is missing here. Please see the full stack information and request ID.
Table has UserId as Hashkey and id as rangekey
Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:1828211111:table/Table"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${cognito-identity.amazonaws.com:sub}"
]
}
}
}
]
}
Code to save data:
AWS.DynamoDBhelper.Credentials.AddLogin(Helpers.Constants.KEY_LAST_USED_PROVIDER,Helpers.Settings.LoginAccessToken );
var identityId = await AWS.DynamoDBhelper.Credentials.GetIdentityIdAsync();
var client = new Amazon.DynamoDBv2.AmazonDynamoDBClient(AWS.DynamoDBhelper.Credentials, Amazon.RegionEndpoint.USEast1);
Amazon.DynamoDBv2.DataModel.DynamoDBContext context = new Amazon.DynamoDBv2.DataModel.DynamoDBContext(client);
AWS.Table table= new AWS.Table();
table.UserId = identityId;
table.id = "1";
await context.SaveAsync(table);
ex = {Amazon.DynamoDBv2.AmazonDynamoDBException: assumed-role/ _auth_MOBILEHUB/CognitoIdentityCredentials is not authorized to perform: dynamodb:DescribeTable on resource: arn:aws:dynamodb:us-east-1
Model:
[DynamoDBTable("Table")]
public class Table
{
[DynamoDBHashKey]
public string UserId { get; set; }
[DynamoDBRangeKey]
public string id { get; set; }
}
The error message:
... is not authorized to perform: dynamodb:DescribeTable on resource:
arn:aws:dynamodb:us-east-1 ...
Add the following to the Action in your policy:
dynamodb:DescribeTable
So your policy will look like this
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:UpdateItem",
"dynamodb:DescribeTable"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:1828211111:table/Table"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${cognito-identity.amazonaws.com:sub}"
]
}
}
}
]
}