Skip to content

Commit

Permalink
More flexible specification of auth credentials.
Browse files Browse the repository at this point in the history
Fixes bug 853933

Add new --username|--password|--tenant|--auth_url|--auth_strategy
switches to bin/glance to allow the username, password, tenant name,
and authentication URL & strategy be specified on the command line.

Avoid needlessly falling back to keystone v2 auth after a successful:

  GET /v1.0/tokens

returns with the X-Image-Management-Url or X-Glance header set,
as opposed to X-Server-Management-Url.

Extend the keystone functional test support to ensure that the URL
returned by keystone via the X-*-Url header contains the appropriate
dynamically allocated port for the glance API service.

Ensure the underlying $OS_* environment variables do not leak into the
TestPrivateImagesCli functional tests, also explicitly exercise both
noauth and keystone strategies.

Change-Id: Iee8bf3745d65a9c57a9da803d5cf9ae5f343a159
  • Loading branch information
Eoghan Glynn committed Jan 24, 2012
1 parent 0db2cfa commit 6cac288
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 19 deletions.
26 changes: 21 additions & 5 deletions bin/glance
Expand Up @@ -731,11 +731,12 @@ def get_client(options):
specified by the --host and --port options
supplied to the CLI
"""
creds = dict(username=os.getenv('OS_AUTH_USER'),
password=os.getenv('OS_AUTH_KEY'),
tenant=os.getenv('OS_AUTH_TENANT'),
auth_url=os.getenv('OS_AUTH_URL'),
strategy=os.getenv('OS_AUTH_STRATEGY', 'noauth'))
creds = dict(username=options.username or os.getenv('OS_AUTH_USER'),
password=options.password or os.getenv('OS_AUTH_KEY'),
tenant=options.tenant or os.getenv('OS_AUTH_TENANT'),
auth_url=options.auth_url or os.getenv('OS_AUTH_URL'),
strategy=options.auth_strategy or \
os.getenv('OS_AUTH_STRATEGY', 'noauth'))

use_ssl = (options.use_ssl or (
creds['auth_url'] is not None and
Expand Down Expand Up @@ -774,6 +775,21 @@ def create_options(parser):
metavar="TOKEN", default=None,
help="Authentication token to use to identify the "
"client to the glance server")
parser.add_option('-I', '--username', dest="username",
metavar="USER", default=None,
help="User name used to acquire an authentication token")
parser.add_option('-K', '--password', dest="password",
metavar="PASSWORD", default=None,
help="Password used to acquire an authentication token")
parser.add_option('-T', '--tenant', dest="tenant",
metavar="TENANT", default=None,
help="Tenant name")
parser.add_option('-N', '--auth_url', dest="auth_url",
metavar="AUTH_URL", default=None,
help="Authentication URL")
parser.add_option('-S', '--auth_strategy', dest="auth_strategy",
metavar="STRATEGY", default=None,
help="Authentication strategy (keystone or noauth)")
parser.add_option('--limit', dest="limit", metavar="LIMIT", default=10,
type="int", help="Page size to use while "
"requesting image metadata")
Expand Down
26 changes: 26 additions & 0 deletions doc/source/authentication.rst
Expand Up @@ -84,6 +84,32 @@ The final step is to verify that the `OS_AUTH_` crednetials are present::
OS_AUTH_URL=<THIS SHOULD POINT TO KEYSTONE>
OS_AUTH_STRATEGY=keystone

Alternatively, these credentials may be specified using the following
switches to the ``bin/glance`` command:

-I USER, --username=USER
User name used to acquire an authentication token
-K PASSWORD, --password=PASSWORD
Password used to acquire an authentication token
-T TENANT, --tenant=TENANT
Tenant name
-N AUTH_URL, --auth_url=AUTH_URL
Authentication URL
-S STRATEGY, --auth_strategy=STRATEGY
Authentication strategy (keystone or noauth)

Or, if a pre-authenticated token is preferred, the following option allows
the client-side interaction with keystone to be by-passed (useful if a long
sequence of commands is being scripted):

-A TOKEN, --auth_token=TOKEN
Authentication token to use to identify the client to
the glance server

In general the command line switch takes precedence over the corresponding
OS_AUTH_* environment variable, if both are set.


Configuring the Glance servers to use Keystone
----------------------------------------------

Expand Down
12 changes: 11 additions & 1 deletion glance/common/auth.py
Expand Up @@ -140,9 +140,19 @@ def _v1_auth(self, token_url):

resp, resp_body = self._do_request(token_url, 'GET', headers=headers)

def _management_url(self, resp):
for url_header in ('x-image-management-url',
'x-server-management-url',
'x-glance'):
try:
return resp[url_header]
except KeyError as e:
not_found = e
raise not_found

if resp.status in (200, 204):
try:
self.management_url = resp['x-server-management-url']
self.management_url = _management_url(self, resp)
self.auth_token = resp['x-auth-token']
except KeyError:
raise exception.AuthorizationFailure()
Expand Down
31 changes: 29 additions & 2 deletions glance/tests/functional/data/keystone_data.py
Expand Up @@ -15,6 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.

import re
import sys

import keystone.manage

DEFAULT_FIXTURE = [
Expand Down Expand Up @@ -85,7 +88,7 @@
'http://nova.publicinternets.com/v1.1/', 'http://127.0.0.1:8774/v1.1',
'http://localhost:8774/v1.1', '1', '0'),
('endpointTemplates', 'add', 'RegionOne', 'glance',
'http://glance.publicinternets.com/v1.1/%tenant_id%',
'http://127.0.0.1:%api_port%/v1',
'http://nova.admin-nets.local/v1.1/%tenant_id%',
'http://127.0.0.1:9292/v1.1/%tenant_id%', '1', '0'),
('endpointTemplates', 'add', 'RegionOne', 'cdn',
Expand Down Expand Up @@ -140,10 +143,34 @@
]


def _get_subsitutions(args):
substitutions = {}
for arg in args:
matches = re.match('(.*)=(.*)', arg)
if matches:
token = '%%%s%%' % matches.group(1)
value = matches.group(2)
substitutions[token] = value
return substitutions


def _expand(cmd, substitutions):
expanded = ()
for word in cmd:
for token in substitutions.keys():
word = word.replace(token, substitutions[token])
expanded = expanded + (word,)
return expanded


def load_fixture(fixture=DEFAULT_FIXTURE, args=None):
substitutions = _get_subsitutions(sys.argv)

keystone.manage.parse_args(args)

for cmd in fixture:
keystone.manage.process(*cmd)
expanded = _expand(cmd, substitutions)
keystone.manage.process(*expanded)


def main():
Expand Down
6 changes: 5 additions & 1 deletion glance/tests/functional/keystone_utils.py
Expand Up @@ -66,6 +66,7 @@ def __init__(self, server_control, server_name, test_dir, port,
backends = keystone.backends.sqlalchemy
service-header-mappings = {
'nova' : 'X-Server-Management-Url',
'glance' : 'X-Image-Management-Url',
'swift' : 'X-Storage-Url',
'cdn' : 'X-CDN-Management-Url'}
service_host = 0.0.0.0
Expand Down Expand Up @@ -247,7 +248,10 @@ def start_servers(self, **kwargs):
keystone_conf = self.auth_server.write_conf(**kwargs)
datafile = os.path.join(os.path.dirname(__file__), 'data',
'keystone_data.py')
execute("python %s -c %s" % (datafile, keystone_conf))

cmd = "python %s -c %s api_port=%d" % \
(datafile, keystone_conf, self.api_server.bind_port)
execute(cmd)

# Start keystone-auth
exitcode, out, err = self.auth_server.start(**kwargs)
Expand Down
66 changes: 56 additions & 10 deletions glance/tests/functional/test_private_images.py
Expand Up @@ -19,6 +19,7 @@

import httplib2
import json
import os

from glance.tests import functional
from glance.tests.functional import keystone_utils
Expand Down Expand Up @@ -741,21 +742,25 @@ class TestPrivateImagesCli(keystone_utils.KeystoneTests):
bin/glance.
"""

@skip_if_disabled
def test_glance_cli(self):
def setUp(self):
"""
Test that we can upload an owned image; that we can manipulate
its is_public setting; and that appropriate authorization
checks are applied to other (non-admin) users.
Clear environment to ensure that pre-existing $OS_* variables
do not leak into test.
"""
self._clear_os_env()
super(TestPrivateImagesCli, self).setUp()

def _do_test_glance_cli(self, cmd):
"""
Test that we can upload an owned image with a given command line;
that we can manipulate its is_public setting; and that appropriate
authorization checks are applied to other (non-admin) users.
"""
self.cleanup()
self.start_servers()

# Add a non-public image
cmd = ("echo testdata | bin/glance --port=%d --auth_token=%s add "
"name=MyImage" %
(self.api_port, keystone_utils.pattieblack_token))
exitcode, out, err = execute(cmd)
# Add a non-public image using the given glance command line
exitcode, out, err = execute("echo testdata | %s" % cmd)

self.assertEqual(0, exitcode)
image_id = out.strip()[25:]
Expand Down Expand Up @@ -833,3 +838,44 @@ def test_glance_cli(self):
self.assertEqual(response['x-image-meta-owner'], '')

self.stop_servers()

def _clear_os_env(self):
os.environ.pop('OS_AUTH_URL', None)
os.environ.pop('OS_AUTH_STRATEGY', None)
os.environ.pop('OS_AUTH_USER', None)
os.environ.pop('OS_AUTH_KEY', None)

@skip_if_disabled
def test_glance_cli_noauth_strategy(self):
"""
Test the CLI with the noauth strategy defaulted to.
"""
cmd = ("bin/glance --port=%d --auth_token=%s add name=MyImage" %
(self.api_port, keystone_utils.pattieblack_token))
self._do_test_glance_cli(cmd)

@skip_if_disabled
def test_glance_cli_keystone_strategy_switches(self):
"""
Test the CLI with the keystone (v1) strategy enabled via
command line switches.
"""
substitutions = (self.api_port, self.auth_port, 'keystone',
'pattieblack', 'secrete')
cmd = ("bin/glance --port=%d --auth_url=http://localhost:%d/v1.0 "
"--auth_strategy=%s --username=%s --password=%s "
" add name=MyImage" % substitutions)
self._do_test_glance_cli(cmd)

@skip_if_disabled
def test_glance_cli_keystone_strategy_environment(self):
"""
Test the CLI with the keystone strategy enabled via
environment variables.
"""
os.environ['OS_AUTH_URL'] = 'http://localhost:%d/v1.0' % self.auth_port
os.environ['OS_AUTH_STRATEGY'] = 'keystone'
os.environ['OS_AUTH_USER'] = 'pattieblack'
os.environ['OS_AUTH_KEY'] = 'secrete'
cmd = "bin/glance --port=%d add name=MyImage" % self.api_port
self._do_test_glance_cli(cmd)

0 comments on commit 6cac288

Please sign in to comment.