Add support for using OIDC tokens via the Docker CLI

This commit is contained in:
Joseph Schorr 2017-06-08 13:13:22 -04:00
parent 6600b380ca
commit e724125459
16 changed files with 176 additions and 14 deletions

View file

@ -10,6 +10,7 @@ from data.users.database import DatabaseUsers
from data.users.externalldap import LDAPUsers
from data.users.externaljwt import ExternalJWTAuthN
from data.users.keystone import get_keystone_users
from data.users.oidc import OIDCInternalAuth
from util.security.aes import AESCipher
logger = logging.getLogger(__name__)
@ -24,6 +25,9 @@ def get_federated_service_name(authentication_type):
if authentication_type == 'Keystone':
return 'keystone'
if authentication_type == 'OIDC':
return 'oidc'
raise Exception('Unknown auth type: %s' % authentication_type)
@ -74,8 +78,15 @@ def get_users_handler(config, _, override_config_dir):
keystone_admin_password = config.get('KEYSTONE_ADMIN_PASSWORD')
keystone_admin_tenant = config.get('KEYSTONE_ADMIN_TENANT')
return get_keystone_users(auth_version, auth_url, keystone_admin_username,
keystone_admin_password, keystone_admin_tenant, timeout,
requires_email=features.MAILING)
keystone_admin_password, keystone_admin_tenant, timeout,
requires_email=features.MAILING)
if authentication_type == 'OIDC':
if features.DIRECT_LOGIN:
raise Exception('Direct login feature must be disabled to use OIDC internal auth')
login_service = config.get('INTERNAL_OIDC_SERVICE_ID')
return OIDCInternalAuth(config, login_service, requires_email=features.MAILING)
raise RuntimeError('Unknown authentication type: %s' % authentication_type)
@ -160,6 +171,11 @@ class UserAuthentication(object):
"""
return self.state.federated_service
@property
def supports_encrypted_credentials(self):
""" Returns whether this auth system supports using encrypted credentials. """
return self.state.supports_encrypted_credentials
def query_users(self, query, limit=20):
""" Performs a lookup against the user system for the specified query. The returned tuple
will be of the form (results, federated_login_id, err_msg). If the method is unsupported,

View file

@ -9,6 +9,10 @@ class DatabaseUsers(object):
""" Always assumed to be working. If the DB is broken, other checks will handle it. """
return (True, None)
@property
def supports_encrypted_credentials(self):
return True
def verify_credentials(self, username_or_email, password):
""" Simply delegate to the model implementation. """
result = model.user.verify_user(username_or_email, password)

View file

@ -23,6 +23,10 @@ class FederatedUsers(object):
def federated_service(self):
return self._federated_service
@property
def supports_encrypted_credentials(self):
return True
def get_user(self, username_or_email):
""" Retrieves the user with the given username or email, returning a tuple containing
a UserInformation (if success) and the error message (on failure).

45
data/users/oidc.py Normal file
View file

@ -0,0 +1,45 @@
import logging
from data.users.federated import FederatedUsers, UserInformation
from oauth.loginmanager import OAuthLoginManager
from oauth.oidc import PublicKeyLoadException
from util.security.jwtutil import InvalidTokenError
logger = logging.getLogger(__name__)
class OIDCInternalAuth(FederatedUsers):
""" Handles authentication by delegating authentication to a signed OIDC JWT produced by the
configured OIDC service.
"""
def __init__(self, config, login_service_id, requires_email):
super(OIDCInternalAuth, self).__init__('oidc', requires_email)
login_manager = OAuthLoginManager(config)
self.login_service = login_manager.get_service(login_service_id)
if self.login_service is None:
raise Exception('Unknown OIDC login service %s' % login_service_id)
@property
def supports_encrypted_credentials(self):
# Since the "password" is already a signed JWT.
return False
def get_user(self, username_or_email):
return (None, 'Cannot retrieve users for OIDC')
def query_users(self, query, limit=20):
return (None, 'Cannot query users for OIDC')
def verify_credentials(self, username_or_email, id_token):
try:
payload = self.login_service.decode_user_jwt(id_token)
except InvalidTokenError as ite:
logger.exception('Got invalid token error on OIDC decode: %s', ite.message)
return (None, 'Could not validate OIDC token')
except PublicKeyLoadException as pke:
logger.exception('Could not load public key during OIDC decode: %s', pke.message)
return (None, 'Could not validate OIDC token')
user_info = UserInformation(username=payload['sub'], id=payload['sub'], email=None)
return (user_info, None)

View file

@ -0,0 +1,19 @@
from httmock import HTTMock
from data.users.oidc import OIDCInternalAuth
from oauth.test.test_oidc import (id_token, oidc_service, signing_key, jwks_handler,
discovery_handler, app_config, http_client,
discovery_content)
def test_oidc_login(app_config, id_token, jwks_handler, discovery_handler):
internal_auth = OIDCInternalAuth(app_config, 'someoidc', False)
with HTTMock(jwks_handler, discovery_handler):
# Try a valid token.
(user, err) = internal_auth.verify_credentials('someusername', id_token)
assert err is None
assert user.username == 'cooluser'
# Try an invalid token.
(user, err) = internal_auth.verify_credentials('someusername', 'invalidtoken')
assert err is not None
assert user is None