Set cfn deletion policy for RDS instance using AWS CDK - amazon-web-services

I have a CDK stack that includes an RDS instance. I want to make sure the DB instance never gets deleted. I can't figure out how to set the deletion policy via CDK.
It looks like I can set deletion protection like this:
this.database = new rds.DatabaseInstanceFromSnapshot(this, 'backendAPIDatabase', {
snapshotIdentifier: this.props.snapshotIdentifier,
instanceIdentifier: this.props.environmentName,
engine: rds.DatabaseInstanceEngine.POSTGRES,
instanceClass: this.props.databaseInstanceSize,
vpc: this.vpc,
multiAz: this.props.databaseMultiAz,
enablePerformanceInsights: true,
parameterGroup,
allocatedStorage: this.props.allocatedDatabaseStorage
});
(this.database.node.defaultChild as rds.CfnDBInstance).deletionProtection = true;
But I can't figure out how to apply a deletion policy as a second backup.

You can set it by using removalPolicy property. You should also set deletion protection via the constructor as shown below.
this.database = new rds.DatabaseInstanceFromSnapshot(this, 'backendAPIDatabase', {
...,
deletionProtection: true,
removalPolicy: cdk.RemovalPolicy.RETAIN
});

Related

Give KMS access permission to Proxy

I want to give the decrypt permission for proxy.
This proxy uses the a key in secret manager as username/password store.
and it uses key in KMS as Encryption key
This code makes role to acceess the secret manager automatically, but not KMS for decryption.
const dbProxy = new rds.DatabaseProxy(this, 'Proxy', {
proxyTarget: rds.ProxyTarget.fromCluster(dbCluster),
dbProxyName: `vr-${targetEnv}-rds-proxy`,
secrets: [dbCluster.secret!],// it makes the role to access the secret manager automatically.
securityGroups:[proxySecurityGroup],
requireTLS:false,
iamAuth:false,
vpc,
});
const kmsPolicy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["kms:Decrypt"],
resources: ["*"],
})
dbProxy.addToRolePolicy(kmsPolicy)
However, there comes this error.
Property 'addToRolePolicy' does not exist on type 'DatabaseProxy'
I want to fetch the default created role and add this kmsPolicy
I tried this too.
const mainEncSecretArn = kms.Key.fromKeyArn(this, "kms-key", "arn:aws:kms:ap-northeast-1:665852837485:key/bf3cf318-1376-44de-a014-181074f4899d");
mainEncSecretArn.grantDecrypt(dbProxy);
The error is
Argument of type 'DatabaseProxy' is not assignable to parameter of type 'IGrantable'.
Property 'grantPrincipal' is missing in type 'DatabaseProxy' but required in type 'IGrantable'.
After working today with AWS Support, i was told (verbatim):
I would suggest explicitly defining the KMS permissions in a IAM policy document rather than using the "fromAliasName" Function.
I have provided some references that should help you write and attach the necessary KMS permission policy that is equivalent to the one that would have been created by the "fromAliasName" Function
And here is manual version of what support is saying cannot be done with cdk yet:
// get your kms key arn:
const kmsKeyArn =
'arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-1234567890';
// you won't be able to use the default proxy role :(
const proxyRole = new iam.Role(this, 'proxy-role', {
assumedBy: new iam.ServicePrincipal('rds.amazonaws.com'),
});
proxyRole.attachInlinePolicy(
new iam.Policy(this, 'kms-proxy-decrypt-policy', {
statements: [
new iam.PolicyStatement({
// grant the permission:
actions: ['kms:Decrypt'],
// put your kms key arn here:
resources: [kmsKeyArn],
}),
],
})
);
const proxy = new rds.DatabaseProxy(this, 'db-proxy', {
// there is other stuff here:
...
// ..but assign the role here:
role: proxyRole,
})

How to create an Athena stack and consume Glue Data catalog?

I have to create an athena template in cloud formation, the task is to replicate the next Terraform script using CF:
resource "aws_athena_workgroup" "sample_athena_wg" {
name = "sample_athena_wg"
}
resource "aws_athena_database" "sample_athena_database" {
name = "sample_athena_database"
bucket = "sample_bucket_id"
}
resource "aws_athena_named_query" "test_query" {
name = "Test"
workgroup = aws_athena_workgroup.sample_athena_wg.id
database = aws_athena_database.sample_athena_database.name
query = "SELECT * FROM ${aws_athena_database.sample_athena_database.name} limit 10;"
}
The problem is that there is no such a resource in CF called "AWS::ATHENA::DATABASE" or something like that, and I don't really know what the terraform resource "aws_athena_database" is creating behind the scenes. When I deploy the Terraform script, it seems like this creates a glue database, but I do know what else this creates.
I found that Terraform creates an Athena Workgroup and GLue database behind the scenes when you try to create a aws_athena_database. I could replicate this resource in CF like this:
AthenaWorkgroup:
Type: AWS::Athena::WorkGroup
Properties:
Name: ....
State: ENABLEDr
WorkGroupConfiguration:
BytesScannedCutoffPerQuery: ...
EnforceWorkGroupConfiguration: ...
PublishCloudWatchMetricsEnabled: ...
ResultConfiguration:
OutputLocation: !Ref S3LocationPath
GlueDatabase:
Type: AWS::Glue::Database
Properties:
DatabaseInput:
Name: ...

AWS CDK: enabling access logging for classical load balancer

We are using Classical load balancer in our Infra deployed via CDK. For deploying Load balancer we are using level 2 Constructs. The code is like this:
const lb = new elb.LoadBalancer(this, 'LB', {
vpc: vpcRef,
internetFacing: true,
healthCheck: {
port: 80
},
});
lb.addListener({
externalPort: 80,
});
}
We are not able to find any property using which we can enable the access logging. Someone suggested me to use AccessLoggingPolicyProperty. I checked that and found that this property can be used with Level 1 constructs only. Can some please guide me on how we can enable the access logs via CDK on a classical load balancer using Level 2 constructs.
As per the documentation you need S3 bucket with right permissions configured. With that you can follow aws-cdk documentation on how to get access to L1 Construct.
It is going to look roughly like the following code
const lbLogs = new Bucket(this, 'LB Logs');
const elbAccountId = 'TODO: find right account for you region in docs';
lbLogs.grantPut(new AccountPrincipal(elbAccountId));
lbLogs.grantPut(
new ServicePrincipal('delivery.logs.amazonaws.com', {
conditions: {
StringEquals: {
's3:x-amz-acl': 'bucket-owner-full-control',
},
},
})
);
lbLogs.grantRead(new ServicePrincipal('delivery.logs.amazonaws.com'));
const cfnLoadBalancer = lb.node.defaultChild as CfnLoadBalancer;
cfnLoadBalancer.accessLoggingPolicy = {
enabled: true,
s3BucketName: lbLogs.bucketName,
};

AWS CDK - Possible to access individual (JSON) value within a Secrets Manager secret when specifying secrets for a container?

I'm trying to put together a relatively simple stack on AWS CDK that involves an ApplicationLoadBalancedFargateService from aws-ecs-patterns.
My problem involves secrets. I have a secret in Secrets Manager that has several key/values (I think technically it's stored as a JSON doc, but AWS provides a key/val interface), and I need to pass them to my containers individually. I do this currently in an equivalent non-cdk (made in the console) stack by simply specifying the key, like this: arn:aws:secretsmanager:us-west-2:[acct]:secret/name-??????:KEY::, where `KEY is the secret key, and the correct value is inserted into the container as an env var.
When I try to do that with CDK, I get an error when I cdk synth:
`secretCompleteArn` does not appear to be complete; missing 6-character suffix
If I remove the last bit (:KEY::), it successfully synths, but my container isn't actually getting what I want.
This is how I'm trying to use it in my cdk (typescript) code:
new ApplicationLoadBalancedFargateService(this, 'Service', {
...
taskImageOptions: {
image: containerImage, // defined elsewhere
...
secrets: {
'DB_DATABASE': ecs.Secret.fromSecretsManager(
Secret.fromSecretCompleteArn(this, 'secret-DB_DATABASE',
'arn:aws:secretsmanager:us-west-2:[acct]:secret:secret/name-??????:KEY::')),
//there's really a few more, pulling keys from the same secret. Omitting for brevity
},
},
});
Is there a way to to make this work? Or do I need to change the way I store/use my secrets?
This is how you pass a specific key as environment variable to your container:
const mySecret = secretsmanager.Secret.fromSecretCompleteArn('<your arn>');
taskDefinition.addContainer('MyContainer', {
// ... other props ...
secrets: {
SECRET_KEY: ecs.Secret.fromSecretsManager(mySecret, 'specificKey'),
},
});
or with the ApplicationLoadBalancedFargateService:
new ApplicationLoadBalancedFargateService(this, 'Service', {
...
taskImageOptions: {
image: containerImage, // defined elsewhere
...
secrets: {
'DB_DATABASE': ecs.Secret.fromSecretsManager(mySecret, 'specificKey'),
},
},
});

aws-cdk LambdaRestApi: The final policy size is bigger than the limit

Hi i have been trying many possibilities, but now i would need some help.
I am using aws-cdk to create architecture by code and so far things have going well. Now i am running into this issue:
The final policy size is bigger than the limit (20480)
In understand what it means, but i have no idea how to solve it.
I am creating a lambdafunction to handle all requests:
const router = new lambda.Function(this, apiName + '-handler-temp', {
runtime: LambdaRuntime, // execution environment
code: lambda.Code.fromAsset('bin/lambda'), // code loaded from "lambda" directory
handler: 'index.handler', // file is "index", function is "handler"
vpc: vpc,
environment: {
DB_HOST: props?.rdsEndpoint as string,
DB_USER: props?.rdsDbUser as string,
DB_PASS: props?.rdsDBPass as string,
DB_PORT: props?.rdsPort as string,
DB_DIALECT: props?.rdsDbSchema as string,
DB_DATABASE: props?.rdsDBName as string,
},
layers: [layer],
timeout: Duration.seconds(30),
memorySize: 1024,
})
and the LambdaRestApi is defined like this:
const api = new LambdaRestApi(this, apiName, {
handler: router,
proxy: false,
cloudWatchRole: false,
description: 'API for Backend',
deployOptions: {
stageName: 'prod',
},
domainName: domainProperties,
})
I am creating Endpoints where i am using 23 times addMethod.
e.g.
const user = api.root.addResource('user')
user.addMethod(HttpMethod.POST)
user.addMethod(HttpMethod.GET)
user.addMethod(HttpMethod.PATCH)
since only one lambda is used to be invoked from apigateway, i am curious, how i can get control of only one policy to be used for lambda execution and it is not creating a new one every time.
I also tried to add property
role: role to the lambda function with this role definition:
const role = new Role(this, apiName + 'ApiGWPermissions', {
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
})
role.addToPolicy(
new PolicyStatement({
resources: ['*'],
actions: ['lambda:InvokeFunction'],
})
)
but then i am running into different errors.
Has someone solved this riddle?
Cheers
As suggested in the CDK issue which Ian Walters mentioned, stripping the generated method permissions solved this for me. I'm using .Net but I'd expect that the approach should work for all language implementations. This function removes the permissions:
public void StripMethodPermissions(ConstructNode node) {
foreach (var child in node.Children) {
if (child is Amazon.CDK.AWS.APIGateway.Method) {
var method = ((Amazon.CDK.AWS.APIGateway.Method)child);
var permissions = method.Node.Children.Where(c => c is Amazon.CDK.AWS.Lambda.CfnPermission);
foreach (var permission in permissions) {
child.Node.TryRemoveChild(permission.Node.Id);
}
}
if (child.Node.Children.Length > 0) StripMethodPermissions(child.Node);
}
}
I'm using the technique like this:
var api = new RestApi(this, "MyRestApi", new RestApiProps {
RestApiName = "MyRestApiV1"
});
var handlerLambda = new Function(this, "RequestHandler", new FunctionProps {
Runtime = Runtime.DOTNET_CORE_3_1,
...
});
// Add resources and methods which use the handlerLambda here
// Remove all generated permissions
StripMethodPermissions(api.Root.Node);
// Add a single invoke permission for the lambda
handlerLambda.GrantInvoke(new ServicePrincipal("apigateway.amazonaws.com"));
Thanks to nija-at for showing the way
#Elliveny has the correct answer. Here is a code snitbit for Python which does the same thing (as I cannot post formatted code in comments):
from aws_cdk import (
aws_lambda as _lambda,
aws_events as events,
aws_iam as iam,
core,
)
for child in self.node.children:
if isinstance(child, events.Rule):
for eventChild in child.node.children:
if isinstance(eventChild, _lambda.CfnPermission):
child.node.try_remove_child(eventChild.node.id)
Remember that if you do this, you still need to grant invoke on your lambda for the "events.amazonaws.com" ServicePrincipal. Something like:
my_lambda.add_permission(
"RuleInvoke",
principal=iam.ServicePrincipal("events.amazonaws.com"),
action="lambda:InvokeFunction",
source_arn=f"arn:aws:events:{core.Aws.REGION}:{core.Aws.ACCOUNT_ID}:rule/your-rule-name-here*",
)
I hit a similar issue. There is a CDK issue that might help resolve it if addressed,
https://github.com/aws/aws-cdk/issues/9327
Its also worth noting that by default lambda integrations have allowTestInvoke set to true, which pretty much is going to double the policy document size.
I'm not sure if you can alter the integration options for the lambda with LambdaRestApi though, I'm using RestApi directly.
A short term fix might be to use RestApi rather than LambdaRestApi and create the lambda integration directly with the allowTestInvoke option set to false.
The other thing I did that helped was to just create more than one lambda that worked the same way, but got attached to different routes (e.g. same code, permissions etc, just different logical id) to also reduce the policy document size a bit.
I'm a bit pressed for development time hence the work-arounds. Personally, I think the right solution would be to fix it in the CDK and propose a PR such that LambdaRestApi just did what the user would expect, wild-card permission for the lambda for all
You can increase the memory size of lambda by doing this
Type: AWS::Serverless::Function
Properties:
CodeUri: src/promoCodes/
Role: !GetAtt FunctionRole.Arn
Handler: promoCodesListCsvPDF.promoCodesListCsvPDF
Policies:
- AWSLambdaExecute # Managed Policy
- AWSLambdaBasicExecutionRole # Managed Policy
MemorySize: 512
Layers:
- !Ref NodeDependenciesLayer
Events:
promoCodesListCsvPDFEvent:
Type: Api
Properties:
Path: /api/promo-codes/csv-pdf
Method: POST