diff --git a/util/config/validator.py b/util/config/validator.py index 848a8a8e5..f09f9192a 100644 --- a/util/config/validator.py +++ b/util/config/validator.py @@ -15,9 +15,6 @@ from boot import setup_jwt_proxy from data.database import validate_database_url from data.users import LDAP_CERT_FILENAME from data.users.externaljwt import ExternalJWTAuthN -from data.users.externalldap import LDAPConnection, LDAPUsers -from data.users.keystone import get_keystone_users -from storage import get_storage_driver from oauth.services.github import GithubOAuthService from oauth.services.google import GoogleOAuthService from oauth.services.gitlab import GitLabOAuthService @@ -31,6 +28,7 @@ from util.config.validators.validate_redis import RedisValidator from util.config.validators.validate_storage import StorageValidator from util.config.validators.validate_email import EmailValidator from util.config.validators.validate_ldap import LDAPValidator +from util.config.validators.validate_keystone import KeystoneValidator logger = logging.getLogger(__name__) @@ -293,43 +291,6 @@ def _validate_jwt(config, user_obj, password): 'properly: %s' % err_msg) -def _validate_keystone(config, user_obj, password): - """ Validates the Keystone authentication system. """ - if config.get('AUTHENTICATION_TYPE', 'Database') != 'Keystone': - return - - auth_url = config.get('KEYSTONE_AUTH_URL') - auth_version = int(config.get('KEYSTONE_AUTH_VERSION', 2)) - 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 ConfigValidationException('Missing authentication URL') - - if not admin_username: - raise ConfigValidationException('Missing admin username') - - if not admin_password: - raise ConfigValidationException('Missing admin password') - - if not admin_tenant: - raise ConfigValidationException('Missing admin tenant') - - requires_email = config.get('FEATURE_MAILING', True) - users = get_keystone_users(auth_version, auth_url, admin_username, admin_password, admin_tenant, - requires_email) - - # Verify that the superuser exists. If not, raise an exception. - username = user_obj.username - (result, err_msg) = users.verify_credentials(username, password) - if not result: - msg = ('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) - raise ConfigValidationException(msg) - - def _validate_signer(config, user_obj, _): """ Validates the GPG public+private key pair used for signing converted ACIs. """ if config.get('SIGNING_ENGINE') is None: @@ -420,7 +381,7 @@ VALIDATORS = { 'ssl': _validate_ssl, LDAPValidator.name: LDAPValidator.validate, 'jwt': _validate_jwt, - 'keystone': _validate_keystone, + KeystoneValidator.name: KeystoneValidator.validate, 'signer': _validate_signer, 'security-scanner': _validate_security_scanner, 'bittorrent': _validate_bittorrent, diff --git a/util/config/validators/test/test_validate_keystone.py b/util/config/validators/test/test_validate_keystone.py new file mode 100644 index 000000000..77a9e35b0 --- /dev/null +++ b/util/config/validators/test/test_validate_keystone.py @@ -0,0 +1,51 @@ +import pytest + +from util.config.validators import ConfigValidationException +from util.config.validators.validate_keystone import KeystoneValidator +from util.morecollections import AttrDict + +from test.test_keystone_auth import fake_keystone + + +@pytest.mark.parametrize('unvalidated_config', [ + ({}), + ({'AUTHENTICATION_TYPE': 'Database'}), +]) +def test_validate_noop(unvalidated_config): + KeystoneValidator.validate(unvalidated_config, None, None) + +@pytest.mark.parametrize('unvalidated_config', [ + ({'AUTHENTICATION_TYPE': 'Keystone'}), + ({'AUTHENTICATION_TYPE': 'Keystone', 'KEYSTONE_AUTH_URL': 'foo'}), + ({'AUTHENTICATION_TYPE': 'Keystone', 'KEYSTONE_AUTH_URL': 'foo', + 'KEYSTONE_ADMIN_USERNAME': 'bar'}), + ({'AUTHENTICATION_TYPE': 'Keystone', 'KEYSTONE_AUTH_URL': 'foo', + 'KEYSTONE_ADMIN_USERNAME': 'bar', 'KEYSTONE_ADMIN_PASSWORD': 'baz'}), +]) +def test_invalid_config(unvalidated_config): + with pytest.raises(ConfigValidationException): + KeystoneValidator.validate(unvalidated_config, None, None) + + +@pytest.mark.parametrize('username, password, expected_exception', [ + ('invaliduser', 'invalidpass', ConfigValidationException), + ('cool.user', 'invalidpass', ConfigValidationException), + ('invaliduser', 'somepass', ConfigValidationException), + ('cool.user', 'password', None), +]) +def test_validated_keystone(username, password, expected_exception): + with fake_keystone(2) as keystone_auth: + auth_url = keystone_auth.auth_url + + config = {} + config['AUTHENTICATION_TYPE'] = 'Keystone' + config['KEYSTONE_AUTH_URL'] = auth_url + config['KEYSTONE_ADMIN_USERNAME'] = 'adminuser' + config['KEYSTONE_ADMIN_PASSWORD'] = 'adminpass' + config['KEYSTONE_ADMIN_TENANT'] = 'admintenant' + + if expected_exception is not None: + with pytest.raises(ConfigValidationException): + KeystoneValidator.validate(config, AttrDict(dict(username=username)), password) + else: + KeystoneValidator.validate(config, AttrDict(dict(username=username)), password) diff --git a/util/config/validators/validate_keystone.py b/util/config/validators/validate_keystone.py new file mode 100644 index 000000000..415f7958b --- /dev/null +++ b/util/config/validators/validate_keystone.py @@ -0,0 +1,42 @@ +from util.config.validators import BaseValidator, ConfigValidationException +from data.users.keystone import get_keystone_users + +class KeystoneValidator(BaseValidator): + name = "keystone" + + @classmethod + def validate(cls, config, user, user_password): + """ Validates the Keystone authentication system. """ + if config.get('AUTHENTICATION_TYPE', 'Database') != 'Keystone': + return + + auth_url = config.get('KEYSTONE_AUTH_URL') + auth_version = int(config.get('KEYSTONE_AUTH_VERSION', 2)) + 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 ConfigValidationException('Missing authentication URL') + + if not admin_username: + raise ConfigValidationException('Missing admin username') + + if not admin_password: + raise ConfigValidationException('Missing admin password') + + if not admin_tenant: + raise ConfigValidationException('Missing admin tenant') + + requires_email = config.get('FEATURE_MAILING', True) + users = get_keystone_users(auth_version, auth_url, admin_username, admin_password, admin_tenant, + requires_email) + + # Verify that the superuser exists. If not, raise an exception. + username = user.username + (result, err_msg) = users.verify_credentials(username, user_password) + if not result: + msg = ('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) + raise ConfigValidationException(msg)