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/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/auth/saml2.pp b/manifests/auth/saml2.pp index c2327a3..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']) @@ -30,13 +28,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/init.pp b/manifests/init.pp index 014ca38..a0df17b 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', + 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, 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 { @@ -32,6 +27,9 @@ 'saml2': { include metrix::auth::saml2 } + 'oidc': { + include metrix::auth::oidc + } default: { fail('Unsupported auth_type') } @@ -42,6 +40,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), 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', ) { diff --git a/templates/91-local.py.epp b/templates/91-local.py.epp index 5cb347d..00449db 100644 --- a/templates/91-local.py.epp +++ b/templates/91-local.py.epp @@ -4,10 +4,10 @@ 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] +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 %>/' AUTH_LDAP_BIND_DN = 'uid=admin,cn=users,cn=accounts,<%= $base_dn %>', 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 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'