{"id":16414638,"url":"https://github.com/mattlqx/cookbook-letsencryptaws","last_synced_at":"2025-10-26T19:31:20.134Z","repository":{"id":28975029,"uuid":"117588759","full_name":"mattlqx/cookbook-letsencryptaws","owner":"mattlqx","description":"Chef cookbook for Let's Encrypt certificate request/retrieval with Route 53-hosted domains using S3","archived":false,"fork":false,"pushed_at":"2024-05-16T18:51:46.000Z","size":120,"stargazers_count":2,"open_issues_count":2,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-31T22:41:30.487Z","etag":null,"topics":["chef","cookbook","letsencrypt","route53"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mattlqx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-15T19:50:51.000Z","updated_at":"2022-03-28T21:46:25.000Z","dependencies_parsed_at":"2023-02-10T18:15:34.198Z","dependency_job_id":null,"html_url":"https://github.com/mattlqx/cookbook-letsencryptaws","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattlqx%2Fcookbook-letsencryptaws","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattlqx%2Fcookbook-letsencryptaws/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattlqx%2Fcookbook-letsencryptaws/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattlqx%2Fcookbook-letsencryptaws/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mattlqx","download_url":"https://codeload.github.com/mattlqx/cookbook-letsencryptaws/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238386474,"owners_count":19463375,"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":["chef","cookbook","letsencrypt","route53"],"created_at":"2024-10-11T06:54:41.348Z","updated_at":"2025-10-26T19:31:14.825Z","avatar_url":"https://github.com/mattlqx.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# letsencryptaws Cookbook\n\nThis cookbook is for an implementation of SSL certificate generation and fetching via the Let's Encrypt certificate authority. Certificates are synced from local storage to S3, which is then used by nodes to retrieve the generated certificate. Authentication is done via DNS challenges and the offical Certbot plugin for Route 53.\n\nNodes do not need to be EC2 instances to retrieve or request certificates. All that is required is AWS credentials or profile to perform Route 53 and S3 operations.\n\n## Requirements\n\n- Python 3\n- certbot ACME client\n- Domain(s) hosted by AWS Route 53\n- S3 bucket for storing/retrieving certificate files\n- Properly stored AWS credentials (see https://github.com/aws/aws-sdk-ruby#configuration)\n\n### Platforms\n\nCertificate generation:\n- Ubuntu\n\nThe goal for certificate retrieval is to support Windows but for now, Ubuntu only.\n\n### Cookbooks\n\n- `remote_file_s3` - To grab certificates from S3\n- `pyenv` - For grabbing Python environment that certbot recipe uses to install `awscli` and `certbot`.\n\n## Usage\n\n### letsencryptaws::default\n\nSet the `certs` attribute as described below and then include this recipe in your cookbook or run_list.\n\n**NOTE** You should manually generate a default certificate (self-signed/fake CA) and place the key, certificate and CA certificate at the `\"/#{node['letsencryptaws']['sync_path']}/default-ssl\"` path in your S3 sync bucket. These will act as stand-ins for the real certificates/key until they are generated by `letsencryptaws::certbot` after they are first \"requested\" by a node. So in theory, your real certificates will take up to (chef interval + splay * 3) until they land on the requesting node.\n\nThe flow looks like this:\n- First Chef run on requesting node. Attribute (`['letsencryptaws']['certs']['example.com'] = []`) gets saved to server (probably created dynamically by a cookbook). Default cert/key gets saved to node from S3.\n- Chef runs on host with 'letsencryptaws::certbot' in its run_list. Requests certificate and uploads to S3.\n- Second Chef run on requesting node overwrites the previously saved default cert/key with real cert/key from S3.\n\nAny service that uses a certificate provided by this recipe should subscribe to one of the certificate file resources so that it can be reloaded when the certificate is renewed. For example:\n\n```\nservice 'nginx' do\n  action %i[start enable]\n  subscribes :restart, \"file[#{::File.join(node['letsencryptaws']['ssl_cert_dir'], 'example.com.crt')}]\", :delayed\nend\n```\n\n**FOR WILDCARD CERTIFICATES**, you should specify the CN as you'd like as `certs` key (e.g. `*.example.com`) however any `*` will be substituted with `star` in the filenames to prevent the need for escaping. So the filename you'd reference for the certificate would be `star.example.com.crt`.\n\n### letsencryptaws::certbot\n\nThis is meant to be run by a single host that manages fetching certificates based on a Chef server `search`. Make sure the instance profile or AWS access keys in the data bag is granted the following permissions on the domains in which you allow certificates to be requested by nodes:\n\n- `route53:ChangeResourceRecordSets`\n- `route53:ListHostedZonesByName`\n- `route53:GetChange`\n\nThe credentials will also require write access to the S3 bucket and path that you choose to sync to.\n\nIf you desire persistent storage on an EBS volume, use the `['letsencryptaws']['ebs_device']` to specify the path to the device. This will device will have an ext4 filesystem created on it if one does not already exist and be mounted at `['letsencryptaws']['config_dir']`. This is where certbot will store its configs and certificates. All operations take place locally at this path and at the end of the recipe gets synced to S3.\n\nCertbot operations use the `--expand` and `--cert-name` arguments to keep the certificates up-to-date with the requested names. This means the certificate will be renewed appropriately as nodes desire for the certificate name changes.\n\n### letsencryptaws::import_keystore\n\nThis recipe takes certificates and imports them into a Java keystore.\n\n## Attributes\n\n### letsencryptaws::default\n\nFor certificate retrieval, just specify what certificates you would like by common name\nof the certificate and an array of Subject Alternative Names for the cert. The certificate\nmay have additional SANs if other nodes request them for the same common name.\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003eKey\u003c/th\u003e\n    \u003cth\u003eType\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n    \u003cth\u003eDefault\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['certs']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003eHash\u003c/td\u003e\n    \u003ctd\u003ekeys are the common name, values are an array of strings that are the SANs for the cert. These all get merged together in the final certificate.\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e{}\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['ssl_cert_dir']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003epath where ssl certs will be downloaded to\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e/etc/ssl/certs\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['ssl_key_dir']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003epath where ssl private keys will be downloaded to\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e/etc/ssl/private\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['ssl_ca_dir']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003epath where ssl CA certificates will be downloaded to\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e/etc/ssl/certs\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n### letsencryptaws::import_keystore\n\nThis recipe is automatically included if the `import_keystore` hash is not empty.\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003eKey\u003c/th\u003e\n    \u003cth\u003eType\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n    \u003cth\u003eDefault\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['import_keystore']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003eHash\u003c/td\u003e\n    \u003ctd\u003ekeys are full paths to Java keystores, values are an array of primary names of certificates to add to the keystore\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e{}\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n### letsencryptaws::certbot\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003eKey\u003c/th\u003e\n    \u003cth\u003eType\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n    \u003cth\u003eDefault\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['config_dir']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003edir where all certbot configuration will be stored, including certs\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e/mnt/letsencrypt\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['certbot_version']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003eversion to enforce for certbot\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e1.18.0\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['certbot_dns_version']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003eversion to enforce for certbot-dns-route53\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['certbot_version']\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['data_bag']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003eName of data bag used for credentials storage\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003enil\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['data_bag_item']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003eName of item within data bag for credentials storage\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003enil\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['email']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003eEmail addressed used for certbot during generation\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003enobody@example.com\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['ebs_device']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003edevice of the ebs volume to mount on `config_dir` (only applies on ec2 instances)\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e/dev/xvdf\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['test_certs']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003eboolean\u003c/td\u003e\n    \u003ctd\u003erequest certs from staging (signed by fake CA, subject to less rate limiting)\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003efalse\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['remove_unused_certs']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003eboolean\u003c/td\u003e\n    \u003ctd\u003eremove certificates that are no longer requested by any node\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003etrue\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['sync_bucket']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003es3 bucket to sync local certificate directory to\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003enil\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['sync_path']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003epath on the `sync_bucket` to sync certificate directory to\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003eletsencrypt\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['kms_key_id']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003eUUID of the kms key to use for server-side encryption (optional)\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003enil\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['blocklist']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003earray of strings\u003c/td\u003e\n    \u003ctd\u003eExact matches of primary certificate name to prevent generation\u003c/td\u003e\n    \u003ctd\u003e\u003ctt\u003e[]\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003e['letsencryptaws']['link_pybins']\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003eboolean\u003c/td\u003e\n    \u003ctd\u003eLink \u003ctt\u003eaws\u003c/tt\u003e and \u003ctt\u003ecertbot\u003c/tt\u003e in /usr/local/bin to pyenv paths if true\n    \u003ctd\u003e\u003ctt\u003etrue\u003c/tt\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## Data Bags\n\nA data bag is used to store sensitive credential information for AWS and Java keystores. You can arbitrarily specify the name and item name with `node['letsencryptaws']['data_bag']` and `node['letsencryptaws']['data_bag_item']` attributes. If you do not wish to use a data bag, you can place the credentials following the same hash structure at `node.run_state['letsencryptaws_creds']`.\n\nThe keys inside the data bag item can be:\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003eKey\u003c/th\u003e\n    \u003cth\u003eType\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003eaws_access_key_id\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003eAWS_ACCESS_KEY_ID for storing/fetching certificates from S3\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003eaws_secret_access_key\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003eAWS_SECRET_ACCESS_KEY for storing/fetching certificates from S3\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003ekeystore_passwords\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003ehash\u003c/td\u003e\n    \u003ctd\u003eKeys are paths to Java keystore files, values are the passwords to them. One special key is `default` which will be used as a catch-all password if a keystore does not have a specific entry.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ctt\u003ep12_password\u003c/tt\u003e\u003c/td\u003e\n    \u003ctd\u003estring\u003c/td\u003e\n    \u003ctd\u003ePassword to use when generating pkcs12 keyring files.\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## License and Authors\n\nAuthors: Matt Kulka \u003cmatt@lqx.net\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattlqx%2Fcookbook-letsencryptaws","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmattlqx%2Fcookbook-letsencryptaws","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattlqx%2Fcookbook-letsencryptaws/lists"}