Add fake Keystone
* enabled 'fake' authentication in nailgun by default * add fake keystone API and middleware to nailgun * fixed problem with not configured logging in keystoneclient - now fuelclient silents all logs * removed TEST_MODE as it's not needed in fake auth Related to blueprint access-control-master-node Closes-Bug: 1340141 Change-Id: I76cb7d1cb19be8e0d23ecc03fdbc968b1eeaff5c
This commit is contained in:
parent
7375949ef9
commit
37d0f23a26
|
@ -13,6 +13,7 @@
|
|||
# under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import urllib2
|
||||
|
||||
|
@ -21,6 +22,13 @@ import yaml
|
|||
from keystoneclient import client as auth_client
|
||||
|
||||
from fuelclient.cli.error import exceptions_decorator
|
||||
from fuelclient.logs import NullHandler
|
||||
|
||||
|
||||
# configure logging to silent all logs
|
||||
# and prevent issues in keystoneclient logging
|
||||
logger = logging.getLogger()
|
||||
logger.addHandler(NullHandler())
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
@ -29,7 +37,6 @@ class Client(object):
|
|||
|
||||
def __init__(self):
|
||||
self.debug = False
|
||||
self.test_mod = bool(os.environ.get('TEST_MODE', ''))
|
||||
path_to_config = "/etc/fuel/client/config.yaml"
|
||||
defaults = {
|
||||
"SERVER_ADDRESS": "127.0.0.1",
|
||||
|
@ -73,10 +80,9 @@ class Client(object):
|
|||
|
||||
def auth_status(self):
|
||||
self.auth_required = False
|
||||
if not self.test_mod:
|
||||
request = urllib2.urlopen(''.join([self.api_root, 'version']))
|
||||
self.auth_required = json.loads(
|
||||
request.read()).get('auth_required', False)
|
||||
request = urllib2.urlopen(''.join([self.api_root, 'version']))
|
||||
self.auth_required = json.loads(
|
||||
request.read()).get('auth_required', False)
|
||||
|
||||
def update_own_password(self, new_pass):
|
||||
if self.auth_token:
|
||||
|
@ -84,7 +90,7 @@ class Client(object):
|
|||
self.password, new_pass)
|
||||
|
||||
def initialize_keystone_client(self):
|
||||
if not self.test_mod and self.auth_required:
|
||||
if self.auth_required:
|
||||
self.keystone_client = auth_client.Client(
|
||||
username=self.user,
|
||||
password=self.password,
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
"""This handler does nothing. It's intended to be used to avoid the
|
||||
"No handlers could be found for logger XXX" one-off warning. This
|
||||
important for library code, which may contain code to log events.
|
||||
of the library does not configure logging, the one-off warning mig
|
||||
produced; to avoid this, the library developer simply needs to ins
|
||||
a NullHandler and add it to the top-level logger of the library mo
|
||||
package.
|
||||
|
||||
Taken from Python 2.7
|
||||
"""
|
||||
def handle(self, record):
|
||||
pass
|
||||
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
def createLock(self):
|
||||
self.lock = None
|
|
@ -102,7 +102,6 @@ class BaseTestCase(TestCase):
|
|||
|
||||
def run_cli_command(self, command_line, check_errors=False):
|
||||
modified_env = os.environ.copy()
|
||||
modified_env['TEST_MODE'] = 'True'
|
||||
command_args = [" ".join((self.fuel_path, command_line))]
|
||||
process_handle = subprocess.Popen(
|
||||
command_args,
|
||||
|
|
|
@ -28,7 +28,8 @@ from nailgun.logger import HTTPLoggerMiddleware
|
|||
from nailgun.logger import logger
|
||||
from nailgun.middleware.http_method_override import \
|
||||
HTTPMethodOverrideMiddleware
|
||||
from nailgun.middleware.keystone import NailgunAuthProtocol
|
||||
from nailgun.middleware.keystone import NailgunFakeKeystoneAuthMiddleware
|
||||
from nailgun.middleware.keystone import NailgunKeystoneAuthMiddleware
|
||||
from nailgun.middleware.static import StaticMiddleware
|
||||
from nailgun.settings import settings
|
||||
from nailgun.urls import urls
|
||||
|
@ -55,7 +56,9 @@ def build_middleware(app):
|
|||
middleware_list.append(StaticMiddleware)
|
||||
|
||||
if settings.AUTH['AUTHENTICATION_METHOD'] == 'keystone':
|
||||
middleware_list.append(NailgunAuthProtocol)
|
||||
middleware_list.append(NailgunKeystoneAuthMiddleware)
|
||||
elif settings.AUTH['AUTHENTICATION_METHOD'] == 'fake':
|
||||
middleware_list.append(NailgunFakeKeystoneAuthMiddleware)
|
||||
|
||||
logger.debug('Initialize middleware: %s' %
|
||||
(map(lambda x: x.__name__, middleware_list)))
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
|
||||
from nailgun.settings import settings
|
||||
|
||||
|
||||
def validate_password_credentials(username, password, **kwargs):
|
||||
return (username == settings.FAKE_KEYSTONE_USERNAME and
|
||||
password == settings.FAKE_KEYSTONE_PASSWORD)
|
||||
|
||||
|
||||
def validate_token(token):
|
||||
return token.startswith('token')
|
||||
|
||||
|
||||
def generate_token():
|
||||
return 'token' + str(int(time.time()))
|
|
@ -0,0 +1,105 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from nailgun.api.v1.handlers.base import BaseHandler
|
||||
from nailgun.api.v1.handlers.base import content_json
|
||||
from nailgun.fake_keystone import generate_token
|
||||
from nailgun.fake_keystone import validate_password_credentials
|
||||
from nailgun.fake_keystone import validate_token
|
||||
from nailgun.settings import settings
|
||||
|
||||
|
||||
class TokensHandler(BaseHandler):
|
||||
|
||||
@content_json
|
||||
def POST(self):
|
||||
data = self.checked_data()
|
||||
try:
|
||||
if 'passwordCredentials' in data['auth']:
|
||||
if not validate_password_credentials(
|
||||
**data['auth']['passwordCredentials']):
|
||||
raise self.http(401)
|
||||
elif 'token' in data['auth']:
|
||||
if not validate_token(data['auth']['token']['id']):
|
||||
raise self.http(401)
|
||||
else:
|
||||
raise self.http(400)
|
||||
except (KeyError, TypeError):
|
||||
raise self.http(400)
|
||||
|
||||
token = generate_token()
|
||||
|
||||
return {
|
||||
"access": {
|
||||
"token": {
|
||||
"issued_at": "2012-07-10T13:37:58.708765",
|
||||
"expires": "2012-07-10T14:37:58Z",
|
||||
"id": token,
|
||||
"tenant": {
|
||||
"description": None,
|
||||
"enabled": True,
|
||||
"id": "12345",
|
||||
"name": "admin"
|
||||
}
|
||||
},
|
||||
"serviceCatalog": [],
|
||||
"user": {
|
||||
"username": "admin",
|
||||
"roles_links": [],
|
||||
"id": "9876",
|
||||
"roles": [{"name": "admin"}],
|
||||
"name": "admin"
|
||||
},
|
||||
"metadata": {
|
||||
"is_admin": 0,
|
||||
"roles": ["4567"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class VersionHandler(BaseHandler):
|
||||
|
||||
@content_json
|
||||
def GET(self):
|
||||
keystone_href = 'http://{ip_addr}:{port}/keystone/v2.0/'.format(
|
||||
ip_addr=settings.LISTEN_ADDRESS, port=settings.LISTEN_PORT)
|
||||
return {
|
||||
'version': {
|
||||
'id': 'v2.0',
|
||||
'status': 'stable',
|
||||
'updated': '2014-04-17T00:00:00Z',
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': keystone_href,
|
||||
}, {
|
||||
'rel': 'describedby',
|
||||
'type': 'text/html',
|
||||
'href': 'http://docs.openstack.org/',
|
||||
},
|
||||
],
|
||||
'media-types': [
|
||||
{
|
||||
'base': 'application/json',
|
||||
'type': 'application/vnd.openstack.identity-v2.0+json',
|
||||
}, {
|
||||
'base': 'application/xml',
|
||||
'type': 'application/vnd.openstack.identity-v2.0+xml',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import web
|
||||
|
||||
from nailgun.fake_keystone.handlers import TokensHandler
|
||||
from nailgun.fake_keystone.handlers import VersionHandler
|
||||
|
||||
urls = (
|
||||
r"/v2.0/?$", VersionHandler.__name__,
|
||||
r"/v2.0/tokens/?$", TokensHandler.__name__,
|
||||
)
|
||||
|
||||
_locals = locals()
|
||||
|
||||
|
||||
def app():
|
||||
return web.application(urls, _locals)
|
|
@ -17,6 +17,7 @@
|
|||
import re
|
||||
|
||||
from nailgun.api.v1 import urls as api_urls
|
||||
from nailgun.fake_keystone import validate_token
|
||||
from nailgun.settings import settings
|
||||
|
||||
from keystoneclient.middleware import auth_token
|
||||
|
@ -29,15 +30,13 @@ def public_urls():
|
|||
urls['{0}{1}'.format('/api', url)] = methods
|
||||
urls["/$"] = ['GET']
|
||||
urls["/static"] = ['GET']
|
||||
urls["/keystone"] = ['GET', 'POST']
|
||||
return urls
|
||||
|
||||
|
||||
class NailgunAuthProtocol(auth_token.AuthProtocol):
|
||||
"""A wrapper on Keystone auth_token middleware.
|
||||
|
||||
Does not perform verification of authentication tokens
|
||||
for public routes in the API.
|
||||
|
||||
class SkipAuthMixin(object):
|
||||
"""Mixin which skips verification of authentication tokens for public
|
||||
routes in the API.
|
||||
"""
|
||||
def __init__(self, app):
|
||||
self.public_api_routes = {}
|
||||
|
@ -47,10 +46,9 @@ class NailgunAuthProtocol(auth_token.AuthProtocol):
|
|||
except re.error as e:
|
||||
msg = 'Cannot compile public API routes: {0}'.format(e)
|
||||
|
||||
auth_token.LOG.error(msg)
|
||||
raise Exception(error_msg=msg)
|
||||
|
||||
super(NailgunAuthProtocol, self).__init__(app, settings.AUTH)
|
||||
super(SkipAuthMixin, self).__init__(app, settings.AUTH)
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
path = env.get('PATH_INFO', '/')
|
||||
|
@ -68,4 +66,30 @@ class NailgunAuthProtocol(auth_token.AuthProtocol):
|
|||
|
||||
if env['is_public_api']:
|
||||
return self.app(env, start_response)
|
||||
return super(NailgunAuthProtocol, self).__call__(env, start_response)
|
||||
return super(SkipAuthMixin, self).__call__(env, start_response)
|
||||
|
||||
|
||||
class FakeAuthProtocol(object):
|
||||
"""Auth protocol for fake mode.
|
||||
"""
|
||||
def __init__(self, app, conf):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
if validate_token(env.get('HTTP_X_AUTH_TOKEN', '')):
|
||||
return self.app(env, start_response)
|
||||
else:
|
||||
start_response('401 Unauthorized', [])
|
||||
return ''
|
||||
|
||||
|
||||
class NailgunKeystoneAuthMiddleware(SkipAuthMixin, auth_token.AuthProtocol):
|
||||
"""Auth middleware for keystone.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NailgunFakeKeystoneAuthMiddleware(SkipAuthMixin, FakeAuthProtocol):
|
||||
"""Auth middleware for fake mode.
|
||||
"""
|
||||
pass
|
||||
|
|
|
@ -6,7 +6,7 @@ AUTH:
|
|||
# - none - authentication is disabled
|
||||
# - fake - no keystone required, credentials: admin/admin
|
||||
# - keystone - authentication enabled.
|
||||
AUTHENTICATION_METHOD: "none"
|
||||
AUTHENTICATION_METHOD: "fake"
|
||||
# use only if AUTHENTICATION_METHOD is set to "keystone"
|
||||
admin_token: "ADMIN"
|
||||
auth_host: "127.0.0.1"
|
||||
|
@ -533,6 +533,9 @@ DNS_SEARCH: "example.com"
|
|||
FAKE_TASKS_TICK_INTERVAL: "1"
|
||||
FAKE_TASKS_TICK_COUNT: "30"
|
||||
|
||||
FAKE_KEYSTONE_USERNAME: admin
|
||||
FAKE_KEYSTONE_PASSWORD: admin
|
||||
|
||||
MAX_ITEMS_PER_PAGE: 500
|
||||
|
||||
SHOTGUN_SSH_KEY: "/root/.ssh/id_rsa"
|
||||
|
|
|
@ -44,6 +44,6 @@ class TestVersionHandler(BaseIntegrationTest):
|
|||
"astute_sha": "Unknown build",
|
||||
"fuellib_sha": "Unknown build",
|
||||
"ostf_sha": "Unknown build",
|
||||
"auth_required": False
|
||||
"auth_required": True,
|
||||
}
|
||||
)
|
||||
|
|
|
@ -15,12 +15,17 @@
|
|||
# under the License.
|
||||
|
||||
from nailgun.api.v1 import urls as api_urls
|
||||
from nailgun.fake_keystone import urls as fake_keystone_urls
|
||||
from nailgun.settings import settings
|
||||
from nailgun.webui import urls as webui_urls
|
||||
|
||||
|
||||
def urls():
|
||||
return (
|
||||
urls = [
|
||||
"/api/v1", api_urls.app(),
|
||||
"/api", api_urls.app(),
|
||||
"", webui_urls.app()
|
||||
)
|
||||
]
|
||||
if settings.AUTH['AUTHENTICATION_METHOD'] == 'fake':
|
||||
urls = ["/keystone", fake_keystone_urls.app()] + urls
|
||||
return urls
|
||||
|
|
Loading…
Reference in New Issue