From bcd411f828d44a1157144edcc3421965b45cc00f Mon Sep 17 00:00:00 2001 From: Oleksandr Kuzminskyi Date: Tue, 20 Jan 2026 16:57:46 -0800 Subject: [PATCH] Promote OpenVPN CRL fix to sandbox environment Tested in development, promoting encrypted CA support and CRL auto-regeneration to sandbox for validation before production. Changes: - Fix CRL regeneration with encrypted CA (OpenSSL 3.x pass: prefix) - Add monthly cron job with MAILTO notifications - Add syslog logging (openvpn-crl tag) - Add /etc/openvpn/README operational guide - Remove EASYRSA_NO_PASS from vars (CA is now encrypted) --- debian/changelog | 6 ++ .../manifests/openvpn_server/config.pp | 95 +++++++++++++++---- .../templates/openvpn_server/README.erb | 77 +++++++++++++++ .../openvpn_server/regenerate-crl.sh.erb | 28 ++++++ .../profile/templates/openvpn_server/vars.erb | 1 - 5 files changed, 188 insertions(+), 19 deletions(-) create mode 100644 environments/sandbox/modules/profile/templates/openvpn_server/README.erb create mode 100644 environments/sandbox/modules/profile/templates/openvpn_server/regenerate-crl.sh.erb diff --git a/debian/changelog b/debian/changelog index 0d9c19f..3d9b6cb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +puppet-code (0.1.0-1build280) noble; urgency=medium + + * commit event. see changes history in git log + + -- root Wed, 21 Jan 2026 00:57:49 +0000 + puppet-code (0.1.0-1build279) noble; urgency=medium * commit event. see changes history in git log diff --git a/environments/sandbox/modules/profile/manifests/openvpn_server/config.pp b/environments/sandbox/modules/profile/manifests/openvpn_server/config.pp index ce3144b..06ed616 100644 --- a/environments/sandbox/modules/profile/manifests/openvpn_server/config.pp +++ b/environments/sandbox/modules/profile/manifests/openvpn_server/config.pp @@ -11,6 +11,9 @@ String $openvpn_topology, String $openvpn_network, String $openvpn_netmask, + $mailto = lookup( + 'profile::cron::mailto', undef, undef, "root@${facts['networking']['hostname']}.${facts['networking']['domain']}" + ), ) { $dns_name = $facts['efs']['dns_name'] @@ -29,15 +32,17 @@ mount_target => $openvp_config_directory, } + $ca_passphrase = aws_get_secret( + $facts['openvpn']['ca_key_passphrase_secret'], + $facts['ec2_metadata']['placement']['region'] + ) + file { $openvpn_easyrsa_passin_file: ensure => file, owner => 'root', group => 'root', mode => '0400', - content => aws_get_secret( - $facts['openvpn']['ca_key_passphrase_secret'], - $facts['ec2_metadata']['placement']['region'] - ), + content => $ca_passphrase, require => [ Mount[$openvp_config_directory], ] @@ -52,6 +57,17 @@ ], } + file { "${openvp_config_directory}/README": + ensure => file, + content => template('profile/openvpn_server/README.erb'), + owner => 'root', + group => 'root', + mode => '0644', + require => [ + Mount[$openvp_config_directory], + ], + } + exec {'generate_dh2048_pem': path => '/usr/bin', command => 'openssl dhparam -out dh2048.pem 2048', @@ -115,25 +131,39 @@ ] } + # Note: Passphrase is briefly visible in process environment during execution. + # This is required because Easy-RSA with OpenSSL 3.x doesn't support file: prefix. + # The passphrase file itself is secured at mode 0400 (root read-only). + # Alternative approaches (stdin redirection) are not supported by Easy-RSA batch mode. exec { 'generate_ca': - command => "/usr/share/easy-rsa/easyrsa --vars=${openvp_config_directory}/vars build-ca", - cwd => $openvp_config_directory, - creates => "${openvp_config_directory}/pki/private/ca.key", - require => [ + command => "/usr/share/easy-rsa/easyrsa --vars=${openvp_config_directory}/vars build-ca", + cwd => $openvp_config_directory, + environment => [ + "EASYRSA_PASSIN=pass:${ca_passphrase}", + "EASYRSA_PASSOUT=pass:${ca_passphrase}", + "EASYRSA_REQ_CN=${openvpn_easyrsa_req_org} CA", + ], + creates => "${openvp_config_directory}/pki/private/ca.key", + require => [ Mount[$openvp_config_directory], Package[easy-rsa], File["${openvp_config_directory}/vars"], + File[$openvpn_easyrsa_passin_file], ] } exec { 'generate_server_key': - command => "/usr/share/easy-rsa/easyrsa --vars=${openvp_config_directory}/vars build-server-full server nopass inline", - cwd => $openvp_config_directory, - creates => "${openvp_config_directory}/pki/private/server.key", - require => [ + command => "/usr/share/easy-rsa/easyrsa --vars=${openvp_config_directory}/vars build-server-full server nopass", + cwd => $openvp_config_directory, + environment => [ + "EASYRSA_PASSIN=pass:${ca_passphrase}", + ], + creates => "${openvp_config_directory}/pki/private/server.key", + require => [ Mount[$openvp_config_directory], Package[easy-rsa], File["${openvp_config_directory}/vars"], + Exec[generate_ca], ] } @@ -145,16 +175,45 @@ } exec { 'generate_gen_crl': - command => "/usr/share/easy-rsa/easyrsa --vars=${openvp_config_directory}/vars gen-crl", - cwd => $openvp_config_directory, - creates => $openvpn_crl_path, - require => [ + command => "/usr/share/easy-rsa/easyrsa --vars=${openvp_config_directory}/vars gen-crl", + cwd => $openvp_config_directory, + environment => [ + "EASYRSA_PASSIN=pass:${ca_passphrase}", + ], + creates => $openvpn_crl_path, + require => [ Mount[$openvp_config_directory], Package[easy-rsa], File["${openvp_config_directory}/vars"], - File[$openvpn_easyrsa_passin_file], - Exec[generate_pki], + Exec[generate_ca], ] } + # Certificate Revocation List (CRL) Management: + # - CRL expires every 180 days (EASYRSA_CRL_DAYS in vars.erb) + # - Automated regeneration: Monthly on the 1st at 3 AM + # - Manual regeneration: Run /etc/openvpn/regenerate-crl.sh as root + # - OpenVPN automatically re-reads CRL on each connection (no restart needed) + # - Monitor CRL age: Check /etc/openvpn/pki/crl.pem modification time + # - Logs to syslog with tag 'openvpn-crl' (auth facility) + $crl_regen_script = "${openvp_config_directory}/regenerate-crl.sh" + + file { $crl_regen_script: + ensure => file, + owner => 'root', + group => 'root', + mode => '0700', + content => template('profile/openvpn_server/regenerate-crl.sh.erb'), + require => Exec['generate_gen_crl'], + } + cron { 'regenerate_openvpn_crl': + command => $crl_regen_script, + user => 'root', + hour => 3, + minute => 0, + monthday => 1, + environment => ["MAILTO=${mailto}"], + require => File[$crl_regen_script], + } + } diff --git a/environments/sandbox/modules/profile/templates/openvpn_server/README.erb b/environments/sandbox/modules/profile/templates/openvpn_server/README.erb new file mode 100644 index 0000000..e90ae2d --- /dev/null +++ b/environments/sandbox/modules/profile/templates/openvpn_server/README.erb @@ -0,0 +1,77 @@ +OpenVPN Server - Operational Guide +=================================== +Managed by Puppet - do not edit manually + +This file is deployed by profile::openvpn_server::config + + +Certificate Revocation List (CRL) Management +--------------------------------------------- + +CRL Configuration: + - Expiration: 180 days (EASYRSA_CRL_DAYS in vars) + - Auto-regeneration: Monthly on the 1st at 3 AM + - Location: <%= @openvp_config_directory %>/pki/crl.pem + +Logging: + - Syslog tag: openvpn-crl + - Facility: auth + - View logs: journalctl -t openvpn-crl + +Manual CRL Regeneration: + sudo <%= @openvp_config_directory %>/regenerate-crl.sh + +Check CRL Age: + stat <%= @openvp_config_directory %>/pki/crl.pem | grep Modify + +Check CRL Expiration: + openssl crl -in <%= @openvp_config_directory %>/pki/crl.pem -noout -nextupdate + + +Troubleshooting +--------------- + +CRL regeneration failed: + 1. Check logs: journalctl -t openvpn-crl -n 50 + 2. Verify CA passphrase file exists: ls -la <%= @openvpn_easyrsa_passin_file %> + 3. Test passphrase manually: + cd <%= @openvp_config_directory %> + EASYRSA_PASSIN="pass:$(cat <%= @openvpn_easyrsa_passin_file %>)" \ + /usr/share/easy-rsa/easyrsa --vars=<%= @openvp_config_directory %>/vars gen-crl + 4. Check AWS Secrets Manager access if passphrase file is empty/missing + +OpenVPN not accepting connections: + 1. Check service: systemctl status openvpn@server + 2. Check CRL validity: openssl crl -in <%= @openvp_config_directory %>/pki/crl.pem -noout -nextupdate + 3. Check logs: journalctl -u openvpn@server -n 100 + +Client certificate revoked unexpectedly: + - CRL is re-read on each connection, no restart needed after regeneration + - Check index.txt for certificate status: + cat <%= @openvp_config_directory %>/pki/index.txt + + +Key Files +--------- + +<%= @openvp_config_directory %>/ + pki/ + ca.crt - CA certificate (public) + crl.pem - Certificate Revocation List + private/ + ca.key - CA private key (encrypted) + server.key - Server private key + issued/ + server.crt - Server certificate + index.txt - Certificate database + vars - Easy-RSA configuration + server.conf - OpenVPN server configuration + regenerate-crl.sh - CRL regeneration script + ca_passphrase - CA key passphrase (mode 0400) + + +Related Documentation +--------------------- + +Easy-RSA: https://github.com/OpenVPN/easy-rsa +OpenSSL 3.x issues: https://github.com/OpenVPN/easy-rsa/issues/692 \ No newline at end of file diff --git a/environments/sandbox/modules/profile/templates/openvpn_server/regenerate-crl.sh.erb b/environments/sandbox/modules/profile/templates/openvpn_server/regenerate-crl.sh.erb new file mode 100644 index 0000000..929f978 --- /dev/null +++ b/environments/sandbox/modules/profile/templates/openvpn_server/regenerate-crl.sh.erb @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Regenerate OpenVPN CRL (Certificate Revocation List) +# Managed by Puppet - do not edit manually +# +# Silent on success, outputs errors only (suitable for cron) + +set -e + +cd <%= @openvp_config_directory %> + +# Read passphrase from file and use pass: prefix +# Note: file: prefix causes "Error reading password from BIO" with OpenSSL 3.x in batch mode +# See: https://github.com/OpenVPN/easy-rsa/issues/692 +CA_PASSPHRASE=$(cat <%= @openvpn_easyrsa_passin_file %>) +export EASYRSA_PASSIN="pass:${CA_PASSPHRASE}" + +OUTPUT=$(/usr/share/easy-rsa/easyrsa --vars=<%= @openvp_config_directory %>/vars gen-crl 2>&1) || { + echo "ERROR: Failed to regenerate CRL" >&2 + echo "$OUTPUT" >&2 + logger -t openvpn-crl -p auth.err "Failed to regenerate CRL" + exit 1 +} + +# Log success to syslog for audit trail +logger -t openvpn-crl -p auth.info "Successfully regenerated CRL" + +# OpenVPN re-reads the CRL file on each new connection attempt, +# so no service restart is needed. diff --git a/environments/sandbox/modules/profile/templates/openvpn_server/vars.erb b/environments/sandbox/modules/profile/templates/openvpn_server/vars.erb index 1222ca1..b155052 100644 --- a/environments/sandbox/modules/profile/templates/openvpn_server/vars.erb +++ b/environments/sandbox/modules/profile/templates/openvpn_server/vars.erb @@ -164,7 +164,6 @@ set_var EASYRSA_NS_COMMENT "Easy-RSA Generated Certificate" set_var EASYRSA_TEMP_FILE "$EASYRSA_TEMP_DIR/extensions.temp" set_var EASYRSA_BATCH true -set_var EASYRSA_NO_PASS 1 # !! # NOTE: ADVANCED OPTIONS BELOW THIS POINT # PLAY WITH THEM AT YOUR OWN RISK