# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/ # Copyright (c) 2011, Eucalyptus Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, dis- # tribute, sublicense, and/or sell copies of the Software, and to permit # persons to whom the Software is furnished to do so, subject to the fol- # lowing conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. from boto.connection import AWSQueryConnection from boto.regioninfo import RegionInfo from credentials import Credentials, FederationToken, AssumedRole import boto import boto.utils import datetime import threading _session_token_cache = {} class STSConnection(AWSQueryConnection): DefaultRegionName = 'us-east-1' DefaultRegionEndpoint = 'sts.amazonaws.com' APIVersion = '2011-06-15' def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, is_secure=True, port=None, proxy=None, proxy_port=None, proxy_user=None, proxy_pass=None, debug=0, https_connection_factory=None, region=None, path='/', converter=None, validate_certs=True): if not region: region = RegionInfo(self, self.DefaultRegionName, self.DefaultRegionEndpoint, connection_cls=STSConnection) self.region = region self._mutex = threading.Semaphore() AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key, is_secure, port, proxy, proxy_port, proxy_user, proxy_pass, self.region.endpoint, debug, https_connection_factory, path, validate_certs=validate_certs) def _required_auth_capability(self): return ['sign-v2'] def _check_token_cache(self, token_key, duration=None, window_seconds=60): token = _session_token_cache.get(token_key, None) if token: now = datetime.datetime.utcnow() expires = boto.utils.parse_ts(token.expiration) delta = expires - now if delta < datetime.timedelta(seconds=window_seconds): msg = 'Cached session token %s is expired' % token_key boto.log.debug(msg) token = None return token def _get_session_token(self, duration=None, mfa_serial_number=None, mfa_token=None): params = {} if duration: params['DurationSeconds'] = duration if mfa_serial_number: params['SerialNumber'] = mfa_serial_number if mfa_token: params['TokenCode'] = mfa_token return self.get_object('GetSessionToken', params, Credentials, verb='POST') def get_session_token(self, duration=None, force_new=False, mfa_serial_number=None, mfa_token=None): """ Return a valid session token. Because retrieving new tokens from the Secure Token Service is a fairly heavyweight operation this module caches previously retrieved tokens and returns them when appropriate. Each token is cached with a key consisting of the region name of the STS endpoint concatenated with the requesting user's access id. If there is a token in the cache meeting with this key, the session expiration is checked to make sure it is still valid and if so, the cached token is returned. Otherwise, a new session token is requested from STS and it is placed into the cache and returned. :type duration: int :param duration: The number of seconds the credentials should remain valid. :type force_new: bool :param force_new: If this parameter is True, a new session token will be retrieved from the Secure Token Service regardless of whether there is a valid cached token or not. :type mfa_serial_number: str :param mfa_serial_number: The serial number of an MFA device. If this is provided and if the mfa_passcode provided is valid, the temporary session token will be authorized with to perform operations requiring the MFA device authentication. :type mfa_token: str :param mfa_token: The 6 digit token associated with the MFA device. """ token_key = '%s:%s' % (self.region.name, self.provider.access_key) token = self._check_token_cache(token_key, duration) if force_new or not token: boto.log.debug('fetching a new token for %s' % token_key) try: self._mutex.acquire() token = self._get_session_token(duration, mfa_serial_number, mfa_token) _session_token_cache[token_key] = token finally: self._mutex.release() return token def get_federation_token(self, name, duration=None, policy=None): """ :type name: str :param name: The name of the Federated user associated with the credentials. :type duration: int :param duration: The number of seconds the credentials should remain valid. :type policy: str :param policy: A JSON policy to associate with these credentials. """ params = {'Name': name} if duration: params['DurationSeconds'] = duration if policy: params['Policy'] = policy return self.get_object('GetFederationToken', params, FederationToken, verb='POST') def assume_role(self, role_arn, role_session_name, policy=None, duration_seconds=None, external_id=None): """ Returns a set of temporary credentials that the caller can use to access resources that are allowed by the temporary credentials. The credentials are valid for the duration that the caller specified, which can be from 15 minutes (900 seconds) to 1 hour (3600 seconds) :type role_arn: str :param role_arn: The Amazon Resource Name (ARN) of the role that the caller is assuming. :type role_session_name: str :param role_session_name: The session name of the temporary security credentials. The session name is part of the AssumedRoleUser. :type policy: str :param policy: A supplemental policy that can be associated with the temporary security credentials. The caller can limit the permissions that are available on the role's temporary security credentials to maintain the least amount of privileges. When a service call is made with the temporary security credentials, both policies (the role policy and supplemental policy) are checked. :type duration_seconds: int :param duration_seconds: he duration, in seconds, of the role session. The value can range from 900 seconds (15 minutes) to 3600 seconds (1 hour). By default, the value is set to 3600 seconds. :type external_id: str :param external_id: A unique identifier that is used by third-party services to ensure that they are assuming a role that corresponds to the correct users. For third-party services that have access to resources across multiple AWS accounts, the unique client ID helps third-party services simplify access control verification. :return: An instance of :class:`boto.sts.credentials.AssumedRole` """ params = { 'RoleArn': role_arn, 'RoleSessionName': role_session_name } if policy is not None: params['Policy'] = policy if duration_seconds is not None: params['DurationSeconds'] = duration_seconds if external_id is not None: params['ExternalId'] = external_id return self.get_object('AssumeRole', params, AssumedRole, verb='POST')