Skip to content

Commit 93ff082

Browse files
committed
release: v0.17.0 — shimkit tls --method dns-route53
AWS Route53 DNS-01 alongside the v0.13.0 Cloudflare path. Second DNS-01 provider; Cloudflare + Route53 cover the two most-used providers in the ACME ecosystem. Surface (additive — webroot + dns-cloudflare unchanged): shimkit tls request --method dns-route53 \ --credentials FILE -d D ... Uses `certbot/dns-route53:v3.0.1` (auto-selected). The three image families are independent: `certbot/certbot:v3.0.1` for webroot, `certbot/dns-cloudflare:v3.0.1` for Cloudflare, `certbot/dns-route53:v3.0.1` for Route53. Credentials shape differs from Cloudflare: Cloudflare: `dns_cloudflare_api_token = <token>` (one line) Parent dir mounted at /credentials in container. Plugin reads --dns-cloudflare-credentials flag. Route53: Standard AWS [default] ini file. boto3 reads it from ~/.aws/credentials by default. shimkit mounts the file itself at /root/.aws/credentials. No --dns-route53-credentials flag exists. The same --credentials CLI flag works for both methods; the manager detects which provider via --method and routes the mount accordingly. The new `credentials_mount` parameter on `certbot.container_volumes` is the dispatch point — defaults to "cloudflare" for back-compat; "route53" picks the AWS file path. Mode 0600 enforced for both providers (certbot's strict check; shimkit catches it up-front). Wildcard certs: DNS-01 is the only ACME path that supports wildcards (`*.example.com`). Either provider works; pick whichever hosts your DNS. IAM key scope for Route53 (minimum): route53:ListHostedZones route53:GetChange route53:ChangeResourceRecordSets Tests: 14 new in test_tools_tls_dns_route53.py (1116 → 1130 total). Pure argv-builder shape (Route53 flags + propagation + staging/dry-run; webroot + cloudflare paths unaffected), container_volumes route53 mount target (`/root/.aws/credentials` vs Cloudflare's `/credentials`), manager validation (missing- credentials / missing-file / loose-mode refusal mirroring Cloudflare / happy path picks dns-route53 image / uses route53_propagation_seconds not cloudflare's / JSON output includes method), config plumbing (route53 image + propagation_seconds range validation incl. negative + over-600 refusals). Adjusted one Cloudflare test that used `dns-route53` as the "unknown method" placeholder. Gates: pytest 1130 passed, ruff clean, mypy strict clean. No new optional dependency extras.
1 parent e57b644 commit 93ff082

13 files changed

Lines changed: 759 additions & 43 deletions

File tree

CHANGELOG.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,60 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
66

77
## [Unreleased]
88

9+
## [0.17.0] — 2026-05-16
10+
11+
### Added
12+
13+
- **`shimkit tls --method dns-route53`** — AWS Route53 DNS-01
14+
alongside the v0.13.0 Cloudflare path. Uses the upstream
15+
`certbot/dns-route53:v3.0.1` image (auto-selected when
16+
`--method dns-route53` is passed).
17+
- `tools.tls.certbot_dns_route53_image` config field — pin the
18+
Route53 plugin image independently.
19+
- `tools.tls.route53_propagation_seconds` config field — same
20+
range (`[0, 600]`) as the Cloudflare equivalent. Default `60`.
21+
22+
### Changed
23+
24+
- `certbot.container_volumes` accepts a `credentials_mount`
25+
parameter (`"cloudflare"` or `"route53"`). Cloudflare keeps the
26+
v0.13.0 behaviour of mounting the credentials file's parent
27+
directory at `/credentials`; Route53 mounts the file itself at
28+
`/root/.aws/credentials` (boto3's default search path — no
29+
`--dns-route53-credentials` flag exists).
30+
- `certbot.request_argv` accepts `method="dns-route53"`. Emits
31+
`--dns-route53` + `--dns-route53-propagation-seconds`.
32+
- `TlsConfig.default_method` widened to include `"dns-route53"`.
33+
- `tls request` `--credentials` flag help text covers both
34+
Cloudflare token format and AWS credentials file format.
35+
36+
### Tests
37+
38+
- 14 new tests in `tests/test_tools_tls_dns_route53.py` (1116 →
39+
1130 total). Pure argv-builder shape (Route53 flags +
40+
propagation + staging/dry-run; webroot + cloudflare paths
41+
unaffected), container_volumes route53 mount target
42+
(`/root/.aws/credentials` vs Cloudflare's `/credentials`),
43+
manager validation (missing-credentials / missing-file /
44+
loose-mode refusal / happy path picks dns-route53 image /
45+
uses route53_propagation_seconds not cloudflare's / JSON
46+
output includes method), config plumbing (route53 image +
47+
propagation_seconds range validation).
48+
- Adjusted one Cloudflare test that previously used `dns-route53`
49+
as the "unknown method" placeholder — now uses
50+
`dns-digitalocean`.
51+
52+
### Notes
53+
54+
Second DNS-01 provider. Cloudflare + Route53 cover the two
55+
most-used providers in the ACME ecosystem. Other providers
56+
(DigitalOcean, Hurricane Electric, etc.) each need their own
57+
credential surface and provider-specific image — opt-in extras
58+
in a future release.
59+
60+
Gates: pytest 1130 passed, ruff clean, mypy strict clean. No new
61+
optional dependency extras.
62+
963
## [0.16.0] — 2026-05-16
1064

1165
### Added

docs/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ Top-level utilities (not tools):
9898

9999
Per-version, user-facing summaries (newest first):
100100

101+
- **[`v0.17.0`](release-notes/v0.17.0.md)** — `shimkit tls --method
102+
dns-route53` for AWS Route53 DNS-01.
101103
- **[`v0.16.0`](release-notes/v0.16.0.md)** — `shimkit framework
102104
django` third framework recipe.
103105
- **[`v0.15.0`](release-notes/v0.15.0.md)**`shimkit db redis`

docs/release-notes/v0.17.0.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# shimkit 0.17.0
2+
3+
`shimkit tls --method dns-route53` — AWS Route53 DNS-01 alongside
4+
the v0.13.0 Cloudflare path. Second DNS-01 provider. For the full
5+
machine-readable changelog, see
6+
[`CHANGELOG.md`](../../CHANGELOG.md).
7+
8+
---
9+
10+
## TL;DR
11+
12+
```
13+
shimkit tls request --method dns-route53 --credentials FILE -d ...
14+
```
15+
16+
Uses `certbot/dns-route53:v3.0.1` (auto-selected). Cloudflare path
17+
still uses `certbot/dns-cloudflare:v3.0.1`; webroot still uses
18+
`certbot/certbot:v3.0.1`. The three image families are
19+
independent — pin each via its own `tools.tls.certbot_*_image`
20+
config field.
21+
22+
---
23+
24+
## Why Route53
25+
26+
After Cloudflare, AWS Route53 is the most-common ACME DNS-01
27+
provider in the wild. Adding it covers the two providers most
28+
teams actually use; other providers (DigitalOcean, Hurricane
29+
Electric, Google Cloud DNS, etc.) follow the same pattern and
30+
can be added in future minor releases.
31+
32+
DNS-01 is the only ACME path that supports wildcards
33+
(`*.example.com`).
34+
35+
---
36+
37+
## Setup
38+
39+
1. **Create an IAM key** with the minimum scope:
40+
41+
```json
42+
{
43+
"Statement": [
44+
{
45+
"Effect": "Allow",
46+
"Action": [
47+
"route53:ListHostedZones",
48+
"route53:GetChange",
49+
"route53:ChangeResourceRecordSets"
50+
],
51+
"Resource": "*"
52+
}
53+
]
54+
}
55+
```
56+
57+
2. **Write the credentials file** — standard AWS format:
58+
59+
```ini
60+
[default]
61+
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
62+
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
63+
```
64+
65+
```bash
66+
chmod 600 ~/.shimkit/data/tls/aws-credentials
67+
```
68+
69+
3. **Issue the cert** (staging first):
70+
71+
```bash
72+
shimkit tls request --yes --staging \
73+
--email ops@example.com \
74+
--method dns-route53 \
75+
--credentials ~/.shimkit/data/tls/aws-credentials \
76+
-d example.com -d '*.example.com'
77+
```
78+
79+
---
80+
81+
## How it differs from Cloudflare
82+
83+
The Route53 plugin reads `~/.aws/credentials` via boto3 — there's
84+
no `--dns-route53-credentials` flag like Cloudflare has. shimkit
85+
handles this transparently:
86+
87+
| | Cloudflare | Route53 |
88+
|---|---|---|
89+
| Container image | `certbot/dns-cloudflare:v3.0.1` | `certbot/dns-route53:v3.0.1` |
90+
| Credentials file format | `dns_cloudflare_api_token = <token>` | AWS `[default]` ini |
91+
| Mount target inside container | parent dir at `/credentials` | file at `/root/.aws/credentials` |
92+
| Certbot flag | `--dns-cloudflare-credentials /credentials/cloudflare.ini` | (none — boto3 finds it) |
93+
| Mode 0600 enforced | yes | yes |
94+
95+
The same `--credentials` CLI flag works for both methods; shimkit
96+
detects which provider and mounts accordingly.
97+
98+
---
99+
100+
## Renewal
101+
102+
Same as Cloudflare and webroot:
103+
104+
```bash
105+
shimkit tls renew --yes
106+
```
107+
108+
certbot reads `/etc/letsencrypt/renewal/<domain>.conf` per cert
109+
(written at issuance time) and reuses the same method. The daily
110+
renewal cron from v0.8.0 covers all three methods.
111+
112+
---
113+
114+
## Config
115+
116+
```json
117+
{
118+
"tools": {
119+
"tls": {
120+
"certbot_dns_route53_image": "certbot/dns-route53:v3.0.1",
121+
"route53_propagation_seconds": 60
122+
}
123+
}
124+
}
125+
```
126+
127+
Pin the image to a specific version if you don't want auto-
128+
updates. Lower `route53_propagation_seconds` if Route53 propagates
129+
fast in your account (often 10-30s in practice).
130+
131+
---
132+
133+
## Stats
134+
135+
- Tests: 1116 → 1130 (+14)
136+
- Gates: pytest, ruff, mypy strict — all green
137+
- New optional extras: 0
138+
139+
---
140+
141+
## Upgrading
142+
143+
```bash
144+
uv tool upgrade shimkit
145+
pipx upgrade shimkit
146+
```
147+
148+
Existing webroot + Cloudflare users see no behavioural change. To
149+
start using Route53, set up the IAM key + credentials file then
150+
pass `--method dns-route53 --credentials`.

docs/tools/tls.md

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,43 @@ docker run --rm \
4141
[--staging]
4242
```
4343

44+
### DNS-route53 (DNS-01, v0.17.0+)
45+
46+
`shimkit tls request --method dns-route53` runs:
47+
48+
```
49+
docker run --rm \
50+
-v ~/.shimkit/data/tls/etc-letsencrypt:/etc/letsencrypt \
51+
-v ~/.shimkit/data/tls/var-lib-letsencrypt:/var/lib/letsencrypt \
52+
-v <aws-credentials-file>:/root/.aws/credentials:ro \
53+
certbot/dns-route53:v3.0.1 \
54+
certonly --non-interactive --agree-tos \
55+
--email ops@example.com \
56+
--dns-route53 \
57+
--dns-route53-propagation-seconds 60 \
58+
-d example.com [-d '*.example.com'] \
59+
[--staging]
60+
```
61+
62+
**Required for wildcards on AWS-hosted domains.** The Route53
63+
plugin reads boto3's default credentials file —
64+
`/root/.aws/credentials` inside the container. shimkit mounts
65+
your local AWS credentials file directly at that path (different
66+
from Cloudflare's `--dns-cloudflare-credentials` flag).
67+
68+
**Credentials file format** (standard AWS):
69+
70+
```ini
71+
[default]
72+
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
73+
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
74+
```
75+
76+
The IAM key needs `route53:ChangeResourceRecordSets` and
77+
`route53:GetChange` on the hosted zone you're issuing for.
78+
79+
**Mode 0600 required** (same check as Cloudflare).
80+
4481
### DNS-cloudflare (DNS-01, v0.13.0+)
4582

4683
`shimkit tls request --method dns-cloudflare` runs:
@@ -207,11 +244,12 @@ in `tls list` / `tls status` (which shells out to `openssl x509
207244
punishing (5 failed validations / hour / hostname). Always
208245
pass `--staging` for first runs; the resulting cert isn't
209246
trusted but proves the webroot setup works.
210-
- **Webroot vs DNS-01.** Both are wired as of v0.13.0. Webroot
211-
(HTTP-01) is the default; DNS-01 via Cloudflare is opt-in via
212-
`--method dns-cloudflare` and is the **only** path to wildcard
213-
certs. Other DNS providers (Route53, DigitalOcean, etc.) each
214-
need their own credential surface and are deferred.
247+
- **Webroot vs DNS-01.** As of v0.17.0, three methods are wired:
248+
webroot (HTTP-01, default), dns-cloudflare (DNS-01), and
249+
dns-route53 (DNS-01). DNS-01 is the only path to wildcard
250+
certs. Other DNS providers (DigitalOcean, Hurricane Electric,
251+
etc.) each need their own credential surface and provider-
252+
specific image — opt-in extras in a future release.
215253
- **No PyPI extra.** This tool reuses the `[docker-clean]`
216254
extra's `docker` package — no new install footprint.
217255
- **Renewal cadence.** Let's Encrypt certs are valid for 90 days;

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "shimkit"
7-
version = "0.16.0"
7+
version = "0.17.0"
88
description = "A toolkit of developer utilities — Java version manager, shell upgrader, and more. Python tools, shimmed by bash."
99
readme = "README.md"
1010
license = { file = "LICENSE" }

src/shimkit/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
Python tools, shimmed by bash.
44
"""
55

6-
__version__ = "0.16.0"
6+
__version__ = "0.17.0"
77
__all__ = ["__version__"]

src/shimkit/config/defaults.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,10 @@
195195
"data_dir": "~/.shimkit/data/tls",
196196
"certbot_image": "certbot/certbot:v3.0.1",
197197
"certbot_dns_cloudflare_image": "certbot/dns-cloudflare:v3.0.1",
198+
"certbot_dns_route53_image": "certbot/dns-route53:v3.0.1",
198199
"default_method": "webroot",
199200
"cloudflare_propagation_seconds": 60,
201+
"route53_propagation_seconds": 60,
200202
"default_email": null,
201203
"renewal_schedule": "17 3 * * *",
202204
"revoke_severe_token": "REVOKE-TLS"

src/shimkit/config/schema.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,18 @@ class TlsConfig(_StrictModel):
428428
# Certbot image with the dns-cloudflare plugin pre-installed.
429429
# Used when `--method dns-cloudflare` is passed.
430430
certbot_dns_cloudflare_image: str = "certbot/dns-cloudflare:v3.0.1"
431+
# Certbot image with the dns-route53 plugin pre-installed
432+
# (v0.17.0+).
433+
certbot_dns_route53_image: str = "certbot/dns-route53:v3.0.1"
431434
# Default ACME challenge method. `webroot` (HTTP-01) is the
432-
# original; `dns-cloudflare` (DNS-01) lands in v0.13.0 and is the
433-
# path to wildcard certs.
434-
default_method: Literal["webroot", "dns-cloudflare"] = "webroot"
435-
# Cloudflare DNS propagation seconds. The plugin's recommended
436-
# baseline; safe to lower on accounts with fast propagation.
435+
# original; `dns-cloudflare` (DNS-01) landed in v0.13.0 +
436+
# `dns-route53` in v0.17.0, both paths to wildcard certs.
437+
default_method: Literal["webroot", "dns-cloudflare", "dns-route53"] = "webroot"
438+
# DNS propagation seconds. Same knob for both Cloudflare and
439+
# Route53 — actual real-world propagation usually 10-30s but
440+
# 60s is the safe published default for both plugins.
437441
cloudflare_propagation_seconds: int = Field(default=60, ge=0, le=600)
442+
route53_propagation_seconds: int = Field(default=60, ge=0, le=600)
438443
# ACME account email. Required by Let's Encrypt for issuance unless
439444
# `--register-unsafely-without-email` is passed (we don't expose
440445
# that flag). User config overrides per-install.

0 commit comments

Comments
 (0)