diff --git a/manifests/init.pp b/manifests/init.pp index 2c4001e..e6bad4b 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -12,6 +12,15 @@ String $slurm_password, String $cluster_name, String $subdomain, + String $ssl_private_key_file = '/etc/ssl/metrix.private.key', + String $ssl_public_cert_file = '/etc/ssl/metrix.public.cert', + Enum['ldap', 'saml2'] $auth_type = 'ldap', + Array[String] $saml2_extra_required_attributes = [], + Array[Hash[String, String]] $staff_attributes = [], + Array[Hash[String, String]] $required_access_attributes = [], + Optional[String] $ssl_private_key = undef, + Optional[String] $ssl_public_cert = undef, + Optional[String] $idp_metadata = undef, ) { include metrix::install @@ -19,19 +28,25 @@ show_diff => false, content => epp('metrix/99-local.py', { - 'password' => $password, - 'slurm_password' => $slurm_password, - 'cluster_name' => $cluster_name, - 'secret_key' => seeded_rand_string(32, $password), - 'domain_name' => $domain_name, - 'subdomain' => $subdomain, - 'logins' => $logins, - 'prometheus_ip' => $prometheus_ip, - 'prometheus_port' => $prometheus_port, - 'db_ip' => $db_ip, - 'db_port' => $db_port, - 'base_dn' => $base_dn, - 'ldap_password' => $ldap_password, + 'password' => $password, + 'slurm_password' => $slurm_password, + 'cluster_name' => $cluster_name, + 'secret_key' => seeded_rand_string(32, $password), + 'domain_name' => $domain_name, + 'subdomain' => $subdomain, + 'logins' => $logins, + 'prometheus_ip' => $prometheus_ip, + 'prometheus_port' => $prometheus_port, + 'db_ip' => $db_ip, + 'db_port' => $db_port, + 'base_dn' => $base_dn, + 'ldap_password' => $ldap_password, + 'auth_type' => $auth_type, + 'ssl_key_file' => $ssl_private_key_file, + 'ssl_cert_file' => $ssl_public_cert_file, + 'saml2_extra_required_attributes' => $saml2_extra_required_attributes, + 'staff_attributes' => $staff_attributes, + 'required_access_attributes' => $required_access_attributes, } ), owner => 'apache', @@ -131,6 +146,31 @@ mode => '0600', } + if $ssl_private_key != undef { + file { $ssl_private_key_file: + content => $ssl_private_key, + mode => '0400', + owner => 'apache', + group => 'apache', + } + } + if $ssl_public_cert != undef { + file { $ssl_public_cert_file: + content => $ssl_public_cert, + mode => '0422', + owner => 'apache', + group => 'apache', + } + } + if $idp_metadata != undef { + file { '/var/www/metrix/idp_metadata.xml': + content => $idp_metadata, + mode => '0422', + owner => 'apache', + group => 'apache', + } + } + exec { 'metrix_api_token': command => Sensitive($api_token_command), subscribe => [ diff --git a/manifests/install.pp b/manifests/install.pp index 390d989..d5a1a6e 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -1,8 +1,13 @@ class metrix::install ( - String $version = '1.6.0', + String $version = '1.7.0', String $python_version = '3.13', ) { - ensure_packages(['gcc', 'openldap-devel',]) + $auth_type = lookup('metrix::auth_type') + + if $auth_type == 'saml2' { + ensure_packages(['libffi-devel', 'xmlsec1', 'xmlsec1-openssl']) + } + ensure_packages(['gcc', 'openldap-devel']) file { '/var/www/metrix/': ensure => 'directory', @@ -20,85 +25,88 @@ cleanup => true, user => 'apache', } - # We use LDAP auth instead of SAML2 auth, so we can remove all - # code and dependencies related to SAML2 - -> file_line { 'remove_saml2_urls': - ensure => absent, - path => '/var/www/metrix/userportal/urls.py', - match => 'saml2', - match_for_absence => true, - multiple => true, - } - -> file_line { 'remove_saml2_10-base': - ensure => absent, - path => '/var/www/metrix/userportal/settings/10-base.py', - match => 'saml2', - match_for_absence => true, - multiple => true, - } - -> file { 'remove_40-saml': - ensure => absent, - path => '/var/www/metrix/userportal/settings/40-saml.py', - } - -> file_line { 'cffi': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^cffi', - match_for_absence => true, - } - -> file_line { 'cryptography': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^cryptography', - match_for_absence => true, - } - -> file_line { 'defusedxml': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^defusedxml', - match_for_absence => true, - } - -> file_line { 'djangosaml2': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^djangosaml2', - match_for_absence => true, - } - -> file_line { 'elementpath': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^elementpath', - match_for_absence => true, - } - -> file_line { 'pycparser': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^pycparser', - match_for_absence => true, - } - -> file_line { 'pyparsing': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^pyparsing', - match_for_absence => true, - } - -> file_line { 'pysaml2': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^pysaml2', - match_for_absence => true, - } - -> file_line { 'pyOpenSSL': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^pyOpenSSL', - match_for_absence => true, - } - -> file_line { 'xmlschema': - ensure => absent, - path => '/var/www/metrix/requirements.txt', - match => '^xmlschema', - match_for_absence => true, + if $auth_type == 'ldap' { + # We use LDAP auth instead of SAML2 auth, so we can remove all + # code and dependencies related to SAML2 + file_line { 'remove_saml2_urls': + ensure => absent, + path => '/var/www/metrix/userportal/urls.py', + match => 'saml2', + match_for_absence => true, + multiple => true, + require => Archive['metrix'] + } + -> file_line { 'remove_saml2_10-base': + ensure => absent, + path => '/var/www/metrix/userportal/settings/10-base.py', + match => 'saml2', + match_for_absence => true, + multiple => true, + } + -> file { 'remove_40-saml': + ensure => absent, + path => '/var/www/metrix/userportal/settings/40-saml.py', + } + -> file_line { 'cffi': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^cffi', + match_for_absence => true, + } + -> file_line { 'cryptography': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^cryptography', + match_for_absence => true, + } + -> file_line { 'defusedxml': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^defusedxml', + match_for_absence => true, + } + -> file_line { 'djangosaml2': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^djangosaml2', + match_for_absence => true, + } + -> file_line { 'elementpath': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^elementpath', + match_for_absence => true, + } + -> file_line { 'pycparser': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^pycparser', + match_for_absence => true, + } + -> file_line { 'pyparsing': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^pyparsing', + match_for_absence => true, + } + -> file_line { 'pysaml2': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^pysaml2', + match_for_absence => true, + } + -> file_line { 'pyOpenSSL': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^pyOpenSSL', + match_for_absence => true, + } + -> file_line { 'xmlschema': + ensure => absent, + path => '/var/www/metrix/requirements.txt', + match => '^xmlschema', + match_for_absence => true, + } } # Next dependencies are not used by Trailblazing Turtle # they are dependencies of matplotlib which should be optional @@ -106,11 +114,12 @@ # so we remove the dependencies and install a fork of prometheus-api-client # that only make matplotlib optional. # See: https://github.com/4n4nd/prometheus-api-client-python/pull/303 - -> file_line { 'contourpy': + file_line { 'contourpy': ensure => absent, path => '/var/www/metrix/requirements.txt', match => '^contourpy', match_for_absence => true, + require => Archive['metrix'], } -> file_line { 'cycler': ensure => absent, @@ -173,22 +182,38 @@ path => '/var/www/metrix/requirements.txt', match => '^mysqlclient', line => 'pymysql~=1.1', - } - -> uv::venv { 'metrix_venv': - prefix => '/opt/software/metrix-env', - python => $python_version, - requirements => 'django-auth-ldap', - requirements_path => '/var/www/metrix/requirements.txt', - require => [ - Package['gcc'], - Package['openldap-devel'], - ], + before => Uv::Venv['metrix_venv'], + } + if $auth_type == 'ldap' { + uv::venv { 'metrix_venv': + prefix => '/opt/software/metrix-env', + python => $python_version, + requirements => 'django-auth-ldap', + requirements_path => '/var/www/metrix/requirements.txt', + require => [ + Package['gcc'], + Package['openldap-devel'], + ], + } + } + else { + uv::venv { 'metrix_venv': + prefix => '/opt/software/metrix-env', + python => $python_version, + requirements_path => '/var/www/metrix/requirements.txt', + require => [ + Package['gcc'], + Package['openldap-devel'], + Package['libffi-devel'], + ], + } } # Replace mysqlclient by pymysql in the Python code import. - -> file_line { 'pymysql': + file_line { 'pymysql': path => '/var/www/metrix/manage.py', after => '^import sys', line => 'import pymysql; pymysql.install_as_MySQLdb()', + require => Uv::Venv['metrix_venv'] } -> file_line { 'manage.py_header': path => '/var/www/metrix/manage.py', diff --git a/templates/99-local.py.epp b/templates/99-local.py.epp index 3f53df7..31085da 100644 --- a/templates/99-local.py.epp +++ b/templates/99-local.py.epp @@ -14,7 +14,6 @@ AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,cn=users,cn=accounts,<%= $base_dn %>" LDAP_BASE_DN = '<%= $base_dn %>' - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', @@ -42,7 +41,6 @@ DATABASES = { import ldap ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW) - PROMETHEUS = { 'url': 'http://<%= $prometheus_ip %>:<%= $prometheus_port %>', 'headers': {}, @@ -55,12 +53,51 @@ STATIC_URL = '/static/' STATIC_ROOT = '/var/www/metrix-static/' AUTHENTICATION_BACKENDS = [ - 'django_auth_ldap.backend.LDAPBackend', 'django.contrib.auth.backends.ModelBackend', ] +REQUIRED_ACCESS_ATTRIBUTES = [ +<% $required_access_attributes.each |$pair| { -%> +<% $pair.each |$attribute, $value| { -%> + ('<%= $attribute %>', '<%= $value %>'), +<% } -%> +<% } -%> +] + +STAFF_ATTRIBUTES = [ +<% $staff_attributes.each |$pair| { -%> +<% $pair.each |$attribute, $value| { -%> + ('<%= $attribute %>', '<%= $value %>'), +<% } -%> +<% } -%> +] + + +<% if $auth_type == 'ldap' { %> +AUTHENTICATION_BACKENDS += ['django_auth_ldap.backend.LDAPBackend'] LOGIN_URL = '/accounts/login/' # So it does not use SAML2 +<% } elsif $auth_type == 'saml2' { %> + +AUTHENTICATION_BACKENDS += ['userportal.authentication.staffSaml2Backend'] +SAML_CONFIG['service']['sp']['endpoints']['assertion_consumer_service'] = ('https://<%= $subdomain %>.<%= $domain_name %>/saml2/acs/', saml2.BINDING_HTTP_POST) +SAML_CONFIG['entityid'] = 'https://<%= $subdomain %>.<%= $domain_name %>/saml2/metadata/' +SAML_CONFIG['key_file'] = '<%= $ssl_key_file %>' +SAML_CONFIG['cert_file'] = '<%= $ssl_cert_file %>' +SAML_CONFIG['encryption_keypairs'][0]['key_file'] = '<%= $ssl_key_file %>' +SAML_CONFIG['encryption_keypairs'][0]['cert_file'] = '<%= $ssl_cert_file %>' +SAML_CONFIG['metadata']['local'][0] = '/var/www/metrix/idp_metadata.xml' +SAML_CONFIG['service']['sp']['required_attributes'] += [ +<% $saml2_extra_required_attributes.each |$attribute| { -%> + '<%= $attribute %>', +<% } -%> +] +SAML_CONFIG['required_access_attributes'] = REQUIRED_ACCESS_ATTRIBUTES +SAML_CONFIG['staff_attributes'] = STAFF_ATTRIBUTES + +<% } %> + + EXPORTER_INSTALLED = [ 'slurm-job-exporter', 'node_exporter',