Browse Source

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
tags/11.0.0.0rc1
Hemanth Makkapati 4 years ago
parent
commit
dcbf54672c

+ 2
- 2
glance/registry/client/v1/api.py View File

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

+ 48
- 11
glance/scrubber.py View File

@@ -42,6 +42,19 @@ scrubber_opts = [
42 42
                       'performing a delete.')),
43 43
     cfg.BoolOpt('delayed_delete', default=False,
44 44
                 help=_('Turn on/off delayed delete.')),
45
+    cfg.StrOpt('admin_role', default='admin',
46
+               help=_('Role used to identify an authenticated user as '
47
+                      'administrator.')),
48
+    cfg.BoolOpt('send_identity_headers', default=False,
49
+                help=_("Whether to pass through headers containing user "
50
+                       "and tenant information when making requests to "
51
+                       "the registry. This allows the registry to use the "
52
+                       "context middleware without keystonemiddleware's "
53
+                       "auth_token middleware, removing calls to the keystone "
54
+                       "auth service. It is recommended that when using this "
55
+                       "option, secure communication between glance api and "
56
+                       "glance registry is ensured by means other than "
57
+                       "auth_token middleware.")),
45 58
 ]
46 59
 
47 60
 scrubber_cmd_opts = [
@@ -73,12 +86,24 @@ class ScrubDBQueue(object):
73 86
         self.metadata_encryption_key = CONF.metadata_encryption_key
74 87
         registry.configure_registry_client()
75 88
         registry.configure_registry_admin_creds()
76
-        self.registry = registry.get_registry_client(context.RequestContext())
77
-        admin_tenant_name = CONF.admin_tenant_name
78
-        admin_token = self.registry.auth_token
79
-        self.admin_context = context.RequestContext(user=CONF.admin_user,
80
-                                                    tenant=admin_tenant_name,
81
-                                                    auth_token=admin_token)
89
+        admin_user = CONF.admin_user
90
+        admin_tenant = CONF.admin_tenant_name
91
+
92
+        if CONF.send_identity_headers:
93
+            # When registry is operating in trusted-auth mode
94
+            roles = [CONF.admin_role]
95
+            self.admin_context = context.RequestContext(user=admin_user,
96
+                                                        tenant=admin_tenant,
97
+                                                        auth_token=None,
98
+                                                        roles=roles)
99
+            self.registry = registry.get_registry_client(self.admin_context)
100
+        else:
101
+            ctxt = context.RequestContext()
102
+            self.registry = registry.get_registry_client(ctxt)
103
+            admin_token = self.registry.auth_token
104
+            self.admin_context = context.RequestContext(user=admin_user,
105
+                                                        tenant=admin_tenant,
106
+                                                        auth_token=admin_token)
82 107
 
83 108
     def add_location(self, image_id, location):
84 109
         """Adding image location to scrub queue.
@@ -215,15 +240,27 @@ class Scrubber(object):
215 240
 
216 241
         registry.configure_registry_client()
217 242
         registry.configure_registry_admin_creds()
218
-        self.registry = registry.get_registry_client(context.RequestContext())
219 243
 
220 244
         # Here we create a request context with credentials to support
221 245
         # delayed delete when using multi-tenant backend storage
246
+        admin_user = CONF.admin_user
222 247
         admin_tenant = CONF.admin_tenant_name
223
-        auth_token = self.registry.auth_token
224
-        self.admin_context = context.RequestContext(user=CONF.admin_user,
225
-                                                    tenant=admin_tenant,
226
-                                                    auth_token=auth_token)
248
+
249
+        if CONF.send_identity_headers:
250
+            # When registry is operating in trusted-auth mode
251
+            roles = [CONF.admin_role]
252
+            self.admin_context = context.RequestContext(user=admin_user,
253
+                                                        tenant=admin_tenant,
254
+                                                        auth_token=None,
255
+                                                        roles=roles)
256
+            self.registry = registry.get_registry_client(self.admin_context)
257
+        else:
258
+            ctxt = context.RequestContext()
259
+            self.registry = registry.get_registry_client(ctxt)
260
+            auth_token = self.registry.auth_token
261
+            self.admin_context = context.RequestContext(user=admin_user,
262
+                                                        tenant=admin_tenant,
263
+                                                        auth_token=auth_token)
227 264
 
228 265
         self.db_queue = get_scrub_queue()
229 266
 

+ 8
- 0
glance/tests/functional/__init__.py View File

@@ -317,6 +317,8 @@ class ApiServer(Server):
317 317
         self.location_strategy = 'location_order'
318 318
         self.store_type_location_strategy_preference = ""
319 319
 
320
+        self.send_identity_headers = False
321
+
320 322
         self.conf_base = """[DEFAULT]
321 323
 verbose = %(verbose)s
322 324
 debug = %(debug)s
@@ -336,6 +338,7 @@ delayed_delete = %(delayed_delete)s
336 338
 owner_is_tenant = %(owner_is_tenant)s
337 339
 workers = %(workers)s
338 340
 scrub_time = %(scrub_time)s
341
+send_identity_headers = %(send_identity_headers)s
339 342
 image_cache_dir = %(image_cache_dir)s
340 343
 image_cache_driver = %(image_cache_driver)s
341 344
 data_api = %(data_api)s
@@ -541,6 +544,9 @@ class ScrubberDaemon(Server):
541 544
         self.policy_file = policy_file
542 545
         self.policy_default_rule = 'default'
543 546
 
547
+        self.send_identity_headers = False
548
+        self.admin_role = 'admin'
549
+
544 550
         self.conf_base = """[DEFAULT]
545 551
 verbose = %(verbose)s
546 552
 debug = %(debug)s
@@ -555,6 +561,8 @@ metadata_encryption_key = %(metadata_encryption_key)s
555 561
 lock_path = %(lock_path)s
556 562
 sql_connection = %(sql_connection)s
557 563
 sql_idle_timeout = 3600
564
+send_identity_headers = %(send_identity_headers)s
565
+admin_role = %(admin_role)s
558 566
 [oslo_policy]
559 567
 policy_file = %(policy_file)s
560 568
 policy_default_rule = %(policy_default_rule)s

+ 110
- 2
glance/tests/functional/test_scrubber.py View File

@@ -76,6 +76,55 @@ class TestScrubber(functional.FunctionalTest):
76 76
 
77 77
         self.stop_servers()
78 78
 
79
+    def test_delayed_delete_with_trustedauth_registry(self):
80
+        """
81
+        test that images don't get deleted immediately and that the scrubber
82
+        scrubs them when registry is operating in trustedauth mode
83
+        """
84
+        self.cleanup()
85
+        self.api_server.deployment_flavor = 'noauth'
86
+        self.registry_server.deployment_flavor = 'trusted-auth'
87
+        self.start_servers(delayed_delete=True, daemon=True,
88
+                           metadata_encryption_key='',
89
+                           send_identity_headers=True)
90
+        base_headers = {
91
+            'X-Identity-Status': 'Confirmed',
92
+            'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
93
+            'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
94
+            'X-Tenant-Id': 'deae8923-075d-4287-924b-840fb2644874',
95
+            'X-Roles': 'admin',
96
+        }
97
+        headers = {
98
+            'x-image-meta-name': 'test_image',
99
+            'x-image-meta-is_public': 'true',
100
+            'x-image-meta-disk_format': 'raw',
101
+            'x-image-meta-container_format': 'ovf',
102
+            'content-type': 'application/octet-stream',
103
+        }
104
+        headers.update(base_headers)
105
+        path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
106
+        http = httplib2.Http()
107
+        response, content = http.request(path, 'POST', body='XXX',
108
+                                         headers=headers)
109
+        self.assertEqual(201, response.status)
110
+        image = jsonutils.loads(content)['image']
111
+        self.assertEqual('active', image['status'])
112
+        image_id = image['id']
113
+
114
+        path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
115
+                                              image_id)
116
+        http = httplib2.Http()
117
+        response, content = http.request(path, 'DELETE', headers=base_headers)
118
+        self.assertEqual(200, response.status)
119
+
120
+        response, content = http.request(path, 'HEAD', headers=base_headers)
121
+        self.assertEqual(200, response.status)
122
+        self.assertEqual('pending_delete', response['x-image-meta-status'])
123
+
124
+        self.wait_for_scrub(path, headers=base_headers)
125
+
126
+        self.stop_servers()
127
+
79 128
     def test_scrubber_app(self):
80 129
         """
81 130
         test that the glance-scrubber script runs successfully when not in
@@ -114,6 +163,65 @@ class TestScrubber(functional.FunctionalTest):
114 163
 
115 164
         self.stop_servers()
116 165
 
166
+    def test_scrubber_app_with_trustedauth_registry(self):
167
+        """
168
+        test that the glance-scrubber script runs successfully when not in
169
+        daemon mode and with a registry that operates in trustedauth mode
170
+        """
171
+        self.cleanup()
172
+        self.api_server.deployment_flavor = 'noauth'
173
+        self.registry_server.deployment_flavor = 'trusted-auth'
174
+        self.start_servers(delayed_delete=True, daemon=False,
175
+                           metadata_encryption_key='',
176
+                           send_identity_headers=True)
177
+        base_headers = {
178
+            'X-Identity-Status': 'Confirmed',
179
+            'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
180
+            'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
181
+            'X-Tenant-Id': 'deae8923-075d-4287-924b-840fb2644874',
182
+            'X-Roles': 'admin',
183
+        }
184
+        headers = {
185
+            'x-image-meta-name': 'test_image',
186
+            'x-image-meta-is_public': 'true',
187
+            'x-image-meta-disk_format': 'raw',
188
+            'x-image-meta-container_format': 'ovf',
189
+            'content-type': 'application/octet-stream',
190
+        }
191
+        headers.update(base_headers)
192
+        path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
193
+        http = httplib2.Http()
194
+        response, content = http.request(path, 'POST', body='XXX',
195
+                                         headers=headers)
196
+        self.assertEqual(201, response.status)
197
+        image = jsonutils.loads(content)['image']
198
+        self.assertEqual('active', image['status'])
199
+        image_id = image['id']
200
+
201
+        path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
202
+                                              image_id)
203
+        http = httplib2.Http()
204
+        response, content = http.request(path, 'DELETE', headers=base_headers)
205
+        self.assertEqual(200, response.status)
206
+
207
+        response, content = http.request(path, 'HEAD', headers=base_headers)
208
+        self.assertEqual(200, response.status)
209
+        self.assertEqual('pending_delete', response['x-image-meta-status'])
210
+
211
+        # wait for the scrub time on the image to pass
212
+        time.sleep(self.api_server.scrub_time)
213
+
214
+        # scrub images and make sure they get deleted
215
+        exe_cmd = "%s -m glance.cmd.scrubber" % sys.executable
216
+        cmd = ("%s --config-file %s" %
217
+               (exe_cmd, self.scrubber_daemon.conf_file_name))
218
+        exitcode, out, err = execute(cmd, raise_error=False)
219
+        self.assertEqual(0, exitcode)
220
+
221
+        self.wait_for_scrub(path, headers=base_headers)
222
+
223
+        self.stop_servers()
224
+
117 225
     def test_scrubber_delete_handles_exception(self):
118 226
         """
119 227
         Test that the scrubber handles the case where an
@@ -165,7 +273,7 @@ class TestScrubber(functional.FunctionalTest):
165 273
 
166 274
         self.stop_servers()
167 275
 
168
-    def wait_for_scrub(self, path):
276
+    def wait_for_scrub(self, path, headers=None):
169 277
         """
170 278
         NOTE(jkoelker) The build servers sometimes take longer than 15 seconds
171 279
         to scrub. Give it up to 5 min, checking checking every 15 seconds.
@@ -177,7 +285,7 @@ class TestScrubber(functional.FunctionalTest):
177 285
         for _ in range(wait_for / check_every):
178 286
             time.sleep(check_every)
179 287
 
180
-            response, content = http.request(path, 'HEAD')
288
+            response, content = http.request(path, 'HEAD', headers=headers)
181 289
             if (response['x-image-meta-status'] == 'deleted' and
182 290
                     response['x-image-meta-deleted'] == 'True'):
183 291
                 break

+ 2
- 2
glance/tests/unit/v1/test_registry_client.py View File

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

Loading…
Cancel
Save