Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: gslib/commands/signurl.py

Issue 698893003: Update checked in version of gsutil to version 4.6 (Closed) Base URL: http://dart.googlecode.com/svn/third_party/gsutil/
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « gslib/commands/setmeta.py ('k') | gslib/commands/stat.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 # -*- coding: utf-8 -*-
2 # Copyright 2014 Google Inc. All Rights Reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 """Implementation of Url Signing workflow.
16
17 see: https://developers.google.com/storage/docs/accesscontrol#Signed-URLs)
18 """
19
20 from __future__ import absolute_import
21
22 import base64
23 import calendar
24 from datetime import datetime
25 from datetime import timedelta
26 import getpass
27 import re
28 import time
29 import urllib
30
31 import httplib2
32
33 from gslib.command import Command
34 from gslib.cs_api_map import ApiSelector
35 from gslib.exception import CommandException
36 from gslib.storage_url import ContainsWildcard
37 from gslib.storage_url import StorageUrlFromString
38 from gslib.util import GetNewHttp
39 from gslib.util import NO_MAX
40
41 try:
42 # Check for openssl.
43 # pylint: disable=C6204
44 from OpenSSL.crypto import load_pkcs12
45 from OpenSSL.crypto import sign
46 HAVE_OPENSSL = True
47 except ImportError:
48 load_pkcs12 = None
49 sign = None
50 HAVE_OPENSSL = False
51
52 _DETAILED_HELP_TEXT = ("""
53 <B>SYNOPSIS</B>
54 gsutil signurl pkcs12-file url...
55
56
57 <B>DESCRIPTION</B>
58 The signurl command will generate signed urls that can be used to access
59 the specified objects without authentication for a specific period of time.
60
61 Please see the `Signed URLs documentation
62 https://developers.google.com/storage/docs/accesscontrol#Signed-URLs` for
63 background about signed URLs.
64
65 Multiple gs:// urls may be provided and may contain wildcards. A signed url
66 will be produced for each provided url, authorized
67 for the specified HTTP method and valid for the given duration.
68
69 Note: Unlike the gsutil ls command, the signurl command does not support
70 operations on sub-directories. For example, if you run the command:
71
72 gsutil signurl <private-key-file> gs://some-bucket/some-object/
73
74 The signurl command uses the private key for a service account (the
75 '<private-key-file>' argument) to generate the cryptographic
76 signature for the generated URL. The private key file must be in PKCS12
77 format. The signurl command will prompt for the passphrase used to protect
78 the private key file (default 'notasecret'). For more information
79 regarding generating a private key for use with the signurl command please
80 see the `Authentication documentation.
81 https://developers.google.com/storage/docs/authentication#generating-a-private -key`
82
83 gsutil will look up information about the object "some-object/" (with a
84 trailing slash) inside bucket "some-bucket", as opposed to operating on
85 objects nested under gs://some-bucket/some-object. Unless you actually
86 have an object with that name, the operation will fail.
87
88 <B>OPTIONS</B>
89 -m Specifies the HTTP method to be authorized for use
90 with the signed url, default is GET.
91
92 -d Specifies the duration that the signed url should be valid
93 for, default duration is 1 hour.
94
95 Times may be specified with no suffix (default hours), or
96 with s = seconds, m = minutes, h = hours, d = days.
97
98 This option may be specified multiple times, in which case
99 the duration the link remains valid is the sum of all the
100 duration options.
101
102 -c Specifies the content type for which the signed url is
103 valid for.
104
105 -p Specify the keystore password instead of prompting.
106
107 <B>USAGE</B>
108
109 Create a signed url for downloading an object valid for 10 minutes:
110
111 gsutil signurl <private-key-file> -d 10m gs://<bucket>/<object>
112
113 Create a signed url for uploading a plain text file via HTTP PUT:
114
115 gsutil signurl <private-key-file> -m PUT -d 1h -c text/plain gs://<bucket>/< obj>
116
117 To construct a signed URL that allows anyone in possession of
118 the URL to PUT to the specified bucket for one day, creating
119 any object of Content-Type image/jpg, run:
120
121 gsutil signurl <private-key-file> -m PUT -d 1d -c image/jpg gs://<bucket>/<o bj>
122
123
124 """)
125
126
127 def _DurationToTimeDelta(duration):
128 r"""Parses the given duration and returns an equivalent timedelta."""
129
130 match = re.match(r'^(\d+)([dDhHmMsS])?$', duration)
131 if not match:
132 raise CommandException('Unable to parse duration string')
133
134 duration, modifier = match.groups('h')
135 duration = int(duration)
136 modifier = modifier.lower()
137
138 if modifier == 'd':
139 ret = timedelta(days=duration)
140 elif modifier == 'h':
141 ret = timedelta(hours=duration)
142 elif modifier == 'm':
143 ret = timedelta(minutes=duration)
144 elif modifier == 's':
145 ret = timedelta(seconds=duration)
146
147 return ret
148
149
150 def _GenSignedUrl(key, client_id, method, md5,
151 content_type, expiration, gcs_path):
152 """Construct a string to sign with the provided key and returns \
153 the complete url."""
154
155 tosign = ('{0}\n{1}\n{2}\n{3}\n/{4}'
156 .format(method, md5, content_type,
157 expiration, gcs_path))
158 signature = base64.b64encode(sign(key, tosign, 'RSA-SHA256'))
159
160 final_url = ('https://storage.googleapis.com/{0}?'
161 'GoogleAccessId={1}&Expires={2}&Signature={3}'
162 .format(gcs_path, client_id, expiration,
163 urllib.quote_plus(str(signature))))
164
165 return final_url
166
167
168 def _ReadKeystore(ks_contents, passwd):
169 ks = load_pkcs12(ks_contents, passwd)
170 client_id = (ks.get_certificate()
171 .get_subject()
172 .CN.replace('.apps.googleusercontent.com',
173 '@developer.gserviceaccount.com'))
174
175 return ks, client_id
176
177
178 class UrlSignCommand(Command):
179 """Implementation of gsutil url_sign command."""
180
181 # Command specification. See base class for documentation.
182 command_spec = Command.CreateCommandSpec(
183 'signurl',
184 command_name_aliases=['signedurl', 'queryauth'],
185 min_args=2,
186 max_args=NO_MAX,
187 supported_sub_args='m:d:c:p:',
188 file_url_ok=False,
189 provider_url_ok=False,
190 urls_start_arg=1,
191 gs_api_support=[ApiSelector.XML, ApiSelector.JSON],
192 gs_default_api=ApiSelector.JSON,
193 )
194 # Help specification. See help_provider.py for documentation.
195 help_spec = Command.HelpSpec(
196 help_name='signurl',
197 help_name_aliases=['signedurl', 'queryauth'],
198 help_type='command_help',
199 help_one_line_summary='Create a signed url',
200 help_text=_DETAILED_HELP_TEXT,
201 subcommand_help_text={},
202 )
203
204 def _ParseSubOpts(self):
205 # Default argument values
206 delta = None
207 method = 'GET'
208 content_type = ''
209 passwd = None
210
211 for o, v in self.sub_opts:
212 if o == '-d':
213 if delta is not None:
214 delta += _DurationToTimeDelta(v)
215 else:
216 delta = _DurationToTimeDelta(v)
217 elif o == '-m':
218 method = v
219 elif o == '-c':
220 content_type = v
221 elif o == '-p':
222 passwd = v
223
224 if delta is None:
225 delta = timedelta(hours=1)
226
227 expiration = calendar.timegm((datetime.utcnow() + delta).utctimetuple())
228 if method not in ['GET', 'PUT', 'DELETE', 'HEAD']:
229 raise CommandException('HTTP method must be one of [GET|HEAD|PUT|DELETE]')
230
231 return method, expiration, content_type, passwd
232
233 def _CheckClientCanRead(self, key, client_id, gcs_path):
234 """Performs a head request against a signed url to check for read access."""
235
236 signed_url = _GenSignedUrl(key, client_id,
237 'HEAD', '', '',
238 int(time.time()) + 10,
239 gcs_path)
240 h = GetNewHttp()
241 try:
242 response, _ = h.request(signed_url, 'HEAD')
243
244 return response.status == 200
245 except httplib2.HttpLib2Error as e:
246 raise CommandException('Unexpected error while querying'
247 'object readability ({0})'
248 .format(e.message))
249
250 def _EnumerateStorageUrls(self, in_urls):
251 ret = []
252
253 for url_str in in_urls:
254 if ContainsWildcard(url_str):
255 ret.extend([blr.storage_url for blr in self.WildcardIterator(url_str)])
256 else:
257 ret.append(StorageUrlFromString(url_str))
258
259 return ret
260
261 def RunCommand(self):
262 """Command entry point for signurl command."""
263 if not HAVE_OPENSSL:
264 raise CommandException(
265 'The signurl command requires the pyopenssl library (try pip '
266 'install pyopenssl or easy_install pyopenssl)')
267
268 method, expiration, content_type, passwd = self._ParseSubOpts()
269 storage_urls = self._EnumerateStorageUrls(self.args[1:])
270
271 if not passwd:
272 passwd = getpass.getpass('Keystore password:')
273
274 ks, client_id = _ReadKeystore(open(self.args[0], 'rb').read(), passwd)
275
276 print 'URL\tHTTP Method\tExpiration\tSigned URL'
277 for url in storage_urls:
278 if url.scheme != 'gs':
279 raise CommandException('Can only create signed urls from gs:// urls')
280 if url.IsBucket():
281 gcs_path = url.bucket_name
282 else:
283 gcs_path = '{0}/{1}'.format(url.bucket_name, url.object_name)
284
285 final_url = _GenSignedUrl(ks.get_privatekey(), client_id,
286 method, '', content_type, expiration,
287 gcs_path)
288
289 expiration_dt = datetime.fromtimestamp(expiration)
290
291 print '{0}\t{1}\t{2}\t{3}'.format(url, method,
292 (expiration_dt
293 .strftime('%Y-%m-%d %H:%M:%S')),
294 final_url)
295 if (method != 'PUT' and
296 not self._CheckClientCanRead(ks.get_privatekey(),
297 client_id,
298 gcs_path)):
299 self.logger.warn(
300 '%s does not have permissions on %s, using this link will likely '
301 'result in a 403 error until at least READ permissions are granted',
302 client_id, url)
303
304 return 0
OLDNEW
« no previous file with comments | « gslib/commands/setmeta.py ('k') | gslib/commands/stat.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698