diff --git a/manifests/auth/oauth2jwt.pp b/manifests/auth/oauth2jwt.pp new file mode 100644 index 0000000..155616d --- /dev/null +++ b/manifests/auth/oauth2jwt.pp @@ -0,0 +1,29 @@ +class metrix::auth::oauth2jwt ( + String $authorization_url, + String $token_url, + String $client_id, + String $client_secret, + Hash[String, String] $extra_token_params = {}, + Boolean $proxied = false, +) { + + file { '/var/www/metrix/userportal/settings/92-local_auth.py': + show_diff => false, + content => epp('metrix/92-local_oauth2jwt.py', + { + 'extra_token_params' => $extra_token_params, + 'authorization_url' => $authorization_url, + 'token_url' => $token_url, + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'proxied' => $proxied, + } + ), + owner => 'apache', + group => 'apache', + mode => '0600', + require => Class['metrix::install'], + notify => Service['metrix'], + } +} + diff --git a/manifests/auth/oidc.pp b/manifests/auth/oidc.pp new file mode 100644 index 0000000..7d7729f --- /dev/null +++ b/manifests/auth/oidc.pp @@ -0,0 +1,33 @@ +class metrix::auth::oidc ( + String $authorization_endpoint, + String $token_endpoint, + String $client_id, + String $client_secret, + 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, + '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 18ebf79..0146095 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', 'oauth2jwt'] $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,12 @@ 'saml2': { include metrix::auth::saml2 } + 'oidc': { + include metrix::auth::oidc + } + 'oauth2jwt': { + include metrix::auth::oauth2jwt + } default: { fail('Unsupported auth_type') } diff --git a/templates/92-local_oauth2jwt.py.epp b/templates/92-local_oauth2jwt.py.epp new file mode 100644 index 0000000..f2554ea --- /dev/null +++ b/templates/92-local_oauth2jwt.py.epp @@ -0,0 +1,39 @@ +JWT_OAUTH2_AUTH_ENABLED = True +AUTHENTICATION_BACKENDS = ['userportal.authentication.staffOAuth2JWTBackend'] + AUTHENTICATION_BACKENDS + +LOGIN_URL = '/oauth2/login/' +LOGIN_REDIRECT_URL = '/' +LOGOUT_REDIRECT_URL = '/' + +# OAuth2JWT provider configurations: +JWT_OAUTH2_AUTHORIZATION_URL = '<%= $authorization_url %>' +JWT_OAUTH2_TOKEN_URL = '<%= $token_url %>' +JWT_OAUTH2_EXTRA_TOKEN_PARAMS = { +<% $extra_token_params.each |$attribute, $value| { -%> + '<%= $attribute %>': '<%= $value %>', +<% } -%> +} + + +JWT_OAUTH2_CLIENT_ID = '<%= $client_id %>' +JWT_OAUTH2_CLIENT_SECRET = '<%= $client_secret %>' + +# Algorithm for verifying JWT signatures (e.g. RS256, HS256) +JWT_OAUTH2_RP_SIGN_ALGO = 'RS256' + +# If set to True, a new Django user will be created if one does not exist +JWT_OAUTH2_CREATE_USER = True + +# Use this to define if the user can login. +# List of tuples of (claim_name, expected_value). All must match. +JWT_OAUTH2_REQUIRED_ACCESS_ATTRIBUTES = REQUIRED_ACCESS_ATTRIBUTES + +# Use this to assign the staff role based on claims returned by the IdP +# List of tuples of (claim_name, expected_value). If ANY matches, the user is_staff will be set to True +JWT_OAUTH2_STAFF_ATTRIBUTES = STAFF_ATTRIBUTES + +<% if $proxied { -%> +USE_X_FORWARDED_HOST = True +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +MIDDLEWARE = ['multipleproxy.middleware.MultipleProxyMiddleware'] + MIDDLEWARE +<% } -%> diff --git a/templates/92-local_oidc.py.epp b/templates/92-local_oidc.py.epp new file mode 100644 index 0000000..df0841b --- /dev/null +++ b/templates/92-local_oidc.py.epp @@ -0,0 +1,42 @@ +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_USERINFO_ENDPOINT = 'https://your-idp.example.com/userinfo' +#OIDC_OP_JWKS_ENDPOINT = 'https://your-idp.example.com/jwks' + +OIDC_RP_CLIENT_ID = '<%= $client_id %>' +OIDC_RP_CLIENT_SECRET = '<%= $client_secret %>' + +# Algorithm for verifying JWT signatures (e.g. RS256, HS256) +OIDC_RP_SIGN_ALGO = 'RS256' + +# Custom scopes if needed +OIDC_RP_SCOPES = ['openid', 'email', 'profile'] +OIDC_RP_SCOPES += [ +<% $extra_scopes.each |$scope| { -%> + '<%= $scope %>', +<% } -%> +] +OIDC_RP_CLIENT_CALLBACK_URL = 'https://metrix.evolo-dev.calculquebec.cloud/oidc/callback/' + +# 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