OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2010 Mitch Garnaat http://garnaat.org/ |
| 2 # Copyright 2010 Google Inc. |
| 3 # Copyright (c) 2010, Eucalyptus Systems, Inc. |
| 4 # Copyright (c) 2011, Nexenta Systems Inc. |
| 5 # All rights reserved. |
| 6 # |
| 7 # Permission is hereby granted, free of charge, to any person obtaining a |
| 8 # copy of this software and associated documentation files (the |
| 9 # "Software"), to deal in the Software without restriction, including |
| 10 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 11 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 12 # persons to whom the Software is furnished to do so, subject to the fol- |
| 13 # lowing conditions: |
| 14 # |
| 15 # The above copyright notice and this permission notice shall be included |
| 16 # in all copies or substantial portions of the Software. |
| 17 # |
| 18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 19 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 20 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 21 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 22 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 23 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 24 # IN THE SOFTWARE. |
| 25 """ |
| 26 This class encapsulates the provider-specific header differences. |
| 27 """ |
| 28 |
| 29 import os |
| 30 from datetime import datetime |
| 31 |
| 32 import boto |
| 33 from boto import config |
| 34 from boto.gs.acl import ACL |
| 35 from boto.gs.acl import CannedACLStrings as CannedGSACLStrings |
| 36 from boto.s3.acl import CannedACLStrings as CannedS3ACLStrings |
| 37 from boto.s3.acl import Policy |
| 38 |
| 39 |
| 40 HEADER_PREFIX_KEY = 'header_prefix' |
| 41 METADATA_PREFIX_KEY = 'metadata_prefix' |
| 42 |
| 43 AWS_HEADER_PREFIX = 'x-amz-' |
| 44 GOOG_HEADER_PREFIX = 'x-goog-' |
| 45 |
| 46 ACL_HEADER_KEY = 'acl-header' |
| 47 AUTH_HEADER_KEY = 'auth-header' |
| 48 COPY_SOURCE_HEADER_KEY = 'copy-source-header' |
| 49 COPY_SOURCE_VERSION_ID_HEADER_KEY = 'copy-source-version-id-header' |
| 50 COPY_SOURCE_RANGE_HEADER_KEY = 'copy-source-range-header' |
| 51 DELETE_MARKER_HEADER_KEY = 'delete-marker-header' |
| 52 DATE_HEADER_KEY = 'date-header' |
| 53 METADATA_DIRECTIVE_HEADER_KEY = 'metadata-directive-header' |
| 54 RESUMABLE_UPLOAD_HEADER_KEY = 'resumable-upload-header' |
| 55 SECURITY_TOKEN_HEADER_KEY = 'security-token-header' |
| 56 STORAGE_CLASS_HEADER_KEY = 'storage-class' |
| 57 MFA_HEADER_KEY = 'mfa-header' |
| 58 SERVER_SIDE_ENCRYPTION_KEY = 'server-side-encryption-header' |
| 59 VERSION_ID_HEADER_KEY = 'version-id-header' |
| 60 |
| 61 STORAGE_COPY_ERROR = 'StorageCopyError' |
| 62 STORAGE_CREATE_ERROR = 'StorageCreateError' |
| 63 STORAGE_DATA_ERROR = 'StorageDataError' |
| 64 STORAGE_PERMISSIONS_ERROR = 'StoragePermissionsError' |
| 65 STORAGE_RESPONSE_ERROR = 'StorageResponseError' |
| 66 |
| 67 |
| 68 class Provider(object): |
| 69 |
| 70 CredentialMap = { |
| 71 'aws': ('aws_access_key_id', 'aws_secret_access_key'), |
| 72 'google': ('gs_access_key_id', 'gs_secret_access_key'), |
| 73 } |
| 74 |
| 75 AclClassMap = { |
| 76 'aws': Policy, |
| 77 'google': ACL |
| 78 } |
| 79 |
| 80 CannedAclsMap = { |
| 81 'aws': CannedS3ACLStrings, |
| 82 'google': CannedGSACLStrings |
| 83 } |
| 84 |
| 85 HostKeyMap = { |
| 86 'aws': 's3', |
| 87 'google': 'gs' |
| 88 } |
| 89 |
| 90 ChunkedTransferSupport = { |
| 91 'aws': False, |
| 92 'google': True |
| 93 } |
| 94 |
| 95 MetadataServiceSupport = { |
| 96 'aws': True, |
| 97 'google': False |
| 98 } |
| 99 |
| 100 # If you update this map please make sure to put "None" for the |
| 101 # right-hand-side for any headers that don't apply to a provider, rather |
| 102 # than simply leaving that header out (which would cause KeyErrors). |
| 103 HeaderInfoMap = { |
| 104 'aws': { |
| 105 HEADER_PREFIX_KEY: AWS_HEADER_PREFIX, |
| 106 METADATA_PREFIX_KEY: AWS_HEADER_PREFIX + 'meta-', |
| 107 ACL_HEADER_KEY: AWS_HEADER_PREFIX + 'acl', |
| 108 AUTH_HEADER_KEY: 'AWS', |
| 109 COPY_SOURCE_HEADER_KEY: AWS_HEADER_PREFIX + 'copy-source', |
| 110 COPY_SOURCE_VERSION_ID_HEADER_KEY: AWS_HEADER_PREFIX + |
| 111 'copy-source-version-id', |
| 112 COPY_SOURCE_RANGE_HEADER_KEY: AWS_HEADER_PREFIX + |
| 113 'copy-source-range', |
| 114 DATE_HEADER_KEY: AWS_HEADER_PREFIX + 'date', |
| 115 DELETE_MARKER_HEADER_KEY: AWS_HEADER_PREFIX + 'delete-marker', |
| 116 METADATA_DIRECTIVE_HEADER_KEY: AWS_HEADER_PREFIX + |
| 117 'metadata-directive', |
| 118 RESUMABLE_UPLOAD_HEADER_KEY: None, |
| 119 SECURITY_TOKEN_HEADER_KEY: AWS_HEADER_PREFIX + 'security-token', |
| 120 SERVER_SIDE_ENCRYPTION_KEY: AWS_HEADER_PREFIX + 'server-side-encrypt
ion', |
| 121 VERSION_ID_HEADER_KEY: AWS_HEADER_PREFIX + 'version-id', |
| 122 STORAGE_CLASS_HEADER_KEY: AWS_HEADER_PREFIX + 'storage-class', |
| 123 MFA_HEADER_KEY: AWS_HEADER_PREFIX + 'mfa', |
| 124 }, |
| 125 'google': { |
| 126 HEADER_PREFIX_KEY: GOOG_HEADER_PREFIX, |
| 127 METADATA_PREFIX_KEY: GOOG_HEADER_PREFIX + 'meta-', |
| 128 ACL_HEADER_KEY: GOOG_HEADER_PREFIX + 'acl', |
| 129 AUTH_HEADER_KEY: 'GOOG1', |
| 130 COPY_SOURCE_HEADER_KEY: GOOG_HEADER_PREFIX + 'copy-source', |
| 131 COPY_SOURCE_VERSION_ID_HEADER_KEY: GOOG_HEADER_PREFIX + |
| 132 'copy-source-version-id', |
| 133 COPY_SOURCE_RANGE_HEADER_KEY: None, |
| 134 DATE_HEADER_KEY: GOOG_HEADER_PREFIX + 'date', |
| 135 DELETE_MARKER_HEADER_KEY: GOOG_HEADER_PREFIX + 'delete-marker', |
| 136 METADATA_DIRECTIVE_HEADER_KEY: GOOG_HEADER_PREFIX + |
| 137 'metadata-directive', |
| 138 RESUMABLE_UPLOAD_HEADER_KEY: GOOG_HEADER_PREFIX + 'resumable', |
| 139 SECURITY_TOKEN_HEADER_KEY: GOOG_HEADER_PREFIX + 'security-token', |
| 140 SERVER_SIDE_ENCRYPTION_KEY: None, |
| 141 # Note that this version header is not to be confused with |
| 142 # the Google Cloud Storage 'x-goog-api-version' header. |
| 143 VERSION_ID_HEADER_KEY: GOOG_HEADER_PREFIX + 'version-id', |
| 144 STORAGE_CLASS_HEADER_KEY: None, |
| 145 MFA_HEADER_KEY: None, |
| 146 } |
| 147 } |
| 148 |
| 149 ErrorMap = { |
| 150 'aws': { |
| 151 STORAGE_COPY_ERROR: boto.exception.S3CopyError, |
| 152 STORAGE_CREATE_ERROR: boto.exception.S3CreateError, |
| 153 STORAGE_DATA_ERROR: boto.exception.S3DataError, |
| 154 STORAGE_PERMISSIONS_ERROR: boto.exception.S3PermissionsError, |
| 155 STORAGE_RESPONSE_ERROR: boto.exception.S3ResponseError, |
| 156 }, |
| 157 'google': { |
| 158 STORAGE_COPY_ERROR: boto.exception.GSCopyError, |
| 159 STORAGE_CREATE_ERROR: boto.exception.GSCreateError, |
| 160 STORAGE_DATA_ERROR: boto.exception.GSDataError, |
| 161 STORAGE_PERMISSIONS_ERROR: boto.exception.GSPermissionsError, |
| 162 STORAGE_RESPONSE_ERROR: boto.exception.GSResponseError, |
| 163 } |
| 164 } |
| 165 |
| 166 def __init__(self, name, access_key=None, secret_key=None, |
| 167 security_token=None): |
| 168 self.host = None |
| 169 self.access_key = access_key |
| 170 self.secret_key = secret_key |
| 171 self.security_token = security_token |
| 172 self.name = name |
| 173 self.acl_class = self.AclClassMap[self.name] |
| 174 self.canned_acls = self.CannedAclsMap[self.name] |
| 175 self._credential_expiry_time = None |
| 176 self.get_credentials(access_key, secret_key) |
| 177 self.configure_headers() |
| 178 self.configure_errors() |
| 179 # allow config file to override default host |
| 180 host_opt_name = '%s_host' % self.HostKeyMap[self.name] |
| 181 if config.has_option('Credentials', host_opt_name): |
| 182 self.host = config.get('Credentials', host_opt_name) |
| 183 |
| 184 def get_access_key(self): |
| 185 if self._credentials_need_refresh(): |
| 186 self._populate_keys_from_metadata_server() |
| 187 return self._access_key |
| 188 |
| 189 def set_access_key(self, value): |
| 190 self._access_key = value |
| 191 |
| 192 access_key = property(get_access_key, set_access_key) |
| 193 |
| 194 def get_secret_key(self): |
| 195 if self._credentials_need_refresh(): |
| 196 self._populate_keys_from_metadata_server() |
| 197 return self._secret_key |
| 198 |
| 199 def set_secret_key(self, value): |
| 200 self._secret_key = value |
| 201 |
| 202 secret_key = property(get_secret_key, set_secret_key) |
| 203 |
| 204 def get_security_token(self): |
| 205 if self._credentials_need_refresh(): |
| 206 self._populate_keys_from_metadata_server() |
| 207 return self._security_token |
| 208 |
| 209 def set_security_token(self, value): |
| 210 self._security_token = value |
| 211 |
| 212 security_token = property(get_security_token, set_security_token) |
| 213 |
| 214 def _credentials_need_refresh(self): |
| 215 if self._credential_expiry_time is None: |
| 216 return False |
| 217 else: |
| 218 # The credentials should be refreshed if they're going to expire |
| 219 # in less than 5 minutes. |
| 220 delta = self._credential_expiry_time - datetime.utcnow() |
| 221 # python2.6 does not have timedelta.total_seconds() so we have |
| 222 # to calculate this ourselves. This is straight from the |
| 223 # datetime docs. |
| 224 seconds_left = ( |
| 225 (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) |
| 226 * 10**6) / 10**6) |
| 227 if seconds_left < (5 * 60): |
| 228 boto.log.debug("Credentials need to be refreshed.") |
| 229 return True |
| 230 else: |
| 231 return False |
| 232 |
| 233 def get_credentials(self, access_key=None, secret_key=None): |
| 234 access_key_name, secret_key_name = self.CredentialMap[self.name] |
| 235 if access_key is not None: |
| 236 self.access_key = access_key |
| 237 boto.log.debug("Using access key provided by client.") |
| 238 elif access_key_name.upper() in os.environ: |
| 239 self.access_key = os.environ[access_key_name.upper()] |
| 240 boto.log.debug("Using access key found in environment variable.") |
| 241 elif config.has_option('Credentials', access_key_name): |
| 242 self.access_key = config.get('Credentials', access_key_name) |
| 243 boto.log.debug("Using access key found in config file.") |
| 244 |
| 245 if secret_key is not None: |
| 246 self.secret_key = secret_key |
| 247 boto.log.debug("Using secret key provided by client.") |
| 248 elif secret_key_name.upper() in os.environ: |
| 249 self.secret_key = os.environ[secret_key_name.upper()] |
| 250 boto.log.debug("Using secret key found in environment variable.") |
| 251 elif config.has_option('Credentials', secret_key_name): |
| 252 self.secret_key = config.get('Credentials', secret_key_name) |
| 253 boto.log.debug("Using secret key found in config file.") |
| 254 elif config.has_option('Credentials', 'keyring'): |
| 255 keyring_name = config.get('Credentials', 'keyring') |
| 256 try: |
| 257 import keyring |
| 258 except ImportError: |
| 259 boto.log.error("The keyring module could not be imported. " |
| 260 "For keyring support, install the keyring " |
| 261 "module.") |
| 262 raise |
| 263 self.secret_key = keyring.get_password( |
| 264 keyring_name, self.access_key) |
| 265 boto.log.debug("Using secret key found in keyring.") |
| 266 |
| 267 if ((self._access_key is None or self._secret_key is None) and |
| 268 self.MetadataServiceSupport[self.name]): |
| 269 self._populate_keys_from_metadata_server() |
| 270 self._secret_key = self._convert_key_to_str(self._secret_key) |
| 271 |
| 272 def _populate_keys_from_metadata_server(self): |
| 273 # get_instance_metadata is imported here because of a circular |
| 274 # dependency. |
| 275 boto.log.debug("Retrieving credentials from metadata server.") |
| 276 from boto.utils import get_instance_metadata |
| 277 timeout = config.getfloat('Boto', 'metadata_service_timeout', 1.0) |
| 278 metadata = get_instance_metadata(timeout=timeout, num_retries=1) |
| 279 # I'm assuming there's only one role on the instance profile. |
| 280 if metadata and 'iam' in metadata: |
| 281 security = metadata['iam']['security-credentials'].values()[0] |
| 282 self._access_key = security['AccessKeyId'] |
| 283 self._secret_key = self._convert_key_to_str(security['SecretAccessKe
y']) |
| 284 self._security_token = security['Token'] |
| 285 expires_at = security['Expiration'] |
| 286 self._credential_expiry_time = datetime.strptime( |
| 287 expires_at, "%Y-%m-%dT%H:%M:%SZ") |
| 288 boto.log.debug("Retrieved credentials will expire in %s at: %s", |
| 289 self._credential_expiry_time - datetime.now(), expire
s_at) |
| 290 |
| 291 def _convert_key_to_str(self, key): |
| 292 if isinstance(key, unicode): |
| 293 # the secret key must be bytes and not unicode to work |
| 294 # properly with hmac.new (see http://bugs.python.org/issue5285) |
| 295 return str(key) |
| 296 return key |
| 297 |
| 298 def configure_headers(self): |
| 299 header_info_map = self.HeaderInfoMap[self.name] |
| 300 self.metadata_prefix = header_info_map[METADATA_PREFIX_KEY] |
| 301 self.header_prefix = header_info_map[HEADER_PREFIX_KEY] |
| 302 self.acl_header = header_info_map[ACL_HEADER_KEY] |
| 303 self.auth_header = header_info_map[AUTH_HEADER_KEY] |
| 304 self.copy_source_header = header_info_map[COPY_SOURCE_HEADER_KEY] |
| 305 self.copy_source_version_id = header_info_map[ |
| 306 COPY_SOURCE_VERSION_ID_HEADER_KEY] |
| 307 self.copy_source_range_header = header_info_map[ |
| 308 COPY_SOURCE_RANGE_HEADER_KEY] |
| 309 self.date_header = header_info_map[DATE_HEADER_KEY] |
| 310 self.delete_marker = header_info_map[DELETE_MARKER_HEADER_KEY] |
| 311 self.metadata_directive_header = ( |
| 312 header_info_map[METADATA_DIRECTIVE_HEADER_KEY]) |
| 313 self.security_token_header = header_info_map[SECURITY_TOKEN_HEADER_KEY] |
| 314 self.resumable_upload_header = ( |
| 315 header_info_map[RESUMABLE_UPLOAD_HEADER_KEY]) |
| 316 self.server_side_encryption_header = header_info_map[SERVER_SIDE_ENCRYPT
ION_KEY] |
| 317 self.storage_class_header = header_info_map[STORAGE_CLASS_HEADER_KEY] |
| 318 self.version_id = header_info_map[VERSION_ID_HEADER_KEY] |
| 319 self.mfa_header = header_info_map[MFA_HEADER_KEY] |
| 320 |
| 321 def configure_errors(self): |
| 322 error_map = self.ErrorMap[self.name] |
| 323 self.storage_copy_error = error_map[STORAGE_COPY_ERROR] |
| 324 self.storage_create_error = error_map[STORAGE_CREATE_ERROR] |
| 325 self.storage_data_error = error_map[STORAGE_DATA_ERROR] |
| 326 self.storage_permissions_error = error_map[STORAGE_PERMISSIONS_ERROR] |
| 327 self.storage_response_error = error_map[STORAGE_RESPONSE_ERROR] |
| 328 |
| 329 def get_provider_name(self): |
| 330 return self.HostKeyMap[self.name] |
| 331 |
| 332 def supports_chunked_transfer(self): |
| 333 return self.ChunkedTransferSupport[self.name] |
| 334 |
| 335 # Static utility method for getting default Provider. |
| 336 def get_default(): |
| 337 return Provider('aws') |
OLD | NEW |