Scrubber to communicate with trustedauth registry

Glance scrubber needs admin context when attempting to talk to
registry to list, update and delete images. However, when registry
is deployed in trusted-auth mode, appropriate admin role is required
to gain admin context on registry.

Closes-bug: #1439666

Change-Id: I5ff68ef8f30e73642889f8e4cde7ba06628cb0e5
This commit is contained in:
Hemanth Makkapati 2015-04-01 23:19:06 -04:00
parent 1225ab48e5
commit dcbf54672c
5 changed files with 170 additions and 17 deletions

View File

@ -131,8 +131,8 @@ def get_registry_client(cxt):
if CONF.send_identity_headers: if CONF.send_identity_headers:
identity_headers = { identity_headers = {
'X-User-Id': cxt.user, 'X-User-Id': cxt.user or '',
'X-Tenant-Id': cxt.tenant, 'X-Tenant-Id': cxt.tenant or '',
'X-Roles': ','.join(cxt.roles), 'X-Roles': ','.join(cxt.roles),
'X-Identity-Status': 'Confirmed', 'X-Identity-Status': 'Confirmed',
'X-Service-Catalog': jsonutils.dumps(cxt.service_catalog), 'X-Service-Catalog': jsonutils.dumps(cxt.service_catalog),

View File

@ -42,6 +42,19 @@ scrubber_opts = [
'performing a delete.')), 'performing a delete.')),
cfg.BoolOpt('delayed_delete', default=False, cfg.BoolOpt('delayed_delete', default=False,
help=_('Turn on/off delayed delete.')), help=_('Turn on/off delayed delete.')),
cfg.StrOpt('admin_role', default='admin',
help=_('Role used to identify an authenticated user as '
'administrator.')),
cfg.BoolOpt('send_identity_headers', default=False,
help=_("Whether to pass through headers containing user "
"and tenant information when making requests to "
"the registry. This allows the registry to use the "
"context middleware without keystonemiddleware's "
"auth_token middleware, removing calls to the keystone "
"auth service. It is recommended that when using this "
"option, secure communication between glance api and "
"glance registry is ensured by means other than "
"auth_token middleware.")),
] ]
scrubber_cmd_opts = [ scrubber_cmd_opts = [
@ -73,12 +86,24 @@ class ScrubDBQueue(object):
self.metadata_encryption_key = CONF.metadata_encryption_key self.metadata_encryption_key = CONF.metadata_encryption_key
registry.configure_registry_client() registry.configure_registry_client()
registry.configure_registry_admin_creds() registry.configure_registry_admin_creds()
self.registry = registry.get_registry_client(context.RequestContext()) admin_user = CONF.admin_user
admin_tenant_name = CONF.admin_tenant_name admin_tenant = CONF.admin_tenant_name
admin_token = self.registry.auth_token
self.admin_context = context.RequestContext(user=CONF.admin_user, if CONF.send_identity_headers:
tenant=admin_tenant_name, # When registry is operating in trusted-auth mode
auth_token=admin_token) roles = [CONF.admin_role]
self.admin_context = context.RequestContext(user=admin_user,
tenant=admin_tenant,
auth_token=None,
roles=roles)
self.registry = registry.get_registry_client(self.admin_context)
else:
ctxt = context.RequestContext()
self.registry = registry.get_registry_client(ctxt)
admin_token = self.registry.auth_token
self.admin_context = context.RequestContext(user=admin_user,
tenant=admin_tenant,
auth_token=admin_token)
def add_location(self, image_id, location): def add_location(self, image_id, location):
"""Adding image location to scrub queue. """Adding image location to scrub queue.
@ -215,15 +240,27 @@ class Scrubber(object):
registry.configure_registry_client() registry.configure_registry_client()
registry.configure_registry_admin_creds() registry.configure_registry_admin_creds()
self.registry = registry.get_registry_client(context.RequestContext())
# Here we create a request context with credentials to support # Here we create a request context with credentials to support
# delayed delete when using multi-tenant backend storage # delayed delete when using multi-tenant backend storage
admin_user = CONF.admin_user
admin_tenant = CONF.admin_tenant_name admin_tenant = CONF.admin_tenant_name
auth_token = self.registry.auth_token
self.admin_context = context.RequestContext(user=CONF.admin_user, if CONF.send_identity_headers:
tenant=admin_tenant, # When registry is operating in trusted-auth mode
auth_token=auth_token) roles = [CONF.admin_role]
self.admin_context = context.RequestContext(user=admin_user,
tenant=admin_tenant,
auth_token=None,
roles=roles)
self.registry = registry.get_registry_client(self.admin_context)
else:
ctxt = context.RequestContext()
self.registry = registry.get_registry_client(ctxt)
auth_token = self.registry.auth_token
self.admin_context = context.RequestContext(user=admin_user,
tenant=admin_tenant,
auth_token=auth_token)
self.db_queue = get_scrub_queue() self.db_queue = get_scrub_queue()

View File

@ -317,6 +317,8 @@ class ApiServer(Server):
self.location_strategy = 'location_order' self.location_strategy = 'location_order'
self.store_type_location_strategy_preference = "" self.store_type_location_strategy_preference = ""
self.send_identity_headers = False
self.conf_base = """[DEFAULT] self.conf_base = """[DEFAULT]
verbose = %(verbose)s verbose = %(verbose)s
debug = %(debug)s debug = %(debug)s
@ -336,6 +338,7 @@ delayed_delete = %(delayed_delete)s
owner_is_tenant = %(owner_is_tenant)s owner_is_tenant = %(owner_is_tenant)s
workers = %(workers)s workers = %(workers)s
scrub_time = %(scrub_time)s scrub_time = %(scrub_time)s
send_identity_headers = %(send_identity_headers)s
image_cache_dir = %(image_cache_dir)s image_cache_dir = %(image_cache_dir)s
image_cache_driver = %(image_cache_driver)s image_cache_driver = %(image_cache_driver)s
data_api = %(data_api)s data_api = %(data_api)s
@ -541,6 +544,9 @@ class ScrubberDaemon(Server):
self.policy_file = policy_file self.policy_file = policy_file
self.policy_default_rule = 'default' self.policy_default_rule = 'default'
self.send_identity_headers = False
self.admin_role = 'admin'
self.conf_base = """[DEFAULT] self.conf_base = """[DEFAULT]
verbose = %(verbose)s verbose = %(verbose)s
debug = %(debug)s debug = %(debug)s
@ -555,6 +561,8 @@ metadata_encryption_key = %(metadata_encryption_key)s
lock_path = %(lock_path)s lock_path = %(lock_path)s
sql_connection = %(sql_connection)s sql_connection = %(sql_connection)s
sql_idle_timeout = 3600 sql_idle_timeout = 3600
send_identity_headers = %(send_identity_headers)s
admin_role = %(admin_role)s
[oslo_policy] [oslo_policy]
policy_file = %(policy_file)s policy_file = %(policy_file)s
policy_default_rule = %(policy_default_rule)s policy_default_rule = %(policy_default_rule)s

View File

@ -76,6 +76,55 @@ class TestScrubber(functional.FunctionalTest):
self.stop_servers() self.stop_servers()
def test_delayed_delete_with_trustedauth_registry(self):
"""
test that images don't get deleted immediately and that the scrubber
scrubs them when registry is operating in trustedauth mode
"""
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.registry_server.deployment_flavor = 'trusted-auth'
self.start_servers(delayed_delete=True, daemon=True,
metadata_encryption_key='',
send_identity_headers=True)
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': 'deae8923-075d-4287-924b-840fb2644874',
'X-Roles': 'admin',
}
headers = {
'x-image-meta-name': 'test_image',
'x-image-meta-is_public': 'true',
'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'content-type': 'application/octet-stream',
}
headers.update(base_headers)
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', body='XXX',
headers=headers)
self.assertEqual(201, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
image_id = image['id']
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE', headers=base_headers)
self.assertEqual(200, response.status)
response, content = http.request(path, 'HEAD', headers=base_headers)
self.assertEqual(200, response.status)
self.assertEqual('pending_delete', response['x-image-meta-status'])
self.wait_for_scrub(path, headers=base_headers)
self.stop_servers()
def test_scrubber_app(self): def test_scrubber_app(self):
""" """
test that the glance-scrubber script runs successfully when not in test that the glance-scrubber script runs successfully when not in
@ -114,6 +163,65 @@ class TestScrubber(functional.FunctionalTest):
self.stop_servers() self.stop_servers()
def test_scrubber_app_with_trustedauth_registry(self):
"""
test that the glance-scrubber script runs successfully when not in
daemon mode and with a registry that operates in trustedauth mode
"""
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.registry_server.deployment_flavor = 'trusted-auth'
self.start_servers(delayed_delete=True, daemon=False,
metadata_encryption_key='',
send_identity_headers=True)
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': 'deae8923-075d-4287-924b-840fb2644874',
'X-Roles': 'admin',
}
headers = {
'x-image-meta-name': 'test_image',
'x-image-meta-is_public': 'true',
'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'content-type': 'application/octet-stream',
}
headers.update(base_headers)
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', body='XXX',
headers=headers)
self.assertEqual(201, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
image_id = image['id']
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE', headers=base_headers)
self.assertEqual(200, response.status)
response, content = http.request(path, 'HEAD', headers=base_headers)
self.assertEqual(200, response.status)
self.assertEqual('pending_delete', response['x-image-meta-status'])
# wait for the scrub time on the image to pass
time.sleep(self.api_server.scrub_time)
# scrub images and make sure they get deleted
exe_cmd = "%s -m glance.cmd.scrubber" % sys.executable
cmd = ("%s --config-file %s" %
(exe_cmd, self.scrubber_daemon.conf_file_name))
exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(0, exitcode)
self.wait_for_scrub(path, headers=base_headers)
self.stop_servers()
def test_scrubber_delete_handles_exception(self): def test_scrubber_delete_handles_exception(self):
""" """
Test that the scrubber handles the case where an Test that the scrubber handles the case where an
@ -165,7 +273,7 @@ class TestScrubber(functional.FunctionalTest):
self.stop_servers() self.stop_servers()
def wait_for_scrub(self, path): def wait_for_scrub(self, path, headers=None):
""" """
NOTE(jkoelker) The build servers sometimes take longer than 15 seconds NOTE(jkoelker) The build servers sometimes take longer than 15 seconds
to scrub. Give it up to 5 min, checking checking every 15 seconds. to scrub. Give it up to 5 min, checking checking every 15 seconds.
@ -177,7 +285,7 @@ class TestScrubber(functional.FunctionalTest):
for _ in range(wait_for / check_every): for _ in range(wait_for / check_every):
time.sleep(check_every) time.sleep(check_every)
response, content = http.request(path, 'HEAD') response, content = http.request(path, 'HEAD', headers=headers)
if (response['x-image-meta-status'] == 'deleted' and if (response['x-image-meta-status'] == 'deleted' and
response['x-image-meta-deleted'] == 'True'): response['x-image-meta-deleted'] == 'True'):
break break

View File

@ -855,8 +855,8 @@ class TestRegistryV1ClientApi(base.IsolatedUnitTest):
def test_get_registry_client_with_identity_headers(self): def test_get_registry_client_with_identity_headers(self):
self.config(send_identity_headers=True) self.config(send_identity_headers=True)
expected_identity_headers = { expected_identity_headers = {
'X-User-Id': self.context.user, 'X-User-Id': '',
'X-Tenant-Id': self.context.tenant, 'X-Tenant-Id': '',
'X-Roles': ','.join(self.context.roles), 'X-Roles': ','.join(self.context.roles),
'X-Identity-Status': 'Confirmed', 'X-Identity-Status': 'Confirmed',
'X-Service-Catalog': 'null', 'X-Service-Catalog': 'null',