CDK deployment stuck on the CertificateManager step - amazon-web-services

I've recently started to get into AWS CloudFormation, specifically CDK.
I first added a domain manually from the Management Console to Route 53. Then, I copied the NS records that AWS picked to my domain provider. I've also made a certificate on the ACM using the DNS-recommended verification. It took AWS a while to issue it, as my domain provider mentioned that the DNS updates happen at fixed times every day (4 times a day), so the Route 53 zone NS records were not pointed out yet. The next day, the certificate was issued.
I then decided to recreate everything, but this time using CDK only. It worked fine the first time I deployed it, but then I realized I hadn't removed the older entities of my domain, and as a result, I had two different certificates and Route 53 zones of my domain. I deleted the manually created entities and my stack so I could redeploy it.
The problem is, this time, the CDK deployment was stuck on the CertificateManager part:
[█████████████████████████████████████████████·············] (14/18)
5:14:03 PM | CREATE_IN_PROGRESS | AWS::CloudFormation::Stack | TestApiStack
5:15:30 PM | CREATE_IN_PROGRESS | AWS::CertificateManager::Certificate | TestApiCertificate
Code:
const domainName = 'test.com';
// Define Route53
const zone = new cdk.aws_route53.HostedZone(this, 'TestApiHostedZone', {
zoneName: domainName,
});
// Define certificate manager
const certificate = new cdk.aws_certificatemanager.Certificate(this, 'TestApiCertificate', {
domainName: domainName,
validation: cdk.aws_certificatemanager.CertificateValidation.fromDns(zone),
});
I assume that AWS waits for the certificate to be verified, while this will never happen due to the new random NS records of the freshly generated Route 53 zone.
What is the best way to approach this problem, assuming this is the case? Is it to edit the NS records to those my domain provider already points? Or create the Route 53 zone manually from the Console, point to the new NS server from my domain provider, and then use the existing domain (cdk.aws_route53.HostedZone.fromLookup)?

Related

API Gateway with DNS record in another account

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.

How to add domain alias to existing CloudFront distribution using AWS CDK

I just deployed a CloudFormation solutions from the AWS Solutions. The solutions included a new CloudFront distribution. My challenge is that I want to add a custom domain mysite.example.com to the dxxxxxx.cloudfront.net distribution. I already created an alias and certificate using Certificate Manager. My question is how do I add a new domain to the existing CloudFront.
I understand that we can import an existing distribution using Distribution.fromDistributionAttributes.
for example
const distribution = cloudfront.Distribution.fromDistributionAttributes(this, 'ImportedDist', {
domainName: 'd111111abcdef8.cloudfront.net',
distributionId: '012345ABCDEF',
});
Let's say I have the alias domain name and certificate ARN ready to use.
const domainName = 'mysite.example.com';
const certificateArn = 'arn:aws:acm:us-east-1: 123456789012:certificate/abcdefgh-1234-5678-9012-abcdefghujkl';
Where do I go from here?
Add your domain and certificate by updating your "AWS solutions" CDK app. CDK apps are designed be modified and redeployed.
The Distribution construct accepts the certificate?:ICertificate and domainNames?: string[] as props to the constructor.
Instances also expose a addBehavior(pathPattern, origin, behaviorOptions?), which seems handy.
If the app is in production, be mindful that updates sometimes result in resource replacement or interruption.
The CloudFormation docs note the update behaviour for each service property. In the happy case you will see Update requires: No interruption. Run the cdk diff command to preview the changes
CloudFormation will make to your resources.
What about cloudfront.Distribution.fromDistributionAttributes? Many CDK classes have static from... methods
to get a reference to an existing AWS resource. These methods are handy (or even necessary) when resources are shared between apps, but should be used only when you cannot modify the original CDK construct.

AWS Route53 + S3 static website gives an error: alias target name does not lie within the target zone

I have registered a domain using Route53 and created a S3 bucket for my website.
Assume the following:
Route53 hosted zone is: domain.com
S3 bucket name is: staging.domain.com
Using the Route53 console I then attempted to create new record to point to my S3 bucket with the following settings:
Record name: staging.domain.com
Value/Route traffic to: Alias to S3 website endpoint
Region: (from drop-down) Africa(Cape Town)[af-south-1]
Choose S3 bucket: (from drop-down) s3-website.af-south-1.amazonaws.com (staging.domain.com)
Record type: A
After clicking on create records I am greeted with the following error:
**Error occurred
Bad request.**
(InvalidChangeBatch 400: Tried to create an alias that targets s3-website.af-south-1.amazonaws.com., type A in zone Z11KHD8FBVPUYU, but the alias target name does not lie within the target zone)
In my mind the alias target is supposed to be staging.domain.com.s3-website.af-south-1.amazonaws.com
not s3-website.af-south-1.amazonaws.com
For completeness sake I have 2 other A records listed on this domain:
dev.domain.com -> Pointing to an EC2 instance (working)
test.domain.com -> Pointing to a CloudFront distribution (working)
Any idea why this is happening or how it can be corrected?
I think drop down you are getting is correct.
See this screenshot :
New Console UI has different feature. First you have to select as region then bucket selection.
Check if you are following same seeting then you should not get error. Also one more check whether S3 bucket is enable for website hosting or not.

Can I move an AWS Lambda site to a private domain?

I'm using Zappa to deploy a Flask app. It works (site). Obviously I'd like to not have it stuck behind the aws domain and put it on my personal domain.
Everything I'm searching keeps talking about hosting a Lambda site with S3 and API Gateway. Is there no way to just deploy my little app to a custom domain?
Edit
Following #mislav's answer, I was able to get my google domain working with AWS. But when I try to finish by running zappa certify I get an error about the domain existing:
raise error_class(parsed_response, operation_name)
botocore.errorfactory.BadRequestException: An error occurred
(BadRequestException) when calling the CreateDomainName operation: The
domain name you provided already exists.
My zappa_settings.json is
{
"dev": {
"app_function": "ping_app.app",
"aws_region": "us-west-1",
"profile_name": "Breuds",
"project_name": "breuds",
"runtime": "python3.6",
"s3_bucket": "zappa-ping-redshift",
"slim_handler": true,
"certificate_arn": "arn:aws:acm:us-east-1:010174774769:certificate/3a92c204-5788-42fc-bc65-74aaae8c1b3f",
"domain": "breuds.com"
}
}
I am beginning to think I am doing something convoluted on my domain side. I'm using Google Domains (since I have a custom domain for my email, just using that), but that seems to cause a headache trying to get AWS to talk to it.
It’s actually explained in zappa’s readme.
Deploying to a Domain With AWS Certificate Manager
Amazon provides their own free alternative to Let's Encrypt called AWS Certificate Manager (ACM). To use this service with Zappa:
Verify your domain in the AWS Certificate Manager console.
In the console, select the N. Virginia (us-east-1) region and request a certificate for your domain or subdomain (sub.yourdomain.tld), or request a wildcard domain (*.yourdomain.tld).
Copy the entire ARN of that certificate and place it in the Zappa setting certificate_arn.
Set your desired domain in the domain setting.
Call $ zappa certify to create and associate the API Gateway distribution using that certificate.
There are also instructions to use your existing certificate etc.
Don’t let it fool you, the title of the section makes it sound like it’s only about certificates, but there are detailed instructions regarding using you own domain.
Edit:
I’m including my own zappa settings for reference.
{
"common": {
"app_function": "app.__hug_wsgi__",
"aws_region": "eu-central-1",
"s3_bucket": "excuse-generator",
"profile_name": "mislavcimpersak",
"remote_env": "s3://excuse-generator/secrets.json",
"certificate_arn": "arn:aws:acm:us-east-1:500819636056:certificate/3edb9c1c-12c5-4601-89a9-dc42df840fa6"
},
"prod": {
"extends": "common",
"domain": "function.xkcd-excuse.com"
},
"dev": {
"extends": "common",
"debug": true,
"keep_warm": false,
"domain": "function-dev.xkcd-excuse.com"
}
}
Step by step guide for domain bought on namecheap.com and served through cloudflare.com
buy domain on namecheap.com/use existing
register new site on cloudflare.com/use existing
cloudflare will give you (most probably) two nameservers
enter under https://ap.www.namecheap.com/domains/domaincontrolpanel/xkcd-excuse.com/domain DNS - choose "Custom DNS" nameservers given on cloudflare
go to AWS ACM and request a new certificate (certificate must be created in us-east-1 region)
enter if necessary multiple subdomains (foo.example.com & foo-dev.example.com)
make note of ACM ARN given from AWS management console
in zappa_settings.json enter under certificate_arn your ARN
in zappa_settings.json under route53_enabled put false - this is a must
in zappa_settings.json under domain enter domain for each stage ie. foo.example.com and foo-dev.example.com
run zappa certify <stage_name>
it should say: "Created a new domain name with supplied certificate. Please note that it can take up to 40 minutes for this domain to be created and propagated through AWS, but it requires no further work on your part.
Certificate updated!"
go to CloudFlare DNS interface and enter
CNAME: foo - i3jtsjkdeu4wxo.cloudfront.net
CNAME: foo-dev - d2jtsjkdeu4wxo.cloudfront.net
wait for 40 minutes and check your domain(s), they should serve your Lambda function
You might not need API Gateway. It will depend on site functionality. You can host a static website using S3 with a custom domain. You can include client-side javascript(angular/react etc..) API Gateway is really for handling http requests and passing it on to defined resources(Lambda or others).
If your site will require backend capability then you have few options.
1) Build lambda functions and then use API Gateway(REST API) to interact with these functions.
yourhostname.com -> S3 -> API Gateway(aws hostname) -> lambda
2) Use AWS JS SDK to interact with your lambda functions directly.
yourhostname.com -> S3 -> AWS SDK -> lambda
3) Use API Gateway and then forwarding to Lambda or self hosted web server(flask node) using EC2 instance
yourhostname.com -> API Gateway(aws hostname) -> lambda
yourhostname.com -> self hosted web server
Here is a picture that might make it a bit clearer.
Correct me if I am wrong flask is used for backend development to build microservices. To host it you would use either build lambda functions or host it on your own instance.
https://andrich.blog/2017/02/12/first-steps-with-aws-lambda-zappa-flask-and-python/
Let me know if this helps.

troposphere to create alias A record when you have hosted zones with same name

funsetgroup = template.add_resource(RecordSet(
'funsetgroup',
HostedZoneName=Join('', [Ref(hostedzone), "."]),
Comment='Hosted Zone Name',
Type="A",
Name=Join('fun.', [Ref(hostedzone), "."]),
AliasTarget=AliasTarget(
HostedZoneId=GetAtt(myelb, "CanonicalHostedZoneNameID"),
DNSName=GetAtt(myelb, "DNSName"),
)))
In this troposphere code I am trying to create an Alias A record for my hosted zone say, example.com. But I have 2 hosted zones with the same name(hosted zone Id's will be different). Although I explicitly mention the hosted zone id cloud formation doesn't create the stack saying "duplicate hosted zone found". I also tried just mentioning the hosted zone id without the hostedzone name that fails as well.
Did you also try using HostedZoneId instead of HostedZoneName in the RecordSet? This should work - we created Alias records in different zones with the same name in Empire all the time, and our code there uses Cloudformation as well (with a shim similar to troposphere - so much so, we call it troposphere). You can see that here:
https://github.com/remind101/empire/blob/ac27a91f3a5131b18700c350ca510d1befed7a51/scheduler/cloudformation/template.go#L647
Doing something similar in troposphere should work fine - just use HostedZoneId both in the RecordSet and the AliasTarget objects.