]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/awsauth.py
1 # -*- coding: utf-8 -*-
4 # Copyright (c) 2012-2013 Paul Tax <paultax@gmail.com> All rights reserved.
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are
10 # 1. Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
13 # 2. Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in
15 # the documentation and/or other materials provided with the
18 # 3. Neither the name of Infrae nor the names of its contributors may
19 # be used to endorse or promote products derived from this software
20 # without specific prior written permission.
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INFRAE OR
26 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 from hashlib
import sha1
as sha
40 from urlparse
import urlparse
, unquote
41 from base64
import encodestring
44 from urllib
.parse
import urlparse
, unquote
45 from base64
import encodebytes
as encodestring
47 from email
.utils
import formatdate
49 from requests
.auth
import AuthBase
52 class S3Auth(AuthBase
):
54 """Attaches AWS Authentication to the given Request object."""
56 service_base_url
= 's3.amazonaws.com'
57 # List of Query String Arguments of Interest
59 'acl', 'location', 'logging', 'partNumber', 'policy', 'requestPayment',
60 'torrent', 'versioning', 'versionId', 'versions', 'website', 'uploads',
61 'uploadId', 'response-content-type', 'response-content-language',
62 'response-expires', 'response-cache-control', 'delete', 'lifecycle',
63 'response-content-disposition', 'response-content-encoding', 'tagging',
64 'notification', 'cors'
67 def __init__(self
, access_key
, secret_key
, service_url
=None):
69 self
.service_base_url
= service_url
70 self
.access_key
= str(access_key
)
71 self
.secret_key
= str(secret_key
)
73 def __call__(self
, r
):
74 # Create date header if it is not created yet.
75 if 'date' not in r
.headers
and 'x-amz-date' not in r
.headers
:
76 r
.headers
['date'] = formatdate(
80 signature
= self
.get_signature(r
)
82 signature
= signature
.decode('utf-8')
83 r
.headers
['Authorization'] = 'AWS %s:%s' % (self
.access_key
, signature
)
86 def get_signature(self
, r
):
87 canonical_string
= self
.get_canonical_string(
88 r
.url
, r
.headers
, r
.method
)
90 key
= self
.secret_key
.encode('utf-8')
91 msg
= canonical_string
.encode('utf-8')
93 key
= self
.secret_key
# type: ignore
94 msg
= canonical_string
95 h
= hmac
.new(key
, msg
, digestmod
=sha
)
96 return encodestring(h
.digest()).strip()
98 def get_canonical_string(self
, url
, headers
, method
):
99 parsedurl
= urlparse(url
)
100 objectkey
= parsedurl
.path
[1:]
101 query_args
= sorted(parsedurl
.query
.split('&'))
103 bucket
= parsedurl
.netloc
[:-len(self
.service_base_url
)]
108 interesting_headers
= {
115 if isinstance(lk
, bytes
):
116 lk
= lk
.decode('utf-8')
117 except UnicodeDecodeError:
119 if headers
[key
] and (lk
in interesting_headers
.keys()
120 or lk
.startswith('x-amz-')):
121 interesting_headers
[lk
] = headers
[key
].strip()
123 # If x-amz-date is used it supersedes the date header.
125 if 'x-amz-date' in interesting_headers
:
126 interesting_headers
['date'] = ''
128 if 'x-amz-date' in interesting_headers
:
129 interesting_headers
['date'] = ''
131 buf
= '%s\n' % method
132 for key
in sorted(interesting_headers
.keys()):
133 val
= interesting_headers
[key
]
134 if key
.startswith('x-amz-'):
135 buf
+= '%s:%s\n' % (key
, val
)
139 # append the bucket if it exists
141 buf
+= '/%s' % bucket
143 # add the objectkey. even if it doesn't exist, add the slash
144 buf
+= '/%s' % objectkey
148 # handle special query string arguments
151 if k
in self
.special_params
:
152 buf
+= '&' if params_found
else '?'
156 k
, v
= q
.split('=', 1)
162 # Riak CS multipart upload ids look like this, `TFDSheOgTxC2Tsh1qVK73A==`,
163 # is should be escaped to be included as part of a query string.
165 # A requests mp upload part request may look like
166 # resp = requests.put(
167 # 'https://url_here',
170 # 'uploadId': 'TFDSheOgTxC2Tsh1qVK73A=='
173 # auth=S3Auth('access_key', 'secret_key')
176 # Requests automatically escapes the values in the `params` dict, so now
177 # our uploadId is `TFDSheOgTxC2Tsh1qVK73A%3D%3D`,
178 # if we sign the request with the encoded value the signature will
179 # not be valid, we'll get 403 Access Denied.
180 # So we unquote, this is no-op if the value isn't encoded.
181 buf
+= '{key}={value}'.format(key
=k
, value
=unquote(v
))