I try to deploy S3 bucket by CDK in TypeScript. When the code is executed, an error occurs even if I have a privilige of administrator. Anyone knows the reason?
export class S3Stack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const s3Example = new s3.Bucket(this, 'bucket', {
versioned: false,
bucketName: 'bucket',
publicReadAccess: false,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: cdk.RemovalPolicy.DESTROY
});
const s3ExamplePolicyDocument = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FirstStatement",
"Effect": "Allow",
"Action": [
"s3:List*",
"s3:Get*"
],
"Resource": [
"arn:aws:s3:::bucket",
"arn:aws:s3:::bucket/*"
],
"Principal": "*",
}
]
};
const s3ExamplePolicy = iam.PolicyDocument.fromJson(s3ExamplePolicyDocument);
new s3.CfnBucketPolicy(this, 'bucketpolicy', {
bucket: s3Example.bucketName,
policyDocument: s3ExamplePolicy
});
}
}
Error message
API: s3:PutBucketPolicy Access Denied
I found the solution. This is caused because I try to apply public permission to non-public bucket. After I modified s3ExamplePolicyDocument, it works
Related
I have a role called awsiotsdk that has full access to iot. I'm trying to get temporary credentials for Connect, Subscribe, and Receive, but my credentials are undefined. What could be the reason for this?
var AWS = require("aws-sdk");
var sts = new AWS.STS({ apiVersion: "2011-06-15" });
const iotpolicy = `{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualStudioCode",
"Effect": "Allow",
"Action": [
"iot:Connect",
"iot:Subscribe",
"iot:Receive"
],
"Resource": "*"
}
]
}`;
const role = {
RoleArn: "arn:aws:iam::258030452305:role/awsiotsdk",
Policy: iotpolicy,
RoleSessionName: "RoleSession1",
};
sts.assumeRole(role, (err, data) => {
console.log({
accessKeyId: data.Credentials.accessKeyId,
secretAcessKey: data.Credentials.secretAccessKey,
sessionToken: data.Credentials.sessionToken,
});
});
Output:
{
accessKeyId: undefined,
secretAcessKey: undefined,
sessionToken: undefined
}
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 have a serverless application in JS, running in AWS lambda on node.js 8.10. It gets triggered by S3 events, and creates a copy of newly uploaded S3 items in a different bucket, with versioning. The lambda function is given a role that contains the following policies:
{
"PolicyName" : {"Fn::Join": ["", [{"Ref": "AWS::Region"}, "-", "S3LambdaPolicy"]]},
"PolicyDocument": {
"Version" : "2012-10-17",
"Statement" : [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:GetObjectTagging",
"s3:PutObject",
"s3:PutObjectTagging",
"s3:ListBucket",
"s3:ListBucketVersions"
],
"Resource": "arn:aws:s3:::*"
}
]
}
}
The function calls this s3 function after copying the item to the targetBucket (and waiting for it to be there):
let tagging = {
Bucket: targetBucket,
Key: targetKey,
Tagging: {
TagSet: [
{
Key: "SourceBucket",
Value: sourceBucket,
},
{
Key: "SourceKey",
Value: key
}
]
}
};
if (data.VersionId) {
tagging.VersionId = data.VersionId;
}
s3.putObjectTagging(tagging, function(err, data){
if (err) {
console.log(err);
} else {
console.log("Set the tagging to " + JSON.stringify(tagging));
}
});
This always fails with an access denied error:
2018-11-06T12:06:24.070Z 389637c4-e1bc-11e8-8eec-8b4d06f7596c { AccessDenied: Access Denied at Request.extractError (/var/runtime/node_modules/aws-sdk/lib/services/s3.js:577:35)
...
message: 'Access Denied',
code: 'AccessDenied',
region: null,
time: 2018-11-06T12:06:24.069Z,
requestId: '178F863CC6FB4960',
extendedRequestId: 'sYbGkGb+hgOWtWp1XPkqtoVRv2XxAg04axRAUaeF0VtMMzMYYyPMkTrwWpx3xUBF0zalKzIJAI8=',
cfId: undefined,
statusCode: 403,
retryable: false,
retryDelay: 39.20736560394356 }
I am not sure what I am missing here, and would appreciate any help with this problem.
Thx,
Stefan
Found the solution myself: As I am using versioning, I also needed to add the specific policies for getting/putting tags on versioned objects. So this is the rule that works for me:
{
"PolicyName" : {"Fn::Join": ["", [{"Ref": "AWS::Region"}, "-", "S3LambdaPolicy"]]},
"PolicyDocument": {
"Version" : "2012-10-17",
"Statement" : [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:GetObjectTagging",
"s3:GetObjectVersionTagging",
"s3:PutObject",
"s3:PutObjectTagging",
"s3:PutObjectVersionTagging",
"s3:ListBucket",
"s3:ListBucketVersions"
],
"Resource": "arn:aws:s3:::*"
}
]
}
}
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')
);