1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
1 |
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
2 |
||
3 |
# Copyright 2013 OpenStack LLC.
|
|
4 |
# All Rights Reserved.
|
|
5 |
#
|
|
6 |
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
7 |
# not use this file except in compliance with the License. You may obtain
|
|
8 |
# a copy of the License at
|
|
9 |
#
|
|
10 |
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11 |
#
|
|
12 |
# Unless required by applicable law or agreed to in writing, software
|
|
13 |
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
14 |
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
15 |
# License for the specific language governing permissions and limitations
|
|
16 |
# under the License.
|
|
17 |
||
18 |
"""
|
|
19 |
This migration handles migrating encrypted image location values from
|
|
20 |
the unquoted form to the quoted form.
|
|
21 |
||
22 |
If 'metadata_encryption_key' is specified in the config then this
|
|
23 |
migration performs the following steps for every entry in the images table:
|
|
24 |
1. Decrypt the location value with the metadata_encryption_key
|
|
25 |
2. Changes the value to its quoted form
|
|
26 |
3. Encrypts the new value with the metadata_encryption_key
|
|
27 |
4. Inserts the new value back into the row
|
|
28 |
||
29 |
Fixes bug #1081043
|
|
30 |
"""
|
|
31 |
import types |
|
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
32 |
import urllib |
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
33 |
import urlparse |
34 |
||
35 |
from oslo.config import cfg |
|
36 |
import sqlalchemy |
|
37 |
||
38 |
from glance.common import crypt |
|
39 |
from glance.common import exception |
|
40 |
import glance.openstack.common.log as logging |
|
41 |
import glance.store.swift |
|
42 |
||
43 |
LOG = logging.getLogger(__name__) |
|
44 |
CONF = cfg.CONF |
|
45 |
||
1.1.50
by Chuck Short
Import upstream version 2013.1~rc1 |
46 |
CONF.import_opt('metadata_encryption_key', 'glance.common.config') |
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
47 |
|
48 |
||
49 |
def upgrade(migrate_engine): |
|
50 |
migrate_location_credentials(migrate_engine, to_quoted=True) |
|
51 |
||
52 |
||
53 |
def downgrade(migrate_engine): |
|
54 |
migrate_location_credentials(migrate_engine, to_quoted=False) |
|
55 |
||
56 |
||
57 |
def migrate_location_credentials(migrate_engine, to_quoted): |
|
58 |
"""
|
|
59 |
Migrate location credentials for encrypted swift uri's between the
|
|
60 |
quoted and unquoted forms.
|
|
61 |
||
62 |
:param migrate_engine: The configured db engine
|
|
63 |
:param to_quoted: If True, migrate location credentials from
|
|
64 |
unquoted to quoted form. If False, do the
|
|
65 |
reverse.
|
|
66 |
"""
|
|
67 |
if not CONF.metadata_encryption_key: |
|
68 |
msg = _("'metadata_encryption_key' was not specified in the config" |
|
69 |
" file or a config file was not specified. This means that"
|
|
70 |
" this migration is a NOOP.") |
|
71 |
LOG.info(msg) |
|
72 |
return
|
|
73 |
||
74 |
meta = sqlalchemy.schema.MetaData() |
|
75 |
meta.bind = migrate_engine |
|
76 |
||
77 |
images_table = sqlalchemy.Table('images', meta, autoload=True) |
|
78 |
||
79 |
images = list(images_table.select().execute()) |
|
80 |
||
81 |
for image in images: |
|
82 |
try: |
|
83 |
fixed_uri = fix_uri_credentials(image['location'], to_quoted) |
|
84 |
images_table.update()\ |
|
85 |
.where(images_table.c.id == image['id'])\ |
|
86 |
.values(location=fixed_uri).execute() |
|
87 |
except exception.Invalid: |
|
88 |
msg = _("Failed to decrypt location value for image %s") |
|
89 |
LOG.warn(msg % image['id']) |
|
90 |
||
91 |
||
92 |
def decrypt_location(uri): |
|
93 |
return crypt.urlsafe_decrypt(CONF.metadata_encryption_key, uri) |
|
94 |
||
95 |
||
96 |
def encrypt_location(uri): |
|
97 |
return crypt.urlsafe_encrypt(CONF.metadata_encryption_key, uri, 64) |
|
98 |
||
99 |
||
100 |
def fix_uri_credentials(uri, to_quoted): |
|
101 |
"""
|
|
102 |
Fix the given uri's embedded credentials by round-tripping with
|
|
103 |
StoreLocation.
|
|
104 |
||
105 |
If to_quoted is True, the uri is assumed to have credentials that
|
|
106 |
have not been quoted, and the resulting uri will contain quoted
|
|
107 |
credentials.
|
|
108 |
||
109 |
If to_quoted is False, the uri is assumed to have credentials that
|
|
110 |
have been quoted, and the resulting uri will contain credentials
|
|
111 |
that have not been quoted.
|
|
112 |
"""
|
|
113 |
if not uri: |
|
114 |
return
|
|
115 |
try: |
|
116 |
decrypted_uri = decrypt_location(uri) |
|
117 |
#NOTE (ameade): If a uri is not encrypted or incorrectly encoded then we
|
|
118 |
# we raise an exception.
|
|
119 |
except (TypeError, ValueError) as e: |
|
120 |
raise exception.Invalid(str(e)) |
|
121 |
||
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
122 |
return legacy_parse_uri(decrypted_uri, to_quoted) |
123 |
||
124 |
||
125 |
def legacy_parse_uri(uri, to_quote): |
|
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
126 |
"""
|
127 |
Parse URLs. This method fixes an issue where credentials specified
|
|
128 |
in the URL are interpreted differently in Python 2.6.1+ than prior
|
|
129 |
versions of Python. It also deals with the peculiarity that new-style
|
|
130 |
Swift URIs have where a username can contain a ':', like so:
|
|
131 |
||
132 |
swift://account:user:pass@authurl.com/container/obj
|
|
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
133 |
|
134 |
If to_quoted is True, the uri is assumed to have credentials that
|
|
135 |
have not been quoted, and the resulting uri will contain quoted
|
|
136 |
credentials.
|
|
137 |
||
138 |
If to_quoted is False, the uri is assumed to have credentials that
|
|
139 |
have been quoted, and the resulting uri will contain credentials
|
|
140 |
that have not been quoted.
|
|
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
141 |
"""
|
142 |
# Make sure that URIs that contain multiple schemes, such as:
|
|
143 |
# swift://user:pass@http://authurl.com/v1/container/obj
|
|
144 |
# are immediately rejected.
|
|
145 |
if uri.count('://') != 1: |
|
146 |
reason = _("URI cannot contain more than one occurrence of a scheme." |
|
147 |
"If you have specified a URI like "
|
|
148 |
"swift://user:pass@http://authurl.com/v1/container/obj"
|
|
149 |
", you need to change it to use the swift+http:// scheme, "
|
|
150 |
"like so: "
|
|
151 |
"swift+http://user:pass@authurl.com/v1/container/obj") |
|
152 |
||
153 |
LOG.error(_("Invalid store uri %(uri)s: %(reason)s") % locals()) |
|
154 |
raise exception.BadStoreUri(message=reason) |
|
155 |
||
156 |
pieces = urlparse.urlparse(uri) |
|
157 |
assert pieces.scheme in ('swift', 'swift+http', 'swift+https') |
|
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
158 |
scheme = pieces.scheme |
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
159 |
netloc = pieces.netloc |
160 |
path = pieces.path.lstrip('/') |
|
161 |
if netloc != '': |
|
162 |
# > Python 2.6.1
|
|
163 |
if '@' in netloc: |
|
164 |
creds, netloc = netloc.split('@') |
|
165 |
else: |
|
166 |
creds = None |
|
167 |
else: |
|
168 |
# Python 2.6.1 compat
|
|
169 |
# see lp659445 and Python issue7904
|
|
170 |
if '@' in path: |
|
171 |
creds, path = path.split('@') |
|
172 |
else: |
|
173 |
creds = None |
|
174 |
netloc = path[0:path.find('/')].strip('/') |
|
175 |
path = path[path.find('/'):].strip('/') |
|
176 |
if creds: |
|
177 |
cred_parts = creds.split(':') |
|
178 |
||
179 |
# User can be account:user, in which case cred_parts[0:2] will be
|
|
180 |
# the account and user. Combine them into a single username of
|
|
181 |
# account:user
|
|
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
182 |
if to_quote: |
183 |
if len(cred_parts) == 1: |
|
184 |
reason = (_("Badly formed credentials '%(creds)s' in Swift " |
|
185 |
"URI") % locals()) |
|
186 |
LOG.error(reason) |
|
187 |
raise exception.BadStoreUri() |
|
188 |
elif len(cred_parts) == 3: |
|
189 |
user = ':'.join(cred_parts[0:2]) |
|
190 |
else: |
|
191 |
user = cred_parts[0] |
|
192 |
key = cred_parts[-1] |
|
193 |
user = user |
|
194 |
key = key |
|
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
195 |
else: |
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
196 |
if len(cred_parts) != 2: |
197 |
reason = (_("Badly formed credentials in Swift URI.")) |
|
198 |
LOG.debug(reason) |
|
199 |
raise exception.BadStoreUri() |
|
200 |
user, key = cred_parts |
|
201 |
user = urllib.unquote(user) |
|
202 |
key = urllib.unquote(key) |
|
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
203 |
else: |
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
204 |
user = None |
205 |
key = None |
|
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
206 |
path_parts = path.split('/') |
207 |
try: |
|
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
208 |
obj = path_parts.pop() |
209 |
container = path_parts.pop() |
|
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
210 |
if not netloc.startswith('http'): |
211 |
# push hostname back into the remaining to build full authurl
|
|
212 |
path_parts.insert(0, netloc) |
|
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
213 |
auth_or_store_url = '/'.join(path_parts) |
1.1.49
by Chuck Short
Import upstream version 2013.1.g3 |
214 |
except IndexError: |
215 |
reason = _("Badly formed S3 URI: %s") % uri |
|
216 |
LOG.error(message=reason) |
|
217 |
raise exception.BadStoreUri() |
|
1.1.52
by Chuck Short
Import upstream version 2013.2~b1 |
218 |
|
219 |
if auth_or_store_url.startswith('http://'): |
|
220 |
auth_or_store_url = auth_or_store_url[len('http://'):] |
|
221 |
elif auth_or_store_url.startswith('https://'): |
|
222 |
auth_or_store_url = auth_or_store_url[len('https://'):] |
|
223 |
||
224 |
credstring = '' |
|
225 |
if user and key: |
|
226 |
if to_quote: |
|
227 |
quote_user = urllib.quote(user) |
|
228 |
quote_key = urllib.quote(key) |
|
229 |
else: |
|
230 |
quote_user = user |
|
231 |
quote_key = key |
|
232 |
credstring = '%s:%s@' % (quote_user, quote_key) |
|
233 |
||
234 |
auth_or_store_url = auth_or_store_url.strip('/') |
|
235 |
container = container.strip('/') |
|
236 |
obj = obj.strip('/') |
|
237 |
||
238 |
uri = '%s://%s%s/%s/%s' % (scheme, credstring, auth_or_store_url, |
|
239 |
container, obj) |
|
240 |
return encrypt_location(uri) |