Skip to content

fix: honour CULVERT_CRL_DAYS and keep crl.pem 0644 - #59

Merged
catinspace-au merged 1 commit into
mainfrom
fix/crl-days-and-copies
Oct 1, 2026
Merged

catinspace-au merged 1 commit into
mainfrom
fix/crl-days-and-copies

Conversation

@catinspace-au

Copy link
Copy Markdown
Contributor

Six follow-ups to #58.

  • CULVERT_CRL_DAYS was honoured on first boot only. It reached Easy-RSA through the environment init_pki_local exported, so after a restart over an existing PKI the refresh loop, update-crl and revoke-client all wrote 180-day CRLs. regenerate_local_crl now passes EASYRSA_CRL_DAYS from the config on every run, and update-crl reads it through Config.from_settings(), so a profile YAML can set it too. Unset, it is still 180.
  • A crl.pem that gen-crl creates from nothing is 0600 root. OpenVPN drops to nobody (config/server.conf.template:107), and uid 65534 cannot open that file. Every regeneration now leaves it 0644. It is a public revocation list with nothing secret in it.
  • revoke-client regenerates through the library instead of its own gen-crl copy, and takes the Easy-RSA path from lib.pki.EASYRSA_DIR instead of its own constant.
  • _regenerate_local_crl is public as regenerate_local_crl, now that three modules call it.
  • The manager fixture moved to tests/unit/conftest.py. The wstunnel test built a bare ProcessManager() and left its SIGTERM, SIGINT and SIGHUP handlers installed for every test after it. The init_pki_local tests now put back the EASYRSA_* variables that function exports, which also outlived them.
  • test_app_version_tracks_the_latest_release reads origin's tags with git ls-remote instead of the clone's, so it runs in CI's shallow checkout. Offline it still skips, and the changelog check beside it still guards.

CRL lifetime and mode, main against this branch. Measured in the v2.1.16 image against Easy-RSA 3.2.5, each regeneration in a fresh process over a PKI first built with CULVERT_CRL_DAYS=30. Lifetime is nextUpdate minus lastUpdate, and "nobody" is whether uid 65534 can read crl.pem.

Path CULVERT_CRL_DAYS main this branch
First boot (init_pki_local, unchanged) 30 30 days 30 days
Refresh loop after a restart 30 180 days 30 days
update-crl 30 180 days 30 days
revoke-client 30 180 days 30 days
Refresh loop unset 180 days 180 days
update-crl unset 180 days 180 days
Refresh loop, crl.pem missing 30 0600, nobody denied 0644, nobody reads
update-crl, crl.pem missing 30 0600, nobody denied 0644, nobody reads
revoke-client, crl.pem missing 30 0600, nobody denied 0644, nobody reads
Refresh loop, existing 0644 crl.pem 30 0644 0644

Unset, both write the same CRL apart from its timestamps and signature: version 2, issuer CN=VPN CA, ecdsa-with-SHA256, and it verifies against the CA.

revoke-client, main against this branch:

main this branch
CRL regeneration its own ./easyrsa gen-crl, argv list lib.pki.regenerate_local_crl, sh -c with quoted paths
Environment EASYRSA, EASYRSA_PKI, EASYRSA_BATCH=1 the same plus EASYRSA_CRL_DAYS from config
Easy-RSA path own EASYRSA constant lib.pki.EASYRSA_DIR, the same /usr/share/easy-rsa
CRL lifetime, CULVERT_CRL_DAYS=30 180 days 30 days
crl.pem created fresh 0600 0644
Success output 13 lines the same 13 lines, timestamps and line numbers aside
gen-crl failure line Failed to update CRL: <stderr> then a blank line CRL regeneration failed: <stderr>, stripped
Exit codes 0 revoked, 1 on a failed revoke or gen-crl unchanged

The failure was forced by making crl.pem a dangling symlink, which easyrsa revoke ignores and gen-crl's final copy refuses. Both versions leave the certificate revoked in index.txt and exit 1.

  • update-crl now logs two lines before its own, Network profile and max-clients calculated, because it builds the config. revoke-client already did.
  • tests/run-unit.sh: 598 passed, also with the files in reverse order. hyperi-ci check --strict 2.12.5: exit 0, coverage 83.21%.

CULVERT_CRL_DAYS reached Easy-RSA only through the environment PKI initialisation exported, so after a restart over an existing PKI the refresh loop, update-crl and revoke-client all wrote 180-day CRLs whatever the operator configured. regenerate_local_crl now passes EASYRSA_CRL_DAYS from the config on every run, and update-crl reads it through the config cascade. Unset, it stays 180.

A crl.pem that gen-crl creates from nothing is 0600 root, which OpenVPN cannot read once it has dropped to nobody. Every regeneration now leaves it 0644.

revoke-client regenerates through the same function instead of its own gen-crl call, and the function is public now that three modules call it.

The manager fixture moves to tests/unit/conftest.py, so the wstunnel test no longer leaves ProcessManager's signal handlers installed, and the init_pki_local tests put back the EASYRSA_* variables it exports. The release-tag test reads origin's tags with git ls-remote, so it runs in CI's shallow checkout instead of skipping.
@catinspace-au
catinspace-au requested a review from a team as a code owner October 1, 2026 10:48
@catinspace-au
catinspace-au merged commit d8fd86e into main Oct 1, 2026
13 checks passed
@catinspace-au
catinspace-au deleted the fix/crl-days-and-copies branch October 1, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant