Add support for Microsoft login
Microsoft login (or Azure AD) nearly supports the OpenID Connect protocol. With extensive configuration and some additional customization of the values expected in the tokens, it will work with Zuul. Add the necessary additional configuration and a HOWTO with the procedure. Change-Id: I445a0494796572762fbf78e580d7d6dd17be7239
This commit is contained in:
parent
ad1351c225
commit
5826a2a6c8
@ -151,4 +151,5 @@ authentication in Zuul and Zuul's Web UI.
|
||||
|
||||
howtos/openid-with-google
|
||||
howtos/openid-with-keycloak
|
||||
howtos/openid-with-microsoft
|
||||
tutorials/keycloak
|
||||
|
@ -992,6 +992,28 @@ authentication on Zuul's web user interface.
|
||||
The well-known configuration of the Identity Provider should provide this URL
|
||||
under the key "jwks_uri", therefore this attribute is usually not necessary.
|
||||
|
||||
Some providers may not conform to the JWT specification and further
|
||||
configuration may be necessary. In these cases, the following
|
||||
additional values may be used:
|
||||
|
||||
.. attr:: authority
|
||||
:default: issuer_id
|
||||
|
||||
If the authority in the token response is not the same as the
|
||||
issuer_id in the request, it may be explicitly set here.
|
||||
|
||||
.. attr:: audience
|
||||
:default: client_id
|
||||
|
||||
If the audience in the token response is not the same as the
|
||||
issuer_id in the request, it may be explicitly set here.
|
||||
|
||||
.. attr:: load_user_info
|
||||
:default: true
|
||||
|
||||
If the web UI should skip accessing the "UserInfo" endpoint and
|
||||
instead rely only on the information returned in the token, set
|
||||
this to ``false``.
|
||||
|
||||
Client
|
||||
------
|
||||
|
84
doc/source/howtos/openid-with-microsoft.rst
Normal file
84
doc/source/howtos/openid-with-microsoft.rst
Normal file
@ -0,0 +1,84 @@
|
||||
Configuring Microsoft Authentication
|
||||
====================================
|
||||
|
||||
This document explains how to configure Zuul in order to enable
|
||||
authentication with Microsoft Login.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
* The Zuul instance must be able to query Microsoft's OAUTH API servers. This
|
||||
simply generally means that the Zuul instance must be able to send and
|
||||
receive HTTPS data to and from the Internet.
|
||||
* You must have an Active Directory instance in Azure and the ability
|
||||
to create an App Registration.
|
||||
|
||||
By convention, we will assume Zuul's Web UI's base URL is
|
||||
``https://zuul.example.com/``.
|
||||
|
||||
Creating the App Registration
|
||||
-----------------------------
|
||||
|
||||
Navigate to the Active Directory instance in Azure and select `App
|
||||
registrations` under ``Manage``. Select ``New registration``. This
|
||||
will open a dialog to register an application.
|
||||
|
||||
Enter a name of your choosing (e.g., ``Zuul``), and select which
|
||||
account types should have access. Under ``Redirect URI`` select
|
||||
``Single-page application(SPA)`` and enter
|
||||
``https://zuul.example.com/auth_callback`` as the redirect URI. Press
|
||||
the ``Register`` button.
|
||||
|
||||
You should now be at the overview of the Zuul App registration. This
|
||||
page displays several values which will be used later. Record the
|
||||
``Application (client) ID`` and ``Directory (tenant) ID``. When we need
|
||||
to construct values including these later, we will refer to them with
|
||||
all caps (e.g., ``CLIENT_ID`` and ``TENANT_ID`` respectively).
|
||||
|
||||
Select ``Authentication`` under ``Manage``. You should see a
|
||||
``Single-page application`` section with the redirect URI previously
|
||||
configured during registration; if not, correct that now.
|
||||
|
||||
Under ``Implicit grant and hybrid flows`` select both ``Access
|
||||
tokens`` and ``ID tokens``, then Save.
|
||||
|
||||
Back at the Zuul App Registration menu, select ``Expose an API``, then
|
||||
press ``Set`` and then press ``Save`` to accept the default
|
||||
Application ID URI (it should look like ``api://CLIENT_ID``).
|
||||
|
||||
Press ``Add a scope`` and enter ``zuul`` as the scope name. Enter
|
||||
``Access zuul`` for both the ``Admin consent display name`` and
|
||||
``Admin consent description``. Leave ``Who can consent`` set to
|
||||
``Admins only``, then press ``Add scope``.
|
||||
|
||||
Optional: Include Groups Claim
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In order to include group information in the token sent to Zuul,
|
||||
select ``Token configuration`` under ``Manage`` and then ``Add groups
|
||||
claim``.
|
||||
|
||||
|
||||
Setting up Zuul
|
||||
---------------
|
||||
|
||||
Edit the ``/etc/zuul/zuul.conf`` to add the microsoft authenticator:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[auth microsoft]
|
||||
default=true
|
||||
driver=OpenIDConnect
|
||||
realm=zuul.example.com
|
||||
authority=https://login.microsoftonline.com/TENANT_ID/v2.0
|
||||
issuer_id=https://sts.windows.net/TENANT_ID/
|
||||
client_id=CLIENT_ID
|
||||
scope=openid profile api://CLIENT_ID/zuul
|
||||
audience=api://CLIENT_ID
|
||||
load_user_info=false
|
||||
|
||||
Restart Zuul services (scheduler, web).
|
||||
|
||||
Head to your tenant's status page. If all went well, you should see a
|
||||
`Sign in` button in the upper right corner of the
|
||||
page. Congratulations!
|
5
releasenotes/notes/ms-login-5be892f3c151bfe7.yaml
Normal file
5
releasenotes/notes/ms-login-5be892f3c151bfe7.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The ability to configure Microsoft Login as an OpenID Connect
|
||||
authentication provider has been added.
|
@ -1312,6 +1312,7 @@ class TestWebCapabilitiesInfo(TestInfo):
|
||||
'type': 'JWT',
|
||||
'scope': 'openid profile',
|
||||
'driver': 'OpenIDConnect',
|
||||
'load_user_info': True,
|
||||
},
|
||||
'myOIDC2': {
|
||||
'authority': 'http://oidc2',
|
||||
@ -1319,6 +1320,7 @@ class TestWebCapabilitiesInfo(TestInfo):
|
||||
'type': 'JWT',
|
||||
'scope': 'openid profile email special-scope',
|
||||
'driver': 'OpenIDConnect',
|
||||
'load_user_info': True,
|
||||
},
|
||||
'zuul.example.com': {
|
||||
'authority': 'zuul_operator',
|
||||
|
@ -36,6 +36,7 @@ function createAuthParamsFromJson(json) {
|
||||
authority: '',
|
||||
client_id: '',
|
||||
scope: '',
|
||||
loadUserInfo: true,
|
||||
}
|
||||
if (!auth_info) {
|
||||
console.log('No auth config')
|
||||
@ -47,6 +48,7 @@ function createAuthParamsFromJson(json) {
|
||||
auth_params.client_id = client_config.client_id
|
||||
auth_params.scope = client_config.scope
|
||||
auth_params.authority = client_config.authority
|
||||
auth_params.loadUserInfo = client_config.load_user_info
|
||||
return auth_params
|
||||
} else {
|
||||
console.log('No OpenIDConnect provider found')
|
||||
|
@ -26,6 +26,7 @@ let auth_params = {
|
||||
authority: '',
|
||||
client_id: '',
|
||||
scope: '',
|
||||
loadUserInfo: true,
|
||||
}
|
||||
if (stored_params !== null) {
|
||||
auth_params = JSON.parse(stored_params)
|
||||
|
@ -22,10 +22,34 @@ from urllib.parse import urljoin
|
||||
|
||||
from zuul import exceptions
|
||||
from zuul.driver import AuthenticatorInterface
|
||||
from zuul.lib.config import any_to_bool
|
||||
|
||||
|
||||
logger = logging.getLogger("zuul.auth.jwt")
|
||||
|
||||
# A few notes on differences between the OpenID Connect specification
|
||||
# and OIDC as implemented by Microsoft, which necessitates several
|
||||
# extra configuration options:
|
||||
#
|
||||
# 1) The issuer (iss) returned in the JWT is not the authority, but
|
||||
# rather a URL referring to the AD instance, therefore authority must
|
||||
# be specified separately.
|
||||
# 2) The audience (aud) returned in the JWT is not the client_id, but
|
||||
# rather a URL constructed from the client_id, therefore must also be
|
||||
# specified separately.
|
||||
# 3) By default, the JWT is simply a forwarded copy of a token from
|
||||
# the Microsoft Graph service. It is signed by the graph service and
|
||||
# not the authority as requested, it therefore fails signature
|
||||
# validation. In order to cause the authority to generate its own
|
||||
# token signed by the expected keys, we must create a new scope
|
||||
# (api://.../zuul) and request that scope with the token.
|
||||
# 4) The userinfo service referred to by the JWT is the Microsoft
|
||||
# Graph service, which means that once our token is signed by the
|
||||
# Microsoft login keys (see item #3) the graph service is then unable
|
||||
# to validate the token. Therefore we must configure the javascript
|
||||
# oidc library not to request userinfo and rely only on what is
|
||||
# supplied in the token.
|
||||
|
||||
|
||||
class JWTAuthenticator(AuthenticatorInterface):
|
||||
"""The base class for JWT-based authentication."""
|
||||
@ -34,19 +58,17 @@ class JWTAuthenticator(AuthenticatorInterface):
|
||||
# Common configuration for all authenticators
|
||||
self.uid_claim = conf.get('uid_claim', 'sub')
|
||||
self.issuer_id = conf.get('issuer_id')
|
||||
self.audience = conf.get('client_id')
|
||||
self.authority = conf.get('authority', self.issuer_id)
|
||||
self.client_id = conf.get('client_id')
|
||||
self.audience = conf.get('audience', self.client_id)
|
||||
self.realm = conf.get('realm')
|
||||
self.allow_authz_override = conf.get('allow_authz_override', False)
|
||||
self.allow_authz_override = any_to_bool(
|
||||
conf.get('allow_authz_override', False))
|
||||
try:
|
||||
self.skew = int(conf.get('skew', 0))
|
||||
except Exception:
|
||||
raise ValueError(
|
||||
'skew must be an integer, got %s' % conf.get('skew'))
|
||||
if isinstance(self.allow_authz_override, str):
|
||||
if self.allow_authz_override.lower() == 'true':
|
||||
self.allow_authz_override = True
|
||||
else:
|
||||
self.allow_authz_override = False
|
||||
try:
|
||||
self.max_validity_time = float(conf.get('max_validity_time',
|
||||
math.inf))
|
||||
@ -56,8 +78,8 @@ class JWTAuthenticator(AuthenticatorInterface):
|
||||
def get_capabilities(self):
|
||||
return {
|
||||
self.realm: {
|
||||
'authority': self.issuer_id,
|
||||
'client_id': self.audience,
|
||||
'authority': self.authority,
|
||||
'client_id': self.client_id,
|
||||
'type': 'JWT',
|
||||
'driver': getattr(self, 'name', 'N/A'),
|
||||
}
|
||||
@ -190,6 +212,8 @@ class OpenIDConnectAuthenticator(JWTAuthenticator):
|
||||
super(OpenIDConnectAuthenticator, self).__init__(**conf)
|
||||
self.keys_url = conf.get('keys_url', None)
|
||||
self.scope = conf.get('scope', 'openid profile')
|
||||
self.load_user_info = any_to_bool(
|
||||
conf.get('load_user_info', True))
|
||||
|
||||
def get_key(self, key_id):
|
||||
keys_url = self.keys_url
|
||||
@ -235,6 +259,7 @@ class OpenIDConnectAuthenticator(JWTAuthenticator):
|
||||
def get_capabilities(self):
|
||||
d = super(OpenIDConnectAuthenticator, self).get_capabilities()
|
||||
d[self.realm]['scope'] = self.scope
|
||||
d[self.realm]['load_user_info'] = self.load_user_info
|
||||
return d
|
||||
|
||||
def _decode(self, rawToken):
|
||||
|
Loading…
x
Reference in New Issue
Block a user