API Gateway with DNS record in another account - amazon-web-services

In our environment there is a dedicated AWS account that contains registered domain as well as hosting zone in Route53. Also an IAM role is created that allows specific set of other accounts to create records in that hosted zone.
Using AWS CDK (v2) is there a way to create API Gateway in one account with DNS record (A Record?) created for it in that dedicated one?
This is an example of setup:
export class CdkRoute53ExampleStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const backend = new lambda.Function(this, 'HelloHandler', {
runtime: lambda.Runtime.NODEJS_14_X,
code: lambda.Code.fromAsset('src'),
handler: 'hello.handler'
});
const restApi = new apigw.LambdaRestApi(this, 'Endpoint', {
handler: backend,
domainName: {
domainName: `cdk53.${Config.domainName}`,
certificate: acm.Certificate.fromCertificateArn(
this,
"my-cert",
Config.certificateARN
),
},
endpointTypes: [apigw.EndpointType.REGIONAL]
});
new route53.ARecord(this, "apiDNS", {
zone: route53.HostedZone.fromLookup(this, "baseZone", {
domainName: Config.domainName,
}),
recordName: "cdk53",
target: route53.RecordTarget.fromAlias(
new route53targets.ApiGateway(restApi)
),
});
}
}
Basically I need that last ARecord construct to be created under credentials from assumed role in another account.

As far as I am aware, a CDK stack is built and deployed entirely within the context of a single IAM user (aka identity). I.e. you can't run different bits of the stack as different IAM users. (As an aside, code which uses the regular AWS SDK - such as lambdas - can switch identities using STS.)
The solution therefore is to do as much as you can using the CDK (in account B). After this is complete, then the final step - registering the DNS record - is done using a different identity which operates within account A.
Registering the DNS record could be done using AWS CLI commands, or you could even create another (mini) stack just for this purpose.
Either way you would execute the second step as an identity which is allowed to write records to the hosted zone in account A.
This could be achieved by using a different --profile with your CLI or CDK commands. Or you could use STS to assume a role which is allowed to create the DNS record in account A.
Using STS has the advantage that you don't need to know credentials of account A. But I've found STS to have a steep learning curve and can be a little confusing to get right.
EDIT: it seems the CDK stack in account B can actually switch roles when registering a DNS record by virtue of the CrossAccountZoneDelegationRecord construct and the delegationRole attribute - see https://stackoverflow.com/a/72097522/226513 This means that you can keep all your code in the account B CDK stack.

Related

CDK cloud9 - How to attach preconstructed instance profile to Cloud9 instance iam role in cdk?

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.

Create VPC endpoint for S3 bucket lambda access using AWS CDK

I am building a system using Python flavored AWS CDK.
I have a lambda function with an attached EFS. To use EFS, I am required to put the lambda function inside a VPC. The problem is, I also want this lambda function to retrieve files from a particular S3 bucket (in the same region). I am getting Timeout errors when doing the retrieval, and upon some research it seems that I need either a NAT Gateway (too expensive) or a VPC endpoint to allow access.
How can I build a VPC endpoint in CDK to allow my lambda function to talk to my S3 bucket?
Edit: The comment below from #gshpychka is correct - only the gateway_endpoint in the vpc definition is required.
Here is what I came up with that seems to work after following the ideas in this guide.
You need to create both an S3 access point as well as a VPC Endpoint.
You make the VPC Endpoint when creating the VPC. This allows S3 buckets to be accessible from the VPC. You can later add a policy to restrict this access.
self.vpc = ec2.Vpc(
scope=self,
id="VPC",
vpc_name="my_VPC",
gateway_endpoints={
"s3": ec2.GatewayVpcEndpointOptions(
service=ec2.GatewayVpcEndpointAwsService.S3
)
},
nat_gateways=0,
)
You later create an S3 access point after creating the S3 bucket. This allows access to the bucket.
self.bucket_access = s3.CfnAccessPoint(
scope=self,
id="s3_access",
bucket=self.my_bucket.bucket_name,
name="bucket-access-point",
vpc_configuration=s3.CfnAccessPoint.VpcConfigurationProperty(
vpc_id=self.vpc.vpc_id
),
)
export class YourStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = ec2.Vpc.fromLookup(this, 'vpc', { isDefault: true });
const s3BucketAcessPoint = vpc.addGatewayEndpoint('s3Endpoint', {
service: ec2.GatewayVpcEndpointAwsService.S3,
});
s3BucketAcessPoint.addToPolicy(
new iam.PolicyStatement({
principals: [new iam.AnyPrincipal()],
actions: ['s3:*'],
resources: ['*'],
}),
);
}
}

Query AWS or OU in master account with CDK from another account?

Busy building a AWS CDK stack on cdk-pipelines, and during one of my stages in the pipeline, I want to query the OU in the AWS Org to get a list of accounts ID's to run certain actions against in the stage.
Looking at the AwsCustomResource construct, something like this
const OrgARN = 'arn:aws:organizations::123fake321:organization/o-onetwoabc'
const awsCustom1 = new cr.AwsCustomResource(this, 'OUAccounts', {
onUpdate: {
service: 'Organizations',
action: 'describeOrganization',
physicalResourceId: cr.PhysicalResourceId.of('VerificationToken')
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({resources: [OrgARN]})
});
But it does not work for me, when I do a cdk deploy I get the following :
Failed to create resource. Inaccessible host: `organizations.eu-central-1.amazonaws.com'. This service may not be available in the `eu-central-1' region.
Anyone have any idea?
AWS Organization is a global service.
DescribeOrganizationalUnit
This operation can be called only from the organization's management account or by a member account that is a delegated administrator for an AWS service.
ListAccounts
This operation can be called only from the organization's management account or by a member account that is a delegated administrator for an AWS service.

AWS CDK - How to pass Access Key Secret and Secret Key Id as Env Param to Container

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.

Is there a way to specify cloudformation stack deletion policy using AWS CDK?

I have created a VPC using CDK, and now have a need to change the number of subnets and NAT gateways without destroying the (now in production) ec2 instances in it or the (now in production) EIPs that are whitelisted in other systems.
I tried this using CDK and it tried to destroy the VPC and re-provision everything, which is undesirable in production.
Since it is apparently not possible to do what I want by changing the stack definition, I need to get rid of the stack, without removing the VPC.
https://docs.aws.amazon.com/cdk/api/latest/docs/#aws-cdk_core.RemovalPolicy.html
I found this document here about a RemovalPoliy enum in CDK, but there is nothing saying where that can be applied. Google searches resulted in only references to S3 and RDS, even though this enum is in CDK core. I tried applying it to the following CDK resources: stack, vpc, security group, etc, but none of those constructs accept the removalPolicy parameter.
eg:
const vpc = new ec2.Vpc(this, 'PRODVPC', {
maxAzs: 2,
removalPolicy: cdk.RemovalPolicy.RETAIN
});
new VPCPRODStack(app, 'RDVPCPRODStack', {
env: {region: 'ca-central-1', account: '12345'},
removalPolicy: cdk.RemovalPolicy.RETAIN
});
What am I missing? Is it even possible to set a cloudformation removal policy using CDK at the stack level?
I think the one you're requesting for is https://github.com/aws/aws-cdk-rfcs/issues/72
For now
You can define a custom resource backed by lambda which calls cloudformation set-stack-policy api and updates policy.
You have https://docs.aws.amazon.com/cdk/api/latest/docs/#aws-cdk_core.CfnResource.html#apply-wbr-removal-wbr-policypolicy-options which you can use to applyRemovalPolicy i.e to either destroy or retain.
ex: If I want to apply destroy policy to all my cfnresources
import { CfnResource, IAspect, IConstruct, RemovalPolicy } from '#aws-cdk/core';
apply_destroy_removal_policy_aspect
export class ApplyDestroyRemovalPolicyAspect implements IAspect {
public visit(construct: IConstruct): void {
if (CfnResource.isCfnResource(construct)) {
construct.applyRemovalPolicy(RemovalPolicy.DESTROY);
}
}
}
and apply it on all the resources by a stack
const destroyResourcesAspect = new ApplyDestroyRemovalPolicyAspect();
core.Aspects.of(safetyStack).add(destroyResourcesAspect));
This I've seen lot of people using to ensure it destroys any resources created as part of Personal Stacks in CDK.