Basic Keystone Auth support
Note: This has been verified as working by the end customer
This commit is contained in:
parent
eb612d606c
commit
066637f496
6 changed files with 151 additions and 1 deletions
|
@ -0,0 +1,26 @@
|
|||
"""Add keystone login service
|
||||
|
||||
Revision ID: 2bf8af5bad95
|
||||
Revises: 154f2befdfbe
|
||||
Create Date: 2015-06-29 21:19:13.053165
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2bf8af5bad95'
|
||||
down_revision = '154f2befdfbe'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade(tables):
|
||||
op.bulk_insert(tables.loginservice, [{'id': 6, 'name': 'keystone'}])
|
||||
|
||||
|
||||
def downgrade(tables):
|
||||
op.execute(
|
||||
tables.loginservice.delete()
|
||||
.where(tables.loginservice.c.name == op.inline_literal('keystone'))
|
||||
)
|
||||
|
|
@ -8,6 +8,9 @@ import jwt
|
|||
|
||||
from collections import namedtuple
|
||||
from datetime import datetime, timedelta
|
||||
from keystoneclient.v2_0 import client as kclient
|
||||
from keystoneclient.exceptions import AuthorizationFailure as KeystoneAuthorizationFailure
|
||||
from keystoneclient.exceptions import Unauthorized as KeystoneUnauthorized
|
||||
|
||||
import features
|
||||
|
||||
|
@ -53,6 +56,37 @@ def _get_federated_user(username, email, federated_service, create_new_user):
|
|||
return (db_user, None)
|
||||
|
||||
|
||||
class KeystoneUsers(object):
|
||||
""" Delegates authentication to OpenStack Keystone. """
|
||||
def __init__(self, auth_url, admin_username, admin_password, admin_tenant):
|
||||
self.auth_url = auth_url
|
||||
self.admin_username = admin_username
|
||||
self.admin_password = admin_password
|
||||
self.admin_tenant = admin_tenant
|
||||
|
||||
def verify_user(self, username_or_email, password, create_new_user=True):
|
||||
try:
|
||||
keystone_client = kclient.Client(username=username_or_email, password=password,
|
||||
auth_url=self.auth_url)
|
||||
user_id = keystone_client.user_id
|
||||
except KeystoneAuthorizationFailure as kaf:
|
||||
logger.exception('Keystone auth failure for user: %s', username_or_email)
|
||||
return (None, kaf.message or 'Invalid username or password')
|
||||
except KeystoneUnauthorized as kut:
|
||||
logger.exception('Keystone unauthorized for user: %s', username_or_email)
|
||||
return (None, kut.message or 'Invalid username or password')
|
||||
|
||||
try:
|
||||
admin_client = kclient.Client(username=self.admin_username, password=self.admin_password,
|
||||
tenant_name=self.admin_tenant, auth_url=self.auth_url)
|
||||
user = admin_client.users.get(user_id)
|
||||
except KeystoneUnauthorized as kut:
|
||||
logger.exception('Keystone unauthorized admin')
|
||||
return (None, 'Keystone admin credentials are invalid: %s' % kut.message)
|
||||
|
||||
return _get_federated_user(username_or_email, user.email, 'keystone', create_new_user)
|
||||
|
||||
|
||||
class ExternalJWTAuthN(object):
|
||||
""" Delegates authentication to a REST endpoint that returns JWTs. """
|
||||
PUBLIC_KEY_FILENAME = 'jwt-authn.cert'
|
||||
|
@ -336,6 +370,13 @@ class UserAuthentication(object):
|
|||
max_fresh_s = app.config.get('JWT_AUTH_MAX_FRESH_S', 300)
|
||||
users = ExternalJWTAuthN(verify_url, issuer, override_config_dir, max_fresh_s,
|
||||
app.config['HTTPCLIENT'])
|
||||
elif authentication_type == 'Keystone':
|
||||
auth_url = app.config.get('KEYSTONE_AUTH_URL')
|
||||
keystone_admin_username = app.config.get('KEYSTONE_ADMIN_USERNAME')
|
||||
keystone_admin_password = app.config.get('KEYSTONE_ADMIN_PASSWORD')
|
||||
keystone_admin_tenant = app.config.get('KEYSTONE_ADMIN_TENANT')
|
||||
users = KeystoneUsers(auth_url, keystone_admin_username, keystone_admin_password,
|
||||
keystone_admin_tenant)
|
||||
else:
|
||||
raise RuntimeError('Unknown authentication type: %s' % authentication_type)
|
||||
|
||||
|
|
|
@ -205,6 +205,7 @@ def initialize_database():
|
|||
LoginService.create(name='quayrobot')
|
||||
LoginService.create(name='ldap')
|
||||
LoginService.create(name='jwtauthn')
|
||||
LoginService.create(name='keystone')
|
||||
|
||||
BuildTriggerService.create(name='github')
|
||||
BuildTriggerService.create(name='custom-git')
|
||||
|
|
|
@ -353,12 +353,55 @@
|
|||
<select ng-model="config.AUTHENTICATION_TYPE">
|
||||
<option value="Database">Local Database</option>
|
||||
<option value="LDAP">LDAP</option>
|
||||
<option value="Keystone">Keystone (OpenStack Identity)</option>
|
||||
<option value="JWT">JWT Custom Authentication</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Keystone Authentication -->
|
||||
<table class="config-table" ng-if="config.AUTHENTICATION_TYPE == 'Keystone'">
|
||||
<tr>
|
||||
<td>Keystone Authentication URL:</td>
|
||||
<td>
|
||||
<span class="config-string-field" binding="config.KEYSTONE_AUTH_URL"
|
||||
pattern="http(s)?://.+"></span>
|
||||
<div class="help-text">
|
||||
The URL (starting with http or https) of the Keystone Server endpoint for auth.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Keystone Administrator Username:</td>
|
||||
<td>
|
||||
<span class="config-string-field" binding="config.KEYSTONE_ADMIN_USERNAME"></span>
|
||||
<div class="help-text">
|
||||
The username for the Keystone admin.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Keystone Administrator Password:</td>
|
||||
<td>
|
||||
<input type="password" ng-model="config.KEYSTONE_ADMIN_PASSWORD"
|
||||
class="form-control" required></span>
|
||||
<div class="help-text">
|
||||
The password for the Keystone admin.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Keystone Administrator Tenant:</td>
|
||||
<td>
|
||||
<span class="config-string-field" binding="config.KEYSTONE_ADMIN_TENANT"></span>
|
||||
<div class="help-text">
|
||||
The tenant (project/group) that contains the administrator user.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- JWT Custom Authentication -->
|
||||
<div class="co-alert co-alert-info" ng-if="config.AUTHENTICATION_TYPE == 'JWT'">
|
||||
JSON Web Token authentication allows your organization to provide an HTTP endpoint that
|
||||
|
|
|
@ -31,6 +31,10 @@ angular.module("core-config-setup", ['angularFileUpload'])
|
|||
return config.AUTHENTICATION_TYPE == 'JWT';
|
||||
}, 'password': true},
|
||||
|
||||
{'id': 'keystone', 'title': 'Keystone Authentication', 'condition': function(config) {
|
||||
return config.AUTHENTICATION_TYPE == 'Keystone';
|
||||
}, 'password': true},
|
||||
|
||||
{'id': 'mail', 'title': 'E-mail Support', 'condition': function(config) {
|
||||
return config.FEATURE_MAILING;
|
||||
}},
|
||||
|
|
|
@ -7,7 +7,7 @@ import OpenSSL
|
|||
import logging
|
||||
|
||||
from fnmatch import fnmatch
|
||||
from data.users import LDAPConnection, ExternalJWTAuthN, LDAPUsers
|
||||
from data.users import LDAPConnection, ExternalJWTAuthN, LDAPUsers, KeystoneUsers
|
||||
from flask import Flask
|
||||
from flask.ext.mail import Mail, Message
|
||||
from data.database import validate_database_url, User
|
||||
|
@ -352,6 +352,40 @@ def _validate_jwt(config, password):
|
|||
'OR JWT auth is misconfigured.') % (username, err_msg))
|
||||
|
||||
|
||||
def _validate_keystone(config, password):
|
||||
""" Validates the Keystone authentication system. """
|
||||
if config.get('AUTHENTICATION_TYPE', 'Database') != 'Keystone':
|
||||
return
|
||||
|
||||
auth_url = config.get('KEYSTONE_AUTH_URL')
|
||||
admin_username = config.get('KEYSTONE_ADMIN_USERNAME')
|
||||
admin_password = config.get('KEYSTONE_ADMIN_PASSWORD')
|
||||
admin_tenant = config.get('KEYSTONE_ADMIN_TENANT')
|
||||
|
||||
if not auth_url:
|
||||
raise Exception('Missing authentication URL')
|
||||
|
||||
if not admin_username:
|
||||
raise Exception('Missing admin username')
|
||||
|
||||
if not admin_password:
|
||||
raise Exception('Missing admin password')
|
||||
|
||||
if not admin_tenant:
|
||||
raise Exception('Missing admin tenant')
|
||||
|
||||
users = KeystoneUsers(auth_url, admin_username, admin_password, admin_tenant)
|
||||
|
||||
# Verify that the superuser exists. If not, raise an exception.
|
||||
username = get_authenticated_user().username
|
||||
|
||||
(result, err_msg) = users.verify_user(username, password)
|
||||
if not result:
|
||||
raise Exception(('Verification of superuser %s failed: %s \n\nThe user either does not ' +
|
||||
'exist in the remote authentication system ' +
|
||||
'OR Keystone auth is misconfigured.') % (username, err_msg))
|
||||
|
||||
|
||||
_VALIDATORS = {
|
||||
'database': _validate_database,
|
||||
'redis': _validate_redis,
|
||||
|
@ -365,4 +399,5 @@ _VALIDATORS = {
|
|||
'ssl': _validate_ssl,
|
||||
'ldap': _validate_ldap,
|
||||
'jwt': _validate_jwt,
|
||||
'keystone': _validate_keystone,
|
||||
}
|
Reference in a new issue