From aaeb3aa88106f02a95cb06075b6421e08473361e Mon Sep 17 00:00:00 2001 From: James Elliott Date: Wed, 19 Apr 2023 14:27:10 +1000 Subject: [PATCH] feat(oidc): private key jwt client auth This adds support for the private_key_jwt client authentication method. Signed-off-by: James Elliott --- config.template.yml | 681 +++++++--------- .../identity-providers/introduction.md | 2 +- .../identity-providers/open-id-connect.md | 671 ---------------- .../openid-connect/_index.md | 15 + .../openid-connect/clients.md | 413 ++++++++++ .../openid-connect/provider.md | 428 ++++++++++ .../en/configuration/methods/secrets.md | 6 +- .../miscellaneous/privacy-policy.md | 2 +- .../openid-connect/apache-guacamole/index.md | 2 +- .../openid-connect/argocd/index.md | 2 +- .../openid-connect/bookstack/index.md | 2 +- .../cloudflare-zerotrust/index.md | 2 +- .../openid-connect/firezone/index.md | 2 +- .../integration/openid-connect/gitea/index.md | 2 +- .../openid-connect/gitlab/index.md | 2 +- .../openid-connect/grafana/index.md | 2 +- .../openid-connect/harbor/index.md | 2 +- .../openid-connect/hashicorp-vault/index.md | 2 +- .../openid-connect/introduction.md | 20 +- .../integration/openid-connect/komga/index.md | 2 +- .../integration/openid-connect/minio/index.md | 2 +- .../openid-connect/misago/index.md | 2 +- .../openid-connect/nextcloud/index.md | 2 +- .../openid-connect/outline/index.md | 2 +- .../openid-connect/portainer/index.md | 2 +- .../openid-connect/proxmox/index.md | 2 +- .../openid-connect/seafile/index.md | 2 +- .../openid-connect/synapse/index.md | 2 +- .../openid-connect/synology-dsm/index.md | 2 +- docs/content/en/integration/proxies/swag.md | 2 +- .../authorization/openid-connect-1.0.md | 2 +- docs/data/configkeys.json | 2 +- docs/layouts/shortcodes/oidc-common.html | 6 +- internal/configuration/config.template.yml | 681 +++++++--------- internal/configuration/provider_test.go | 16 +- .../schema/identity_providers.go | 39 +- internal/configuration/schema/keys.go | 35 +- .../test_resources/config_oidc_modern.yml | 2 +- internal/configuration/validator/const.go | 106 ++- .../configuration/validator/const_test.go | 1 + .../validator/identity_providers.go | 262 +++++-- .../validator/identity_providers_test.go | 731 ++++++++++++++---- internal/configuration/validator/notifier.go | 8 +- .../configuration/validator/notifier_test.go | 15 + internal/configuration/validator/server.go | 34 +- .../configuration/validator/server_test.go | 29 +- internal/configuration/validator/shared.go | 6 +- .../configuration/validator/shared_test.go | 30 + internal/configuration/validator/util.go | 10 +- internal/configuration/validator/util_test.go | 41 + .../handlers/handler_oidc_authorization.go | 2 +- internal/oidc/amr_test.go | 26 +- .../{client_auth.go => authentication.go} | 42 + .../authentication_test.go} | 718 +++++++++++++---- internal/oidc/client.go | 14 +- internal/oidc/client_test.go | 189 +++-- internal/oidc/config.go | 38 +- internal/oidc/config_test.go | 229 ++++++ internal/oidc/const.go | 8 +- internal/oidc/const_test.go | 71 +- internal/oidc/core_strategy_hmac.go | 55 +- ...go => core_strategy_hmac_blackbox_test.go} | 59 +- .../oidc/core_strategy_hmac_whitebox_test.go | 56 ++ internal/oidc/discovery.go | 26 +- internal/oidc/discovery_test.go | 570 ++++++++++++-- internal/oidc/hasher.go | 49 -- internal/oidc/hasher_test.go | 61 -- internal/oidc/keys.go | 97 ++- .../{keys_test.go => keys_blackbox_test.go} | 193 +++-- internal/oidc/keys_whitebox_test.go | 37 + internal/oidc/provider_test.go | 310 +------- internal/oidc/store.go | 4 +- internal/oidc/store_test.go | 566 +++++++++++++- internal/oidc/types.go | 38 +- internal/oidc/types_test.go | 39 +- internal/utils/bytes_test.go | 16 + internal/utils/clocK_test.go | 48 ++ 77 files changed, 5080 insertions(+), 2817 deletions(-) delete mode 100644 docs/content/en/configuration/identity-providers/open-id-connect.md create mode 100644 docs/content/en/configuration/identity-providers/openid-connect/_index.md create mode 100644 docs/content/en/configuration/identity-providers/openid-connect/clients.md create mode 100644 docs/content/en/configuration/identity-providers/openid-connect/provider.md create mode 100644 internal/configuration/validator/shared_test.go rename internal/oidc/{client_auth.go => authentication.go} (93%) rename internal/{handlers/handler_oidc_token_test.go => oidc/authentication_test.go} (73%) create mode 100644 internal/oidc/config_test.go rename internal/oidc/{core_strategy_hmac_test.go => core_strategy_hmac_blackbox_test.go} (72%) create mode 100644 internal/oidc/core_strategy_hmac_whitebox_test.go delete mode 100644 internal/oidc/hasher.go delete mode 100644 internal/oidc/hasher_test.go rename internal/oidc/{keys_test.go => keys_blackbox_test.go} (60%) create mode 100644 internal/oidc/keys_whitebox_test.go create mode 100644 internal/utils/bytes_test.go create mode 100644 internal/utils/clocK_test.go diff --git a/config.template.yml b/config.template.yml index dbeb986f3..8b3f31007 100644 --- a/config.template.yml +++ b/config.template.yml @@ -4,7 +4,16 @@ # Authelia Configuration # ############################################################################### -## Note: the container by default expects to find this file at /config/configuration.yml. +## +## Notes: +## +## - the default location of this file is assumed to be configuration.yml unless otherwise noted +## - when using docker the container expects this by default to be at /config/configuration.yml +## - the default location where this file is loaded from can be overridden with the X_AUTHELIA_CONFIG environment var +## - the comments in this configuration file are helpful but users should consult the official documentation on the +## website at https://www.authellia.com/ or https://www.authelia.com/configuration/prologue/introduction/ +## - this configuration file template is not automatically updated +## ## Certificates directory specifies where Authelia will load trusted certificates (public portion) from in addition to ## the system certificates store. @@ -357,73 +366,37 @@ authentication_backend: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- ## The distinguished name of the container searched for objects in the directory information tree. @@ -485,7 +458,7 @@ authentication_backend: # permit_referrals: false ## The username and password of the admin user. - # user: cn=admin,dc=example,dc=com + # user: 'cn=admin,dc=example,dc=com' ## Password can also be set using a secret: https://www.authelia.com/c/secrets # password: 'password' @@ -622,7 +595,7 @@ access_control: # networks: # - '10.10.0.0/16' # - '192.168.2.0/24' - # - name: VPN + # - name: 'VPN' # networks: '10.9.0.0/16' # rules: @@ -748,7 +721,8 @@ session: # expiration: '1h' ## The time before the cookie expires and the session is destroyed if remember me IS selected by the user. Setting - ## this value to -1 disables remember me for this session cookie domain. + ## this value to -1 disables remember me for this session cookie domain. If allowed and the user uses the remember + ## me checkbox this overrides the expiration option and disables the inactivity option. # remember_me: '1M' ## Cookie Session Domain default 'name' value. @@ -816,73 +790,37 @@ session: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- ## The Redis HA configuration options. @@ -997,73 +935,37 @@ regulation: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- ## @@ -1116,74 +1018,38 @@ regulation: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== - # -----END RSA PRIVATE KEY----- + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= + # -----END RSA PRIVATE KEY----- ## ## Notification Provider @@ -1270,73 +1136,37 @@ notifier: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- ## @@ -1354,80 +1184,88 @@ notifier: ## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets # hmac_secret: 'this_is_a_secret_abc123abc123abc' - ## The issuer_certificate_chain is an optional PEM encoded certificate chain. It's used in conjunction with the - ## issuer_private_key to sign JWT's. All certificates in the chain must be within the validity period, and every - ## certificate included must be signed by the certificate immediately after it if provided. - # issuer_certificate_chain: | - # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= - # -----END CERTIFICATE----- - # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= - # -----END CERTIFICATE----- + ## Issuer JWKS configures multiple JSON Web Keys. It's required that at least one of these is RS256 or the + ## option issuer_private_key is configured. There must only be one key per algorithm at this time. + ## For RSA keys the minimum is a 2048 bit key. + # issuer_private_keys: + # - + ## Key ID embedded into the JWT header for key matching. Must be an alphanumeric string with 7 or less characters. + ## This value is automatically generated if not provided. It's recommended to not configure this. + # key_id: 'example' - ## The issuer_private_key is used to sign the JWT forged by OpenID Connect. + ## The key algorithm used with this key. + # algorithm: 'RS256' + + ## The key use expected with this key. Currently only 'sig' is supported. + # use: 'sig' + + ## Required Private Key in PEM DER form. + # key: | + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= + # -----END RSA PRIVATE KEY----- + + + ## Optional matching certificate chain in PEM DER form that matches the key. All certificates within the chain + ## must be valid and current, and from top to bottom each certificate must be signed by the subsequent one. + # certificate_chain: | + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- + + ## The issuer_private_key is used to sign the JWT forged by OpenID Connect. This is in addition to the + ## issuer_private_keys option. Assumed to use the RS256 algorithm, and must not be specified if any of the + ## keys in issuer_private_keys also has the algorithm RS256 or are an RSA key without an algorithm. ## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets # issuer_private_key: | # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- + ## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within + ## the chain must be valid and current, and from top to bottom each certificate must be signed by the next + ## certificate in the chain if provided. + # issuer_certificate_chain: | + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- + ## The lifespans configure the expiration for these token types in the duration common syntax. # access_token_lifespan: '1h' # authorize_code_lifespan: '1m' @@ -1499,6 +1337,11 @@ notifier: # - 'email' # - 'profile' + ## Grant Types configures which grants this client can obtain. + ## It's not recommended to define this unless you know what you're doing. + # grant_types: + # - 'authorization_code' + ## Response Types configures which responses this client can be sent. ## It's not recommended to define this unless you know what you're doing. # response_types: @@ -1509,25 +1352,19 @@ notifier: # - 'form_post' # - 'query' - ## Grant Types configures which grants this client can obtain. - ## It's not recommended to define this unless you know what you're doing. - # grant_types: - # - 'authorization_code' - - ## The permitted client authentication method for the Token Endpoint for this client. - # token_endpoint_auth_method: 'client_secret_basic' - - ## The permitted client authentication signing algorithm for the Token Endpoint for this client when using - ## the 'client_secret_jwt' token_endpoint_auth_method. - # token_endpoint_auth_signing_alg: HS256 - - ## The permitted client authentication signing algorithm for the Token Endpoint for this client when using - ## the 'client_secret_jwt' token_endpoint_auth_method. - # token_endpoint_auth_signing_alg: HS256 - ## The policy to require for this client; one_factor or two_factor. # authorization_policy: 'two_factor' + ## The consent mode controls how consent is obtained. + # consent_mode: 'auto' + + ## This value controls the duration a consent on this client remains remembered when the consent mode is + ## configured as 'auto' or 'pre-configured' in the duration common syntax. + # pre_configured_consent_duration: '1w' + + ## Enforces the use of Pushed Authorization Requests for this client when set to true. + # enforce_par: false + ## Enforces the use of PKCE for this client when set to true. # enforce_pkce: false @@ -1535,13 +1372,69 @@ notifier: ## Options are 'plain' and 'S256'. # pkce_challenge_method: 'S256' + ## The permitted client authentication method for the Token Endpoint for this client. + # token_endpoint_auth_method: 'client_secret_basic' + + ## The permitted client authentication signing algorithm for the Token Endpoint for this client when using + ## the 'client_secret_jwt' or 'private_key_jwt' token_endpoint_auth_method. + # token_endpoint_auth_signing_alg: 'RS256' + + ## The signing algorithm which must be used for request objects. A client JWK with a matching algorithm must be + ## included if configured. + # request_object_signing_alg: 'RS256' + + ## The signing algorithm used for ID Tokens. Am issuer JWK with a matching algorithm must be included. + # id_token_signing_alg: 'RS256' + ## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256. # userinfo_signing_algorithm: 'none' - ## The consent mode controls how consent is obtained. - # consent_mode: 'auto' + ## Trusted public keys configuration for request object signing for things such as private_key_jwt + # public_keys: - ## This value controls the duration a consent on this client remains remembered when the consent mode is - ## configured as 'auto' or 'pre-configured' in the duration common syntax. - # pre_configured_consent_duration: '1w' + ## URL of the HTTPS endpoint which serves the keys. It's recommended to manually configure them in the + ## values option below. Please note the URL and the individual values are mutually exclusive. + # uri: 'https://app.example.com/jwks.json' + + ## Values from the individual keys. + # values: + # - + ## Key ID used to match the JWT's to an individual identifier. This option is required if configured. + # key_id: 'example' + + ## The key algorithm expected with this key. + # algorithm: 'RS256' + + ## The key use expected with this key. Currently only 'sig' is supported. + # use: 'sig' + + ## Required Public Key in PEM DER form. + # key: | + # -----BEGIN RSA PUBLIC KEY----- + # MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz + # 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE= + # -----END RSA PUBLIC KEY---- + + ## The matching certificate chain in PEM DER form that matches the key if available. + # certificate_chain: | + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- ... diff --git a/docs/content/en/configuration/identity-providers/introduction.md b/docs/content/en/configuration/identity-providers/introduction.md index f8a940a49..87ba9dbc4 100644 --- a/docs/content/en/configuration/identity-providers/introduction.md +++ b/docs/content/en/configuration/identity-providers/introduction.md @@ -16,4 +16,4 @@ aliases: ## OpenID Connect -The only identity provider implementation supported at this time is [OpenID Connect 1.0](open-id-connect.md). +The only identity provider implementation supported at this time is [OpenID Connect 1.0](openid-connect/provider.md). diff --git a/docs/content/en/configuration/identity-providers/open-id-connect.md b/docs/content/en/configuration/identity-providers/open-id-connect.md deleted file mode 100644 index 05e550e2c..000000000 --- a/docs/content/en/configuration/identity-providers/open-id-connect.md +++ /dev/null @@ -1,671 +0,0 @@ ---- -title: "OpenID Connect" -description: "OpenID Connect Configuration" -lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure this." -date: 2022-06-15T17:51:47+10:00 -draft: false -images: [] -menu: - configuration: - parent: "identity-providers" -weight: 190200 -toc: true -aliases: - - /c/oidc - - /docs/configuration/identity-providers/oidc.html ---- - -__Authelia__ currently supports the [OpenID Connect 1.0] Provider role as an open -[__beta__](../../roadmap/active/openid-connect.md) feature. We currently do not support the [OpenID Connect 1.0] Relying -Party role. This means other applications that implement the [OpenID Connect 1.0] Relying Party role can use Authelia as -an [OpenID Connect 1.0] Provider similar to how you may use social media or development platforms for login. - -The [OpenID Connect 1.0] Relying Party role is the role which allows an application to use GitHub, Google, or other -[OpenID Connect 1.0] Providers for authentication and authorization. We do not intend to support this functionality at -this moment in time. - -More information about the beta can be found in the [roadmap](../../roadmap/active/openid-connect.md). - -## Configuration - -The following snippet provides a sample-configuration for the OIDC identity provider explaining each field in detail. - -```yaml -identity_providers: - oidc: - hmac_secret: this_is_a_secret_abc123abc123abc - issuer_certificate_chain: | - -----BEGIN CERTIFICATE----- - MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= - -----END CERTIFICATE----- - -----BEGIN CERTIFICATE----- - MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - qocikt3WAdU^invalid DO NOT USE= - -----END CERTIFICATE----- - issuer_private_key: | - -----BEGIN RSA PRIVATE KEY----- - MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - DO NOT USE== - -----END RSA PRIVATE KEY----- - issuer_jwks: - - key_id: '' - algorithm: 'RS256' - key: | - - certificate_chain: | - - access_token_lifespan: 1h - authorize_code_lifespan: 1m - id_token_lifespan: 1h - refresh_token_lifespan: 90m - enable_client_debug_messages: false - enforce_pkce: public_clients_only - cors: - endpoints: - - authorization - - token - - revocation - - introspection - allowed_origins: - - https://example.com - allowed_origins_from_client_redirect_uris: false - clients: - - id: myapp - description: My Application - secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'. - sector_identifier: '' - public: false - authorization_policy: two_factor - consent_mode: explicit - pre_configured_consent_duration: 1w - audience: [] - scopes: - - openid - - groups - - email - - profile - redirect_uris: - - https://oidc.example.com:8080/oauth2/callback - grant_types: - - refresh_token - - authorization_code - response_types: - - code - response_modes: - - form_post - - query - - fragment - userinfo_signing_algorithm: none -``` - -## Options - -### hmac_secret - -{{< confkey type="string" required="yes" >}} - -*__Important Note:__ This can also be defined using a [secret](../methods/secrets.md) which is __strongly recommended__ -especially for containerized deployments.* - -The HMAC secret used to sign the [JWT]'s. The provided string is hashed to a SHA256 ([RFC6234]) byte string for the -purpose of meeting the required format. - -It's __strongly recommended__ this is a -[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) -with 64 or more characters. - -### issuer_private_key - -{{< confkey type="string" required="yes" >}} - -*__Important Note:__ This can also be defined using a [secret](../methods/secrets.md) which is __strongly recommended__ -especially for containerized deployments.* - -The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator -and can be done by following the -[Generating an RSA Keypair](../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide. - -The private key *__MUST__*: -* Be a PEM block encoded in the DER base64 format ([RFC4648]). -* Be an RSA Key. -* Have a key size of at least 2048 bits. - -If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public -key data for the first certificate in the chain. - -### issuer_certificate_chain - -{{< confkey type="string" required="no" >}} - -The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648]) -encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t] -JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints) -as per [RFC7517]. - -[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517 -[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7 -[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8 - -The first certificate in the chain must have the public key for the [issuer_private_key](#issuerprivatekey), each -certificate in the chain must be valid for the current date, and each certificate in the chain should be signed by the -certificate immediately following it if present. - -### issuer_jwks - -{{< confkey type="list(object" required="no" >}} - -The list of JWKS instead of or in addition to the [issuer_private_key](#issuerprivatekey) and -[issuer_certificate_chain](#issuercertificatechain). Can also accept ECDSA Private Key's and Certificates. - -#### key_id - -{{< confkey type="string" default="" required="no" >}} - -Completely optional, and generally discouraged unless there is a collision between the automatically generated key id's. -If provided must be a unique string with 7 or less alphanumeric characters. - -This value is the first 7 characters of the public key thumbprint (SHA1) encoded into hexadecimal. - -#### algorithm - -{{< confkey type="string" required="no" >}} - -The algorithm for this key. This value must be unique. It's automatically detected based on the type of key. - -#### key - -{{< confkey type="string" required="yes" >}} - -The private key associated with this key entry. - -The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator -and can be done by following the -[Generating an RSA Keypair](../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide. - -The private key *__MUST__*: -* Be a PEM block encoded in the DER base64 format ([RFC4648]). -* Be one of: - * An RSA key with a key size of at least 2048 bits. - * An ECDSA private key with one of the P-256, P-384, or P-521 elliptical curves. - -If the [certificate_chain](#certificatechain) is provided the private key must include matching public -key data for the first certificate in the chain. - -#### certificate_chain - -{{< confkey type="string" required="no" >}} - -The certificate chain/bundle to be used with the [key](#key) DER base64 ([RFC4648]) -encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t] -JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints) -as per [RFC7517]. - -[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517 -[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7 -[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8 - -The first certificate in the chain must have the public key for the [key](#key), each certificate in the chain must be -valid for the current date, and each certificate in the chain should be signed by the certificate immediately following -it if present. - -### access_token_lifespan - -{{< confkey type="duration" default="1h" required="no" >}} - -The maximum lifetime of an access token. It's generally recommended keeping this short similar to the default. -For more information read these docs about [token lifespan]. - -### authorize_code_lifespan - -{{< confkey type="duration" default="1m" required="no" >}} - -The maximum lifetime of an authorize code. This can be rather short, as the authorize code should only be needed to -obtain the other token types. For more information read these docs about [token lifespan]. - -### id_token_lifespan - -{{< confkey type="duration" default="1h" required="no" >}} - -The maximum lifetime of an ID token. For more information read these docs about [token lifespan]. - -### refresh_token_lifespan - -{{< confkey type="string" default="90m" required="no" >}} - -The maximum lifetime of a refresh token. The -refresh token can be used to obtain new refresh tokens as well as access tokens or id tokens with an -up-to-date expiration. For more information read these docs about [token lifespan]. - -A good starting point is 50% more or 30 minutes more (which ever is less) time than the highest lifespan out of the -[access token lifespan](#access_token_lifespan), the [authorize code lifespan](#authorize_code_lifespan), and the -[id token lifespan](#id_token_lifespan). For instance the default for all of these is 60 minutes, so the default refresh -token lifespan is 90 minutes. - -### enable_client_debug_messages - -{{< confkey type="boolean" default="false" required="no" >}} - -Allows additional debug messages to be sent to the clients. - -### minimum_parameter_entropy - -{{< confkey type="integer" default="8" required="no" >}} - -This controls the minimum length of the `nonce` and `state` parameters. - -*__Security Notice:__* Changing this value is generally discouraged, reducing it from the default can theoretically -make certain scenarios less secure. It is highly encouraged that if your OpenID Connect RP does not send these -parameters or sends parameters with a lower length than the default that they implement a change rather than changing -this value. - -### enforce_pkce - -{{< confkey type="string" default="public_clients_only" required="no" >}} - -[Proof Key for Code Exchange](https://datatracker.ietf.org/doc/html/rfc7636) enforcement policy: if specified, must be -either `never`, `public_clients_only` or `always`. - -If set to `public_clients_only` (default), [PKCE] will be required for public clients using the -[Authorization Code Flow]. - -When set to `always`, [PKCE] will be required for all clients using the Authorization Code flow. - -*__Security Notice:__* Changing this value to `never` is generally discouraged, reducing it from the default can -theoretically make certain client-side applications (mobile applications, SPA) vulnerable to CSRF and authorization code -interception attacks. - -### enable_pkce_plain_challenge - -{{< confkey type="boolean" default="false" required="no" >}} - -Allows [PKCE] `plain` challenges when set to `true`. - -*__Security Notice:__* Changing this value is generally discouraged. Applications should use the `S256` [PKCE] challenge -method instead. - -### pushed_authorizations - -Controls the behaviour of [Pushed Authorization Requests]. - -#### enforce - -{{< confkey type="boolean" default="false" required="no" >}} - -When enabled all authorization requests must use the [Pushed Authorization Requests] flow. - -#### context_lifespan - -{{< confkey type="duration" default="5m" required="no" >}} - -The maximum amount of time between the [Pushed Authorization Requests] flow being initiated and the generated -`request_uri` being utilized by a client. - -### cors - -Some [OpenID Connect 1.0] Endpoints need to allow cross-origin resource sharing, however some are optional. This section allows -you to configure the optional parts. We reply with CORS headers when the request includes the Origin header. - -#### endpoints - -{{< confkey type="list(string)" required="no" >}} - -A list of endpoints to configure with cross-origin resource sharing headers. It is recommended that the `userinfo` -option is at least in this list. The potential endpoints which this can be enabled on are as follows: - -* authorization -* pushed-authorization-request -* token -* revocation -* introspection -* userinfo - -#### allowed_origins - -{{< confkey type="list(string)" required="no" >}} - -A list of permitted origins. - -Any origin with https is permitted unless this option is configured or the -[allowed_origins_from_client_redirect_uris](#allowed_origins_from_client_redirect_uris) option is enabled. This means -you must configure this option manually if you want http endpoints to be permitted to make cross-origin requests to the -[OpenID Connect 1.0] endpoints, however this is not recommended. - -Origins must only have the scheme, hostname and port, they may not have a trailing slash or path. - -In addition to an Origin URI, you may specify the wildcard origin in the allowed_origins. It MUST be specified by itself -and the [allowed_origins_from_client_redirect_uris](#allowedoriginsfromclientredirecturis) MUST NOT be enabled. The -wildcard origin is denoted as `*`. Examples: - -```yaml -identity_providers: - oidc: - cors: - allowed_origins: "*" -``` - -```yaml -identity_providers: - oidc: - cors: - allowed_origins: - - "*" -``` - -#### allowed_origins_from_client_redirect_uris - -{{< confkey type="boolean" default="false" required="no" >}} - -Automatically adds the origin portion of all redirect URI's on all clients to the list of -[allowed_origins](#allowed_origins), provided they have the scheme http or https and do not have the hostname of -localhost. - -### clients - -{{< confkey type="list" required="yes" >}} - -A list of clients to configure. The options for each client are described below. - -#### id - -{{< confkey type="string" required="yes" >}} - -The Client ID for this client. It must exactly match the Client ID configured in the application -consuming this client. - -#### description - -{{< confkey type="string" default="*same as id*" required="no" >}} - -A friendly description for this client shown in the UI. This defaults to the same as the ID. - -#### secret - -{{< confkey type="string" required="situational" >}} - -The shared secret between Authelia and the application consuming this client. This secret must match the secret -configured in the application. - -This secret must be generated by the administrator and can be done by following the -[How Do I Generate Client Secrets](../../integration/openid-connect/frequently-asked-questions.md#how-do-i-generate-client-secrets) FAQ. - -This must be provided when the client is a confidential client type, and must be blank when using the public client -type. To set the client type to public see the [public](#public) configuration option. - -#### sector_identifier - -{{< confkey type="string" required="no" >}} - -*__Important Note:__ because adjusting this option will inevitably change the `sub` claim of all tokens generated for -the specified client, changing this should cause the relying party to detect all future authorizations as completely new -users.* - -Must be an empty string or the host component of a URL. This is commonly just the domain name, but may also include a -port. - -Authelia utilizes UUID version 4 subject identifiers. By default the public [Subject Identifier Type] is utilized for -all clients. This means the subject identifiers will be the same for all clients. This configuration option enables -[Pairwise Identifier Algorithm] for this client, and configures the sector identifier utilized for both the storage and -the lookup of the subject identifier. - -1. All clients who do not have this configured will generate the same subject identifier for a particular user - regardless of which client obtains the ID token. -2. All clients which have the same sector identifier will: - 1. have the same subject identifier for a particular user when compared to clients with the same sector identifier. - 2. have a completely different subject identifier for a particular user whe compared to: - 1. any client with the public subject identifier type. - 2. any client with a differing sector identifier. - -In specific but limited scenarios this option is beneficial for privacy reasons. In particular this is useful when the -party utilizing the *Authelia* [OpenID Connect 1.0] Authorization Server is foreign and not controlled by the user. It would -prevent the third party utilizing the subject identifier with another third party in order to track the user. - -Keep in mind depending on the other claims they may still be able to perform this tracking and it is not a silver -bullet. There are very few benefits when utilizing this in a homelab or business where no third party is utilizing -the server. - -#### public - -{{< confkey type="bool" default="false" required="no" >}} - -This enables the public client type for this client. This is for clients that are not capable of maintaining -confidentiality of credentials, you can read more about client types in [RFC6749 Section 2.1]. This is particularly -useful for SPA's and CLI tools. This option requires setting the [client secret](#secret) to a blank string. - -#### redirect_uris - -{{< confkey type="list(string)" required="yes" >}} - -A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are -case-sensitive and they differ from application to application - the community has provided -[a list of URL´s for common applications](../../integration/openid-connect/introduction.md). - -Some restrictions that have been placed on clients and -their redirect URIs are as follows: - -1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the - attempt to authorize will fail and an error will be generated. -2. The redirect URIs are case-sensitive. -3. The URI must include a scheme and that scheme must be one of `http` or `https`. - -#### audience - -{{< confkey type="list(string)" required="no" >}} - -A list of audiences this client is allowed to request. - -#### scopes - -{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}} - -A list of scopes to allow this client to consume. See -[scope definitions](../../integration/openid-connect/introduction.md#scope-definitions) for more information. The -documentation for the application you are trying to configure [OpenID Connect 1.0] for will likely have a list of scopes -or claims required which can be matched with the above guide. - -#### response_types - -{{< confkey type="list(string)" default="code" required="no" >}} - -*__Security Note:__ It is recommended that only the `code` response type (i.e. the default) is used. The other response -types are not as secure as this response type.* - -A list of response types this client supports. If a response type not in this list is requested by a client then an -error will be returned to the client. The response type indicates the types of values that are returned to the client. - -See the [Response Types](../../integration/openid-connect/introduction.md#response-types) section of the -[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-types) for more information. - -#### response_modes - -{{< confkey type="list(string)" default="form_post, query" required="no" >}} - -*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.* - -A list of response modes this client supports. If a response mode not in this list is requested by a client then an -error will be returned to the client. The response mode controls how the response type is returned to the client. - -See the [Response Modes](../../integration/openid-connect/introduction.md#response-modes) section of the -[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-modes) for more -information. - -The default values are based on the [response_types](#responsetypes) values. When the [response_types](#responsetypes) -values include the `code` type then the `query` response mode will be included. When any other type is included the -`fragment` response mode will be included. It's important to note at this time we do not support the `none` response -type, but when it is supported it will include the `query` response mode. - -#### grant_types - -{{< confkey type="list(string)" default="authorization_code" required="no" >}} - -*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.* - -The list of grant types this client is permitted to use in order to obtain access to the relevant tokens. - -See the [Grant Types](../../integration/openid-connect/introduction.md#grant-types) section of the -[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#grant-types) for more information. - -#### authorization_policy - -{{< confkey type="string" default="two_factor" required="no" >}} - -The authorization policy for this client: either `one_factor` or `two_factor`. - -#### enforce_par - -{{< confkey type="boolean" default="false" required="no" >}} - -Enforces the use of a [Pushed Authorization Requests] flow for this client. - -#### enforce_pkce - -{{< confkey type="bool" default="false" required="no" >}} - -This setting enforces the use of [PKCE] for this individual client. To enforce it for all clients see the global -[enforce_pkce](#enforcepkce) setting. - -#### pkce_challenge_method - -{{< confkey type="string" default="" required="no" >}} - -This setting enforces the use of the specified [PKCE] challenge method for this individual client. This setting also -effectively enables the [enforce_pkce](#enforcepkce-1) option for this client. - -Valid values are an empty string, `plain`, or `S256`. It should be noted that `S256` is strongly recommended if the -relying party supports it. - -#### userinfo_signing_algorithm - -{{< confkey type="string" default="none" required="no" >}} - -The algorithm used to sign the userinfo endpoint responses. This can either be `none` or `RS256`. - -See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for -more information. - -#### token_endpoint_auth_method - -{{< confkey type="string" default="" required="no" >}} - -The registered client authentication mechanism used by this client for the [Token Endpoint]. If no method is defined -the confidential client type will accept any supported method. The public client type defaults to `none` as this -is required by the specification. This may be required as a breaking change in future versions. -Supported values are `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, and `none`. - -See the [integration guide](../../integration/openid-connect/introduction.md#client-authentication-method) for -more information. - -#### token_endpoint_auth_signing_alg - -{{< confkey type="string" default="HS256" required="no" >}} - -The JWT signing algorithm accepted when the [token_endpoint_auth_method](#tokenendpointauthmethod) is configured as -`client_secret_jwt`. Supported values are `HS256`, `HS385`, and `HS512`. - -#### consent_mode - -{{< confkey type="string" default="auto" required="no" >}} - -*__Important Note:__ the `implicit` consent mode is not technically part of the specification. It theoretically could be -misused in certain conditions specifically with the public client type or when the client credentials (i.e. client -secret) has been exposed to an attacker. For these reasons this mode is discouraged.* - -Configures the consent mode. The following table describes the different modes: - -| Value | Description | -|:--------------:|:----------------------------------------------------------------------------------------------------------------------------------------------:| -| auto | Automatically determined (default). Uses `explicit` unless [pre_configured_consent_duration] is specified in which case uses `pre-configured`. | -| explicit | Requires the user provide unique explicit consent for every authorization. | -| implicit | Automatically assumes consent for every authorization, never asking the user if they wish to give consent. | -| pre-configured | Allows the end-user to remember their consent for the [pre_configured_consent_duration]. | - -[pre_configured_consent_duration]: #preconfiguredconsentduration - -#### pre_configured_consent_duration - -{{< confkey type="duration" default="1w" required="no" >}} - -*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see -the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.* - -Specifying this in the configuration without a consent [consent_mode] enables the `pre-configured` mode. If this is -specified as well as the [consent_mode] then it only has an effect if the [consent_mode] is `pre-configured` or `auto`. - -The period of time dictates how long a users choice to remember the pre-configured consent lasts. - -Pre-configured consents are only valid if the subject, client id are exactly the same and the requested scopes/audience -match exactly with the granted scopes/audience. - -[consent_mode]: #consentmode - -## Integration - -To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party please see the -[integration docs](../../integration/openid-connect/introduction.md). - -[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration -[OpenID Connect 1.0]: https://openid.net/connect/ -[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint -[JWT]: https://datatracker.ietf.org/doc/html/rfc7519 -[RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234 -[RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648 -[RFC7468]: https://datatracker.ietf.org/doc/html/rfc7468 -[RFC6749 Section 2.1]: https://datatracker.ietf.org/doc/html/rfc6749#section-2.1 -[PKCE]: https://datatracker.ietf.org/doc/html/rfc7636 -[Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth -[Subject Identifier Type]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes -[Pairwise Identifier Algorithm]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg -[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126 diff --git a/docs/content/en/configuration/identity-providers/openid-connect/_index.md b/docs/content/en/configuration/identity-providers/openid-connect/_index.md new file mode 100644 index 000000000..575323138 --- /dev/null +++ b/docs/content/en/configuration/identity-providers/openid-connect/_index.md @@ -0,0 +1,15 @@ +--- +title: "OpenID Connect 1.0" +description: "" +lead: "" +date: 2023-05-08T13:38:08+10:00 +lastmod: 2022-01-18T20:07:56+01:00 +draft: false +images: [] +menu: + docs: + parent: "identity-providers" + identifier: "openid-connect" +weight: 190120 +toc: true +--- diff --git a/docs/content/en/configuration/identity-providers/openid-connect/clients.md b/docs/content/en/configuration/identity-providers/openid-connect/clients.md new file mode 100644 index 000000000..1c70a3679 --- /dev/null +++ b/docs/content/en/configuration/identity-providers/openid-connect/clients.md @@ -0,0 +1,413 @@ +--- +title: "OpenID Connect 1.0 Clients" +description: "OpenID Connect 1.0 Registered Clients Configuration" +lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure the registered clients." +date: 2023-05-08T13:38:08+10:00 +draft: false +images: [] +menu: + configuration: + parent: "openid-connect" +weight: 190220 +toc: true +--- + +This section covers specifics regarding configuring the providers registered clients for [OpenID Connect 1.0]. For the +provider specific configuration and information not related to clients see the [OpenID Connect 1.0 Provider](provider.md) +documentation. + +More information about the beta can be found in the [roadmap](../../../roadmap/active/openid-connect.md) and in the +[integration](../../../integration/openid-connect/introduction.md) documentation. + +## Configuration + +The following snippet provides a configuration example for the [OpenID Connect 1.0] Registered Clients. This is not +intended for production use it's used to provide context and an indentation example. + +```yaml +identity_providers: + oidc: + clients: + - id: myapp + description: My Application + secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'. + sector_identifier: '' + public: false + redirect_uris: + - https://oidc.example.com:8080/oauth2/callback + audience: [] + scopes: + - openid + - groups + - email + - profile + grant_types: + - refresh_token + - authorization_code + response_types: + - code + response_modes: + - form_post + - query + - fragment + authorization_policy: two_factor + consent_mode: explicit + pre_configured_consent_duration: 1w + enforce_par: false + enforce_pkce: false + pkce_challenge_method: S256 + token_endpoint_auth_method: '' + token_endpoint_auth_signing_alg: RS256 + id_token_signing_alg: RS256 + request_object_signing_alg: RS256 + userinfo_signing_algorithm: none +``` + +## Options + +### id + +{{< confkey type="string" required="yes" >}} + +The Client ID for this client. It must exactly match the Client ID configured in the application consuming this client. + +### description + +{{< confkey type="string" default="*same as id*" required="no" >}} + +A friendly description for this client shown in the UI. This defaults to the same as the ID. + +### secret + +{{< confkey type="string" required="situational" >}} + +The shared secret between Authelia and the application consuming this client. This secret must match the secret +configured in the application. + +This secret must be generated by the administrator and can be done by following the +[How Do I Generate Client Secrets](../../../integration/openid-connect/frequently-asked-questions.md#how-do-i-generate-client-secrets) FAQ. + +This must be provided when the client is a confidential client type, and must be blank when using the public client +type. To set the client type to public see the [public](#public) configuration option. + +### sector_identifier + +{{< confkey type="string" required="no" >}} + +*__Important Note:__ because adjusting this option will inevitably change the `sub` claim of all tokens generated for +the specified client, changing this should cause the relying party to detect all future authorizations as completely new +users.* + +Must be an empty string or the host component of a URL. This is commonly just the domain name, but may also include a +port. + +Authelia utilizes UUID version 4 subject identifiers. By default the public [Subject Identifier Type] is utilized for +all clients. This means the subject identifiers will be the same for all clients. This configuration option enables +[Pairwise Identifier Algorithm] for this client, and configures the sector identifier utilized for both the storage and +the lookup of the subject identifier. + +1. All clients who do not have this configured will generate the same subject identifier for a particular user + regardless of which client obtains the ID token. +2. All clients which have the same sector identifier will: + 1. have the same subject identifier for a particular user when compared to clients with the same sector identifier. + 2. have a completely different subject identifier for a particular user whe compared to: + 1. any client with the public subject identifier type. + 2. any client with a differing sector identifier. + +In specific but limited scenarios this option is beneficial for privacy reasons. In particular this is useful when the +party utilizing the *Authelia* [OpenID Connect 1.0] Authorization Server is foreign and not controlled by the user. It would +prevent the third party utilizing the subject identifier with another third party in order to track the user. + +Keep in mind depending on the other claims they may still be able to perform this tracking and it is not a silver +bullet. There are very few benefits when utilizing this in a homelab or business where no third party is utilizing +the server. + +### public + +{{< confkey type="bool" default="false" required="no" >}} + +This enables the public client type for this client. This is for clients that are not capable of maintaining +confidentiality of credentials, you can read more about client types in [RFC6749 Section 2.1]. This is particularly +useful for SPA's and CLI tools. This option requires setting the [client secret](#secret) to a blank string. + +### redirect_uris + +{{< confkey type="list(string)" required="yes" >}} + +A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are +case-sensitive and they differ from application to application - the community has provided +[a list of URL´s for common applications](../../../integration/openid-connect/introduction.md). + +Some restrictions that have been placed on clients and +their redirect URIs are as follows: + +1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the + attempt to authorize will fail and an error will be generated. +2. The redirect URIs are case-sensitive. +3. The URI must include a scheme and that scheme must be one of `http` or `https`. + +### audience + +{{< confkey type="list(string)" required="no" >}} + +A list of audiences this client is allowed to request. + +### scopes + +{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}} + +A list of scopes to allow this client to consume. See +[scope definitions](../../../integration/openid-connect/introduction.md#scope-definitions) for more information. The +documentation for the application you are trying to configure [OpenID Connect 1.0] for will likely have a list of scopes +or claims required which can be matched with the above guide. + +### grant_types + +{{< confkey type="list(string)" default="authorization_code" required="no" >}} + +*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.* + +The list of grant types this client is permitted to use in order to obtain access to the relevant tokens. + +See the [Grant Types](../../../integration/openid-connect/introduction.md#grant-types) section of the +[OpenID Connect 1.0 Integration Guide](../../../integration/openid-connect/introduction.md#grant-types) for more information. + +### response_types + +{{< confkey type="list(string)" default="code" required="no" >}} + +*__Security Note:__ It is recommended that only the `code` response type (i.e. the default) is used. The other response +types are not as secure as this response type.* + +A list of response types this client supports. If a response type not in this list is requested by a client then an +error will be returned to the client. The response type indicates the types of values that are returned to the client. + +See the [Response Types](../../../integration/openid-connect/introduction.md#response-types) section of the +[OpenID Connect 1.0 Integration Guide](../../../integration/openid-connect/introduction.md#response-types) for more information. + +### response_modes + +{{< confkey type="list(string)" default="form_post, query" required="no" >}} + +*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.* + +A list of response modes this client supports. If a response mode not in this list is requested by a client then an +error will be returned to the client. The response mode controls how the response type is returned to the client. + +See the [Response Modes](../../../integration/openid-connect/introduction.md#response-modes) section of the +[OpenID Connect 1.0 Integration Guide](../../../integration/openid-connect/introduction.md#response-modes) for more +information. + +The default values are based on the [response_types](#responsetypes) values. When the [response_types](#responsetypes) +values include the `code` type then the `query` response mode will be included. When any other type is included the +`fragment` response mode will be included. It's important to note at this time we do not support the `none` response +type, but when it is supported it will include the `query` response mode. + +### authorization_policy + +{{< confkey type="string" default="two_factor" required="no" >}} + +The authorization policy for this client: either `one_factor` or `two_factor`. + +### consent_mode + +{{< confkey type="string" default="auto" required="no" >}} + +*__Important Note:__ the `implicit` consent mode is not technically part of the specification. It theoretically could be +misused in certain conditions specifically with the public client type or when the client credentials (i.e. client +secret) has been exposed to an attacker. For these reasons this mode is discouraged.* + +Configures the consent mode. The following table describes the different modes: + +| Value | Description | +|:--------------:|:----------------------------------------------------------------------------------------------------------------------------------------------:| +| auto | Automatically determined (default). Uses `explicit` unless [pre_configured_consent_duration] is specified in which case uses `pre-configured`. | +| explicit | Requires the user provide unique explicit consent for every authorization. | +| implicit | Automatically assumes consent for every authorization, never asking the user if they wish to give consent. | +| pre-configured | Allows the end-user to remember their consent for the [pre_configured_consent_duration]. | + +[pre_configured_consent_duration]: #preconfiguredconsentduration + +### pre_configured_consent_duration + +{{< confkey type="duration" default="1w" required="no" >}} + +*__Note:__ This setting uses the [duration notation format](../../prologue/common.md#duration-notation-format). Please see +the [common options](../../prologue/common.md#duration-notation-format) documentation for information on this format.* + +Specifying this in the configuration without a consent [consent_mode] enables the `pre-configured` mode. If this is +specified as well as the [consent_mode] then it only has an effect if the [consent_mode] is `pre-configured` or `auto`. + +The period of time dictates how long a users choice to remember the pre-configured consent lasts. + +Pre-configured consents are only valid if the subject, client id are exactly the same and the requested scopes/audience +match exactly with the granted scopes/audience. + +[consent_mode]: #consentmode + +### enforce_par + +{{< confkey type="boolean" default="false" required="no" >}} + +This configuration option enforces the use of a [Pushed Authorization Requests] flow for this registered client. +To enforce it for all clients see the global [pushed_authorizations enforce](provider.md#enforce) provider configuration +option. + +### enforce_pkce + +{{< confkey type="bool" default="false" required="no" >}} + +This configuration option enforces the use of [PKCE] for this registered client. To enforce it for all clients see the +global [enforce_pkce](provider.md#enforcepkce) provider configuration option. + +### pkce_challenge_method + +{{< confkey type="string" default="" required="no" >}} + +This setting enforces the use of the specified [PKCE] challenge method for this individual client. This setting also +effectively enables the [enforce_pkce](#enforcepkce) option for this client. + +Valid values are an empty string, `plain`, or `S256`. It should be noted that `S256` is strongly recommended if the +relying party supports it. + +### token_endpoint_auth_method + +{{< confkey type="string" default="" required="no" >}} + +The registered client authentication mechanism used by this client for the [Token Endpoint]. If no method is defined +the confidential client type will accept any supported method. The public client type defaults to `none` as this +is required by the specification. This may be required as a breaking change in future versions. +Supported values are `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, `private_key_jwt`, and `none`. + +See the [integration guide](../../../integration/openid-connect/introduction.md#client-authentication-method) for +more information. + +### token_endpoint_auth_signing_alg + +{{< confkey type="string" default="RS256" required="no" >}} + +The JWT signing algorithm accepted when the [token_endpoint_auth_method](#tokenendpointauthmethod) is configured as +`client_secret_jwt` or `private_key_jwt`. + +See the request object section of the [integration guide](../../../integration/openid-connect/introduction.md#request-object) +for more information including the algorithm column for supported values. + +It's recommended that you specifically configure this when the following options are configured to specific values +otherwise we assume the default value: + +| Configuration Option | Value | Default | +|:----------------------------------------------------------:|:-------------------:|:-------:| +| [token_endpoint_auth_method](#tokenendpointauthsigningalg) | `private_key_jwt` | `RS256` | +| [token_endpoint_auth_method](#tokenendpointauthsigningalg) | `client_secret_jwt` | `HS256` | + +### request_object_signing_alg + +{{< confkey type="string" default="RSA256" required="no" >}} + +The JWT signing algorithm accepted for request objects. + +See the request object section of the [integration guide](../../../integration/openid-connect/introduction.md#request-object) +for more information including the algorithm column for supported values. + +### id_token_signing_alg + +{{< confkey type="string" default="RS256" required="no" >}} + +The algorithm used to sign the ID Tokens in the token responses. + +See the response object section of the [integration guide](../../../integration/openid-connect/introduction.md#response-object) +for more information including the algorithm column for supported values. In addition to the values listed we also +support `none` as a value for this endpoint. + +### userinfo_signing_algorithm + +{{< confkey type="string" default="none" required="no" >}} + +The algorithm used to sign the userinfo endpoint responses. + +See the response object section of the [integration guide](../../../integration/openid-connect/introduction.md#response-object) +for more information including the algorithm column for supported values. In addition to the values listed we also +support `none` as a value for this endpoint. + +### public_keys + +This section configures the trusted JSON Web Keys or JWKS for this registered client. This can either be static values +(recommended) or a URI using the `https` scheme. This section is situational required. These are used to validate the +[JWT] assertions from clients. + +Required when the following options are configured: + +- [request_object_signing_alg](#requestobjectsigningalg) +- [token_endpoint_auth_signing_alg](#tokenendpointauthsigningalg) + +Required when the following options are configured to specific values: + +- [token_endpoint_auth_method](#tokenendpointauthsigningalg): `private_key_jwt` + +#### uri + +{{< confkey type="string" required="no" >}} + +The fully qualified, `https` scheme, and appropriately signed URI for the JWKS endpoint that implements +[RFC7517 Section 5](https://datatracker.ietf.org/doc/html/rfc7517#section-5). Must not be configured at the same time +as [values](#values). It's recommended that you do not configure this option, but statically configure [values](#values) +instead. + +*__Important Note:__ the URL given in this value MUST be resolvable by Authelia and MUST present a certificate signed by +a certificate trusted by your environment. It is beyond our intentions to support anything other than this.* + +#### values + +{{< confkey type="list(object)" required="situational" >}} + +A list of static keys. + +##### key_id + +{{< confkey type="string" required="yes" >}} + +The Key ID used to match the request object's JWT header `kid` value against. + +##### key + +{{< confkey type="string" required="yes" >}} + +The public key portion of the JSON Web Key + +The public key the clients use to sign/encrypt the [OpenID Connect 1.0] asserted [JWT]'s. The key is generated by the +client application or the administrator of the client application. + +The key *__MUST__*: + +* Be a PEM block encoded in the DER base64 format ([RFC4648]). +* Be either: + * An RSA public key: + * With a key size of at least 2048 bits. + * An ECDSA public key with one of: + * A P-256 elliptical curve. + * A P-384 elliptical curve. + * A P-512 elliptical curve. + +If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public +key data for the first certificate in the chain. + + +## Integration + +To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party please see the +[integration docs](../../../integration/openid-connect/introduction.md). + +[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration +[OpenID Connect 1.0]: https://openid.net/connect/ +[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint +[JWT]: https://datatracker.ietf.org/doc/html/rfc7519 +[RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234 +[RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648 +[RFC7468]: https://datatracker.ietf.org/doc/html/rfc7468 +[RFC6749 Section 2.1]: https://datatracker.ietf.org/doc/html/rfc6749#section-2.1 +[PKCE]: https://datatracker.ietf.org/doc/html/rfc7636 +[Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth +[Subject Identifier Type]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes +[Pairwise Identifier Algorithm]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg +[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126 + diff --git a/docs/content/en/configuration/identity-providers/openid-connect/provider.md b/docs/content/en/configuration/identity-providers/openid-connect/provider.md new file mode 100644 index 000000000..0aee8a2e7 --- /dev/null +++ b/docs/content/en/configuration/identity-providers/openid-connect/provider.md @@ -0,0 +1,428 @@ +--- +title: "OpenID Connect 1.0 Provider" +description: "OpenID Connect 1.0 Provider Configuration" +lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure this." +date: 2023-05-08T13:38:08+10:00 +draft: false +images: [] +menu: + configuration: + parent: "openid-connect" +weight: 190200 +toc: true +aliases: + - /c/oidc + - /docs/configuration/identity-providers/oidc.html +--- + +__Authelia__ currently supports the [OpenID Connect 1.0] Provider role as an open +[__beta__](../../../roadmap/active/openid-connect.md) feature. We currently do not support the [OpenID Connect 1.0] Relying +Party role. This means other applications that implement the [OpenID Connect 1.0] Relying Party role can use Authelia as +an [OpenID Connect 1.0] Provider similar to how you may use social media or development platforms for login. + +The [OpenID Connect 1.0] Relying Party role is the role which allows an application to use GitHub, Google, or other +[OpenID Connect 1.0] Providers for authentication and authorization. We do not intend to support this functionality at +this moment in time. + +This section covers the [OpenID Connect 1.0] Provider configuration. For information on configuring individual +registered clients see the [OpenID Connect 1.0 Clients](clients.md) documentation. + +More information about the beta can be found in the [roadmap](../../../roadmap/active/openid-connect.md) and in the +[integration](../../../integration/openid-connect/introduction.md) documentation. + +## Configuration + +The following snippet provides a configuration example for the [OpenID Connect 1.0] Provider. This is not +intended for production use it's used to provide context and an indentation example. + +```yaml +identity_providers: + oidc: + hmac_secret: this_is_a_secret_abc123abc123abc + issuer_private_keys: + - key_id: example + algorithm: RS256 + use: sig + key: | + -----BEGIN RSA PUBLIC KEY----- + MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz + 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE= + -----END RSA PUBLIC KEY---- + certificate_chain: | + -----BEGIN CERTIFICATE----- + MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + -----END CERTIFICATE----- + issuer_private_key: | + -----BEGIN RSA PUBLIC KEY----- + MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz + 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE= + -----END RSA PUBLIC KEY---- + issuer_certificate_chain: | + -----BEGIN CERTIFICATE----- + MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + -----END CERTIFICATE----- + access_token_lifespan: 1h + authorize_code_lifespan: 1m + id_token_lifespan: 1h + refresh_token_lifespan: 90m + enable_client_debug_messages: false + minimum_parameter_entropy: 8 + enforce_pkce: public_clients_only + enable_pkce_plain_challenge: false + pushed_authorizations: + enforce: false + context_lifespan: 5m + cors: + endpoints: + - authorization + - token + - revocation + - introspection + allowed_origins: + - https://example.com + allowed_origins_from_client_redirect_uris: false +``` + +## Options + +### hmac_secret + +{{< confkey type="string" required="yes" >}} + +*__Important Note:__ This can also be defined using a [secret](../../methods/secrets.md) which is __strongly recommended__ +especially for containerized deployments.* + +The HMAC secret used to sign the [JWT]'s. The provided string is hashed to a SHA256 ([RFC6234]) byte string for the +purpose of meeting the required format. + +It's __strongly recommended__ this is a +[Random Alphanumeric String](../../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) +with 64 or more characters. + +### issuer_private_keys + +The key *__MUST__*: + +* Be a PEM block encoded in the DER base64 format ([RFC4648]). +* Be either: + * An RSA public key: + * With a key size of at least 2048 bits. + * An ECDSA public key with one of: + * A P-256 elliptical curve. + * A P-384 elliptical curve. + * A P-512 elliptical curve. + +### issuer_private_keys + +{{< confkey type="list(object" required="no" >}} + +The list of JWKS instead of or in addition to the [issuer_private_key](#issuerprivatekey) and +[issuer_certificate_chain](#issuercertificatechain). Can also accept ECDSA Private Key's and Certificates. + +#### key_id + +{{< confkey type="string" default="" required="no" >}} + +Completely optional, and generally discouraged unless there is a collision between the automatically generated key id's. +If provided must be a unique string with 7 or less alphanumeric characters. + +This value is the first 7 characters of the public key thumbprint (SHA1) encoded into hexadecimal. + +#### algorithm + +{{< confkey type="string" default="RS256" required="no" >}} + +The algorithm for this key. This value must be unique. It's automatically detected based on the type of key. + +See the response object table in the [integration guide](../../../integration/openid-connect/introduction.md#response-object) +including the algorithm column for the supported values and the key type column for the default algorithm value. + +#### use + +{{< confkey type="string" default="sig" required="no" >}} + +The key usage. Defaults to `sig` which is the only available option at this time. + +#### key + +{{< confkey type="string" required="yes" >}} + +The private key associated with this key entry. + +The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator +and can be done by following the +[Generating an RSA Keypair](../../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide. + +The private key *__MUST__*: +* Be a PEM block encoded in the DER base64 format ([RFC4648]). +* Be one of: + * An RSA key with a key size of at least 2048 bits. + * An ECDSA private key with one of the P-256, P-384, or P-521 elliptical curves. + +If the [certificate_chain](#certificatechain) is provided the private key must include matching public +key data for the first certificate in the chain. + +#### certificate_chain + +{{< confkey type="string" required="no" >}} + +The certificate chain/bundle to be used with the [key](#key) DER base64 ([RFC4648]) +encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t] +JSON key's in the JWKs [Discoverable Endpoint](../../../integration/openid-connect/introduction.md#discoverable-endpoints) +as per [RFC7517]. + +[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517 +[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7 +[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8 + +The first certificate in the chain must have the public key for the [key](#key), each certificate in the chain must be +valid for the current date, and each certificate in the chain should be signed by the certificate immediately following +it if present. + +### issuer_private_key + +{{< confkey type="string" required="yes" >}} + +*__Important Note:__ This can also be defined using a [secret](../../methods/secrets.md) which is __strongly recommended__ +especially for containerized deployments.* + +The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator +and can be done by following the +[Generating an RSA Keypair](../../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide. + +This private key is automatically appended to the [issuer_private_keys](#issuerprivatekeys) and assumed to be for the +RS256 algorithm. As such no other key in this list should be RS256 if this is configured. + +The issuer private key *__MUST__*: + +* Be a PEM block encoded in the DER base64 format ([RFC4648]). +* Be an RSA private key: + * With a key size of at least 2048 bits. + +If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public +key data for the first certificate in the chain. + +### issuer_certificate_chain + +{{< confkey type="string" required="no" >}} + +The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648]) +encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t] +JSON key's in the JWKs [Discoverable Endpoint](../../../integration/openid-connect/introduction.md#discoverable-endpoints) +as per [RFC7517]. + +[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517 +[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7 +[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8 + +The first certificate in the chain must have the public key for the [issuer_private_key](#issuerprivatekey), each +certificate in the chain must be valid for the current date, and each certificate in the chain should be signed by the +certificate immediately following it if present. + +### access_token_lifespan + +{{< confkey type="duration" default="1h" required="no" >}} + +The maximum lifetime of an access token. It's generally recommended keeping this short similar to the default. +For more information read these docs about [token lifespan]. + +### authorize_code_lifespan + +{{< confkey type="duration" default="1m" required="no" >}} + +The maximum lifetime of an authorize code. This can be rather short, as the authorize code should only be needed to +obtain the other token types. For more information read these docs about [token lifespan]. + +### id_token_lifespan + +{{< confkey type="duration" default="1h" required="no" >}} + +The maximum lifetime of an ID token. For more information read these docs about [token lifespan]. + +### refresh_token_lifespan + +{{< confkey type="string" default="90m" required="no" >}} + +The maximum lifetime of a refresh token. The +refresh token can be used to obtain new refresh tokens as well as access tokens or id tokens with an +up-to-date expiration. For more information read these docs about [token lifespan]. + +A good starting point is 50% more or 30 minutes more (which ever is less) time than the highest lifespan out of the +[access token lifespan](#accesstokenlifespan), the [authorize code lifespan](#authorizecodelifespan), and the +[id token lifespan](#idtokenlifespan). For instance the default for all of these is 60 minutes, so the default refresh +token lifespan is 90 minutes. + +### enable_client_debug_messages + +{{< confkey type="boolean" default="false" required="no" >}} + +Allows additional debug messages to be sent to the clients. + +### minimum_parameter_entropy + +{{< confkey type="integer" default="8" required="no" >}} + +This controls the minimum length of the `nonce` and `state` parameters. + +*__Security Notice:__* Changing this value is generally discouraged, reducing it from the default can theoretically +make certain scenarios less secure. It is highly encouraged that if your OpenID Connect RP does not send these +parameters or sends parameters with a lower length than the default that they implement a change rather than changing +this value. + +### enforce_pkce + +{{< confkey type="string" default="public_clients_only" required="no" >}} + +[Proof Key for Code Exchange](https://datatracker.ietf.org/doc/html/rfc7636) enforcement policy: if specified, must be +either `never`, `public_clients_only` or `always`. + +If set to `public_clients_only` (default), [PKCE] will be required for public clients using the +[Authorization Code Flow]. + +When set to `always`, [PKCE] will be required for all clients using the Authorization Code flow. + +*__Security Notice:__* Changing this value to `never` is generally discouraged, reducing it from the default can +theoretically make certain client-side applications (mobile applications, SPA) vulnerable to CSRF and authorization code +interception attacks. + +### enable_pkce_plain_challenge + +{{< confkey type="boolean" default="false" required="no" >}} + +Allows [PKCE] `plain` challenges when set to `true`. + +*__Security Notice:__* Changing this value is generally discouraged. Applications should use the `S256` [PKCE] challenge +method instead. + +### pushed_authorizations + +Controls the behaviour of [Pushed Authorization Requests]. + +#### enforce + +{{< confkey type="boolean" default="false" required="no" >}} + +When enabled all authorization requests must use the [Pushed Authorization Requests] flow. + +#### context_lifespan + +{{< confkey type="duration" default="5m" required="no" >}} + +The maximum amount of time between the [Pushed Authorization Requests] flow being initiated and the generated +`request_uri` being utilized by a client. + +### cors + +Some [OpenID Connect 1.0] Endpoints need to allow cross-origin resource sharing, however some are optional. This section allows +you to configure the optional parts. We reply with CORS headers when the request includes the Origin header. + +#### endpoints + +{{< confkey type="list(string)" required="no" >}} + +A list of endpoints to configure with cross-origin resource sharing headers. It is recommended that the `userinfo` +option is at least in this list. The potential endpoints which this can be enabled on are as follows: + +* authorization +* pushed-authorization-request +* token +* revocation +* introspection +* userinfo + +#### allowed_origins + +{{< confkey type="list(string)" required="no" >}} + +A list of permitted origins. + +Any origin with https is permitted unless this option is configured or the +[allowed_origins_from_client_redirect_uris](#allowedoriginsfromclientredirecturis) option is enabled. This means +you must configure this option manually if you want http endpoints to be permitted to make cross-origin requests to the +[OpenID Connect 1.0] endpoints, however this is not recommended. + +Origins must only have the scheme, hostname and port, they may not have a trailing slash or path. + +In addition to an Origin URI, you may specify the wildcard origin in the allowed_origins. It MUST be specified by itself +and the [allowed_origins_from_client_redirect_uris](#allowedoriginsfromclientredirecturis) MUST NOT be enabled. The +wildcard origin is denoted as `*`. Examples: + +```yaml +identity_providers: + oidc: + cors: + allowed_origins: "*" +``` + +```yaml +identity_providers: + oidc: + cors: + allowed_origins: + - "*" +``` + +#### allowed_origins_from_client_redirect_uris + +{{< confkey type="boolean" default="false" required="no" >}} + +Automatically adds the origin portion of all redirect URI's on all clients to the list of +[allowed_origins](#allowed_origins), provided they have the scheme http or https and do not have the hostname of +localhost. + +### clients + +See the [OpenID Connect 1.0 Registered Clients](clients.md) documentation for configuring clients. + +## Integration + +To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party please see the +[integration docs](../../integration/openid-connect/introduction.md). + +[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration +[OpenID Connect 1.0]: https://openid.net/connect/ +[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint +[JWT]: https://datatracker.ietf.org/doc/html/rfc7519 +[RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234 +[RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648 +[RFC7468]: https://datatracker.ietf.org/doc/html/rfc7468 +[RFC6749 Section 2.1]: https://datatracker.ietf.org/doc/html/rfc6749#section-2.1 +[PKCE]: https://datatracker.ietf.org/doc/html/rfc7636 +[Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth +[Subject Identifier Type]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes +[Pairwise Identifier Algorithm]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg +[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126 diff --git a/docs/content/en/configuration/methods/secrets.md b/docs/content/en/configuration/methods/secrets.md index 182c74edd..714376996 100644 --- a/docs/content/en/configuration/methods/secrets.md +++ b/docs/content/en/configuration/methods/secrets.md @@ -77,9 +77,9 @@ other configuration using the environment but instead of loading a file the valu [authentication_backend.ldap.password]: ../first-factor/ldap.md#password [authentication_backend.ldap.tls.certificate_chain]: ../first-factor/ldap.md#tls [authentication_backend.ldap.tls.private_key]: ../first-factor/ldap.md#tls -[identity_providers.oidc.issuer_certificate_chain]: ../identity-providers/open-id-connect.md#issuercertificatechain -[identity_providers.oidc.issuer_private_key]: ../identity-providers/open-id-connect.md#issuerprivatekey -[identity_providers.oidc.hmac_secret]: ../identity-providers/open-id-connect.md#hmacsecret +[identity_providers.oidc.issuer_certificate_chain]: ../identity-providers/openid-connectx.md#issuercertificatechain +[identity_providers.oidc.issuer_private_key]: ../identity-providers/openid-connectx.md#issuerprivatekey +[identity_providers.oidc.hmac_secret]: ../identity-providers/openid-connectx.md#hmacsecret ## Secrets in configuration file diff --git a/docs/content/en/configuration/miscellaneous/privacy-policy.md b/docs/content/en/configuration/miscellaneous/privacy-policy.md index 327b7e821..48edbe3e2 100644 --- a/docs/content/en/configuration/miscellaneous/privacy-policy.md +++ b/docs/content/en/configuration/miscellaneous/privacy-policy.md @@ -44,7 +44,7 @@ accepted is recorded and checked in the browser If the user has not accepted the policy they should not be able to interact with the Authelia UI via normal means. Administrators who are required to abide by the [GDPR] or other privacy laws should be advised that -[OpenID Connect 1.0](../identity-providers/open-id-connect.md) clients configured with the `implicit` consent mode are +[OpenID Connect 1.0](../identity-providers/openid-connectx.md) clients configured with the `implicit` consent mode are unlikely to trigger the display of the Authelia UI if the user is already authenticated. We wont be adding checks like this to the `implicit` consent mode when that mode in particular is unlikely to be diff --git a/docs/content/en/integration/openid-connect/apache-guacamole/index.md b/docs/content/en/integration/openid-connect/apache-guacamole/index.md index ac21220f2..cb6804db1 100644 --- a/docs/content/en/integration/openid-connect/apache-guacamole/index.md +++ b/docs/content/en/integration/openid-connect/apache-guacamole/index.md @@ -53,7 +53,7 @@ openid-groups-claim-type: groups ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Apache Guacamole] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/argocd/index.md b/docs/content/en/integration/openid-connect/argocd/index.md index 43071cced..d8b51b757 100644 --- a/docs/content/en/integration/openid-connect/argocd/index.md +++ b/docs/content/en/integration/openid-connect/argocd/index.md @@ -56,7 +56,7 @@ requestedScopes: ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Argo CD] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Argo CD] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/bookstack/index.md b/docs/content/en/integration/openid-connect/bookstack/index.md index 3d8ef133c..a18e81a3a 100644 --- a/docs/content/en/integration/openid-connect/bookstack/index.md +++ b/docs/content/en/integration/openid-connect/bookstack/index.md @@ -58,7 +58,7 @@ To configure [BookStack] to utilize Authelia as an [OpenID Connect 1.0] Provider ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [BookStack] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [BookStack] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/cloudflare-zerotrust/index.md b/docs/content/en/integration/openid-connect/cloudflare-zerotrust/index.md index 2259f473a..53cd11c62 100644 --- a/docs/content/en/integration/openid-connect/cloudflare-zerotrust/index.md +++ b/docs/content/en/integration/openid-connect/cloudflare-zerotrust/index.md @@ -66,7 +66,7 @@ To configure [Cloudflare Zero Trust] to utilize Authelia as an [OpenID Connect 1 ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Cloudflare] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Cloudflare] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/firezone/index.md b/docs/content/en/integration/openid-connect/firezone/index.md index 121c49cc3..3d4bbf3ea 100644 --- a/docs/content/en/integration/openid-connect/firezone/index.md +++ b/docs/content/en/integration/openid-connect/firezone/index.md @@ -67,7 +67,7 @@ descriptions. ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Firezone] which +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Firezone] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/gitea/index.md b/docs/content/en/integration/openid-connect/gitea/index.md index ed4c691c9..360f4b02b 100644 --- a/docs/content/en/integration/openid-connect/gitea/index.md +++ b/docs/content/en/integration/openid-connect/gitea/index.md @@ -77,7 +77,7 @@ descriptions. ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Gitea] which +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Gitea] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/gitlab/index.md b/docs/content/en/integration/openid-connect/gitlab/index.md index 71ba8d01c..9764d4110 100644 --- a/docs/content/en/integration/openid-connect/gitlab/index.md +++ b/docs/content/en/integration/openid-connect/gitlab/index.md @@ -69,7 +69,7 @@ gitlab_rails['omniauth_providers'] = [ ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [GitLab] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [GitLab] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/grafana/index.md b/docs/content/en/integration/openid-connect/grafana/index.md index 245b588c6..268f8b406 100644 --- a/docs/content/en/integration/openid-connect/grafana/index.md +++ b/docs/content/en/integration/openid-connect/grafana/index.md @@ -87,7 +87,7 @@ Configure the following environment variables: ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Grafana] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Grafana] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/harbor/index.md b/docs/content/en/integration/openid-connect/harbor/index.md index 2c8223657..5735b427d 100644 --- a/docs/content/en/integration/openid-connect/harbor/index.md +++ b/docs/content/en/integration/openid-connect/harbor/index.md @@ -60,7 +60,7 @@ To configure [Harbor] to utilize Authelia as an [OpenID Connect 1.0] Provider: ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Harbor] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Harbor] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/hashicorp-vault/index.md b/docs/content/en/integration/openid-connect/hashicorp-vault/index.md index 8c5a9713c..7c5b60a48 100644 --- a/docs/content/en/integration/openid-connect/hashicorp-vault/index.md +++ b/docs/content/en/integration/openid-connect/hashicorp-vault/index.md @@ -43,7 +43,7 @@ To configure [HashiCorp Vault] to utilize Authelia as an [OpenID Connect 1.0] Pr ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [HashiCorp Vault] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [HashiCorp Vault] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/introduction.md b/docs/content/en/integration/openid-connect/introduction.md index e1568acdd..2d528283b 100644 --- a/docs/content/en/integration/openid-connect/introduction.md +++ b/docs/content/en/integration/openid-connect/introduction.md @@ -18,8 +18,10 @@ Authelia can act as an [OpenID Connect 1.0] Provider as part of an open beta. Th specifics that can be used for integrating Authelia with an [OpenID Connect 1.0] Relying Party, as well as specific documentation for some [OpenID Connect 1.0] Relying Party implementations. -See the [configuration documentation](../../configuration/identity-providers/open-id-connect.md) for information on how -to configure the Authelia [OpenID Connect 1.0] Provider. +See the [OpenID Connect 1.0 Provider](../../configuration/identity-providers/openid-connect/provider.md) and +[OpenID Connect 1.0 Clients](../../configuration/identity-providers/openid-connect/clients.md) configuration guides for +information on how to configure the Authelia [OpenID Connect 1.0] Provider (note the clients guide is for configuring +the registered clients in the provider). This page is intended as an integration reference point for any implementers who wish to integrate an [OpenID Connect 1.0] Relying Party (client application) either as a developer or user of the third party Reyling Party. @@ -124,6 +126,7 @@ Authelia's response objects can have the following signature algorithms: ### Request Object +Authelia accepts a wide variety of request object types. | Algorithm | Key Type | Hashing Algorithm | Use | Notes | |:---------:|:------------------:|:-----------------:|:---------:|:--------------------------------------------------:| @@ -131,6 +134,15 @@ Authelia's response objects can have the following signature algorithms: | HS256 | HMAC Shared Secret | SHA-256 | Signature | [Client Authentication Method] `client_secret_jwt` | | HS384 | HMAC Shared Secret | SHA-384 | Signature | [Client Authentication Method] `client_secret_jwt` | | HS512 | HMAC Shared Secret | SHA-512 | Signature | [Client Authentication Method] `client_secret_jwt` | +| RS256 | RSA | SHA-256 | Signature | [Client Authentication Method] `private_key_jwt` | +| RS384 | RSA | SHA-384 | Signature | [Client Authentication Method] `private_key_jwt` | +| RS512 | RSA | SHA-512 | Signature | [Client Authentication Method] `private_key_jwt` | +| ES256 | ECDSA P-256 | SHA-256 | Signature | [Client Authentication Method] `private_key_jwt` | +| ES384 | ECDSA P-384 | SHA-384 | Signature | [Client Authentication Method] `private_key_jwt` | +| ES512 | ECDSA P-521 | SHA-512 | Signature | [Client Authentication Method] `private_key_jwt` | +| PS256 | RSA (MFG1) | SHA-256 | Signature | [Client Authentication Method] `private_key_jwt` | +| PS384 | RSA (MFG1) | SHA-384 | Signature | [Client Authentication Method] `private_key_jwt` | +| PS512 | RSA (MFG1) | SHA-512 | Signature | [Client Authentication Method] `private_key_jwt` | [Client Authentication Method]: #client-authentication-method @@ -208,7 +220,7 @@ specification and the [OAuth 2.0 - Client Types] specification for more informat | Secret via HTTP Basic Auth Scheme | `client_secret_basic` | `confidential` | N/A | N/A | | Secret via HTTP POST Body | `client_secret_post` | `confidential` | N/A | N/A | | JWT (signed by secret) | `client_secret_jwt` | `confidential` | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | -| JWT (signed by private key) | `private_key_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | +| JWT (signed by private key) | `private_key_jwt` | `confidential` | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | | [OAuth 2.0 Mutual-TLS] | `tls_client_auth` | Not Supported | N/A | N/A | | [OAuth 2.0 Mutual-TLS] (Self Signed) | `self_signed_tls_client_auth` | Not Supported | N/A | N/A | | No Authentication | `none` | `public` | `public` | N/A | @@ -243,7 +255,7 @@ Below is a list of the potential values we place in the [Claim] and their meanin ## User Information Signing Algorithm The following table describes the response from the [UserInfo] endpoint depending on the -[userinfo_signing_algorithm](../../configuration/identity-providers/open-id-connect.md#userinfosigningalgorithm). +[userinfo_signing_algorithm](../../configuration/identity-providers/openid-connect/clients.md#userinfosigningalgorithm). | Signing Algorithm | Encoding | Content Type | |:-----------------:|:------------:|:-----------------------------------:| diff --git a/docs/content/en/integration/openid-connect/komga/index.md b/docs/content/en/integration/openid-connect/komga/index.md index 2299af893..8e80ed01b 100644 --- a/docs/content/en/integration/openid-connect/komga/index.md +++ b/docs/content/en/integration/openid-connect/komga/index.md @@ -65,7 +65,7 @@ spring: ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Komga] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Komga] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/minio/index.md b/docs/content/en/integration/openid-connect/minio/index.md index 8b3067002..570613d02 100644 --- a/docs/content/en/integration/openid-connect/minio/index.md +++ b/docs/content/en/integration/openid-connect/minio/index.md @@ -63,7 +63,7 @@ To configure [MinIO] to utilize Authelia as an [OpenID Connect 1.0] Provider: ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [MinIO] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [MinIO] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/misago/index.md b/docs/content/en/integration/openid-connect/misago/index.md index dc11c8efc..9e5b408ce 100644 --- a/docs/content/en/integration/openid-connect/misago/index.md +++ b/docs/content/en/integration/openid-connect/misago/index.md @@ -79,7 +79,7 @@ To configure [Misago] to utilize Authelia as an [OpenID Connect 1.0](https://www ### Authelia -The following YAML configuration is an example **Authelia** [client configuration](https://www.authelia.com/configuration/identity-providers/open-id-connect/#clients) for use with [Misago] which will operate with the above example: +The following YAML configuration is an example **Authelia** [client configuration](https://www.authelia.com/configuration/identity-providers/openid-connect/#clients) for use with [Misago] which will operate with the above example: ```yaml identity_providers: diff --git a/docs/content/en/integration/openid-connect/nextcloud/index.md b/docs/content/en/integration/openid-connect/nextcloud/index.md index 540e2069e..9cad0a8bb 100644 --- a/docs/content/en/integration/openid-connect/nextcloud/index.md +++ b/docs/content/en/integration/openid-connect/nextcloud/index.md @@ -86,7 +86,7 @@ $CONFIG = array ( ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Nextcloud] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Nextcloud] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/outline/index.md b/docs/content/en/integration/openid-connect/outline/index.md index 1a330e0f8..116aa5985 100644 --- a/docs/content/en/integration/openid-connect/outline/index.md +++ b/docs/content/en/integration/openid-connect/outline/index.md @@ -60,7 +60,7 @@ OIDC_SCOPES="openid offline_access profile email" ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Outline] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Outline] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/portainer/index.md b/docs/content/en/integration/openid-connect/portainer/index.md index e50a282da..ca4316820 100644 --- a/docs/content/en/integration/openid-connect/portainer/index.md +++ b/docs/content/en/integration/openid-connect/portainer/index.md @@ -61,7 +61,7 @@ To configure [Portainer] to utilize Authelia as an [OpenID Connect 1.0] Provider ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Portainer] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Portainer] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/proxmox/index.md b/docs/content/en/integration/openid-connect/proxmox/index.md index 17eefafc9..c9800fcba 100644 --- a/docs/content/en/integration/openid-connect/proxmox/index.md +++ b/docs/content/en/integration/openid-connect/proxmox/index.md @@ -65,7 +65,7 @@ To configure [Proxmox] to utilize Authelia as an [OpenID Connect 1.0] Provider: ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Proxmox] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Proxmox] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/seafile/index.md b/docs/content/en/integration/openid-connect/seafile/index.md index 41b4e1482..f5d738c4d 100644 --- a/docs/content/en/integration/openid-connect/seafile/index.md +++ b/docs/content/en/integration/openid-connect/seafile/index.md @@ -69,7 +69,7 @@ OAUTH_ATTRIBUTE_MAP = { ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Seafile] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Seafile] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/synapse/index.md b/docs/content/en/integration/openid-connect/synapse/index.md index 1927f306e..2067fe372 100644 --- a/docs/content/en/integration/openid-connect/synapse/index.md +++ b/docs/content/en/integration/openid-connect/synapse/index.md @@ -63,7 +63,7 @@ oidc_providers: ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Synapse] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Synapse] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/openid-connect/synology-dsm/index.md b/docs/content/en/integration/openid-connect/synology-dsm/index.md index a5dcd075e..5ebc53e26 100644 --- a/docs/content/en/integration/openid-connect/synology-dsm/index.md +++ b/docs/content/en/integration/openid-connect/synology-dsm/index.md @@ -65,7 +65,7 @@ To configure [Synology DSM] to utilize Authelia as an [OpenID Connect 1.0] Provi ### Authelia The following YAML configuration is an example __Authelia__ -[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Synology DSM] +[client configuration](../../../configuration/identity-providers/openid-connect/clients.md) for use with [Synology DSM] which will operate with the above example: ```yaml diff --git a/docs/content/en/integration/proxies/swag.md b/docs/content/en/integration/proxies/swag.md index f936ac7fa..ca936527c 100644 --- a/docs/content/en/integration/proxies/swag.md +++ b/docs/content/en/integration/proxies/swag.md @@ -58,7 +58,7 @@ In addition this represents a bad user experience in some instances such as: * Users sometimes visit the `https://app.example.com/authelia` URL which doesn't automatically redirect the user to `https://app.example.com` (if they visit `https://app.example.com` then they'll be redirected to authenticate then redirected back to their original URL) -* Administrators may wish to setup [OpenID Connect 1.0](../../configuration/identity-providers/open-id-connect.md) in +* Administrators may wish to setup [OpenID Connect 1.0](../../configuration/identity-providers/openid-connect/provider.md) in which case it also doesn't represent a good user experience as the `issuer` will be `https://app.example.com/authelia` for example * Using the [SWAG] default configurations are more difficult to support as our specific familiarity is with our own diff --git a/docs/content/en/overview/authorization/openid-connect-1.0.md b/docs/content/en/overview/authorization/openid-connect-1.0.md index a8326699a..9764f6403 100644 --- a/docs/content/en/overview/authorization/openid-connect-1.0.md +++ b/docs/content/en/overview/authorization/openid-connect-1.0.md @@ -16,6 +16,6 @@ configure your applications to use Authelia as an [OpenID Connect 1.0 Provider]( currently operate as an [OpenID Connect 1.0 Relying Party](https://openid.net/connect/). This like all single-sign on technologies requires support by the protected application. -See the [OpenID Connect 1.0 Configuration Guide](../../configuration/identity-providers/open-id-connect.md) and the +See the [OpenID Connect 1.0 Provider Configuration Guide](../../configuration/identity-providers/openid-connect/provider.md), and the [OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md) for more information. diff --git a/docs/data/configkeys.json b/docs/data/configkeys.json index cdbb3a02c..55fbabd2d 100644 --- a/docs/data/configkeys.json +++ b/docs/data/configkeys.json @@ -1 +1 @@ -[{"path":"theme","secret":false,"env":"AUTHELIA_THEME"},{"path":"certificates_directory","secret":false,"env":"AUTHELIA_CERTIFICATES_DIRECTORY"},{"path":"jwt_secret","secret":true,"env":"AUTHELIA_JWT_SECRET_FILE"},{"path":"default_redirection_url","secret":false,"env":"AUTHELIA_DEFAULT_REDIRECTION_URL"},{"path":"default_2fa_method","secret":false,"env":"AUTHELIA_DEFAULT_2FA_METHOD"},{"path":"log.level","secret":false,"env":"AUTHELIA_LOG_LEVEL"},{"path":"log.format","secret":false,"env":"AUTHELIA_LOG_FORMAT"},{"path":"log.file_path","secret":false,"env":"AUTHELIA_LOG_FILE_PATH"},{"path":"log.keep_stdout","secret":false,"env":"AUTHELIA_LOG_KEEP_STDOUT"},{"path":"identity_providers.oidc.hmac_secret","secret":true,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE"},{"path":"identity_providers.oidc.issuer_certificate_chain","secret":true,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_CERTIFICATE_CHAIN_FILE"},{"path":"identity_providers.oidc.issuer_private_key","secret":true,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE"},{"path":"identity_providers.oidc.access_token_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ACCESS_TOKEN_LIFESPAN"},{"path":"identity_providers.oidc.authorize_code_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_AUTHORIZE_CODE_LIFESPAN"},{"path":"identity_providers.oidc.id_token_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ID_TOKEN_LIFESPAN"},{"path":"identity_providers.oidc.refresh_token_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_REFRESH_TOKEN_LIFESPAN"},{"path":"identity_providers.oidc.enable_client_debug_messages","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_CLIENT_DEBUG_MESSAGES"},{"path":"identity_providers.oidc.minimum_parameter_entropy","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_MINIMUM_PARAMETER_ENTROPY"},{"path":"identity_providers.oidc.enforce_pkce","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENFORCE_PKCE"},{"path":"identity_providers.oidc.enable_pkce_plain_challenge","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_PKCE_PLAIN_CHALLENGE"},{"path":"identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ALLOWED_ORIGINS_FROM_CLIENT_REDIRECT_URIS"},{"path":"identity_providers.oidc.pushed_authorizations.enforce","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_PUSHED_AUTHORIZATIONS_ENFORCE"},{"path":"identity_providers.oidc.pushed_authorizations.context_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_PUSHED_AUTHORIZATIONS_CONTEXT_LIFESPAN"},{"path":"authentication_backend.password_reset.disable","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_DISABLE"},{"path":"authentication_backend.password_reset.custom_url","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_CUSTOM_URL"},{"path":"authentication_backend.refresh_interval","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_REFRESH_INTERVAL"},{"path":"authentication_backend.file.path","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PATH"},{"path":"authentication_backend.file.watch","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_WATCH"},{"path":"authentication_backend.file.password.algorithm","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ALGORITHM"},{"path":"authentication_backend.file.password.argon2.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_VARIANT"},{"path":"authentication_backend.file.password.argon2.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_ITERATIONS"},{"path":"authentication_backend.file.password.argon2.memory","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_MEMORY"},{"path":"authentication_backend.file.password.argon2.parallelism","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_PARALLELISM"},{"path":"authentication_backend.file.password.argon2.key_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_KEY_LENGTH"},{"path":"authentication_backend.file.password.argon2.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_SALT_LENGTH"},{"path":"authentication_backend.file.password.sha2crypt.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_VARIANT"},{"path":"authentication_backend.file.password.sha2crypt.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_ITERATIONS"},{"path":"authentication_backend.file.password.sha2crypt.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_SALT_LENGTH"},{"path":"authentication_backend.file.password.pbkdf2.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_VARIANT"},{"path":"authentication_backend.file.password.pbkdf2.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_ITERATIONS"},{"path":"authentication_backend.file.password.pbkdf2.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_SALT_LENGTH"},{"path":"authentication_backend.file.password.bcrypt.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_VARIANT"},{"path":"authentication_backend.file.password.bcrypt.cost","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_COST"},{"path":"authentication_backend.file.password.scrypt.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_ITERATIONS"},{"path":"authentication_backend.file.password.scrypt.block_size","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_BLOCK_SIZE"},{"path":"authentication_backend.file.password.scrypt.parallelism","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_PARALLELISM"},{"path":"authentication_backend.file.password.scrypt.key_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_KEY_LENGTH"},{"path":"authentication_backend.file.password.scrypt.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_SALT_LENGTH"},{"path":"authentication_backend.file.password.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ITERATIONS"},{"path":"authentication_backend.file.password.memory","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_MEMORY"},{"path":"authentication_backend.file.password.parallelism","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PARALLELISM"},{"path":"authentication_backend.file.password.key_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_KEY_LENGTH"},{"path":"authentication_backend.file.password.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SALT_LENGTH"},{"path":"authentication_backend.file.search.email","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_EMAIL"},{"path":"authentication_backend.file.search.case_insensitive","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_CASE_INSENSITIVE"},{"path":"authentication_backend.ldap.address","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDRESS"},{"path":"authentication_backend.ldap.implementation","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_IMPLEMENTATION"},{"path":"authentication_backend.ldap.timeout","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TIMEOUT"},{"path":"authentication_backend.ldap.start_tls","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_START_TLS"},{"path":"authentication_backend.ldap.tls.minimum_version","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MINIMUM_VERSION"},{"path":"authentication_backend.ldap.tls.maximum_version","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MAXIMUM_VERSION"},{"path":"authentication_backend.ldap.tls.skip_verify","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SKIP_VERIFY"},{"path":"authentication_backend.ldap.tls.server_name","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SERVER_NAME"},{"path":"authentication_backend.ldap.tls.private_key","secret":true,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_PRIVATE_KEY_FILE"},{"path":"authentication_backend.ldap.tls.certificate_chain","secret":true,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"authentication_backend.ldap.base_dn","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_BASE_DN"},{"path":"authentication_backend.ldap.additional_users_dn","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_USERS_DN"},{"path":"authentication_backend.ldap.users_filter","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USERS_FILTER"},{"path":"authentication_backend.ldap.additional_groups_dn","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_GROUPS_DN"},{"path":"authentication_backend.ldap.groups_filter","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUPS_FILTER"},{"path":"authentication_backend.ldap.group_name_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUP_NAME_ATTRIBUTE"},{"path":"authentication_backend.ldap.username_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USERNAME_ATTRIBUTE"},{"path":"authentication_backend.ldap.mail_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_MAIL_ATTRIBUTE"},{"path":"authentication_backend.ldap.display_name_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_DISPLAY_NAME_ATTRIBUTE"},{"path":"authentication_backend.ldap.permit_referrals","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_REFERRALS"},{"path":"authentication_backend.ldap.permit_unauthenticated_bind","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_UNAUTHENTICATED_BIND"},{"path":"authentication_backend.ldap.permit_feature_detection_failure","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_FEATURE_DETECTION_FAILURE"},{"path":"authentication_backend.ldap.user","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USER"},{"path":"authentication_backend.ldap.password","secret":true,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE"},{"path":"session.secret","secret":true,"env":"AUTHELIA_SESSION_SECRET_FILE"},{"path":"session.name","secret":false,"env":"AUTHELIA_SESSION_NAME"},{"path":"session.domain","secret":false,"env":"AUTHELIA_SESSION_DOMAIN"},{"path":"session.same_site","secret":false,"env":"AUTHELIA_SESSION_SAME_SITE"},{"path":"session.expiration","secret":false,"env":"AUTHELIA_SESSION_EXPIRATION"},{"path":"session.inactivity","secret":false,"env":"AUTHELIA_SESSION_INACTIVITY"},{"path":"session.remember_me","secret":false,"env":"AUTHELIA_SESSION_REMEMBER_ME"},{"path":"session","secret":false,"env":"AUTHELIA_SESSION"},{"path":"session.redis.host","secret":false,"env":"AUTHELIA_SESSION_REDIS_HOST"},{"path":"session.redis.port","secret":false,"env":"AUTHELIA_SESSION_REDIS_PORT"},{"path":"session.redis.username","secret":false,"env":"AUTHELIA_SESSION_REDIS_USERNAME"},{"path":"session.redis.password","secret":true,"env":"AUTHELIA_SESSION_REDIS_PASSWORD_FILE"},{"path":"session.redis.database_index","secret":false,"env":"AUTHELIA_SESSION_REDIS_DATABASE_INDEX"},{"path":"session.redis.maximum_active_connections","secret":false,"env":"AUTHELIA_SESSION_REDIS_MAXIMUM_ACTIVE_CONNECTIONS"},{"path":"session.redis.minimum_idle_connections","secret":false,"env":"AUTHELIA_SESSION_REDIS_MINIMUM_IDLE_CONNECTIONS"},{"path":"session.redis.tls.minimum_version","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_MINIMUM_VERSION"},{"path":"session.redis.tls.maximum_version","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_MAXIMUM_VERSION"},{"path":"session.redis.tls.skip_verify","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_SKIP_VERIFY"},{"path":"session.redis.tls.server_name","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_SERVER_NAME"},{"path":"session.redis.tls.private_key","secret":true,"env":"AUTHELIA_SESSION_REDIS_TLS_PRIVATE_KEY_FILE"},{"path":"session.redis.tls.certificate_chain","secret":true,"env":"AUTHELIA_SESSION_REDIS_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"session.redis.high_availability.sentinel_name","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_NAME"},{"path":"session.redis.high_availability.sentinel_username","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_USERNAME"},{"path":"session.redis.high_availability.sentinel_password","secret":true,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE"},{"path":"session.redis.high_availability.route_by_latency","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_BY_LATENCY"},{"path":"session.redis.high_availability.route_randomly","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_RANDOMLY"},{"path":"totp.disable","secret":false,"env":"AUTHELIA_TOTP_DISABLE"},{"path":"totp.issuer","secret":false,"env":"AUTHELIA_TOTP_ISSUER"},{"path":"totp.algorithm","secret":false,"env":"AUTHELIA_TOTP_ALGORITHM"},{"path":"totp.digits","secret":false,"env":"AUTHELIA_TOTP_DIGITS"},{"path":"totp.period","secret":false,"env":"AUTHELIA_TOTP_PERIOD"},{"path":"totp.skew","secret":false,"env":"AUTHELIA_TOTP_SKEW"},{"path":"totp.secret_size","secret":false,"env":"AUTHELIA_TOTP_SECRET_SIZE"},{"path":"duo_api.disable","secret":false,"env":"AUTHELIA_DUO_API_DISABLE"},{"path":"duo_api.hostname","secret":false,"env":"AUTHELIA_DUO_API_HOSTNAME"},{"path":"duo_api.integration_key","secret":true,"env":"AUTHELIA_DUO_API_INTEGRATION_KEY_FILE"},{"path":"duo_api.secret_key","secret":true,"env":"AUTHELIA_DUO_API_SECRET_KEY_FILE"},{"path":"duo_api.enable_self_enrollment","secret":false,"env":"AUTHELIA_DUO_API_ENABLE_SELF_ENROLLMENT"},{"path":"access_control.default_policy","secret":false,"env":"AUTHELIA_ACCESS_CONTROL_DEFAULT_POLICY"},{"path":"ntp.address","secret":false,"env":"AUTHELIA_NTP_ADDRESS"},{"path":"ntp.version","secret":false,"env":"AUTHELIA_NTP_VERSION"},{"path":"ntp.max_desync","secret":false,"env":"AUTHELIA_NTP_MAX_DESYNC"},{"path":"ntp.disable_startup_check","secret":false,"env":"AUTHELIA_NTP_DISABLE_STARTUP_CHECK"},{"path":"ntp.disable_failure","secret":false,"env":"AUTHELIA_NTP_DISABLE_FAILURE"},{"path":"regulation.max_retries","secret":false,"env":"AUTHELIA_REGULATION_MAX_RETRIES"},{"path":"regulation.find_time","secret":false,"env":"AUTHELIA_REGULATION_FIND_TIME"},{"path":"regulation.ban_time","secret":false,"env":"AUTHELIA_REGULATION_BAN_TIME"},{"path":"storage.local.path","secret":false,"env":"AUTHELIA_STORAGE_LOCAL_PATH"},{"path":"storage.mysql.address","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_ADDRESS"},{"path":"storage.mysql.database","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_DATABASE"},{"path":"storage.mysql.username","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_USERNAME"},{"path":"storage.mysql.password","secret":true,"env":"AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE"},{"path":"storage.mysql.timeout","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TIMEOUT"},{"path":"storage.mysql.host","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_HOST"},{"path":"storage.mysql.port","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_PORT"},{"path":"storage.mysql.tls.minimum_version","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_MINIMUM_VERSION"},{"path":"storage.mysql.tls.maximum_version","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_MAXIMUM_VERSION"},{"path":"storage.mysql.tls.skip_verify","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_SKIP_VERIFY"},{"path":"storage.mysql.tls.server_name","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_SERVER_NAME"},{"path":"storage.mysql.tls.private_key","secret":true,"env":"AUTHELIA_STORAGE_MYSQL_TLS_PRIVATE_KEY_FILE"},{"path":"storage.mysql.tls.certificate_chain","secret":true,"env":"AUTHELIA_STORAGE_MYSQL_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"storage.postgres.address","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_ADDRESS"},{"path":"storage.postgres.database","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_DATABASE"},{"path":"storage.postgres.username","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_USERNAME"},{"path":"storage.postgres.password","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE"},{"path":"storage.postgres.timeout","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TIMEOUT"},{"path":"storage.postgres.host","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_HOST"},{"path":"storage.postgres.port","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_PORT"},{"path":"storage.postgres.schema","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SCHEMA"},{"path":"storage.postgres.tls.minimum_version","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_MINIMUM_VERSION"},{"path":"storage.postgres.tls.maximum_version","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_MAXIMUM_VERSION"},{"path":"storage.postgres.tls.skip_verify","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_SKIP_VERIFY"},{"path":"storage.postgres.tls.server_name","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_SERVER_NAME"},{"path":"storage.postgres.tls.private_key","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_PRIVATE_KEY_FILE"},{"path":"storage.postgres.tls.certificate_chain","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"storage.postgres.ssl.mode","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_MODE"},{"path":"storage.postgres.ssl.root_certificate","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_ROOT_CERTIFICATE"},{"path":"storage.postgres.ssl.certificate","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_CERTIFICATE"},{"path":"storage.postgres.ssl.key","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_KEY_FILE"},{"path":"storage.encryption_key","secret":true,"env":"AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE"},{"path":"notifier.disable_startup_check","secret":false,"env":"AUTHELIA_NOTIFIER_DISABLE_STARTUP_CHECK"},{"path":"notifier.filesystem.filename","secret":false,"env":"AUTHELIA_NOTIFIER_FILESYSTEM_FILENAME"},{"path":"notifier.smtp.address","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_ADDRESS"},{"path":"notifier.smtp.timeout","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TIMEOUT"},{"path":"notifier.smtp.username","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_USERNAME"},{"path":"notifier.smtp.password","secret":true,"env":"AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE"},{"path":"notifier.smtp.identifier","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_IDENTIFIER"},{"path":"notifier.smtp.sender","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_SENDER"},{"path":"notifier.smtp.subject","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_SUBJECT"},{"path":"notifier.smtp.startup_check_address","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_STARTUP_CHECK_ADDRESS"},{"path":"notifier.smtp.disable_require_tls","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_DISABLE_REQUIRE_TLS"},{"path":"notifier.smtp.disable_html_emails","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_DISABLE_HTML_EMAILS"},{"path":"notifier.smtp.disable_starttls","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_DISABLE_STARTTLS"},{"path":"notifier.smtp.tls.minimum_version","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_MINIMUM_VERSION"},{"path":"notifier.smtp.tls.maximum_version","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_MAXIMUM_VERSION"},{"path":"notifier.smtp.tls.skip_verify","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_SKIP_VERIFY"},{"path":"notifier.smtp.tls.server_name","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_SERVER_NAME"},{"path":"notifier.smtp.tls.private_key","secret":true,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_PRIVATE_KEY_FILE"},{"path":"notifier.smtp.tls.certificate_chain","secret":true,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"notifier.smtp.host","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_HOST"},{"path":"notifier.smtp.port","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_PORT"},{"path":"notifier.template_path","secret":false,"env":"AUTHELIA_NOTIFIER_TEMPLATE_PATH"},{"path":"server.address","secret":false,"env":"AUTHELIA_SERVER_ADDRESS"},{"path":"server.path","secret":false,"env":"AUTHELIA_SERVER_PATH"},{"path":"server.asset_path","secret":false,"env":"AUTHELIA_SERVER_ASSET_PATH"},{"path":"server.disable_healthcheck","secret":false,"env":"AUTHELIA_SERVER_DISABLE_HEALTHCHECK"},{"path":"server.tls.certificate","secret":false,"env":"AUTHELIA_SERVER_TLS_CERTIFICATE"},{"path":"server.tls.key","secret":true,"env":"AUTHELIA_SERVER_TLS_KEY_FILE"},{"path":"server.headers.csp_template","secret":false,"env":"AUTHELIA_SERVER_HEADERS_CSP_TEMPLATE"},{"path":"server.endpoints.enable_pprof","secret":false,"env":"AUTHELIA_SERVER_ENDPOINTS_ENABLE_PPROF"},{"path":"server.endpoints.enable_expvars","secret":false,"env":"AUTHELIA_SERVER_ENDPOINTS_ENABLE_EXPVARS"},{"path":"server.buffers.read","secret":false,"env":"AUTHELIA_SERVER_BUFFERS_READ"},{"path":"server.buffers.write","secret":false,"env":"AUTHELIA_SERVER_BUFFERS_WRITE"},{"path":"server.timeouts.read","secret":false,"env":"AUTHELIA_SERVER_TIMEOUTS_READ"},{"path":"server.timeouts.write","secret":false,"env":"AUTHELIA_SERVER_TIMEOUTS_WRITE"},{"path":"server.timeouts.idle","secret":false,"env":"AUTHELIA_SERVER_TIMEOUTS_IDLE"},{"path":"server.host","secret":false,"env":"AUTHELIA_SERVER_HOST"},{"path":"server.port","secret":false,"env":"AUTHELIA_SERVER_PORT"},{"path":"telemetry.metrics.enabled","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_ENABLED"},{"path":"telemetry.metrics.address","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_ADDRESS"},{"path":"telemetry.metrics.buffers.read","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_BUFFERS_READ"},{"path":"telemetry.metrics.buffers.write","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_BUFFERS_WRITE"},{"path":"telemetry.metrics.timeouts.read","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_READ"},{"path":"telemetry.metrics.timeouts.write","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_WRITE"},{"path":"telemetry.metrics.timeouts.idle","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_IDLE"},{"path":"webauthn.disable","secret":false,"env":"AUTHELIA_WEBAUTHN_DISABLE"},{"path":"webauthn.display_name","secret":false,"env":"AUTHELIA_WEBAUTHN_DISPLAY_NAME"},{"path":"webauthn.attestation_conveyance_preference","secret":false,"env":"AUTHELIA_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE"},{"path":"webauthn.user_verification","secret":false,"env":"AUTHELIA_WEBAUTHN_USER_VERIFICATION"},{"path":"webauthn.timeout","secret":false,"env":"AUTHELIA_WEBAUTHN_TIMEOUT"},{"path":"password_policy.standard.enabled","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_ENABLED"},{"path":"password_policy.standard.min_length","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_MIN_LENGTH"},{"path":"password_policy.standard.max_length","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_MAX_LENGTH"},{"path":"password_policy.standard.require_uppercase","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_UPPERCASE"},{"path":"password_policy.standard.require_lowercase","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_LOWERCASE"},{"path":"password_policy.standard.require_number","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_NUMBER"},{"path":"password_policy.standard.require_special","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_SPECIAL"},{"path":"password_policy.zxcvbn.enabled","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_ZXCVBN_ENABLED"},{"path":"password_policy.zxcvbn.min_score","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_ZXCVBN_MIN_SCORE"},{"path":"privacy_policy.enabled","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_ENABLED"},{"path":"privacy_policy.require_user_acceptance","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_REQUIRE_USER_ACCEPTANCE"},{"path":"privacy_policy.policy_url","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_POLICY_URL"}] \ No newline at end of file +[{"path":"theme","secret":false,"env":"AUTHELIA_THEME"},{"path":"certificates_directory","secret":false,"env":"AUTHELIA_CERTIFICATES_DIRECTORY"},{"path":"jwt_secret","secret":true,"env":"AUTHELIA_JWT_SECRET_FILE"},{"path":"default_redirection_url","secret":false,"env":"AUTHELIA_DEFAULT_REDIRECTION_URL"},{"path":"default_2fa_method","secret":false,"env":"AUTHELIA_DEFAULT_2FA_METHOD"},{"path":"log.level","secret":false,"env":"AUTHELIA_LOG_LEVEL"},{"path":"log.format","secret":false,"env":"AUTHELIA_LOG_FORMAT"},{"path":"log.file_path","secret":false,"env":"AUTHELIA_LOG_FILE_PATH"},{"path":"log.keep_stdout","secret":false,"env":"AUTHELIA_LOG_KEEP_STDOUT"},{"path":"identity_providers.oidc.hmac_secret","secret":true,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE"},{"path":"identity_providers.oidc.issuer_certificate_chain","secret":true,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_CERTIFICATE_CHAIN_FILE"},{"path":"identity_providers.oidc.issuer_private_key","secret":true,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE"},{"path":"identity_providers.oidc.access_token_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ACCESS_TOKEN_LIFESPAN"},{"path":"identity_providers.oidc.authorize_code_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_AUTHORIZE_CODE_LIFESPAN"},{"path":"identity_providers.oidc.id_token_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ID_TOKEN_LIFESPAN"},{"path":"identity_providers.oidc.refresh_token_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_REFRESH_TOKEN_LIFESPAN"},{"path":"identity_providers.oidc.enable_client_debug_messages","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_CLIENT_DEBUG_MESSAGES"},{"path":"identity_providers.oidc.minimum_parameter_entropy","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_MINIMUM_PARAMETER_ENTROPY"},{"path":"identity_providers.oidc.enforce_pkce","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENFORCE_PKCE"},{"path":"identity_providers.oidc.enable_pkce_plain_challenge","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_PKCE_PLAIN_CHALLENGE"},{"path":"identity_providers.oidc.pushed_authorizations.enforce","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_PUSHED_AUTHORIZATIONS_ENFORCE"},{"path":"identity_providers.oidc.pushed_authorizations.context_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_PUSHED_AUTHORIZATIONS_CONTEXT_LIFESPAN"},{"path":"identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ALLOWED_ORIGINS_FROM_CLIENT_REDIRECT_URIS"},{"path":"identity_providers.oidc","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC"},{"path":"authentication_backend.password_reset.disable","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_DISABLE"},{"path":"authentication_backend.password_reset.custom_url","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_CUSTOM_URL"},{"path":"authentication_backend.refresh_interval","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_REFRESH_INTERVAL"},{"path":"authentication_backend.file.path","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PATH"},{"path":"authentication_backend.file.watch","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_WATCH"},{"path":"authentication_backend.file.password.algorithm","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ALGORITHM"},{"path":"authentication_backend.file.password.argon2.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_VARIANT"},{"path":"authentication_backend.file.password.argon2.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_ITERATIONS"},{"path":"authentication_backend.file.password.argon2.memory","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_MEMORY"},{"path":"authentication_backend.file.password.argon2.parallelism","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_PARALLELISM"},{"path":"authentication_backend.file.password.argon2.key_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_KEY_LENGTH"},{"path":"authentication_backend.file.password.argon2.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_SALT_LENGTH"},{"path":"authentication_backend.file.password.sha2crypt.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_VARIANT"},{"path":"authentication_backend.file.password.sha2crypt.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_ITERATIONS"},{"path":"authentication_backend.file.password.sha2crypt.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_SALT_LENGTH"},{"path":"authentication_backend.file.password.pbkdf2.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_VARIANT"},{"path":"authentication_backend.file.password.pbkdf2.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_ITERATIONS"},{"path":"authentication_backend.file.password.pbkdf2.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_SALT_LENGTH"},{"path":"authentication_backend.file.password.bcrypt.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_VARIANT"},{"path":"authentication_backend.file.password.bcrypt.cost","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_COST"},{"path":"authentication_backend.file.password.scrypt.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_ITERATIONS"},{"path":"authentication_backend.file.password.scrypt.block_size","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_BLOCK_SIZE"},{"path":"authentication_backend.file.password.scrypt.parallelism","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_PARALLELISM"},{"path":"authentication_backend.file.password.scrypt.key_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_KEY_LENGTH"},{"path":"authentication_backend.file.password.scrypt.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_SALT_LENGTH"},{"path":"authentication_backend.file.password.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ITERATIONS"},{"path":"authentication_backend.file.password.memory","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_MEMORY"},{"path":"authentication_backend.file.password.parallelism","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PARALLELISM"},{"path":"authentication_backend.file.password.key_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_KEY_LENGTH"},{"path":"authentication_backend.file.password.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SALT_LENGTH"},{"path":"authentication_backend.file.search.email","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_EMAIL"},{"path":"authentication_backend.file.search.case_insensitive","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_CASE_INSENSITIVE"},{"path":"authentication_backend.ldap.address","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDRESS"},{"path":"authentication_backend.ldap.implementation","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_IMPLEMENTATION"},{"path":"authentication_backend.ldap.timeout","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TIMEOUT"},{"path":"authentication_backend.ldap.start_tls","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_START_TLS"},{"path":"authentication_backend.ldap.tls.minimum_version","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MINIMUM_VERSION"},{"path":"authentication_backend.ldap.tls.maximum_version","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MAXIMUM_VERSION"},{"path":"authentication_backend.ldap.tls.skip_verify","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SKIP_VERIFY"},{"path":"authentication_backend.ldap.tls.server_name","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SERVER_NAME"},{"path":"authentication_backend.ldap.tls.private_key","secret":true,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_PRIVATE_KEY_FILE"},{"path":"authentication_backend.ldap.tls.certificate_chain","secret":true,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"authentication_backend.ldap.base_dn","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_BASE_DN"},{"path":"authentication_backend.ldap.additional_users_dn","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_USERS_DN"},{"path":"authentication_backend.ldap.users_filter","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USERS_FILTER"},{"path":"authentication_backend.ldap.additional_groups_dn","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_GROUPS_DN"},{"path":"authentication_backend.ldap.groups_filter","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUPS_FILTER"},{"path":"authentication_backend.ldap.group_name_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUP_NAME_ATTRIBUTE"},{"path":"authentication_backend.ldap.username_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USERNAME_ATTRIBUTE"},{"path":"authentication_backend.ldap.mail_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_MAIL_ATTRIBUTE"},{"path":"authentication_backend.ldap.display_name_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_DISPLAY_NAME_ATTRIBUTE"},{"path":"authentication_backend.ldap.permit_referrals","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_REFERRALS"},{"path":"authentication_backend.ldap.permit_unauthenticated_bind","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_UNAUTHENTICATED_BIND"},{"path":"authentication_backend.ldap.permit_feature_detection_failure","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_FEATURE_DETECTION_FAILURE"},{"path":"authentication_backend.ldap.user","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USER"},{"path":"authentication_backend.ldap.password","secret":true,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE"},{"path":"session.secret","secret":true,"env":"AUTHELIA_SESSION_SECRET_FILE"},{"path":"session.name","secret":false,"env":"AUTHELIA_SESSION_NAME"},{"path":"session.domain","secret":false,"env":"AUTHELIA_SESSION_DOMAIN"},{"path":"session.same_site","secret":false,"env":"AUTHELIA_SESSION_SAME_SITE"},{"path":"session.expiration","secret":false,"env":"AUTHELIA_SESSION_EXPIRATION"},{"path":"session.inactivity","secret":false,"env":"AUTHELIA_SESSION_INACTIVITY"},{"path":"session.remember_me","secret":false,"env":"AUTHELIA_SESSION_REMEMBER_ME"},{"path":"session","secret":false,"env":"AUTHELIA_SESSION"},{"path":"session.redis.host","secret":false,"env":"AUTHELIA_SESSION_REDIS_HOST"},{"path":"session.redis.port","secret":false,"env":"AUTHELIA_SESSION_REDIS_PORT"},{"path":"session.redis.username","secret":false,"env":"AUTHELIA_SESSION_REDIS_USERNAME"},{"path":"session.redis.password","secret":true,"env":"AUTHELIA_SESSION_REDIS_PASSWORD_FILE"},{"path":"session.redis.database_index","secret":false,"env":"AUTHELIA_SESSION_REDIS_DATABASE_INDEX"},{"path":"session.redis.maximum_active_connections","secret":false,"env":"AUTHELIA_SESSION_REDIS_MAXIMUM_ACTIVE_CONNECTIONS"},{"path":"session.redis.minimum_idle_connections","secret":false,"env":"AUTHELIA_SESSION_REDIS_MINIMUM_IDLE_CONNECTIONS"},{"path":"session.redis.tls.minimum_version","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_MINIMUM_VERSION"},{"path":"session.redis.tls.maximum_version","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_MAXIMUM_VERSION"},{"path":"session.redis.tls.skip_verify","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_SKIP_VERIFY"},{"path":"session.redis.tls.server_name","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_SERVER_NAME"},{"path":"session.redis.tls.private_key","secret":true,"env":"AUTHELIA_SESSION_REDIS_TLS_PRIVATE_KEY_FILE"},{"path":"session.redis.tls.certificate_chain","secret":true,"env":"AUTHELIA_SESSION_REDIS_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"session.redis.high_availability.sentinel_name","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_NAME"},{"path":"session.redis.high_availability.sentinel_username","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_USERNAME"},{"path":"session.redis.high_availability.sentinel_password","secret":true,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE"},{"path":"session.redis.high_availability.route_by_latency","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_BY_LATENCY"},{"path":"session.redis.high_availability.route_randomly","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_RANDOMLY"},{"path":"totp.disable","secret":false,"env":"AUTHELIA_TOTP_DISABLE"},{"path":"totp.issuer","secret":false,"env":"AUTHELIA_TOTP_ISSUER"},{"path":"totp.algorithm","secret":false,"env":"AUTHELIA_TOTP_ALGORITHM"},{"path":"totp.digits","secret":false,"env":"AUTHELIA_TOTP_DIGITS"},{"path":"totp.period","secret":false,"env":"AUTHELIA_TOTP_PERIOD"},{"path":"totp.skew","secret":false,"env":"AUTHELIA_TOTP_SKEW"},{"path":"totp.secret_size","secret":false,"env":"AUTHELIA_TOTP_SECRET_SIZE"},{"path":"duo_api.disable","secret":false,"env":"AUTHELIA_DUO_API_DISABLE"},{"path":"duo_api.hostname","secret":false,"env":"AUTHELIA_DUO_API_HOSTNAME"},{"path":"duo_api.integration_key","secret":true,"env":"AUTHELIA_DUO_API_INTEGRATION_KEY_FILE"},{"path":"duo_api.secret_key","secret":true,"env":"AUTHELIA_DUO_API_SECRET_KEY_FILE"},{"path":"duo_api.enable_self_enrollment","secret":false,"env":"AUTHELIA_DUO_API_ENABLE_SELF_ENROLLMENT"},{"path":"access_control.default_policy","secret":false,"env":"AUTHELIA_ACCESS_CONTROL_DEFAULT_POLICY"},{"path":"ntp.address","secret":false,"env":"AUTHELIA_NTP_ADDRESS"},{"path":"ntp.version","secret":false,"env":"AUTHELIA_NTP_VERSION"},{"path":"ntp.max_desync","secret":false,"env":"AUTHELIA_NTP_MAX_DESYNC"},{"path":"ntp.disable_startup_check","secret":false,"env":"AUTHELIA_NTP_DISABLE_STARTUP_CHECK"},{"path":"ntp.disable_failure","secret":false,"env":"AUTHELIA_NTP_DISABLE_FAILURE"},{"path":"regulation.max_retries","secret":false,"env":"AUTHELIA_REGULATION_MAX_RETRIES"},{"path":"regulation.find_time","secret":false,"env":"AUTHELIA_REGULATION_FIND_TIME"},{"path":"regulation.ban_time","secret":false,"env":"AUTHELIA_REGULATION_BAN_TIME"},{"path":"storage.local.path","secret":false,"env":"AUTHELIA_STORAGE_LOCAL_PATH"},{"path":"storage.mysql.address","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_ADDRESS"},{"path":"storage.mysql.database","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_DATABASE"},{"path":"storage.mysql.username","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_USERNAME"},{"path":"storage.mysql.password","secret":true,"env":"AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE"},{"path":"storage.mysql.timeout","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TIMEOUT"},{"path":"storage.mysql.host","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_HOST"},{"path":"storage.mysql.port","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_PORT"},{"path":"storage.mysql.tls.minimum_version","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_MINIMUM_VERSION"},{"path":"storage.mysql.tls.maximum_version","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_MAXIMUM_VERSION"},{"path":"storage.mysql.tls.skip_verify","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_SKIP_VERIFY"},{"path":"storage.mysql.tls.server_name","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_SERVER_NAME"},{"path":"storage.mysql.tls.private_key","secret":true,"env":"AUTHELIA_STORAGE_MYSQL_TLS_PRIVATE_KEY_FILE"},{"path":"storage.mysql.tls.certificate_chain","secret":true,"env":"AUTHELIA_STORAGE_MYSQL_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"storage.postgres.address","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_ADDRESS"},{"path":"storage.postgres.database","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_DATABASE"},{"path":"storage.postgres.username","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_USERNAME"},{"path":"storage.postgres.password","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE"},{"path":"storage.postgres.timeout","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TIMEOUT"},{"path":"storage.postgres.host","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_HOST"},{"path":"storage.postgres.port","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_PORT"},{"path":"storage.postgres.schema","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SCHEMA"},{"path":"storage.postgres.tls.minimum_version","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_MINIMUM_VERSION"},{"path":"storage.postgres.tls.maximum_version","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_MAXIMUM_VERSION"},{"path":"storage.postgres.tls.skip_verify","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_SKIP_VERIFY"},{"path":"storage.postgres.tls.server_name","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_SERVER_NAME"},{"path":"storage.postgres.tls.private_key","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_PRIVATE_KEY_FILE"},{"path":"storage.postgres.tls.certificate_chain","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"storage.postgres.ssl.mode","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_MODE"},{"path":"storage.postgres.ssl.root_certificate","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_ROOT_CERTIFICATE"},{"path":"storage.postgres.ssl.certificate","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_CERTIFICATE"},{"path":"storage.postgres.ssl.key","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_KEY_FILE"},{"path":"storage.encryption_key","secret":true,"env":"AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE"},{"path":"notifier.disable_startup_check","secret":false,"env":"AUTHELIA_NOTIFIER_DISABLE_STARTUP_CHECK"},{"path":"notifier.filesystem.filename","secret":false,"env":"AUTHELIA_NOTIFIER_FILESYSTEM_FILENAME"},{"path":"notifier.smtp.address","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_ADDRESS"},{"path":"notifier.smtp.timeout","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TIMEOUT"},{"path":"notifier.smtp.username","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_USERNAME"},{"path":"notifier.smtp.password","secret":true,"env":"AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE"},{"path":"notifier.smtp.identifier","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_IDENTIFIER"},{"path":"notifier.smtp.sender","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_SENDER"},{"path":"notifier.smtp.subject","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_SUBJECT"},{"path":"notifier.smtp.startup_check_address","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_STARTUP_CHECK_ADDRESS"},{"path":"notifier.smtp.disable_require_tls","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_DISABLE_REQUIRE_TLS"},{"path":"notifier.smtp.disable_html_emails","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_DISABLE_HTML_EMAILS"},{"path":"notifier.smtp.disable_starttls","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_DISABLE_STARTTLS"},{"path":"notifier.smtp.tls.minimum_version","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_MINIMUM_VERSION"},{"path":"notifier.smtp.tls.maximum_version","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_MAXIMUM_VERSION"},{"path":"notifier.smtp.tls.skip_verify","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_SKIP_VERIFY"},{"path":"notifier.smtp.tls.server_name","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_SERVER_NAME"},{"path":"notifier.smtp.tls.private_key","secret":true,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_PRIVATE_KEY_FILE"},{"path":"notifier.smtp.tls.certificate_chain","secret":true,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"notifier.smtp.host","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_HOST"},{"path":"notifier.smtp.port","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_PORT"},{"path":"notifier.template_path","secret":false,"env":"AUTHELIA_NOTIFIER_TEMPLATE_PATH"},{"path":"server.address","secret":false,"env":"AUTHELIA_SERVER_ADDRESS"},{"path":"server.umask","secret":false,"env":"AUTHELIA_SERVER_UMASK"},{"path":"server.path","secret":false,"env":"AUTHELIA_SERVER_PATH"},{"path":"server.asset_path","secret":false,"env":"AUTHELIA_SERVER_ASSET_PATH"},{"path":"server.disable_healthcheck","secret":false,"env":"AUTHELIA_SERVER_DISABLE_HEALTHCHECK"},{"path":"server.tls.certificate","secret":false,"env":"AUTHELIA_SERVER_TLS_CERTIFICATE"},{"path":"server.tls.key","secret":true,"env":"AUTHELIA_SERVER_TLS_KEY_FILE"},{"path":"server.headers.csp_template","secret":false,"env":"AUTHELIA_SERVER_HEADERS_CSP_TEMPLATE"},{"path":"server.endpoints.enable_pprof","secret":false,"env":"AUTHELIA_SERVER_ENDPOINTS_ENABLE_PPROF"},{"path":"server.endpoints.enable_expvars","secret":false,"env":"AUTHELIA_SERVER_ENDPOINTS_ENABLE_EXPVARS"},{"path":"server.buffers.read","secret":false,"env":"AUTHELIA_SERVER_BUFFERS_READ"},{"path":"server.buffers.write","secret":false,"env":"AUTHELIA_SERVER_BUFFERS_WRITE"},{"path":"server.timeouts.read","secret":false,"env":"AUTHELIA_SERVER_TIMEOUTS_READ"},{"path":"server.timeouts.write","secret":false,"env":"AUTHELIA_SERVER_TIMEOUTS_WRITE"},{"path":"server.timeouts.idle","secret":false,"env":"AUTHELIA_SERVER_TIMEOUTS_IDLE"},{"path":"server.host","secret":false,"env":"AUTHELIA_SERVER_HOST"},{"path":"server.port","secret":false,"env":"AUTHELIA_SERVER_PORT"},{"path":"telemetry.metrics.enabled","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_ENABLED"},{"path":"telemetry.metrics.address","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_ADDRESS"},{"path":"telemetry.metrics.umask","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_UMASK"},{"path":"telemetry.metrics.buffers.read","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_BUFFERS_READ"},{"path":"telemetry.metrics.buffers.write","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_BUFFERS_WRITE"},{"path":"telemetry.metrics.timeouts.read","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_READ"},{"path":"telemetry.metrics.timeouts.write","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_WRITE"},{"path":"telemetry.metrics.timeouts.idle","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_IDLE"},{"path":"webauthn.disable","secret":false,"env":"AUTHELIA_WEBAUTHN_DISABLE"},{"path":"webauthn.display_name","secret":false,"env":"AUTHELIA_WEBAUTHN_DISPLAY_NAME"},{"path":"webauthn.attestation_conveyance_preference","secret":false,"env":"AUTHELIA_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE"},{"path":"webauthn.user_verification","secret":false,"env":"AUTHELIA_WEBAUTHN_USER_VERIFICATION"},{"path":"webauthn.timeout","secret":false,"env":"AUTHELIA_WEBAUTHN_TIMEOUT"},{"path":"password_policy.standard.enabled","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_ENABLED"},{"path":"password_policy.standard.min_length","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_MIN_LENGTH"},{"path":"password_policy.standard.max_length","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_MAX_LENGTH"},{"path":"password_policy.standard.require_uppercase","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_UPPERCASE"},{"path":"password_policy.standard.require_lowercase","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_LOWERCASE"},{"path":"password_policy.standard.require_number","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_NUMBER"},{"path":"password_policy.standard.require_special","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_SPECIAL"},{"path":"password_policy.zxcvbn.enabled","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_ZXCVBN_ENABLED"},{"path":"password_policy.zxcvbn.min_score","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_ZXCVBN_MIN_SCORE"},{"path":"privacy_policy.enabled","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_ENABLED"},{"path":"privacy_policy.require_user_acceptance","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_REQUIRE_USER_ACCEPTANCE"},{"path":"privacy_policy.policy_url","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_POLICY_URL"}] \ No newline at end of file diff --git a/docs/layouts/shortcodes/oidc-common.html b/docs/layouts/shortcodes/oidc-common.html index 8b6034c29..61512bf5b 100644 --- a/docs/layouts/shortcodes/oidc-common.html +++ b/docs/layouts/shortcodes/oidc-common.html @@ -1,4 +1,4 @@ -{{ $faq := "../frequently-asked-questions/" }}{{ $config := "../../../configuration/identity-providers/open-id-connect.md" }} +{{ $faq := "../frequently-asked-questions/" }}{{ $config := "../../../configuration/identity-providers/openid-connect/" }} {{- with .Get "faq" }}{{ $faq = . }}{{ end }} {{- with .Get "config" }}{{ $config = . }}{{ end }} ### Common Notes @@ -15,4 +15,6 @@ guaranteed to be supported in the future. See the [Plaintext]({{ $faq }}#plaintext) guide for more information. 3. The Configuration example for Authelia is only a portion of the required configuration and it should be used as a - guide in conjunction with the standard [OpenID Connect 1.0 Configuration]({{ $config }}) guide. \ No newline at end of file + guide in conjunction with the standard + [OpenID Connect 1.0 Provider Configuration]({{ printf "%s/provider.md" $config }}) and + [OpenID Connect 1.0 Clients Configuration]({{ printf "%s/clients.md" $config }}) guides. \ No newline at end of file diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index dbeb986f3..8b3f31007 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -4,7 +4,16 @@ # Authelia Configuration # ############################################################################### -## Note: the container by default expects to find this file at /config/configuration.yml. +## +## Notes: +## +## - the default location of this file is assumed to be configuration.yml unless otherwise noted +## - when using docker the container expects this by default to be at /config/configuration.yml +## - the default location where this file is loaded from can be overridden with the X_AUTHELIA_CONFIG environment var +## - the comments in this configuration file are helpful but users should consult the official documentation on the +## website at https://www.authellia.com/ or https://www.authelia.com/configuration/prologue/introduction/ +## - this configuration file template is not automatically updated +## ## Certificates directory specifies where Authelia will load trusted certificates (public portion) from in addition to ## the system certificates store. @@ -357,73 +366,37 @@ authentication_backend: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- ## The distinguished name of the container searched for objects in the directory information tree. @@ -485,7 +458,7 @@ authentication_backend: # permit_referrals: false ## The username and password of the admin user. - # user: cn=admin,dc=example,dc=com + # user: 'cn=admin,dc=example,dc=com' ## Password can also be set using a secret: https://www.authelia.com/c/secrets # password: 'password' @@ -622,7 +595,7 @@ access_control: # networks: # - '10.10.0.0/16' # - '192.168.2.0/24' - # - name: VPN + # - name: 'VPN' # networks: '10.9.0.0/16' # rules: @@ -748,7 +721,8 @@ session: # expiration: '1h' ## The time before the cookie expires and the session is destroyed if remember me IS selected by the user. Setting - ## this value to -1 disables remember me for this session cookie domain. + ## this value to -1 disables remember me for this session cookie domain. If allowed and the user uses the remember + ## me checkbox this overrides the expiration option and disables the inactivity option. # remember_me: '1M' ## Cookie Session Domain default 'name' value. @@ -816,73 +790,37 @@ session: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- ## The Redis HA configuration options. @@ -997,73 +935,37 @@ regulation: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- ## @@ -1116,74 +1018,38 @@ regulation: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== - # -----END RSA PRIVATE KEY----- + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= + # -----END RSA PRIVATE KEY----- ## ## Notification Provider @@ -1270,73 +1136,37 @@ notifier: ## i.e. Mutual TLS. # certificate_chain: | # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= # -----END CERTIFICATE----- ## The private key used with the certificate_chain if the server requests TLS Client Authentication ## i.e. Mutual TLS. # private_key: | - # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- ## @@ -1354,80 +1184,88 @@ notifier: ## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets # hmac_secret: 'this_is_a_secret_abc123abc123abc' - ## The issuer_certificate_chain is an optional PEM encoded certificate chain. It's used in conjunction with the - ## issuer_private_key to sign JWT's. All certificates in the chain must be within the validity period, and every - ## certificate included must be signed by the certificate immediately after it if provided. - # issuer_certificate_chain: | - # -----BEGIN CERTIFICATE----- - # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q - # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6 - # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY - # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H - # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR - # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD - # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN - # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh - # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4 - # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq - # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg - # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i - # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE= - # -----END CERTIFICATE----- - # -----BEGIN CERTIFICATE----- - # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw - # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw - # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP - # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S - # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50 - # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou - # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7 - # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi - # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD - # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU - # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/ - # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf - # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ - # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB - # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj - # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b - # qocikt3WAdU^invalid DO NOT USE= - # -----END CERTIFICATE----- + ## Issuer JWKS configures multiple JSON Web Keys. It's required that at least one of these is RS256 or the + ## option issuer_private_key is configured. There must only be one key per algorithm at this time. + ## For RSA keys the minimum is a 2048 bit key. + # issuer_private_keys: + # - + ## Key ID embedded into the JWT header for key matching. Must be an alphanumeric string with 7 or less characters. + ## This value is automatically generated if not provided. It's recommended to not configure this. + # key_id: 'example' - ## The issuer_private_key is used to sign the JWT forged by OpenID Connect. + ## The key algorithm used with this key. + # algorithm: 'RS256' + + ## The key use expected with this key. Currently only 'sig' is supported. + # use: 'sig' + + ## Required Private Key in PEM DER form. + # key: | + # -----BEGIN RSA PRIVATE KEY----- + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= + # -----END RSA PRIVATE KEY----- + + + ## Optional matching certificate chain in PEM DER form that matches the key. All certificates within the chain + ## must be valid and current, and from top to bottom each certificate must be signed by the subsequent one. + # certificate_chain: | + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- + + ## The issuer_private_key is used to sign the JWT forged by OpenID Connect. This is in addition to the + ## issuer_private_keys option. Assumed to use the RS256 algorithm, and must not be specified if any of the + ## keys in issuer_private_keys also has the algorithm RS256 or are an RSA key without an algorithm. ## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets # issuer_private_key: | # -----BEGIN RSA PRIVATE KEY----- - # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1 - # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ - # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+ - # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW - # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5 - # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um - # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc - # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB - # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr - # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG - # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5 - # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR - # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV - # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i - # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9 - # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR - # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx - # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos - # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ - # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX - # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe - # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84 - # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk - # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ - # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid.. - # DO NOT USE== + # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF + # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P + # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh + # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH + # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg + # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg + # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE= # -----END RSA PRIVATE KEY----- + ## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within + ## the chain must be valid and current, and from top to bottom each certificate must be signed by the next + ## certificate in the chain if provided. + # issuer_certificate_chain: | + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- + ## The lifespans configure the expiration for these token types in the duration common syntax. # access_token_lifespan: '1h' # authorize_code_lifespan: '1m' @@ -1499,6 +1337,11 @@ notifier: # - 'email' # - 'profile' + ## Grant Types configures which grants this client can obtain. + ## It's not recommended to define this unless you know what you're doing. + # grant_types: + # - 'authorization_code' + ## Response Types configures which responses this client can be sent. ## It's not recommended to define this unless you know what you're doing. # response_types: @@ -1509,25 +1352,19 @@ notifier: # - 'form_post' # - 'query' - ## Grant Types configures which grants this client can obtain. - ## It's not recommended to define this unless you know what you're doing. - # grant_types: - # - 'authorization_code' - - ## The permitted client authentication method for the Token Endpoint for this client. - # token_endpoint_auth_method: 'client_secret_basic' - - ## The permitted client authentication signing algorithm for the Token Endpoint for this client when using - ## the 'client_secret_jwt' token_endpoint_auth_method. - # token_endpoint_auth_signing_alg: HS256 - - ## The permitted client authentication signing algorithm for the Token Endpoint for this client when using - ## the 'client_secret_jwt' token_endpoint_auth_method. - # token_endpoint_auth_signing_alg: HS256 - ## The policy to require for this client; one_factor or two_factor. # authorization_policy: 'two_factor' + ## The consent mode controls how consent is obtained. + # consent_mode: 'auto' + + ## This value controls the duration a consent on this client remains remembered when the consent mode is + ## configured as 'auto' or 'pre-configured' in the duration common syntax. + # pre_configured_consent_duration: '1w' + + ## Enforces the use of Pushed Authorization Requests for this client when set to true. + # enforce_par: false + ## Enforces the use of PKCE for this client when set to true. # enforce_pkce: false @@ -1535,13 +1372,69 @@ notifier: ## Options are 'plain' and 'S256'. # pkce_challenge_method: 'S256' + ## The permitted client authentication method for the Token Endpoint for this client. + # token_endpoint_auth_method: 'client_secret_basic' + + ## The permitted client authentication signing algorithm for the Token Endpoint for this client when using + ## the 'client_secret_jwt' or 'private_key_jwt' token_endpoint_auth_method. + # token_endpoint_auth_signing_alg: 'RS256' + + ## The signing algorithm which must be used for request objects. A client JWK with a matching algorithm must be + ## included if configured. + # request_object_signing_alg: 'RS256' + + ## The signing algorithm used for ID Tokens. Am issuer JWK with a matching algorithm must be included. + # id_token_signing_alg: 'RS256' + ## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256. # userinfo_signing_algorithm: 'none' - ## The consent mode controls how consent is obtained. - # consent_mode: 'auto' + ## Trusted public keys configuration for request object signing for things such as private_key_jwt + # public_keys: - ## This value controls the duration a consent on this client remains remembered when the consent mode is - ## configured as 'auto' or 'pre-configured' in the duration common syntax. - # pre_configured_consent_duration: '1w' + ## URL of the HTTPS endpoint which serves the keys. It's recommended to manually configure them in the + ## values option below. Please note the URL and the individual values are mutually exclusive. + # uri: 'https://app.example.com/jwks.json' + + ## Values from the individual keys. + # values: + # - + ## Key ID used to match the JWT's to an individual identifier. This option is required if configured. + # key_id: 'example' + + ## The key algorithm expected with this key. + # algorithm: 'RS256' + + ## The key use expected with this key. Currently only 'sig' is supported. + # use: 'sig' + + ## Required Public Key in PEM DER form. + # key: | + # -----BEGIN RSA PUBLIC KEY----- + # MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz + # 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE= + # -----END RSA PUBLIC KEY---- + + ## The matching certificate chain in PEM DER form that matches the key if available. + # certificate_chain: | + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- + # -----BEGIN CERTIFICATE----- + # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT + # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3 + # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB + # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00 + # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud + # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8 + # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx + # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE= + # -----END CERTIFICATE----- ... diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go index bae07d5d5..75ee6d831 100644 --- a/internal/configuration/provider_test.go +++ b/internal/configuration/provider_test.go @@ -261,17 +261,17 @@ func TestShouldLoadNewOIDCConfig(t *testing.T) { assert.Len(t, val.Errors(), 0) - assert.Len(t, config.IdentityProviders.OIDC.IssuerJWKS.Keys, 2) - assert.Equal(t, "keya", config.IdentityProviders.OIDC.IssuerJWKS.DefaultKeyID) + assert.Len(t, config.IdentityProviders.OIDC.IssuerPrivateKeys.Keys, 2) + assert.Equal(t, "keya", config.IdentityProviders.OIDC.IssuerPrivateKeys.DefaultKeyID) - assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerJWKS.Keys["keya"].Use) - assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, config.IdentityProviders.OIDC.IssuerJWKS.Keys["keya"].Algorithm) + assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerPrivateKeys.Keys["keya"].Use) + assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, config.IdentityProviders.OIDC.IssuerPrivateKeys.Keys["keya"].Algorithm) - assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerJWKS.Keys["ec521"].Use) - assert.Equal(t, oidc.SigningAlgECDSAUsingP521AndSHA512, config.IdentityProviders.OIDC.IssuerJWKS.Keys["ec521"].Algorithm) + assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerPrivateKeys.Keys["ec521"].Use) + assert.Equal(t, oidc.SigningAlgECDSAUsingP521AndSHA512, config.IdentityProviders.OIDC.IssuerPrivateKeys.Keys["ec521"].Algorithm) - assert.Contains(t, config.IdentityProviders.OIDC.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgRSAUsingSHA256) - assert.Contains(t, config.IdentityProviders.OIDC.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgECDSAUsingP521AndSHA512) + assert.Contains(t, config.IdentityProviders.OIDC.Discovery.ResponseObjectSigningAlgs, oidc.SigningAlgRSAUsingSHA256) + assert.Contains(t, config.IdentityProviders.OIDC.Discovery.ResponseObjectSigningAlgs, oidc.SigningAlgECDSAUsingP521AndSHA512) }. */ diff --git a/internal/configuration/schema/identity_providers.go b/internal/configuration/schema/identity_providers.go index e3713718e..697121130 100644 --- a/internal/configuration/schema/identity_providers.go +++ b/internal/configuration/schema/identity_providers.go @@ -13,12 +13,12 @@ type IdentityProvidersConfiguration struct { // OpenIDConnectConfiguration configuration for OpenID Connect. type OpenIDConnectConfiguration struct { - HMACSecret string `koanf:"hmac_secret"` + HMACSecret string `koanf:"hmac_secret"` + IssuerPrivateKeys []JWK `koanf:"issuer_private_keys"` + IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain"` IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key"` - IssuerJWKS []JWK `koanf:"issuer_jwks"` - AccessTokenLifespan time.Duration `koanf:"access_token_lifespan"` AuthorizeCodeLifespan time.Duration `koanf:"authorize_code_lifespan"` IDTokenLifespan time.Duration `koanf:"id_token_lifespan"` @@ -30,8 +30,8 @@ type OpenIDConnectConfiguration struct { EnforcePKCE string `koanf:"enforce_pkce"` EnablePKCEPlainChallenge bool `koanf:"enable_pkce_plain_challenge"` - CORS OpenIDConnectCORSConfiguration `koanf:"cors"` PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"` + CORS OpenIDConnectCORSConfiguration `koanf:"cors"` Clients []OpenIDConnectClientConfiguration `koanf:"clients"` @@ -39,8 +39,9 @@ type OpenIDConnectConfiguration struct { } type OpenIDConnectDiscovery struct { - DefaultKeyID string - RegisteredJWKSigningAlgs []string + DefaultKeyID string + ResponseObjectSigningAlgs []string + RequestObjectSigningAlgs []string } // OpenIDConnectPARConfiguration represents an OpenID Connect PAR config. @@ -73,21 +74,31 @@ type OpenIDConnectClientConfiguration struct { ResponseTypes []string `koanf:"response_types"` ResponseModes []string `koanf:"response_modes"` - TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"` - TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"` - - IDTokenSigningAlg string `koanf:"id_token_signing_alg"` - Policy string `koanf:"authorization_policy"` + ConsentMode string `koanf:"consent_mode"` + ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"` + EnforcePAR bool `koanf:"enforce_par"` EnforcePKCE bool `koanf:"enforce_pkce"` PKCEChallengeMethod string `koanf:"pkce_challenge_method"` - UserinfoSigningAlg string `koanf:"userinfo_signing_algorithm"` - ConsentMode string `koanf:"consent_mode"` - ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"` + TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"` + + TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"` + RequestObjectSigningAlg string `koanf:"request_object_signing_alg"` + IDTokenSigningAlg string `koanf:"id_token_signing_alg"` + UserinfoSigningAlg string `koanf:"userinfo_signing_algorithm"` + + PublicKeys OpenIDConnectClientPublicKeys `koanf:"public_keys"` + + Discovery OpenIDConnectDiscovery +} + +type OpenIDConnectClientPublicKeys struct { + URI *url.URL `koanf:"uri"` + Values []JWK `koanf:"values"` } // DefaultOpenIDConnectConfiguration contains defaults for OIDC. diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go index 1473226c0..1ff2b2061 100644 --- a/internal/configuration/schema/keys.go +++ b/internal/configuration/schema/keys.go @@ -18,14 +18,14 @@ var Keys = []string{ "log.file_path", "log.keep_stdout", "identity_providers.oidc.hmac_secret", + "identity_providers.oidc.issuer_private_keys", + "identity_providers.oidc.issuer_private_keys[].key_id", + "identity_providers.oidc.issuer_private_keys[]", + "identity_providers.oidc.issuer_private_keys[].algorithm", + "identity_providers.oidc.issuer_private_keys[].key", + "identity_providers.oidc.issuer_private_keys[].certificate_chain", "identity_providers.oidc.issuer_certificate_chain", "identity_providers.oidc.issuer_private_key", - "identity_providers.oidc.issuer_jwks", - "identity_providers.oidc.issuer_jwks[].key_id", - "identity_providers.oidc.issuer_jwks[]", - "identity_providers.oidc.issuer_jwks[].algorithm", - "identity_providers.oidc.issuer_jwks[].key", - "identity_providers.oidc.issuer_jwks[].certificate_chain", "identity_providers.oidc.access_token_lifespan", "identity_providers.oidc.authorize_code_lifespan", "identity_providers.oidc.id_token_lifespan", @@ -34,11 +34,11 @@ var Keys = []string{ "identity_providers.oidc.minimum_parameter_entropy", "identity_providers.oidc.enforce_pkce", "identity_providers.oidc.enable_pkce_plain_challenge", + "identity_providers.oidc.pushed_authorizations.enforce", + "identity_providers.oidc.pushed_authorizations.context_lifespan", "identity_providers.oidc.cors.endpoints", "identity_providers.oidc.cors.allowed_origins", "identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris", - "identity_providers.oidc.pushed_authorizations.enforce", - "identity_providers.oidc.pushed_authorizations.context_lifespan", "identity_providers.oidc.clients", "identity_providers.oidc.clients[].id", "identity_providers.oidc.clients[].description", @@ -51,16 +51,25 @@ var Keys = []string{ "identity_providers.oidc.clients[].grant_types", "identity_providers.oidc.clients[].response_types", "identity_providers.oidc.clients[].response_modes", - "identity_providers.oidc.clients[].token_endpoint_auth_method", - "identity_providers.oidc.clients[].token_endpoint_auth_signing_alg", - "identity_providers.oidc.clients[].id_token_signing_alg", "identity_providers.oidc.clients[].authorization_policy", + "identity_providers.oidc.clients[].consent_mode", + "identity_providers.oidc.clients[].pre_configured_consent_duration", "identity_providers.oidc.clients[].enforce_par", "identity_providers.oidc.clients[].enforce_pkce", "identity_providers.oidc.clients[].pkce_challenge_method", + "identity_providers.oidc.clients[].token_endpoint_auth_method", + "identity_providers.oidc.clients[].token_endpoint_auth_signing_alg", + "identity_providers.oidc.clients[].request_object_signing_alg", + "identity_providers.oidc.clients[].id_token_signing_alg", "identity_providers.oidc.clients[].userinfo_signing_algorithm", - "identity_providers.oidc.clients[].consent_mode", - "identity_providers.oidc.clients[].pre_configured_consent_duration", + "identity_providers.oidc.clients[].public_keys.uri", + "identity_providers.oidc.clients[].public_keys.values", + "identity_providers.oidc.clients[].public_keys.values[].key_id", + "identity_providers.oidc.clients[].public_keys.values[]", + "identity_providers.oidc.clients[].public_keys.values[].algorithm", + "identity_providers.oidc.clients[].public_keys.values[].key", + "identity_providers.oidc.clients[].public_keys.values[].certificate_chain", + "identity_providers.oidc.clients[]", "identity_providers.oidc", "authentication_backend.password_reset.disable", "authentication_backend.password_reset.custom_url", diff --git a/internal/configuration/test_resources/config_oidc_modern.yml b/internal/configuration/test_resources/config_oidc_modern.yml index be63faba5..56a455505 100644 --- a/internal/configuration/test_resources/config_oidc_modern.yml +++ b/internal/configuration/test_resources/config_oidc_modern.yml @@ -127,7 +127,7 @@ notifier: identity_providers: oidc: hmac_secret: 1nb2j3kh1b23kjh1b23jh1b23j1h2b3 - issuer_jwks: + issuer_private_keys: keys: keya: key: | diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index ec29e700f..bc25e5b51 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -142,15 +142,26 @@ const ( // OpenID Error constants. const ( - errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " + + errFmtOIDCProviderNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " + "more clients configured" - errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' or `issuer_jwks` is required" - errFmtOIDCInvalidPrivateKeyBitSize = "identity_providers: oidc: option 'issuer_private_key' must be an RSA private key with %d bits or more but it only has %d bits" - errFmtOIDCInvalidPrivateKeyMalformedMissingPublicKey = "identity_providers: oidc: option 'issuer_private_key' must be a valid RSA private key but the provided data is missing the public key bits" - errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'" - errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w" - errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " + + errFmtOIDCProviderNoPrivateKey = "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required" + errFmtOIDCProviderEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " + "'public_clients_only' or 'always', but it's configured as '%s'" + errFmtOIDCProviderInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " + + "configured to an unsafe value, it should be above 8 but it's configured to %d" + + errFmtOIDCProviderPrivateKeysInvalid = "identity_providers: oidc: issuer_private_keys: key #%d: option 'key' must be a valid private key but the provided data is malformed as it's missing the public key bits" + errFmtOIDCProviderPrivateKeysCalcThumbprint = "identity_providers: oidc: issuer_private_keys: key #%d: option 'key' failed to calculate thumbprint to configure key id value: %w" + errFmtOIDCProviderPrivateKeysKeyIDLength = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option `key_id`` must be 7 characters or less" + errFmtOIDCProviderPrivateKeysAttributeNotUnique = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option '%s' must be unique" + errFmtOIDCProviderPrivateKeysKeyIDNotAlphaNumeric = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key_id' must only have alphanumeric characters" + errFmtOIDCProviderPrivateKeysProperties = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' failed to get key properties: %w" + errFmtOIDCProviderPrivateKeysInvalidOptionOneOf = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'" + errFmtOIDCProviderPrivateKeysRSAKeyLessThan2048Bits = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must at minimum be a RSA 2048 bit private key" + errFmtOIDCProviderPrivateKeysKeyNotRSAOrECDSA = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' must be a RSA private key or ECDSA private key but it's type is %T" + errFmtOIDCProviderPrivateKeysKeyCertificateMismatch = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'certificate_chain' does not appear to contain the public key for the private key provided by option 'key'" + errFmtOIDCProviderPrivateKeysCertificateChainInvalid = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'certificate_chain' produced an error during validation of the chain: %w" + errFmtOIDCProviderPrivateKeysNoRS256 = "identity_providers: oidc: issuer_private_keys: keys: must at least have one key supporting the '%s' algorithm but only has %s" errFmtOIDCCORSInvalidOrigin = "identity_providers: oidc: cors: option 'allowed_origins' contains an invalid value '%s' as it has a %s: origins must only be scheme, hostname, and an optional port" errFmtOIDCCORSInvalidOriginWildcard = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' with more than one origin but the wildcard origin must be defined by itself" @@ -161,45 +172,61 @@ const ( errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s" errFmtOIDCClientsDeprecated = "identity_providers: oidc: clients: warnings for clients above indicate deprecated functionality and it's strongly suggested these issues are checked and fixed if they're legitimate issues or reported if they are not as in a future version these warnings will become errors" - errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required" - errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: client '%s': option 'secret' is plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable" - errFmtOIDCClientInvalidSecretNotPlainText = "identity_providers: oidc: client '%s': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'" - errFmtOIDCClientPublicInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is " + + errFmtOIDCClientInvalidSecret = "identity_providers: oidc: clients: client '%s': option 'secret' is required" + errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: clients: client '%s': option 'secret' is plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable" + errFmtOIDCClientInvalidSecretNotPlainText = "identity_providers: oidc: clients: client '%s': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'" + errFmtOIDCClientPublicInvalidSecret = "identity_providers: oidc: clients: client '%s': option 'secret' is " + "required to be empty when option 'public' is true" - errFmtOIDCClientRedirectURICantBeParsed = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " + + errFmtOIDCClientRedirectURICantBeParsed = "identity_providers: oidc: clients: client '%s': option 'redirect_uris' has an " + "invalid value: redirect uri '%s' could not be parsed: %v" - errFmtOIDCClientRedirectURIPublic = "identity_providers: oidc: client '%s': option 'redirect_uris' has the " + + errFmtOIDCClientRedirectURIPublic = "identity_providers: oidc: clients: client '%s': option 'redirect_uris' has the " + "redirect uri '%s' when option 'public' is false but this is invalid as this uri is not valid " + "for the openid connect confidential client type" - errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " + + errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: clients: client '%s': option 'redirect_uris' has an " + "invalid value: redirect uri '%s' must have a scheme but it's absent" - errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " + + errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: clients: client '%s': consent: option 'mode' must be one of " + "%s but it's configured as '%s'" - errFmtOIDCClientInvalidEntries = "identity_providers: oidc: client '%s': option '%s' must only have the values " + + errFmtOIDCClientInvalidEntries = "identity_providers: oidc: clients: client '%s': option '%s' must only have the values " + "%s but the values %s are present" - errFmtOIDCClientInvalidEntryDuplicates = "identity_providers: oidc: client '%s': option '%s' must have unique values but the values %s are duplicated" - errFmtOIDCClientInvalidValue = "identity_providers: oidc: client '%s': option " + + errFmtOIDCClientInvalidEntryDuplicates = "identity_providers: oidc: clients: client '%s': option '%s' must have unique values but the values %s are duplicated" + errFmtOIDCClientInvalidValue = "identity_providers: oidc: clients: client '%s': option " + "'%s' must be one of %s but it's configured as '%s'" - errFmtOIDCClientInvalidTokenEndpointAuthMethod = "identity_providers: oidc: client '%s': option " + + errFmtOIDCClientInvalidTokenEndpointAuthMethod = "identity_providers: oidc: clients: client '%s': option " + "'token_endpoint_auth_method' must be one of %s when configured as the confidential client type unless it only includes implicit flow response types such as %s but it's configured as '%s'" - errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: client '%s': option " + + errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: clients: client '%s': option " + "'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as '%s'" - errFmtOIDCClientInvalidTokenEndpointAuthSigAlg = "identity_providers: oidc: client '%s': option " + - "'token_endpoint_auth_signing_alg' must be %s when option 'token_endpoint_auth_method' is %s" - errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: client '%s': option " + + errFmtOIDCClientInvalidTokenEndpointAuthSigAlg = "identity_providers: oidc: clients: client '%s': option " + + "'token_endpoint_auth_signing_alg' must be one of %s when option 'token_endpoint_auth_method' is configured to '%s'" + errFmtOIDCClientInvalidTokenEndpointAuthSigAlgReg = "identity_providers: oidc: clients: client '%s': option " + + "'token_endpoint_auth_signing_alg' must be one of registered public key algorithm values %s when option 'token_endpoint_auth_method' is configured to '%s'" + errFmtOIDCClientInvalidTokenEndpointAuthSigAlgMissingPrivateKeyJWT = "identity_providers: oidc: clients: client '%s': option " + + "'token_endpoint_auth_signing_alg' is required when option 'token_endpoint_auth_method' is configured to 'private_key_jwt'" + errFmtOIDCClientInvalidPublicKeysPrivateKeyJWT = "identity_providers: oidc: clients: client '%s': option " + + "'public_keys' is required with 'token_endpoint_auth_method' set to 'private_key_jwt'" + errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: clients: client '%s': option " + "'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s with the value '%s'" - errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: client '%s': option " + + errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: clients: client '%s': option " + "'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s" - errFmtOIDCClientInvalidSectorIdentifierHost = "identity_providers: oidc: client '%s': option " + + errFmtOIDCClientInvalidSectorIdentifierHost = "identity_providers: oidc: clients: client '%s': option " + "'sector_identifier' with value '%s': must be a URL with only the host component but appears to be invalid" - errFmtOIDCClientInvalidGrantTypeMatch = "identity_providers: oidc: client '%s': option " + + errFmtOIDCClientInvalidGrantTypeMatch = "identity_providers: oidc: clients: client '%s': option " + "'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but '%s' expects a response type %s such as %s but the response types are %s" - errFmtOIDCClientInvalidGrantTypeRefresh = "identity_providers: oidc: client '%s': option " + + errFmtOIDCClientInvalidGrantTypeRefresh = "identity_providers: oidc: clients: client '%s': option " + "'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope" - errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType = "identity_providers: oidc: client '%s': option " + + errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType = "identity_providers: oidc: clients: client '%s': option " + "'%s' should only have the values %s if the client is also configured with a 'response_type' such as %s which respond with authorization codes" - errFmtOIDCServerInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " + - "configured to an unsafe value, it should be above 8 but it's configured to %d" + + errFmtOIDCClientPublicKeysBothURIAndValuesConfigured = "identity_providers: oidc: clients: client '%s': public_keys: option 'uri' must not be defined at the same time as option 'values'" + errFmtOIDCClientPublicKeysURIInvalidScheme = "identity_providers: oidc: clients: client '%s': public_keys: option 'uri' must have the 'https' scheme but the scheme is '%s'" + errFmtOIDCClientPublicKeysProperties = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'key' failed to get key properties: %w" + errFmtOIDCClientPublicKeysInvalidOptionOneOf = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'" + errFmtOIDCClientPublicKeysInvalidOptionMissingOneOf = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d: option '%s' must be provided" + errFmtOIDCClientPublicKeysKeyMalformed = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d: option 'key' option 'key' must be a valid private key but the provided data is malformed as it's missing the public key bits" + errFmtOIDCClientPublicKeysRSAKeyLessThan2048Bits = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must at minimum be a RSA 2048 bit private key" + errFmtOIDCClientPublicKeysKeyNotRSAOrECDSA = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'key' must be a RSA public key or ECDSA public key but it's type is %T" + errFmtOIDCClientPublicKeysCertificateChainKeyMismatch = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'certificate_chain' does not appear to contain the public key for the public key provided by option 'key'" + errFmtOIDCClientPublicKeysCertificateChainInvalid = "identity_providers: oidc: clients: client '%s': public_keys: values: key #%d with key id '%s': option 'certificate_chain' produced an error during validation of the chain: %w" + errFmtOIDCClientPublicKeysROSAMissingAlgorithm = "identity_providers: oidc: clients: client '%s': option 'request_object_signing_alg' must be one of %s configured in the client option 'public_keys'" ) // WebAuthn Error constants. @@ -288,11 +315,8 @@ const ( // Server Error constants. const ( - errFmtServerTLSCert = "server: tls: option 'key' must also be accompanied by option 'certificate'" - errFmtServerTLSKey = "server: tls: option 'certificate' must also be accompanied by option 'key'" - errFmtServerTLSFileNotExist = "server: tls: option '%s' the file '%s' does not exist" - errFmtServerTLSFileNotExistErr = "server: tls: option '%s' could not determine if the file '%s' exists: %w" - + errFmtServerTLSCert = "server: tls: option 'key' must also be accompanied by option 'certificate'" + errFmtServerTLSKey = "server: tls: option 'certificate' must also be accompanied by option 'key'" errFmtServerTLSClientAuthNoAuth = "server: tls: client authentication cannot be configured if no server certificate and key are provided" errFmtServerAddressLegacyAndModern = "server: option 'host' and 'port' can't be configured at the same time as 'address'" @@ -402,6 +426,10 @@ var ( var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"} const ( + attrOIDCKey = "key" + attrOIDCKeyID = "key_id" + attrOIDCKeyUse = "use" + attrOIDCAlgorithm = "algorithm" attrOIDCScopes = "scopes" attrOIDCResponseTypes = "response_types" attrOIDCResponseModes = "response_modes" @@ -425,10 +453,10 @@ var ( validOIDCClientResponseTypesRefreshToken = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth} validOIDCClientGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode} - validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodClientSecretJWT} - validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic} - validOIDCClientTokenEndpointAuthSigAlgs = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512} - validOIDCIssuerJWKSigningAlgs = []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP521AndSHA512} + validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodPrivateKeyJWT, oidc.ClientAuthMethodClientSecretJWT} + validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodPrivateKeyJWT} + validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512} + validOIDCIssuerJWKSigningAlgs = []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP521AndSHA512} ) var ( diff --git a/internal/configuration/validator/const_test.go b/internal/configuration/validator/const_test.go index 2ae760b5d..c11f928aa 100644 --- a/internal/configuration/validator/const_test.go +++ b/internal/configuration/validator/const_test.go @@ -17,6 +17,7 @@ const ( const ( exampleDotCom = "example.com" + rs256 = "rs256" ) const ( diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go index a716c841f..393bf8bd6 100644 --- a/internal/configuration/validator/identity_providers.go +++ b/internal/configuration/validator/identity_providers.go @@ -33,20 +33,23 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV validateOIDCIssuer(config, val) - sort.Sort(oidc.SortedSigningAlgs(config.Discovery.RegisteredJWKSigningAlgs)) + sort.Sort(oidc.SortedSigningAlgs(config.Discovery.ResponseObjectSigningAlgs)) if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 { - val.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy)) + val.PushWarning(fmt.Errorf(errFmtOIDCProviderInsecureParameterEntropy, config.MinimumParameterEntropy)) } - if config.EnforcePKCE != "never" && config.EnforcePKCE != "public_clients_only" && config.EnforcePKCE != "always" { - val.Push(fmt.Errorf(errFmtOIDCEnforcePKCEInvalidValue, config.EnforcePKCE)) + switch config.EnforcePKCE { + case "always", "never", "public_clients_only": + break + default: + val.Push(fmt.Errorf(errFmtOIDCProviderEnforcePKCEInvalidValue, config.EnforcePKCE)) } validateOIDCOptionsCORS(config, val) if len(config.Clients) == 0 { - val.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured)) + val.Push(fmt.Errorf(errFmtOIDCProviderNoClientsConfigured)) } else { validateOIDCClients(config, val) } @@ -55,56 +58,46 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV func validateOIDCIssuer(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { switch { case config.IssuerPrivateKey != nil: - validateOIDCIssuerLegacy(config, val) + validateOIDCIssuerPrivateKey(config) fallthrough - case len(config.IssuerJWKS) != 0: - validateOIDCIssuerModern(config, val) + case len(config.IssuerPrivateKeys) != 0: + validateOIDCIssuerPrivateKeys(config, val) default: - val.Push(fmt.Errorf(errFmtOIDCNoPrivateKey)) + val.Push(fmt.Errorf(errFmtOIDCProviderNoPrivateKey)) } } -func validateOIDCIssuerLegacy(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { - j := &jose.JSONWebKey{Key: &config.IssuerPrivateKey.PublicKey} - - thumbprint, err := j.Thumbprint(crypto.SHA1) - if err != nil { - val.Push(fmt.Errorf("identity_providers: oidc: option 'issuer_private_key' failed to calculate thumbprint to configure key id value: %w", err)) - - return - } - - config.IssuerJWKS = append(config.IssuerJWKS, schema.JWK{ - KeyID: fmt.Sprintf("%x", thumbprint)[:6], +func validateOIDCIssuerPrivateKey(config *schema.OpenIDConnectConfiguration) { + config.IssuerPrivateKeys = append([]schema.JWK{{ Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, Key: config.IssuerPrivateKey, CertificateChain: config.IssuerCertificateChain, - }) + }}, config.IssuerPrivateKeys...) } //nolint:gocyclo // Refactor time permitting. -func validateOIDCIssuerModern(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { +func validateOIDCIssuerPrivateKeys(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { var ( props *JWKProperties err error ) - kids := make([]string, len(config.IssuerJWKS)) + kids := make([]string, len(config.IssuerPrivateKeys)) - for i := 0; i < len(config.IssuerJWKS); i++ { - if key, ok := config.IssuerJWKS[i].Key.(*rsa.PrivateKey); ok && key.PublicKey.N == nil { - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d: option 'key' must be a valid RSA private key but the provided data is malformed as it's missing the public key bits", i+1)) + for i := 0; i < len(config.IssuerPrivateKeys); i++ { + if key, ok := config.IssuerPrivateKeys[i].Key.(*rsa.PrivateKey); ok && key.PublicKey.N == nil { + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysInvalid, i+1)) continue } - switch n := len(config.IssuerJWKS[i].KeyID); { + switch n := len(config.IssuerPrivateKeys[i].KeyID); { case n == 0: j := jose.JSONWebKey{} - switch key := config.IssuerJWKS[i].Key.(type) { + switch key := config.IssuerPrivateKeys[i].Key.(type) { case schema.CryptographicPrivateKey: j.Key = key.Public() case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: @@ -120,92 +113,92 @@ func validateOIDCIssuerModern(config *schema.OpenIDConnectConfiguration, val *sc var thumbprint []byte if thumbprint, err = j.Thumbprint(crypto.SHA1); err != nil { - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d: option 'key' failed to calculate thumbprint to configure key id value: %w", i+1, err)) + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysCalcThumbprint, i+1, err)) continue } - config.IssuerJWKS[i].KeyID = fmt.Sprintf("%x", thumbprint)[:6] + config.IssuerPrivateKeys[i].KeyID = fmt.Sprintf("%x", thumbprint)[:6] case n > 7: - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option `key_id`` must be 7 characters or less", i+1, config.IssuerJWKS[i].KeyID)) + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDLength, i+1, config.IssuerPrivateKeys[i].KeyID)) } - if config.IssuerJWKS[i].KeyID != "" && utils.IsStringInSlice(config.IssuerJWKS[i].KeyID, kids) { - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key_id' must be unique", i+1, config.IssuerJWKS[i].KeyID)) + if config.IssuerPrivateKeys[i].KeyID != "" && utils.IsStringInSlice(config.IssuerPrivateKeys[i].KeyID, kids) { + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysAttributeNotUnique, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCKeyID)) } - kids[i] = config.IssuerJWKS[i].KeyID + kids[i] = config.IssuerPrivateKeys[i].KeyID - if !utils.IsStringAlphaNumeric(config.IssuerJWKS[i].KeyID) { - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key_id' must only have alphanumeric characters", i+1, config.IssuerJWKS[i].KeyID)) + if !utils.IsStringAlphaNumeric(config.IssuerPrivateKeys[i].KeyID) { + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDNotAlphaNumeric, i+1, config.IssuerPrivateKeys[i].KeyID)) } - if props, err = schemaJWKGetProperties(config.IssuerJWKS[i]); err != nil { - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' failed to get key properties: %w", i+1, config.IssuerJWKS[i].KeyID, err)) + if props, err = schemaJWKGetProperties(config.IssuerPrivateKeys[i]); err != nil { + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysProperties, i+1, config.IssuerPrivateKeys[i].KeyID, err)) continue } - switch config.IssuerJWKS[i].Use { + switch config.IssuerPrivateKeys[i].Use { case "": - config.IssuerJWKS[i].Use = props.Use + config.IssuerPrivateKeys[i].Use = props.Use case oidc.KeyUseSignature: break default: - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'", i+1, config.IssuerJWKS[i].KeyID, "use", strJoinOr([]string{oidc.KeyUseSignature}), config.IssuerJWKS[i].Use)) + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysInvalidOptionOneOf, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCKeyUse, strJoinOr([]string{oidc.KeyUseSignature}), config.IssuerPrivateKeys[i].Use)) } switch { - case config.IssuerJWKS[i].Algorithm == "": - config.IssuerJWKS[i].Algorithm = props.Algorithm - case utils.IsStringInSlice(config.IssuerJWKS[i].Algorithm, validOIDCIssuerJWKSigningAlgs): + case config.IssuerPrivateKeys[i].Algorithm == "": + config.IssuerPrivateKeys[i].Algorithm = props.Algorithm + case utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, validOIDCIssuerJWKSigningAlgs): break default: - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'", i+1, config.IssuerJWKS[i].KeyID, "algorithm", strJoinOr(validOIDCIssuerJWKSigningAlgs), config.IssuerJWKS[i].Algorithm)) + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysInvalidOptionOneOf, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCAlgorithm, strJoinOr(validOIDCIssuerJWKSigningAlgs), config.IssuerPrivateKeys[i].Algorithm)) } - if config.IssuerJWKS[i].Algorithm != "" { - if utils.IsStringInSlice(config.IssuerJWKS[i].Algorithm, config.Discovery.RegisteredJWKSigningAlgs) { - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'algorithm' must be unique but another key is using it", i+1, config.IssuerJWKS[i].KeyID)) + if config.IssuerPrivateKeys[i].Algorithm != "" { + if utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, config.Discovery.ResponseObjectSigningAlgs) { + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysAttributeNotUnique, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCAlgorithm)) } else { - config.Discovery.RegisteredJWKSigningAlgs = append(config.Discovery.RegisteredJWKSigningAlgs, config.IssuerJWKS[i].Algorithm) + config.Discovery.ResponseObjectSigningAlgs = append(config.Discovery.ResponseObjectSigningAlgs, config.IssuerPrivateKeys[i].Algorithm) } } - if config.IssuerJWKS[i].Algorithm == oidc.SigningAlgRSAUsingSHA256 && config.Discovery.DefaultKeyID == "" { - config.Discovery.DefaultKeyID = config.IssuerJWKS[i].KeyID + if config.IssuerPrivateKeys[i].Algorithm == oidc.SigningAlgRSAUsingSHA256 && config.Discovery.DefaultKeyID == "" { + config.Discovery.DefaultKeyID = config.IssuerPrivateKeys[i].KeyID } var checkEqualKey bool - switch key := config.IssuerJWKS[i].Key.(type) { + switch key := config.IssuerPrivateKeys[i].Key.(type) { case *rsa.PrivateKey: checkEqualKey = true if key.Size() < 256 { checkEqualKey = false - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must be a RSA 2048 bit private key", i+1, config.IssuerJWKS[i].KeyID, key.Size()*8)) + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysRSAKeyLessThan2048Bits, i+1, config.IssuerPrivateKeys[i].KeyID, key.Size()*8)) } case *ecdsa.PrivateKey: checkEqualKey = true default: - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' must be a *rsa.PrivateKey or *ecdsa.PrivateKey but it's a %T", i+1, config.IssuerJWKS[i].KeyID, key)) + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyNotRSAOrECDSA, i+1, config.IssuerPrivateKeys[i].KeyID, key)) } - if config.IssuerJWKS[i].CertificateChain.HasCertificates() { - if checkEqualKey && !config.IssuerJWKS[i].CertificateChain.EqualKey(config.IssuerJWKS[i].Key) { - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' does not appear to be the private key the certificate provided by option 'certificate_chain'", i+1, config.IssuerJWKS[i].KeyID)) + if config.IssuerPrivateKeys[i].CertificateChain.HasCertificates() { + if checkEqualKey && !config.IssuerPrivateKeys[i].CertificateChain.EqualKey(config.IssuerPrivateKeys[i].Key) { + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyCertificateMismatch, i+1, config.IssuerPrivateKeys[i].KeyID)) } - if err = config.IssuerJWKS[i].CertificateChain.Validate(); err != nil { - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'certificate_chain' produced an error during validation of the chain: %w", i+1, config.IssuerJWKS[i].KeyID, err)) + if err = config.IssuerPrivateKeys[i].CertificateChain.Validate(); err != nil { + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysCertificateChainInvalid, i+1, config.IssuerPrivateKeys[i].KeyID, err)) } } } - if len(config.Discovery.RegisteredJWKSigningAlgs) != 0 && !utils.IsStringInSlice(oidc.SigningAlgRSAUsingSHA256, config.Discovery.RegisteredJWKSigningAlgs) { - val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: keys: must at least have one key supporting the '%s' algorithm but only has %s", oidc.SigningAlgRSAUsingSHA256, strJoinAnd(config.Discovery.RegisteredJWKSigningAlgs))) + if len(config.Discovery.ResponseObjectSigningAlgs) != 0 && !utils.IsStringInSlice(oidc.SigningAlgRSAUsingSHA256, config.Discovery.ResponseObjectSigningAlgs) { + val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysNoRS256, oidc.SigningAlgRSAUsingSHA256, strJoinAnd(config.Discovery.ResponseObjectSigningAlgs))) } } @@ -356,7 +349,7 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s case policyOneFactor, policyTwoFactor: break default: - val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "policy", strJoinOr([]string{policyOneFactor, policyTwoFactor}), config.Clients[c].Policy)) + val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "authorization_policy", strJoinOr([]string{policyOneFactor, policyTwoFactor}), config.Clients[c].Policy)) } switch config.Clients[c].PKCEChallengeMethod { @@ -374,10 +367,110 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s validateOIDCClientGrantTypes(c, config, val, errDeprecatedFunc) validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc) - validateOIDCClientTokenEndpointAuth(c, config, val) validateOIDDClientSigningAlgs(c, config, val) validateOIDCClientSectorIdentifier(c, config, val) + + validateOIDCClientPublicKeys(c, config, val) + validateOIDCClientTokenEndpointAuth(c, config, val) +} + +func validateOIDCClientPublicKeys(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { + switch { + case config.Clients[c].PublicKeys.URI != nil && len(config.Clients[c].PublicKeys.Values) != 0: + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysBothURIAndValuesConfigured, config.Clients[c].ID)) + case config.Clients[c].PublicKeys.URI != nil: + if config.Clients[c].PublicKeys.URI.Scheme != schemeHTTPS { + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysURIInvalidScheme, config.Clients[c].ID, config.Clients[c].PublicKeys.URI.Scheme)) + } + case len(config.Clients[c].PublicKeys.Values) != 0: + validateOIDCClientJSONWebKeysList(c, config, val) + } +} + +//nolint:gocyclo +func validateOIDCClientJSONWebKeysList(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { + var ( + props *JWKProperties + err error + ) + + for i := 0; i < len(config.Clients[c].PublicKeys.Values); i++ { + if config.Clients[c].PublicKeys.Values[i].KeyID == "" { + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysInvalidOptionMissingOneOf, config.Clients[c].ID, i+1, attrOIDCKeyID)) + } + + if props, err = schemaJWKGetProperties(config.Clients[c].PublicKeys.Values[i]); err != nil { + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysProperties, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, err)) + + continue + } + + switch config.Clients[c].PublicKeys.Values[i].Use { + case "": + config.Clients[c].PublicKeys.Values[i].Use = props.Use + case oidc.KeyUseSignature: + break + default: + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysInvalidOptionOneOf, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, attrOIDCKeyUse, strJoinOr([]string{oidc.KeyUseSignature}), config.Clients[c].PublicKeys.Values[i].Use)) + } + + switch { + case config.Clients[c].PublicKeys.Values[i].Algorithm == "": + config.Clients[c].PublicKeys.Values[i].Algorithm = props.Algorithm + case utils.IsStringInSlice(config.Clients[c].PublicKeys.Values[i].Algorithm, validOIDCIssuerJWKSigningAlgs): + break + default: + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysInvalidOptionOneOf, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, attrOIDCAlgorithm, strJoinOr(validOIDCIssuerJWKSigningAlgs), config.Clients[c].PublicKeys.Values[i].Algorithm)) + } + + if config.Clients[c].PublicKeys.Values[i].Algorithm != "" { + if !utils.IsStringInSlice(config.Clients[c].PublicKeys.Values[i].Algorithm, config.Discovery.RequestObjectSigningAlgs) { + config.Discovery.RequestObjectSigningAlgs = append(config.Discovery.RequestObjectSigningAlgs, config.Clients[c].PublicKeys.Values[i].Algorithm) + } + + if !utils.IsStringInSlice(config.Clients[c].PublicKeys.Values[i].Algorithm, config.Clients[c].Discovery.RequestObjectSigningAlgs) { + config.Clients[c].Discovery.RequestObjectSigningAlgs = append(config.Clients[c].Discovery.RequestObjectSigningAlgs, config.Clients[c].PublicKeys.Values[i].Algorithm) + } + } + + var checkEqualKey bool + + switch key := config.Clients[c].PublicKeys.Values[i].Key.(type) { + case nil: + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysInvalidOptionMissingOneOf, config.Clients[c].ID, i+1, attrOIDCKey)) + case *rsa.PublicKey: + checkEqualKey = true + + if key.N == nil { + checkEqualKey = false + + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysKeyMalformed, config.Clients[c].ID, i+1)) + } else if key.Size() < 256 { + checkEqualKey = false + + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysRSAKeyLessThan2048Bits, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, key.Size()*8)) + } + case *ecdsa.PublicKey: + checkEqualKey = true + default: + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysKeyNotRSAOrECDSA, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, key)) + } + + if config.Clients[c].PublicKeys.Values[i].CertificateChain.HasCertificates() { + if checkEqualKey && !config.Clients[c].PublicKeys.Values[i].CertificateChain.EqualKey(config.Clients[c].PublicKeys.Values[i].Key) { + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysCertificateChainKeyMismatch, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID)) + } + + if err = config.Clients[c].PublicKeys.Values[i].CertificateChain.Validate(); err != nil { + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysCertificateChainInvalid, config.Clients[c].ID, i+1, config.Clients[c].PublicKeys.Values[i].KeyID, err)) + } + } + } + + if config.Clients[c].RequestObjectSigningAlg != "" && !utils.IsStringInSlice(config.Clients[c].RequestObjectSigningAlg, config.Clients[c].Discovery.RequestObjectSigningAlgs) { + val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysROSAMissingAlgorithm, config.Clients[c].ID, strJoinOr(config.Clients[c].Discovery.RequestObjectSigningAlgs))) + } } func validateOIDCClientSectorIdentifier(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { @@ -654,11 +747,34 @@ func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnectConf case "": break case oidc.ClientAuthMethodClientSecretJWT: - switch { - case config.Clients[c].TokenEndpointAuthSigningAlg == "": - config.Clients[c].TokenEndpointAuthSigningAlg = oidc.SigningAlgHMACUsingSHA256 - case !utils.IsStringInSlice(config.Clients[c].TokenEndpointAuthSigningAlg, validOIDCClientTokenEndpointAuthSigAlgs): - val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlg, config.Clients[c].ID, strJoinOr(validOIDCClientTokenEndpointAuthSigAlgs), config.Clients[c].TokenEndpointAuthMethod)) + validateOIDCClientTokenEndpointAuthClientSecretJWT(c, config, val) + case oidc.ClientAuthMethodPrivateKeyJWT: + validateOIDCClientTokenEndpointAuthPublicKeyJWT(config.Clients[c], val) + } +} + +func validateOIDCClientTokenEndpointAuthClientSecretJWT(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { + switch { + case config.Clients[c].TokenEndpointAuthSigningAlg == "": + config.Clients[c].TokenEndpointAuthSigningAlg = oidc.SigningAlgHMACUsingSHA256 + case !utils.IsStringInSlice(config.Clients[c].TokenEndpointAuthSigningAlg, validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT): + val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlg, config.Clients[c].ID, strJoinOr(validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT), config.Clients[c].TokenEndpointAuthMethod)) + } +} + +func validateOIDCClientTokenEndpointAuthPublicKeyJWT(config schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) { + switch { + case config.TokenEndpointAuthSigningAlg == "": + val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlgMissingPrivateKeyJWT, config.ID)) + case !utils.IsStringInSlice(config.TokenEndpointAuthSigningAlg, validOIDCIssuerJWKSigningAlgs): + val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlg, config.ID, strJoinOr(validOIDCIssuerJWKSigningAlgs), config.TokenEndpointAuthMethod)) + } + + if config.PublicKeys.URI == nil { + if len(config.PublicKeys.Values) == 0 { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidPublicKeysPrivateKeyJWT, config.ID)) + } else if len(config.Discovery.RequestObjectSigningAlgs) != 0 && !utils.IsStringInSlice(config.TokenEndpointAuthSigningAlg, config.Discovery.RequestObjectSigningAlgs) { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlgReg, config.ID, strJoinOr(config.Discovery.RequestObjectSigningAlgs), config.TokenEndpointAuthMethod)) } } } @@ -666,15 +782,15 @@ func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnectConf func validateOIDDClientSigningAlgs(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { if config.Clients[c].UserinfoSigningAlg == "" { config.Clients[c].UserinfoSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlg - } else if config.Clients[c].UserinfoSigningAlg != oidc.SigningAlgNone && !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlg, config.Discovery.RegisteredJWKSigningAlgs) { + } else if config.Clients[c].UserinfoSigningAlg != oidc.SigningAlgNone && !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlg, config.Discovery.ResponseObjectSigningAlgs) { val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, - config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(append(config.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgNone)), config.Clients[c].UserinfoSigningAlg)) + config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(append(config.Discovery.ResponseObjectSigningAlgs, oidc.SigningAlgNone)), config.Clients[c].UserinfoSigningAlg)) } if config.Clients[c].IDTokenSigningAlg == "" { config.Clients[c].IDTokenSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.IDTokenSigningAlg - } else if !utils.IsStringInSlice(config.Clients[c].IDTokenSigningAlg, config.Discovery.RegisteredJWKSigningAlgs) { + } else if !utils.IsStringInSlice(config.Clients[c].IDTokenSigningAlg, config.Discovery.ResponseObjectSigningAlgs) { val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, - config.Clients[c].ID, attrOIDCIDTokenSigAlg, strJoinOr(config.Discovery.RegisteredJWKSigningAlgs), config.Clients[c].IDTokenSigningAlg)) + config.Clients[c].ID, attrOIDCIDTokenSigAlg, strJoinOr(config.Discovery.ResponseObjectSigningAlgs), config.Clients[c].IDTokenSigningAlg)) } } diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go index 93178a795..3de4b3b00 100644 --- a/internal/configuration/validator/identity_providers_test.go +++ b/internal/configuration/validator/identity_providers_test.go @@ -33,7 +33,7 @@ func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) { require.Len(t, validator.Errors(), 2) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'issuer_private_key' or `issuer_jwks` is required") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required") assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured") } @@ -181,7 +181,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - "identity_providers: oidc: client '': option 'secret' is required", + "identity_providers: oidc: clients: client '': option 'secret' is required", "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions #1", }, }, @@ -198,7 +198,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - "identity_providers: oidc: client 'client-1': option 'policy' must be one of 'one_factor' or 'two_factor' but it's configured as 'a-policy'", + "identity_providers: oidc: clients: client 'client-1': option 'authorization_policy' must be one of 'one_factor' or 'two_factor' but it's configured as 'a-policy'", }, }, { @@ -234,7 +234,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - "identity_providers: oidc: client 'client-check-uri-parse': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"", + "identity_providers: oidc: clients: client 'client-check-uri-parse': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"", }, }, { @@ -250,7 +250,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - "identity_providers: oidc: client 'client-check-uri-abs': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent", + "identity_providers: oidc: clients: client 'client-check-uri-abs': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent", }, }, { @@ -295,12 +295,12 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a scheme with the value 'https'", - "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a path with the value '/path'", - "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a query with the value 'query=abc'", - "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a fragment with the value 'fragment'", - "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a username with the value 'user'", - "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a password", + "identity_providers: oidc: clients: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a scheme with the value 'https'", + "identity_providers: oidc: clients: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a path with the value '/path'", + "identity_providers: oidc: clients: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a query with the value 'query=abc'", + "identity_providers: oidc: clients: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a fragment with the value 'fragment'", + "identity_providers: oidc: clients: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a username with the value 'user'", + "identity_providers: oidc: clients: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a password", }, }, { @@ -317,7 +317,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'example.com/path?query=abc#fragment': must be a URL with only the host component but appears to be invalid", + "identity_providers: oidc: clients: client 'client-invalid-sector': option 'sector_identifier' with value 'example.com/path?query=abc#fragment': must be a URL with only the host component but appears to be invalid", }, }, { @@ -334,7 +334,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - "identity_providers: oidc: client 'client-bad-consent-mode': consent: option 'mode' must be one of 'auto', 'implicit', 'explicit', 'pre-configured', or 'auto' but it's configured as 'cap'", + "identity_providers: oidc: clients: client 'client-bad-consent-mode': consent: option 'mode' must be one of 'auto', 'implicit', 'explicit', 'pre-configured', or 'auto' but it's configured as 'cap'", }, }, { @@ -351,7 +351,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - "identity_providers: oidc: client 'client-bad-pkce-mode': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 'abc'", + "identity_providers: oidc: clients: client 'client-bad-pkce-mode': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 'abc'", }, }, { @@ -368,7 +368,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - "identity_providers: oidc: client 'client-bad-pkce-mode-s256': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 's256'", + "identity_providers: oidc: clients: client 'client-bad-pkce-mode-s256': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 's256'", }, }, } @@ -421,7 +421,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) { ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'bad_scope' are present") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: clients: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'bad_scope' are present") } func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) { @@ -447,7 +447,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'bad_grant_type' are present") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: clients: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'bad_grant_type' are present") } func TestShouldNotErrorOnCertificateValid(t *testing.T) { @@ -501,7 +501,7 @@ func TestShouldRaiseErrorOnCertificateNotValid(t *testing.T) { assert.Len(t, validator.Warnings(), 0) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: issuer_jwks: key #1 with key id '9c7423': option 'key' does not appear to be the private key the certificate provided by option 'certificate_chain'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: issuer_private_keys: key #1 with key id '9c7423': option 'certificate_chain' does not appear to contain the public key for the private key provided by option 'key'") } func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T) { @@ -566,8 +566,8 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi require.Len(t, validator.Errors(), 2) assert.Len(t, validator.Warnings(), 0) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'client-with-invalid-secret': option 'secret' is required to be empty when option 'public' is true") - assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: client 'client-with-bad-redirect-uri': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: clients: client 'client-with-invalid-secret': option 'secret' is required to be empty when option 'public' is true") + assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: clients: client 'client-with-bad-redirect-uri': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type") } func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidClientOptions(t *testing.T) { @@ -653,7 +653,7 @@ func TestValidateIdentityProvidersShouldRaiseWarningOnPlainTextClients(t *testin assert.Len(t, validator.Errors(), 0) require.Len(t, validator.Warnings(), 1) - assert.EqualError(t, validator.Warnings()[0], "identity_providers: oidc: client 'client-with-invalid-secret_standard': option 'secret' is plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable") + assert.EqualError(t, validator.Warnings()[0], "identity_providers: oidc: clients: client 'client-with-invalid-secret_standard': option 'secret' is plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable") } // All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 @@ -691,7 +691,7 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing assert.Len(t, validator.Warnings(), 0) assert.Len(t, validator.Errors(), 1) assert.ElementsMatch(t, validator.Errors(), []error{ - errors.New("identity_providers: oidc: client 'owncloud': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type"), + errors.New("identity_providers: oidc: clients: client 'owncloud': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type"), }) }) } @@ -901,7 +901,7 @@ func TestValidateOIDCClients(t *testing.T) { []string{oidc.GrantTypeAuthorizationCode}, }, []string{ - "identity_providers: oidc: client 'test': option 'scopes' must have unique values but the values 'openid' are duplicated", + "identity_providers: oidc: clients: client 'test': option 'scopes' must have unique values but the values 'openid' are duplicated", }, nil, }, @@ -923,7 +923,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'group' are present", + "identity_providers: oidc: clients: client 'test': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'group' are present", }, }, { @@ -943,8 +943,8 @@ func TestValidateOIDCClients(t *testing.T) { []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken}, }, []string{ - "identity_providers: oidc: client 'test': option 'scopes' should only have the values 'offline_access' or 'offline' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes", - "identity_providers: oidc: client 'test': option 'grant_types' should only have the values 'refresh_token' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes", + "identity_providers: oidc: clients: client 'test': option 'scopes' should only have the values 'offline_access' or 'offline' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes", + "identity_providers: oidc: clients: client 'test': option 'grant_types' should only have the values 'refresh_token' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes", }, nil, }, @@ -965,7 +965,7 @@ func TestValidateOIDCClients(t *testing.T) { []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, }, []string{ - "identity_providers: oidc: client 'test': option 'response_types' must have unique values but the values 'code' are duplicated", + "identity_providers: oidc: clients: client 'test': option 'response_types' must have unique values but the values 'code' are duplicated", }, nil, }, @@ -986,7 +986,7 @@ func TestValidateOIDCClients(t *testing.T) { []string{"implicit"}, }, []string{ - "identity_providers: oidc: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'token id_token' are present", + "identity_providers: oidc: clients: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'token id_token' are present", }, nil, }, @@ -1007,7 +1007,7 @@ func TestValidateOIDCClients(t *testing.T) { nil, }, []string{ - "identity_providers: oidc: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'not_valid' are present", + "identity_providers: oidc: clients: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'not_valid' are present", }, nil, }, @@ -1029,7 +1029,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'response_modes' must only have the values 'form_post', 'query', or 'fragment' but the values 'not_valid' are present", + "identity_providers: oidc: clients: client 'test': option 'response_modes' must only have the values 'form_post', 'query', or 'fragment' but the values 'not_valid' are present", }, }, { @@ -1049,7 +1049,7 @@ func TestValidateOIDCClients(t *testing.T) { []string{oidc.GrantTypeAuthorizationCode}, }, []string{ - "identity_providers: oidc: client 'test': option 'response_modes' must have unique values but the values 'query' are duplicated", + "identity_providers: oidc: clients: client 'test': option 'response_modes' must have unique values but the values 'query' are duplicated", }, nil, }, @@ -1071,7 +1071,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'invalid' are present", + "identity_providers: oidc: clients: client 'test': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'invalid' are present", }, }, { @@ -1091,7 +1091,7 @@ func TestValidateOIDCClients(t *testing.T) { []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeAuthorizationCode}, }, []string{ - "identity_providers: oidc: client 'test': option 'grant_types' must have unique values but the values 'authorization_code' are duplicated", + "identity_providers: oidc: clients: client 'test': option 'grant_types' must have unique values but the values 'authorization_code' are duplicated", }, nil, }, @@ -1112,7 +1112,7 @@ func TestValidateOIDCClients(t *testing.T) { []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken}, }, []string{ - "identity_providers: oidc: client 'test': option 'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope", + "identity_providers: oidc: clients: client 'test': option 'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope", }, nil, }, @@ -1133,7 +1133,7 @@ func TestValidateOIDCClients(t *testing.T) { []string{oidc.GrantTypeAuthorizationCode}, }, []string{ - "identity_providers: oidc: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'authorization_code' expects a response type for either the authorization code or hybrid flow such as 'code', 'code id_token', 'code token', or 'code id_token token' but the response types are 'id_token token'", + "identity_providers: oidc: clients: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'authorization_code' expects a response type for either the authorization code or hybrid flow such as 'code', 'code id_token', 'code token', or 'code id_token token' but the response types are 'id_token token'", }, nil, }, @@ -1154,7 +1154,7 @@ func TestValidateOIDCClients(t *testing.T) { []string{oidc.GrantTypeImplicit}, }, []string{ - "identity_providers: oidc: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'implicit' expects a response type for either the implicit or hybrid flow such as 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the response types are 'code'", + "identity_providers: oidc: clients: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'implicit' expects a response type for either the implicit or hybrid flow such as 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the response types are 'code'", }, nil, }, @@ -1234,7 +1234,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type", + "identity_providers: oidc: clients: client 'test': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type", }, }, { @@ -1261,7 +1261,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"", + "identity_providers: oidc: clients: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"", }, }, { @@ -1288,7 +1288,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent", + "identity_providers: oidc: clients: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent", }, }, { @@ -1315,7 +1315,7 @@ func TestValidateOIDCClients(t *testing.T) { []string{oidc.GrantTypeAuthorizationCode}, }, []string{ - "identity_providers: oidc: client 'test': option 'redirect_uris' must have unique values but the values 'https://google.com' are duplicated", + "identity_providers: oidc: clients: client 'test': option 'redirect_uris' must have unique values but the values 'https://google.com' are duplicated", }, nil, }, @@ -1385,7 +1385,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', 'client_secret_basic', or 'client_secret_jwt' but it's configured as 'client_credentials'", + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', 'client_secret_basic', 'private_key_jwt', or 'client_secret_jwt' but it's configured as 'client_credentials'", }, }, { @@ -1412,7 +1412,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_basic'", + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_basic'", }, }, { @@ -1437,7 +1437,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post', 'client_secret_basic', or 'private_key_jwt' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", }, }, { @@ -1462,7 +1462,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post', 'client_secret_basic', or 'private_key_jwt' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", }, }, { @@ -1510,12 +1510,12 @@ func TestValidateOIDCClients(t *testing.T) { nil, }, { - "ShouldRaiseErrorOnInvalidUserInfoAlg", + "ShouldRaiseErrorOnInvalidUserInfoSigningAlg", func(have *schema.OpenIDConnectConfiguration) { - have.Clients[0].UserinfoSigningAlg = "rs256" + have.Clients[0].UserinfoSigningAlg = rs256 }, func(t *testing.T, have *schema.OpenIDConnectConfiguration) { - assert.Equal(t, "rs256", have.Clients[0].UserinfoSigningAlg) + assert.Equal(t, rs256, have.Clients[0].UserinfoSigningAlg) }, tcv{ nil, @@ -1531,7 +1531,32 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'userinfo_signing_algorithm' must be one of 'RS256' or 'none' but it's configured as 'rs256'", + "identity_providers: oidc: clients: client 'test': option 'userinfo_signing_algorithm' must be one of 'RS256' or 'none' but it's configured as 'rs256'", + }, + }, + { + "ShouldRaiseErrorOnInvalidIDTokenSigningAlg", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].IDTokenSigningAlg = rs256 + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, rs256, have.Clients[0].IDTokenSigningAlg) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': option 'id_token_signing_alg' must be one of 'RS256' but it's configured as 'rs256'", }, }, { @@ -1677,6 +1702,114 @@ func TestValidateOIDCClients(t *testing.T) { nil, nil, }, + { + "ShouldRaiseErrorOnTokenEndpointClientAuthMethodPrivateKeyJWTMustSetAlg", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodPrivateKeyJWT + have.Clients[0].Secret = tOpenIDConnectPBKDF2ClientSecret + }, + nil, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_signing_alg' is required when option 'token_endpoint_auth_method' is configured to 'private_key_jwt'", + "identity_providers: oidc: clients: client 'test': option 'public_keys' is required with 'token_endpoint_auth_method' set to 'private_key_jwt'", + }, + }, + { + "ShouldRaiseErrorOnTokenEndpointClientAuthMethodPrivateKeyJWTMustSetAlg", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodPrivateKeyJWT + have.Clients[0].TokenEndpointAuthSigningAlg = "nope" + have.Clients[0].Secret = tOpenIDConnectPBKDF2ClientSecret + }, + nil, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_signing_alg' must be one of 'RS256', 'PS256', 'ES256', 'RS384', 'PS384', 'ES384', 'RS512', 'PS512', or 'ES512' when option 'token_endpoint_auth_method' is configured to 'private_key_jwt'", + "identity_providers: oidc: clients: client 'test': option 'public_keys' is required with 'token_endpoint_auth_method' set to 'private_key_jwt'", + }, + }, + { + "ShouldRaiseErrorOnTokenEndpointClientAuthMethodPrivateKeyJWTMustSetKnownAlg", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodPrivateKeyJWT + have.Clients[0].TokenEndpointAuthSigningAlg = oidc.SigningAlgECDSAUsingP384AndSHA384 + have.Clients[0].Secret = tOpenIDConnectPBKDF2ClientSecret + have.Clients[0].PublicKeys.Values = []schema.JWK{ + { + KeyID: "test", + Key: keyRSA2048.Public(), + Algorithm: oidc.SigningAlgRSAUsingSHA256, + }, + } + }, + nil, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_signing_alg' must be one of registered public key algorithm values 'RS256' when option 'token_endpoint_auth_method' is configured to 'private_key_jwt'", + }, + }, + { + "ShouldRaiseErrorOnTokenEndpointClientAuthMethodPrivateKeyJWTMustSetKnownAlg", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodPrivateKeyJWT + have.Clients[0].TokenEndpointAuthSigningAlg = oidc.SigningAlgECDSAUsingP384AndSHA384 + have.Clients[0].Secret = tOpenIDConnectPBKDF2ClientSecret + }, + nil, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': option 'public_keys' is required with 'token_endpoint_auth_method' set to 'private_key_jwt'", + }, + }, { "ShouldRaiseErrorOnIncorrectlyConfiguredTokenEndpointClientAuthMethodClientSecretJWT", func(have *schema.OpenIDConnectConfiguration) { @@ -1698,7 +1831,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'", + "identity_providers: oidc: clients: client 'test': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'", }, }, { @@ -1744,7 +1877,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'", + "identity_providers: oidc: clients: client 'test': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'", }, }, { @@ -1818,7 +1951,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_jwt'", + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_jwt'", }, }, { @@ -1845,7 +1978,7 @@ func TestValidateOIDCClients(t *testing.T) { }, nil, []string{ - "identity_providers: oidc: client 'test': option 'token_endpoint_auth_signing_alg' must be 'HS256', 'HS384', or 'HS512' when option 'token_endpoint_auth_method' is client_secret_jwt", + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_signing_alg' must be one of 'HS256', 'HS384', or 'HS512' when option 'token_endpoint_auth_method' is configured to 'client_secret_jwt'", }, }, } @@ -1856,7 +1989,7 @@ func TestValidateOIDCClients(t *testing.T) { t.Run(tc.name, func(t *testing.T) { have := &schema.OpenIDConnectConfiguration{ Discovery: schema.OpenIDConnectDiscovery{ - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, Clients: []schema.OpenIDConnectClientConfiguration{ { @@ -1917,17 +2050,17 @@ func TestValidateOIDCClientTokenEndpointAuthMethod(t *testing.T) { {"ShouldSetDefaultValueConfidential", "", false, "", nil}, {"ShouldErrorOnInvalidValue", "abc", false, "abc", []string{ - "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', 'client_secret_basic', or 'client_secret_jwt' but it's configured as 'abc'", + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', 'client_secret_basic', 'private_key_jwt', or 'client_secret_jwt' but it's configured as 'abc'", }, }, {"ShouldErrorOnInvalidValueForPublicClient", "client_secret_post", true, "client_secret_post", []string{ - "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_post'", + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_post'", }, }, {"ShouldErrorOnInvalidValueForConfidentialClient", "none", false, "none", []string{ - "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", + "identity_providers: oidc: clients: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post', 'client_secret_basic', or 'private_key_jwt' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", }, }, } @@ -1961,6 +2094,309 @@ func TestValidateOIDCClientTokenEndpointAuthMethod(t *testing.T) { } } +func TestValidateOIDCClientJWKS(t *testing.T) { + frankenchain := schema.NewX509CertificateChainFromCerts([]*x509.Certificate{certRSA2048.Leaf(), certRSA1024.Leaf()}) + frankenkey := &rsa.PrivateKey{} + + *frankenkey = *keyRSA2048 + + frankenkey.PublicKey.N = nil + + testCases := []struct { + name string + haveURI *url.URL + haveJWKS []schema.JWK + setup func(config *schema.OpenIDConnectConfiguration) + expected func(t *testing.T, config *schema.OpenIDConnectConfiguration) + errs []string + }{ + { + "ShouldValidateURL", + MustParseURL("https://example.com"), + nil, + nil, + nil, + nil, + }, + { + "ShouldErrorOnHTTPURL", + MustParseURL("http://example.com"), + nil, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: option 'uri' must have the 'https' scheme but the scheme is 'http'", + }, + }, + { + "ShouldErrorOnBothDefined", + MustParseURL("http://example.com"), + []schema.JWK{ + {KeyID: "test"}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: option 'uri' must not be defined at the same time as option 'values'", + }, + }, + { + "ShouldAllowGoodKey", + nil, + []schema.JWK{ + {KeyID: "test", Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA2048PKCS8.Public()}, + }, + nil, + nil, + nil, + }, + { + "ShouldAllowGoodKeyWithCertificate", + nil, + []schema.JWK{ + {KeyID: "test", Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA2048.Public(), CertificateChain: certRSA2048}, + }, + nil, + nil, + nil, + }, + { + "ShouldErrorOnPrivateKey", + nil, + []schema.JWK{ + {KeyID: "test", Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA2048PKCS8}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1 with key id 'test': option 'key' must be a RSA public key or ECDSA public key but it's type is *rsa.PrivateKey", + }, + }, + { + "ShouldErrorOnMissingKID", + nil, + []schema.JWK{ + {Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA2048PKCS8.Public()}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1: option 'key_id' must be provided", + }, + }, + { + "ShouldFailOnNonKey", + nil, + []schema.JWK{ + {Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: "nokey", KeyID: "KeyID"}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1 with key id 'KeyID': option 'key' failed to get key properties: the key type 'string' is unknown or not valid for the configuration", + }, + }, + { + "ShouldFailOnBadUseAlg", + nil, + []schema.JWK{ + {KeyID: "test", Use: "enc", Algorithm: "bad", Key: keyRSA2048PKCS8.Public()}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1 with key id 'test': option 'use' must be one of 'sig' but it's configured as 'enc'", + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1 with key id 'test': option 'algorithm' must be one of 'RS256', 'PS256', 'ES256', 'RS384', 'PS384', 'ES384', 'RS512', 'PS512', or 'ES512' but it's configured as 'bad'", + }, + }, + { + "ShouldFailOnEmptyKey", + nil, + []schema.JWK{ + {KeyID: "test", Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: nil}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1: option 'key' must be provided", + }, + }, + { + "ShouldFailOnMalformedKey", + nil, + []schema.JWK{ + {KeyID: "test", Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: frankenkey.Public()}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1: option 'key' option 'key' must be a valid private key but the provided data is malformed as it's missing the public key bits", + }, + }, + { + "ShouldFailOnBadKeySize", + nil, + []schema.JWK{ + {KeyID: "test", Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA1024.Public()}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1 with key id 'test': option 'key' is an RSA 1024 bit private key but it must at minimum be a RSA 2048 bit private key", + }, + }, + { + "ShouldFailOnMismatchedKeyCert", + nil, + []schema.JWK{ + {KeyID: "test", Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA2048PKCS8.Public(), CertificateChain: certRSA2048}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1 with key id 'test': option 'certificate_chain' does not appear to contain the public key for the public key provided by option 'key'", + }, + }, + { + "ShouldFailOnMismatchedCertChain", + nil, + []schema.JWK{ + {KeyID: "test", Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA2048.Public(), CertificateChain: frankenchain}, + }, + nil, + nil, + []string{ + "identity_providers: oidc: clients: client 'test': public_keys: values: key #1 with key id 'test': option 'certificate_chain' produced an error during validation of the chain: certificate #1 in chain is not signed properly by certificate #2 in chain: x509: invalid signature: parent certificate cannot sign this kind of certificate", + }, + }, + { + "ShouldSetDefaultUseAlgRSA", + nil, + []schema.JWK{ + {KeyID: "test", Use: "", Algorithm: "", Key: keyRSA2048PKCS8.Public()}, + }, + nil, + func(t *testing.T, config *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.KeyUseSignature, config.Clients[0].PublicKeys.Values[0].Use) + assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, config.Clients[0].PublicKeys.Values[0].Algorithm) + }, + nil, + }, + { + "ShouldSetDefaultUseAlgECDSA256", + nil, + []schema.JWK{ + {KeyID: "test", Use: "", Algorithm: "", Key: keyECDSAP256.Public()}, + }, + nil, + func(t *testing.T, config *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.KeyUseSignature, config.Clients[0].PublicKeys.Values[0].Use) + assert.Equal(t, oidc.SigningAlgECDSAUsingP256AndSHA256, config.Clients[0].PublicKeys.Values[0].Algorithm) + }, + nil, + }, + { + "ShouldSetDefaultUseAlgECDSA384", + nil, + []schema.JWK{ + {KeyID: "test", Use: "", Algorithm: "", Key: keyECDSAP384.Public()}, + }, + nil, + func(t *testing.T, config *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.KeyUseSignature, config.Clients[0].PublicKeys.Values[0].Use) + assert.Equal(t, oidc.SigningAlgECDSAUsingP384AndSHA384, config.Clients[0].PublicKeys.Values[0].Algorithm) + }, + nil, + }, + { + "ShouldSetDefaultUseAlgECDSA521", + nil, + []schema.JWK{ + {KeyID: "test", Use: "", Algorithm: "", Key: keyECDSAP521.Public()}, + }, + nil, + func(t *testing.T, config *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.KeyUseSignature, config.Clients[0].PublicKeys.Values[0].Use) + assert.Equal(t, oidc.SigningAlgECDSAUsingP521AndSHA512, config.Clients[0].PublicKeys.Values[0].Algorithm) + }, + nil, + }, + { + "ShouldConfigureRegisteredRequestObjectAlgs", + nil, + []schema.JWK{ + {KeyID: "test", Use: "", Algorithm: "", Key: keyECDSAP521.Public()}, + }, + nil, + func(t *testing.T, config *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.KeyUseSignature, config.Clients[0].PublicKeys.Values[0].Use) + assert.Equal(t, oidc.SigningAlgECDSAUsingP521AndSHA512, config.Clients[0].PublicKeys.Values[0].Algorithm) + + assert.Equal(t, []string{oidc.SigningAlgECDSAUsingP521AndSHA512}, config.Discovery.RequestObjectSigningAlgs) + }, + nil, + }, + { + "ShouldOnlyAllowRequetsObjectSigningAlgsThatTheClientHasKeysFor", + nil, + []schema.JWK{ + {KeyID: "test", Use: "", Algorithm: "", Key: keyECDSAP521.Public()}, + }, + func(config *schema.OpenIDConnectConfiguration) { + config.Clients[0].RequestObjectSigningAlg = oidc.SigningAlgRSAUsingSHA512 + }, + func(t *testing.T, config *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.KeyUseSignature, config.Clients[0].PublicKeys.Values[0].Use) + assert.Equal(t, oidc.SigningAlgECDSAUsingP521AndSHA512, config.Clients[0].PublicKeys.Values[0].Algorithm) + + assert.Equal(t, []string{oidc.SigningAlgECDSAUsingP521AndSHA512}, config.Discovery.RequestObjectSigningAlgs) + }, + []string{ + "identity_providers: oidc: clients: client 'test': option 'request_object_signing_alg' must be one of 'ES512' configured in the client option 'public_keys'", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config := &schema.OpenIDConnectConfiguration{ + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "test", + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + URI: tc.haveURI, + Values: tc.haveJWKS, + }, + }, + }, + } + + if tc.setup != nil { + tc.setup(config) + } + + val := schema.NewStructValidator() + + validateOIDCClientPublicKeys(0, config, val) + + if tc.expected != nil { + tc.expected(t, config) + } + + n := len(tc.errs) + + assert.Len(t, val.Warnings(), 0) + + theErrors := val.Errors() + require.Len(t, theErrors, n) + + for i := 0; i < n; i++ { + assert.EqualError(t, theErrors[i], tc.errs[i]) + } + }) + } +} + func TestValidateOIDCIssuer(t *testing.T) { frankenchain := schema.NewX509CertificateChainFromCerts([]*x509.Certificate{certRSA2048.Leaf(), certRSA1024.Leaf()}) frankenkey := &rsa.PrivateKey{} @@ -1982,12 +2418,12 @@ func TestValidateOIDCIssuer(t *testing.T) { }, schema.OpenIDConnectConfiguration{ IssuerPrivateKey: keyRSA2048, - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {KeyID: "e7dfdc", Key: keyRSA2048, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "e7dfdc", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + DefaultKeyID: "e7dfdc", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, }, nil, @@ -1995,7 +2431,7 @@ func TestValidateOIDCIssuer(t *testing.T) { { "ShouldSetDefaultKeyValues", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA2048, CertificateChain: certRSA2048}, {Key: keyECDSAP256, CertificateChain: certECDSAP256}, {Key: keyECDSAP384, CertificateChain: certECDSAP384}, @@ -2003,15 +2439,15 @@ func TestValidateOIDCIssuer(t *testing.T) { }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA2048, CertificateChain: certRSA2048, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "e7dfdc"}, {Key: keyECDSAP256, CertificateChain: certECDSAP256, Algorithm: oidc.SigningAlgECDSAUsingP256AndSHA256, Use: oidc.KeyUseSignature, KeyID: "29b3f2"}, {Key: keyECDSAP384, CertificateChain: certECDSAP384, Algorithm: oidc.SigningAlgECDSAUsingP384AndSHA384, Use: oidc.KeyUseSignature, KeyID: "e968b4"}, {Key: keyECDSAP521, CertificateChain: certECDSAP521, Algorithm: oidc.SigningAlgECDSAUsingP521AndSHA512, Use: oidc.KeyUseSignature, KeyID: "6b20c3"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "e7dfdc", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512}, + DefaultKeyID: "e7dfdc", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512}, }, }, nil, @@ -2019,288 +2455,288 @@ func TestValidateOIDCIssuer(t *testing.T) { { "ShouldRaiseErrorsDuplicateRSA256Keys", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA2048, CertificateChain: certRSA2048}, {Key: keyRSA4096, CertificateChain: certRSA4096}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA2048, CertificateChain: certRSA2048, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "e7dfdc"}, {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "9c7423"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "e7dfdc", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + DefaultKeyID: "e7dfdc", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #2 with key id '9c7423': option 'algorithm' must be unique but another key is using it", + "identity_providers: oidc: issuer_private_keys: key #2 with key id '9c7423': option 'algorithm' must be unique", }, }, { "ShouldRaiseErrorsDuplicateRSA256Keys", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: oidc.SigningAlgRSAUsingSHA512}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: oidc.SigningAlgRSAUsingSHA512, Use: oidc.KeyUseSignature, KeyID: "9c7423"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA512}, + DefaultKeyID: "", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA512}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: keys: must at least have one key supporting the 'RS256' algorithm but only has 'RS512'", + "identity_providers: oidc: issuer_private_keys: keys: must at least have one key supporting the 'RS256' algorithm but only has 'RS512'", }, }, { "ShouldRaiseErrorOnBadCurve", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096}, {Key: keyECDSAP224, CertificateChain: certECDSAP224}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "9c7423"}, {Key: keyECDSAP224, CertificateChain: certECDSAP224}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "9c7423", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + DefaultKeyID: "9c7423", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #2: option 'key' failed to calculate thumbprint to configure key id value: square/go-jose: unsupported/unknown elliptic curve", + "identity_providers: oidc: issuer_private_keys: key #2: option 'key' failed to calculate thumbprint to configure key id value: square/go-jose: unsupported/unknown elliptic curve", }, }, { "ShouldRaiseErrorOnBadRSAKey", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA1024, CertificateChain: certRSA1024}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA1024, CertificateChain: certRSA1024, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "a9c018"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "a9c018", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + DefaultKeyID: "a9c018", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1 with key id 'a9c018': option 'key' is an RSA 1024 bit private key but it must be a RSA 2048 bit private key", + "identity_providers: oidc: issuer_private_keys: key #1 with key id 'a9c018': option 'key' is an RSA 1024 bit private key but it must at minimum be a RSA 2048 bit private key", }, }, { "ShouldRaiseErrorOnBadAlg", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: "invalid"}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: "invalid", Use: oidc.KeyUseSignature, KeyID: "9c7423"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "", - RegisteredJWKSigningAlgs: []string{"invalid"}, + DefaultKeyID: "", + ResponseObjectSigningAlgs: []string{"invalid"}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1 with key id '9c7423': option 'algorithm' must be one of 'RS256', 'PS256', 'ES256', 'RS384', 'PS384', 'ES384', 'RS512', 'PS512', or 'ES512' but it's configured as 'invalid'", - "identity_providers: oidc: issuer_jwks: keys: must at least have one key supporting the 'RS256' algorithm but only has 'invalid'", + "identity_providers: oidc: issuer_private_keys: key #1 with key id '9c7423': option 'algorithm' must be one of 'RS256', 'PS256', 'ES256', 'RS384', 'PS384', 'ES384', 'RS512', 'PS512', or 'ES512' but it's configured as 'invalid'", + "identity_providers: oidc: issuer_private_keys: keys: must at least have one key supporting the 'RS256' algorithm but only has 'invalid'", }, }, { "ShouldRaiseErrorOnBadUse", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Use: "invalid"}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: "invalid", KeyID: "9c7423"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "9c7423", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + DefaultKeyID: "9c7423", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1 with key id '9c7423': option 'use' must be one of 'sig' but it's configured as 'invalid'", + "identity_providers: oidc: issuer_private_keys: key #1 with key id '9c7423': option 'use' must be one of 'sig' but it's configured as 'invalid'", }, }, { "ShouldRaiseErrorOnBadKeyIDLength", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, KeyID: "thisistoolong"}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "thisistoolong"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "thisistoolong", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + DefaultKeyID: "thisistoolong", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1 with key id 'thisistoolong': option `key_id`` must be 7 characters or less", + "identity_providers: oidc: issuer_private_keys: key #1 with key id 'thisistoolong': option `key_id`` must be 7 characters or less", }, }, { "ShouldRaiseErrorOnBadKeyIDCharacters", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, KeyID: "x@x"}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "x@x"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "x@x", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + DefaultKeyID: "x@x", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1 with key id 'x@x': option 'key_id' must only have alphanumeric characters", + "identity_providers: oidc: issuer_private_keys: key #1 with key id 'x@x': option 'key_id' must only have alphanumeric characters", }, }, { "ShouldRaiseErrorOnBadKeyIDDuplicates", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, KeyID: "x"}, {Key: keyRSA2048, CertificateChain: certRSA2048, Algorithm: oidc.SigningAlgRSAPSSUsingSHA256, KeyID: "x"}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA4096, CertificateChain: certRSA4096, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "x"}, {Key: keyRSA2048, CertificateChain: certRSA2048, Algorithm: oidc.SigningAlgRSAPSSUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "x"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "x", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256}, + DefaultKeyID: "x", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #2 with key id 'x': option 'key_id' must be unique", + "identity_providers: oidc: issuer_private_keys: key #2 with key id 'x': option 'key_id' must be unique", }, }, { "ShouldRaiseErrorOnEd25519Keys", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyEd2519, CertificateChain: certEd15519}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyEd2519, CertificateChain: certEd15519, KeyID: "d2dd94"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "", - RegisteredJWKSigningAlgs: []string(nil), + DefaultKeyID: "", + ResponseObjectSigningAlgs: []string(nil), }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1 with key id 'd2dd94': option 'key' must be a *rsa.PrivateKey or *ecdsa.PrivateKey but it's a ed25519.PrivateKey", + "identity_providers: oidc: issuer_private_keys: key #1 with key id 'd2dd94': option 'key' must be a RSA private key or ECDSA private key but it's type is ed25519.PrivateKey", }, }, { "ShouldRaiseErrorOnCertificateAsKey", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: publicRSA2048Pair}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: publicRSA2048Pair, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "904c62"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "904c62", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + DefaultKeyID: "904c62", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1 with key id '904c62': option 'key' must be a *rsa.PrivateKey or *ecdsa.PrivateKey but it's a *rsa.PublicKey", + "identity_providers: oidc: issuer_private_keys: key #1 with key id '904c62': option 'key' must be a RSA private key or ECDSA private key but it's type is *rsa.PublicKey", }, }, { "ShouldRaiseErrorOnInvalidChain", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA2048, CertificateChain: frankenchain}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: keyRSA2048, CertificateChain: frankenchain, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature, KeyID: "e7dfdc"}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "e7dfdc", - RegisteredJWKSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, + DefaultKeyID: "e7dfdc", + ResponseObjectSigningAlgs: []string{oidc.SigningAlgRSAUsingSHA256}, }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1 with key id 'e7dfdc': option 'certificate_chain' produced an error during validation of the chain: certificate #1 in chain is not signed properly by certificate #2 in chain: x509: invalid signature: parent certificate cannot sign this kind of certificate", + "identity_providers: oidc: issuer_private_keys: key #1 with key id 'e7dfdc': option 'certificate_chain' produced an error during validation of the chain: certificate #1 in chain is not signed properly by certificate #2 in chain: x509: invalid signature: parent certificate cannot sign this kind of certificate", }, }, { "ShouldRaiseErrorOnInvalidPrivateKeyN", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: frankenkey}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: frankenkey}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "", - RegisteredJWKSigningAlgs: []string(nil), + DefaultKeyID: "", + ResponseObjectSigningAlgs: []string(nil), }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1: option 'key' must be a valid RSA private key but the provided data is malformed as it's missing the public key bits", + "identity_providers: oidc: issuer_private_keys: key #1: option 'key' must be a valid private key but the provided data is malformed as it's missing the public key bits", }, }, { "ShouldRaiseErrorOnCertForKey", schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: certRSA2048}, }, }, schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ {Key: certRSA2048}, }, Discovery: schema.OpenIDConnectDiscovery{ - DefaultKeyID: "", - RegisteredJWKSigningAlgs: []string(nil), + DefaultKeyID: "", + ResponseObjectSigningAlgs: []string(nil), }, }, []string{ - "identity_providers: oidc: issuer_jwks: key #1 with key id '': option 'key' failed to get key properties: the key type 'schema.X509CertificateChain' is unknown or not valid for the configuration", + "identity_providers: oidc: issuer_private_keys: key #1 with key id '': option 'key' failed to get key properties: the key type 'schema.X509CertificateChain' is unknown or not valid for the configuration", }, }, } @@ -2314,21 +2750,21 @@ func TestValidateOIDCIssuer(t *testing.T) { validateOIDCIssuer(&tc.have, val) assert.Equal(t, tc.expected.Discovery.DefaultKeyID, tc.have.Discovery.DefaultKeyID) - assert.Equal(t, tc.expected.Discovery.RegisteredJWKSigningAlgs, tc.have.Discovery.RegisteredJWKSigningAlgs) + assert.Equal(t, tc.expected.Discovery.ResponseObjectSigningAlgs, tc.have.Discovery.ResponseObjectSigningAlgs) assert.Equal(t, tc.expected.IssuerPrivateKey, tc.have.IssuerPrivateKey) assert.Equal(t, tc.expected.IssuerCertificateChain, tc.have.IssuerCertificateChain) - n = len(tc.expected.IssuerJWKS) + n = len(tc.expected.IssuerPrivateKeys) - require.Len(t, tc.have.IssuerJWKS, n) + require.Len(t, tc.have.IssuerPrivateKeys, n) for i := 0; i < n; i++ { t.Run(fmt.Sprintf("Key%d", i), func(t *testing.T) { - assert.Equal(t, tc.expected.IssuerJWKS[i].Algorithm, tc.have.IssuerJWKS[i].Algorithm) - assert.Equal(t, tc.expected.IssuerJWKS[i].Use, tc.have.IssuerJWKS[i].Use) - assert.Equal(t, tc.expected.IssuerJWKS[i].KeyID, tc.have.IssuerJWKS[i].KeyID) - assert.Equal(t, tc.expected.IssuerJWKS[i].Key, tc.have.IssuerJWKS[i].Key) - assert.Equal(t, tc.expected.IssuerJWKS[i].CertificateChain, tc.have.IssuerJWKS[i].CertificateChain) + assert.Equal(t, tc.expected.IssuerPrivateKeys[i].Algorithm, tc.have.IssuerPrivateKeys[i].Algorithm) + assert.Equal(t, tc.expected.IssuerPrivateKeys[i].Use, tc.have.IssuerPrivateKeys[i].Use) + assert.Equal(t, tc.expected.IssuerPrivateKeys[i].KeyID, tc.have.IssuerPrivateKeys[i].KeyID) + assert.Equal(t, tc.expected.IssuerPrivateKeys[i].Key, tc.have.IssuerPrivateKeys[i].Key) + assert.Equal(t, tc.expected.IssuerPrivateKeys[i].CertificateChain, tc.have.IssuerPrivateKeys[i].CertificateChain) }) } @@ -2387,17 +2823,6 @@ func MustLoadCertificateChain(alg, op string) schema.X509CertificateChain { } } -func MustLoadCertificate(alg, op string) *x509.Certificate { - decoded := MustLoadCrypto(alg, op, "crt") - - cert, ok := decoded.(*x509.Certificate) - if !ok { - panic(fmt.Errorf("the key was not a *x509.Certificate, it's a %T", cert)) - } - - return cert -} - func MustLoadEd15519PrivateKey(curve string, extra ...string) ed25519.PrivateKey { decoded := MustLoadCrypto("ED25519", curve, "pem", extra...) diff --git a/internal/configuration/validator/notifier.go b/internal/configuration/validator/notifier.go index eaa64508b..deb2bc24c 100644 --- a/internal/configuration/validator/notifier.go +++ b/internal/configuration/validator/notifier.go @@ -37,13 +37,7 @@ func validateNotifierTemplates(config *schema.NotifierConfiguration, validator * return } - var ( - err error - ) - - _, err = os.Stat(config.TemplatePath) - - switch { + switch _, err := os.Stat(config.TemplatePath); { case os.IsNotExist(err): validator.Push(fmt.Errorf(errFmtNotifierTemplatePathNotExist, config.TemplatePath)) return diff --git a/internal/configuration/validator/notifier_test.go b/internal/configuration/validator/notifier_test.go index e3c764153..f2d39d5f5 100644 --- a/internal/configuration/validator/notifier_test.go +++ b/internal/configuration/validator/notifier_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/authelia/authelia/v4/internal/configuration/schema" @@ -284,3 +285,17 @@ func (suite *NotifierSuite) TestFileShouldEnsureFilenameIsProvided() { func TestNotifierSuite(t *testing.T) { suite.Run(t, new(NotifierSuite)) } + +func TestNotifierMiscMissingTemplateTests(t *testing.T) { + config := &schema.NotifierConfiguration{ + TemplatePath: string([]byte{0x0, 0x1}), + } + + val := schema.NewStructValidator() + + validateNotifierTemplates(config, val) + + require.Len(t, val.Errors(), 1) + + assert.EqualError(t, val.Errors()[0], "notifier: option 'template_path' refers to location '\x00\x01' which couldn't be opened: stat \x00\x01: invalid argument") +} diff --git a/internal/configuration/validator/server.go b/internal/configuration/validator/server.go index 6b71a00b0..3dcfc7533 100644 --- a/internal/configuration/validator/server.go +++ b/internal/configuration/validator/server.go @@ -2,6 +2,7 @@ package validator import ( "fmt" + "os" "path" "sort" "strings" @@ -19,11 +20,11 @@ func ValidateServerTLS(config *schema.Configuration, validator *schema.StructVal } if config.Server.TLS.Key != "" { - validateFileExists(config.Server.TLS.Key, validator, "key") + validateServerTLSFileExists("key", config.Server.TLS.Key, validator) } if config.Server.TLS.Certificate != "" { - validateFileExists(config.Server.TLS.Certificate, validator, "certificate") + validateServerTLSFileExists("certificate", config.Server.TLS.Certificate, validator) } if config.Server.TLS.Key == "" && config.Server.TLS.Certificate == "" && @@ -32,7 +33,24 @@ func ValidateServerTLS(config *schema.Configuration, validator *schema.StructVal } for _, clientCertPath := range config.Server.TLS.ClientCertificates { - validateFileExists(clientCertPath, validator, "client_certificates") + validateServerTLSFileExists("client_certificates", clientCertPath, validator) + } +} + +// validateServerTLSFileExists checks whether a file exist. +func validateServerTLSFileExists(name, path string, validator *schema.StructValidator) { + var ( + info os.FileInfo + err error + ) + + switch info, err = os.Stat(path); { + case os.IsNotExist(err): + validator.Push(fmt.Errorf("server: tls: option '%s' with path '%s' refers to a file that doesn't exist", name, path)) + case err != nil: + validator.Push(fmt.Errorf("server: tls: option '%s' with path '%s' could not be verified due to a file system error: %w", name, path, err)) + case info.IsDir(): + validator.Push(fmt.Errorf("server: tls: option '%s' with path '%s' refers to a directory but it should refer to a file", name, path)) } } @@ -197,13 +215,3 @@ func validateServerEndpointsAuthzStrategies(name string, strategies []schema.Ser } } } - -// validateFileExists checks whether a file exist. -func validateFileExists(path string, validator *schema.StructValidator, opt string) { - exist, err := utils.FileExists(path) - if err != nil { - validator.Push(fmt.Errorf(errFmtServerTLSFileNotExistErr, opt, path, err)) - } else if !exist { - validator.Push(fmt.Errorf(errFmtServerTLSFileNotExist, opt, path)) - } -} diff --git a/internal/configuration/validator/server_test.go b/internal/configuration/validator/server_test.go index 8c0c9d56b..7ba5f55cf 100644 --- a/internal/configuration/validator/server_test.go +++ b/internal/configuration/validator/server_test.go @@ -1,6 +1,7 @@ package validator import ( + "fmt" "os" "testing" "time" @@ -296,7 +297,7 @@ func TestShouldRaiseErrorWhenTLSCertDoesNotExist(t *testing.T) { ValidateServer(&config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "server: tls: option 'certificate' the file '/tmp/unexisting_file' does not exist") + assert.EqualError(t, validator.Errors()[0], "server: tls: option 'certificate' with path '/tmp/unexisting_file' refers to a file that doesn't exist") } func TestShouldRaiseErrorWhenTLSKeyWithoutCertIsProvided(t *testing.T) { @@ -329,7 +330,7 @@ func TestShouldRaiseErrorWhenTLSKeyDoesNotExist(t *testing.T) { ValidateServer(&config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "server: tls: option 'key' the file '/tmp/unexisting_file' does not exist") + assert.EqualError(t, validator.Errors()[0], "server: tls: option 'key' with path '/tmp/unexisting_file' refers to a file that doesn't exist") } func TestShouldNotRaiseErrorWhenBothTLSCertificateAndKeyAreProvided(t *testing.T) { @@ -373,7 +374,7 @@ func TestShouldRaiseErrorWhenTLSClientCertificateDoesNotExist(t *testing.T) { ValidateServer(&config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "server: tls: option 'client_certificates' the file '/tmp/unexisting' does not exist") + assert.EqualError(t, validator.Errors()[0], "server: tls: option 'client_certificates' with path '/tmp/unexisting' refers to a file that doesn't exist") } func TestShouldRaiseErrorWhenTLSClientAuthIsDefinedButNotServerCertificate(t *testing.T) { @@ -569,3 +570,25 @@ func TestServerAuthzEndpointLegacyAsImplementationLegacyWhenBlank(t *testing.T) assert.Equal(t, authzImplementationLegacy, config.Server.Endpoints.Authz[legacy].Implementation) } + +func TestValidateTLSPathStatInvalidArgument(t *testing.T) { + val := schema.NewStructValidator() + + validateServerTLSFileExists("key", string([]byte{0x0, 0x1}), val) + + require.Len(t, val.Errors(), 1) + + assert.EqualError(t, val.Errors()[0], "server: tls: option 'key' with path '\x00\x01' could not be verified due to a file system error: stat \x00\x01: invalid argument") +} + +func TestValidateTLSPathIsDir(t *testing.T) { + dir := t.TempDir() + + val := schema.NewStructValidator() + + validateServerTLSFileExists("key", dir, val) + + require.Len(t, val.Errors(), 1) + + assert.EqualError(t, val.Errors()[0], fmt.Sprintf("server: tls: option 'key' with path '%s' refers to a directory but it should refer to a file", dir)) +} diff --git a/internal/configuration/validator/shared.go b/internal/configuration/validator/shared.go index 804945553..297ba0c2b 100644 --- a/internal/configuration/validator/shared.go +++ b/internal/configuration/validator/shared.go @@ -10,6 +10,10 @@ import ( // ValidateTLSConfig sets the default values and validates a schema.TLSConfig. func ValidateTLSConfig(config *schema.TLSConfig, configDefault *schema.TLSConfig) (err error) { + if configDefault == nil { + return errors.New("must provide configDefault") + } + if config == nil { return } @@ -35,7 +39,7 @@ func ValidateTLSConfig(config *schema.TLSConfig, configDefault *schema.TLSConfig } if (config.CertificateChain.HasCertificates() || config.PrivateKey != nil) && !config.CertificateChain.EqualKey(config.PrivateKey) { - return errors.New("option 'certificates' is invalid: provided certificate is not the public key for the private key provided") + return errors.New("option 'certificates' is invalid: provided certificate does not contain the public key for the private key provided") } return nil diff --git a/internal/configuration/validator/shared_test.go b/internal/configuration/validator/shared_test.go new file mode 100644 index 000000000..27e48ad75 --- /dev/null +++ b/internal/configuration/validator/shared_test.go @@ -0,0 +1,30 @@ +package validator + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/authelia/authelia/v4/internal/configuration/schema" +) + +func TestValidateTLSConfig(t *testing.T) { + var ( + config, configDefault *schema.TLSConfig + ) + + assert.EqualError(t, ValidateTLSConfig(config, configDefault), "must provide configDefault") + + configDefault = &schema.TLSConfig{} + + assert.NoError(t, ValidateTLSConfig(config, configDefault)) + + config = &schema.TLSConfig{} + + assert.NoError(t, ValidateTLSConfig(config, configDefault)) + + config.PrivateKey = keyRSA2048 + config.CertificateChain = certRSA4096 + + assert.EqualError(t, ValidateTLSConfig(config, configDefault), "option 'certificates' is invalid: provided certificate does not contain the public key for the private key provided") +} diff --git a/internal/configuration/validator/util.go b/internal/configuration/validator/util.go index e40f9ec4f..9ccf235c5 100644 --- a/internal/configuration/validator/util.go +++ b/internal/configuration/validator/util.go @@ -125,12 +125,20 @@ type JWKProperties struct { func schemaJWKGetProperties(jwk schema.JWK) (properties *JWKProperties, err error) { switch key := jwk.Key.(type) { case nil: - return nil, fmt.Errorf("private key is nil") + return nil, nil case ed25519.PrivateKey, ed25519.PublicKey: return &JWKProperties{}, nil case *rsa.PrivateKey: + if key.PublicKey.N == nil { + return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, 0, nil}, nil + } + return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, key.Size(), nil}, nil case *rsa.PublicKey: + if key.N == nil { + return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, 0, nil}, nil + } + return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, key.Size(), nil}, nil case *ecdsa.PublicKey: switch key.Curve { diff --git a/internal/configuration/validator/util_test.go b/internal/configuration/validator/util_test.go index 54c9bc711..2e3f14542 100644 --- a/internal/configuration/validator/util_test.go +++ b/internal/configuration/validator/util_test.go @@ -1,9 +1,14 @@ package validator import ( + "crypto/elliptic" + "crypto/rsa" "testing" "github.com/stretchr/testify/assert" + + "github.com/authelia/authelia/v4/internal/configuration/schema" + "github.com/authelia/authelia/v4/internal/oidc" ) func TestIsCookieDomainValid(t *testing.T) { @@ -38,3 +43,39 @@ func TestIsCookieDomainValid(t *testing.T) { }) } } + +func TestBuildStringFuncsMissingTests(t *testing.T) { + assert.Equal(t, "", buildJoinedString(".", ":", "'", nil)) + assert.Equal(t, "'abc', '123'", strJoinComma("", []string{"abc", "123"})) +} + +func TestSchemaJWKGetPropertiesMissingTests(t *testing.T) { + props, err := schemaJWKGetProperties(schema.JWK{Key: keyECDSAP224}) + + assert.NoError(t, err) + assert.Equal(t, oidc.KeyUseSignature, props.Use) + assert.Equal(t, "", props.Algorithm) + assert.Equal(t, elliptic.P224(), props.Curve) + assert.Equal(t, -1, props.Bits) + + props, err = schemaJWKGetProperties(schema.JWK{Key: keyECDSAP224.Public()}) + + assert.NoError(t, err) + assert.Equal(t, oidc.KeyUseSignature, props.Use) + assert.Equal(t, "", props.Algorithm) + assert.Equal(t, elliptic.P224(), props.Curve) + assert.Equal(t, -1, props.Bits) + + rsa := &rsa.PrivateKey{} + + *rsa = *keyRSA2048 + rsa.PublicKey.N = nil + + props, err = schemaJWKGetProperties(schema.JWK{Key: rsa}) + + assert.NoError(t, err) + assert.Equal(t, oidc.KeyUseSignature, props.Use) + assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, props.Algorithm) + assert.Equal(t, nil, props.Curve) + assert.Equal(t, 0, props.Bits) +} diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go index 9d8ea4e4e..2b1af3ed5 100644 --- a/internal/handlers/handler_oidc_authorization.go +++ b/internal/handlers/handler_oidc_authorization.go @@ -127,7 +127,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID) - session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKIDFromAlg(ctx, client.GetIDTokenSigningAlg()), + session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKeyIDFromAlg(ctx, client.GetIDTokenSigningAlg()), userSession.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extraClaims, authTime, consent, requester) ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with claims: %+v", diff --git a/internal/oidc/amr_test.go b/internal/oidc/amr_test.go index d1f382242..522102973 100644 --- a/internal/oidc/amr_test.go +++ b/internal/oidc/amr_test.go @@ -1,10 +1,12 @@ -package oidc +package oidc_test import ( "fmt" "testing" "github.com/stretchr/testify/assert" + + "github.com/authelia/authelia/v4/internal/oidc" ) type testAMRWant struct { @@ -17,13 +19,13 @@ type testAMRWant struct { func TestAuthenticationMethodsReferences(t *testing.T) { testCases := []struct { desc string - is AuthenticationMethodsReferences + is oidc.AuthenticationMethodsReferences want testAMRWant }{ { desc: "Username and Password", - is: AuthenticationMethodsReferences{UsernameAndPassword: true}, + is: oidc.AuthenticationMethodsReferences{UsernameAndPassword: true}, want: testAMRWant{ FactorKnowledge: true, FactorPossession: false, @@ -37,7 +39,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) { { desc: "TOTP", - is: AuthenticationMethodsReferences{TOTP: true}, + is: oidc.AuthenticationMethodsReferences{TOTP: true}, want: testAMRWant{ FactorKnowledge: false, FactorPossession: true, @@ -51,7 +53,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) { { desc: "WebAuthn", - is: AuthenticationMethodsReferences{WebAuthn: true}, + is: oidc.AuthenticationMethodsReferences{WebAuthn: true}, want: testAMRWant{ FactorKnowledge: false, FactorPossession: true, @@ -65,7 +67,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) { { desc: "WebAuthn User Presence", - is: AuthenticationMethodsReferences{WebAuthnUserPresence: true}, + is: oidc.AuthenticationMethodsReferences{WebAuthnUserPresence: true}, want: testAMRWant{ FactorKnowledge: false, FactorPossession: false, @@ -79,7 +81,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) { { desc: "WebAuthn User Verified", - is: AuthenticationMethodsReferences{WebAuthnUserVerified: true}, + is: oidc.AuthenticationMethodsReferences{WebAuthnUserVerified: true}, want: testAMRWant{ FactorKnowledge: false, FactorPossession: false, @@ -93,7 +95,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) { { desc: "WebAuthn with User Presence and Verified", - is: AuthenticationMethodsReferences{WebAuthn: true, WebAuthnUserVerified: true, WebAuthnUserPresence: true}, + is: oidc.AuthenticationMethodsReferences{WebAuthn: true, WebAuthnUserVerified: true, WebAuthnUserPresence: true}, want: testAMRWant{ FactorKnowledge: false, FactorPossession: true, @@ -107,7 +109,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) { { desc: "Duo", - is: AuthenticationMethodsReferences{Duo: true}, + is: oidc.AuthenticationMethodsReferences{Duo: true}, want: testAMRWant{ FactorKnowledge: false, FactorPossession: true, @@ -121,7 +123,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) { { desc: "Duo WebAuthn TOTP", - is: AuthenticationMethodsReferences{Duo: true, WebAuthn: true, TOTP: true}, + is: oidc.AuthenticationMethodsReferences{Duo: true, WebAuthn: true, TOTP: true}, want: testAMRWant{ FactorKnowledge: false, FactorPossession: true, @@ -135,7 +137,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) { { desc: "Duo TOTP", - is: AuthenticationMethodsReferences{Duo: true, TOTP: true}, + is: oidc.AuthenticationMethodsReferences{Duo: true, TOTP: true}, want: testAMRWant{ FactorKnowledge: false, FactorPossession: true, @@ -149,7 +151,7 @@ func TestAuthenticationMethodsReferences(t *testing.T) { { desc: "Username and Password with Duo", - is: AuthenticationMethodsReferences{Duo: true, UsernameAndPassword: true}, + is: oidc.AuthenticationMethodsReferences{Duo: true, UsernameAndPassword: true}, want: testAMRWant{ FactorKnowledge: true, FactorPossession: true, diff --git a/internal/oidc/client_auth.go b/internal/oidc/authentication.go similarity index 93% rename from internal/oidc/client_auth.go rename to internal/oidc/authentication.go index c852153bc..f656a0eb4 100644 --- a/internal/oidc/client_auth.go +++ b/internal/oidc/authentication.go @@ -10,6 +10,8 @@ import ( "net/url" "time" + "github.com/go-crypt/crypt" + "github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/algorithm/plaintext" "github.com/golang-jwt/jwt/v4" "github.com/ory/fosite" @@ -20,6 +22,46 @@ import ( "github.com/authelia/authelia/v4/internal/configuration/schema" ) +// NewHasher returns a new Hasher. +func NewHasher() (hasher *Hasher, err error) { + hasher = &Hasher{} + + if hasher.decoder, err = crypt.NewDefaultDecoder(); err != nil { + return nil, err + } + + if err = plaintext.RegisterDecoderPlainText(hasher.decoder); err != nil { + return nil, err + } + + return hasher, nil +} + +// Hasher implements the fosite.Hasher interface and adaptively compares hashes. +type Hasher struct { + decoder algorithm.DecoderRegister +} + +// Compare compares the hash with the data and returns an error if they don't match. +func (h Hasher) Compare(_ context.Context, hash, data []byte) (err error) { + var digest algorithm.Digest + + if digest, err = h.decoder.Decode(string(hash)); err != nil { + return err + } + + if digest.MatchBytes(data) { + return nil + } + + return errPasswordsDoNotMatch +} + +// Hash creates a new hash from data. +func (h Hasher) Hash(_ context.Context, data []byte) (hash []byte, err error) { + return data, nil +} + // DefaultClientAuthenticationStrategy is a copy of fosite's with the addition of the client_secret_jwt method and some // minor superficial changes. // diff --git a/internal/handlers/handler_oidc_token_test.go b/internal/oidc/authentication_test.go similarity index 73% rename from internal/handlers/handler_oidc_token_test.go rename to internal/oidc/authentication_test.go index 279cc4e3d..3c666a9db 100644 --- a/internal/handlers/handler_oidc_token_test.go +++ b/internal/oidc/authentication_test.go @@ -1,14 +1,10 @@ -package handlers +package oidc_test import ( "context" - "crypto/ecdsa" - "crypto/rsa" "crypto/sha256" - "crypto/x509" "database/sql" "encoding/base64" - "encoding/pem" "fmt" "io" "net/http" @@ -21,6 +17,8 @@ import ( "github.com/golang/mock/gomock" "github.com/google/uuid" "github.com/ory/fosite" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/valyala/fasthttp" @@ -31,6 +29,58 @@ import ( "github.com/authelia/authelia/v4/internal/oidc" ) +func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) { + hasher, err := oidc.NewHasher() + + require.NoError(t, err) + + a := []byte("$plaintext$abc") + b := []byte("abc") + + ctx := context.Background() + + assert.NoError(t, hasher.Compare(ctx, a, b)) +} + +func TestShouldNotRaiseErrorOnEqualPasswordsPlainTextWithSeparator(t *testing.T) { + hasher, err := oidc.NewHasher() + + require.NoError(t, err) + + a := []byte("$plaintext$abc$123") + b := []byte("abc$123") + + ctx := context.Background() + + assert.NoError(t, hasher.Compare(ctx, a, b)) +} + +func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) { + hasher, err := oidc.NewHasher() + + require.NoError(t, err) + + a := []byte("$plaintext$abc") + b := []byte("abcd") + + ctx := context.Background() + + assert.EqualError(t, hasher.Compare(ctx, a, b), "The provided client secret did not match the registered client secret.") +} + +func TestShouldHashPassword(t *testing.T) { + hasher := oidc.Hasher{} + + data := []byte("abc") + + ctx := context.Background() + + hash, err := hasher.Hash(ctx, data) + + assert.NoError(t, err) + assert.Equal(t, data, hash) +} + func TestClientAuthenticationStrategySuite(t *testing.T) { suite.Run(t, &ClientAuthenticationStrategySuite{}) } @@ -135,7 +185,9 @@ func (s *ClientAuthenticationStrategySuite) GetAssertionRequest(token string) (r } func (s *ClientAuthenticationStrategySuite) GetCtx() oidc.OpenIDConnectContext { - return &oidc.MockOpenIDConnectContext{ + fmt.Println(s.GetIssuerURL()) + + return &MockOpenIDConnectContext{ Context: context.Background(), MockIssuerURL: s.GetIssuerURL(), } @@ -145,13 +197,13 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() { s.ctrl = gomock.NewController(s.T()) s.store = mocks.NewMockStorage(s.ctrl) - secret := MustDecodeSecret("$plaintext$client-secret") + secret := tOpenIDConnectPlainTextClientSecret s.provider = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ - IssuerJWKS: []schema.JWK{}, - IssuerCertificateChain: schema.X509CertificateChain{}, - IssuerPrivateKey: MustParseRSAPrivateKey(exampleRSAPrivateKey), - HMACSecret: "abc123", + IssuerPrivateKeys: []schema.JWK{ + {Key: keyRSA2048, CertificateChain: certRSA2048, Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256}, + }, + HMACSecret: "abc123", Clients: []schema.OpenIDConnectClientConfiguration{ { ID: "hs256", @@ -184,7 +236,7 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() { TokenEndpointAuthSigningAlg: oidc.SigningAlgHMACUsingSHA512, }, { - ID: "rs256", + ID: rs256, Secret: secret, Policy: authorization.OneFactor.String(), RedirectURIs: []string{ @@ -273,6 +325,142 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() { TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, TokenEndpointAuthSigningAlg: oidc.SigningAlgECDSAUsingP521AndSHA512, }, + + { + ID: "rs256k", + Secret: secret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAUsingSHA256, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + Values: []schema.JWK{ + {KeyID: rs256, Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAUsingSHA256, Use: oidc.KeyUseSignature}, + }, + }, + }, + { + ID: "rs384k", + Secret: secret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAUsingSHA384, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + Values: []schema.JWK{ + {KeyID: "rs384", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAUsingSHA384, Use: oidc.KeyUseSignature}, + }, + }, + }, + { + ID: "rs512k", + Secret: secret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAUsingSHA512, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + Values: []schema.JWK{ + {KeyID: "rs512", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAUsingSHA512, Use: oidc.KeyUseSignature}, + }, + }, + }, + { + ID: "ps256k", + Secret: secret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAPSSUsingSHA256, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + Values: []schema.JWK{ + {KeyID: "ps256", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAPSSUsingSHA256, Use: oidc.KeyUseSignature}, + }, + }, + }, + { + ID: "ps384k", + Secret: secret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAPSSUsingSHA384, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + Values: []schema.JWK{ + {KeyID: "ps384", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAPSSUsingSHA384, Use: oidc.KeyUseSignature}, + }, + }, + }, + { + ID: "ps512k", + Secret: secret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgRSAPSSUsingSHA512, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + Values: []schema.JWK{ + {KeyID: "ps512", Key: keyRSA2048.PublicKey, Algorithm: oidc.SigningAlgRSAPSSUsingSHA512, Use: oidc.KeyUseSignature}, + }, + }, + }, + { + ID: "es256k", + Secret: secret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgECDSAUsingP256AndSHA256, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + Values: []schema.JWK{ + {KeyID: "es256", Key: keyECDSAP256.PublicKey, Algorithm: oidc.SigningAlgECDSAUsingP256AndSHA256, Use: oidc.KeyUseSignature}, + }, + }, + }, + { + ID: "es384k", + Secret: secret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgECDSAUsingP384AndSHA384, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + Values: []schema.JWK{ + {KeyID: "es384", Key: keyECDSAP384.PublicKey, Algorithm: oidc.SigningAlgECDSAUsingP384AndSHA384, Use: oidc.KeyUseSignature}, + }, + }, + }, + { + ID: "es512k", + Secret: secret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgECDSAUsingP521AndSHA512, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + Values: []schema.JWK{ + {KeyID: "es512", Key: keyECDSAP521.PublicKey, Algorithm: oidc.SigningAlgECDSAUsingP521AndSHA512, Use: oidc.KeyUseSignature}, + }, + }, + }, { ID: "hs5122", Secret: secret, @@ -471,7 +659,7 @@ func (s *ClientAuthenticationStrategySuite) TestShouldValidateAssertionHS512() { } func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnMismatchedAlg() { - assertion := NewAssertion("rs256", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + assertion := NewAssertion(rs256, s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodHS512, assertion) @@ -507,11 +695,11 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnMismatchedAlgS } func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysRS256() { - assertion := NewAssertion("rs256", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + assertion := NewAssertion(rs256, s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS256, assertion) - token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey)) + token, err := assertionJWT.SignedString(keyRSA2048) s.Require().NoError(err) s.Require().NotEqual("", token) @@ -524,12 +712,100 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe s.Nil(client) } +func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnBadAlgRS256() { + assertion := NewAssertion("rs256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS256, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = rs256 + + token, err := assertionJWT.SignedString(keyRSA2048) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.EqualError(ErrorToRFC6749ErrorTest(err), "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The 'client_assertion' uses signing algorithm 'PS256' but the requested OAuth 2.0 Client enforces signing algorithm 'RS256'.") + s.Nil(client) +} + +func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnBadKidRS256() { + assertion := NewAssertion("rs256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS256, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "nokey" + + token, err := assertionJWT.SignedString(keyRSA2048) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.EqualError(ErrorToRFC6749ErrorTest(err), "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The JSON Web Token uses signing key with kid 'nokey', which could not be found.") + s.Nil(client) +} + +func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnBadTypRS256() { + assertion := NewAssertion("rs256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES256, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = rs256 + + token, err := assertionJWT.SignedString(keyECDSAP256) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.EqualError(ErrorToRFC6749ErrorTest(err), "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The 'client_assertion' uses signing algorithm 'ES256' but the requested OAuth 2.0 Client enforces signing algorithm 'RS256'.") + s.Nil(client) +} + +func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysRS256() { + assertion := NewAssertion("rs256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS256, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = rs256 + + token, err := assertionJWT.SignedString(keyRSA2048) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID))) + + ctx := s.GetCtx() + + gomock.InOrder( + s.store. + EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig). + Return(nil, sql.ErrNoRows), + + s.store. + EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}). + Return(nil), + ) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.NoError(err) + s.Require().NotNil(client) + + s.Equal("rs256k", client.GetID()) +} + func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysRS384() { assertion := NewAssertion("rs384", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS384, assertion) - token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey)) + token, err := assertionJWT.SignedString(keyRSA2048) s.Require().NoError(err) s.Require().NotEqual("", token) @@ -542,12 +818,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe s.Nil(client) } +func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysRS384() { + assertion := NewAssertion("rs384k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS384, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "rs384" + + token, err := assertionJWT.SignedString(keyRSA2048) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID))) + + ctx := s.GetCtx() + + gomock.InOrder( + s.store. + EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig). + Return(nil, sql.ErrNoRows), + + s.store. + EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}). + Return(nil), + ) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.NoError(err) + s.Require().NotNil(client) + + s.Equal("rs384k", client.GetID()) +} + func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysRS512() { assertion := NewAssertion("rs512", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS512, assertion) - token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey)) + token, err := assertionJWT.SignedString(keyRSA2048) s.Require().NoError(err) s.Require().NotEqual("", token) @@ -560,12 +870,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe s.Nil(client) } +func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysRS512() { + assertion := NewAssertion("rs512k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodRS512, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "rs512" + + token, err := assertionJWT.SignedString(keyRSA2048) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID))) + + ctx := s.GetCtx() + + gomock.InOrder( + s.store. + EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig). + Return(nil, sql.ErrNoRows), + + s.store. + EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}). + Return(nil), + ) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.NoError(err) + s.Require().NotNil(client) + + s.Equal("rs512k", client.GetID()) +} + func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysPS256() { assertion := NewAssertion("ps256", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS256, assertion) - token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey)) + token, err := assertionJWT.SignedString(keyRSA2048) s.Require().NoError(err) s.Require().NotEqual("", token) @@ -578,12 +922,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe s.Nil(client) } +func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysPS256() { + assertion := NewAssertion("ps256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS256, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "ps256" + + token, err := assertionJWT.SignedString(keyRSA2048) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID))) + + ctx := s.GetCtx() + + gomock.InOrder( + s.store. + EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig). + Return(nil, sql.ErrNoRows), + + s.store. + EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}). + Return(nil), + ) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.NoError(err) + s.Require().NotNil(client) + + s.Equal("ps256k", client.GetID()) +} + func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysPS384() { assertion := NewAssertion("ps384", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS384, assertion) - token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey)) + token, err := assertionJWT.SignedString(keyRSA2048) s.Require().NoError(err) s.Require().NotEqual("", token) @@ -596,12 +974,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe s.Nil(client) } +func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysPS384() { + assertion := NewAssertion("ps384k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS384, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "ps384" + + token, err := assertionJWT.SignedString(keyRSA2048) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID))) + + ctx := s.GetCtx() + + gomock.InOrder( + s.store. + EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig). + Return(nil, sql.ErrNoRows), + + s.store. + EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}). + Return(nil), + ) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.NoError(err) + s.Require().NotNil(client) + + s.Equal("ps384k", client.GetID()) +} + func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysPS512() { assertion := NewAssertion("ps512", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS512, assertion) - token, err := assertionJWT.SignedString(MustParseRSAPrivateKey(exampleRSAPrivateKey)) + token, err := assertionJWT.SignedString(keyRSA2048) s.Require().NoError(err) s.Require().NotEqual("", token) @@ -614,12 +1026,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe s.Nil(client) } +func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysPS512() { + assertion := NewAssertion("ps512k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodPS512, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "ps512" + + token, err := assertionJWT.SignedString(keyRSA2048) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID))) + + ctx := s.GetCtx() + + gomock.InOrder( + s.store. + EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig). + Return(nil, sql.ErrNoRows), + + s.store. + EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}). + Return(nil), + ) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.NoError(err) + s.Require().NotNil(client) + + s.Equal("ps512k", client.GetID()) +} + func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysES256() { assertion := NewAssertion("es256", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES256, assertion) - token, err := assertionJWT.SignedString(MustParseECPrivateKey(exampleECP256PrivateKey)) + token, err := assertionJWT.SignedString(keyECDSAP256) s.Require().NoError(err) s.Require().NotEqual("", token) @@ -632,12 +1078,47 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe s.Nil(client) } +func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysES256() { + assertion := NewAssertion("es256k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES256, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "es256" + + token, err := assertionJWT.SignedString(keyECDSAP256) + + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID))) + + ctx := s.GetCtx() + + gomock.InOrder( + s.store. + EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig). + Return(nil, sql.ErrNoRows), + + s.store. + EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}). + Return(nil), + ) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.NoError(err) + s.Require().NotNil(client) + + s.Equal("es256k", client.GetID()) +} + func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysES384() { assertion := NewAssertion("es384", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES384, assertion) - token, err := assertionJWT.SignedString(MustParseECPrivateKey(exampleECP384PrivateKey)) + token, err := assertionJWT.SignedString(keyECDSAP384) s.Require().NoError(err) s.Require().NotEqual("", token) @@ -650,12 +1131,46 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe s.Nil(client) } +func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysES384() { + assertion := NewAssertion("es384k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES384, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "es384" + + token, err := assertionJWT.SignedString(keyECDSAP384) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID))) + + ctx := s.GetCtx() + + gomock.InOrder( + s.store. + EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig). + Return(nil, sql.ErrNoRows), + + s.store. + EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}). + Return(nil), + ) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.NoError(err) + s.Require().NotNil(client) + + s.Equal("es384k", client.GetID()) +} + func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKeysES512() { assertion := NewAssertion("es512", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES512, assertion) - token, err := assertionJWT.SignedString(MustParseECPrivateKey(exampleECP521PrivateKey)) + token, err := assertionJWT.SignedString(keyECDSAP521) s.Require().NoError(err) s.Require().NotEqual("", token) @@ -668,6 +1183,40 @@ func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnUnregisteredKe s.Nil(client) } +func (s *ClientAuthenticationStrategySuite) TestShouldAuthKeysES512() { + assertion := NewAssertion("es512k", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) + + assertionJWT := jwt.NewWithClaims(jwt.SigningMethodES512, assertion) + assertionJWT.Header[oidc.JWTHeaderKeyIdentifier] = "es512" + + token, err := assertionJWT.SignedString(keyECDSAP521) + s.Require().NoError(err) + s.Require().NotEqual("", token) + + r := s.GetAssertionRequest(token) + + sig := fmt.Sprintf("%x", sha256.Sum256([]byte(assertion.ID))) + + ctx := s.GetCtx() + + gomock.InOrder( + s.store. + EXPECT().LoadOAuth2BlacklistedJTI(ctx, sig). + Return(nil, sql.ErrNoRows), + + s.store. + EXPECT().SaveOAuth2BlacklistedJTI(ctx, model.OAuth2BlacklistedJTI{Signature: sig, ExpiresAt: assertion.ExpiresAt.Time}). + Return(nil), + ) + + client, err := s.provider.DefaultClientAuthenticationStrategy(s.GetCtx(), r, r.PostForm) + + s.NoError(err) + s.Require().NotNil(client) + + s.Equal("es512k", client.GetID()) +} + func (s *ClientAuthenticationStrategySuite) TestShouldRaiseErrorOnJTIKnown() { assertion := NewAssertion("hs512", s.GetTokenURL(), time.Now().Add(time.Second*-3), time.Unix(time.Now().Add(time.Minute).Unix(), 0)) @@ -1384,126 +1933,3 @@ func NewAssertion(clientID string, tokenURL *url.URL, iat, exp time.Time) Regist }, } } - -type RFC6749ErrorTest struct { - *fosite.RFC6749Error -} - -func (err *RFC6749ErrorTest) Error() string { - return err.WithExposeDebug(true).GetDescription() -} - -func ErrorToRFC6749ErrorTest(err error) (rfc error) { - if err == nil { - return nil - } - - ferr := fosite.ErrorToRFC6749Error(err) - - return &RFC6749ErrorTest{ferr} -} - -func MustDecodeSecret(value string) *schema.PasswordDigest { - if secret, err := schema.DecodePasswordDigest(value); err != nil { - panic(err) - } else { - return secret - } -} - -func MustParseRequestURI(input string) *url.URL { - if requestURI, err := url.ParseRequestURI(input); err != nil { - panic(err) - } else { - return requestURI - } -} - -func MustParseRSAPrivateKey(data string) *rsa.PrivateKey { - block, _ := pem.Decode([]byte(data)) - if block == nil || block.Bytes == nil || len(block.Bytes) == 0 { - panic("not pem encoded") - } - - if block.Type != "RSA PRIVATE KEY" { - panic("not private key") - } - - key, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - panic(err) - } - - return key -} - -func MustParseECPrivateKey(data string) *ecdsa.PrivateKey { - block, _ := pem.Decode([]byte(data)) - if block == nil || block.Bytes == nil || len(block.Bytes) == 0 { - panic("not pem encoded") - } - - if block.Type != "EC PRIVATE KEY" { - panic("not private key") - } - - key, err := x509.ParseECPrivateKey(block.Bytes) - if err != nil { - panic(err) - } - - return key -} - -const exampleRSAPrivateKey = ` ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA60Vuz1N1wUHiCDIlbz8gE0dWPCmHEWnXKchEEISqIJ6j5Eah -Q/GwX3WK0UV5ATRvWhg6o7/WfrLYcAsi4w79TgMjJHLWIY/jzAS3quEtzOLlLSWZ -9FR9SomQm3T/ETOS8IvSGrksIj0WgX35jB1NnbqSTRnYx7Cg/TBJjmiaqd0b9G/8 -LlReaihwGf8tvPgnteWIdon3EI2MKDBkaesRjpL98Cz7VvD7dajseAlUh9jQWVge -sN8qnm8pNPFAYsgxf//Jf0RfsND6H70zKKybDmyct4T4o/8qjivw4ly0XkArDCUj -Qx2KUF7nN+Bo9wwnNppjdnsOPUbus8o1a9vY1QIDAQABAoIBAQDl1SBY3PlN36SF -yScUtCALdUbi4taVxkVxBbioQlFIKHGGkRD9JN/dgSApK6r36FdXNhAi40cQ4nnZ -iqd8FKqTSTFNa/mPM9ee+ITMI8nwOz8SiYcKTndPF2/yzapXDYDgCFcpz/czQ2X2 -/i+IFyA5k4dUVomVGhFLBZ71xW5BvGUBMUH0XkeR5+c4gLvgR209BlpBHlkX4tUQ -+RQoxbKpkntl0mjqf91zcOe4LJVsXZFyN+NVSzLEbGC3lVSSiyjVQH3s7ExnTaHi -PpwSoXzu5QJj5xRit/1B3/LEGpIlPGFrkhMzBDTN+HYV/VLbCHJzjg5GVJawA82E -h2BY6YWJAoGBAPmGaZL5ggnTVR2XVBLDKbwL/sesqiPZk45B+I5eObHl+v236JH9 -RPMjdE10jOR1TzfQdmE2/RboKhiVn+osS+2W6VXSo7sMsSM1bLBPYhnwrNIqzrX8 -Vgi2bCl2S8ZhVo2R8c5WUaD0Gpxs6hwPIMOQWWwxDlsbg/UoLrhD3X4XAoGBAPFg -VSvaWQdDVAqjM42ObhZtWxeLfEAcxRQDMQq7btrTwBZSrtP3S3Egu66cp/4PT4VD -Hc8tYyT2rNETiqT6b2Rm1MgeoJ8wRqte6ZXSQVVQUOd42VG04O3aaleAGhXjEkM2 -avctRdKHDhQdIt+riPgaNj4FdYpmQ5zIrcZtBr/zAoGBAOBXzBX7xMHmwxEe3NUd -qSlMM579C9+9oF/3ymzeJMtgtcBmGHEhoFtmVgvJrV8+ZaIOCFExam2tASQnaqbV -etK7q0ChaNok+CJqxzThupcN/6PaHw4aOJQOx8KjfE95dqNEQ367txqaPk7D0dy2 -cUPDRdLzbC/X1lWV8iNzyPGzAoGBAN4R2epRpYz4Fa7/vWNkAcaib6c2zmaR0YN6 -+Di+ftvW6yfehDhBkWgQTHv2ZtxoK6oYOKmuQUP1qsNkbi8gtTEzJlrDStWKbcom -tVMAsNkT3otHdPEmL7bFNwcvtVAjrF6oBztHrLBnTr2UnMwZnhdczkC7dwuQ0G3D -d5VSI16fAoGAY7eeVDkic73GbZmtZibuodvPJ/z85RIBOrzf3ColO4jGI6Ej/EnD -rMEe/mRC27CJzS9L9Jc0Kt66mGSvodDGl0nBsXGNfPog0cGwweCVN0Eo2VJZbRTT -UoU05/Pvu2h3/E8gGTBY0/WPSo06YUsICjVDWNuOIa/7IY7SyE6Xxn0= ------END RSA PRIVATE KEY-----` - -const exampleECP256PrivateKey = ` ------BEGIN EC PRIVATE KEY----- -MHcCAQEEID1fSsJ8qyEqj2DVkrshaNiXqaSDX7qViASRkyGGJFbEoAoGCCqGSM49 -AwEHoUQDQgAENnBG+bBJIaIa+bRlHaLiXD86RAy+Ef9CVdAfpPGoNRfkOTcrrIV7 -2wv3Y5e0he63Tn9iVAFYRFexK1mjFw7TfA== ------END EC PRIVATE KEY-----` - -const exampleECP384PrivateKey = ` ------BEGIN EC PRIVATE KEY----- -MIGkAgEBBDBPoOfapxtgZ8XNE7Wwdlw+9oDc6x4m57MITZyWzN62jkFUAYsvPJDF -9+g+e8CT5yqgBwYFK4EEACKhZANiAAQ2uZ0HIIxIavyjGyX13tIZVOaRB4+D64dF -s3DXDrpXcuDTSohw9xBW5sLDqRVu2LkBsCUFXtEJUHgC+O7wToNw8nh+KdDrcu/J -miNqbvEHuvlSlHWyx9HH8kAEuu1+SZg= ------END EC PRIVATE KEY-----` - -const exampleECP521PrivateKey = ` ------BEGIN EC PRIVATE KEY----- -MIHcAgEBBEIBT07AnitDd1Z01bl5W5VW8/vTWyu7w3MSqEmCeKcM19p/TAJAeS8L -6UOig2fTUeuMeA2PoOUjI2Bid927VsWcxE2gBwYFK4EEACOhgYkDgYYABAGnV9mu -xY0E7/k8b+glOOMaN0+Qt70H9OmSz6tC8tU3EayRwFlNPch9TlvEpbCS3MsDE9dN -78EpFx45MUqzzdZcOgAu+EUC9Zas1YVK+WMo0GFy+XtFq3kxubOclBb52M/63mcd -zZnA8aAu9iTK9YPfcw1YWTJliNdKUoxmGVV5Ca1W4w== ------END EC PRIVATE KEY-----` diff --git a/internal/oidc/client.go b/internal/oidc/client.go index 64d1f22b3..2950e4ca6 100644 --- a/internal/oidc/client.go +++ b/internal/oidc/client.go @@ -46,12 +46,22 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) { base.ResponseModes = append(base.ResponseModes, fosite.ResponseModeType(mode)) } - if config.TokenEndpointAuthMethod != "" && config.TokenEndpointAuthMethod != "auto" { - client = &FullClient{ + if config.TokenEndpointAuthMethod != "" || config.TokenEndpointAuthSigningAlg != "" || + len(config.PublicKeys.Values) != 0 || config.PublicKeys.URI != nil || config.RequestObjectSigningAlg != "" { + full := &FullClient{ BaseClient: base, TokenEndpointAuthMethod: config.TokenEndpointAuthMethod, TokenEndpointAuthSigningAlgorithm: config.TokenEndpointAuthSigningAlg, + RequestObjectSigningAlgorithm: config.RequestObjectSigningAlg, + + JSONWebKeys: NewPublicJSONWebKeySetFromSchemaJWK(config.PublicKeys.Values), } + + if config.PublicKeys.URI != nil { + full.JSONWebKeysURI = config.PublicKeys.URI.String() + } + + client = full } else { client = base } diff --git a/internal/oidc/client_test.go b/internal/oidc/client_test.go index 814c42a3c..b8c5d3e29 100644 --- a/internal/oidc/client_test.go +++ b/internal/oidc/client_test.go @@ -1,4 +1,4 @@ -package oidc +package oidc_test import ( "fmt" @@ -14,30 +14,31 @@ import ( "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/model" + "github.com/authelia/authelia/v4/internal/oidc" ) func TestNewClient(t *testing.T) { config := schema.OpenIDConnectClientConfiguration{} - client := NewClient(config) + client := oidc.NewClient(config) assert.Equal(t, "", client.GetID()) assert.Equal(t, "", client.GetDescription()) assert.Len(t, client.GetResponseModes(), 0) assert.Len(t, client.GetResponseTypes(), 1) assert.Equal(t, "", client.GetSectorIdentifier()) - bclient, ok := client.(*BaseClient) + bclient, ok := client.(*oidc.BaseClient) require.True(t, ok) assert.Equal(t, "", bclient.UserinfoSigningAlg) - assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg()) + assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg()) - _, ok = client.(*FullClient) + _, ok = client.(*oidc.FullClient) assert.False(t, ok) config = schema.OpenIDConnectClientConfiguration{ ID: myclient, Description: myclientdesc, Policy: twofactor, - Secret: MustDecodeSecret(badsecret), + Secret: tOpenIDConnectPlainTextClientSecret, RedirectURIs: []string{examplecom}, Scopes: schema.DefaultOpenIDConnectClientConfiguration.Scopes, ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes, @@ -45,44 +46,42 @@ func TestNewClient(t *testing.T) { ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes, } - client = NewClient(config) + client = oidc.NewClient(config) assert.Equal(t, myclient, client.GetID()) require.Len(t, client.GetResponseModes(), 1) assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0]) assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy()) config = schema.OpenIDConnectClientConfiguration{ - TokenEndpointAuthMethod: ClientAuthMethodClientSecretPost, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost, } - client = NewClient(config) + client = oidc.NewClient(config) - fclient, ok := client.(*FullClient) - - var niljwks *jose.JSONWebKeySet + fclient, ok := client.(*oidc.FullClient) require.True(t, ok) assert.Equal(t, "", fclient.UserinfoSigningAlg) - assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg()) - assert.Equal(t, SigningAlgNone, fclient.UserinfoSigningAlg) + assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg()) + assert.Equal(t, oidc.SigningAlgNone, fclient.UserinfoSigningAlg) assert.Equal(t, "", fclient.IDTokenSigningAlg) - assert.Equal(t, SigningAlgRSAUsingSHA256, client.GetIDTokenSigningAlg()) - assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.IDTokenSigningAlg) + assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, client.GetIDTokenSigningAlg()) + assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.IDTokenSigningAlg) - assert.Equal(t, ClientAuthMethodClientSecretPost, fclient.TokenEndpointAuthMethod) - assert.Equal(t, ClientAuthMethodClientSecretPost, fclient.GetTokenEndpointAuthMethod()) + assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, fclient.TokenEndpointAuthMethod) + assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, fclient.GetTokenEndpointAuthMethod()) assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm) - assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm()) - assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.TokenEndpointAuthSigningAlgorithm) + assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm()) + assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.TokenEndpointAuthSigningAlgorithm) assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm) assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm()) - fclient.RequestObjectSigningAlgorithm = SigningAlgRSAUsingSHA256 - assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm()) + fclient.RequestObjectSigningAlgorithm = oidc.SigningAlgRSAUsingSHA256 + assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm()) assert.Equal(t, "", fclient.JSONWebKeysURI) assert.Equal(t, "", fclient.GetJSONWebKeysURI()) @@ -90,22 +89,24 @@ func TestNewClient(t *testing.T) { fclient.JSONWebKeysURI = "https://example.com" assert.Equal(t, "https://example.com", fclient.GetJSONWebKeysURI()) + var niljwks *jose.JSONWebKeySet + assert.Equal(t, niljwks, fclient.JSONWebKeys) assert.Equal(t, niljwks, fclient.GetJSONWebKeys()) - assert.Equal(t, ClientConsentMode(0), fclient.Consent.Mode) + assert.Equal(t, oidc.ClientConsentMode(0), fclient.Consent.Mode) assert.Equal(t, time.Second*0, fclient.Consent.Duration) - assert.Equal(t, ClientConsent{Mode: ClientConsentModeExplicit}, fclient.GetConsentPolicy()) + assert.Equal(t, oidc.ClientConsent{Mode: oidc.ClientConsentModeExplicit}, fclient.GetConsentPolicy()) fclient.TokenEndpointAuthMethod = "" fclient.Public = false - assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod()) - assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod) + assert.Equal(t, oidc.ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod()) + assert.Equal(t, oidc.ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod) fclient.TokenEndpointAuthMethod = "" fclient.Public = true - assert.Equal(t, ClientAuthMethodNone, fclient.GetTokenEndpointAuthMethod()) - assert.Equal(t, ClientAuthMethodNone, fclient.TokenEndpointAuthMethod) + assert.Equal(t, oidc.ClientAuthMethodNone, fclient.GetTokenEndpointAuthMethod()) + assert.Equal(t, oidc.ClientAuthMethodNone, fclient.TokenEndpointAuthMethod) assert.Equal(t, []string(nil), fclient.RequestURIs) assert.Equal(t, []string(nil), fclient.GetRequestURIs()) @@ -114,13 +115,13 @@ func TestNewClient(t *testing.T) { func TestBaseClient_ValidatePARPolicy(t *testing.T) { testCases := []struct { name string - client *BaseClient + client *oidc.BaseClient have *fosite.Request expected string }{ { "ShouldNotEnforcePAR", - &BaseClient{ + &oidc.BaseClient{ EnforcePAR: false, }, &fosite.Request{}, @@ -128,36 +129,36 @@ func TestBaseClient_ValidatePARPolicy(t *testing.T) { }, { "ShouldEnforcePARAndErrorWithoutCorrectRequestURI", - &BaseClient{ + &oidc.BaseClient{ EnforcePAR: true, }, &fosite.Request{ Form: map[string][]string{ - FormParameterRequestURI: {"https://google.com"}, + oidc.FormParameterRequestURI: {"https://google.com"}, }, }, "invalid_request", }, { "ShouldEnforcePARAndErrorWithEmptyRequestURI", - &BaseClient{ + &oidc.BaseClient{ EnforcePAR: true, }, &fosite.Request{ Form: map[string][]string{ - FormParameterRequestURI: {""}, + oidc.FormParameterRequestURI: {""}, }, }, "invalid_request", }, { "ShouldEnforcePARAndNotErrorWithCorrectRequestURI", - &BaseClient{ + &oidc.BaseClient{ EnforcePAR: true, }, &fosite.Request{ Form: map[string][]string{ - FormParameterRequestURI: {urnPARPrefix + "abc"}, + oidc.FormParameterRequestURI: {oidc.RedirectURIPrefixPushedAuthorizationRequestURN + "abc"}, }, }, "", @@ -166,7 +167,7 @@ func TestBaseClient_ValidatePARPolicy(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := tc.client.ValidatePARPolicy(tc.have, urnPARPrefix) + err := tc.client.ValidatePARPolicy(tc.have, oidc.RedirectURIPrefixPushedAuthorizationRequestURN) switch tc.expected { case "": @@ -179,7 +180,7 @@ func TestBaseClient_ValidatePARPolicy(t *testing.T) { } func TestIsAuthenticationLevelSufficient(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} c.Policy = authorization.Bypass assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated)) @@ -203,7 +204,7 @@ func TestIsAuthenticationLevelSufficient(t *testing.T) { } func TestClient_GetConsentResponseBody(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} consentRequestBody := c.GetConsentResponseBody(nil) assert.Equal(t, "", consentRequestBody.ClientID) @@ -216,10 +217,10 @@ func TestClient_GetConsentResponseBody(t *testing.T) { consent := &model.OAuth2ConsentSession{ RequestedAudience: []string{examplecom}, - RequestedScopes: []string{ScopeOpenID, ScopeGroups}, + RequestedScopes: []string{oidc.ScopeOpenID, oidc.ScopeGroups}, } - expectedScopes := []string{ScopeOpenID, ScopeGroups} + expectedScopes := []string{oidc.ScopeOpenID, oidc.ScopeGroups} expectedAudiences := []string{examplecom} consentRequestBody = c.GetConsentResponseBody(consent) @@ -230,7 +231,7 @@ func TestClient_GetConsentResponseBody(t *testing.T) { } func TestClient_GetAudience(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} audience := c.GetAudience() assert.Len(t, audience, 0) @@ -243,24 +244,24 @@ func TestClient_GetAudience(t *testing.T) { } func TestClient_GetScopes(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} scopes := c.GetScopes() assert.Len(t, scopes, 0) - c.Scopes = []string{ScopeOpenID} + c.Scopes = []string{oidc.ScopeOpenID} scopes = c.GetScopes() require.Len(t, scopes, 1) - assert.Equal(t, ScopeOpenID, scopes[0]) + assert.Equal(t, oidc.ScopeOpenID, scopes[0]) } func TestClient_GetGrantTypes(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} grantTypes := c.GetGrantTypes() require.Len(t, grantTypes, 1) - assert.Equal(t, GrantTypeAuthorizationCode, grantTypes[0]) + assert.Equal(t, oidc.GrantTypeAuthorizationCode, grantTypes[0]) c.GrantTypes = []string{"device_code"} @@ -270,30 +271,30 @@ func TestClient_GetGrantTypes(t *testing.T) { } func TestClient_Hashing(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} hashedSecret := c.GetHashedSecret() assert.Equal(t, []byte(nil), hashedSecret) - c.Secret = MustDecodeSecret(badsecret) + c.Secret = tOpenIDConnectPlainTextClientSecret - assert.True(t, c.Secret.MatchBytes([]byte("a_bad_secret"))) + assert.True(t, c.Secret.MatchBytes([]byte("client-secret"))) } func TestClient_GetHashedSecret(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} hashedSecret := c.GetHashedSecret() assert.Equal(t, []byte(nil), hashedSecret) - c.Secret = MustDecodeSecret(badsecret) + c.Secret = tOpenIDConnectPlainTextClientSecret hashedSecret = c.GetHashedSecret() - assert.Equal(t, []byte(badsecret), hashedSecret) + assert.Equal(t, []byte("$plaintext$client-secret"), hashedSecret) } func TestClient_GetID(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} id := c.GetID() assert.Equal(t, "", id) @@ -305,7 +306,7 @@ func TestClient_GetID(t *testing.T) { } func TestClient_GetRedirectURIs(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} redirectURIs := c.GetRedirectURIs() require.Len(t, redirectURIs, 0) @@ -318,7 +319,7 @@ func TestClient_GetRedirectURIs(t *testing.T) { } func TestClient_GetResponseModes(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} responseModes := c.GetResponseModes() require.Len(t, responseModes, 0) @@ -337,18 +338,18 @@ func TestClient_GetResponseModes(t *testing.T) { } func TestClient_GetResponseTypes(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} responseTypes := c.GetResponseTypes() require.Len(t, responseTypes, 1) - assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0]) + assert.Equal(t, oidc.ResponseTypeAuthorizationCodeFlow, responseTypes[0]) - c.ResponseTypes = []string{ResponseTypeAuthorizationCodeFlow, ResponseTypeImplicitFlowIDToken} + c.ResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken} responseTypes = c.GetResponseTypes() require.Len(t, responseTypes, 2) - assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0]) - assert.Equal(t, ResponseTypeImplicitFlowIDToken, responseTypes[1]) + assert.Equal(t, oidc.ResponseTypeAuthorizationCodeFlow, responseTypes[0]) + assert.Equal(t, oidc.ResponseTypeImplicitFlowIDToken, responseTypes[1]) } func TestNewClientPKCE(t *testing.T) { @@ -423,7 +424,7 @@ func TestNewClientPKCE(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - client := NewClient(tc.have) + client := oidc.NewClient(tc.have) assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement()) assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement()) @@ -473,14 +474,14 @@ func TestNewClientPAR(t *testing.T) { "ShouldEnforcePARAndErrorOnNonPARRequest", schema.OpenIDConnectClientConfiguration{EnforcePAR: true}, true, - &fosite.Request{Form: map[string][]string{FormParameterRequestURI: {"https://example.com"}}}, + &fosite.Request{Form: map[string][]string{oidc.FormParameterRequestURI: {"https://example.com"}}}, "invalid_request", "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Pushed Authorization Requests are enforced for this client but no such request was sent. The request_uri parameter 'https://example.com' is malformed."}, { "ShouldEnforcePARAndNotErrorOnPARRequest", schema.OpenIDConnectClientConfiguration{EnforcePAR: true}, true, - &fosite.Request{Form: map[string][]string{FormParameterRequestURI: {fmt.Sprintf("%sabc", urnPARPrefix)}}}, + &fosite.Request{Form: map[string][]string{oidc.FormParameterRequestURI: {fmt.Sprintf("%sabc", oidc.RedirectURIPrefixPushedAuthorizationRequestURN)}}}, "", "", }, @@ -488,12 +489,12 @@ func TestNewClientPAR(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - client := NewClient(tc.have) + client := oidc.NewClient(tc.have) assert.Equal(t, tc.expected, client.GetPAREnforcement()) if tc.r != nil { - err := client.ValidatePARPolicy(tc.r, urnPARPrefix) + err := client.ValidatePARPolicy(tc.r, oidc.RedirectURIPrefixPushedAuthorizationRequestURN) if tc.err != "" { require.NotNil(t, err) @@ -518,25 +519,25 @@ func TestNewClientResponseModes(t *testing.T) { }{ { "ShouldEnforceResponseModePolicyAndAllowDefaultModeQuery", - schema.OpenIDConnectClientConfiguration{ResponseModes: []string{ResponseModeQuery}}, + schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeQuery}}, []fosite.ResponseModeType{fosite.ResponseModeQuery}, - &fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: nil}}}, + &fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: nil}}}, "", "", }, { "ShouldEnforceResponseModePolicyAndFailOnDefaultMode", - schema.OpenIDConnectClientConfiguration{ResponseModes: []string{ResponseModeFormPost}}, + schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeFormPost}}, []fosite.ResponseModeType{fosite.ResponseModeFormPost}, - &fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: nil}}}, + &fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: nil}}}, "unsupported_response_mode", "The authorization server does not support obtaining a response using this response mode. The request omitted the response_mode making the default response_mode 'query' based on the other authorization request parameters but registered OAuth 2.0 client doesn't support this response_mode", }, { "ShouldNotEnforceConfiguredResponseMode", - schema.OpenIDConnectClientConfiguration{ResponseModes: []string{ResponseModeFormPost}}, + schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeFormPost}}, []fosite.ResponseModeType{fosite.ResponseModeFormPost}, - &fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeQuery, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: {ResponseModeQuery}}}}, + &fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeQuery, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: {oidc.ResponseModeQuery}}}}, "", "", }, @@ -544,7 +545,7 @@ func TestNewClientResponseModes(t *testing.T) { "ShouldNotEnforceUnconfiguredResponseMode", schema.OpenIDConnectClientConfiguration{ResponseModes: []string{}}, []fosite.ResponseModeType{}, - &fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: {ResponseModeQuery}}}}, + &fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: {oidc.ResponseModeQuery}}}}, "", "", }, @@ -552,7 +553,7 @@ func TestNewClientResponseModes(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - client := NewClient(tc.have) + client := oidc.NewClient(tc.have) assert.Equal(t, tc.expected, client.GetResponseModes()) @@ -572,10 +573,48 @@ func TestNewClientResponseModes(t *testing.T) { } func TestClient_IsPublic(t *testing.T) { - c := &FullClient{BaseClient: &BaseClient{}} + c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}} assert.False(t, c.IsPublic()) c.Public = true assert.True(t, c.IsPublic()) } + +func TestNewClient_JSONWebKeySetURI(t *testing.T) { + var ( + client oidc.Client + clientf *oidc.FullClient + ok bool + ) + + client = oidc.NewClient(schema.OpenIDConnectClientConfiguration{ + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + URI: MustParseRequestURI("https://google.com"), + }, + }) + + require.NotNil(t, client) + + clientf, ok = client.(*oidc.FullClient) + + require.True(t, ok) + + assert.Equal(t, "https://google.com", clientf.GetJSONWebKeysURI()) + + client = oidc.NewClient(schema.OpenIDConnectClientConfiguration{ + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost, + PublicKeys: schema.OpenIDConnectClientPublicKeys{ + URI: nil, + }, + }) + + require.NotNil(t, client) + + clientf, ok = client.(*oidc.FullClient) + + require.True(t, ok) + + assert.Equal(t, "", clientf.GetJSONWebKeysURI()) +} diff --git a/internal/oidc/config.go b/internal/oidc/config.go index 1db4e757a..6dbd29e34 100644 --- a/internal/oidc/config.go +++ b/internal/oidc/config.go @@ -6,7 +6,6 @@ import ( "hash" "html/template" "net/url" - "path" "time" "github.com/hashicorp/go-retryablehttp" @@ -43,7 +42,7 @@ func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.P PAR: PARConfig{ Enforced: config.PAR.Enforce, ContextLifespan: config.PAR.ContextLifespan, - URIPrefix: urnPARPrefix, + URIPrefix: RedirectURIPrefixPushedAuthorizationRequestURN, }, Templates: templates, } @@ -90,9 +89,8 @@ type Config struct { AllowedPrompts []string RefreshTokenScopes []string - HTTPClient *retryablehttp.Client - FormPostHTMLTemplate *template.Template - MessageCatalog i18n.MessageCatalog + HTTPClient *retryablehttp.Client + MessageCatalog i18n.MessageCatalog Templates *templates.Provider } @@ -386,8 +384,8 @@ func (c *Config) GetDisableRefreshTokenValidation(ctx context.Context) (disable // GetAuthorizeCodeLifespan returns the authorization code lifespan. func (c *Config) GetAuthorizeCodeLifespan(ctx context.Context) (lifespan time.Duration) { - if c.Lifespans.AuthorizeCode <= 0 { - c.Lifespans.AccessToken = lifespanAuthorizeCodeDefault + if c.Lifespans.AuthorizeCode.Seconds() <= 0 { + c.Lifespans.AuthorizeCode = lifespanAuthorizeCodeDefault } return c.Lifespans.AuthorizeCode @@ -395,8 +393,8 @@ func (c *Config) GetAuthorizeCodeLifespan(ctx context.Context) (lifespan time.Du // GetRefreshTokenLifespan returns the refresh token lifespan. func (c *Config) GetRefreshTokenLifespan(ctx context.Context) (lifespan time.Duration) { - if c.Lifespans.RefreshToken <= 0 { - c.Lifespans.AccessToken = lifespanRefreshTokenDefault + if c.Lifespans.RefreshToken.Seconds() <= 0 { + c.Lifespans.RefreshToken = lifespanRefreshTokenDefault } return c.Lifespans.RefreshToken @@ -404,8 +402,8 @@ func (c *Config) GetRefreshTokenLifespan(ctx context.Context) (lifespan time.Dur // GetIDTokenLifespan returns the ID token lifespan. func (c *Config) GetIDTokenLifespan(ctx context.Context) (lifespan time.Duration) { - if c.Lifespans.IDToken <= 0 { - c.Lifespans.AccessToken = lifespanTokenDefault + if c.Lifespans.IDToken.Seconds() <= 0 { + c.Lifespans.IDToken = lifespanTokenDefault } return c.Lifespans.IDToken @@ -413,7 +411,7 @@ func (c *Config) GetIDTokenLifespan(ctx context.Context) (lifespan time.Duration // GetAccessTokenLifespan returns the access token lifespan. func (c *Config) GetAccessTokenLifespan(ctx context.Context) (lifespan time.Duration) { - if c.Lifespans.AccessToken <= 0 { + if c.Lifespans.AccessToken.Seconds() <= 0 { c.Lifespans.AccessToken = lifespanTokenDefault } @@ -528,15 +526,13 @@ func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) (tmpl *template.Te // GetTokenURL returns the token URL. func (c *Config) GetTokenURL(ctx context.Context) (tokenURL string) { - if ctx, ok := ctx.(OpenIDConnectContext); ok { - tokenURI, err := ctx.IssuerURL() - if err != nil { + if octx, ok := ctx.(OpenIDConnectContext); ok { + switch issuerURL, err := octx.IssuerURL(); err { + case nil: + return issuerURL.JoinPath(EndpointPathToken).String() + default: return c.TokenURL } - - tokenURI.Path = path.Join(tokenURI.Path, EndpointPathToken) - - return tokenURI.String() } return c.TokenURL @@ -592,7 +588,7 @@ func (c *Config) GetResponseModeHandlerExtension(ctx context.Context) (handler f // usually 'urn:ietf:params:oauth:request_uri:'. func (c *Config) GetPushedAuthorizeRequestURIPrefix(ctx context.Context) string { if c.PAR.URIPrefix == "" { - c.PAR.URIPrefix = urnPARPrefix + c.PAR.URIPrefix = RedirectURIPrefixPushedAuthorizationRequestURN } return c.PAR.URIPrefix @@ -607,7 +603,7 @@ func (c *Config) EnforcePushedAuthorize(ctx context.Context) bool { // GetPushedAuthorizeContextLifespan is the lifespan of the short-lived PAR context. func (c *Config) GetPushedAuthorizeContextLifespan(ctx context.Context) (lifespan time.Duration) { - if c.PAR.ContextLifespan.Seconds() == 0 { + if c.PAR.ContextLifespan.Seconds() <= 0 { c.PAR.ContextLifespan = lifespanPARContextDefault } diff --git a/internal/oidc/config_test.go b/internal/oidc/config_test.go new file mode 100644 index 000000000..192cc6ce5 --- /dev/null +++ b/internal/oidc/config_test.go @@ -0,0 +1,229 @@ +package oidc_test + +import ( + "context" + "fmt" + "net/url" + "testing" + "time" + + "github.com/ory/fosite/token/jwt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/authelia/authelia/v4/internal/oidc" + "github.com/authelia/authelia/v4/internal/templates" +) + +func TestConfig_GetAllowedPrompts(t *testing.T) { + ctx := context.Background() + + config := &oidc.Config{} + + assert.Equal(t, []string(nil), config.AllowedPrompts) + assert.Equal(t, []string{oidc.PromptNone, oidc.PromptLogin, oidc.PromptConsent}, config.GetAllowedPrompts(ctx)) + assert.Equal(t, []string{oidc.PromptNone, oidc.PromptLogin, oidc.PromptConsent}, config.AllowedPrompts) + + config.AllowedPrompts = []string{oidc.PromptNone} + assert.Equal(t, []string{oidc.PromptNone}, config.AllowedPrompts) +} + +func TestConfig_PKCE(t *testing.T) { + ctx := context.Background() + + config := &oidc.Config{} + + assert.False(t, config.GetEnforcePKCE(ctx)) + assert.False(t, config.GetEnforcePKCEForPublicClients(ctx)) + + config.ProofKeyCodeExchange.Enforce = true + assert.True(t, config.GetEnforcePKCE(ctx)) + assert.True(t, config.GetEnforcePKCEForPublicClients(ctx)) + + config.ProofKeyCodeExchange.Enforce = false + + assert.False(t, config.GetEnforcePKCEForPublicClients(ctx)) + + config.ProofKeyCodeExchange.EnforcePublicClients = true + + assert.True(t, config.GetEnforcePKCEForPublicClients(ctx)) + + assert.False(t, config.GetEnablePKCEPlainChallengeMethod(ctx)) + config.ProofKeyCodeExchange.AllowPlainChallengeMethod = true + + assert.True(t, config.GetEnablePKCEPlainChallengeMethod(ctx)) +} + +func TestConfig_GrantTypeJWTBearer(t *testing.T) { + ctx := context.Background() + + config := &oidc.Config{} + assert.False(t, config.GetGrantTypeJWTBearerIDOptional(ctx)) + assert.False(t, config.GetGrantTypeJWTBearerCanSkipClientAuth(ctx)) + assert.False(t, config.GetGrantTypeJWTBearerIssuedDateOptional(ctx)) + + config.GrantTypeJWTBearer.OptionalJTIClaim = true + assert.True(t, config.GetGrantTypeJWTBearerIDOptional(ctx)) + assert.False(t, config.GetGrantTypeJWTBearerCanSkipClientAuth(ctx)) + assert.False(t, config.GetGrantTypeJWTBearerIssuedDateOptional(ctx)) + + config.GrantTypeJWTBearer.OptionalClientAuth = true + assert.True(t, config.GetGrantTypeJWTBearerIDOptional(ctx)) + assert.True(t, config.GetGrantTypeJWTBearerCanSkipClientAuth(ctx)) + assert.False(t, config.GetGrantTypeJWTBearerIssuedDateOptional(ctx)) + + config.GrantTypeJWTBearer.OptionalIssuedDate = true + assert.True(t, config.GetGrantTypeJWTBearerIDOptional(ctx)) + assert.True(t, config.GetGrantTypeJWTBearerCanSkipClientAuth(ctx)) + assert.True(t, config.GetGrantTypeJWTBearerIssuedDateOptional(ctx)) +} + +func TestConfig_Durations(t *testing.T) { + ctx := context.Background() + + config := &oidc.Config{} + assert.Equal(t, time.Duration(0), config.JWTMaxDuration) + assert.Equal(t, time.Hour*24, config.GetJWTMaxDuration(ctx)) + assert.Equal(t, time.Hour*24, config.JWTMaxDuration) + + assert.Equal(t, time.Duration(0), config.Lifespans.IDToken) + assert.Equal(t, time.Hour, config.GetIDTokenLifespan(ctx)) + assert.Equal(t, time.Hour, config.Lifespans.IDToken) + + assert.Equal(t, time.Duration(0), config.Lifespans.AccessToken) + assert.Equal(t, time.Hour, config.GetAccessTokenLifespan(ctx)) + assert.Equal(t, time.Hour, config.Lifespans.AccessToken) + + assert.Equal(t, time.Duration(0), config.Lifespans.RefreshToken) + assert.Equal(t, time.Hour*24*30, config.GetRefreshTokenLifespan(ctx)) + assert.Equal(t, time.Hour*24*30, config.Lifespans.RefreshToken) + + assert.Equal(t, time.Duration(0), config.Lifespans.AuthorizeCode) + assert.Equal(t, time.Minute*15, config.GetAuthorizeCodeLifespan(ctx)) + assert.Equal(t, time.Minute*15, config.Lifespans.AuthorizeCode) +} + +func TestConfig_GetTokenEntropy(t *testing.T) { + ctx := context.Background() + + config := &oidc.Config{} + + assert.Equal(t, 0, config.TokenEntropy) + assert.Equal(t, 32, config.GetTokenEntropy(ctx)) + assert.Equal(t, 32, config.TokenEntropy) +} + +func TestConfig_Misc(t *testing.T) { + ctx := context.Background() + + config := &oidc.Config{} + + assert.False(t, config.DisableRefreshTokenValidation) + assert.False(t, config.GetDisableRefreshTokenValidation(ctx)) + + assert.Equal(t, "", config.Issuers.AccessToken) + assert.Equal(t, "", config.GetAccessTokenIssuer(ctx)) + + assert.Equal(t, "", config.Issuers.IDToken) + assert.Equal(t, "", config.GetIDTokenIssuer(ctx)) + + assert.Equal(t, jwt.JWTScopeFieldUnset, config.JWTScopeField) + assert.Equal(t, jwt.JWTScopeFieldList, config.GetJWTScopeField(ctx)) + assert.Equal(t, jwt.JWTScopeFieldList, config.JWTScopeField) + + assert.Equal(t, []string(nil), config.SanitationWhiteList) + assert.Equal(t, []string(nil), config.GetSanitationWhiteList(ctx)) + assert.Equal(t, []string(nil), config.SanitationWhiteList) + + assert.False(t, config.OmitRedirectScopeParameter) + assert.False(t, config.GetOmitRedirectScopeParam(ctx)) + + assert.NotNil(t, config.GetRedirectSecureChecker(ctx)) + assert.NotNil(t, config.GetHTTPClient(ctx)) + + assert.Nil(t, config.Strategy.Scope) + assert.NotNil(t, config.GetScopeStrategy(ctx)) + assert.NotNil(t, config.Strategy.Scope) + + assert.Nil(t, config.Strategy.Audience) + assert.NotNil(t, config.GetAudienceStrategy(ctx)) + assert.NotNil(t, config.Strategy.Audience) + + assert.Equal(t, []string(nil), config.RefreshTokenScopes) + assert.Equal(t, []string{oidc.ScopeOffline, oidc.ScopeOfflineAccess}, config.GetRefreshTokenScopes(ctx)) + assert.Equal(t, []string{oidc.ScopeOffline, oidc.ScopeOfflineAccess}, config.RefreshTokenScopes) + + assert.Equal(t, 0, config.MinParameterEntropy) + assert.Equal(t, 8, config.GetMinParameterEntropy(ctx)) + assert.Equal(t, 8, config.MinParameterEntropy) + + assert.False(t, config.SendDebugMessagesToClients) + assert.False(t, config.GetSendDebugMessagesToClients(ctx)) + + config.SendDebugMessagesToClients = true + + assert.True(t, config.GetSendDebugMessagesToClients(ctx)) + + assert.Nil(t, config.Strategy.JWKSFetcher) + assert.NotNil(t, config.GetJWKSFetcherStrategy(ctx)) + assert.NotNil(t, config.Strategy.JWKSFetcher) + + assert.Nil(t, config.Strategy.ClientAuthentication) + assert.Nil(t, config.GetClientAuthenticationStrategy(ctx)) + + assert.Nil(t, config.MessageCatalog) + assert.Nil(t, config.GetMessageCatalog(ctx)) + + assert.Nil(t, config.Templates) + assert.Nil(t, config.GetFormPostHTMLTemplate(ctx)) + + var err error + + config.Templates, err = templates.New(templates.Config{}) + require.NoError(t, err) + + assert.NotNil(t, config.GetFormPostHTMLTemplate(ctx)) + assert.NotNil(t, config.Templates) + + assert.False(t, config.GetUseLegacyErrorFormat(ctx)) + + assert.Nil(t, config.GetAuthorizeEndpointHandlers(ctx)) + assert.Nil(t, config.GetTokenEndpointHandlers(ctx)) + assert.Nil(t, config.GetTokenIntrospectionHandlers(ctx)) + assert.Nil(t, config.GetRevocationHandlers(ctx)) + assert.Nil(t, config.GetPushedAuthorizeEndpointHandlers(ctx)) + assert.Nil(t, config.GetResponseModeHandlerExtension(ctx)) + + assert.Equal(t, "", config.GetTokenURL(ctx)) + + octx := &MockOpenIDConnectContext{ + Context: ctx, + IssuerURLFunc: func() (issuerURL *url.URL, err error) { + return nil, fmt.Errorf("test error") + }, + } + + assert.Equal(t, "", config.GetTokenURL(octx)) +} + +func TestConfig_PAR(t *testing.T) { + ctx := context.Background() + + config := &oidc.Config{} + + assert.Equal(t, "", config.PAR.URIPrefix) + assert.Equal(t, "urn:ietf:params:oauth:request_uri:", config.GetPushedAuthorizeRequestURIPrefix(ctx)) + assert.Equal(t, "urn:ietf:params:oauth:request_uri:", config.PAR.URIPrefix) + + assert.False(t, config.PAR.Enforced) + assert.False(t, config.EnforcePushedAuthorize(ctx)) + assert.False(t, config.PAR.Enforced) + + config.PAR.Enforced = true + + assert.True(t, config.EnforcePushedAuthorize(ctx)) + + assert.Equal(t, time.Duration(0), config.PAR.ContextLifespan) + assert.Equal(t, time.Minute*5, config.GetPushedAuthorizeContextLifespan(ctx)) + assert.Equal(t, time.Minute*5, config.PAR.ContextLifespan) +} diff --git a/internal/oidc/const.go b/internal/oidc/const.go index 2143236af..911ce4175 100644 --- a/internal/oidc/const.go +++ b/internal/oidc/const.go @@ -53,7 +53,7 @@ const ( ) const ( - urnPARPrefix = "urn:ietf:params:oauth:request_uri:" + RedirectURIPrefixPushedAuthorizationRequestURN = "urn:ietf:params:oauth:request_uri:" ) const ( @@ -175,9 +175,9 @@ const ( tokenPrefixOrgAutheliaFmt = "authelia_%s_" //nolint:gosec tokenPrefixOrgOryFmt = "ory_%s_" //nolint:gosec - tokenPrefixPartAccessToken = "at" - tokenPrefixPartRefreshToken = "rt" - tokenPrefixPartAuthorizeCode = "ac" + TokenPrefixPartAccessToken = "at" + TokenPrefixPartRefreshToken = "rt" + TokenPrefixPartAuthorizeCode = "ac" ) // Paths. diff --git a/internal/oidc/const_test.go b/internal/oidc/const_test.go index 73441b964..a99279abb 100644 --- a/internal/oidc/const_test.go +++ b/internal/oidc/const_test.go @@ -1,8 +1,7 @@ -package oidc +package oidc_test import ( "crypto/ecdsa" - "crypto/ed25519" "crypto/rsa" "crypto/x509" "fmt" @@ -10,20 +9,26 @@ import ( "os" "strings" + "github.com/ory/fosite" + "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" ) const ( - pathCrypto = "../configuration/test_resources/crypto/%s.%s" - myclient = "myclient" - myclientdesc = "My Client" - onefactor = "one_factor" - twofactor = "two_factor" - examplecom = "https://example.com" - examplecomsid = "example.com" - badsecret = "$plaintext$a_bad_secret" - badhmac = "asbdhaaskmdlkamdklasmdlkams" + pathCrypto = "../configuration/test_resources/crypto/%s.%s" + myclient = "myclient" + myclientdesc = "My Client" + onefactor = "one_factor" + twofactor = "two_factor" + examplecom = "https://example.com" + examplecomsid = "example.com" + badhmac = "asbdhaaskmdlkamdklasmdlkams" + badTokenString = "badTokenString" +) + +const ( + rs256 = "rs256" ) func MustDecodeSecret(value string) *schema.PasswordDigest { @@ -78,28 +83,6 @@ func MustLoadCertificateChain(alg, op string) schema.X509CertificateChain { } } -func MustLoadCertificate(alg, op string) *x509.Certificate { - decoded := MustLoadCrypto(alg, op, "crt") - - cert, ok := decoded.(*x509.Certificate) - if !ok { - panic(fmt.Errorf("the key was not a *x509.Certificate, it's a %T", cert)) - } - - return cert -} - -func MustLoadEd15519PrivateKey(curve string, extra ...string) ed25519.PrivateKey { - decoded := MustLoadCrypto("ED25519", curve, "pem", extra...) - - key, ok := decoded.(ed25519.PrivateKey) - if !ok { - panic(fmt.Errorf("the key was not a ed25519.PrivateKey, it's a %T", key)) - } - - return key -} - func MustLoadECDSAPrivateKey(curve string, extra ...string) *ecdsa.PrivateKey { decoded := MustLoadCrypto("ECDSA", curve, "pem", extra...) @@ -133,6 +116,24 @@ func MustLoadRSAPrivateKey(bits string, extra ...string) *rsa.PrivateKey { return key } +type RFC6749ErrorTest struct { + *fosite.RFC6749Error +} + +func (err *RFC6749ErrorTest) Error() string { + return err.WithExposeDebug(true).GetDescription() +} + +func ErrorToRFC6749ErrorTest(err error) (rfc error) { + if err == nil { + return nil + } + + ferr := fosite.ErrorToRFC6749Error(err) + + return &RFC6749ErrorTest{ferr} +} + var ( tOpenIDConnectPBKDF2ClientSecret, tOpenIDConnectPlainTextClientSecret *schema.PasswordDigest @@ -146,8 +147,8 @@ var ( ) func init() { - tOpenIDConnectPBKDF2ClientSecret = MustDecodeSecret("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng") - tOpenIDConnectPlainTextClientSecret = MustDecodeSecret("$plaintext$example") + tOpenIDConnectPBKDF2ClientSecret = MustDecodeSecret("$pbkdf2-sha512$100000$cfNEo93VkIUIvaXHqetFoQ$O6qFLAlwCMz6.hv9XqUEPnMtrFxODw70T7bmnfTzfNPi3iXbgUEmGiyA6msybOfmj7m3QJS6lLy4DglgJifkKw") + tOpenIDConnectPlainTextClientSecret = MustDecodeSecret("$plaintext$client-secret") keyRSA1024 = MustLoadRSAPrivateKey("1024") keyRSA2048 = MustLoadRSAPrivateKey("2048") diff --git a/internal/oidc/core_strategy_hmac.go b/internal/oidc/core_strategy_hmac.go index 47d52da7a..2ad0f0a3e 100644 --- a/internal/oidc/core_strategy_hmac.go +++ b/internal/oidc/core_strategy_hmac.go @@ -22,22 +22,21 @@ type HMACCoreStrategy struct { } // AccessTokenSignature implements oauth2.AccessTokenStrategy. -func (h *HMACCoreStrategy) AccessTokenSignature(ctx context.Context, token string) string { - return h.Enigma.Signature(token) +func (h *HMACCoreStrategy) AccessTokenSignature(ctx context.Context, tokenString string) string { + return h.Enigma.Signature(tokenString) } // GenerateAccessToken implements oauth2.AccessTokenStrategy. -func (h *HMACCoreStrategy) GenerateAccessToken(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) { - token, sig, err := h.Enigma.Generate(ctx) - if err != nil { +func (h *HMACCoreStrategy) GenerateAccessToken(ctx context.Context, _ fosite.Requester) (tokenString string, sig string, err error) { + if tokenString, sig, err = h.Enigma.Generate(ctx); err != nil { return "", "", err } - return h.setPrefix(token, tokenPrefixPartAccessToken), sig, nil + return h.setPrefix(tokenString, TokenPrefixPartAccessToken), sig, nil } // ValidateAccessToken implements oauth2.AccessTokenStrategy. -func (h *HMACCoreStrategy) ValidateAccessToken(ctx context.Context, r fosite.Requester, token string) (err error) { +func (h *HMACCoreStrategy) ValidateAccessToken(ctx context.Context, r fosite.Requester, tokenString string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.AccessToken) if exp.IsZero() && r.GetRequestedAt().Add(h.Config.GetAccessTokenLifespan(ctx)).Before(time.Now().UTC()) { return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", r.GetRequestedAt().Add(h.Config.GetAccessTokenLifespan(ctx)))) @@ -47,37 +46,36 @@ func (h *HMACCoreStrategy) ValidateAccessToken(ctx context.Context, r fosite.Req return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", exp)) } - return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartAccessToken)) + return h.Enigma.Validate(ctx, h.trimPrefix(tokenString, TokenPrefixPartAccessToken)) } // RefreshTokenSignature implements oauth2.RefreshTokenStrategy. -func (h *HMACCoreStrategy) RefreshTokenSignature(ctx context.Context, token string) string { - return h.Enigma.Signature(token) +func (h *HMACCoreStrategy) RefreshTokenSignature(ctx context.Context, tokenString string) string { + return h.Enigma.Signature(tokenString) } // GenerateRefreshToken implements oauth2.RefreshTokenStrategy. -func (h *HMACCoreStrategy) GenerateRefreshToken(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) { - token, sig, err := h.Enigma.Generate(ctx) - if err != nil { +func (h *HMACCoreStrategy) GenerateRefreshToken(ctx context.Context, _ fosite.Requester) (tokenString string, sig string, err error) { + if tokenString, sig, err = h.Enigma.Generate(ctx); err != nil { return "", "", err } - return h.setPrefix(token, tokenPrefixPartRefreshToken), sig, nil + return h.setPrefix(tokenString, TokenPrefixPartRefreshToken), sig, nil } // ValidateRefreshToken implements oauth2.RefreshTokenStrategy. -func (h *HMACCoreStrategy) ValidateRefreshToken(ctx context.Context, r fosite.Requester, token string) (err error) { +func (h *HMACCoreStrategy) ValidateRefreshToken(ctx context.Context, r fosite.Requester, tokenString string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.RefreshToken) if exp.IsZero() { - return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartRefreshToken)) + return h.Enigma.Validate(ctx, h.trimPrefix(tokenString, TokenPrefixPartRefreshToken)) } if exp.Before(time.Now().UTC()) { return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Refresh token expired at '%s'.", exp)) } - return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartRefreshToken)) + return h.Enigma.Validate(ctx, h.trimPrefix(tokenString, TokenPrefixPartRefreshToken)) } // AuthorizeCodeSignature implements oauth2.AuthorizeCodeStrategy. @@ -86,17 +84,16 @@ func (h *HMACCoreStrategy) AuthorizeCodeSignature(ctx context.Context, token str } // GenerateAuthorizeCode implements oauth2.AuthorizeCodeStrategy. -func (h *HMACCoreStrategy) GenerateAuthorizeCode(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) { - token, sig, err := h.Enigma.Generate(ctx) - if err != nil { +func (h *HMACCoreStrategy) GenerateAuthorizeCode(ctx context.Context, _ fosite.Requester) (tokenString string, sig string, err error) { + if tokenString, sig, err = h.Enigma.Generate(ctx); err != nil { return "", "", err } - return h.setPrefix(token, tokenPrefixPartAuthorizeCode), sig, nil + return h.setPrefix(tokenString, TokenPrefixPartAuthorizeCode), sig, nil } // ValidateAuthorizeCode implements oauth2.AuthorizeCodeStrategy. -func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.Requester, token string) (err error) { +func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.Requester, tokenString string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.AuthorizeCode) if exp.IsZero() && r.GetRequestedAt().Add(h.Config.GetAuthorizeCodeLifespan(ctx)).Before(time.Now().UTC()) { @@ -107,7 +104,7 @@ func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.R return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", exp)) } - return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartAuthorizeCode)) + return h.Enigma.Validate(ctx, h.trimPrefix(tokenString, TokenPrefixPartAuthorizeCode)) } func (h *HMACCoreStrategy) getPrefix(part string) string { @@ -118,14 +115,14 @@ func (h *HMACCoreStrategy) getCustomPrefix(tokenPrefixFmt, part string) string { return fmt.Sprintf(tokenPrefixFmt, part) } -func (h *HMACCoreStrategy) setPrefix(token, part string) string { - return h.getPrefix(part) + token +func (h *HMACCoreStrategy) setPrefix(tokenString, part string) string { + return h.getPrefix(part) + tokenString } -func (h *HMACCoreStrategy) trimPrefix(token, part string) string { - if strings.HasPrefix(token, h.getCustomPrefix(tokenPrefixOrgOryFmt, part)) { - return strings.TrimPrefix(token, h.getCustomPrefix(tokenPrefixOrgOryFmt, part)) +func (h *HMACCoreStrategy) trimPrefix(tokenString, part string) string { + if strings.HasPrefix(tokenString, h.getCustomPrefix(tokenPrefixOrgOryFmt, part)) { + return strings.TrimPrefix(tokenString, h.getCustomPrefix(tokenPrefixOrgOryFmt, part)) } - return strings.TrimPrefix(token, h.getPrefix(part)) + return strings.TrimPrefix(tokenString, h.getPrefix(part)) } diff --git a/internal/oidc/core_strategy_hmac_test.go b/internal/oidc/core_strategy_hmac_blackbox_test.go similarity index 72% rename from internal/oidc/core_strategy_hmac_test.go rename to internal/oidc/core_strategy_hmac_blackbox_test.go index 57dc6a038..7e2b7df92 100644 --- a/internal/oidc/core_strategy_hmac_test.go +++ b/internal/oidc/core_strategy_hmac_blackbox_test.go @@ -1,33 +1,33 @@ -package oidc +package oidc_test import ( "context" - "fmt" "regexp" - "strings" "testing" "time" "github.com/ory/fosite" "github.com/ory/fosite/token/hmac" "github.com/stretchr/testify/assert" + + "github.com/authelia/authelia/v4/internal/oidc" ) func TestHMACStrategy(t *testing.T) { goodsecret := []byte("R7VCSUfnKc7Y5zE84q6GstYqfMGjL4wM") secreta := []byte("a") - config := &Config{ + config := &oidc.Config{ TokenEntropy: 10, GlobalSecret: secreta, - Lifespans: LifespanConfig{ + Lifespans: oidc.LifespanConfig{ AccessToken: time.Hour, RefreshToken: time.Hour, AuthorizeCode: time.Minute, }, } - strategy := &HMACCoreStrategy{ + strategy := &oidc.HMACCoreStrategy{ Enigma: &hmac.HMACStrategy{Config: config}, Config: config, } @@ -87,50 +87,3 @@ func TestHMACStrategy(t *testing.T) { assert.NoError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().Add(100 * time.Hour)}}}, token)) assert.EqualError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().Add(-100 * time.Second)}}}, token), "invalid_token") } - -func TestHMACCoreStrategy_TrimPrefix(t *testing.T) { - testCases := []struct { - name string - have string - part string - expected string - }{ - {"ShouldTrimAutheliaPrefix", "authelia_at_example", tokenPrefixPartAccessToken, "example"}, - {"ShouldTrimOryPrefix", "ory_at_example", tokenPrefixPartAccessToken, "example"}, - {"ShouldTrimOnlyAutheliaPrefix", "authelia_at_ory_at_example", tokenPrefixPartAccessToken, "ory_at_example"}, - {"ShouldTrimOnlyOryPrefix", "ory_at_authelia_at_example", tokenPrefixPartAccessToken, "authelia_at_example"}, - {"ShouldNotTrimGitHubPrefix", "gh_at_example", tokenPrefixPartAccessToken, "gh_at_example"}, - } - - strategy := &HMACCoreStrategy{} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, strategy.trimPrefix(tc.have, tc.part)) - }) - } -} - -func TestHMACCoreStrategy_GetSetPrefix(t *testing.T) { - testCases := []struct { - name string - have string - expectedSet string - expectedGet string - }{ - {"ShouldAddPrefix", "example", "authelia_%s_example", "authelia_%s_"}, - } - - strategy := &HMACCoreStrategy{} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - for _, part := range []string{tokenPrefixPartAccessToken, tokenPrefixPartAuthorizeCode, tokenPrefixPartRefreshToken} { - t.Run(strings.ToUpper(part), func(t *testing.T) { - assert.Equal(t, fmt.Sprintf(tc.expectedSet, part), strategy.setPrefix(tc.have, part)) - assert.Equal(t, fmt.Sprintf(tc.expectedGet, part), strategy.getPrefix(part)) - }) - } - }) - } -} diff --git a/internal/oidc/core_strategy_hmac_whitebox_test.go b/internal/oidc/core_strategy_hmac_whitebox_test.go new file mode 100644 index 000000000..261b22a8e --- /dev/null +++ b/internal/oidc/core_strategy_hmac_whitebox_test.go @@ -0,0 +1,56 @@ +package oidc + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHMACCoreStrategy_TrimPrefix(t *testing.T) { + testCases := []struct { + name string + have string + part string + expected string + }{ + {"ShouldTrimAutheliaPrefix", "authelia_at_example", TokenPrefixPartAccessToken, "example"}, + {"ShouldTrimOryPrefix", "ory_at_example", TokenPrefixPartAccessToken, "example"}, + {"ShouldTrimOnlyAutheliaPrefix", "authelia_at_ory_at_example", TokenPrefixPartAccessToken, "ory_at_example"}, + {"ShouldTrimOnlyOryPrefix", "ory_at_authelia_at_example", TokenPrefixPartAccessToken, "authelia_at_example"}, + {"ShouldNotTrimGitHubPrefix", "gh_at_example", TokenPrefixPartAccessToken, "gh_at_example"}, + } + + strategy := &HMACCoreStrategy{} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, strategy.trimPrefix(tc.have, tc.part)) + }) + } +} + +func TestHMACCoreStrategy_GetSetPrefix(t *testing.T) { + testCases := []struct { + name string + have string + expectedSet string + expectedGet string + }{ + {"ShouldAddPrefix", "example", "authelia_%s_example", "authelia_%s_"}, + } + + strategy := &HMACCoreStrategy{} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + for _, part := range []string{TokenPrefixPartAccessToken, TokenPrefixPartAuthorizeCode, TokenPrefixPartRefreshToken} { + t.Run(strings.ToUpper(part), func(t *testing.T) { + assert.Equal(t, fmt.Sprintf(tc.expectedSet, part), strategy.setPrefix(tc.have, part)) + assert.Equal(t, fmt.Sprintf(tc.expectedGet, part), strategy.getPrefix(part)) + }) + } + }) + } +} diff --git a/internal/oidc/discovery.go b/internal/oidc/discovery.go index 5410e36ad..919a5955a 100644 --- a/internal/oidc/discovery.go +++ b/internal/oidc/discovery.go @@ -66,6 +66,7 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration ClientAuthMethodClientSecretBasic, ClientAuthMethodClientSecretPost, ClientAuthMethodClientSecretJWT, + ClientAuthMethodPrivateKeyJWT, ClientAuthMethodNone, }, TokenEndpointAuthSigningAlgValuesSupported: []string{ @@ -82,6 +83,7 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration ClientAuthMethodClientSecretBasic, ClientAuthMethodClientSecretPost, ClientAuthMethodClientSecretJWT, + ClientAuthMethodPrivateKeyJWT, ClientAuthMethodNone, }, RevocationEndpointAuthSigningAlgValuesSupported: []string{ @@ -104,12 +106,12 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration SigningAlgRSAUsingSHA256, }, UserinfoSigningAlgValuesSupported: []string{ - SigningAlgNone, SigningAlgRSAUsingSHA256, + SigningAlgNone, }, RequestObjectSigningAlgValuesSupported: []string{ - SigningAlgNone, SigningAlgRSAUsingSHA256, + SigningAlgNone, }, }, OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{}, @@ -122,11 +124,7 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration }, } - algs := make([]string, len(c.Discovery.RegisteredJWKSigningAlgs)) - - copy(algs, c.Discovery.RegisteredJWKSigningAlgs) - - for _, alg := range algs { + for _, alg := range c.Discovery.ResponseObjectSigningAlgs { if !utils.IsStringInSlice(alg, config.IDTokenSigningAlgValuesSupported) { config.IDTokenSigningAlgValuesSupported = append(config.IDTokenSigningAlgValuesSupported, alg) } @@ -136,6 +134,20 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration } } + for _, alg := range c.Discovery.RequestObjectSigningAlgs { + if !utils.IsStringInSlice(alg, config.RequestObjectSigningAlgValuesSupported) { + config.RequestObjectSigningAlgValuesSupported = append(config.RequestObjectSigningAlgValuesSupported, alg) + } + + if !utils.IsStringInSlice(alg, config.RevocationEndpointAuthSigningAlgValuesSupported) { + config.RevocationEndpointAuthSigningAlgValuesSupported = append(config.RevocationEndpointAuthSigningAlgValuesSupported, alg) + } + + if !utils.IsStringInSlice(alg, config.TokenEndpointAuthSigningAlgValuesSupported) { + config.TokenEndpointAuthSigningAlgValuesSupported = append(config.TokenEndpointAuthSigningAlgValuesSupported, alg) + } + } + sort.Sort(SortedSigningAlgs(config.IDTokenSigningAlgValuesSupported)) sort.Sort(SortedSigningAlgs(config.UserinfoSigningAlgValuesSupported)) sort.Sort(SortedSigningAlgs(config.RequestObjectSigningAlgValuesSupported)) diff --git a/internal/oidc/discovery_test.go b/internal/oidc/discovery_test.go index b3d880f84..7b410b3b2 100644 --- a/internal/oidc/discovery_test.go +++ b/internal/oidc/discovery_test.go @@ -1,11 +1,14 @@ -package oidc +package oidc_test import ( + "encoding/json" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/authelia/authelia/v4/internal/configuration/schema" + "github.com/authelia/authelia/v4/internal/oidc" ) func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { @@ -13,91 +16,119 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { desc string pkcePlainChallenge bool enforcePAR bool - clients map[string]Client + clients map[string]oidc.Client discovery schema.OpenIDConnectDiscovery - expectCodeChallengeMethodsSupported, expectSubjectTypesSupported, expectedIDTokenSigAlgsSupported, expectedUserInfoSigAlgsSupported []string + expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string + expectedIDTokenSigAlgsSupported, expectedUserInfoSigAlgsSupported []string + + expectedRequestObjectSigAlgsSupported, expectedRevocationSigAlgsSupported, expectedTokenAuthSigAlgsSupported []string }{ { - desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic", - pkcePlainChallenge: false, - clients: map[string]Client{"a": &BaseClient{}}, - expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256}, - expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, - expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256}, - expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone}, + desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic", + pkcePlainChallenge: false, + clients: map[string]oidc.Client{"a": &oidc.BaseClient{}}, + expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256}, + expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise}, + expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256}, + expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, + expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, }, { - desc: "ShouldIncludDiscoveryInfo", + desc: "ShouldIncludeDiscoveryInfo", pkcePlainChallenge: false, - clients: map[string]Client{"a": &BaseClient{}}, + clients: map[string]oidc.Client{"a": &oidc.BaseClient{}}, discovery: schema.OpenIDConnectDiscovery{ - RegisteredJWKSigningAlgs: []string{SigningAlgECDSAUsingP521AndSHA512}, + ResponseObjectSigningAlgs: []string{oidc.SigningAlgECDSAUsingP521AndSHA512}, + RequestObjectSigningAlgs: []string{oidc.SigningAlgECDSAUsingP256AndSHA256}, }, - expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256}, - expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, - expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgECDSAUsingP521AndSHA512}, - expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgECDSAUsingP521AndSHA512, SigningAlgNone}, + expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256}, + expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise}, + expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP521AndSHA512}, + expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgNone}, + expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgNone}, + expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256}, + expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256}, }, { - desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic", - pkcePlainChallenge: true, - clients: map[string]Client{"a": &BaseClient{}}, - expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, - expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, - expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256}, - expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone}, + desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic", + pkcePlainChallenge: true, + clients: map[string]oidc.Client{"a": &oidc.BaseClient{}}, + expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain}, + expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise}, + expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256}, + expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, + expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, }, { - desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise", - pkcePlainChallenge: false, - clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}}, - expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256}, - expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, - expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256}, - expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone}, + desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise", + pkcePlainChallenge: false, + clients: map[string]oidc.Client{"a": &oidc.BaseClient{SectorIdentifier: "yes"}}, + expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256}, + expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise}, + expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256}, + expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, + expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, }, { - desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise", - pkcePlainChallenge: true, - clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}}, - expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, - expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, - expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256}, - expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone}, + desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise", + pkcePlainChallenge: true, + clients: map[string]oidc.Client{"a": &oidc.BaseClient{SectorIdentifier: "yes"}}, + expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain}, + expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise}, + expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256}, + expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, + expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, }, { - desc: "ShouldHaveTokenAuthMethodsNone", - pkcePlainChallenge: true, - clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}}, - expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, - expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, - expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256}, - expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone}, + desc: "ShouldHaveTokenAuthMethodsNone", + pkcePlainChallenge: true, + clients: map[string]oidc.Client{"a": &oidc.BaseClient{SectorIdentifier: "yes"}}, + expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain}, + expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise}, + expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256}, + expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, + expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, }, { desc: "ShouldHaveTokenAuthMethodsNone", pkcePlainChallenge: true, - clients: map[string]Client{ - "a": &BaseClient{SectorIdentifier: "yes"}, - "b": &BaseClient{SectorIdentifier: "yes"}, + clients: map[string]oidc.Client{ + "a": &oidc.BaseClient{SectorIdentifier: "yes"}, + "b": &oidc.BaseClient{SectorIdentifier: "yes"}, }, - expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, - expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, - expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256}, - expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone}, + expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain}, + expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise}, + expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256}, + expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, + expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, }, { desc: "ShouldHaveTokenAuthMethodsNone", pkcePlainChallenge: true, - clients: map[string]Client{ - "a": &BaseClient{SectorIdentifier: "yes"}, - "b": &BaseClient{SectorIdentifier: "yes"}, + clients: map[string]oidc.Client{ + "a": &oidc.BaseClient{SectorIdentifier: "yes"}, + "b": &oidc.BaseClient{SectorIdentifier: "yes"}, }, - expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, - expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, - expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256}, - expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone}, + expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain}, + expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise}, + expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256}, + expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, + expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, + expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}, }, } @@ -111,7 +142,7 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { Discovery: tc.discovery, } - actual := NewOpenIDConnectWellKnownConfiguration(&c) + actual := oidc.NewOpenIDConnectWellKnownConfiguration(&c) for _, codeChallengeMethod := range tc.expectCodeChallengeMethodsSupported { assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod) } @@ -130,6 +161,425 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { assert.Equal(t, tc.expectedUserInfoSigAlgsSupported, actual.UserinfoSigningAlgValuesSupported) assert.Equal(t, tc.expectedIDTokenSigAlgsSupported, actual.IDTokenSigningAlgValuesSupported) + assert.Equal(t, tc.expectedRequestObjectSigAlgsSupported, actual.RequestObjectSigningAlgValuesSupported) + assert.Equal(t, tc.expectedRevocationSigAlgsSupported, actual.RevocationEndpointAuthSigningAlgValuesSupported) + assert.Equal(t, tc.expectedTokenAuthSigAlgsSupported, actual.TokenEndpointAuthSigningAlgValuesSupported) }) } } + +func TestNewOpenIDConnectProviderDiscovery(t *testing.T) { + provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ + IssuerCertificateChain: schema.X509CertificateChain{}, + IssuerPrivateKey: keyRSA2048, + HMACSecret: "asbdhaaskmdlkamdklasmdlkams", + EnablePKCEPlainChallenge: true, + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "a-client", + Secret: tOpenIDConnectPlainTextClientSecret, + Policy: onefactor, + RedirectURIs: []string{ + "https://google.com", + }, + }, + }, + }, nil, nil) + + a := provider.GetOpenIDConnectWellKnownConfiguration("https://auth.example.com") + + data, err := json.Marshal(&a) + assert.NoError(t, err) + + b := oidc.OpenIDConnectWellKnownConfiguration{} + + assert.NoError(t, json.Unmarshal(data, &b)) + + assert.Equal(t, a, b) + + y := provider.GetOAuth2WellKnownConfiguration("https://auth.example.com") + + data, err = json.Marshal(&y) + assert.NoError(t, err) + + z := oidc.OAuth2WellKnownConfiguration{} + + assert.NoError(t, json.Unmarshal(data, &z)) + + assert.Equal(t, y, z) +} + +func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) { + provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ + IssuerCertificateChain: schema.X509CertificateChain{}, + IssuerPrivateKey: keyRSA2048, + HMACSecret: "asbdhaaskmdlkamdklasmdlkams", + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "a-client", + Secret: tOpenIDConnectPlainTextClientSecret, + Policy: onefactor, + RedirectURIs: []string{ + "https://google.com", + }, + }, + }, + }, nil, nil) + + require.NotNil(t, provider) + + disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) + + assert.Equal(t, examplecom, disco.Issuer) + assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI) + assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint) + assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint) + assert.Equal(t, "https://example.com/api/oidc/userinfo", disco.UserinfoEndpoint) + assert.Equal(t, "https://example.com/api/oidc/introspection", disco.IntrospectionEndpoint) + assert.Equal(t, "https://example.com/api/oidc/revocation", disco.RevocationEndpoint) + assert.Equal(t, "", disco.RegistrationEndpoint) + + assert.Len(t, disco.CodeChallengeMethodsSupported, 1) + assert.Contains(t, disco.CodeChallengeMethodsSupported, oidc.PKCEChallengeMethodSHA256) + + assert.Len(t, disco.ScopesSupported, 5) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeOpenID) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeOfflineAccess) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeProfile) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeGroups) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeEmail) + + assert.Len(t, disco.ResponseModesSupported, 3) + assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeFormPost) + assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeQuery) + assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeFragment) + + assert.Len(t, disco.SubjectTypesSupported, 2) + assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePublic) + assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePairwise) + + assert.Len(t, disco.ResponseTypesSupported, 7) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeAuthorizationCodeFlow) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowIDToken) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowToken) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowBoth) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowIDToken) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowToken) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowBoth) + + assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 5) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretPost) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretJWT) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodPrivateKeyJWT) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone) + + assert.Len(t, disco.RevocationEndpointAuthMethodsSupported, 5) + assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic) + assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretPost) + assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretJWT) + assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodPrivateKeyJWT) + assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone) + + assert.Len(t, disco.IntrospectionEndpointAuthMethodsSupported, 2) + assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic) + assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone) + + assert.Len(t, disco.GrantTypesSupported, 3) + assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeAuthorizationCode) + assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeRefreshToken) + assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeImplicit) + + assert.Len(t, disco.RevocationEndpointAuthSigningAlgValuesSupported, 3) + assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[0], oidc.SigningAlgHMACUsingSHA256) + assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[1], oidc.SigningAlgHMACUsingSHA384) + assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[2], oidc.SigningAlgHMACUsingSHA512) + + assert.Len(t, disco.TokenEndpointAuthSigningAlgValuesSupported, 3) + assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[0], oidc.SigningAlgHMACUsingSHA256) + assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[1], oidc.SigningAlgHMACUsingSHA384) + assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[2], oidc.SigningAlgHMACUsingSHA512) + + assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1) + assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, oidc.SigningAlgRSAUsingSHA256) + + assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2) + assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[0], oidc.SigningAlgRSAUsingSHA256) + assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[1], oidc.SigningAlgNone) + + require.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2) + assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, disco.RequestObjectSigningAlgValuesSupported[0]) + assert.Equal(t, oidc.SigningAlgNone, disco.RequestObjectSigningAlgValuesSupported[1]) + + assert.Len(t, disco.ClaimsSupported, 18) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationMethodsReference) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAudience) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthorizedParty) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimClientIdentifier) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimExpirationTime) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimIssuedAt) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimIssuer) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimJWTID) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimRequestedAt) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimSubject) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationTime) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimNonce) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimPreferredEmail) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimEmailVerified) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimEmailAlts) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimGroups) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimPreferredUsername) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimFullName) + + assert.Len(t, disco.PromptValuesSupported, 2) + assert.Contains(t, disco.PromptValuesSupported, oidc.PromptConsent) + assert.Contains(t, disco.PromptValuesSupported, oidc.PromptNone) +} + +func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) { + provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ + IssuerCertificateChain: schema.X509CertificateChain{}, + IssuerPrivateKey: keyRSA2048, + HMACSecret: "asbdhaaskmdlkamdklasmdlkams", + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "a-client", + Secret: tOpenIDConnectPlainTextClientSecret, + Policy: onefactor, + RedirectURIs: []string{ + "https://google.com", + }, + }, + }, + }, nil, nil) + + require.NotNil(t, provider) + + disco := provider.GetOAuth2WellKnownConfiguration(examplecom) + + assert.Equal(t, examplecom, disco.Issuer) + assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI) + assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint) + assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint) + assert.Equal(t, "https://example.com/api/oidc/introspection", disco.IntrospectionEndpoint) + assert.Equal(t, "https://example.com/api/oidc/revocation", disco.RevocationEndpoint) + assert.Equal(t, "", disco.RegistrationEndpoint) + + require.Len(t, disco.CodeChallengeMethodsSupported, 1) + assert.Equal(t, "S256", disco.CodeChallengeMethodsSupported[0]) + + assert.Len(t, disco.ScopesSupported, 5) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeOpenID) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeOfflineAccess) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeProfile) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeGroups) + assert.Contains(t, disco.ScopesSupported, oidc.ScopeEmail) + + assert.Len(t, disco.ResponseModesSupported, 3) + assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeFormPost) + assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeQuery) + assert.Contains(t, disco.ResponseModesSupported, oidc.ResponseModeFragment) + + assert.Len(t, disco.SubjectTypesSupported, 2) + assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePublic) + assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePairwise) + + assert.Len(t, disco.ResponseTypesSupported, 7) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeAuthorizationCodeFlow) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowIDToken) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowToken) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeImplicitFlowBoth) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowIDToken) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowToken) + assert.Contains(t, disco.ResponseTypesSupported, oidc.ResponseTypeHybridFlowBoth) + + assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 5) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretPost) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretJWT) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodPrivateKeyJWT) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone) + + assert.Len(t, disco.GrantTypesSupported, 3) + assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeAuthorizationCode) + assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeRefreshToken) + assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeImplicit) + + assert.Len(t, disco.ClaimsSupported, 18) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationMethodsReference) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAudience) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthorizedParty) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimClientIdentifier) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimExpirationTime) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimIssuedAt) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimIssuer) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimJWTID) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimRequestedAt) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimSubject) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationTime) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimNonce) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimPreferredEmail) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimEmailVerified) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimEmailAlts) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimGroups) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimPreferredUsername) + assert.Contains(t, disco.ClaimsSupported, oidc.ClaimFullName) +} + +func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) { + provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ + IssuerCertificateChain: schema.X509CertificateChain{}, + IssuerPrivateKey: keyRSA2048, + HMACSecret: "asbdhaaskmdlkamdklasmdlkams", + EnablePKCEPlainChallenge: true, + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "a-client", + Secret: tOpenIDConnectPlainTextClientSecret, + Policy: onefactor, + RedirectURIs: []string{ + "https://google.com", + }, + }, + }, + }, nil, nil) + + require.NotNil(t, provider) + + disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) + + require.Len(t, disco.CodeChallengeMethodsSupported, 2) + assert.Equal(t, oidc.PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0]) + assert.Equal(t, oidc.PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1]) +} + +func TestNewOpenIDConnectWellKnownConfiguration_Copy(t *testing.T) { + config := &oidc.OpenIDConnectWellKnownConfiguration{ + OAuth2WellKnownConfiguration: oidc.OAuth2WellKnownConfiguration{ + CommonDiscoveryOptions: oidc.CommonDiscoveryOptions{ + Issuer: "https://example.com", + JWKSURI: "https://example.com/jwks.json", + AuthorizationEndpoint: "", + TokenEndpoint: "", + SubjectTypesSupported: nil, + ResponseTypesSupported: nil, + GrantTypesSupported: nil, + ResponseModesSupported: nil, + ScopesSupported: nil, + ClaimsSupported: nil, + UILocalesSupported: nil, + TokenEndpointAuthMethodsSupported: nil, + TokenEndpointAuthSigningAlgValuesSupported: nil, + ServiceDocumentation: "", + OPPolicyURI: "", + OPTOSURI: "", + SignedMetadata: "", + }, + OAuth2DiscoveryOptions: oidc.OAuth2DiscoveryOptions{ + IntrospectionEndpoint: "", + RevocationEndpoint: "", + RegistrationEndpoint: "", + IntrospectionEndpointAuthMethodsSupported: nil, + RevocationEndpointAuthMethodsSupported: nil, + RevocationEndpointAuthSigningAlgValuesSupported: nil, + IntrospectionEndpointAuthSigningAlgValuesSupported: nil, + CodeChallengeMethodsSupported: nil, + }, + OAuth2DeviceAuthorizationGrantDiscoveryOptions: &oidc.OAuth2DeviceAuthorizationGrantDiscoveryOptions{ + DeviceAuthorizationEndpoint: "", + }, + OAuth2MutualTLSClientAuthenticationDiscoveryOptions: &oidc.OAuth2MutualTLSClientAuthenticationDiscoveryOptions{ + TLSClientCertificateBoundAccessTokens: false, + MutualTLSEndpointAliases: oidc.OAuth2MutualTLSClientAuthenticationAliasesDiscoveryOptions{ + AuthorizationEndpoint: "", + TokenEndpoint: "", + IntrospectionEndpoint: "", + RevocationEndpoint: "", + EndSessionEndpoint: "", + UserinfoEndpoint: "", + BackChannelAuthenticationEndpoint: "", + FederationRegistrationEndpoint: "", + PushedAuthorizationRequestEndpoint: "", + RegistrationEndpoint: "", + }, + }, + OAuth2IssuerIdentificationDiscoveryOptions: &oidc.OAuth2IssuerIdentificationDiscoveryOptions{ + AuthorizationResponseIssuerParameterSupported: false, + }, + OAuth2JWTIntrospectionResponseDiscoveryOptions: &oidc.OAuth2JWTIntrospectionResponseDiscoveryOptions{ + IntrospectionSigningAlgValuesSupported: nil, + IntrospectionEncryptionAlgValuesSupported: nil, + IntrospectionEncryptionEncValuesSupported: nil, + }, + OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions: &oidc.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions{ + RequireSignedRequestObject: false, + }, + OAuth2PushedAuthorizationDiscoveryOptions: &oidc.OAuth2PushedAuthorizationDiscoveryOptions{ + PushedAuthorizationRequestEndpoint: "", + RequirePushedAuthorizationRequests: false, + }, + }, + OpenIDConnectDiscoveryOptions: oidc.OpenIDConnectDiscoveryOptions{ + UserinfoEndpoint: "", + IDTokenSigningAlgValuesSupported: nil, + UserinfoSigningAlgValuesSupported: nil, + RequestObjectSigningAlgValuesSupported: nil, + IDTokenEncryptionAlgValuesSupported: nil, + UserinfoEncryptionAlgValuesSupported: nil, + RequestObjectEncryptionAlgValuesSupported: nil, + IDTokenEncryptionEncValuesSupported: nil, + UserinfoEncryptionEncValuesSupported: nil, + RequestObjectEncryptionEncValuesSupported: nil, + ACRValuesSupported: nil, + DisplayValuesSupported: nil, + ClaimTypesSupported: nil, + ClaimLocalesSupported: nil, + RequestParameterSupported: false, + RequestURIParameterSupported: false, + RequireRequestURIRegistration: false, + ClaimsParameterSupported: false, + }, + OpenIDConnectFrontChannelLogoutDiscoveryOptions: &oidc.OpenIDConnectFrontChannelLogoutDiscoveryOptions{ + FrontChannelLogoutSupported: false, + FrontChannelLogoutSessionSupported: false, + }, + OpenIDConnectBackChannelLogoutDiscoveryOptions: &oidc.OpenIDConnectBackChannelLogoutDiscoveryOptions{ + BackChannelLogoutSupported: false, + BackChannelLogoutSessionSupported: false, + }, + OpenIDConnectSessionManagementDiscoveryOptions: &oidc.OpenIDConnectSessionManagementDiscoveryOptions{ + CheckSessionIFrame: "", + }, + OpenIDConnectRPInitiatedLogoutDiscoveryOptions: &oidc.OpenIDConnectRPInitiatedLogoutDiscoveryOptions{ + EndSessionEndpoint: "", + }, + OpenIDConnectPromptCreateDiscoveryOptions: &oidc.OpenIDConnectPromptCreateDiscoveryOptions{ + PromptValuesSupported: nil, + }, + OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions: &oidc.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions{ + BackChannelAuthenticationEndpoint: "", + BackChannelTokenDeliveryModesSupported: nil, + BackChannelAuthRequestSigningAlgValuesSupported: nil, + BackChannelUserCodeParameterSupported: false, + }, + OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions: &oidc.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions{ + AuthorizationSigningAlgValuesSupported: nil, + AuthorizationEncryptionAlgValuesSupported: nil, + AuthorizationEncryptionEncValuesSupported: nil, + }, + OpenIDFederationDiscoveryOptions: &oidc.OpenIDFederationDiscoveryOptions{ + FederationRegistrationEndpoint: "", + ClientRegistrationTypesSupported: nil, + RequestAuthenticationMethodsSupported: nil, + RequestAuthenticationSigningAlgValuesSupproted: nil, + }, + } + + x := config.Copy() + + assert.Equal(t, config, &x) + + y := config.OAuth2WellKnownConfiguration.Copy() + + assert.Equal(t, config.OAuth2WellKnownConfiguration, y) +} diff --git a/internal/oidc/hasher.go b/internal/oidc/hasher.go deleted file mode 100644 index 1714ec688..000000000 --- a/internal/oidc/hasher.go +++ /dev/null @@ -1,49 +0,0 @@ -package oidc - -import ( - "context" - - "github.com/go-crypt/crypt" - "github.com/go-crypt/crypt/algorithm" - "github.com/go-crypt/crypt/algorithm/plaintext" -) - -// NewHasher returns a new Hasher. -func NewHasher() (hasher *Hasher, err error) { - hasher = &Hasher{} - - if hasher.decoder, err = crypt.NewDefaultDecoder(); err != nil { - return nil, err - } - - if err = plaintext.RegisterDecoderPlainText(hasher.decoder); err != nil { - return nil, err - } - - return hasher, nil -} - -// Hasher implements the fosite.Hasher interface and adaptively compares hashes. -type Hasher struct { - decoder algorithm.DecoderRegister -} - -// Compare compares the hash with the data and returns an error if they don't match. -func (h Hasher) Compare(_ context.Context, hash, data []byte) (err error) { - var digest algorithm.Digest - - if digest, err = h.decoder.Decode(string(hash)); err != nil { - return err - } - - if digest.MatchBytes(data) { - return nil - } - - return errPasswordsDoNotMatch -} - -// Hash creates a new hash from data. -func (h Hasher) Hash(_ context.Context, data []byte) (hash []byte, err error) { - return data, nil -} diff --git a/internal/oidc/hasher_test.go b/internal/oidc/hasher_test.go deleted file mode 100644 index 7f757460b..000000000 --- a/internal/oidc/hasher_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package oidc - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) { - hasher, err := NewHasher() - - require.NoError(t, err) - - a := []byte("$plaintext$abc") - b := []byte("abc") - - ctx := context.Background() - - assert.NoError(t, hasher.Compare(ctx, a, b)) -} - -func TestShouldNotRaiseErrorOnEqualPasswordsPlainTextWithSeparator(t *testing.T) { - hasher, err := NewHasher() - - require.NoError(t, err) - - a := []byte("$plaintext$abc$123") - b := []byte("abc$123") - - ctx := context.Background() - - assert.NoError(t, hasher.Compare(ctx, a, b)) -} - -func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) { - hasher, err := NewHasher() - - require.NoError(t, err) - - a := []byte("$plaintext$abc") - b := []byte("abcd") - - ctx := context.Background() - - assert.Equal(t, errPasswordsDoNotMatch, hasher.Compare(ctx, a, b)) -} - -func TestShouldHashPassword(t *testing.T) { - hasher := Hasher{} - - data := []byte("abc") - - ctx := context.Background() - - hash, err := hasher.Hash(ctx, data) - - assert.NoError(t, err) - assert.Equal(t, data, hash) -} diff --git a/internal/oidc/keys.go b/internal/oidc/keys.go index fa367fd41..51fe0dcdc 100644 --- a/internal/oidc/keys.go +++ b/internal/oidc/keys.go @@ -26,7 +26,7 @@ func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManag algs: map[string]*JWK{}, } - for _, sjwk := range config.IssuerJWKS { + for _, sjwk := range config.IssuerPrivateKeys { jwk := NewJWK(sjwk) manager.kids[sjwk.KeyID] = jwk @@ -47,7 +47,13 @@ type KeyManager struct { algs map[string]*JWK } -func (m *KeyManager) GetKIDFromAlgStrict(ctx context.Context, alg string) (kid string, err error) { +// GetKeyID returns the default key id. +func (m *KeyManager) GetKeyID(ctx context.Context) string { + return m.kid +} + +// GetKeyIDFromAlgStrict returns the key id given an alg or an error if it doesn't exist. +func (m *KeyManager) GetKeyIDFromAlgStrict(ctx context.Context, alg string) (kid string, err error) { if jwks, ok := m.algs[alg]; ok { return jwks.kid, nil } @@ -55,7 +61,8 @@ func (m *KeyManager) GetKIDFromAlgStrict(ctx context.Context, alg string) (kid s return "", fmt.Errorf("alg not found") } -func (m *KeyManager) GetKIDFromAlg(ctx context.Context, alg string) string { +// GetKeyIDFromAlg returns the key id given an alg or the default if it doesn't exist. +func (m *KeyManager) GetKeyIDFromAlg(ctx context.Context, alg string) string { if jwks, ok := m.algs[alg]; ok { return jwks.kid } @@ -63,6 +70,7 @@ func (m *KeyManager) GetKIDFromAlg(ctx context.Context, alg string) string { return m.kid } +// GetByAlg returns the JWK given an alg or nil if it doesn't exist. func (m *KeyManager) GetByAlg(ctx context.Context, alg string) *JWK { if jwk, ok := m.algs[alg]; ok { return jwk @@ -71,6 +79,7 @@ func (m *KeyManager) GetByAlg(ctx context.Context, alg string) *JWK { return nil } +// GetByKID returns the JWK given an key id or nil if it doesn't exist. If given a blank string it returns the default. func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK { if kid == "" { return m.kids[m.kid] @@ -83,6 +92,7 @@ func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK { return nil } +// GetByHeader returns the JWK a JWT header with the appropriate kid value or returns an error. func (m *KeyManager) GetByHeader(ctx context.Context, header fjwt.Mapper) (jwk *JWK, err error) { var ( kid string @@ -104,6 +114,7 @@ func (m *KeyManager) GetByHeader(ctx context.Context, header fjwt.Mapper) (jwk * return jwk, nil } +// GetByTokenString does an invalidated decode of a token to get the header, then calls GetByHeader. func (m *KeyManager) GetByTokenString(ctx context.Context, tokenString string) (jwk *JWK, err error) { var ( token *jwt.Token @@ -116,6 +127,7 @@ func (m *KeyManager) GetByTokenString(ctx context.Context, tokenString string) ( return m.GetByHeader(ctx, &fjwt.Headers{Extra: token.Header}) } +// Set returns the *jose.JSONWebKeySet. func (m *KeyManager) Set(ctx context.Context) *jose.JSONWebKeySet { keys := make([]jose.JSONWebKey, 0, len(m.kids)) @@ -130,6 +142,7 @@ func (m *KeyManager) Set(ctx context.Context) *jose.JSONWebKeySet { } } +// Generate implements the fosite jwt.Signer interface and automatically maps the underlying keys based on the JWK Header kid. func (m *KeyManager) Generate(ctx context.Context, claims fjwt.MapClaims, header fjwt.Mapper) (tokenString string, sig string, err error) { var jwk *JWK @@ -140,6 +153,7 @@ func (m *KeyManager) Generate(ctx context.Context, claims fjwt.MapClaims, header return jwk.Strategy().Generate(ctx, claims, header) } +// Validate implements the fosite jwt.Signer interface and automatically maps the underlying keys based on the JWK Header kid. func (m *KeyManager) Validate(ctx context.Context, tokenString string) (sig string, err error) { var jwk *JWK @@ -150,10 +164,12 @@ func (m *KeyManager) Validate(ctx context.Context, tokenString string) (sig stri return jwk.Strategy().Validate(ctx, tokenString) } +// Hash implements the fosite jwt.Signer interface. func (m *KeyManager) Hash(ctx context.Context, in []byte) (sum []byte, err error) { return m.GetByKID(ctx, "").Strategy().Hash(ctx, in) } +// Decode implements the fosite jwt.Signer interface and automatically maps the underlying keys based on the JWK Header kid. func (m *KeyManager) Decode(ctx context.Context, tokenString string) (token *fjwt.Token, err error) { var jwk *JWK @@ -164,14 +180,58 @@ func (m *KeyManager) Decode(ctx context.Context, tokenString string) (token *fjw return jwk.Strategy().Decode(ctx, tokenString) } +// GetSignature implements the fosite jwt.Signer interface. func (m *KeyManager) GetSignature(ctx context.Context, tokenString string) (sig string, err error) { return getTokenSignature(tokenString) } +// GetSigningMethodLength implements the fosite jwt.Signer interface. func (m *KeyManager) GetSigningMethodLength(ctx context.Context) (size int) { return m.GetByKID(ctx, "").Strategy().GetSigningMethodLength(ctx) } +// NewPublicJSONWebKeySetFromSchemaJWK creates a *jose.JSONWebKeySet from a slice of schema.JWK. +func NewPublicJSONWebKeySetFromSchemaJWK(sjwks []schema.JWK) (jwks *jose.JSONWebKeySet) { + n := len(sjwks) + + if n == 0 { + return nil + } + + keys := make([]jose.JSONWebKey, n) + + for i := 0; i < n; i++ { + jwk := jose.JSONWebKey{ + KeyID: sjwks[i].KeyID, + Algorithm: sjwks[i].Algorithm, + Use: sjwks[i].Use, + Certificates: sjwks[i].CertificateChain.Certificates(), + } + + switch key := sjwks[i].Key.(type) { + case *rsa.PublicKey: + jwk.Key = key + case rsa.PublicKey: + jwk.Key = &key + case *rsa.PrivateKey: + jwk.Key = key.PublicKey + case *ecdsa.PublicKey: + jwk.Key = key + case ecdsa.PublicKey: + jwk.Key = &key + case *ecdsa.PrivateKey: + jwk.Key = key.PublicKey + } + + keys[i] = jwk + } + + return &jose.JSONWebKeySet{ + Keys: keys, + } +} + +// NewJWK creates a *JWK f rom a schema.JWK. func NewJWK(s schema.JWK) (jwk *JWK) { jwk = &JWK{ kid: s.KeyID, @@ -198,6 +258,7 @@ func NewJWK(s schema.JWK) (jwk *JWK) { return jwk } +// JWK is a representation layer over the *jose.JSONWebKey for convenience. type JWK struct { kid string use string @@ -210,16 +271,24 @@ type JWK struct { thumbprint []byte } +// GetSigningMethod returns the jwt.SigningMethod for this *JWK. +func (j *JWK) GetSigningMethod() jwt.SigningMethod { + return j.alg +} + +// GetPrivateKey returns the Private Key for this *JWK. func (j *JWK) GetPrivateKey(ctx context.Context) (any, error) { return j.PrivateJWK(), nil } +// KeyID returns the Key ID for this *JWK. func (j *JWK) KeyID() string { return j.kid } -func (j *JWK) PrivateJWK() (jwk *jose.JSONWebKey) { - return &jose.JSONWebKey{ +// DirectJWK directly returns the *JWK as a jose.JSONWebKey with the private key if appropriate. +func (j *JWK) DirectJWK() (jwk jose.JSONWebKey) { + return jose.JSONWebKey{ Key: j.key, KeyID: j.kid, Algorithm: j.alg.Alg(), @@ -230,10 +299,23 @@ func (j *JWK) PrivateJWK() (jwk *jose.JSONWebKey) { } } -func (j *JWK) JWK() (jwk jose.JSONWebKey) { - return j.PrivateJWK().Public() +// PrivateJWK directly returns the *JWK as a *jose.JSONWebKey with the private key if appropriate. +func (j *JWK) PrivateJWK() (jwk *jose.JSONWebKey) { + value := j.DirectJWK() + + return &value } +// JWK directly returns the *JWK as a jose.JSONWebKey specifically without the private key. +func (j *JWK) JWK() (jwk jose.JSONWebKey) { + if jwk = j.DirectJWK(); jwk.IsPublic() { + return jwk + } + + return jwk.Public() +} + +// Strategy returns the fosite jwt.Signer. func (j *JWK) Strategy() (strategy fjwt.Signer) { return &Signer{ hash: j.hash, @@ -250,6 +332,7 @@ type Signer struct { GetPrivateKey fjwt.GetPrivateKeyFunc } +// GetPublicKey returns the PublicKey for this Signer. func (j *Signer) GetPublicKey(ctx context.Context) (key crypto.PublicKey, err error) { var k any diff --git a/internal/oidc/keys_test.go b/internal/oidc/keys_blackbox_test.go similarity index 60% rename from internal/oidc/keys_test.go rename to internal/oidc/keys_blackbox_test.go index 70c86e418..5bbe43336 100644 --- a/internal/oidc/keys_test.go +++ b/internal/oidc/keys_blackbox_test.go @@ -1,7 +1,8 @@ -package oidc +package oidc_test import ( "context" + "crypto" "encoding/json" "fmt" "testing" @@ -12,6 +13,7 @@ import ( "gopkg.in/square/go-jose.v2" "github.com/authelia/authelia/v4/internal/configuration/schema" + "github.com/authelia/authelia/v4/internal/oidc" ) func TestKeyManager(t *testing.T) { @@ -19,82 +21,82 @@ func TestKeyManager(t *testing.T) { Discovery: schema.OpenIDConnectDiscovery{ DefaultKeyID: "kid-RS256-sig", }, - IssuerJWKS: []schema.JWK{ + IssuerPrivateKeys: []schema.JWK{ { - Use: KeyUseSignature, - Algorithm: SigningAlgRSAUsingSHA256, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA2048, CertificateChain: certRSA2048, }, { - Use: KeyUseSignature, - Algorithm: SigningAlgRSAUsingSHA384, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAUsingSHA384, Key: keyRSA2048, CertificateChain: certRSA2048, }, { - Use: KeyUseSignature, - Algorithm: SigningAlgRSAUsingSHA512, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAUsingSHA512, Key: keyRSA4096, CertificateChain: certRSA4096, }, { - Use: KeyUseSignature, - Algorithm: SigningAlgRSAPSSUsingSHA256, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAPSSUsingSHA256, Key: keyRSA2048, CertificateChain: certRSA2048, }, { - Use: KeyUseSignature, - Algorithm: SigningAlgRSAPSSUsingSHA384, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAPSSUsingSHA384, Key: keyRSA2048, CertificateChain: certRSA2048, }, { - Use: KeyUseSignature, - Algorithm: SigningAlgRSAPSSUsingSHA512, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAPSSUsingSHA512, Key: keyRSA4096, CertificateChain: certRSA4096, }, { - Use: KeyUseSignature, - Algorithm: SigningAlgECDSAUsingP256AndSHA256, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgECDSAUsingP256AndSHA256, Key: keyECDSAP256, CertificateChain: certECDSAP256, }, { - Use: KeyUseSignature, - Algorithm: SigningAlgECDSAUsingP384AndSHA384, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgECDSAUsingP384AndSHA384, Key: keyECDSAP384, CertificateChain: certECDSAP384, }, { - Use: KeyUseSignature, - Algorithm: SigningAlgECDSAUsingP521AndSHA512, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgECDSAUsingP521AndSHA512, Key: keyECDSAP521, CertificateChain: certECDSAP521, }, }, } - for i, key := range config.IssuerJWKS { - config.IssuerJWKS[i].KeyID = fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use) + for i, key := range config.IssuerPrivateKeys { + config.IssuerPrivateKeys[i].KeyID = fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use) } - manager := NewKeyManager(config) + manager := oidc.NewKeyManager(config) assert.NotNil(t, manager) - assert.Len(t, manager.kids, len(config.IssuerJWKS)) - assert.Len(t, manager.algs, len(config.IssuerJWKS)) - - assert.Equal(t, "kid-RS256-sig", manager.kid) - ctx := context.Background() + assert.Equal(t, "kid-RS256-sig", manager.GetKeyID(ctx)) + var ( - jwk *JWK - err error + jwk *oidc.JWK + tokenString, sig string + sum []byte + token *fjwt.Token + err error ) jwk = manager.GetByAlg(ctx, "notalg") @@ -107,7 +109,7 @@ func TestKeyManager(t *testing.T) { assert.NotNil(t, jwk) assert.Equal(t, config.Discovery.DefaultKeyID, jwk.KeyID()) - jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: "notalg"}}) + jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{oidc.JWTHeaderKeyIdentifier: "notalg"}}) assert.EqualError(t, err, "jwt header 'kid' with value 'notalg' does not match a managed jwk") assert.Nil(t, jwk) @@ -119,17 +121,17 @@ func TestKeyManager(t *testing.T) { assert.EqualError(t, err, "jwt header was nil") assert.Nil(t, jwk) - kid, err := manager.GetKIDFromAlgStrict(ctx, "notalg") + kid, err := manager.GetKeyIDFromAlgStrict(ctx, "notalg") assert.EqualError(t, err, "alg not found") assert.Equal(t, "", kid) - kid = manager.GetKIDFromAlg(ctx, "notalg") + kid = manager.GetKeyIDFromAlg(ctx, "notalg") assert.Equal(t, config.Discovery.DefaultKeyID, kid) set := manager.Set(ctx) assert.NotNil(t, set) - assert.Len(t, set.Keys, len(config.IssuerJWKS)) + assert.Len(t, set.Keys, len(config.IssuerPrivateKeys)) data, err := json.Marshal(&set) assert.NoError(t, err) @@ -139,9 +141,32 @@ func TestKeyManager(t *testing.T) { assert.NoError(t, json.Unmarshal(data, &out)) assert.Equal(t, *set, out) - for _, alg := range []string{SigningAlgRSAUsingSHA256, SigningAlgRSAUsingSHA384, SigningAlgRSAPSSUsingSHA512, SigningAlgRSAPSSUsingSHA256, SigningAlgRSAPSSUsingSHA384, SigningAlgRSAPSSUsingSHA512, SigningAlgECDSAUsingP256AndSHA256, SigningAlgECDSAUsingP384AndSHA384, SigningAlgECDSAUsingP521AndSHA512} { + jwk, err = manager.GetByTokenString(ctx, badTokenString) + assert.EqualError(t, err, "token contains an invalid number of segments") + assert.Nil(t, jwk) + + tokenString, sig, err = manager.Generate(ctx, nil, nil) + assert.EqualError(t, err, "error getting jwk from header: jwt header was nil") + assert.Equal(t, "", tokenString) + assert.Equal(t, "", sig) + + sig, err = manager.Validate(ctx, badTokenString) + assert.EqualError(t, err, "error getting jwk from token string: token contains an invalid number of segments") + assert.Equal(t, "", sig) + + token, err = manager.Decode(ctx, badTokenString) + assert.EqualError(t, err, "error getting jwk from token string: token contains an invalid number of segments") + assert.Nil(t, token) + + sum, err = manager.Hash(ctx, []byte("abc")) + assert.NoError(t, err) + assert.Equal(t, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", fmt.Sprintf("%x", sum)) + + assert.Equal(t, crypto.SHA256.Size(), manager.GetSigningMethodLength(ctx)) + + for _, alg := range []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512} { t.Run(alg, func(t *testing.T) { - expectedKID := fmt.Sprintf("kid-%s-%s", alg, KeyUseSignature) + expectedKID := fmt.Sprintf("kid-%s-%s", alg, oidc.KeyUseSignature) t.Run("ShouldGetCorrectKey", func(t *testing.T) { jwk = manager.GetByKID(ctx, expectedKID) @@ -151,17 +176,17 @@ func TestKeyManager(t *testing.T) { jwk = manager.GetByAlg(ctx, alg) assert.NotNil(t, jwk) - assert.Equal(t, alg, jwk.alg.Alg()) + assert.Equal(t, alg, jwk.GetSigningMethod().Alg()) assert.Equal(t, expectedKID, jwk.KeyID()) - kid, err = manager.GetKIDFromAlgStrict(ctx, alg) + kid, err = manager.GetKeyIDFromAlgStrict(ctx, alg) assert.NoError(t, err) assert.Equal(t, expectedKID, kid) - kid = manager.GetKIDFromAlg(ctx, alg) + kid = manager.GetKeyIDFromAlg(ctx, alg) assert.Equal(t, expectedKID, kid) - jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: expectedKID}}) + jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{oidc.JWTHeaderKeyIdentifier: expectedKID}}) assert.NoError(t, err) assert.NotNil(t, jwk) @@ -169,10 +194,9 @@ func TestKeyManager(t *testing.T) { }) t.Run("ShouldUseCorrectSigner", func(t *testing.T) { - var tokenString, sig, sigb string - var token *fjwt.Token + var sigb string - tokenString, sig, err = manager.Generate(ctx, fjwt.MapClaims{}, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: expectedKID}}) + tokenString, sig, err = manager.Generate(ctx, fjwt.MapClaims{}, &fjwt.Headers{Extra: map[string]any{oidc.JWTHeaderKeyIdentifier: expectedKID}}) assert.NoError(t, err) sigb, err = manager.GetSignature(ctx, tokenString) @@ -185,10 +209,9 @@ func TestKeyManager(t *testing.T) { token, err = manager.Decode(ctx, tokenString) assert.NoError(t, err) - assert.Equal(t, expectedKID, token.Header[JWTHeaderKeyIdentifier]) + assert.Equal(t, expectedKID, token.Header[oidc.JWTHeaderKeyIdentifier]) jwk, err = manager.GetByTokenString(ctx, tokenString) - assert.NoError(t, err) sigb, err = jwk.Strategy().Validate(ctx, tokenString) @@ -206,8 +229,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa2048-rs256", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAUsingSHA256, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA2048, CertificateChain: certRSA2048, }, @@ -215,8 +238,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa2048-rs384", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAUsingSHA384, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAUsingSHA384, Key: keyRSA2048, CertificateChain: certRSA2048, }, @@ -224,8 +247,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa2048-rs512", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAUsingSHA512, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAUsingSHA512, Key: keyRSA2048, CertificateChain: certRSA2048, }, @@ -233,8 +256,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa4096-rs256", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAUsingSHA256, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAUsingSHA256, Key: keyRSA4096, CertificateChain: certRSA4096, }, @@ -242,8 +265,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa4096-rs384", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAUsingSHA384, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAUsingSHA384, Key: keyRSA4096, CertificateChain: certRSA4096, }, @@ -251,8 +274,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa4096-rs512", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAUsingSHA512, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAUsingSHA512, Key: keyRSA4096, CertificateChain: certRSA4096, }, @@ -260,8 +283,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa2048-rs256", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAPSSUsingSHA256, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAPSSUsingSHA256, Key: keyRSA2048, CertificateChain: certRSA2048, }, @@ -269,8 +292,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa2048-ps384", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAPSSUsingSHA384, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAPSSUsingSHA384, Key: keyRSA2048, CertificateChain: certRSA2048, }, @@ -278,8 +301,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa2048-ps512", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAPSSUsingSHA512, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAPSSUsingSHA512, Key: keyRSA2048, CertificateChain: certRSA2048, }, @@ -287,8 +310,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa4096-ps256", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAPSSUsingSHA256, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAPSSUsingSHA256, Key: keyRSA4096, CertificateChain: certRSA4096, }, @@ -296,8 +319,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa4096-ps384", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAPSSUsingSHA384, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAPSSUsingSHA384, Key: keyRSA4096, CertificateChain: certRSA4096, }, @@ -305,8 +328,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "rsa4096-ps512", - Use: KeyUseSignature, - Algorithm: SigningAlgRSAPSSUsingSHA512, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgRSAPSSUsingSHA512, Key: keyRSA4096, CertificateChain: certRSA4096, }, @@ -314,8 +337,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "ecdsaP256", - Use: KeyUseSignature, - Algorithm: SigningAlgECDSAUsingP256AndSHA256, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgECDSAUsingP256AndSHA256, Key: keyECDSAP256, CertificateChain: certECDSAP256, }, @@ -323,8 +346,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "ecdsaP384", - Use: KeyUseSignature, - Algorithm: SigningAlgECDSAUsingP384AndSHA384, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgECDSAUsingP384AndSHA384, Key: keyECDSAP384, CertificateChain: certECDSAP384, }, @@ -332,8 +355,8 @@ func TestJWKFunctionality(t *testing.T) { { schema.JWK{ KeyID: "ecdsaP521", - Use: KeyUseSignature, - Algorithm: SigningAlgECDSAUsingP521AndSHA512, + Use: oidc.KeyUseSignature, + Algorithm: oidc.SigningAlgECDSAUsingP521AndSHA512, Key: keyECDSAP521, CertificateChain: certECDSAP521, }, @@ -344,24 +367,28 @@ func TestJWKFunctionality(t *testing.T) { t.Run(tc.have.KeyID, func(t *testing.T) { t.Run("Generating", func(t *testing.T) { var ( - jwk *JWK + jwk *oidc.JWK ) ctx := context.Background() - jwk = NewJWK(tc.have) + jwk = oidc.NewJWK(tc.have) signer := jwk.Strategy() claims := fjwt.MapClaims{} header := &fjwt.Headers{ Extra: map[string]any{ - "kid": jwk.kid, + oidc.JWTHeaderKeyIdentifier: jwk.KeyID(), }, } - tokenString, sig, err := signer.Generate(ctx, claims, header) + tokenString, sig, err := signer.Generate(ctx, nil, nil) + assert.EqualError(t, err, "either claims or header is nil") + assert.Equal(t, "", tokenString) + assert.Equal(t, "", sig) + tokenString, sig, err = signer.Generate(ctx, claims, header) assert.NoError(t, err) assert.NotEqual(t, "", tokenString) assert.NotEqual(t, "", sig) @@ -376,7 +403,7 @@ func TestJWKFunctionality(t *testing.T) { fmt.Println(tokenString) assert.True(t, token.Valid()) - assert.Equal(t, jwk.alg.Alg(), string(token.Method)) + assert.Equal(t, jwk.GetSigningMethod().Alg(), string(token.Method)) sigv, err := signer.Validate(ctx, tokenString) assert.NoError(t, err) @@ -385,19 +412,19 @@ func TestJWKFunctionality(t *testing.T) { t.Run("Marshalling", func(t *testing.T) { var ( - jwk *JWK + jwk *oidc.JWK out jose.JSONWebKey data []byte err error ) - jwk = NewJWK(tc.have) + jwk = oidc.NewJWK(tc.have) strategy := jwk.Strategy() assert.NotNil(t, strategy) - signer, ok := strategy.(*Signer) + signer, ok := strategy.(*oidc.Signer) require.True(t, ok) diff --git a/internal/oidc/keys_whitebox_test.go b/internal/oidc/keys_whitebox_test.go new file mode 100644 index 000000000..00b6607ce --- /dev/null +++ b/internal/oidc/keys_whitebox_test.go @@ -0,0 +1,37 @@ +package oidc + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateToken(t *testing.T) { + sig, err := validateToken("none", nil) + assert.Equal(t, "", sig) + assert.EqualError(t, err, "square/go-jose: compact JWS format must have three parts") +} + +func TestGetTokenSignature(t *testing.T) { + sig, err := getTokenSignature("abc.123") + assert.Equal(t, "", sig) + assert.EqualError(t, err, "header, body and signature must all be set") +} + +func TestAssign(t *testing.T) { + a := map[string]any{ + "a": "valuea", + "c": "valuea", + } + + b := map[string]any{ + "b": "valueb", + "c": "valueb", + } + + c := assign(a, b) + + assert.Equal(t, "valuea", c["a"]) + assert.Equal(t, "valueb", c["b"]) + assert.Equal(t, "valuea", c["c"]) +} diff --git a/internal/oidc/provider_test.go b/internal/oidc/provider_test.go index 335b03ab9..8e17af71e 100644 --- a/internal/oidc/provider_test.go +++ b/internal/oidc/provider_test.go @@ -1,7 +1,6 @@ -package oidc +package oidc_test import ( - "encoding/json" "net/url" "testing" @@ -9,16 +8,17 @@ import ( "github.com/stretchr/testify/require" "github.com/authelia/authelia/v4/internal/configuration/schema" + "github.com/authelia/authelia/v4/internal/oidc" ) func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) { - provider := NewOpenIDConnectProvider(nil, nil, nil) + provider := oidc.NewOpenIDConnectProvider(nil, nil, nil) assert.Nil(t, provider) } func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) { - provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ + provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: keyRSA2048, EnablePKCEPlainChallenge: true, @@ -26,7 +26,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing Clients: []schema.OpenIDConnectClientConfiguration{ { ID: myclient, - Secret: MustDecodeSecret(badsecret), + Secret: tOpenIDConnectPlainTextClientSecret, SectorIdentifier: url.URL{Host: examplecomsid}, Policy: onefactor, RedirectURIs: []string{ @@ -41,23 +41,23 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) assert.Len(t, disco.SubjectTypesSupported, 2) - assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) - assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise) + assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePublic) + assert.Contains(t, disco.SubjectTypesSupported, oidc.SubjectTypePairwise) assert.Len(t, disco.CodeChallengeMethodsSupported, 2) - assert.Contains(t, disco.CodeChallengeMethodsSupported, PKCEChallengeMethodSHA256) - assert.Contains(t, disco.CodeChallengeMethodsSupported, PKCEChallengeMethodSHA256) + assert.Contains(t, disco.CodeChallengeMethodsSupported, oidc.PKCEChallengeMethodSHA256) + assert.Contains(t, disco.CodeChallengeMethodsSupported, oidc.PKCEChallengeMethodSHA256) } func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) { - provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ + provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: keyRSA2048, HMACSecret: badhmac, Clients: []schema.OpenIDConnectClientConfiguration{ { ID: "a-client", - Secret: MustDecodeSecret("$plaintext$a-client-secret"), + Secret: tOpenIDConnectPlainTextClientSecret, Policy: onefactor, RedirectURIs: []string{ "https://google.com", @@ -66,16 +66,16 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes { ID: "b-client", Description: "Normal Description", - Secret: MustDecodeSecret("$plaintext$b-client-secret"), + Secret: tOpenIDConnectPlainTextClientSecret, Policy: twofactor, RedirectURIs: []string{ "https://google.com", }, Scopes: []string{ - ScopeGroups, + oidc.ScopeGroups, }, GrantTypes: []string{ - GrantTypeRefreshToken, + oidc.GrantTypeRefreshToken, }, ResponseTypes: []string{ "token", @@ -87,285 +87,3 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes assert.NotNil(t, provider) } - -func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) { - provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ - IssuerCertificateChain: schema.X509CertificateChain{}, - IssuerPrivateKey: keyRSA2048, - HMACSecret: "asbdhaaskmdlkamdklasmdlkams", - Clients: []schema.OpenIDConnectClientConfiguration{ - { - ID: "a-client", - Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: onefactor, - RedirectURIs: []string{ - "https://google.com", - }, - }, - }, - }, nil, nil) - - require.NotNil(t, provider) - - disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) - - assert.Equal(t, examplecom, disco.Issuer) - assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI) - assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint) - assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint) - assert.Equal(t, "https://example.com/api/oidc/userinfo", disco.UserinfoEndpoint) - assert.Equal(t, "https://example.com/api/oidc/introspection", disco.IntrospectionEndpoint) - assert.Equal(t, "https://example.com/api/oidc/revocation", disco.RevocationEndpoint) - assert.Equal(t, "", disco.RegistrationEndpoint) - - assert.Len(t, disco.CodeChallengeMethodsSupported, 1) - assert.Contains(t, disco.CodeChallengeMethodsSupported, PKCEChallengeMethodSHA256) - - assert.Len(t, disco.ScopesSupported, 5) - assert.Contains(t, disco.ScopesSupported, ScopeOpenID) - assert.Contains(t, disco.ScopesSupported, ScopeOfflineAccess) - assert.Contains(t, disco.ScopesSupported, ScopeProfile) - assert.Contains(t, disco.ScopesSupported, ScopeGroups) - assert.Contains(t, disco.ScopesSupported, ScopeEmail) - - assert.Len(t, disco.ResponseModesSupported, 3) - assert.Contains(t, disco.ResponseModesSupported, ResponseModeFormPost) - assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery) - assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment) - - assert.Len(t, disco.SubjectTypesSupported, 2) - assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) - assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise) - - assert.Len(t, disco.ResponseTypesSupported, 7) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth) - - assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone) - - assert.Len(t, disco.RevocationEndpointAuthMethodsSupported, 4) - assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) - assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost) - assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT) - assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodNone) - - assert.Len(t, disco.IntrospectionEndpointAuthMethodsSupported, 2) - assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) - assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, ClientAuthMethodNone) - - assert.Len(t, disco.GrantTypesSupported, 3) - assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode) - assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken) - assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit) - - assert.Len(t, disco.RevocationEndpointAuthSigningAlgValuesSupported, 3) - assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[0], SigningAlgHMACUsingSHA256) - assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[1], SigningAlgHMACUsingSHA384) - assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[2], SigningAlgHMACUsingSHA512) - - assert.Len(t, disco.TokenEndpointAuthSigningAlgValuesSupported, 3) - assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[0], SigningAlgHMACUsingSHA256) - assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[1], SigningAlgHMACUsingSHA384) - assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[2], SigningAlgHMACUsingSHA512) - - assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1) - assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgRSAUsingSHA256) - - assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2) - assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[0], SigningAlgRSAUsingSHA256) - assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[1], SigningAlgNone) - - require.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2) - assert.Equal(t, SigningAlgRSAUsingSHA256, disco.RequestObjectSigningAlgValuesSupported[0]) - assert.Equal(t, SigningAlgNone, disco.RequestObjectSigningAlgValuesSupported[1]) - - assert.Len(t, disco.ClaimsSupported, 18) - assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference) - assert.Contains(t, disco.ClaimsSupported, ClaimAudience) - assert.Contains(t, disco.ClaimsSupported, ClaimAuthorizedParty) - assert.Contains(t, disco.ClaimsSupported, ClaimClientIdentifier) - assert.Contains(t, disco.ClaimsSupported, ClaimExpirationTime) - assert.Contains(t, disco.ClaimsSupported, ClaimIssuedAt) - assert.Contains(t, disco.ClaimsSupported, ClaimIssuer) - assert.Contains(t, disco.ClaimsSupported, ClaimJWTID) - assert.Contains(t, disco.ClaimsSupported, ClaimRequestedAt) - assert.Contains(t, disco.ClaimsSupported, ClaimSubject) - assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationTime) - assert.Contains(t, disco.ClaimsSupported, ClaimNonce) - assert.Contains(t, disco.ClaimsSupported, ClaimPreferredEmail) - assert.Contains(t, disco.ClaimsSupported, ClaimEmailVerified) - assert.Contains(t, disco.ClaimsSupported, ClaimEmailAlts) - assert.Contains(t, disco.ClaimsSupported, ClaimGroups) - assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername) - assert.Contains(t, disco.ClaimsSupported, ClaimFullName) - - assert.Len(t, disco.PromptValuesSupported, 2) - assert.Contains(t, disco.PromptValuesSupported, PromptConsent) - assert.Contains(t, disco.PromptValuesSupported, PromptNone) -} - -func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) { - provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ - IssuerCertificateChain: schema.X509CertificateChain{}, - IssuerPrivateKey: keyRSA2048, - HMACSecret: "asbdhaaskmdlkamdklasmdlkams", - Clients: []schema.OpenIDConnectClientConfiguration{ - { - ID: "a-client", - Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: onefactor, - RedirectURIs: []string{ - "https://google.com", - }, - }, - }, - }, nil, nil) - - require.NotNil(t, provider) - - disco := provider.GetOAuth2WellKnownConfiguration(examplecom) - - assert.Equal(t, examplecom, disco.Issuer) - assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI) - assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint) - assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint) - assert.Equal(t, "https://example.com/api/oidc/introspection", disco.IntrospectionEndpoint) - assert.Equal(t, "https://example.com/api/oidc/revocation", disco.RevocationEndpoint) - assert.Equal(t, "", disco.RegistrationEndpoint) - - require.Len(t, disco.CodeChallengeMethodsSupported, 1) - assert.Equal(t, "S256", disco.CodeChallengeMethodsSupported[0]) - - assert.Len(t, disco.ScopesSupported, 5) - assert.Contains(t, disco.ScopesSupported, ScopeOpenID) - assert.Contains(t, disco.ScopesSupported, ScopeOfflineAccess) - assert.Contains(t, disco.ScopesSupported, ScopeProfile) - assert.Contains(t, disco.ScopesSupported, ScopeGroups) - assert.Contains(t, disco.ScopesSupported, ScopeEmail) - - assert.Len(t, disco.ResponseModesSupported, 3) - assert.Contains(t, disco.ResponseModesSupported, ResponseModeFormPost) - assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery) - assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment) - - assert.Len(t, disco.SubjectTypesSupported, 2) - assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) - assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise) - - assert.Len(t, disco.ResponseTypesSupported, 7) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken) - assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth) - - assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone) - - assert.Len(t, disco.GrantTypesSupported, 3) - assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode) - assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken) - assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit) - - assert.Len(t, disco.ClaimsSupported, 18) - assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference) - assert.Contains(t, disco.ClaimsSupported, ClaimAudience) - assert.Contains(t, disco.ClaimsSupported, ClaimAuthorizedParty) - assert.Contains(t, disco.ClaimsSupported, ClaimClientIdentifier) - assert.Contains(t, disco.ClaimsSupported, ClaimExpirationTime) - assert.Contains(t, disco.ClaimsSupported, ClaimIssuedAt) - assert.Contains(t, disco.ClaimsSupported, ClaimIssuer) - assert.Contains(t, disco.ClaimsSupported, ClaimJWTID) - assert.Contains(t, disco.ClaimsSupported, ClaimRequestedAt) - assert.Contains(t, disco.ClaimsSupported, ClaimSubject) - assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationTime) - assert.Contains(t, disco.ClaimsSupported, ClaimNonce) - assert.Contains(t, disco.ClaimsSupported, ClaimPreferredEmail) - assert.Contains(t, disco.ClaimsSupported, ClaimEmailVerified) - assert.Contains(t, disco.ClaimsSupported, ClaimEmailAlts) - assert.Contains(t, disco.ClaimsSupported, ClaimGroups) - assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername) - assert.Contains(t, disco.ClaimsSupported, ClaimFullName) -} - -func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) { - provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ - IssuerCertificateChain: schema.X509CertificateChain{}, - IssuerPrivateKey: keyRSA2048, - HMACSecret: "asbdhaaskmdlkamdklasmdlkams", - EnablePKCEPlainChallenge: true, - Clients: []schema.OpenIDConnectClientConfiguration{ - { - ID: "a-client", - Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: onefactor, - RedirectURIs: []string{ - "https://google.com", - }, - }, - }, - }, nil, nil) - - require.NotNil(t, provider) - - disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) - - require.Len(t, disco.CodeChallengeMethodsSupported, 2) - assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0]) - assert.Equal(t, PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1]) -} - -func TestNewOpenIDConnectProviderDiscovery(t *testing.T) { - provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ - IssuerCertificateChain: schema.X509CertificateChain{}, - IssuerPrivateKey: keyRSA2048, - HMACSecret: "asbdhaaskmdlkamdklasmdlkams", - EnablePKCEPlainChallenge: true, - Clients: []schema.OpenIDConnectClientConfiguration{ - { - ID: "a-client", - Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: onefactor, - RedirectURIs: []string{ - "https://google.com", - }, - }, - }, - }, nil, nil) - - a := provider.GetOpenIDConnectWellKnownConfiguration("https://auth.example.com") - - data, err := json.Marshal(&a) - assert.NoError(t, err) - - b := OpenIDConnectWellKnownConfiguration{} - - assert.NoError(t, json.Unmarshal(data, &b)) - - assert.Equal(t, a, b) - - y := provider.GetOAuth2WellKnownConfiguration("https://auth.example.com") - - data, err = json.Marshal(&y) - assert.NoError(t, err) - - z := OAuth2WellKnownConfiguration{} - - assert.NoError(t, json.Unmarshal(data, &z)) - - assert.Equal(t, y, z) -} diff --git a/internal/oidc/store.go b/internal/oidc/store.go index 2f21b368c..23a591ea0 100644 --- a/internal/oidc/store.go +++ b/internal/oidc/store.go @@ -235,7 +235,7 @@ func (s *Store) CreatePKCERequestSession(ctx context.Context, signature string, // DeletePKCERequestSession marks the authorization request for a given PKCE request as deleted. // This implements a portion of pkce.PKCERequestStorage. func (s *Store) DeletePKCERequestSession(ctx context.Context, signature string) (err error) { - return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeAccessToken, signature) + return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypePKCEChallenge, signature) } // GetPKCERequestSession gets the authorization request for a given PKCE request. @@ -254,7 +254,7 @@ func (s *Store) CreateOpenIDConnectSession(ctx context.Context, authorizeCode st // DeleteOpenIDConnectSession just implements the method required by fosite even though it's unused. // This implements a portion of openid.OpenIDConnectRequestStorage. func (s *Store) DeleteOpenIDConnectSession(ctx context.Context, authorizeCode string) (err error) { - return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeAccessToken, authorizeCode) + return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeOpenIDConnect, authorizeCode) } // GetOpenIDConnectSession returns error: diff --git a/internal/oidc/store_test.go b/internal/oidc/store_test.go index bdd5f4d69..f6eb95818 100644 --- a/internal/oidc/store_test.go +++ b/internal/oidc/store_test.go @@ -1,19 +1,30 @@ -package oidc +package oidc_test import ( "context" + "database/sql" + "encoding/json" + "fmt" "testing" + "time" + "github.com/golang/mock/gomock" + "github.com/google/uuid" "github.com/ory/fosite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/configuration/schema" + "github.com/authelia/authelia/v4/internal/mocks" + "github.com/authelia/authelia/v4/internal/model" + "github.com/authelia/authelia/v4/internal/oidc" + "github.com/authelia/authelia/v4/internal/storage" ) func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) { - s := NewStore(&schema.OpenIDConnectConfiguration{ + s := oidc.NewStore(&schema.OpenIDConnectConfiguration{ IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: keyRSA2048, Clients: []schema.OpenIDConnectClientConfiguration{ @@ -21,15 +32,15 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) { ID: myclient, Description: myclientdesc, Policy: onefactor, - Scopes: []string{ScopeOpenID, ScopeProfile}, - Secret: MustDecodeSecret("$plaintext$mysecret"), + Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile}, + Secret: tOpenIDConnectPlainTextClientSecret, }, { ID: "myotherclient", Description: myclientdesc, Policy: twofactor, - Scopes: []string{ScopeOpenID, ScopeProfile}, - Secret: MustDecodeSecret("$plaintext$mysecret"), + Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile}, + Secret: tOpenIDConnectPlainTextClientSecret, }, }, }, nil) @@ -45,7 +56,7 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) { } func TestOpenIDConnectStore_GetInternalClient(t *testing.T) { - s := NewStore(&schema.OpenIDConnectConfiguration{ + s := oidc.NewStore(&schema.OpenIDConnectConfiguration{ IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: keyRSA2048, Clients: []schema.OpenIDConnectClientConfiguration{ @@ -53,8 +64,8 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) { ID: myclient, Description: myclientdesc, Policy: onefactor, - Scopes: []string{ScopeOpenID, ScopeProfile}, - Secret: MustDecodeSecret("$plaintext$mysecret"), + Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile}, + Secret: tOpenIDConnectPlainTextClientSecret, }, }, }, nil) @@ -76,11 +87,11 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) { ID: id, Description: myclientdesc, Policy: onefactor, - Scopes: []string{ScopeOpenID, ScopeProfile}, - Secret: MustDecodeSecret("$plaintext$mysecret"), + Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile}, + Secret: tOpenIDConnectPlainTextClientSecret, } - s := NewStore(&schema.OpenIDConnectConfiguration{ + s := oidc.NewStore(&schema.OpenIDConnectConfiguration{ IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: keyRSA2048, Clients: []schema.OpenIDConnectClientConfiguration{c1}, @@ -92,11 +103,11 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) { assert.Equal(t, id, client.GetID()) assert.Equal(t, myclientdesc, client.GetDescription()) assert.Equal(t, fosite.Arguments(c1.Scopes), client.GetScopes()) - assert.Equal(t, fosite.Arguments([]string{GrantTypeAuthorizationCode}), client.GetGrantTypes()) - assert.Equal(t, fosite.Arguments([]string{ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes()) + assert.Equal(t, fosite.Arguments([]string{oidc.GrantTypeAuthorizationCode}), client.GetGrantTypes()) + assert.Equal(t, fosite.Arguments([]string{oidc.ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes()) assert.Equal(t, []string(nil), client.GetRedirectURIs()) assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy()) - assert.Equal(t, "$plaintext$mysecret", client.GetSecret().Encode()) + assert.Equal(t, "$plaintext$client-secret", client.GetSecret().Encode()) } func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) { @@ -104,11 +115,11 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) { ID: myclient, Description: myclientdesc, Policy: onefactor, - Scopes: []string{ScopeOpenID, ScopeProfile}, - Secret: MustDecodeSecret("$plaintext$mysecret"), + Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile}, + Secret: tOpenIDConnectPlainTextClientSecret, } - s := NewStore(&schema.OpenIDConnectConfiguration{ + s := oidc.NewStore(&schema.OpenIDConnectConfiguration{ IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: keyRSA2048, Clients: []schema.OpenIDConnectClientConfiguration{c1}, @@ -120,7 +131,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) { } func TestOpenIDConnectStore_IsValidClientID(t *testing.T) { - s := NewStore(&schema.OpenIDConnectConfiguration{ + s := oidc.NewStore(&schema.OpenIDConnectConfiguration{ IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: keyRSA2048, Clients: []schema.OpenIDConnectClientConfiguration{ @@ -128,8 +139,8 @@ func TestOpenIDConnectStore_IsValidClientID(t *testing.T) { ID: myclient, Description: myclientdesc, Policy: onefactor, - Scopes: []string{ScopeOpenID, ScopeProfile}, - Secret: MustDecodeSecret("$plaintext$mysecret"), + Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile}, + Secret: tOpenIDConnectPlainTextClientSecret, }, }, }, nil) @@ -140,3 +151,516 @@ func TestOpenIDConnectStore_IsValidClientID(t *testing.T) { assert.True(t, validClient) assert.False(t, invalidClient) } + +func TestStoreSuite(t *testing.T) { + suite.Run(t, &StoreSuite{}) +} + +type StoreSuite struct { + suite.Suite + + ctx context.Context + ctrl *gomock.Controller + mock *mocks.MockStorage + store *oidc.Store +} + +func (s *StoreSuite) SetupTest() { + s.ctx = context.Background() + s.ctrl = gomock.NewController(s.T()) + s.mock = mocks.NewMockStorage(s.ctrl) + s.store = oidc.NewStore(&schema.OpenIDConnectConfiguration{ + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "hs256", + Secret: tOpenIDConnectPBKDF2ClientSecret, + Policy: authorization.OneFactor.String(), + RedirectURIs: []string{ + "https://client.example.com", + }, + TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT, + TokenEndpointAuthSigningAlg: oidc.SigningAlgHMACUsingSHA256, + }, + }}, s.mock) +} + +func (s *StoreSuite) TestGetSubject() { + s.T().Run("GenerateNew", func(t *testing.T) { + s.mock. + EXPECT(). + LoadUserOpaqueIdentifierBySignature(s.ctx, "openid", "", "john"). + Return(nil, nil) + + s.mock. + EXPECT(). + SaveUserOpaqueIdentifier(s.ctx, gomock.Any()). + Return(nil) + + opaqueID, err := s.store.GetSubject(s.ctx, "", "john") + + assert.NoError(t, err) + assert.NotEqual(t, uint32(0), opaqueID) + }) + + s.T().Run("ReturnDatabaseErrorOnLoad", func(t *testing.T) { + s.mock. + EXPECT(). + LoadUserOpaqueIdentifierBySignature(s.ctx, "openid", "", "john"). + Return(nil, fmt.Errorf("failed to load")) + + opaqueID, err := s.store.GetSubject(s.ctx, "", "john") + + assert.EqualError(t, err, "failed to load") + assert.Equal(t, uint32(0), opaqueID.ID()) + }) + + s.T().Run("ReturnDatabaseErrorOnSave", func(t *testing.T) { + s.mock. + EXPECT(). + LoadUserOpaqueIdentifierBySignature(s.ctx, "openid", "", "john"). + Return(nil, nil) + + s.mock. + EXPECT(). + SaveUserOpaqueIdentifier(s.ctx, gomock.Any()). + Return(fmt.Errorf("failed to save")) + + opaqueID, err := s.store.GetSubject(s.ctx, "", "john") + + assert.EqualError(t, err, "failed to save") + assert.Equal(t, uint32(0), opaqueID.ID()) + }) +} + +func (s *StoreSuite) TestTx() { + gomock.InOrder( + s.mock.EXPECT().BeginTX(s.ctx).Return(s.ctx, nil), + s.mock.EXPECT().Commit(s.ctx).Return(nil), + s.mock.EXPECT().Rollback(s.ctx).Return(nil), + s.mock.EXPECT().BeginTX(s.ctx).Return(nil, fmt.Errorf("failed to begin")), + s.mock.EXPECT().Commit(s.ctx).Return(fmt.Errorf("failed to commit")), + s.mock.EXPECT().Rollback(s.ctx).Return(fmt.Errorf("failed to rollback")), + ) + + x, err := s.store.BeginTX(s.ctx) + s.Equal(s.ctx, x) + s.NoError(err) + s.NoError(s.store.Commit(s.ctx)) + s.NoError(s.store.Rollback(s.ctx)) + + x, err = s.store.BeginTX(s.ctx) + s.Equal(nil, x) + s.EqualError(err, "failed to begin") + s.EqualError(s.store.Commit(s.ctx), "failed to commit") + s.EqualError(s.store.Rollback(s.ctx), "failed to rollback") +} + +func (s *StoreSuite) TestClientAssertionJWTValid() { + gomock.InOrder( + s.mock. + EXPECT(). + LoadOAuth2BlacklistedJTI(s.ctx, "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63"). + Return(&model.OAuth2BlacklistedJTI{ + ID: 1, + Signature: "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63", + ExpiresAt: time.Now().Add(time.Hour), + }, nil), + s.mock. + EXPECT(). + LoadOAuth2BlacklistedJTI(s.ctx, "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068"). + Return(&model.OAuth2BlacklistedJTI{ + ID: 1, + Signature: "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068", + ExpiresAt: time.Now().Add(-time.Hour), + }, nil), + s.mock. + EXPECT(). + LoadOAuth2BlacklistedJTI(s.ctx, "f29ef0d85303a09411b76001c579980f1b1b7fc9deb1fa647875a724f4f231c6"). + Return(nil, fmt.Errorf("failed to load")), + ) + + s.EqualError(s.store.ClientAssertionJWTValid(s.ctx, "066ee771-e156-4886-b99f-ee09b0d3edf4"), "jti_known") + s.NoError(s.store.ClientAssertionJWTValid(s.ctx, "5dad3ff7-e4f2-41b6-98a3-b73d872076ce")) + s.EqualError(s.store.ClientAssertionJWTValid(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202"), "failed to load") +} + +func (s *StoreSuite) TestCreateSessions() { + challenge := uuid.Must(uuid.NewRandom()) + session := &model.OpenIDSession{ + ChallengeID: challenge, + } + sessionData, _ := json.Marshal(session) + + gomock.InOrder( + s.mock. + EXPECT(). + SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}). + Return(nil), + s.mock. + EXPECT(). + SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}). + Return(fmt.Errorf("duplicate key")), + s.mock. + EXPECT(). + SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}). + Return(nil), + s.mock. + EXPECT(). + SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}). + Return(nil), + s.mock. + EXPECT(). + SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}). + Return(nil), + s.mock. + EXPECT(). + SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}). + Return(nil), + s.mock. + EXPECT(). + SaveOAuth2PARContext(s.ctx, model.OAuth2PARContext{Signature: "abc", RequestID: "abc", ClientID: "example", Session: sessionData}). + Return(nil), + ) + + s.NoError(s.store.CreateAuthorizeCodeSession(s.ctx, "abc", &fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: session, + })) + + s.EqualError(s.store.CreateAuthorizeCodeSession(s.ctx, "abc", &fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: session, + }), "duplicate key") + + s.EqualError(s.store.CreateAuthorizeCodeSession(s.ctx, "abc", &fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: nil, + }), "can't convert type '' to an *OAuth2Session") + + s.NoError(s.store.CreateAccessTokenSession(s.ctx, "abc", &fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: session, + })) + + s.NoError(s.store.CreateRefreshTokenSession(s.ctx, "abc", &fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: session, + })) + + s.NoError(s.store.CreateOpenIDConnectSession(s.ctx, "abc", &fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: session, + })) + + s.NoError(s.store.CreatePKCERequestSession(s.ctx, "abc", &fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: session, + })) + + s.NoError(s.store.CreatePARSession(s.ctx, "abc", &fosite.AuthorizeRequest{ + Request: fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: session, + }})) + + s.EqualError(s.store.CreatePARSession(s.ctx, "abc", &fosite.AuthorizeRequest{ + Request: fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: nil, + }}), "can't convert type '' to an *OAuth2Session") +} + +func (s *StoreSuite) TestRevokeSessions() { + gomock.InOrder( + s.mock. + EXPECT(). + DeactivateOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "abc1"). + Return(nil), + s.mock. + EXPECT(). + DeactivateOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "abc2"). + Return(fmt.Errorf("not found")), + s.mock. + EXPECT(). + RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, "at_example1"). + Return(nil), + s.mock. + EXPECT(). + RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, "at_example2"). + Return(fmt.Errorf("not found")), + s.mock. + EXPECT(). + RevokeOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeAccessToken, "65471ccb-d650-4006-a95f-cb4f4e3d7200"). + Return(nil), + s.mock. + EXPECT(). + RevokeOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeAccessToken, "65471ccb-d650-4006-a95f-cb4f4e3d7201"). + Return(fmt.Errorf("not found")), + s.mock. + EXPECT(). + RevokeOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeAccessToken, "65471ccb-d650-4006-a95f-cb4f4e3d7202"). + Return(sql.ErrNoRows), + s.mock. + EXPECT(). + RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, "rt_example1"). + Return(nil), + s.mock. + EXPECT(). + RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, "rt_example2"). + Return(fmt.Errorf("not found")), + s.mock. + EXPECT(). + DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7200"). + Return(nil), + s.mock. + EXPECT(). + DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7201"). + Return(fmt.Errorf("not found")), + s.mock. + EXPECT(). + DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7202"). + Return(sql.ErrNoRows), + s.mock. + EXPECT(). + DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7200"). + Return(nil), + s.mock. + EXPECT(). + DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7201"). + Return(fmt.Errorf("not found")), + s.mock. + EXPECT(). + DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7202"). + Return(sql.ErrNoRows), + s.mock. + EXPECT(). + RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, "pkce1"). + Return(nil), + s.mock. + EXPECT(). + RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, "pkce2"). + Return(fmt.Errorf("not found")), + s.mock. + EXPECT(). + RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, "ac_1"). + Return(nil), + s.mock. + EXPECT(). + RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, "ac_2"). + Return(fmt.Errorf("not found")), + s.mock. + EXPECT(). + RevokeOAuth2PARContext(s.ctx, "urn:par1"). + Return(nil), + s.mock. + EXPECT(). + RevokeOAuth2PARContext(s.ctx, "urn:par2"). + Return(fmt.Errorf("not found")), + ) + + s.NoError(s.store.InvalidateAuthorizeCodeSession(s.ctx, "abc1")) + s.EqualError(s.store.InvalidateAuthorizeCodeSession(s.ctx, "abc2"), "not found") + + s.NoError(s.store.DeleteAccessTokenSession(s.ctx, "at_example1")) + s.EqualError(s.store.DeleteAccessTokenSession(s.ctx, "at_example2"), "not found") + + s.NoError(s.store.RevokeAccessToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7200")) + s.EqualError(s.store.RevokeAccessToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201"), "not found") + s.EqualError(s.store.RevokeAccessToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202"), "not_found") + + s.NoError(s.store.DeleteRefreshTokenSession(s.ctx, "rt_example1")) + s.EqualError(s.store.DeleteRefreshTokenSession(s.ctx, "rt_example2"), "not found") + + s.NoError(s.store.RevokeRefreshToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7200")) + s.EqualError(s.store.RevokeRefreshToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201"), "not found") + s.EqualError(s.store.RevokeRefreshToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202"), "sql: no rows in result set") + + s.NoError(s.store.RevokeRefreshTokenMaybeGracePeriod(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7200", "1")) + s.EqualError(s.store.RevokeRefreshTokenMaybeGracePeriod(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201", "2"), "not found") + s.EqualError(s.store.RevokeRefreshTokenMaybeGracePeriod(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202", "3"), "sql: no rows in result set") + + s.NoError(s.store.DeletePKCERequestSession(s.ctx, "pkce1")) + s.EqualError(s.store.DeletePKCERequestSession(s.ctx, "pkce2"), "not found") + + s.NoError(s.store.DeleteOpenIDConnectSession(s.ctx, "ac_1")) + s.EqualError(s.store.DeleteOpenIDConnectSession(s.ctx, "ac_2"), "not found") + + s.NoError(s.store.DeletePARSession(s.ctx, "urn:par1")) + s.EqualError(s.store.DeletePARSession(s.ctx, "urn:par2"), "not found") +} + +func (s *StoreSuite) TestGetSessions() { + challenge := uuid.Must(uuid.NewRandom()) + session := &model.OpenIDSession{ + ChallengeID: challenge, + ClientID: "hs256", + } + sessionData, _ := json.Marshal(session) + + sessionb := &model.OpenIDSession{ + ChallengeID: challenge, + ClientID: "hs256", + } + sessionDatab, _ := json.Marshal(sessionb) + + gomock.InOrder( + s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_123"). + Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil), + s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_456"). + Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: false}, nil), + s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_aaa"). + Return(nil, sql.ErrNoRows), + s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_130"). + Return(nil, fmt.Errorf("timeout")), + s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_badclient"). + Return(&model.OAuth2Session{ClientID: "no-client", Session: sessionDatab, Active: true}, nil), + s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, "at"). + Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil), + s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, "rt"). + Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil), + s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, "pkce"). + Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil), + s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, "ot"). + Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil), + s.mock. + EXPECT(). + LoadOAuth2PARContext(s.ctx, "urn:par"). + Return(&model.OAuth2PARContext{Signature: "abc", RequestID: "abc", ClientID: "hs256", Session: sessionData}, nil), + s.mock. + EXPECT(). + LoadOAuth2PARContext(s.ctx, "urn:par"). + Return(nil, sql.ErrNoRows), + ) + + var ( + r fosite.Requester + err error + ) + + r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_123", &model.OpenIDSession{}) + s.NotNil(r) + s.NoError(err) + + r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_456", &model.OpenIDSession{}) + s.NotNil(r) + s.EqualError(err, "Authorization code has ben invalidated") + + r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_aaa", &model.OpenIDSession{}) + s.Nil(r) + s.EqualError(err, "not_found") + + r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_130", &model.OpenIDSession{}) + s.Nil(r) + s.EqualError(err, "timeout") + + r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_badclient", &model.OpenIDSession{}) + s.Nil(r) + s.EqualError(err, "invalid_client") + + r, err = s.store.GetAccessTokenSession(s.ctx, "at", &model.OpenIDSession{}) + s.NotNil(r) + s.NoError(err) + + r, err = s.store.GetRefreshTokenSession(s.ctx, "rt", &model.OpenIDSession{}) + s.NotNil(r) + s.NoError(err) + + r, err = s.store.GetPKCERequestSession(s.ctx, "pkce", &model.OpenIDSession{}) + s.NotNil(r) + s.NoError(err) + + r, err = s.store.GetOpenIDConnectSession(s.ctx, "ot", &fosite.Request{ + ID: "abc", + Client: &oidc.BaseClient{ + ID: "example", + }, + Session: session, + }) + s.NotNil(r) + s.NoError(err) + + r, err = s.store.GetPARSession(s.ctx, "urn:par") + s.NotNil(r) + s.NoError(err) + + r, err = s.store.GetPARSession(s.ctx, "urn:par") + s.Nil(r) + s.EqualError(err, "sql: no rows in result set") +} + +func (s *StoreSuite) TestIsJWTUsed() { + gomock.InOrder( + s.mock. + EXPECT(). + LoadOAuth2BlacklistedJTI(s.ctx, "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63"). + Return(&model.OAuth2BlacklistedJTI{ + ID: 1, + Signature: "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63", + ExpiresAt: time.Now().Add(time.Hour), + }, nil), + s.mock. + EXPECT(). + LoadOAuth2BlacklistedJTI(s.ctx, "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068"). + Return(&model.OAuth2BlacklistedJTI{ + ID: 1, + Signature: "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068", + ExpiresAt: time.Now().Add(-time.Hour), + }, nil), + s.mock. + EXPECT(). + LoadOAuth2BlacklistedJTI(s.ctx, "f29ef0d85303a09411b76001c579980f1b1b7fc9deb1fa647875a724f4f231c6"). + Return(nil, fmt.Errorf("failed to load")), + ) + + used, err := s.store.IsJWTUsed(s.ctx, "066ee771-e156-4886-b99f-ee09b0d3edf4") + s.True(used) + s.EqualError(err, "jti_known") + + used, err = s.store.IsJWTUsed(s.ctx, "5dad3ff7-e4f2-41b6-98a3-b73d872076ce") + s.False(used) + s.NoError(err) + + used, err = s.store.IsJWTUsed(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202") + s.True(used) + s.EqualError(err, "failed to load") +} + +func (s *StoreSuite) TestMarkJWTUsedForTime() { + gomock.InOrder( + s.mock.EXPECT(). + SaveOAuth2BlacklistedJTI(s.ctx, model.OAuth2BlacklistedJTI{Signature: "f29ef0d85303a09411b76001c579980f1b1b7fc9deb1fa647875a724f4f231c6", ExpiresAt: time.Unix(160000000, 0)}). + Return(nil), + s.mock.EXPECT().SaveOAuth2BlacklistedJTI(s.ctx, model.OAuth2BlacklistedJTI{Signature: "0dab0de97ed4e05da82763497448daf4f6b555c99218100e3ef5a81f36232940", ExpiresAt: time.Unix(160000000, 0)}). + Return(fmt.Errorf("already marked")), + ) + + s.NoError(s.store.MarkJWTUsedForTime(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202", time.Unix(160000000, 0))) + s.EqualError(s.store.MarkJWTUsedForTime(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201", time.Unix(160000000, 0)), "already marked") +} diff --git a/internal/oidc/types.go b/internal/oidc/types.go index ecec14e5f..720ac4fa7 100644 --- a/internal/oidc/types.go +++ b/internal/oidc/types.go @@ -515,18 +515,20 @@ type OAuth2MutualTLSClientAuthenticationDiscoveryOptions struct { within mtls_endpoint_aliases that do not define endpoints to which an OAuth client makes a direct request have no meaning and SHOULD be ignored. */ - MutualTLSEndpointAliases struct { - AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"` - TokenEndpoint string `json:"token_endpoint,omitempty"` - IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"` - RevocationEndpoint string `json:"revocation_endpoint,omitempty"` - EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` - UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` - BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint,omitempty"` - FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"` - PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"` - RegistrationEndpoint string `json:"registration_endpoint,omitempty"` - } `json:"mtls_endpoint_aliases"` + MutualTLSEndpointAliases OAuth2MutualTLSClientAuthenticationAliasesDiscoveryOptions `json:"mtls_endpoint_aliases"` +} + +type OAuth2MutualTLSClientAuthenticationAliasesDiscoveryOptions struct { + AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"` + TokenEndpoint string `json:"token_endpoint,omitempty"` + IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"` + RevocationEndpoint string `json:"revocation_endpoint,omitempty"` + EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` + UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` + BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint,omitempty"` + FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"` + PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"` + RegistrationEndpoint string `json:"registration_endpoint,omitempty"` } type OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions struct { @@ -954,15 +956,3 @@ type OpenIDConnectContext interface { IssuerURL() (issuerURL *url.URL, err error) } - -// MockOpenIDConnectContext is a minimal implementation of OpenIDConnectContext for the purpose of testing. -type MockOpenIDConnectContext struct { - context.Context - - MockIssuerURL *url.URL -} - -// IssuerURL returns the MockIssuerURL. -func (m *MockOpenIDConnectContext) IssuerURL() (issuerURL *url.URL, err error) { - return m.MockIssuerURL, nil -} diff --git a/internal/oidc/types_test.go b/internal/oidc/types_test.go index a17f3e3ad..baffc3dbd 100644 --- a/internal/oidc/types_test.go +++ b/internal/oidc/types_test.go @@ -1,6 +1,7 @@ -package oidc +package oidc_test import ( + "context" "net/url" "testing" "time" @@ -11,10 +12,11 @@ import ( "github.com/stretchr/testify/require" "github.com/authelia/authelia/v4/internal/model" + "github.com/authelia/authelia/v4/internal/oidc" ) func TestNewSession(t *testing.T) { - session := NewSession() + session := oidc.NewSession() require.NotNil(t, session) @@ -34,24 +36,24 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) { formValues := url.Values{} - formValues.Set(ClaimNonce, "abc123xyzauthelia") + formValues.Set(oidc.ClaimNonce, "abc123xyzauthelia") request := &fosite.AuthorizeRequest{ Request: fosite.Request{ ID: requestID.String(), Form: formValues, - Client: &BaseClient{ID: "example"}, + Client: &oidc.BaseClient{ID: "example"}, }, } extra := map[string]any{ - ClaimPreferredUsername: "john", + oidc.ClaimPreferredUsername: "john", } requested := time.Unix(1647332518, 0) authAt := time.Unix(1647332500, 0) issuer := examplecom - amr := []string{AMRPasswordBasedAuthentication} + amr := []string{oidc.AMRPasswordBasedAuthentication} consent := &model.OAuth2ConsentSession{ ChallengeID: uuid.New(), @@ -59,7 +61,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) { Subject: uuid.NullUUID{UUID: subject, Valid: true}, } - session := NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", amr, extra, authAt, consent, request) + session := oidc.NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", amr, extra, authAt, consent, request) require.NotNil(t, session) require.NotNil(t, session.Extra) @@ -80,19 +82,36 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) { assert.Equal(t, authAt, session.Claims.AuthTime) assert.Equal(t, requested, session.Claims.RequestedAt) assert.Equal(t, issuer, session.Claims.Issuer) - assert.Equal(t, "john", session.Claims.Extra[ClaimPreferredUsername]) + assert.Equal(t, "john", session.Claims.Extra[oidc.ClaimPreferredUsername]) - assert.Equal(t, "primary", session.Headers.Get(JWTHeaderKeyIdentifier)) + assert.Equal(t, "primary", session.Headers.Get(oidc.JWTHeaderKeyIdentifier)) consent = &model.OAuth2ConsentSession{ ChallengeID: uuid.New(), RequestedAt: requested, } - session = NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", nil, nil, authAt, consent, request) + session = oidc.NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", nil, nil, authAt, consent, request) require.NotNil(t, session) require.NotNil(t, session.Claims) assert.NotNil(t, session.Claims.Extra) assert.Nil(t, session.Claims.AuthenticationMethodsReferences) } + +// MockOpenIDConnectContext is a minimal implementation of OpenIDConnectContext for the purpose of testing. +type MockOpenIDConnectContext struct { + context.Context + + MockIssuerURL *url.URL + IssuerURLFunc func() (issuerURL *url.URL, err error) +} + +// IssuerURL returns the MockIssuerURL. +func (m *MockOpenIDConnectContext) IssuerURL() (issuerURL *url.URL, err error) { + if m.IssuerURLFunc != nil { + return m.IssuerURLFunc() + } + + return m.MockIssuerURL, nil +} diff --git a/internal/utils/bytes_test.go b/internal/utils/bytes_test.go new file mode 100644 index 000000000..af1f32d07 --- /dev/null +++ b/internal/utils/bytes_test.go @@ -0,0 +1,16 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBytesJoin(t *testing.T) { + a := []byte("a") + b := []byte("b") + + assert.Equal(t, "ab", string(BytesJoin(a, b))) + assert.Equal(t, "a", string(BytesJoin(a))) + assert.Equal(t, "", string(BytesJoin())) +} diff --git a/internal/utils/clocK_test.go b/internal/utils/clocK_test.go new file mode 100644 index 000000000..3f243618b --- /dev/null +++ b/internal/utils/clocK_test.go @@ -0,0 +1,48 @@ +package utils + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTestingClock(t *testing.T) { + c := &TestingClock{ + now: time.Unix(0, 0), + } + + assert.Equal(t, int64(0), c.Now().Unix()) + c.now = time.Unix(20, 0) + + assert.Equal(t, int64(20), c.Now().Unix()) + assert.Equal(t, int64(20000000000), c.Now().UnixNano()) + + c.Set(time.Unix(16000000, 0)) + + assert.Equal(t, int64(16000000), c.Now().Unix()) + + before := c.Now() + + <-c.After(time.Millisecond * 100) + + assert.Equal(t, before, c.Now()) +} + +func TestRealClock(t *testing.T) { + c := &RealClock{} + + assert.WithinDuration(t, time.Now(), c.Now(), time.Second) + + before := c.Now() + + <-c.After(time.Millisecond * 100) + + after := c.Now() + + assert.WithinDuration(t, before, after, time.Millisecond*120) + + diff := after.Sub(before) + + assert.GreaterOrEqual(t, diff, time.Millisecond*100) +}