From 1cdafa9fa35b28ea60becf17cba8416ce7a85ebc Mon Sep 17 00:00:00 2001 From: Maxime Boissonneault Date: Thu, 18 Jun 2026 12:50:45 -0400 Subject: [PATCH 1/8] use the same 92-local_auth.py file for all auths. change single quotes to double quotes to allow substitution --- manifests/auth/ldap.pp | 2 +- manifests/auth/saml2.pp | 4 +--- manifests/install.pp | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/manifests/auth/ldap.pp b/manifests/auth/ldap.pp index 2ece1b2..174f173 100644 --- a/manifests/auth/ldap.pp +++ b/manifests/auth/ldap.pp @@ -99,7 +99,7 @@ before => Uv::Venv['metrix_venv'], } - file { '/var/www/metrix/userportal/settings/92-local_ldap.py': + file { '/var/www/metrix/userportal/settings/92-local_auth.py': show_diff => false, content => epp('metrix/92-local_ldap.py', { diff --git a/manifests/auth/saml2.pp b/manifests/auth/saml2.pp index c2327a3..64e25e3 100644 --- a/manifests/auth/saml2.pp +++ b/manifests/auth/saml2.pp @@ -30,13 +30,11 @@ require => File['/var/www/metrix'], } - file { '/var/www/metrix/userportal/settings/92-local_saml2.py': + file { '/var/www/metrix/userportal/settings/92-local_auth.py': show_diff => false, content => epp('metrix/92-local_saml2.py', { 'extra_required_attributes' => $extra_required_attributes, - 'staff_attributes' => $staff_attributes, - 'required_access_attributes' => $required_access_attributes } ), owner => 'apache', diff --git a/manifests/install.pp b/manifests/install.pp index 7a7bee7..1c78bca 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -1,5 +1,5 @@ class metrix::install ( - String $source_url = 'https://github.com/guilbaults/TrailblazingTurtle/archive/refs/tags/v${version}.tar.gz', + String $source_url = "https://github.com/guilbaults/TrailblazingTurtle/archive/refs/tags/v${version}.tar.gz", String $version = '1.7.0', String $python_version = '3.13', ) { From 51d2ba229912cd97619b9b99409e7da575fb5e15 Mon Sep 17 00:00:00 2001 From: Maxime Boissonneault Date: Thu, 18 Jun 2026 14:26:02 -0400 Subject: [PATCH 2/8] slurm_user must be given to the template --- manifests/init.pp | 1 + 1 file changed, 1 insertion(+) diff --git a/manifests/init.pp b/manifests/init.pp index 014ca38..1d0f2f9 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -42,6 +42,7 @@ content => epp('metrix/91-local.py', { 'password' => $password, + 'slurm_user' => $slurm_user, 'slurm_password' => $slurm_password, 'cluster_name' => $cluster_name, 'secret_key' => seeded_rand_string(32, $password), From 017396b964fa7d2621ca3a4e6000b7346781b909 Mon Sep 17 00:00:00 2001 From: Maxime Boissonneault Date: Thu, 18 Jun 2026 14:36:00 -0400 Subject: [PATCH 3/8] BASE_URL must have a trailing / --- templates/91-local.py.epp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/91-local.py.epp b/templates/91-local.py.epp index 5cb347d..67be4dc 100644 --- a/templates/91-local.py.epp +++ b/templates/91-local.py.epp @@ -4,7 +4,7 @@ pymysql.install_as_MySQLdb() SECRET_KEY = '<%= $secret_key %>' DEBUG = False -BASE_URL = 'https://<%= $subdomain %>.<%= $domain_name %>' +BASE_URL = 'https://<%= $subdomain %>.<%= $domain_name %>/' ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] CSRF_TRUSTED_ORIGINS = [BASE_URL] From 693a1cfd9f2912ad2e855c8b0a7a08c556808df7 Mon Sep 17 00:00:00 2001 From: Maxime Boissonneault Date: Thu, 18 Jun 2026 14:38:35 -0400 Subject: [PATCH 4/8] staff_attributes and required_access_attributes are not parth of auth but of config --- manifests/auth/saml2.pp | 2 -- 1 file changed, 2 deletions(-) diff --git a/manifests/auth/saml2.pp b/manifests/auth/saml2.pp index 64e25e3..6145ac8 100644 --- a/manifests/auth/saml2.pp +++ b/manifests/auth/saml2.pp @@ -3,8 +3,6 @@ String $ssl_public_cert, String $idp_metadata, Array[String] $extra_required_attributes = [], - Array[Hash[String, String]] $staff_attributes = [], - Array[Hash[String, String]] $required_access_attributes = [], ) { ensure_packages(['libffi-devel', 'xmlsec1', 'xmlsec1-openssl']) From 012de7805366e4676013e6d000f128ab85c4c407 Mon Sep 17 00:00:00 2001 From: Maxime Boissonneault Date: Fri, 19 Jun 2026 10:54:33 -0400 Subject: [PATCH 5/8] ssl attributes are for saml2 auth, move them there --- manifests/init.pp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/manifests/init.pp b/manifests/init.pp index 1d0f2f9..760bd39 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -13,16 +13,11 @@ String $cluster_name, String $subdomain, String $slurm_user = 'slurm', - 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[Hash[String, String]] $staff_attributes = [], Array[Hash[String, String]] $required_access_attributes = [], Optional[String] $slurm_db_ip = undef, Optional[Integer] $slurm_db_port = undef, - Optional[String] $ssl_private_key = undef, - Optional[String] $ssl_public_cert = undef, - Optional[String] $idp_metadata = undef, ) { include metrix::install case $auth_type { From c08a4a1ae7fec2a193f00c27f275d7902364689c Mon Sep 17 00:00:00 2001 From: Maxime Boissonneault Date: Fri, 19 Jun 2026 11:39:39 -0400 Subject: [PATCH 6/8] update SAML config and CSRF_TRUSTED_ORIGINS for BASE_URL with trailing slash --- templates/91-local.py.epp | 2 +- templates/92-local_saml2.py.epp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/91-local.py.epp b/templates/91-local.py.epp index 67be4dc..411c26f 100644 --- a/templates/91-local.py.epp +++ b/templates/91-local.py.epp @@ -7,7 +7,7 @@ DEBUG = False BASE_URL = 'https://<%= $subdomain %>.<%= $domain_name %>/' ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] -CSRF_TRUSTED_ORIGINS = [BASE_URL] +CSRF_TRUSTED_ORIGINS = [BASE_URL.strip('/')] AUTH_LDAP_SERVER_URI = 'ldaps://ipa.int.<%= $domain_name %>/' AUTH_LDAP_BIND_DN = 'uid=admin,cn=users,cn=accounts,<%= $base_dn %>', diff --git a/templates/92-local_saml2.py.epp b/templates/92-local_saml2.py.epp index 49827b8..9b04df2 100644 --- a/templates/92-local_saml2.py.epp +++ b/templates/92-local_saml2.py.epp @@ -1,6 +1,6 @@ AUTHENTICATION_BACKENDS += ['userportal.authentication.staffSaml2Backend'] -SAML_CONFIG['service']['sp']['endpoints']['assertion_consumer_service'] = (BASE_URL + '/saml2/acs/', saml2.BINDING_HTTP_POST) -SAML_CONFIG['entityid'] = BASE_URL + '/saml2/metadata/' +SAML_CONFIG['service']['sp']['endpoints']['assertion_consumer_service'] = (BASE_URL + 'saml2/acs/', saml2.BINDING_HTTP_POST) +SAML_CONFIG['entityid'] = BASE_URL + 'saml2/metadata/' SAML_CONFIG['key_file'] = '/var/www/metrix/saml2-private.key' SAML_CONFIG['cert_file'] = '/var/www/metrix/saml2-public.pem' SAML_CONFIG['encryption_keypairs'][0]['key_file'] = '/var/www/metrix/saml2-private.key' From 08170accf51f54596727674922de6c47c51fc88e Mon Sep 17 00:00:00 2001 From: Maxime Boissonneault Date: Fri, 19 Jun 2026 15:38:34 -0400 Subject: [PATCH 7/8] add the actual domain in the ALLOWED_HOSTS --- templates/91-local.py.epp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/91-local.py.epp b/templates/91-local.py.epp index 411c26f..00449db 100644 --- a/templates/91-local.py.epp +++ b/templates/91-local.py.epp @@ -6,7 +6,7 @@ DEBUG = False BASE_URL = 'https://<%= $subdomain %>.<%= $domain_name %>/' -ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] +ALLOWED_HOSTS = ['<%= $subdomain %>.<%= $domain_name%>', '127.0.0.1', 'localhost'] CSRF_TRUSTED_ORIGINS = [BASE_URL.strip('/')] AUTH_LDAP_SERVER_URI = 'ldaps://ipa.int.<%= $domain_name %>/' From c57c4d02db18f499de6d686cda5cbaaeb1810b1b Mon Sep 17 00:00:00 2001 From: Maxime Boissonneault Date: Fri, 19 Jun 2026 17:03:38 -0400 Subject: [PATCH 8/8] added support for oidc --- manifests/auth/oidc.pp | 38 ++++++++++++++++++++++++++ manifests/init.pp | 5 +++- templates/92-local_oidc.py.epp | 49 ++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 manifests/auth/oidc.pp create mode 100644 templates/92-local_oidc.py.epp diff --git a/manifests/auth/oidc.pp b/manifests/auth/oidc.pp new file mode 100644 index 0000000..3d7866f --- /dev/null +++ b/manifests/auth/oidc.pp @@ -0,0 +1,38 @@ +class metrix::auth::oidc ( + String $authorization_endpoint, + String $token_endpoint, + String $user_endpoint, + String $jwks_endpoint, + String $client_id, + String $client_secret, + Boolean $proxied = false, + Array[String] $extra_scopes = [], +) { + + file_line { 'mozilla-django-oidc': + ensure => present, + path => '/var/www/metrix/requirements.txt', + before => Uv::Venv['metrix_venv'], + line => 'mozilla-django-oidc~=5.0.2', + } + file { '/var/www/metrix/userportal/settings/92-local_auth.py': + show_diff => false, + content => epp('metrix/92-local_oidc.py', + { + 'extra_scopes' => $extra_scopes, + 'authorization_endpoint' => $authorization_endpoint, + 'token_endpoint' => $token_endpoint, + 'user_endpoint' => $user_endpoint, + 'jwks_endpoint' => $jwks_endpoint, + 'proxied' => $proxied, + 'client_id' => $client_id, + 'client_secret' => $client_secret, + } + ), + owner => 'apache', + group => 'apache', + mode => '0600', + require => Class['metrix::install'], + notify => Service['metrix'], + } +} diff --git a/manifests/init.pp b/manifests/init.pp index 760bd39..a0df17b 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -13,7 +13,7 @@ String $cluster_name, String $subdomain, String $slurm_user = 'slurm', - Enum['ldap', 'saml2'] $auth_type = 'ldap', + Enum['ldap', 'saml2', 'oidc'] $auth_type = 'ldap', Array[Hash[String, String]] $staff_attributes = [], Array[Hash[String, String]] $required_access_attributes = [], Optional[String] $slurm_db_ip = undef, @@ -27,6 +27,9 @@ 'saml2': { include metrix::auth::saml2 } + 'oidc': { + include metrix::auth::oidc + } default: { fail('Unsupported auth_type') } diff --git a/templates/92-local_oidc.py.epp b/templates/92-local_oidc.py.epp new file mode 100644 index 0000000..debb868 --- /dev/null +++ b/templates/92-local_oidc.py.epp @@ -0,0 +1,49 @@ +INSTALLED_APPS += ['mozilla_django_oidc'] + +AUTHENTICATION_BACKENDS = ['userportal.authentication.staffOIDCBackend'] +LOGIN_URL = '/oidc/authenticate/' +LOGIN_REDIRECT_URL = '/' +LOGOUT_REDIRECT_URL = '/' + +# OpenID Connect Provider configurations: +OIDC_OP_AUTHORIZATION_ENDPOINT = '<%= $authorization_endpoint %>' +OIDC_OP_TOKEN_ENDPOINT = '<%= $token_endpoint %>' +OIDC_OP_USER_ENDPOINT = '<%= $user_endpoint %>' +OIDC_OP_JWKS_ENDPOINT = '<%= $jwks_endpoint %>' + +OIDC_TOKEN_USE_BASIC_AUTH = True + +OIDC_RP_CLIENT_ID = '<%= $client_id %>' +OIDC_RP_CLIENT_SECRET = '<%= $client_secret %>' + +<% if $proxied { -%> +USE_X_FORWARDED_HOST = True +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +MIDDLEWARE = ['multipleproxy.middleware.MultipleProxyMiddleware'] + MIDDLEWARE +<% } -%> + +# Algorithm for verifying JWT signatures (e.g. RS256, HS256) +OIDC_RP_SIGN_ALGO = 'RS256' + +# Custom scopes if needed +OIDC_RP_SCOPES = 'openid ' +OIDC_RP_SCOPES += ' '.join([ +<% $extra_scopes.each |$scope| { -%> + '<%= $scope %>', +<% } -%> +]) + +# If set to True, a new Django user will be created if one does not exist +OIDC_CREATE_USER = True + +# Username claim to use for matching the LDAP username. +# Our custom staffOIDCBackend cleans the domain part if the claim contains an email or full principal name. +OIDC_USERNAME_CLAIM = 'preferred_username' + +# Use this to define if the user can login. +# List of tuples of (claim_name, expected_value). All must match. +OIDC_REQUIRED_ACCESS_ATTRIBUTES = REQUIRED_ACCESS_ATTRIBUTES + +# Use this to assign the staff role based on claims returned by OIDC. +# List of tuples of (claim_name, expected_value). If ANY matches, the user is_staff will be set to True. +OIDC_STAFF_ATTRIBUTES = STAFF_ATTRIBUTES