Esempio n. 1
0
def handle_notification_request(bucket, method, data):
    response = Response()
    response.status_code = 200
    response._content = ''
    if method == 'GET':
        # TODO check if bucket exists
        result = '<NotificationConfiguration xmlns="%s">' % XMLNS_S3
        if bucket in S3_NOTIFICATIONS:
            notif = S3_NOTIFICATIONS[bucket]
            for dest in NOTIFICATION_DESTINATION_TYPES:
                if dest in notif:
                    dest_dict = {
                        '%sConfiguration' % dest: {
                            'Id': uuid.uuid4(),
                            dest: notif[dest],
                            'Event': notif['Event'],
                            'Filter': notif['Filter']
                        }
                    }
                    result += xmltodict.unparse(dest_dict, full_document=False)
        result += '</NotificationConfiguration>'
        response._content = result

    if method == 'PUT':
        parsed = xmltodict.parse(data)
        notif_config = parsed.get('NotificationConfiguration')
        S3_NOTIFICATIONS.pop(bucket, None)
        for dest in NOTIFICATION_DESTINATION_TYPES:
            config = notif_config.get('%sConfiguration' % (dest))
            if config:
                events = config.get('Event')
                if isinstance(events, six.string_types):
                    events = [events]
                event_filter = config.get('Filter', {})
                # make sure FilterRule is an array
                s3_filter = _get_s3_filter(event_filter)
                if s3_filter and not isinstance(
                        s3_filter.get('FilterRule', []), list):
                    s3_filter['FilterRule'] = [s3_filter['FilterRule']]
                # create final details dict
                notification_details = {
                    'Id': config.get('Id'),
                    'Event': events,
                    dest: config.get(dest),
                    'Filter': event_filter
                }
                # TODO: what if we have multiple destinations - would we overwrite the config?
                S3_NOTIFICATIONS[bucket] = clone(notification_details)
    return response
Esempio n. 2
0
    def get_config_variable(self, logical_name, methods=("instance", "env", "config"), default=None):
        """
        Retrieve the value associated with the specified logical_name
        from the environment or the config file.  Values found in the
        environment variable take precedence of values found in the
        config file.  If no value can be found, a None will be returned.

        :type logical_name: str
        :param logical_name: The logical name of the session variable
            you want to retrieve.  This name will be mapped to the
            appropriate environment variable name for this session as
            well as the appropriate config file entry.

        :type method: tuple
        :param method: Defines which methods will be used to find
            the variable value.  By default, all available methods
            are tried but you can limit which methods are used
            by supplying a different value to this parameter.
            Valid choices are: instance|env|config

        :param default: The default value to return if there is no
            value associated with the config file.  This value will
            override any default value specified in ``SessionVariables``.

        :returns: str value of variable of None if not defined.

        """
        value = None
        # There's two types of defaults here.  One if the
        # default value specified in the SessionVariables.
        # The second is an explicit default value passed into this
        # function (the default parameter).
        # config_default is tracking the default value specified
        # in the SessionVariables.
        config_default = None
        if logical_name in self.session_var_map:
            # Short circuit case, check if the var has been explicitly
            # overriden via set_config_variable.
            if "instance" in methods and logical_name in self._session_instance_vars:
                return self._session_instance_vars[logical_name]
            config_name, envvar_name, config_default = self.session_var_map[logical_name]
            if logical_name in ("config_file", "profile"):
                config_name = None
            if logical_name == "profile" and self._profile:
                value = self._profile
            elif "env" in methods and envvar_name and envvar_name in os.environ:
                value = os.environ[envvar_name]
            elif "config" in methods:
                if config_name:
                    config = self.get_scoped_config()
                    value = config.get(config_name)
        # If we don't have a value at this point, we need to try to assign
        # a default value.  An explicit default argument will win over the
        # default value from SessionVariables.
        if value is None and default is not None:
            value = default
        if value is None and config_default is not None:
            value = config_default
        return value
Esempio n. 3
0
def send_notifications(method, bucket_name, object_path):
    for bucket, config in iteritems(S3_NOTIFICATIONS):
        if bucket == bucket_name:
            action = {'PUT': 'ObjectCreated', 'POST': 'ObjectCreated', 'DELETE': 'ObjectRemoved'}[method]
            # TODO: support more detailed methods, e.g., DeleteMarkerCreated
            # http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html
            api_method = {'PUT': 'Put', 'POST': 'Post', 'DELETE': 'Delete'}[method]
            event_name = '%s:%s' % (action, api_method)
            if (event_type_matches(config['Event'], action, api_method) and
                    filter_rules_match(config.get('Filter'), object_path)):
                # send notification
                message = get_event_message(
                    event_name=event_name, bucket_name=bucket_name,
                    file_name=urlparse.urlparse(object_path[1:]).path
                )
                message = json.dumps(message)
                if config.get('Queue'):
                    sqs_client = aws_stack.connect_to_service('sqs')
                    try:
                        queue_url = queue_url_for_arn(config['Queue'])
                        sqs_client.send_message(QueueUrl=queue_url, MessageBody=message)
                    except Exception as e:
                        LOGGER.warning('Unable to send notification for S3 bucket "%s" to SQS queue "%s": %s' %
                            (bucket_name, config['Queue'], e))
                if config.get('Topic'):
                    sns_client = aws_stack.connect_to_service('sns')
                    try:
                        sns_client.publish(TopicArn=config['Topic'], Message=message, Subject='Amazon S3 Notification')
                    except Exception as e:
                        LOGGER.warning('Unable to send notification for S3 bucket "%s" to SNS topic "%s".' %
                            (bucket_name, config['Topic']))
                if config.get('CloudFunction'):
                    # make sure we don't run into a socket timeout
                    connection_config = botocore.config.Config(read_timeout=300)
                    lambda_client = aws_stack.connect_to_service('lambda', config=connection_config)
                    try:
                        lambda_client.invoke(FunctionName=config['CloudFunction'],
                                             InvocationType='Event', Payload=message)
                    except Exception as e:
                        LOGGER.warning('Unable to send notification for S3 bucket "%s" to Lambda function "%s".' %
                            (bucket_name, config['CloudFunction']))
                if not filter(lambda x: config.get(x), ('Queue', 'Topic', 'CloudFunction')):
                    LOGGER.warning('Neither of Queue/Topic/CloudFunction defined for S3 notification.')
Esempio n. 4
0
 def _create_creds_from_response(self, response):
     config = self._get_role_config_values()
     if config.get('mfa_serial') is not None:
         # MFA would require getting a new TokenCode which would require
         # prompting the user for a new token, so we use a different
         # refresh_func.
         refresh_func = create_mfa_serial_refresher()
     else:
         refresh_func = create_assume_role_refresher(
             self._create_client_from_config(config),
             self._assume_role_base_kwargs(config))
     return RefreshableCredentials(
         access_key=response['Credentials']['AccessKeyId'],
         secret_key=response['Credentials']['SecretAccessKey'],
         token=response['Credentials']['SessionToken'],
         method=self.METHOD,
         expiry_time=_parse_if_needed(
             response['Credentials']['Expiration']),
         refresh_using=refresh_func)
Esempio n. 5
0
 def _create_creds_from_response(self, response):
     config = self._get_role_config_values()
     if config.get('mfa_serial') is not None:
         # MFA would require getting a new TokenCode which would require
         # prompting the user for a new token, so we use a different
         # refresh_func.
         refresh_func = create_mfa_serial_refresher()
     else:
         refresh_func = create_assume_role_refresher(
             self._create_client_from_config(config),
             self._assume_role_base_kwargs(config))
     return RefreshableCredentials(
         access_key=response['Credentials']['AccessKeyId'],
         secret_key=response['Credentials']['SecretAccessKey'],
         token=response['Credentials']['SessionToken'],
         method=self.METHOD,
         expiry_time=_parse_if_needed(
             response['Credentials']['Expiration']),
         refresh_using=refresh_func)
Esempio n. 6
0
    def get_variable(self, logical_name, methods=('env', 'config')):
        """
        Retrieve the value associated with the specified logical_name
        from the environment or the config file.  Values found in the
        environment variable take precedence of values found in the
        config file.  If no value can be found, a None will be returned.

        :type logical_name: str
        :param logical_name: The logical name of the session variable
            you want to retrieve.  This name will be mapped to the
            appropriate environment variable name for this session as
            well as the appropriate config file entry.

        :type method: tuple
        :param method: Defines which methods will be used to find
            the variable value.  By default, all available methods
            are tried but you can limit which methods are used
            by supplying a different value to this parameter.
            Valid choices are: both|env|config

        :returns: str value of variable of None if not defined.
        """
        value = None
        default = None
        if logical_name in self.env_vars:
            config_name, envvar_name, default = self.env_vars[logical_name]
            if logical_name in ('config_file', 'profile'):
                config_name = None
            if logical_name == 'profile' and self._profile:
                value = self._profile
            elif 'env' in methods and envvar_name and envvar_name in os.environ:
                value = os.environ[envvar_name]
            elif 'config' in methods:
                if config_name:
                    config = self.get_config()
                    value = config.get(config_name, default)
        if value is None and default is not None:
            value = default
        return value
Esempio n. 7
0
    def get_variable(self, logical_name, methods=('env', 'config')):
        """
        Retrieve the value associated with the specified logical_name
        from the environment or the config file.  Values found in the
        environment variable take precedence of values found in the
        config file.  If no value can be found, a None will be returned.

        :type logical_name: str
        :param logical_name: The logical name of the session variable
            you want to retrieve.  This name will be mapped to the
            appropriate environment variable name for this session as
            well as the appropriate config file entry.

        :type method: tuple
        :param method: Defines which methods will be used to find
            the variable value.  By default, all available methods
            are tried but you can limit which methods are used
            by supplying a different value to this parameter.
            Valid choices are: both|env|config

        :returns: str value of variable of None if not defined.
        """
        value = None
        default = None
        if logical_name in self.env_vars:
            config_name, envvar_name, default = self.env_vars[logical_name]
            if logical_name in ('config_file', 'profile'):
                config_name = None
            if logical_name == 'profile' and self._profile:
                value = self._profile
            elif 'env' in methods and envvar_name and envvar_name in os.environ:
                value = os.environ[envvar_name]
            elif 'config' in methods:
                if config_name:
                    config = self.get_config()
                    value = config.get(config_name, default)
        if value is None and default is not None:
            value = default
        return value
Esempio n. 8
0
def send_notifications(method, bucket_name, object_path):
    for bucket, config in iteritems(S3_NOTIFICATIONS):
        if bucket == bucket_name:
            action = {'PUT': 'ObjectCreated', 'POST': 'ObjectCreated', 'DELETE': 'ObjectRemoved'}[method]
            # TODO: support more detailed methods, e.g., DeleteMarkerCreated
            # http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html
            api_method = {'PUT': 'Put', 'POST': 'Post', 'DELETE': 'Delete'}[method]
            event_name = '%s:%s' % (action, api_method)
            if (event_type_matches(config['Event'], action, api_method) and
                    filter_rules_match(config.get('Filter'), object_path)):
                # send notification
                message = get_event_message(
                    event_name=event_name, bucket_name=bucket_name,
                    file_name=urlparse.urlparse(object_path[1:]).path
                )
                message = json.dumps(message)
                if config.get('Queue'):
                    sqs_client = aws_stack.connect_to_service('sqs')
                    try:
                        queue_url = queue_url_for_arn(config['Queue'])
                        sqs_client.send_message(QueueUrl=queue_url, MessageBody=message)
                    except Exception as e:
                        LOGGER.warning('Unable to send notification for S3 bucket "%s" to SQS queue "%s": %s' %
                            (bucket_name, config['Queue'], e))
                if config.get('Topic'):
                    sns_client = aws_stack.connect_to_service('sns')
                    try:
                        sns_client.publish(TopicArn=config['Topic'], Message=message, Subject='Amazon S3 Notification')
                    except Exception as e:
                        LOGGER.warning('Unable to send notification for S3 bucket "%s" to SNS topic "%s".' %
                            (bucket_name, config['Topic']))
                # CloudFunction and LambdaFunction are semantically identical
                lambda_function_config = config.get('CloudFunction') or config.get('LambdaFunction')
                if lambda_function_config:
                    # make sure we don't run into a socket timeout
                    connection_config = botocore.config.Config(read_timeout=300)
                    lambda_client = aws_stack.connect_to_service('lambda', config=connection_config)
                    try:
                        lambda_client.invoke(FunctionName=lambda_function_config,
                                             InvocationType='Event', Payload=message)
                    except Exception as e:
                        LOGGER.warning('Unable to send notification for S3 bucket "%s" to Lambda function "%s".' %
                            (bucket_name, lambda_function_config))
                if not filter(lambda x: config.get(x), NOTIFICATION_DESTINATION_TYPES):
                    LOGGER.warning('Neither of %s defined for S3 notification.' %
                        '/'.join(NOTIFICATION_DESTINATION_TYPES))
Esempio n. 9
0
    def forward_request(self, method, path, data, headers):
        LOGGER.debug('forward_request - method: "%s"' % method)
        modified_data = None

        # If this request contains streaming v4 authentication signatures, strip them from the message
        # Related isse: https://github.com/localstack/localstack/issues/98
        # TODO we should evaluate whether to replace moto s3 with scality/S3:
        # https://github.com/scality/S3/issues/237
        if headers.get('x-amz-content-sha256'
                       ) == 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD':
            modified_data = strip_chunk_signatures(data)

        # POST requests to S3 may include a "${filename}" placeholder in the
        # key, which should be replaced with an actual file name before storing.
        if method == 'POST':
            original_data = modified_data or data
            expanded_data = multipart_content.expand_multipart_filename(
                original_data, headers)
            if expanded_data is not original_data:
                modified_data = expanded_data

        # If no content-type is provided, 'binary/octet-stream' should be used
        # src: https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html
        if method == 'PUT' and not headers.get('content-type'):
            headers['content-type'] = 'binary/octet-stream'

        # persist this API call to disk
        persistence.record('s3', method, path, data, headers)

        parsed = urlparse.urlparse(path)
        query = parsed.query
        path = parsed.path
        object_path = parsed.path
        LOGGER.debug('forward_request - query: "%s"' % query)
        LOGGER.debug('forward_request - path: "%s"' % path)
        LOGGER.debug('forward_request - host: "%s"' % headers['host'])
        LOGGER.debug('forward_request - object_path: "%s"' % object_path)

        # Check bucket name in host
        bucket = None
        hostname_parts = headers['host'].split('.')
        if len(hostname_parts) > 1:
            bucket = hostname_parts[0]

        # No bucket name in host, check in path.
        if (not bucket or len(bucket) == 0):
            bucket = get_bucket_name(path, headers)

        LOGGER.debug('forward_request - Bucket name: "%s"' % bucket)

        query_map = urlparse.parse_qs(query)
        if query == 'notification' or 'notification' in query_map:
            LOGGER.debug('forward_request - query: "%s"' % query)
            response = Response()
            response.status_code = 200
            if method == 'GET':
                # TODO check if bucket exists
                result = '<NotificationConfiguration xmlns="%s">' % XMLNS_S3
                if bucket in S3_NOTIFICATIONS:
                    notif = S3_NOTIFICATIONS[bucket]
                    for dest in NOTIFICATION_DESTINATION_TYPES:
                        if dest in notif:
                            dest_dict = {
                                '%sConfiguration' % dest: {
                                    'Id': uuid.uuid4(),
                                    dest: notif[dest],
                                    'Event': notif['Event'],
                                    'Filter': notif['Filter']
                                }
                            }
                            result += xmltodict.unparse(dest_dict,
                                                        full_document=False)
                result += '</NotificationConfiguration>'
                response._content = result

            if method == 'PUT':
                LOGGER.debug('forward_request - method: "%s"' % method)
                parsed = xmltodict.parse(data)
                notif_config = parsed.get('NotificationConfiguration')
                S3_NOTIFICATIONS.pop(bucket, None)
                for dest in NOTIFICATION_DESTINATION_TYPES:
                    LOGGER.debug(
                        'forward_request - NOTIFICATION_DESTINATION_TYPES - dest: "%s"'
                        % dest)
                    config = notif_config.get('%sConfiguration' % (dest))
                    if config:
                        events = config.get('Event')
                        if isinstance(events, six.string_types):
                            events = [events]
                        event_filter = config.get('Filter', {})
                        # make sure FilterRule is an array
                        s3_filter = _get_s3_filter(event_filter)
                        if s3_filter and not isinstance(
                                s3_filter.get('FilterRule', []), list):
                            s3_filter['FilterRule'] = [s3_filter['FilterRule']]
                        # create final details dict
                        notification_details = {
                            'Id': config.get('Id'),
                            'Event': events,
                            dest: config.get(dest),
                            'Filter': event_filter
                        }
                        # TODO: what if we have multiple destinations - would we overwrite the config?
                        LOGGER.debug(
                            'forward_request - S3_NOTIFICATIONS - bucket: "%s"'
                            % bucket)
                        S3_NOTIFICATIONS[bucket] = clone(notification_details)

            # return response for ?notification request
            return response

        if query == 'cors' or 'cors' in query_map:
            if method == 'GET':
                return get_cors(bucket)
            if method == 'PUT':
                return set_cors(bucket, data)
            if method == 'DELETE':
                return delete_cors(bucket)

        if query == 'lifecycle' or 'lifecycle' in query_map:
            if method == 'GET':
                return get_lifecycle(bucket)
            if method == 'PUT':
                return set_lifecycle(bucket, data)

        LOGGER.debug('forward_request - query_map: "%s"' % query_map)
        if method == 'PUT' and 'x-amz-meta-filename' in query_map and bucket is not None and object_path is not None:
            unique_id = get_unique_id(bucket, object_path)
            set_user_defined_metadata(unique_id, query_map)

        if modified_data:
            return Request(data=modified_data, headers=headers, method=method)
        return True
Esempio n. 10
0
    def get_config_variable(self,
                            logical_name,
                            methods=('instance', 'env', 'config'),
                            default=None):
        """
        Retrieve the value associated with the specified logical_name
        from the environment or the config file.  Values found in the
        environment variable take precedence of values found in the
        config file.  If no value can be found, a None will be returned.

        :type logical_name: str
        :param logical_name: The logical name of the session variable
            you want to retrieve.  This name will be mapped to the
            appropriate environment variable name for this session as
            well as the appropriate config file entry.

        :type method: tuple
        :param method: Defines which methods will be used to find
            the variable value.  By default, all available methods
            are tried but you can limit which methods are used
            by supplying a different value to this parameter.
            Valid choices are: instance|env|config

        :param default: The default value to return if there is no
            value associated with the config file.  This value will
            override any default value specified in ``SessionVariables``.

        :returns: str value of variable of None if not defined.

        """
        value = None
        # There's two types of defaults here.  One if the
        # default value specified in the SessionVariables.
        # The second is an explicit default value passed into this
        # function (the default parameter).
        # config_default is tracking the default value specified
        # in the SessionVariables.
        config_default = None
        if logical_name in self.session_var_map:
            # Short circuit case, check if the var has been explicitly
            # overriden via set_config_variable.
            if 'instance' in methods and \
                    logical_name in self._session_instance_vars:
                return self._session_instance_vars[logical_name]
            config_name, envvar_name, config_default = self.session_var_map[
                logical_name]
            if logical_name in ('config_file', 'profile'):
                config_name = None
            if logical_name == 'profile' and self._profile:
                value = self._profile
            elif 'env' in methods and envvar_name and envvar_name in os.environ:
                value = os.environ[envvar_name]
            elif 'config' in methods:
                if config_name:
                    config = self.get_scoped_config()
                    value = config.get(config_name)
        # If we don't have a value at this point, we need to try to assign
        # a default value.  An explicit default argument will win over the
        # default value from SessionVariables.
        if value is None and default is not None:
            value = default
        if value is None and config_default is not None:
            value = config_default
        return value
Esempio n. 11
0
    def forward_request(self, method, path, data, headers):

        modified_data = None

        # If this request contains streaming v4 authentication signatures, strip them from the message
        # Related isse: https://github.com/localstack/localstack/issues/98
        # TODO we should evaluate whether to replace moto s3 with scality/S3:
        # https://github.com/scality/S3/issues/237
        if headers.get('x-amz-content-sha256'
                       ) == 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD':
            modified_data = strip_chunk_signatures(data)

        # POST requests to S3 may include a "${filename}" placeholder in the
        # key, which should be replaced with an actual file name before storing.
        if method == 'POST':
            original_data = modified_data or data
            expanded_data = expand_multipart_filename(original_data, headers)
            if expanded_data is not original_data:
                modified_data = expanded_data

        # persist this API call to disk
        persistence.record('s3', method, path, data, headers)

        parsed = urlparse.urlparse(path)
        query = parsed.query
        path = parsed.path
        bucket = path.split('/')[1]
        query_map = urlparse.parse_qs(query)
        if query == 'notification' or 'notification' in query_map:
            response = Response()
            response.status_code = 200
            if method == 'GET':
                # TODO check if bucket exists
                result = '<NotificationConfiguration xmlns="%s">' % XMLNS_S3
                if bucket in S3_NOTIFICATIONS:
                    notif = S3_NOTIFICATIONS[bucket]
                    for dest in ['Queue', 'Topic', 'CloudFunction']:
                        if dest in notif:
                            dest_dict = {
                                '%sConfiguration' % dest: {
                                    'Id': uuid.uuid4(),
                                    dest: notif[dest],
                                    'Event': notif['Event'],
                                    'Filter': notif['Filter']
                                }
                            }
                            result += xmltodict.unparse(dest_dict,
                                                        full_document=False)
                result += '</NotificationConfiguration>'
                response._content = result

            if method == 'PUT':
                parsed = xmltodict.parse(data)
                notif_config = parsed.get('NotificationConfiguration')
                S3_NOTIFICATIONS.pop(bucket, None)
                for dest in ['Queue', 'Topic', 'CloudFunction']:
                    config = notif_config.get('%sConfiguration' % (dest))
                    if config:
                        events = config.get('Event')
                        if isinstance(events, six.string_types):
                            events = [events]
                        event_filter = config.get('Filter', {})
                        # make sure FilterRule is an array
                        s3_filter = _get_s3_filter(event_filter)
                        if s3_filter and not isinstance(
                                s3_filter.get('FilterRule', []), list):
                            s3_filter['FilterRule'] = [s3_filter['FilterRule']]
                        # create final details dict
                        notification_details = {
                            'Id': config.get('Id'),
                            'Event': events,
                            dest: config.get(dest),
                            'Filter': event_filter
                        }
                        # TODO: what if we have multiple destinations - would we overwrite the config?
                        S3_NOTIFICATIONS[bucket] = clone(notification_details)

            # return response for ?notification request
            return response

        if query == 'cors' or 'cors' in query_map:
            if method == 'GET':
                return get_cors(bucket)
            if method == 'PUT':
                return set_cors(bucket, data)
            if method == 'DELETE':
                return delete_cors(bucket)

        if query == 'lifecycle' or 'lifecycle' in query_map:
            if method == 'GET':
                return get_lifecycle(bucket)
            if method == 'PUT':
                return set_lifecycle(bucket, data)

        if modified_data:
            return Request(data=modified_data, headers=headers, method=method)
        return True
Esempio n. 12
0
    def forward_request(self, method, path, data, headers):

        modified_data = None

        # If this request contains streaming v4 authentication signatures, strip them from the message
        # Related isse: https://github.com/localstack/localstack/issues/98
        # TODO we should evaluate whether to replace moto s3 with scality/S3:
        # https://github.com/scality/S3/issues/237
        if headers.get('x-amz-content-sha256') == 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD':
            modified_data = strip_chunk_signatures(data)

        # POST requests to S3 may include a "${filename}" placeholder in the
        # key, which should be replaced with an actual file name before storing.
        if method == 'POST':
            original_data = modified_data or data
            expanded_data = multipart_content.expand_multipart_filename(original_data, headers)
            if expanded_data is not original_data:
                modified_data = expanded_data

        # If no content-type is provided, 'binary/octet-stream' should be used
        # src: https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html
        if method == 'PUT' and not headers.get('content-type'):
            headers['content-type'] = 'binary/octet-stream'

        # persist this API call to disk
        persistence.record('s3', method, path, data, headers)

        parsed = urlparse.urlparse(path)
        query = parsed.query
        path = parsed.path
        bucket = path.split('/')[1]
        query_map = urlparse.parse_qs(query)
        if query == 'notification' or 'notification' in query_map:
            response = Response()
            response.status_code = 200
            if method == 'GET':
                # TODO check if bucket exists
                result = '<NotificationConfiguration xmlns="%s">' % XMLNS_S3
                if bucket in S3_NOTIFICATIONS:
                    notif = S3_NOTIFICATIONS[bucket]
                    for dest in NOTIFICATION_DESTINATION_TYPES:
                        if dest in notif:
                            dest_dict = {
                                '%sConfiguration' % dest: {
                                    'Id': uuid.uuid4(),
                                    dest: notif[dest],
                                    'Event': notif['Event'],
                                    'Filter': notif['Filter']
                                }
                            }
                            result += xmltodict.unparse(dest_dict, full_document=False)
                result += '</NotificationConfiguration>'
                response._content = result

            if method == 'PUT':
                parsed = xmltodict.parse(data)
                notif_config = parsed.get('NotificationConfiguration')
                S3_NOTIFICATIONS.pop(bucket, None)
                for dest in NOTIFICATION_DESTINATION_TYPES:
                    config = notif_config.get('%sConfiguration' % (dest))
                    if config:
                        events = config.get('Event')
                        if isinstance(events, six.string_types):
                            events = [events]
                        event_filter = config.get('Filter', {})
                        # make sure FilterRule is an array
                        s3_filter = _get_s3_filter(event_filter)
                        if s3_filter and not isinstance(s3_filter.get('FilterRule', []), list):
                            s3_filter['FilterRule'] = [s3_filter['FilterRule']]
                        # create final details dict
                        notification_details = {
                            'Id': config.get('Id'),
                            'Event': events,
                            dest: config.get(dest),
                            'Filter': event_filter
                        }
                        # TODO: what if we have multiple destinations - would we overwrite the config?
                        S3_NOTIFICATIONS[bucket] = clone(notification_details)

            # return response for ?notification request
            return response

        if query == 'cors' or 'cors' in query_map:
            if method == 'GET':
                return get_cors(bucket)
            if method == 'PUT':
                return set_cors(bucket, data)
            if method == 'DELETE':
                return delete_cors(bucket)

        if query == 'lifecycle' or 'lifecycle' in query_map:
            if method == 'GET':
                return get_lifecycle(bucket)
            if method == 'PUT':
                return set_lifecycle(bucket, data)

        if modified_data:
            return Request(data=modified_data, headers=headers, method=method)
        return True
Esempio n. 13
0
def run(profile=None,
        region=None,
        session_duration=None,
        idp_arn=None,
        role_arn=None,
        saml=None):
    profile_name = profile or os.environ.get("AWS_PROFILE", "default")
    region_name = region or os.environ.get("AWS_DEFAULT_REGION", None)
    section_name = (profile_name if profile_name == "default" else
                    "profile {}".format(profile_name))

    config_path = os.environ.get("AWS_CONFIG_FILE") or os.path.expanduser(
        "~/.aws/config")
    cred_path = os.environ.get("AWS_SHARED_CREDENTIALS_FILE"
                               ) or os.path.expanduser("~/.aws/credentials")

    config = configparser.RawConfigParser()
    config.read(config_path)

    try:
        session_duration = session_duration or config.getint(
            section_name, "saml.session_duration")
    except configparser.NoOptionError:
        session_duration = 3600

    principal_arn = idp_arn or config.get(section_name, "saml.idp_arn")
    role_arn = role_arn or config.get(section_name, "saml.role_arn")
    try:
        region_name = region_name or config.get(section_name, "region")
    except configparser.NoOptionError:
        pass

    # would use getpass, but truncates to terminal max 4096
    saml_assertion = saml or os.environ.get("SAML_ASSERTION")
    if not saml_assertion:
        try:
            import readline  # needed for terminal raw mode (> 4096 characters)
        except:
            print("Failed to load readline\n{}".format(traceback.format_exc()))
        saml_assertion = input("Base64 encoded SAML response:\n")

    sts = boto3.client(
        "sts",
        config=botocore.config.Config(signature_version=botocore.UNSIGNED))
    response = sts.assume_role_with_saml(
        DurationSeconds=session_duration,
        PrincipalArn=principal_arn,
        RoleArn=role_arn,
        SAMLAssertion=saml_assertion,
    )

    cred = configparser.RawConfigParser()
    cred.read(cred_path)

    if not cred.has_section(profile_name):
        cred.add_section(profile_name)

    cred.set(profile_name, "aws_access_key_id",
             response["Credentials"]["AccessKeyId"])
    if region_name is not None:
        cred.set(profile_name, "region", region_name)
    else:
        cred.remove_option(profile_name, "region")
    cred.set(
        profile_name,
        "aws_secret_access_key",
        response["Credentials"]["SecretAccessKey"],
    )
    cred.set(profile_name, "aws_session_token",
             response["Credentials"]["SessionToken"])
    # Duplicate aws_session_token to aws_security_token to support legacy AWS clients.
    cred.set(profile_name, "aws_security_token",
             response["Credentials"]["SessionToken"])

    cred.set(
        profile_name,
        "aws_session_expiration",
        response["Credentials"]["Expiration"].strftime("%Y-%m-%dT%H:%M:%S%z"),
    )

    with open(cred_path, "w+") as f:
        cred.write(f)

    print("Credentials saved for {}. Expire {}.".format(
        profile_name, response["Credentials"]["Expiration"]))