{"id":18445696,"url":"https://github.com/voronenko/route53-letsencrypt-policy","last_synced_at":"2025-07-26T17:34:53.933Z","repository":{"id":145345740,"uuid":"189261498","full_name":"Voronenko/route53-letsencrypt-policy","owner":"Voronenko","description":"Approach to introduce letsencrypt wildcard certificates on aws with a bit limited credentials set.","archived":false,"fork":false,"pushed_at":"2019-05-29T18:30:22.000Z","size":23,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-15T01:14:56.418Z","etag":null,"topics":["acme-sh","aws","certbot","letsencrypt","wildcard-certificates"],"latest_commit_sha":null,"homepage":null,"language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Voronenko.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-29T16:28:24.000Z","updated_at":"2021-08-13T03:11:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"612e8649-c1f7-4531-ad61-a577a06757da","html_url":"https://github.com/Voronenko/route53-letsencrypt-policy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronenko%2Froute53-letsencrypt-policy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronenko%2Froute53-letsencrypt-policy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronenko%2Froute53-letsencrypt-policy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronenko%2Froute53-letsencrypt-policy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Voronenko","download_url":"https://codeload.github.com/Voronenko/route53-letsencrypt-policy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248986315,"owners_count":21194025,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acme-sh","aws","certbot","letsencrypt","wildcard-certificates"],"created_at":"2024-11-06T07:06:55.992Z","updated_at":"2025-04-15T01:15:00.376Z","avatar_url":"https://github.com/Voronenko.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Wildcard certificates from letsencrypt on aws cloud\n\nLetsencrypt is nowadays very popular certificates authority.\n\nIt is standard de-facto for most of situations when you need green sealed certificate on your environment.\nNew version of the API (v2) provides very nice way to issue wildcard certificates using DNS validation.\n\nAlthough it is not recommended to put read/write dns credentials on a such environment, there might be\nexception that forces you to do so on a temporary basis.\n\nWorkaround below provides way to limit write scope of the credentials, when your domain is served by AWS Route53.\n\nLet's assume we want to create wildcard certificate for our staging environment\n\n`*.staging.yourdomain.com`\n\n## Step A - zone preparation for acme checks\n\nCreate another public hosted zone for the domain `_acme-challenge.staging.yourdomain.com`\nI know, you are probably thinking \"but that's not a domain!\", but in the relevant sense,\nit actually is still a domain, just four level one.\n\nRoute 53 will assign 4 new nameservers to this new hosted zone. Make a note of those servers.\n\nYou will get something like\n\n```txt\nns-1918.awsdns-47.co.uk.\nns-1211.awsdns-23.org.\nns-626.awsdns-14.net.\nns-126.awsdns-15.com.\n```\n\n## Step B -  delegating acme actions to dedicated acme hosted zone\n\nReturn to your original hosted zone, and create a record for `_acme-challenge.staging.yourdomain.com` of type NS. As a value you will use to create this record use those 4 nameservers that Route 53 assigned to the new hosted zone, one per line.\n\nImportant! Do not change any of the existing NS records in either of the zones.\n\nWhat we have implemented by this step is called a delegation -  i.e. you are delegating authority for `staging.yourdomain.com` subdomain to a different hosted zone, which you will notice was automatically assigned a completely different set of 4 Route 53 servers from those that handle your parent domain.\n\nYou can now create a new record in the root of the new hosted zone, and when you do a DNS query for _acme-challenge.staging.yourdomain.com, the answer returned will be the answer from the new hosted zone.\n\nNow, you can give your script permission only to modify records in the new zone, and it will be unable to modify anything in the parent zone, because you gave it no permissions, there. So we approaching to the next step\n\n## STEP C - creating policy with limited access \n\nNow we can prepare AWS policy with fine grained permissions\n\n```json\n{\n\"Version\": \"2012-10-17\",\n\"Statement\": [\n    {\n        \"Effect\": \"Allow\",\n        \"Action\": [\n            \"route53:ChangeResourceRecordSets\"\n        ],\n        \"Resource\": [\n            \"arn:aws:route53:::hostedzone/{HOSTEDZONEID}\"\n        ]\n    },\n    {\n        \"Effect\": \"Allow\",\n        \"Action\": [\n            \"route53:ListHostedZonesByName\"\n        ],\n        \"Resource\": [\n            \"*\"\n        ]\n    }\n]}\n```\n\nand link this policy to appropriate IAM user, and get his access credentials.\n\nCorresponding terraform script might be as following (always check to most recent documentation \nhttps://certbot-dns-route53.readthedocs.io/en/stable/ )\n\n```tf\n\ndata \"aws_route53_zone\" \"acme\" {\n    name = \"_acme-challenge.staging.yourdomain.com.\"\n}\n\nresource \"aws_iam_policy\" \"allow_writing_acme_zone\" {\n  # ... other configuration ...\n  name = \"allow_writing_staging_acme_zone\"\n  policy = \"${data.aws_iam_policy_document.allow_writing_acme_zone.json}\"\n}\n\ndata \"aws_iam_policy_document\" \"allow_writing_acme_zone\" {\n\n  statement {\n    actions   = [\"route53:ChangeResourceRecordSets\"]\n    resources = [\"arn:aws:route53:::hostedzone/${data.aws_route53_zone.acme.zone_id}\"]\n    effect = \"Allow\"\n  }\n\n  statement {\n    actions   = [\"route53:GetHostedZone\"]\n    resources = [\"arn:aws:route53:::hostedzone/${data.aws_route53_zone.acme.zone_id}\"]\n    effect = \"Allow\"\n  }\n\n  statement {\n    actions   = [\"route53:ListResourceRecordSets\"]\n    resources = [\"arn:aws:route53:::hostedzone/${data.aws_route53_zone.acme.zone_id}\"]\n    effect = \"Allow\"\n  }\n\n  statement {\n    actions   = [\"route53:ChangeResourceRecordSets\"]\n    resources = [\"arn:aws:route53:::hostedzone/${data.aws_route53_zone.acme.zone_id}\"]\n    effect = \"Allow\"\n  }\n\n\n  statement {\n    actions   = [\"route53:GetChange\"]\n    resources = [\"*\"]\n    effect = \"Allow\"\n  }\n\n  statement {\n    actions   = [\"route53:ListHostedZones\"]\n    resources = [\"*\"]\n    effect = \"Allow\"\n  }\n    \n}\n\nresource \"aws_iam_user\" \"acme-writer\" {\n  name = \"acme-domain-writer\"\n}\n\nresource \"aws_iam_user_policy_attachment\" \"acme-writer-policy\" {\n  user       = \"${aws_iam_user.acme-writer.name}\"\n  policy_arn = \"${aws_iam_policy.allow_writing_acme_zone.arn}\"\n}\n\n\n```\n\n## STEP D - setting up aws credentials (for certbot example)\n\nSetup AWS credentials\nWe now need to put the AWS credentials on the server so the plugin can use them. \nI run all my certbot commands out of the default user’s home folder, \nyour setup might be different.\n\nIn the home folder create an .aws folder and inside that create a text file with the name \n`credentials` with the following contents.\n\n```txt\n[default]\naws_access_key_id=XXXXXX\naws_secret_access_key=XXXX/XXXXX\n```\nReplace the placeholders with the access key and secret access key that you just saved from AWS and fill them in.\n\nOnce created, check it was configured properly\n\n```sh\n\naws sts get-caller-identity\n{\n    \"Account\": \"XXX\", \n    \"UserId\": \"YYYYYYYYYYYYYY\", \n    \"Arn\": \"arn:aws:iam::XXX:user/acme-domain-writer\"\n}\n\n\n```\n\nNext step would be, naturally, generating the certificate\n\n## Example - generating certificate with certbot\n\nInstall tool of your choice, for example, classic `certbot` (examples below are given for ubuntu family)\n\n```sh\nsudo apt-get update\nsudo apt-get install software-properties-common\nsudo add-apt-repository universe\nsudo add-apt-repository ppa:certbot/certbot\nsudo apt-get update\nsudo apt-get install certbot python-certbot-nginx python3-certbot-dns-route53\n```\n\nor using their up-to-date script\n\n```sh\n\nwget https://dl.eff.org/certbot-auto\nchmod a+x ./certbot-auto\nsudo ./certbot-auto\n\n```\n\n\n```sh\n\ncertbot certonly -d staging.yourdomain.com -d *.staging.yourdomain.com --dns-route53 --logs-dir ~/letsencrypt/log/ --config-dir ~/letsencrypt/config/ --work-dir /home/username/letsencrypt/work/ -m git@voronenko.info --agree-tos --non-interactive --server https://acme-v02.api.letsencrypt.org/directory\n\n```\n\nIf you see output like below, you are done:\n\n```txt\nFound credentials in shared credentials file: ~/.aws/credentials\nPlugins selected: Authenticator dns-route53, Installer None\nStarting new HTTPS connection (1): acme-v02.api.letsencrypt.org\nObtaining a new certificate\nPerforming the following challenges:\ndns-01 challenge for staging.yourdomain.com\ndns-01 challenge for staging.yourdomain.com\nStarting new HTTPS connection (1): route53.amazonaws.com\nWaiting 10 seconds for DNS changes to propagate\nWaiting for verification...\nCleaning up challenges\nResetting dropped connection: route53.amazonaws.com\nResetting dropped connection: acme-v02.api.letsencrypt.org\n```\n\nNow let's ensure, that our domain will be prolonged\n\n```sh\ncertbot renew  --logs-dir ~/letsencrypt/log/ --config-dir ~/letsencrypt/config/ --work-dir ~/letsencrypt/work/\n```\n\nIf you want to setup cron, and you have specified custom log, config, work dirs - make\nsure to specify full path in crontab.\n\n```sh\ncrontab -e\n\n43 6 * * * certbot renew  --post-hook \"service nginx restart\"  --logs-dir /home/user/letsencrypt/log/ --config-dir /home/user/letsencrypt/config/ --work-dir /home/user/letsencrypt/work/\n```\n\nYour certificates will be located under config dir\n\n\n```sh\nls config/live/staging.yourdomain.com/\nREADME  cert.pem  chain.pem  fullchain.pem  privkey.pem\n\n```\n\n## Example - generating certificate with acme.sh\n\nBut my own preference is pure shell acme.sh\n\nAcme.sh  https://github.com/Neilpang/acme.sh - it is another extra cool tool written purely in shell.\nIt supports number of dns providers in form of shell(!) plugins https://github.com/Neilpang/acme.sh/tree/master/dnsapi\n\nEach plugin has comprehensive documentation on configuring  https://github.com/Neilpang/acme.sh/wiki/dnsapi\n\nFor Route53, you will need to export your credentials  \n\n```sh\nexport  AWS_ACCESS_KEY_ID=XXXXXXXXXX\nexport  AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXX\n```\n\nWith acme.sh\n\n```sh\n\nacme.sh --issue --dns dns_aws -d staging.yourdomain.com -d \"*.staging.yourdomain.com\"\n\n[середа, 29 травня 2019 19:58:01 +0200] Multi domain='DNS:staging.yourdomain.com,DNS:*.staging.yourdomain.com'\n[середа, 29 травня 2019 19:58:01 +0200] Getting domain auth token for each domain\n[середа, 29 травня 2019 19:58:03 +0200] Getting webroot for domain='staging.yourdomain.com'\n[середа, 29 травня 2019 19:58:03 +0200] Getting webroot for domain='*.staging.yourdomain.com'\n[середа, 29 травня 2019 19:58:03 +0200] Adding txt value: GvaPZqTUMceQVt8yFl4IkW0ZqJ22-680Du-7BLfTcVc for domain:  _acme-challenge.staging.yourdomain.com\n[середа, 29 травня 2019 19:58:04 +0200] Getting existing records for _acme-challenge.staging.yourdomain.com\n[середа, 29 травня 2019 19:58:05 +0200] TXT record updated successfully.\n[середа, 29 травня 2019 19:58:05 +0200] The txt record is added: Success.\n[середа, 29 травня 2019 19:58:05 +0200] Adding txt value: txy1JYnOGAWpT1rQ8_U99Ird-Pq-P0iWYzixGPPQIvw for domain:  _acme-challenge.staging.yourdomain.com\n[середа, 29 травня 2019 19:58:06 +0200] Getting existing records for _acme-challenge.staging.yourdomain.com\n[середа, 29 травня 2019 19:58:07 +0200] TXT record updated successfully.\n[середа, 29 травня 2019 19:58:07 +0200] The txt record is added: Success.\n[середа, 29 травня 2019 19:58:07 +0200] Let's check each dns records now. Sleep 20 seconds first.\n...\n[середа, 29 травня 2019 19:58:28 +0200] All success, let's return\n[середа, 29 травня 2019 19:58:28 +0200] Verifying: staging.yourdomain.com\n[середа, 29 травня 2019 19:58:31 +0200] Success\n[середа, 29 травня 2019 19:58:31 +0200] Verifying: *.staging.yourdomain.com\n[середа, 29 травня 2019 19:58:34 +0200] Success\n...\n[середа, 29 травня 2019 19:58:39 +0200] Your cert is in  /home/slavko/.acme.sh/staging.yourdomain.com/staging.yourdomain.com.cer \n[середа, 29 травня 2019 19:58:39 +0200] Your cert key is in  /home/slavko/.acme.sh/staging.yourdomain.com/staging.yourdomain.com.key \n[середа, 29 травня 2019 19:58:39 +0200] The intermediate CA cert is in  /home/slavko/.acme.sh/staging.yourdomain.com/ca.cer \n[середа, 29 травня 2019 19:58:39 +0200] And the full chain certs is there:  /home/slavko/.acme.sh/staging.yourdomain.com/fullchain.cer \n\n\n```\n\nRenewing is also straightforward - just create cron similar to \n\n```txt\n0 0 * * * \"/home/user/.acme.sh\"/acme.sh --cron --home \"/home/user/.acme.sh\" \u003e /dev/null\n```\n\n## Finally\nUse certificates in your webserver\n\n## Code\nTerraform snippets might be found at https://github.com/Voronenko/route53-letsencrypt-policy/ ;\nAnsible role assisting you with install on target server can be found here:  https://github.com/softasap/sa-acme-sh\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoronenko%2Froute53-letsencrypt-policy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvoronenko%2Froute53-letsencrypt-policy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoronenko%2Froute53-letsencrypt-policy/lists"}