Revoke OpenVPN certs of deactivated Google Workspace users (development)#284
Merged
Conversation
A user suspended or deleted in Google Workspace can no longer mint a new
certificate -- the portal blocks their login -- but any .ovpn they already
downloaded keeps working until the CA revokes it. This closes that gap with a
daily reconciliation: valid user CNs in the PKI index minus active directory
users is the set to revoke. Certificate CNs are Google email addresses because
the portal signs requests with EASYRSA_REQ_CN set to the authenticated user.
Development environment only, following the usual promotion path to sandbox and
then global.
The sync logic itself lives in infrahouse-toolkit as `ih-openvpn
sync-google-users` (not yet released); this change is the wrapper and its
schedule. Each of the three external dependencies is gated at the level it can
actually be observed:
1. terraform-aws-openvpn -- COMPILE time, on the new
openvpn.google_directory_revocation fact. Hosts with the feature off get
nothing at all: no script, no cron. The fact is absent on instances built
by a module version predating the feature, which reads as false.
2. infrahouse-toolkit -- RUNTIME, by probing for the subcommand rather than
pinning a version, since the toolkit ships on its own release cycle.
3. Google Workspace domain-wide delegation -- RUNTIME, via the sync command's
exit status. This one can never be a fact: authorizing the service
account's client id is a manual step in the admin console with no
Terraform resource behind it, so the feature can be enabled for days
before it is done.
Because Puppet installs the wrapper only where the feature was explicitly
enabled, an unmet dependency is reported as a failure rather than skipped
quietly. A silent skip would leave a host that was opted in never revoking
anything with nothing anywhere to say so. Only genuinely self-resolving
conditions stay quiet: another ASG instance holding the cluster-wide lock, and
a PKI that has not been generated yet.
Safety: the OpenVPN server's own certificate must never become a candidate. On
a fresh deployment CN=server is the only valid certificate, so a naive set-diff
would compute it as belonging to no active user and revoke it, killing the VPN
before any user has enrolled. The toolkit filters to CNs containing @ and
refuses to act on an empty directory response.
The lock lives on the EFS-backed config directory rather than /var/run so it is
cluster-wide -- every instance mounts the same PKI and concurrent easyrsa
revoke runs would race on index.txt. The cron minute is derived from the
hostname so the ASG does not query the Directory API in lockstep.
Verified end to end on a live development instance: the wrapper rendered by
Puppet, driving the real toolkit, authenticated keylessly through AWS -> GCP ->
domain-wide delegation and read the real directory.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
infrahouse8
approved these changes
Jul 19, 2026
This was referenced Jul 21, 2026
akuzminsky
added a commit
that referenced
this pull request
Jul 21, 2026
Final promotion of the OpenVPN certificate-revocation feature: development -> sandbox -> global, completing the development -> sandbox -> global path (cf. #284, #285). Copies the two files verbatim from environments/development into the top-level modules/ (which serves every environment, i.e. production) and adds the same fact-gated include to the global openvpn_server.pp. Inert until activated: the class is declared only where the openvpn.google_directory_revocation fact is true, which terraform-aws-openvpn (>= 7.0.0) sets only when enable_google_directory_revocation = true. On every existing node the fact is absent, so this installs no script and no cron and changes nothing until the feature is deliberately switched on per consumer. Validated on a live sandbox OpenVPN node running the promoted code: catalog compiled and no sync cron was installed while the fact was absent. Runtime dependencies Terraform cannot see (toolkit >= 2.61.0 for `ih-openvpn sync-google-users`, Google Workspace domain-wide delegation) are gated inside the wrapper. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A user suspended or deleted in Google Workspace can no longer mint a new certificate — the portal blocks their login — but any
.ovpnthey already downloaded keeps working until the CA revokes it. This closes that gap with a daily reconciliation: valid user CNs in the PKI index minus active directory users is the set to revoke.Certificate CNs are Google email addresses, because the portal signs requests with
EASYRSA_REQ_CNset to the authenticated user.Development environment only, following the usual promotion path (development → sandbox → global).
Scope
The sync logic itself lives in
infrahouse-toolkitasih-openvpn sync-google-users(not yet released). This PR is the wrapper and its schedule. Until that toolkit release lands, the wrapper reports the missing subcommand as a misconfiguration.Gating
Each external dependency is gated at the level it can actually be observed:
openvpn.google_directory_revocationfactHosts with the feature off get nothing at all: no script, no cron. The fact is absent on instances built by a module version predating the feature, which reads as false.
The third can never be a fact: authorizing the service account's client id is a paste into the Workspace admin console with no
hashicorp/googleresource behind it, so the feature can be enabled for days before it is done.Unmet dependencies are loud
Because Puppet installs the wrapper only where the feature was explicitly enabled, its presence means someone asked for it. An unmet dependency is therefore a failure, not a skip — a silent skip would leave an opted-in host never revoking anything with nothing anywhere to say so.
Only genuinely self-resolving conditions stay quiet: another ASG instance holding the cluster-wide lock, and a PKI that has not been generated yet.
Safety
The OpenVPN server's own certificate must never become a candidate. On a fresh deployment
CN=serveris the only valid certificate, so a naive set-diff computes it as belonging to no active user and revokes it — killing the VPN before any user has enrolled. Two independent guards in the toolkit: CNs must contain@, and an empty directory response is refused rather than treated as "everyone was deactivated".The lock lives on the EFS-backed config directory rather than
/var/run, so it is cluster-wide — every instance mounts the same PKI and concurrenteasyrsa revokeruns would race onindex.txt. The cron minute is derived from the hostname so the ASG does not query the Directory API in lockstep.Testing
Verified end to end on a live development instance: the wrapper as rendered by Puppet, driving the real toolkit, authenticated keylessly through AWS → GCP → domain-wide delegation and read the real directory.
Every dependency state was exercised against a sandboxed copy on a real Ubuntu host — missing credentials, stale toolkit, unauthorized delegation, real failure, success, uninitialized PKI, lock contention — confirming which exit non-zero and which stay quiet.
puppet-lint --fail-on-warningsclean on all four module paths.Not covered
revoke_clientusesEASYRSA_PASSIN=file:, whileregenerate-crl.sh.erbdocuments thatfile:fails under easy-rsa batch mode with OpenSSL 3.x and usespass:instead. Unresolved — raw OpenSSL handles both, so if the bug is real it lives in easy-rsa's batch handling. Worth settling before relying on this path.Depends on
infrahouse-toolkit:ih-openvpn sync-google-users(unreleased)terraform-aws-openvpn: emits theopenvpn.google_directory_revocationfact (unreleased)🤖 Generated with Claude Code