JWT drivers: Deprecate RS256withJWKS, introduce OpenIDConnect
Replace the RS256withJWKS driver with the simplified OpenIDConnect driver. The new driver doesn't require the 'keys_url' parameter, all needed parameters are fetched from the well-known config endpoint inferred from the issuer_id. Add a simple workflow test of the OpenIDConnect driver. Change-Id: I4b0936a587918d6051a4206e20cad68577617e3d
This commit is contained in:
parent
9ce06b4a73
commit
8f243b0126
|
@ -977,8 +977,9 @@ protected endpoints and configure JWT validation:
|
|||
|
||||
.. attr:: driver
|
||||
|
||||
The signing algorithm to use. Accepted values are ``HS256``, ``RS256`` or
|
||||
``RS256withJWKS``. See below for driver-specific configuration options.
|
||||
The signing algorithm to use. Accepted values are ``HS256``, ``RS256``,
|
||||
``RS256withJWKS`` or ``OpenIDConnect``. See below for driver-specific
|
||||
configuration options.
|
||||
|
||||
.. attr:: allow_authz_override
|
||||
:default: false
|
||||
|
@ -1064,6 +1065,10 @@ the public key is needed by Zuul for signature validation.
|
|||
RS256withJWKS
|
||||
,,,,,,,,,,,,,
|
||||
|
||||
.. warning::
|
||||
|
||||
This driver is deprecated, use ``OpenIDConnect`` instead.
|
||||
|
||||
Some Identity Providers use key sets (also known as **JWKS**), therefore the key to
|
||||
use when verifying the Authentication Token's signatures cannot be known in
|
||||
advance; the key's id is stored in the JWT's header and the key must then be
|
||||
|
@ -1076,6 +1081,31 @@ The key set is usually available at a specific URL that can be found in the
|
|||
The URL where the Identity Provider's key set can be found. For example, for
|
||||
Google's OAuth service: https://www.googleapis.com/oauth2/v3/certs
|
||||
|
||||
OpenIDConnect
|
||||
,,,,,,,,,,,,,
|
||||
|
||||
Use a third-party Identity Provider implementing the OpenID Connect protocol.
|
||||
The issuer ID should be an URI, from which the "well-known" configuration URI
|
||||
of the Identity Provider can be inferred. This is intended to be used for
|
||||
authentication on Zuul's web user interface.
|
||||
|
||||
.. attr:: scope
|
||||
:default: openid profile
|
||||
|
||||
The scope(s) to use when requesting access to a user's details. This attribute
|
||||
can be multivalued (values must be separated by a space). Most OpenID Connect
|
||||
Identity Providers support the default scopes "openid profile". A full list
|
||||
of supported scopes can be found in the well-known configuration of the
|
||||
Identity Provider under the key "scopes_supported".
|
||||
|
||||
.. attr:: keys_url
|
||||
|
||||
Optional. The URL where the Identity Provider's key set can be found.
|
||||
For example, for Google's OAuth service: https://www.googleapis.com/oauth2/v3/certs
|
||||
The well-known configuration of the Identity Provider should provide this URL
|
||||
under the key "jwks_uri", therefore this attribute is usually not necessary.
|
||||
|
||||
|
||||
Operation
|
||||
~~~~~~~~~
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
deprecations:
|
||||
- |
|
||||
Authentication: the JWT driver "RS256withJWKS" is deprecated in favor of the
|
||||
"OpenIDConnect" driver. The "OpenIDConnect" driver simplifies configuration
|
||||
for administrators and is better aligned with OIDC configuration discovery
|
||||
conventions.
|
|
@ -0,0 +1,51 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsaUXr+7qhvCcHniUgZWz
|
||||
RHtMqeYMT6obdd7zh6ZKLPVb6G13Sn8aVGM0C+sei6tcCTSBkbb0vjPa/z3pWSF7
|
||||
Ozvt68fjUDElJImb7RUC7g8gTU/pHCqW7UKiSjTpggUbgU2Driz45/890CAguG5m
|
||||
efQ918ITy3MPBR9/yDgPsy7b+Opml6fuodSvm6FCv90CZW+Y3o/bSLvTIUxbVhmj
|
||||
rDLYk4TpeaGPGV3CxopPEE3KQXoFCE8V7OCK+KB82gUqDD5e3Gk2cIwNoh90nCqc
|
||||
XCnR7+lGZJxK9D+00xeKBFvhIgD0wlt86T47sr1hwWTT8Ds5lKsE1JhGYs01/9Ia
|
||||
6QIqpW9sEUa61OMiaROM9IhI5PGUSeOA+9NZokEWfMkYeV1W2ziuKerCpRGjUgHF
|
||||
e9aDh3T2ssQIznaIvxMgWg10XuVl8bxY4pusPizpMuxOb4R+7KVkSBPd7HnuXOs2
|
||||
xatOABoNms7plHbrEVsnpX6hfta3z47H+MkXAuo10/8SKl0MYhQAMo/bYqYaZUY8
|
||||
4+tgxN6twZzTj3vf5zDD1N7oYCt+pneRmywQmDJvXXG9uwF6E5BqMxHwNGdB980g
|
||||
6oJWUqJ1HdQG7ML2r/DHEe06DeIQ2bN87ihiQifgZ1U+oyp0a2zkAOWCDEx4RQqw
|
||||
c/EE7rmq5NpcSvpbVaXLOeMCAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
|
@ -0,0 +1,130 @@
|
|||
{
|
||||
"issuer": "https://my.oidc.provider/auth/realms/realm-one",
|
||||
"authorization_endpoint": "https://my.oidc.provider/auth/realms/realm-one/protocol/openid-connect/auth",
|
||||
"token_endpoint": "https://my.oidc.provider/auth/realms/realm-one/protocol/openid-connect/token",
|
||||
"token_introspection_endpoint": "https://my.oidc.provider/auth/realms/realm-one/protocol/openid-connect/token/introspect",
|
||||
"userinfo_endpoint": "https://my.oidc.provider/auth/realms/realm-one/protocol/openid-connect/userinfo",
|
||||
"end_session_endpoint": "https://my.oidc.provider/auth/realms/realm-one/protocol/openid-connect/logout",
|
||||
"jwks_uri": "https://my.oidc.provider/auth/realms/realm-one/protocol/openid-connect/certs",
|
||||
"check_session_iframe": "https://my.oidc.provider/auth/realms/realm-one/protocol/openid-connect/login-status-iframe.html",
|
||||
"grant_types_supported": [
|
||||
"authorization_code",
|
||||
"implicit",
|
||||
"refresh_token",
|
||||
"password",
|
||||
"client_credentials"
|
||||
],
|
||||
"response_types_supported": [
|
||||
"code",
|
||||
"none",
|
||||
"id_token",
|
||||
"token",
|
||||
"id_token token",
|
||||
"code id_token",
|
||||
"code token",
|
||||
"code id_token token"
|
||||
],
|
||||
"subject_types_supported": [
|
||||
"public",
|
||||
"pairwise"
|
||||
],
|
||||
"id_token_signing_alg_values_supported": [
|
||||
"PS384",
|
||||
"ES384",
|
||||
"RS384",
|
||||
"HS256",
|
||||
"HS512",
|
||||
"ES256",
|
||||
"RS256",
|
||||
"HS384",
|
||||
"ES512",
|
||||
"PS256",
|
||||
"PS512",
|
||||
"RS512"
|
||||
],
|
||||
"id_token_encryption_alg_values_supported": [
|
||||
"RSA-OAEP",
|
||||
"RSA1_5"
|
||||
],
|
||||
"id_token_encryption_enc_values_supported": [
|
||||
"A128GCM",
|
||||
"A128CBC-HS256"
|
||||
],
|
||||
"userinfo_signing_alg_values_supported": [
|
||||
"PS384",
|
||||
"ES384",
|
||||
"RS384",
|
||||
"HS256",
|
||||
"HS512",
|
||||
"ES256",
|
||||
"RS256",
|
||||
"HS384",
|
||||
"ES512",
|
||||
"PS256",
|
||||
"PS512",
|
||||
"RS512",
|
||||
"none"
|
||||
],
|
||||
"request_object_signing_alg_values_supported": [
|
||||
"PS384",
|
||||
"ES384",
|
||||
"RS384",
|
||||
"ES256",
|
||||
"RS256",
|
||||
"ES512",
|
||||
"PS256",
|
||||
"PS512",
|
||||
"RS512",
|
||||
"none"
|
||||
],
|
||||
"response_modes_supported": [
|
||||
"query",
|
||||
"fragment",
|
||||
"form_post"
|
||||
],
|
||||
"registration_endpoint": "https://my.oidc.provider/auth/realms/realm-one/clients-registrations/openid-connect",
|
||||
"token_endpoint_auth_methods_supported": [
|
||||
"private_key_jwt",
|
||||
"client_secret_basic",
|
||||
"client_secret_post",
|
||||
"client_secret_jwt"
|
||||
],
|
||||
"token_endpoint_auth_signing_alg_values_supported": [
|
||||
"RS256"
|
||||
],
|
||||
"claims_supported": [
|
||||
"aud",
|
||||
"sub",
|
||||
"iss",
|
||||
"auth_time",
|
||||
"name",
|
||||
"given_name",
|
||||
"family_name",
|
||||
"preferred_username",
|
||||
"email"
|
||||
],
|
||||
"claim_types_supported": [
|
||||
"normal"
|
||||
],
|
||||
"claims_parameter_supported": false,
|
||||
"scopes_supported": [
|
||||
"openid",
|
||||
"web-origins",
|
||||
"email",
|
||||
"profile",
|
||||
"microprofile-jwt",
|
||||
"address",
|
||||
"zuul_audience",
|
||||
"offline_access",
|
||||
"phone",
|
||||
"roles"
|
||||
],
|
||||
"request_parameter_supported": true,
|
||||
"request_uri_parameter_supported": true,
|
||||
"code_challenge_methods_supported": [
|
||||
"plain",
|
||||
"S256"
|
||||
],
|
||||
"tls_client_certificate_bound_access_tokens": true,
|
||||
"introspection_endpoint": "https://my.oidc.provider/auth/realms/realm-one/protocol/openid-connect/token/introspect"
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
# Copyright 2020 OpenStack Foundation
|
||||
# Copyright 2020 Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
from unittest import mock
|
||||
import os.path
|
||||
import jwt
|
||||
import time
|
||||
|
||||
from zuul.driver import auth
|
||||
|
||||
from tests.base import BaseTestCase, FIXTURE_DIR
|
||||
|
||||
with open(os.path.join(FIXTURE_DIR,
|
||||
'auth/openid-configuration.json'), 'r') as well_known:
|
||||
FAKE_WELL_KNOWN_CONFIG = json.loads(well_known.read())
|
||||
|
||||
|
||||
algo = jwt.algorithms.RSAAlgorithm(jwt.algorithms.RSAAlgorithm.SHA256)
|
||||
with open(os.path.join(FIXTURE_DIR,
|
||||
'auth/oidc-key'), 'r') as k:
|
||||
OIDC_PRIVATE_KEY = algo.prepare_key(k.read().encode('utf-8'))
|
||||
with open(os.path.join(FIXTURE_DIR,
|
||||
'auth/oidc-key.pub'), 'r') as k:
|
||||
pub_key = algo.prepare_key(k.read().encode('utf-8'))
|
||||
pub_jwk = algo.to_jwk(pub_key)
|
||||
key = {
|
||||
"kid": "OwO",
|
||||
"use": "sig",
|
||||
"alg": "RS256"
|
||||
}
|
||||
key.update(json.loads(pub_jwk))
|
||||
# not present in keycloak jwks
|
||||
if "key_ops" in key:
|
||||
del key["key_ops"]
|
||||
FAKE_CERTS = {
|
||||
"keys": [
|
||||
key
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def mock_get(url, params=None, **kwargs):
|
||||
if url == ("https://my.oidc.provider/auth/realms/realm-one/"
|
||||
".well-known/openid-configuration"):
|
||||
return FakeResponse(FAKE_WELL_KNOWN_CONFIG)
|
||||
elif url == ("https://my.oidc.provider/auth/realms/realm-one/"
|
||||
"protocol/openid-connect/certs"):
|
||||
return FakeResponse(FAKE_CERTS)
|
||||
else:
|
||||
raise Exception("Unknown URL %s" % url)
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, json_dict):
|
||||
self._json = json_dict
|
||||
|
||||
def json(self):
|
||||
return self._json
|
||||
|
||||
|
||||
class TestOpenIDConnectAuthenticator(BaseTestCase):
|
||||
def test_decodeToken(self):
|
||||
"""Test the decoding workflow"""
|
||||
config = {
|
||||
'issuer_id': FAKE_WELL_KNOWN_CONFIG['issuer'],
|
||||
'client_id': 'zuul-app',
|
||||
'realm': 'realm-one',
|
||||
}
|
||||
OIDCAuth = auth.jwt.OpenIDConnectAuthenticator(**config)
|
||||
payload = {
|
||||
'iss': FAKE_WELL_KNOWN_CONFIG['issuer'],
|
||||
'aud': config['client_id'],
|
||||
'exp': time.time() + 3600,
|
||||
'sub': 'someone'
|
||||
}
|
||||
token = jwt.encode(
|
||||
payload,
|
||||
OIDC_PRIVATE_KEY,
|
||||
algorithm='RS256',
|
||||
headers={'kid': 'OwO'})
|
||||
with mock.patch('requests.get', side_effect=mock_get):
|
||||
decoded = OIDCAuth.decodeToken(token)
|
||||
for claim in payload.keys():
|
||||
self.assertEqual(payload[claim], decoded[claim])
|
|
@ -19,6 +19,7 @@ import time
|
|||
import jwt
|
||||
import requests
|
||||
import json
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from zuul import exceptions
|
||||
from zuul.driver import AuthenticatorInterface
|
||||
|
@ -164,19 +165,74 @@ class RS256Authenticator(JWTAuthenticator):
|
|||
algorithms=self.algorithm)
|
||||
|
||||
|
||||
class RS256withJWKSAuthenticator(JWTAuthenticator):
|
||||
"""JWT authentication using the RS256 algorithm.
|
||||
class OpenIDConnectAuthenticator(JWTAuthenticator):
|
||||
"""JWT authentication using an OpenIDConnect provider.
|
||||
|
||||
Requires the URL of the certificates used by the Identity Provier. It can
|
||||
be found usually under the key "jwks_uri" at the provider's
|
||||
.well-known/openid-configuration URL."""
|
||||
If the optional 'keys_url' parameter is not specified, the authenticator
|
||||
will attempt to determine it via the well-known configuration URI as
|
||||
described in
|
||||
https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig""" # noqa
|
||||
|
||||
# default algorithm, TOFO: should this be a config param?
|
||||
algorithm = 'RS256'
|
||||
name = 'RS256withJWKS'
|
||||
name = 'OpenIDConnect'
|
||||
|
||||
def __init__(self, **conf):
|
||||
super(RS256withJWKSAuthenticator, self).__init__(**conf)
|
||||
super(OpenIDConnectAuthenticator, self).__init__(**conf)
|
||||
self.keys_url = conf.get('keys_url', None)
|
||||
self.scope = conf.get('scope', 'openid profile')
|
||||
|
||||
def get_key(self, key_id):
|
||||
keys_url = self.keys_url
|
||||
if keys_url is None:
|
||||
well_known = self.get_well_known_config()
|
||||
keys_url = well_known.get('jwks_uri', None)
|
||||
if keys_url is None:
|
||||
msg = 'Invalid OpenID configuration: "jwks_uri" not found'
|
||||
logger.error(msg)
|
||||
raise exceptions.JWKSException(
|
||||
realm=self.realm,
|
||||
msg=msg)
|
||||
# TODO keys can probably be cached
|
||||
try:
|
||||
certs = requests.get(keys_url).json()
|
||||
except Exception as e:
|
||||
msg = 'Could not fetch Identity Provider keys at %s: %s'
|
||||
logger.error(msg % (keys_url, e))
|
||||
raise exceptions.JWKSException(
|
||||
realm=self.realm,
|
||||
msg='There was an error while fetching '
|
||||
'keys for Identity Provider, check logs for details')
|
||||
for key_dict in certs['keys']:
|
||||
if key_dict.get('kid') == key_id:
|
||||
# TODO: theoretically two other types of keys are
|
||||
# supported by the JWKS standard. We should raise an error
|
||||
# in the unlikely case 'kty' is not RSA.
|
||||
# (see https://tools.ietf.org/html/rfc7518#section-6.1)
|
||||
key = jwt.algorithms.RSAAlgorithm.from_jwk(
|
||||
json.dumps(key_dict))
|
||||
algorithm = key_dict.get('alg', None) or self.algorithm
|
||||
return key, algorithm
|
||||
raise exceptions.JWKSException(
|
||||
self.realm,
|
||||
'Cannot verify token: public key %s '
|
||||
'not listed by Identity Provider' % key_id)
|
||||
|
||||
def get_well_known_config(self):
|
||||
issuer = self.issuer_id
|
||||
if not issuer.endswith('/'):
|
||||
issuer += '/'
|
||||
well_known_uri = urljoin(issuer,
|
||||
'.well-known/openid-configuration')
|
||||
try:
|
||||
return requests.get(well_known_uri).json()
|
||||
except Exception as e:
|
||||
msg = 'Could not fetch OpenID configuration at %s: %s'
|
||||
logger.error(msg % (well_known_uri, e))
|
||||
raise exceptions.JWKSException(
|
||||
realm=self.realm,
|
||||
msg='There was an error while fetching '
|
||||
'OpenID configuration, check logs for details')
|
||||
|
||||
def _decode(self, rawToken):
|
||||
unverified_headers = jwt.get_unverified_header(rawToken)
|
||||
|
@ -184,35 +240,22 @@ class RS256withJWKSAuthenticator(JWTAuthenticator):
|
|||
if key_id is None:
|
||||
raise exceptions.JWKSException(
|
||||
self.realm, 'No key ID in token header')
|
||||
# TODO keys can probably be cached
|
||||
try:
|
||||
certs = requests.get(self.keys_url).json()
|
||||
except Exception as e:
|
||||
msg = 'Could not fetch Identity Provider keys at %s: %s'
|
||||
logger.error(msg % (self.keys_url, e))
|
||||
raise exceptions.JWKSException(
|
||||
realm=self.realm,
|
||||
msg='There was an error while fetching '
|
||||
'keys for Identity Provider')
|
||||
for key_dict in certs['keys']:
|
||||
if key_dict.get('kid') == key_id:
|
||||
key = jwt.algorithms.RSAAlgorithm.from_jwk(
|
||||
json.dumps(key_dict))
|
||||
return jwt.decode(rawToken, key, issuer=self.issuer_id,
|
||||
audience=self.audience,
|
||||
algorithms=self.algorithm)
|
||||
raise exceptions.JWKSException(
|
||||
self.realm,
|
||||
'Cannot verify token: public key %s '
|
||||
'not listed by Identity Provider' % key_id)
|
||||
key, algorithm = self.get_key(key_id)
|
||||
return jwt.decode(rawToken, key, issuer=self.issuer_id,
|
||||
audience=self.audience,
|
||||
algorithms=algorithm)
|
||||
|
||||
|
||||
AUTHENTICATORS = {
|
||||
'HS256': HS256Authenticator,
|
||||
'RS256': RS256Authenticator,
|
||||
'RS256withJWKS': RS256withJWKSAuthenticator,
|
||||
'RS256withJWKS': OpenIDConnectAuthenticator,
|
||||
'OpenIDConnect': OpenIDConnectAuthenticator,
|
||||
}
|
||||
|
||||
|
||||
def get_authenticator_by_name(name):
|
||||
if name == 'RS256withJWKS':
|
||||
logger.info(
|
||||
'Driver "%s" is deprecated, please use "OpenIDConnect" instead')
|
||||
return AUTHENTICATORS[name]
|
||||
|
|
Loading…
Reference in New Issue