Skip to content

Commit ef032c9

Browse files
committed
Add ACME companion timer packaging
1 parent cd42fc7 commit ef032c9

16 files changed

Lines changed: 356 additions & 76 deletions

docs/build-and-podman.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,8 @@ name = "actalis"
439439
directory_url = "https://acme-api.actalis.com/acme/directory"
440440
441441
[tls.acme.issuers.eab]
442-
key_id_file = "/run/secrets/actalis-eab-kid"
443-
hmac_key_file = "/run/secrets/actalis-eab-hmac-key"
442+
key_id_credential = "actalis-eab-kid"
443+
hmac_key_credential = "actalis-eab-hmac-key"
444444
```
445445

446446
For container secret files, mount the files read-only. If the host tree is

docs/certificate-renewal.md

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,18 @@ LoadCredential=actalis-eab-kid:/etc/fluxheim/secrets/actalis-eab-kid.cred
8585
LoadCredential=actalis-eab-hmac-key:/etc/fluxheim/secrets/actalis-eab-hmac-key.cred
8686
```
8787

88-
Matching Fluxheim config:
88+
Matching Fluxheim config. Credential names resolve below
89+
`$CREDENTIALS_DIRECTORY` when Fluxheim is launched by systemd, and below
90+
`/run/secrets` for normal container-style secret mounts:
8991

9092
```toml
9193
[[tls.acme.issuers]]
9294
name = "actalis"
9395
directory_url = "https://acme-api.actalis.com/acme/directory"
9496

9597
[tls.acme.issuers.eab]
96-
key_id_file = "/run/credentials/fluxheim.service/actalis-eab-kid"
97-
hmac_key_file = "/run/credentials/fluxheim.service/actalis-eab-hmac-key"
98+
key_id_credential = "actalis-eab-kid"
99+
hmac_key_credential = "actalis-eab-hmac-key"
98100
```
99101

100102
Built-in issuer names:
@@ -117,8 +119,8 @@ mounted files:
117119

118120
```toml
119121
[tls.acme.issuers.eab]
120-
key_id_file = "/run/secrets/actalis-eab-kid"
121-
hmac_key_file = "/run/secrets/actalis-eab-hmac-key"
122+
key_id_credential = "actalis-eab-kid"
123+
hmac_key_credential = "actalis-eab-hmac-key"
122124
```
123125

124126
Environment variables remain useful for local testing, but they should not be
@@ -244,16 +246,17 @@ sudo systemctl daemon-reload
244246
sudo systemctl restart fluxheim
245247
```
246248

247-
Then point the issuer EAB paths at systemd's mounted credential directory:
249+
Then point the issuer EAB sources at credential names. The same TOML works for
250+
`fluxheim.service`, `fluxheim-acme.service`, and container secrets:
248251

249252
```toml
250253
[[tls.acme.issuers]]
251254
name = "actalis"
252255
directory_url = "https://acme-api.actalis.com/acme/directory"
253256

254257
[tls.acme.issuers.eab]
255-
key_id_file = "/run/credentials/fluxheim.service/actalis-eab-kid"
256-
hmac_key_file = "/run/credentials/fluxheim.service/actalis-eab-hmac-key"
258+
key_id_credential = "actalis-eab-kid"
259+
hmac_key_credential = "actalis-eab-hmac-key"
257260
```
258261

259262
## Renewal Queue Planning
@@ -288,16 +291,28 @@ resolver or callback so new handshakes can use the freshly installed files
288291
without restarting. If a TLS backend or listener shape cannot provide a reload
289292
handle, Fluxheim logs that a restart or process reload is required.
290293

291-
Future production packaging should add a companion ACME operating mode while
292-
keeping the integrated background worker for simple installs. In that model,
293-
`fluxheim.service` stays focused on serving traffic and challenge files, while a
294-
one-shot `fluxheim-acme.service` and scheduled `fluxheim-acme.timer` run
295-
renewals as the Fluxheim runtime user. The companion command should reuse the
296-
same ACME engine and storage layout as `fluxheim acme-renew`, use systemd
297-
credentials or container secrets for EAB material, and write certificates below
298-
the configured `tls.acme.storage` so the running webserver can reload them
299-
without a restart. Do not make the webserver spawn a long-lived helper process;
300-
let the service manager or container orchestrator supervise the companion.
294+
Production packages include a companion ACME operating mode while keeping the
295+
integrated background worker for simple installs. In this model,
296+
`fluxheim.service` stays focused on serving traffic and challenge files, while
297+
the one-shot `fluxheim-acme.service` and scheduled `fluxheim-acme.timer` run
298+
renewals as the Fluxheim runtime user. The companion command reuses the same
299+
ACME engine and storage layout as `fluxheim acme-renew`, uses systemd
300+
credentials or container secrets for EAB material, and writes certificates below
301+
the configured `tls.acme.storage`.
302+
303+
Enable the packaged timer after ACME config and credentials are installed:
304+
305+
```bash
306+
sudo install -d /etc/systemd/system/fluxheim-acme.service.d
307+
sudo cp /usr/share/doc/fluxheim/systemd/actalis-eab-acme.conf \
308+
/etc/systemd/system/fluxheim-acme.service.d/actalis-eab.conf
309+
sudo systemctl daemon-reload
310+
sudo systemctl enable --now fluxheim-acme.timer
311+
sudo systemctl start fluxheim-acme.service
312+
```
313+
314+
Do not make the webserver spawn a long-lived helper process; let the service
315+
manager or container orchestrator supervise the companion.
301316

302317
## Runtime Crate Candidates
303318

docs/config-reference.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -628,10 +628,11 @@ with file-backed External Account Binding secrets.
628628
Built-in issuer names include `letsencrypt`, `letsencrypt-staging`,
629629
`actalis`, `google-trust-services`, and `google-trust-services-staging`.
630630
Actalis and Google Trust Services require External Account Binding. Their EAB
631-
secret sources are configured through environment variables or files.
632-
File-backed secrets are preferred for production because they work with systemd
633-
credentials, Docker/Podman secrets, and Kubernetes secret volumes without
634-
exposing values in process environments or container metadata.
631+
secret sources are configured through environment variables, files, or
632+
credential names. Credential names are preferred for production because the same
633+
config works with systemd credentials, Docker/Podman secrets, and Kubernetes
634+
secret volumes without exposing values in process environments or container
635+
metadata.
635636

636637
Example with systemd credentials:
637638

@@ -641,8 +642,8 @@ name = "actalis"
641642
directory_url = "https://acme-api.actalis.com/acme/directory"
642643

643644
[tls.acme.issuers.eab]
644-
key_id_file = "/run/credentials/fluxheim.service/actalis-eab-kid"
645-
hmac_key_file = "/run/credentials/fluxheim.service/actalis-eab-hmac-key"
645+
key_id_credential = "actalis-eab-kid"
646+
hmac_key_credential = "actalis-eab-hmac-key"
646647
```
647648

648649
Example with container secrets:
@@ -653,8 +654,8 @@ name = "actalis"
653654
directory_url = "https://acme-api.actalis.com/acme/directory"
654655

655656
[tls.acme.issuers.eab]
656-
key_id_file = "/run/secrets/actalis-eab-kid"
657-
hmac_key_file = "/run/secrets/actalis-eab-hmac-key"
657+
key_id_credential = "actalis-eab-kid"
658+
hmac_key_credential = "actalis-eab-hmac-key"
658659
```
659660

660661
Google Trust Services production uses

docs/systemd.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,40 @@ sudo systemctl restart fluxheim.service
123123
Fluxheim exits on `SIGTERM`; the unit uses `TimeoutStopSec=30s` so the process
124124
has time to drain and shut down cleanly before systemd escalates.
125125

126+
## ACME Timer
127+
128+
RPM packages install a one-shot renewal unit and timer:
129+
130+
- `fluxheim-acme.service`
131+
- `fluxheim-acme.timer`
132+
133+
The service runs `fluxheim --config ${FLUXHEIM_CONFIG} acme-renew` as the same
134+
`fluxheim` user, with the same state/cache/log directories as the web service.
135+
It does not bind ports and does not receive `CAP_NET_BIND_SERVICE`.
136+
137+
For issuers with External Account Binding, install the ACME credential drop-in
138+
for the ACME unit and use credential names in the TOML:
139+
140+
```bash
141+
sudo install -d /etc/systemd/system/fluxheim-acme.service.d
142+
sudo cp /usr/share/doc/fluxheim/systemd/actalis-eab-acme.conf \
143+
/etc/systemd/system/fluxheim-acme.service.d/actalis-eab.conf
144+
sudo systemctl daemon-reload
145+
```
146+
147+
```toml
148+
[tls.acme.issuers.eab]
149+
key_id_credential = "actalis-eab-kid"
150+
hmac_key_credential = "actalis-eab-hmac-key"
151+
```
152+
153+
Enable scheduled renewal:
154+
155+
```bash
156+
sudo systemctl enable --now fluxheim-acme.timer
157+
sudo systemctl start fluxheim-acme.service
158+
```
159+
126160
## Sandbox Overrides
127161

128162
The default unit is strict enough for normal static/proxy deployments. If a

docs/versioning-plan.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,17 @@ Stable scope:
346346
- Production ACME companion operating mode:
347347
- Keep the `1.1` in-process ACME background worker for simple single-binary
348348
installs.
349-
- Add a `fluxheim-acme` command or binary that reuses the same ACME engine
350-
for `init`, due-only renewal, and forced renewal.
349+
- Use the existing `fluxheim acme-init` and `fluxheim acme-renew` commands as
350+
the first companion interface; split to a dedicated `fluxheim-acme` binary
351+
only if packaging or operator ergonomics require it later.
351352
- Ship `fluxheim-acme.service` as a one-shot unit and
352353
`fluxheim-acme.timer` for scheduled renewal. The main `fluxheim.service`
353354
remains the traffic-serving webserver and should not spawn long-lived child
354355
processes itself.
355356
- Run the companion service as the same runtime user as Fluxheim by default,
356-
using systemd credentials or container secrets for EAB material.
357+
using systemd credentials or container secrets for EAB material. Prefer
358+
`key_id_credential` and `hmac_key_credential` config fields so the same
359+
TOML works under the web service, ACME service, and container secrets.
357360
- Share only the configured ACME storage directory with the webserver. The
358361
webserver continues to serve HTTP-01 challenge files and to reload
359362
certificate handles after files change.

examples/acme-actalis.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ name = "actalis"
2727
directory_url = "https://acme-api.actalis.com/acme/directory"
2828

2929
[tls.acme.issuers.eab]
30-
key_id_file = "/run/credentials/fluxheim.service/actalis-eab-kid"
31-
hmac_key_file = "/run/credentials/fluxheim.service/actalis-eab-hmac-key"
30+
key_id_credential = "actalis-eab-kid"
31+
hmac_key_credential = "actalis-eab-hmac-key"
3232

3333
[[vhosts]]
3434
name = "actalis.example.test"

packaging/rpm/fluxheim.spec

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Source3: fluxheim.service
2222
Source4: fluxheim.env
2323
Source5: fluxheim.sysusers
2424
Source6: actalis-eab.conf
25+
Source7: fluxheim-acme.service
26+
Source8: fluxheim-acme.timer
27+
Source9: actalis-eab-acme.conf
2528

2629
ExclusiveArch: x86_64 aarch64
2730

@@ -81,6 +84,9 @@ install -Dm0644 %{SOURCE3} %{buildroot}%{_unitdir}/fluxheim.service
8184
install -Dm0644 %{SOURCE4} %{buildroot}%{_sysconfdir}/sysconfig/fluxheim
8285
install -Dm0644 %{SOURCE5} %{buildroot}%{_sysusersdir}/fluxheim.conf
8386
install -Dm0644 %{SOURCE6} %{buildroot}%{_docdir}/fluxheim/systemd/actalis-eab.conf
87+
install -Dm0644 %{SOURCE7} %{buildroot}%{_unitdir}/fluxheim-acme.service
88+
install -Dm0644 %{SOURCE8} %{buildroot}%{_unitdir}/fluxheim-acme.timer
89+
install -Dm0644 %{SOURCE9} %{buildroot}%{_docdir}/fluxheim/systemd/actalis-eab-acme.conf
8490

8591
install -d -m0755 %{buildroot}%{_sysconfdir}/fluxheim/conf.d
8692
install -d -m0755 %{buildroot}%{_sysconfdir}/fluxheim/tls
@@ -123,10 +129,13 @@ fi
123129
%license LICENSE
124130
%doc README.md CHANGELOG.md ROADMAP.md RELEASE_NOTES_1.1.0.md docs examples
125131
%{_docdir}/fluxheim/systemd/actalis-eab.conf
132+
%{_docdir}/fluxheim/systemd/actalis-eab-acme.conf
126133
%{_bindir}/fluxheim
127134
%{_tmpfilesdir}/fluxheim.conf
128135
%{_sysusersdir}/fluxheim.conf
129136
%{_unitdir}/fluxheim.service
137+
%{_unitdir}/fluxheim-acme.service
138+
%{_unitdir}/fluxheim-acme.timer
130139
%dir %{_sysconfdir}/fluxheim
131140
%dir %{_sysconfdir}/fluxheim/conf.d
132141
%dir %{_sysconfdir}/fluxheim/tls
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[Service]
2+
# Optional Actalis External Account Binding credentials for the standalone
3+
# Fluxheim ACME renewal unit.
4+
#
5+
# Install the source files as root-readable secrets:
6+
# install -d -m 0700 -o root -g root /etc/fluxheim/secrets
7+
# install -m 0600 -o root -g root /dev/null /etc/fluxheim/secrets/actalis-eab-kid
8+
# install -m 0600 -o root -g root /dev/null /etc/fluxheim/secrets/actalis-eab-hmac-key
9+
#
10+
# Then copy this file to:
11+
# /etc/systemd/system/fluxheim-acme.service.d/actalis-eab.conf
12+
#
13+
# Fluxheim config should prefer credential names so the same config works under
14+
# fluxheim.service, fluxheim-acme.service, and container secret mounts:
15+
# key_id_credential = "actalis-eab-kid"
16+
# hmac_key_credential = "actalis-eab-hmac-key"
17+
LoadCredential=actalis-eab-kid:/etc/fluxheim/secrets/actalis-eab-kid
18+
LoadCredential=actalis-eab-hmac-key:/etc/fluxheim/secrets/actalis-eab-hmac-key

packaging/systemd/actalis-eab.conf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
# Then copy this file to:
1010
# /etc/systemd/system/fluxheim.service.d/actalis-eab.conf
1111
#
12-
# Fluxheim config should point at:
13-
# /run/credentials/fluxheim.service/actalis-eab-kid
14-
# /run/credentials/fluxheim.service/actalis-eab-hmac-key
12+
# Fluxheim config should prefer credential names so the same config works under
13+
# fluxheim.service, fluxheim-acme.service, and container secret mounts:
14+
# key_id_credential = "actalis-eab-kid"
15+
# hmac_key_credential = "actalis-eab-hmac-key"
1516
LoadCredential=actalis-eab-kid:/etc/fluxheim/secrets/actalis-eab-kid
1617
LoadCredential=actalis-eab-hmac-key:/etc/fluxheim/secrets/actalis-eab-hmac-key
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[Unit]
2+
Description=Fluxheim ACME certificate renewal
3+
Documentation=https://github.com/valkyoth/fluxheim
4+
After=network-online.target
5+
Wants=network-online.target
6+
7+
[Service]
8+
Type=oneshot
9+
User=fluxheim
10+
Group=fluxheim
11+
Environment=FLUXHEIM_CONFIG=/etc/fluxheim/fluxheim.toml
12+
EnvironmentFile=-/etc/sysconfig/fluxheim
13+
ExecStart=/usr/bin/fluxheim --config ${FLUXHEIM_CONFIG} acme-renew
14+
RuntimeDirectory=fluxheim
15+
RuntimeDirectoryMode=0750
16+
StateDirectory=fluxheim
17+
StateDirectoryMode=0750
18+
CacheDirectory=fluxheim
19+
CacheDirectoryMode=0750
20+
LogsDirectory=fluxheim
21+
LogsDirectoryMode=0750
22+
NoNewPrivileges=true
23+
PrivateTmp=true
24+
PrivateDevices=true
25+
ProtectSystem=strict
26+
ProtectHome=true
27+
ProtectControlGroups=true
28+
ProtectKernelLogs=true
29+
ProtectKernelModules=true
30+
ProtectKernelTunables=true
31+
ProtectProc=invisible
32+
ProcSubset=pid
33+
ReadWritePaths=/run/fluxheim /var/lib/fluxheim /var/cache/fluxheim /var/log/fluxheim
34+
ReadOnlyPaths=/etc/fluxheim /srv/fluxheim
35+
CapabilityBoundingSet=
36+
AmbientCapabilities=
37+
LockPersonality=true
38+
MemoryDenyWriteExecute=true
39+
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
40+
RestrictNamespaces=true
41+
RestrictRealtime=true
42+
RestrictSUIDSGID=true
43+
SystemCallArchitectures=native
44+
SystemCallFilter=@system-service @network-io
45+
SystemCallErrorNumber=EPERM

0 commit comments

Comments
 (0)