In AWS CDK v2 the ECS TaskDefinition L2 construct has an optional property TaskRole if not specified CDK default behavior is to create a task role. However I do not want a task role set for this resource, it is not actually required in AWS - the Task Definition can function without this property. How can i manage that in CDK? I can't see any way to unset that task role or not have it generated in the first place. Do I need to step back to the L1 construct for this? My configuration:
taskDefinition := awsecs.NewEc2TaskDefinition(stack, jsii.String(deploymentEnv+service.Tag+"TaskDef"), &awsecs.Ec2TaskDefinitionProps{
Family: jsii.String(deploymentEnv + service.Tag),
NetworkMode: awsecs.NetworkMode_BRIDGE,
//TaskRole: what can i do here to fix this
Volumes: &[]*awsecs.Volume{
&efs_shared_volume,
},
})
In the CDK, it's necessary because the L2 construct implements the Grantable interface, and its methods depend on the existence of the role. Technically, you can override almost any property on any node which would allow you to get this effect, but that may result in difficult to track errors down the road.
Additionally, if no role is specified for a task definition, your tasks inherit permissions from the EC2 instance role in the cluster, which is almost certainly not a behavior you want. If that is the behavior you want, you're better off explicitly defining the role to be the same as the role used in the EC2 cluster.
Alternatively, if your intention is to make your tasks have no permissions, your best bet is to either stick with the default behavior or explicitly define a role with no attached policies then (optionally) pass the object returned by the .withoutPolicyUpdates on the role object to prevent it from being updated by grants.
const role = new iam.Role(this, 'Role', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
description: 'Empty ECS task role with no permissions',
});
// ...
taskDefinition := awsecs.NewEc2TaskDefinition(stack, jsii.String(deploymentEnv+service.Tag+"TaskDef"), &awsecs.Ec2TaskDefinitionProps{
// ...
TaskRole: role.withoutPolicyUpdates(),
// ...
},
})
You can remove arbitrary child constructs by ID, using the tryRemoveChild escape hatch method:
// remove the role
taskDefinition.Node().TryRemoveChild(jsii.String("TaskRole"))
// remove the reference to the role
t := taskDefinition.Node().DefaultChild().(awsecs.CfnTaskDefinition)
t.AddPropertyDeletionOverride(jsii.String("TaskRoleArn"))
The trick is identifying the construct ID. You sometimes need to look for it in the source code.
Related
I created cloud9 instance and vpc environment via cdk. Also with role permissions and instance profile, how do i attach that at the end via cdk too?
Currently there seem to be no in built parameters about setting iam role in Ec2Environment
Can't achieve this automatically too if i use CloudFormation, so i am thinking this is not available yet?
I know i can use custom resource or create a lambda to achieve that, but was thinking it's just a bit too much to just to use to attach an instance profile
My code:
const c9IamRole = new iam.Role(this, 'C9IamRole', {
roleName: 'cloud9-admin-access-role',
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'),
]
});
const c9InstanceProfile = new iam.CfnInstanceProfile(this, 'C9InstanceProfile', {
roles: [c9IamRole.roleName],
});
// create a cloud9 ec2 environment in a new VPC
const vpc = new ec2.Vpc(this, 'VPC', { maxAzs: 3 });
const c9Env = new cloud9.Ec2Environment(this, 'Cloud9Env', {
vpc,
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
});
IAM role that i want to attach the instance profile (at the created cloud9 ec2 instance page)
Anything using a Cfn prefixed method is an L1 construct. They do not have the hooks necessary to automatically apply them to other constructs (l2 and l3 - the higher level objects) - they are bare bones, just basically a translation from your code to cfn template snippet.
if iam.CfnInstanceProfile does not have a l2 or l3 version (as of this answer it does not seem to, but the CDK team is always updating) then you'll have to manually attach it using other cfn methods.
Also, the cloud9 library is (as of this writing) still Experimental, which is a good indication that it wont have all the things it needs - It does not seem to have any property for attaching a role. You might be able to manually (again using cfn escape hatch methods) attach a role.
You might try instead applying the roles to a User/Group and giving them permission to access the cloud9, rather than attaching the role to cloud9 and give allowance to various Identities - it may be easier with current CDK constructs.
I’m creating a generic stack template using CloudFormation, and I’ve hit a rather annoying circular reference.
Overall Requirements:
I want to be able to provision (a lot of other things, but mainly) an ECS Cluster Service that auto-scales using capacity providers, the capacity providers are using auto-scaling groups, and the auto scaling groups are using a launch template.
I don’t want static resource names. This causes issues if a resource has to be re-created due to an update and that particular resource has to have a unique name.
Problem:
Without the launch template “knowing the cluster name” (via UserData) the service tasks get stuck in a PROVISIONING state.
So we have the first dependency chain:
Launch Template <- Cluster (Name)
But the Cluster has a dependency chain of:
Cluster <- Capacity Provider <- AutoScalingGroup <- Launch Template
Thus, we have a circular reference: Cluster <-> Launch Template
——
One way I can think of resolving this is to add a suffix to another resource’s name (one that lives outside of this dependency chain, e.g., the target group) as the Cluster’s name; in that way, it is not static but also removes the circular reference.
My question is: is there a better way?
It feels like there should be a resource that the cluster can subscribe to and the ec2 instance can publish to, which would remove the circular dependency as well as the need to assign resource names.
There is no such resource to break the dependency and the cluster name must be pre-defined. This has already been recognized as a problem and its part of open github issue:
[ECS] Full support for Capacity Providers in CloudFormation.
One of the issues noted is:
Break circular dependency so that unnamed clusters can be created
At the moment one work around noted is to partially predefine the name, e.g.:
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub ${AWS::StackName}-ECSCluster
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
UserData:
Fn::Base64: !Sub |
#!/bin/bash
echo ECS_CLUSTER=${AWS::StackName}-ECSCluster >> /etc/ecs/ecs.config
Alternatively, one could try to solve that by development of some custom resource that would be in the form of a lambda function. So you could probably create your unnamed cluster with launch template (LT) that has some dummy name for cluster. Then once the cluster is running, you would use the custom resource to create new version of LT with updated cluster name and refresh your auto-scaling group to use the new LT version. But I'm not sure if this would work. Nevertheless, its something that can be considered at least.
Sharing an update from the GitHub issue. The circular dependency has been broken by introducing a new resource: Cluster Capacity Provider Associations.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-clustercapacityproviderassociations.html
To use it in my example, you:
Create Cluster (without specifying name)
Create Launch Template (using Ref to get cluster name)
Create Auto Scaling Group(s)
Create Capacity Provider(s)
Create Cluster Capacity Provider Associations <- This is new!
The one gotcha is that you have to wait for the new association to be created before you can create a service on the cluster. So be sure that your service "DependsOn" these associations!
I'm using CDK to build our infra on AWS. I create my IAM User for my microservices to talk to AWS Services under the defined policies. My issue is I cannot get aws secret key and id and then, pass as Env variable to my container. See below:
First, I create my IAM user which will be used by my microservices.
const user = new User(this, "user", {
userName: `${myAppEnv}-api-iam-user`,
});
Second, I'm trying to create Access Key.
const accessKey = new CfnAccessKey(this, "ApiUserAccessKey", {
userName: user.userName,
});
const accessKeyId = new CfnOutput(this, "AccessKeyId", {
value: accessKey.ref,
});
const accessKeySecret = new CfnOutput(this, "SecretAccessKeyId", {
value: accessKey.attrSecretAccessKey,
});
Next, I want to pass it as an env variable.
const apiContainer = apiTaskDefinition.addContainer(containerId, {
image: apiImage,
environment: {
APP_ENV: myAppEnv,
AWS_ACCESS_KEY_ID: awsAccessKeyId.value || ":(",
AWS_SECRET_ACCESS_KEY: awsSecretAccessKey.value || ":(",
NOTIFICATIONS_TABLE_ARN: notificationsTableDynamoDBArn,
NOTIFICATIONS_QUEUE_URL: notificationsQueueUrl,
},
cpu: 256,
memoryLimitMiB: 512,
logging: new AwsLogDriver({ streamPrefix: `${myAppEnv}-ec-api` }),
});
When my CDK deploy finishes successfully, I see the below printed out :/
Outputs:
staging-ecstack.AccessKeyId = SOMETHING
staging-ecstack.SecretAccessKeyId = SOMETHINGsy12X21xSSOMETHING2X2
Do you have any idea how I can achieve this?
Generally speaking, creating an IAM user isn't the way to go here - you're better off using an IAM role. With the CDK it will create a taskRole for you automatically when you instantiate the taskDefinition construct. You can assign permissions to other constructs in your stack using various grant* methods as described here:
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef');
const container = taskDefinition.addContainer('web', {
image: ecs.ContainerImage.fromRegistry("apps/myapp"),
memoryLimitMiB: 256,
});
// Grant this task role access to use other resources
myDynamodbTable.grantReadWriteData(taskDefinition.taskRole);
mySnsTopic.grantPublish(taskDefinition.taskRole);
In short, find the answer in my blogpost here:
https://blog.michaelfecher.com/i-tell-you-a-secret-provide-database-credentials-to-an-ecs-fargate-task-in-aws-cdk
To explain a bit more detailled on your issue:
Avoid custom created IAM Roles and use the generated ones by CDK. They are aligned and using the least-privilege principle. In my blog post, the manual IAM role creation isn't necessary. Yes, I know... I need to update that. ;)
Use the grant* method of the corresponding resource, e.g. taskDefinition.
This will create a policy statement for the generated role of 1)
Don't use environment variables for secrets.
There are a lot of resources on the web, which tell you why.
One is this here: https://security.stackexchange.com/questions/197784/is-it-unsafe-to-use-environmental-variables-for-secret-data
Especially, when working with ECS, make use of the secrets argument in the task definition (see the blog post).
It seems, that you're passing the token instead of the actual secret value.
That doesn't work. The token is resolved during synth / Cloud Formation generation.
You won't need the CfnOutput. Use the direct Stack -> Stack passing of fields.
The CfnOutput is really sth., which you should avoid, when having all Stacks under control in one CDK application.
That only makes sense, if you want to share between CDK applications, which are separated deployments and repositories.
If sth. is unclear, don't hesitate asking questions.
I am attempting to create an autoscaling group for my EC2 instances. These instances are encrypted by custom key, so I need to be able to inject a policy that allows the read of said key to the autoscaling group.
I decided to utilize the ServiceLinkedRoleARN: property of the AutoScalingGroup and create new autoscale role that would have all the necessary permissions.
Unfortunatelly, the properties of IAM::ServiceLinkedRole (SLR) do not offer policy inputs (unlike standard roles):
Type: AWS::IAM::ServiceLinkedRole
Properties:
AWSServiceName: String
CustomSuffix: String
Description: String
and so far I was unable to find a solution for this. If there is no way to modify the policies of SLR, I even fail to see a reason for allowing to create our own SLR so I imagine there has to be a reason for that.
Can you please help with how to resolve my problem? My company mandates that I use CloudFormation for that so no console adjustments are possible, but using the custom SLR is not necessary, it just felt like the cleanest solution for me.
You can't modify service-linked role for Auto Scaling:
With the AWSServiceRoleForAutoScalingPlans_EC2AutoScaling role created by AWS Auto Scaling, you can edit only its description and not its permissions.
However, the KMS permissions should be added to your instance role, not service-linked role for Auto Scaling. So you have to change the role associated with your AWS::IAM::InstanceProfile.
Can someone explain to me the difference between an AWS Policy and an AWS Managed Policy in the context of Cloud Formation?
More specifically, I'm trying to define an auto scaling template where:
Each instance in an auto scale configuration is assigned an IAM Instance Role that has a policy.
The same policy is applied to the user when they try and access these instances.
I'm trying to keep duplication to a minimum and it seems like I may be able to achieve it via a Policy linked to a role, and group of users. The role can then be associated with EC2 Instance via instance profile and users can be added to the groups which in turn are assigned the policy.
Why and under what circumstances would one use a ManagedPolicy?
Thank you for your assistance.
EDIT: It seems like Role requires a policy document irrespective. So even having a separate policy won't really help? Or am I missing something?
AWS::IAM::Role only requires a trust policy. The Policy/Managed Policy can be defined separately.
The difference between AWS::IAM::ManagedPolicy and AWS::IAM::Policy is that AWS::IAM::ManagedPolicy does not require you to assign a Group, Role or User when defining it. AWS::IAM::Policy does. In your use case, you're probably fine using AWS::IAM::Policy.
If I may add, testing Policy creation using CDK v2.12.0, groups, users or roles are not required. iam.ManagedPolicy creates a policy you can share, iam.Policy is created as an inline policy.
new iam.Policy(this, 'testPolicy2', {
statements: policyDocs,
//groups: [s3UserGroup],
policyName: 'testPolicy2'
})
new iam.ManagedPolicy(this, 'testPolicy3', {
statements: policyDocs,
//groups: [s3UserGroup],
managedPolicyName: 'testPolicy3'
})