diff --git a/salt/kratos/config.sls b/salt/kratos/config.sls index 55949ea3c6..0be43b4602 100644 --- a/salt/kratos/config.sls +++ b/salt/kratos/config.sls @@ -51,6 +51,14 @@ kratosschema: - group: 928 - mode: 600 +kratosoidc: + file.managed: + - name: /opt/so/conf/kratos/oidc.jsonnet + - source: salt://kratos/files/oidc.jsonnet + - user: 928 + - group: 928 + - mode: 600 + kratosconfig: file.managed: - name: /opt/so/conf/kratos/kratos.yaml diff --git a/salt/kratos/defaults.yaml b/salt/kratos/defaults.yaml index 3f5370dde1..1e2eef5edb 100644 --- a/salt/kratos/defaults.yaml +++ b/salt/kratos/defaults.yaml @@ -1,5 +1,18 @@ kratos: enabled: False + oidc: + enabled: false + config: + id: SSO + mapper_url: file:///kratos-conf/oidc.jsonnet + subject_source: userinfo + scope: + - email + - profile + requested_claims: + id_token: + email: + essential: true config: session: lifespan: 24h diff --git a/salt/kratos/enabled.sls b/salt/kratos/enabled.sls index 52d53a4dbd..31097ccf43 100644 --- a/salt/kratos/enabled.sls +++ b/salt/kratos/enabled.sls @@ -21,8 +21,7 @@ so-kratos: - sobridge: - ipv4_address: {{ DOCKER.containers['so-kratos'].ip }} - binds: - - /opt/so/conf/kratos/schema.json:/kratos-conf/schema.json:ro - - /opt/so/conf/kratos/kratos.yaml:/kratos-conf/kratos.yaml:ro + - /opt/so/conf/kratos/:/kratos-conf:ro - /opt/so/log/kratos/:/kratos-log:rw - /nsm/kratos/db:/kratos-data:rw {% if DOCKER.containers['so-kratos'].custom_bind_mounts %} diff --git a/salt/kratos/files/oidc.jsonnet b/salt/kratos/files/oidc.jsonnet new file mode 100644 index 0000000000..c155b275df --- /dev/null +++ b/salt/kratos/files/oidc.jsonnet @@ -0,0 +1,8 @@ +local claims = std.extVar('claims'); +{ + identity: { + traits: { + email: if 'email' in claims then claims.email else claims.preferred_username + }, + }, +} \ No newline at end of file diff --git a/salt/kratos/map.jinja b/salt/kratos/map.jinja index 6a2b1e0c9f..a2477098d7 100644 --- a/salt/kratos/map.jinja +++ b/salt/kratos/map.jinja @@ -20,3 +20,7 @@ {% do KRATOSDEFAULTS.kratos.config.courier.smtp.update({'connection_uri': KRATOSDEFAULTS.kratos.config.courier.smtp.connection_uri | replace("URL_BASE", GLOBALS.url_base)}) %} {% set KRATOSMERGED = salt['pillar.get']('kratos', default=KRATOSDEFAULTS.kratos, merge=true) %} + +{% if KRATOSMERGED.oidc.enabled and 'oidc' in salt['pillar.get']('features') %} +{% do KRATOSMERGED.config.selfservice.methods.update({'oidc': {'enabled': true, 'config': {'providers': [KRATOSMERGED.oidc.config]}}}) %} +{% endif %} \ No newline at end of file diff --git a/salt/kratos/soc_kratos.yaml b/salt/kratos/soc_kratos.yaml index b580e96114..6285bf1ad5 100644 --- a/salt/kratos/soc_kratos.yaml +++ b/salt/kratos/soc_kratos.yaml @@ -3,6 +3,90 @@ kratos: description: You can enable or disable Kratos. advanced: True helpLink: kratos.html + + oidc: + enabled: + description: Set to True to enable OIDC / Single Sign-On (SSO) to SOC. Requires a valid Security Onion license key. + global: True + helpLink: oidc.html + config: + id: + description: Customize the OIDC provider name. This name appears on the login page. Required. + global: True + forcedType: string + helpLink: oidc.html + provider: + description: "Specify the provider type. Required. Valid values are: auth0, generic, github, google, microsoft" + global: True + forcedType: string + regex: "auth0|generic|github|google|microsoft" + regexFailureMessage: "Valid values are: auth0, generic, github, google, microsoft" + helpLink: oidc.html + client_id: + description: Specify the client ID, also referenced as the application ID. Required. + global: True + forcedType: string + helpLink: oidc.html + client_secret: + description: Specify the client secret. Required. + global: True + forcedType: string + helpLink: oidc.html + microsoft_tenant: + description: Specify the Microsoft Active Directory Tenant ID. Required when provider is 'microsoft'. + global: True + forcedType: string + helpLink: oidc.html + subject_source: + description: The source of the subject identifier. Typically 'userinfo'. Only used when provider is 'microsoft'. + global: True + forcedType: string + regex: me|userinfo + regexFailureMessage: "Valid values are: me, userinfo" + helpLink: oidc.html + auth_url: + description: Provider's auth URL. Required when provider is 'generic'. + global: True + forcedType: string + helpLink: oidc.html + issuer_url: + description: Provider's issuer URL. Required when provider is 'auth0' or 'generic'. + global: True + forcedType: string + helpLink: oidc.html + mapper_url: + description: A file path or URL in Jsonnet format, used to map OIDC claims to the Kratos schema. Defaults to an included file that maps the email claim. Note that the contents of the included file can be customized via the "OIDC Claims Mapping" setting. + advanced: True + global: True + forcedType: string + helpLink: oidc.html + token_url: + description: Provider's token URL. Required when provider is 'generic'. + global: True + forcedType: string + helpLink: oidc.html + scope: + description: List of scoped data categories to request in the authentication response. Typically 'email' and 'profile' are the minimum required scopes. However, GitHub requires `user:email', instead and Auth0 requires 'profile', 'email', and 'openid'. + global: True + forcedType: "[]string" + helpLink: oidc.html + requested_claims: + id_token: + email: + essential: + description: Specifies whether the email claim is necessary. Typically leave this value set to true. + advanced: True + global: True + helpLink: oidc.html + files: + oidc__jsonnet: + title: OIDC Claims Mapping + description: Customize the OIDC claim mappings to the Kratos schema. The default mappings include the minimum required for login functionality, so this typically does not need to be customized. Visit https://jsonnet.org for more information about this file format. + advanced: True + file: True + global: True + helpLink: oidc.html + config: session: lifespan: @@ -19,10 +103,10 @@ kratos: methods: password: enabled: - description: Set to True to enable traditional password authentication. Leave as default to ensure proper security protections remain in place. + description: Set to True to enable traditional password authentication to SOC. Typically set to true, except when exclusively using OIDC authentication. Some external tool interfaces may not be accessible if local password authentication is disabled. global: True advanced: True - helpLink: kratos.html + helpLink: oidc.html config: haveibeenpwned_enabled: description: Set to True to check if a newly chosen password has ever been found in a published list of previously-compromised passwords. Requires outbound Internet connectivity when enabled. @@ -30,7 +114,7 @@ kratos: helpLink: kratos.html totp: enabled: - description: Set to True to enable Time-based One-Time Password (TOTP) multi-factor authentication (MFA). Enable to ensure proper security protections remain in place. Be aware that disabling this setting, after users have already setup TOTP, may prevent users from logging in. + description: Set to True to enable Time-based One-Time Password (TOTP) multi-factor authentication (MFA) to SOC. Enable to ensure proper security protections remain in place. Be aware that disabling this setting, after users have already setup TOTP, may prevent users from logging in. global: True helpLink: kratos.html config: @@ -41,7 +125,7 @@ kratos: helpLink: kratos.html webauthn: enabled: - description: Set to True to enable Security Keys (WebAuthn / PassKeys) for passwordless or multi-factor authentication (MFA) logins. Security Keys are a Public-Key Infrastructure (PKI) based authentication method, typically involving biometric hardware devices, such as laptop fingerprint scanners and USB hardware keys. Be aware that disabling this setting, after users have already setup their accounts with Security Keys, may prevent users from logging in. + description: Set to True to enable Security Keys (WebAuthn / PassKeys) for passwordless or multi-factor authentication (MFA) SOC logins. Security Keys are a Public-Key Infrastructure (PKI) based authentication method, typically involving biometric hardware devices, such as laptop fingerprint scanners and USB hardware keys. Be aware that disabling this setting, after users have already setup their accounts with Security Keys, may prevent users from logging in. global: True helpLink: kratos.html config: @@ -65,6 +149,7 @@ kratos: global: True advanced: True helpLink: kratos.html + flows: settings: privileged_session_max_age: diff --git a/salt/manager/tools/sbin/so-user b/salt/manager/tools/sbin/so-user index 50836e94c4..d597cdacb8 100755 --- a/salt/manager/tools/sbin/so-user +++ b/salt/manager/tools/sbin/so-user @@ -235,8 +235,8 @@ function updatePassword() { # Update DB with new hash echo "update identity_credentials set config=CAST('{\"hashed_password\":\"$passwordHash\"}' as BLOB), created_at=datetime('now'), updated_at=datetime('now') where identity_id='${identityId}' and identity_credential_type_id=(select id from identity_credential_types where name='password');" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath" # Deactivate MFA - echo "delete from identity_credential_identifiers where identity_credential_id=(select id from identity_credentials where identity_id='${identityId}' and identity_credential_type_id=(select id from identity_credential_types where name in ('totp', 'webauthn')));" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath" - echo "delete from identity_credentials where identity_id='${identityId}' and identity_credential_type_id=(select id from identity_credential_types where name in ('totp', 'webauthn'));" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath" + echo "delete from identity_credential_identifiers where identity_credential_id=(select id from identity_credentials where identity_id='${identityId}' and identity_credential_type_id=(select id from identity_credential_types where name in ('totp', 'webauthn', 'oidc')));" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath" + echo "delete from identity_credentials where identity_id='${identityId}' and identity_credential_type_id=(select id from identity_credential_types where name in ('totp', 'webauthn', 'oidc'));" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath" [[ $? != 0 ]] && fail "Unable to update password" fi } @@ -341,14 +341,19 @@ function syncElastic() { " and ic.identity_id=i.id " \ " and ict.id=ic.identity_credential_type_id " \ " and ict.name='password' " \ - " and instr(ic.config, 'hashed_password') " \ " and i.state == 'active' " \ "order by ici.identifier;" | \ sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath") [[ $? != 0 ]] && fail "Unable to read credential hashes from database" - echo "${userData}" | \ - jq -r '.user + ":" + .data.hashed_password' \ - >> "$usersTmpFile" + + user_data_formatted=$(echo "${userData}" | jq -r '.user + ":" + .data.hashed_password') + if lookup_salt_value "licensed_features" "" "pillar" | grep -x oidc; then + # generate random placeholder salt/hash for users without passwords + random_crypt=$(get_random_value 53) + user_data_formatted=$(echo "${user_data_formatted}" | sed -r "s/^(.+:)\$/\\1\$2a\$12${random_crypt}/") + fi + + echo "${user_data_formatted}" >> "$usersTmpFile" # Append the user roles while IFS="" read -r rolePair || [ -n "$rolePair" ]; do diff --git a/salt/nginx/etc/nginx.conf b/salt/nginx/etc/nginx.conf index 3ef0c5c1ff..d5981be77e 100644 --- a/salt/nginx/etc/nginx.conf +++ b/salt/nginx/etc/nginx.conf @@ -147,7 +147,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } - location ~ ^/auth/.*?(login) { + location ~ ^/auth/.*?(login|oidc/callback/) { rewrite /auth/(.*) /$1 break; limit_req zone=auth_throttle burst={{ NGINXMERGED.config.throttle_login_burst }} nodelay; limit_req_status 429;