From 96088454f18e7c429ea25b30af4e3731f65276d1 Mon Sep 17 00:00:00 2001 From: dtapiacl Date: Mon, 6 Oct 2025 16:13:27 -0300 Subject: [PATCH] (openvpnas) openvpnas module install tests --- Puppetfile | 1 + hieradata/role/openvpnas.yaml | 6 + site/profile/manifests/core/openvpnas.pp | 220 +++++++++++++++++++++++ spec/classes/core/openvpnas_spec.rb | 141 +++++++++++++++ spec/hosts/roles/openvpnas_spec.rb | 29 +++ 5 files changed, 397 insertions(+) create mode 100644 hieradata/role/openvpnas.yaml create mode 100644 site/profile/manifests/core/openvpnas.pp create mode 100644 spec/classes/core/openvpnas_spec.rb create mode 100644 spec/hosts/roles/openvpnas_spec.rb diff --git a/Puppetfile b/Puppetfile index 148eecbaca..2037b76f89 100644 --- a/Puppetfile +++ b/Puppetfile @@ -30,6 +30,7 @@ mod 'lsst/java_artisanal', '3.4.1' mod 'lsst/kubectl', '1.2.0' mod 'lsst/maven', '3.1.0' mod 'lsst/nm', '0.3.0' +mod 'lsst/openvpnas', git: 'https://github.com/lsst-it/puppet-openvpnas', ref: '026a9c4' mod 'lsst/pi', '1.0.0' mod 'lsst/powertop', '0.1.2' mod 'lsst/rke', '2.1.0' diff --git a/hieradata/role/openvpnas.yaml b/hieradata/role/openvpnas.yaml new file mode 100644 index 0000000000..d411e8ff7e --- /dev/null +++ b/hieradata/role/openvpnas.yaml @@ -0,0 +1,6 @@ +--- +classes: + - "profile::core::common" + - "profile::core::openvpnas" + +profile::core::openvpnas::version: "3.0.1_84b60e70" diff --git a/site/profile/manifests/core/openvpnas.pp b/site/profile/manifests/core/openvpnas.pp new file mode 100644 index 0000000000..643d58bb73 --- /dev/null +++ b/site/profile/manifests/core/openvpnas.pp @@ -0,0 +1,220 @@ +# @summary +# Installs and configures OpenVPN Access Server with LDAP authentication and group setup. +# +# @param version +# Sets version lock for OpenVPN package. +# +# @param bind_pw +# Optional. LDAP bind password for OpenVPN Access Server. +# +class profile::core::openvpnas ( + String[1] $version, + Optional[String[1]] $bind_pw = undef, +) { + include profile::core::letsencrypt + + $fqdn = fact('networking.fqdn') + $le_root = "/etc/letsencrypt/live/${fqdn}" + $ldap_pw = pick($bind_pw, 'testpassword') + $sacli = '/usr/local/openvpn_as/scripts/sacli' + + # --- CERTIFICATE MANAGEMENT --- + letsencrypt::certonly { $fqdn: + plugin => 'dns-route53', + manage_cron => true, + } + + class { 'openvpnas': + manage_repo => true, + version => $version, + versionlock_enable => true, + versionlock_release => '1.el9', + manage_service => true, + manage_web_certs => true, + cert_source_path => $le_root, + require => Letsencrypt::Certonly[$fqdn], + } + + exec { 'wait_for_openvpnas_socket': + command => '/bin/true', + unless => '/usr/bin/test -S /usr/local/openvpn_as/etc/sock/sagent', + require => Service['openvpnas'], + } + + exec { 'wait_for_openvpnas_ready': + command => "${sacli} ConfigQuery > /dev/null 2>&1", + tries => 10, + try_sleep => 3, + timeout => 60, + require => Exec['wait_for_openvpnas_socket'], + } + + exec { 'set_auth_module_ldap': + command => "${sacli} --key 'auth.module.type' --value 'ldap' ConfigPut", + unless => "${sacli} ConfigQuery | grep -q '\"auth.module.type\": \"ldap\"'", + require => Exec['wait_for_openvpnas_ready'], + } + + exec { 'set_ldap_primary': + command => "${sacli} --key 'auth.ldap.0.server.0.host' --value 'ipa1.cp.lsst.org' ConfigPut", + unless => "${sacli} ConfigQuery | grep -q '\"auth.ldap.0.server.0.host\": \"ipa1.cp.lsst.org\"'", + require => Exec['set_auth_module_ldap'], + } + + exec { 'set_ldap_secondary': + command => "${sacli} --key 'auth.ldap.0.server.1.host' --value 'ipa1.ls.lsst.org' ConfigPut", + unless => "${sacli} ConfigQuery | grep -q '\"auth.ldap.0.server.1.host\": \"ipa1.ls.lsst.org\"'", + require => Exec['set_ldap_primary'], + } + + exec { 'set_ldap_bind_dn': + command => "${sacli} --key 'auth.ldap.0.bind_dn' --value 'uid=svc_openvpnas,cn=users,cn=accounts,dc=lsst,dc=cloud' ConfigPut", + unless => "${sacli} ConfigQuery | grep -q '\"auth.ldap.0.bind_dn\": \"uid=svc_openvpnas,cn=users,cn=accounts,dc=lsst,dc=cloud\"'", + require => Exec['set_ldap_secondary'], + } + + exec { 'set_ldap_bind_pw': + command => "${sacli} --key 'auth.ldap.0.bind_pw' --value '${ldap_pw}' ConfigPut", + unless => "${sacli} ConfigQuery | grep -q '\"auth.ldap.0.bind_pw\":'", + require => Exec['set_ldap_bind_dn'], + } + + exec { 'set_ldap_users_base_dn': + command => "${sacli} --key 'auth.ldap.0.users_base_dn' --value 'cn=accounts,dc=lsst,dc=cloud' ConfigPut", + unless => "${sacli} ConfigQuery | grep -q '\"auth.ldap.0.users_base_dn\": \"cn=accounts,dc=lsst,dc=cloud\"'", + require => Exec['set_ldap_bind_pw'], + } + + exec { 'set_ldap_uname_attr': + command => "${sacli} --key 'auth.ldap.0.uname_attr' --value 'uid' ConfigPut", + unless => "${sacli} ConfigQuery | grep -q '\"auth.ldap.0.uname_attr\": \"uid\"'", + require => Exec['set_ldap_users_base_dn'], + } + + exec { 'enable_ldap_auth': + command => "${sacli} --key 'auth.ldap.0.enable' --value 'true' ConfigPut", + unless => "${sacli} ConfigQuery | grep -q '\"auth.ldap.0.enable\": \"true\"'", + require => Exec['set_ldap_uname_attr'], + } + + exec { 'restart_openvpnas_after_ldap': + command => "${sacli} start", + refreshonly => true, + subscribe => Exec['enable_ldap_auth'], + } + + exec { 'create_group_vpn_default': + command => "${sacli} --user 'vpn-default' --key 'type' --value 'group' UserPropPut && \ + ${sacli} --user 'vpn-default' --key 'group_declare' --value 'true' UserPropPut && \ + ${sacli} start", + unless => "${sacli} UserPropGet vpn-default | grep -q '\"group_declare\": \"true\"'", + require => Exec['restart_openvpnas_after_ldap'], + path => ['/usr/local/openvpn_as/scripts', '/usr/bin', '/bin'], + } + + exec { 'create_group_vpn_it': + command => "${sacli} --user 'vpn-it' --key 'type' --value 'group' UserPropPut && \ + ${sacli} --user 'vpn-it' --key 'group_declare' --value 'true' UserPropPut && \ + ${sacli} start", + unless => "${sacli} UserPropGet vpn-it | grep -q '\"group_declare\": \"true\"'", + require => Exec['create_group_vpn_default'], + path => ['/usr/local/openvpn_as/scripts', '/usr/bin', '/bin'], + } + + exec { 'grant_admin_to_vpn_it': + command => "${sacli} --user 'vpn-it' --key 'prop_superuser' --value 'true' UserPropPut && ${sacli} start", + unless => "${sacli} UserPropGet vpn-it | grep -q '\"prop_superuser\": \"true\"'", + require => Exec['create_group_vpn_it'], + path => ['/usr/local/openvpn_as/scripts', '/usr/bin', '/bin'], + } + + exec { 'restart_openvpnas_after_groups': + command => "${sacli} start", + refreshonly => true, + subscribe => [Exec['create_group_vpn_default'], Exec['create_group_vpn_it'], Exec['grant_admin_to_vpn_it']], + path => ['/usr/local/openvpn_as/scripts', '/usr/bin', '/bin'], + } + + file { '/root/ldap.py': + ensure => file, + owner => 'root', + group => 'root', + mode => '0644', + content => @(EOF/L) + #!/usr/bin/env python3 + import re + from pyovpn.plugin import * + + re_group = re.compile(r"^CN=([^,]+)", re.IGNORECASE) + + def ldap_groups_parse(res): + ret = set() + for g in res: + m = re.match(re_group, g) + if m: + ret.add(m.groups()[0]) + return ret + + def post_auth(authcred, attributes, authret, info): + group = "vpn-default" + proplist_save = {} + if info.get('auth_method') == 'ldap': + user_dn = info['user_dn'] + with info['ldap_context'] as l: + ldap_groups = set() + if hasattr(l, 'search_ext_s'): + import ldap + ldap_groups = l.search_ext_s(user_dn, ldap.SCOPE_SUBTREE, attrlist=["memberOf"])[0][1]['memberOf'] + if ldap_groups: + ldap_groups = ldap_groups_parse(ldap_groups) + else: + search_base = info['search_base'] + uname_attr = info['ldap_context'].authldap.parms['uname_attr'] + search_filter = '(%s=%s)' % (uname_attr, user_dn) + attribute = 'memberOf' + if l.search(search_base, search_filter, attributes=[attribute]): + ldap_groups = getattr(l.entries[0], attribute).value + if not isinstance(ldap_groups, (list, tuple)): + ldap_groups = {ldap_groups} + if ldap_groups: + ldap_groups = ldap_groups_parse(ldap_groups) + else: + print('POST_AUTH: Ldap groups for user %r not found, please check filters %r' % (user_dn, search_filter)) + + if ldap_groups: + print("LDAP_GROUPS %s" % ldap_groups) + if 'vpn-it' in ldap_groups: + group = "vpn-it" + elif 'vpn-science' in ldap_groups: + group = "vpn-science" + elif 'vpn-users' in ldap_groups: + group = "vpn-users" + elif 'vpn-cucm' in ldap_groups: + group = "vpn-cucm" + elif 'vpn-tssw' in ldap_groups: + group = "vpn-tssw" + elif 'vpn-comm' in ldap_groups: + group = "vpn-comm" + elif 'vpn-clyso' in ldap_groups: + group = "vpn-clyso" + + if group: + print("POST_AUTH: Setting group %r for %r" % (group, user_dn)) + authret['proplist']['conn_group'] = group + proplist_save['conn_group'] = group + else: + print("POST_AUTH: No mapping found for %r, using default" % user_dn) + authret['proplist']['conn_group'] = group + proplist_save['conn_group'] = group + return authret, proplist_save + | EOF + } + + exec { 'install_post_auth_script': + command => "${sacli} -k auth.module.post_auth_script --value_file=/root/ldap.py ConfigPut && ${sacli} start", + refreshonly => true, + subscribe => File['/root/ldap.py'], + require => Exec['restart_openvpnas_after_groups'], + path => ['/usr/local/openvpn_as/scripts', '/usr/bin', '/bin'], + } +} diff --git a/spec/classes/core/openvpnas_spec.rb b/spec/classes/core/openvpnas_spec.rb new file mode 100644 index 0000000000..b8731945a2 --- /dev/null +++ b/spec/classes/core/openvpnas_spec.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'profile::core::openvpnas' do + on_supported_os.each do |os, os_facts| + next unless os =~ %r{almalinux-9-x86_64} + + context "on #{os}" do + let(:facts) do + os_facts.merge( + networking: { + fqdn: 'foo.example.com', + } + ) + end + + let(:fqdn) { facts[:networking][:fqdn] } + let(:le_root) { "/etc/letsencrypt/live/#{fqdn}" } + let(:sacli) { '/usr/local/openvpn_as/scripts/sacli' } + + context 'with default parameters' do + let(:params) do + { + version: '3.0.1_84b60e70', + } + end + + it { is_expected.to compile.with_all_deps } + + it do + is_expected.to contain_letsencrypt__certonly(fqdn).with( + plugin: 'dns-route53', + manage_cron: true + ) + end + + it do + is_expected.to contain_class('openvpnas').with( + manage_repo: true, + version: '3.0.1_84b60e70', + versionlock_enable: true, + versionlock_release: '1.el9', + manage_service: true, + manage_web_certs: true, + cert_source_path: le_root, + require: "Letsencrypt::Certonly[#{fqdn}]" + ) + end + + it do + is_expected.to contain_exec('wait_for_openvpnas_socket').with( + command: '/bin/true', + unless: '/usr/bin/test -S /usr/local/openvpn_as/etc/sock/sagent', + require: 'Service[openvpnas]' + ) + end + + it do + is_expected.to contain_exec('wait_for_openvpnas_ready').with( + command: "#{sacli} ConfigQuery > /dev/null 2>&1", + tries: 10, + try_sleep: 3, + timeout: 60, + require: 'Exec[wait_for_openvpnas_socket]' + ) + end + + it do + is_expected.to contain_exec('set_auth_module_ldap').with( + command: "#{sacli} --key 'auth.module.type' --value 'ldap' ConfigPut", + unless: "#{sacli} ConfigQuery | grep -q '\"auth.module.type\": \"ldap\"'", + require: 'Exec[wait_for_openvpnas_ready]' + ) + end + + it do + is_expected.to contain_exec('set_ldap_primary').with( + command: "#{sacli} --key 'auth.ldap.0.server.0.host' --value 'ipa1.cp.lsst.org' ConfigPut", + unless: "#{sacli} ConfigQuery | grep -q '\"auth.ldap.0.server.0.host\": \"ipa1.cp.lsst.org\"'", + require: 'Exec[set_auth_module_ldap]' + ) + end + + it do + is_expected.to contain_exec('set_ldap_secondary').with( + command: "#{sacli} --key 'auth.ldap.0.server.1.host' --value 'ipa1.ls.lsst.org' ConfigPut", + unless: "#{sacli} ConfigQuery | grep -q '\"auth.ldap.0.server.1.host\": \"ipa1.ls.lsst.org\"'", + require: 'Exec[set_ldap_primary]' + ) + end + + it do + is_expected.to contain_exec('enable_ldap_auth').with( + command: "#{sacli} --key 'auth.ldap.0.enable' --value 'true' ConfigPut", + unless: "#{sacli} ConfigQuery | grep -q '\"auth.ldap.0.enable\": \"true\"'", + require: 'Exec[set_ldap_uname_attr]' + ) + end + + it do + is_expected.to contain_exec('restart_openvpnas_after_ldap').with( + command: "#{sacli} start", + refreshonly: true, + subscribe: 'Exec[enable_ldap_auth]' + ) + end + + it do + is_expected.to contain_exec('restart_openvpnas_after_groups').with( + command: "#{sacli} start", + refreshonly: true, + subscribe: ['Exec[create_group_vpn_default]', 'Exec[create_group_vpn_it]', 'Exec[grant_admin_to_vpn_it]'], + path: ['/usr/local/openvpn_as/scripts', '/usr/bin', '/bin'] + ) + end + + it do + is_expected.to contain_file('/root/ldap.py').with( + ensure: 'file', + owner: 'root', + group: 'root', + mode: '0644' + ).with_content(%r{#!/usr/bin/env python3}) + .with_content(%r{def post_auth\(}) + .with_content(%r{group = "vpn-default"}) + end + + it do + is_expected.to contain_exec('install_post_auth_script').with( + command: "#{sacli} -k auth.module.post_auth_script --value_file=/root/ldap.py ConfigPut && #{sacli} start", + refreshonly: true, + subscribe: 'File[/root/ldap.py]', + require: 'Exec[restart_openvpnas_after_groups]', + path: ['/usr/local/openvpn_as/scripts', '/usr/bin', '/bin'] + ) + end + end + end + end +end diff --git a/spec/hosts/roles/openvpnas_spec.rb b/spec/hosts/roles/openvpnas_spec.rb new file mode 100644 index 0000000000..05ee131dbd --- /dev/null +++ b/spec/hosts/roles/openvpnas_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +role = 'openvpnas' + +describe "#{role} role" do + on_supported_os.each do |os, os_facts| + next unless os =~ %r{almalinux-9-x86_64} + + context "on #{os}" do + lsst_sites.each do |site| + describe "#{role}.#{site}.lsst.org", :sitepp do + let(:node_params) do + { + role:, + site:, + } + end + let(:facts) { lsst_override_facts(os_facts) } + + it { is_expected.to compile.with_all_deps } + + include_examples('common', os_facts:, site:) + end # host + end # lsst_sites + end # on os + end # on_supported_os +end # role