diff --git a/almanach/api/auth/keystone_auth.py b/almanach/api/auth/keystone_auth.py index 82dc210..913877c 100644 --- a/almanach/api/auth/keystone_auth.py +++ b/almanach/api/auth/keystone_auth.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from keystoneauth1 import exceptions as keystoneauth1_exceptions from keystoneauth1.identity import v3 from keystoneauth1 import session from keystoneclient.v3 import client as keystone_client @@ -23,9 +24,14 @@ from almanach.core import exception class KeystoneAuthentication(base_auth.BaseAuth): def __init__(self, config): - auth = v3.Password(username=config.auth.keystone_username, - password=config.auth.keystone_password, - auth_url=config.auth.keystone_url) + auth = v3.Password(username=config.keystone_authtoken.username, + password=config.keystone_authtoken.password, + user_domain_id=config.keystone_authtoken.user_domain_id, + user_domain_name=config.keystone_authtoken.user_domain_name, + project_domain_name=config.keystone_authtoken.project_domain_name, + project_name=config.keystone_authtoken.project_name, + auth_url=config.keystone_authtoken.auth_url) + sess = session.Session(auth=auth) self._client = keystone_client.Client(session=sess) @@ -33,9 +39,10 @@ class KeystoneAuthentication(base_auth.BaseAuth): if token is None: raise exception.AuthenticationFailureException('No token provided') - result = self._client.tokens.validate(token) - - if not result: - raise exception.AuthenticationFailureException('Invalid token') + try: + if not self._client.tokens.validate(token): + raise exception.AuthenticationFailureException('Invalid token') + except keystoneauth1_exceptions.HttpError as e: + raise exception.AuthenticationFailureException(e.message) return True diff --git a/almanach/core/opts.py b/almanach/core/opts.py index 66e5d90..65d628a 100644 --- a/almanach/core/opts.py +++ b/almanach/core/opts.py @@ -54,22 +54,37 @@ collector_opts = [ help='Delay in seconds between retries'), ] +keystone_opts = [ + cfg.StrOpt('username', + help='Keystone service username'), + cfg.StrOpt('password', + secret=True, + help='Keystone service password'), + cfg.StrOpt('user_domain_id', + default='default', + help='Keystone service user domain ID'), + cfg.StrOpt('user_domain_name', + default='Default', + help='Keystone service user domain name'), + cfg.StrOpt('project_domain_name', + default='Default', + help='Keystone service project domain name'), + cfg.StrOpt('project_name', + default='service', + help='Keystone service project name'), + cfg.StrOpt('auth_url', + default='http://127.0.0.1:35357/v3', + help='Keystone API V3 admin endpoint'), +] + auth_opts = [ cfg.StrOpt('strategy', default='private_key', - help='Authentication driver for the API'), + help='Authentication driver for the API: private_key or keystone'), cfg.StrOpt('private_key', secret=True, default='secret', help='Private key for private key authentication'), - cfg.StrOpt('keystone_username', - help='Keystone service username'), - cfg.StrOpt('keystone_password', - secret=True, - help='Keystone service password'), - cfg.StrOpt('keystone_url', - default='http://keystone_url:5000/v3', - help='Keystone URL'), ] resource_opts = [ @@ -86,6 +101,7 @@ CONF.register_opts(database_opts, group='database') CONF.register_opts(api_opts, group='api') CONF.register_opts(collector_opts, group='collector') CONF.register_opts(auth_opts, group='auth') +CONF.register_opts(keystone_opts, group='keystone_authtoken') CONF.register_opts(resource_opts, group='resources') logging.register_options(CONF) @@ -97,6 +113,7 @@ def list_opts(): ('database', database_opts), ('api', api_opts), ('collector', collector_opts), - ('api.auth', auth_opts), + ('auth', auth_opts), + ('keystone_authtoken', keystone_opts), ('resources', resource_opts), ] diff --git a/almanach/tests/unit/api/auth/test_keystone_auth.py b/almanach/tests/unit/api/auth/test_keystone_auth.py index 58b925d..aad191b 100644 --- a/almanach/tests/unit/api/auth/test_keystone_auth.py +++ b/almanach/tests/unit/api/auth/test_keystone_auth.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from keystoneauth1 import exceptions as keystoneauth1_exceptions import mock from almanach.api.auth import keystone_auth @@ -43,6 +44,12 @@ class KeystoneAuthenticationTest(base.BaseTestCase): self.assertRaises(exception.AuthenticationFailureException, self.driver.validate, token) self.validation_mock.assert_called_once_with(token) + def test_with_http_error(self): + token = 'some keystone token' + self.validation_mock.side_effect = keystoneauth1_exceptions.HttpError(message='Some Error') + self.assertRaises(exception.AuthenticationFailureException, self.driver.validate, token) + self.validation_mock.assert_called_once_with(token) + def test_with_empty_token(self): token = None self.assertRaises(exception.AuthenticationFailureException, self.driver.validate, token) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index bd23949..6deb7a0 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -35,9 +35,13 @@ function almanach_configure { iniset $ALMANACH_CONF auth strategy keystone - iniset $ALMANACH_CONF auth keystone_username almanach - iniset $ALMANACH_CONF auth keystone_password $SERVICE_PASSWORD - iniset $ALMANACH_CONF auth keystone_url $KEYSTONE_SERVICE_URI/v2.0 + iniset $ALMANACH_CONF keystone_authtoken username almanach + iniset $ALMANACH_CONF keystone_authtoken password $SERVICE_PASSWORD + iniset $ALMANACH_CONF keystone_authtoken user_domain_id default + iniset $ALMANACH_CONF keystone_authtoken user_domain_name $SERVICE_DOMAIN_NAME + iniset $ALMANACH_CONF keystone_authtoken project_domain_name $SERVICE_DOMAIN_NAME + iniset $ALMANACH_CONF keystone_authtoken project_name $SERVICE_PROJECT_NAME + iniset $ALMANACH_CONF keystone_authtoken auth_url $KEYSTONE_SERVICE_URI_V3 iniset $ALMANACH_CONF collector transport_url rabbit://stackrabbit:secret@localhost:5672 diff --git a/doc/source/index.rst b/doc/source/index.rst index e0601b4..34bc953 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -76,7 +76,7 @@ Protocol The authentication mechanism use the HTTP header :code:`X-Auth-Token` to send a token. This token is validated through Keystone or with the config file (private secret key). -:: code:: raw +.. code:: raw GET /volume_types HTTP/1.1 X-Auth-Token: secret @@ -93,7 +93,7 @@ Private Key Authentication The private secret key authentication is the default method. In your config file, you have to define your private key in the field :code:`auth_token`: -:: code:: raw +.. code:: raw [auth] strategy = private_key @@ -106,13 +106,34 @@ Keystone Authentication The token will be validated with Keystone. To use this authentication backend you have to define the authentication strategy to :code:`keystone`. -:: code:: raw +.. code:: raw [auth] strategy = keystone - keystone_username = my_service_username - keystone_password = my_service_password - keystone_url = http://keystone_url:5000/v3 + + [keystone_authtoken] + + # Keystone service username (string value) + username = almanach + + # Keystone service password (string value) + password = secret + + # Keystone service user domain ID (string value) + user_domain_id = default + + # Keystone service user domain name (string value) + user_domain_name = Default + + # Keystone service project domain name (string value) + project_domain_name = Default + + # Keystone service project name (string value) + project_name = service + + # Keystone API V3 admin endpoint (string value) + auth_url = http://127.0.0.1:35357/v3 + RabbitMQ configuration