Beispiel #1
0
def authenticate_presign_url_signv2(method, path, headers, data, url, query_params, request_dict):

    # Calculating Signature
    aws_request = create_request_object(request_dict)
    credentials = Credentials(access_key=TEST_AWS_ACCESS_KEY_ID, secret_key=TEST_AWS_SECRET_ACCESS_KEY)
    auth = HmacV1QueryAuth(credentials=credentials, expires=query_params['Expires'][0])
    split = urlsplit(aws_request.url)
    string_to_sign = auth.get_string_to_sign(method=method, split=split, headers=aws_request.headers)
    signature = auth.get_signature(string_to_sign=string_to_sign)

    # Comparing the signature in url with signature we calculated
    query_sig = urlparse.unquote(query_params['Signature'][0])
    if query_sig != signature:

        return requests_error_response_xml_signature_calculation(
            code=403,
            code_string='SignatureDoesNotMatch',
            aws_access_token=TEST_AWS_ACCESS_KEY_ID,
            string_to_sign=string_to_sign,
            signature=signature,
            message='The request signature we calculated does not match the signature you provided. \
                    Check your key and signing method.')

    # Checking whether the url is expired or not
    if int(query_params['Expires'][0]) < time.time():
        return requests_error_response_xml_signature_calculation(
            code=403,
            code_string='AccessDenied',
            message='Request has expired',
            expires=query_params['Expires'][0]
        )
Beispiel #2
0
def authenticate_presign_url_signv4(method, path, headers, data, url, query_params, request_dict):

    # Calculating Signature
    aws_request = create_request_object(request_dict)
    ReadOnlyCredentials = namedtuple('ReadOnlyCredentials',
                                 ['access_key', 'secret_key', 'token'])
    credentials = ReadOnlyCredentials(TEST_AWS_ACCESS_KEY_ID, TEST_AWS_SECRET_ACCESS_KEY, None)
    region = query_params['X-Amz-Credential'][0].split('/')[2]
    signer = S3SigV4QueryAuth(credentials, 's3', region, expires=int(query_params['X-Amz-Expires'][0]))
    signature = signer.add_auth(aws_request, query_params['X-Amz-Date'][0])

    expiration_time = datetime.datetime.strptime(query_params['X-Amz-Date'][0], '%Y%m%dT%H%M%SZ') + \
        datetime.timedelta(seconds=int(query_params['X-Amz-Expires'][0]))

    # Comparing the signature in url with signature we calculated
    query_sig = urlparse.unquote(query_params['X-Amz-Signature'][0])
    if query_sig != signature:

        return requests_error_response_xml_signature_calculation(
            code=403,
            code_string='SignatureDoesNotMatch',
            aws_access_token=TEST_AWS_ACCESS_KEY_ID,
            signature=signature,
            message='The request signature we calculated does not match the signature you provided. \
                    Check your key and signing method.')

    # Checking whether the url is expired or not
    if expiration_time < datetime.datetime.utcnow():
        return requests_error_response_xml_signature_calculation(
            code=403,
            code_string='AccessDenied',
            message='Request has expired',
            expires=query_params['X-Amz-Expires'][0]
        )
Beispiel #3
0
def authenticate_presign_url_signv4(method, path, headers, data, url, query_params, request_dict):

    is_presign_valid = False
    for port in PORT_REPLACEMENT:
        match = re.match(HOST_COMBINATION_REGEX, urlparse.urlparse(request_dict['url']).netloc)
        if match and match.group(2):
            request_dict['url'] = request_dict['url'].replace('%s' % match.group(2), '%s' % port)
        else:
            request_dict['url'] = '%s:%s' % (request_dict['url'], port)

        # Calculating Signature
        aws_request = create_request_object(request_dict)
        ReadOnlyCredentials = namedtuple('ReadOnlyCredentials',
                                ['access_key', 'secret_key', 'token'])
        credentials = ReadOnlyCredentials(TEST_AWS_ACCESS_KEY_ID, TEST_AWS_SECRET_ACCESS_KEY,
            query_params.get('X-Amz-Security-Token', None))
        region = query_params['X-Amz-Credential'][0].split('/')[2]
        signer = S3SigV4QueryAuth(credentials, 's3', region, expires=int(query_params['X-Amz-Expires'][0]))
        signature = signer.add_auth(aws_request, query_params['X-Amz-Date'][0])

        expiration_time = datetime.datetime.strptime(query_params['X-Amz-Date'][0], '%Y%m%dT%H%M%SZ') + \
            datetime.timedelta(seconds=int(query_params['X-Amz-Expires'][0]))

        # Comparing the signature in url with signature we calculated
        query_sig = urlparse.unquote(query_params['X-Amz-Signature'][0])
        if query_sig == signature:
            is_presign_valid = True
            break

    # Comparing the signature in url with signature we calculated
    if config.S3_SKIP_SIGNATURE_VALIDATION:
        if not is_presign_valid:
            LOGGER.warning('Signatures do not match, but not raising an error, as S3_SKIP_SIGNATURE_VALIDATION=1')
        signature = query_sig
        is_presign_valid = True

    if not is_presign_valid:

        return requests_error_response_xml_signature_calculation(
            code=403,
            code_string='SignatureDoesNotMatch',
            aws_access_token=TEST_AWS_ACCESS_KEY_ID,
            signature=signature,
            message='The request signature we calculated does not match the signature you provided. \
                    Check your key and signing method.')

    # Checking whether the url is expired or not
    if expiration_time < datetime.datetime.utcnow():
        return requests_error_response_xml_signature_calculation(
            code=403,
            code_string='AccessDenied',
            message='Request has expired',
            expires=query_params['X-Amz-Expires'][0]
        )
Beispiel #4
0
def authenticate_presign_url_signv2(method, path, headers, data, url,
                                    query_params, request_dict):

    # Calculating Signature
    aws_request = create_request_object(request_dict)
    credentials = Credentials(
        access_key=TEST_AWS_ACCESS_KEY_ID,
        secret_key=TEST_AWS_SECRET_ACCESS_KEY,
        token=query_params.get("X-Amz-Security-Token", None),
    )
    auth = HmacV1QueryAuth(credentials=credentials,
                           expires=query_params["Expires"][0])
    split = urlsplit(aws_request.url)
    string_to_sign = auth.get_string_to_sign(method=method,
                                             split=split,
                                             headers=aws_request.headers)
    signature = auth.get_signature(string_to_sign=string_to_sign)

    # Comparing the signature in url with signature we calculated
    query_sig = urlparse.unquote(query_params["Signature"][0])
    if config.S3_SKIP_SIGNATURE_VALIDATION:
        if query_sig != signature:
            LOGGER.warning(
                "Signatures do not match, but not raising an error, as S3_SKIP_SIGNATURE_VALIDATION=1"
            )
        signature = query_sig

    if query_sig != signature:

        return requests_error_response_xml_signature_calculation(
            code=403,
            code_string="SignatureDoesNotMatch",
            aws_access_token=TEST_AWS_ACCESS_KEY_ID,
            string_to_sign=string_to_sign,
            signature=signature,
            message=
            "The request signature we calculated does not match the signature you provided. \
                    Check your key and signing method.",
        )

    # Checking whether the url is expired or not
    if int(query_params["Expires"][0]) < time.time():
        if config.S3_SKIP_SIGNATURE_VALIDATION:
            LOGGER.warning(
                "Signature is expired, but not raising an error, as S3_SKIP_SIGNATURE_VALIDATION=1"
            )
        else:
            return requests_error_response_xml_signature_calculation(
                code=403,
                code_string="AccessDenied",
                message="Request has expired",
                expires=query_params["Expires"][0],
            )
Beispiel #5
0
def authenticate_presign_url(method, path, headers, data=None):

    url = '{}{}'.format(config.get_edge_url(), path)
    parsed = urlparse.urlparse(url)
    query_params = parse_qs(parsed.query)
    forwarded_for = get_forwarded_for_host(headers)
    if forwarded_for:
        url = re.sub('://[^/]+', '://%s' % forwarded_for, url)

    LOGGER.debug('Received presign S3 URL: %s' % url)

    sign_headers = {}
    query_string = {}

    is_v2 = all([p in query_params for p in SIGNATURE_V2_PARAMS])
    is_v4 = all([p in query_params for p in SIGNATURE_V4_PARAMS])

    # Add overrided headers to the query string params
    for param_name, header_name in ALLOWED_HEADER_OVERRIDES.items():
        if param_name in query_params:
            query_string[param_name] = query_params[param_name][0]

    # Request's headers are more essentials than the query parameters in the request.
    # Different values of header in the header of the request and in the query parameter of the
    # request URL will fail the signature calulation. As per the AWS behaviour

    # Add valid headers into the sign_header. Skip the overrided headers
    # and the headers which have been sent in the query string param
    presign_params_lower = \
        [p.lower() for p in SIGNATURE_V4_PARAMS] if is_v4 else [p.lower() for p in SIGNATURE_V2_PARAMS]
    params_header_override = [param_name for param_name, header_name in ALLOWED_HEADER_OVERRIDES.items()]
    if len(query_params) > 2:
        for key in query_params:
            key_lower = key.lower()
            if key_lower not in presign_params_lower:
                if (key_lower not in (header[0].lower() for header in headers) and
                        key_lower not in params_header_override):
                    if key_lower in ['versionid', 'uploadid', 'partnumber']:
                        query_string[key] = query_params[key][0]
                    else:
                        sign_headers[key] = query_params[key][0]

    for header_name, header_value in headers.items():
        header_name_lower = header_name.lower()
        if header_name_lower.startswith('x-amz-') or header_name_lower.startswith('content-'):
            if is_v2 and header_name_lower in query_params:
                sign_headers[header_name] = header_value
            if is_v4 and header_name_lower in query_params['X-Amz-SignedHeaders'][0]:
                sign_headers[header_name] = header_value

    # Preparnig dictionary of request to build AWSRequest's object of the botocore
    request_url = '{}://{}{}'.format(parsed.scheme, parsed.netloc, urlparse.quote(parsed.path))
    request_url = \
        ('%s?%s' % (request_url, urlencode(query_string)) if query_string else request_url)

    if forwarded_for:
        request_url = re.sub('://[^/]+', '://%s' % forwarded_for, request_url)

    bucket_name = extract_bucket_name(headers, parsed.path)

    request_dict = {
        'url_path': urlparse.quote(parsed.path),
        'query_string': query_string,
        'method': method,
        'headers': sign_headers,
        'body': b'',
        'url': request_url,
        'context': {
            'is_presign_request': True,
            'use_global_endpoint': True,
            'signing': {
                'bucket': bucket_name
            }
        }
    }

    # Support for virtual host addressing style in signature version 2
    # We don't need to do this in v4 as we already concerting it to the virtual addressing style.
    # v2 require path base styled request_dict and v4 require virtual styled request_dict

    if uses_host_addressing(headers) and is_v2:
        request_dict['url_path'] = '/{}{}'.format(bucket_name, request_dict['url_path'])
        parsed_url = urlparse.urlparse(request_url)
        request_dict['url'] = '{}://{}:{}{}'.format(
            parsed_url.scheme, S3_VIRTUAL_HOSTNAME, config.EDGE_PORT, request_dict['url_path'])
        request_dict['url'] = \
            ('%s?%s' % (request_dict['url'], urlencode(query_string)) if query_string else request_dict['url'])

    if not is_v2 and any([p in query_params for p in SIGNATURE_V2_PARAMS]):
        response = requests_error_response_xml_signature_calculation(
            code=403,
            message='Query-string authentication requires the Signature, Expires and AWSAccessKeyId parameters',
            code_string='AccessDenied'
        )
    elif is_v2 and not is_v4:
        response = authenticate_presign_url_signv2(method, path, headers, data, url, query_params, request_dict)

    if not is_v4 and any([p in query_params for p in SIGNATURE_V4_PARAMS]):
        response = requests_error_response_xml_signature_calculation(
            code=403,
            message='Query-string authentication requires the X-Amz-Algorithm, \
                X-Amz-Credential, X-Amz-Date, X-Amz-Expires, \
                X-Amz-SignedHeaders and X-Amz-Signature parameters.',
            code_string='AccessDenied'
        )
    elif is_v4 and not is_v2:
        response = authenticate_presign_url_signv4(method, path, headers, data, url, query_params, request_dict)

    if response is not None:
        LOGGER.error('Presign signature calculation failed: %s' % response)
        return response
    LOGGER.debug('Valid presign url.')
Beispiel #6
0
def authenticate_presign_url_signv4(method, path, headers, data, url, query_params, request_dict):
    is_presign_valid = False
    for port in PORT_REPLACEMENT:
        match = re.match(HOST_COMBINATION_REGEX, urlparse.urlparse(request_dict["url"]).netloc)
        if match and match.group(2):
            request_dict["url"] = request_dict["url"].replace("%s" % match.group(2), "%s" % port)
        else:
            request_dict["url"] = "%s:%s" % (request_dict["url"], port)

        # Calculating Signature
        aws_request = create_request_object(request_dict)
        ReadOnlyCredentials = namedtuple(
            "ReadOnlyCredentials", ["access_key", "secret_key", "token"]
        )
        credentials = ReadOnlyCredentials(
            TEST_AWS_ACCESS_KEY_ID,
            TEST_AWS_SECRET_ACCESS_KEY,
            query_params.get("X-Amz-Security-Token", None),
        )
        region = query_params["X-Amz-Credential"][0].split("/")[2]
        signer = S3SigV4QueryAuth(
            credentials, "s3", region, expires=int(query_params["X-Amz-Expires"][0])
        )
        signature = signer.add_auth(aws_request, query_params["X-Amz-Date"][0])

        expiration_time = datetime.datetime.strptime(
            query_params["X-Amz-Date"][0], "%Y%m%dT%H%M%SZ"
        ) + datetime.timedelta(seconds=int(query_params["X-Amz-Expires"][0]))
        expiration_time = expiration_time.replace(tzinfo=datetime.timezone.utc)

        # Comparing the signature in url with signature we calculated
        query_sig = urlparse.unquote(query_params["X-Amz-Signature"][0])
        if query_sig == signature:
            is_presign_valid = True
            break

    # Comparing the signature in url with signature we calculated
    if config.S3_SKIP_SIGNATURE_VALIDATION:
        if not is_presign_valid:
            LOGGER.warning(
                "Signatures do not match, but not raising an error, as S3_SKIP_SIGNATURE_VALIDATION=1"
            )
        signature = query_sig
        is_presign_valid = True

    if not is_presign_valid:
        return requests_error_response_xml_signature_calculation(
            code=403,
            code_string="SignatureDoesNotMatch",
            aws_access_token=TEST_AWS_ACCESS_KEY_ID,
            signature=signature,
            message="The request signature we calculated does not match the signature you provided. \
                    Check your key and signing method.",
        )

    # Checking whether the url is expired or not
    if is_expired(expiration_time):
        if config.S3_SKIP_SIGNATURE_VALIDATION:
            LOGGER.warning(
                "Signature is expired, but not raising an error, as S3_SKIP_SIGNATURE_VALIDATION=1"
            )
        else:
            return requests_error_response_xml_signature_calculation(
                code=403,
                code_string="AccessDenied",
                message="Request has expired",
                expires=query_params["X-Amz-Expires"][0],
            )
Beispiel #7
0
def authenticate_presign_url(method, path, headers, data=None):

    url = "{}{}".format(config.get_edge_url(), path)
    parsed = urlparse.urlparse(url)
    query_params = parse_qs(parsed.query)
    forwarded_for = get_forwarded_for_host(headers)
    if forwarded_for:
        url = re.sub("://[^/]+", "://%s" % forwarded_for, url)

    LOGGER.debug("Received presign S3 URL: %s", url)

    sign_headers = {}
    query_string = {}

    is_v2 = all(p in query_params for p in SIGNATURE_V2_PARAMS)
    is_v4 = all(p in query_params for p in SIGNATURE_V4_PARAMS)

    # Add overrided headers to the query string params
    for param_name, header_name in ALLOWED_HEADER_OVERRIDES.items():
        if param_name in query_params:
            query_string[param_name] = query_params[param_name][0]

    # Request's headers are more essentials than the query parameters in the request.
    # Different values of header in the header of the request and in the query parameter of the
    # request URL will fail the signature calulation. As per the AWS behaviour

    # Add valid headers into the sign_header. Skip the overrided headers
    # and the headers which have been sent in the query string param
    presign_params_lower = (
        [p.lower() for p in SIGNATURE_V4_PARAMS]
        if is_v4
        else [p.lower() for p in SIGNATURE_V2_PARAMS]
    )
    params_header_override = [
        param_name for param_name, header_name in ALLOWED_HEADER_OVERRIDES.items()
    ]
    if len(query_params) > 2:
        for key in query_params:
            key_lower = key.lower()
            if key_lower not in presign_params_lower:
                if (
                    key_lower not in (header[0].lower() for header in headers)
                    and key_lower not in params_header_override
                ):
                    if key_lower in (
                        allowed_param.lower() for allowed_param in ALLOWED_QUERY_PARAMS
                    ):
                        query_string[key] = query_params[key][0]
                    elif key_lower in (
                        blacklisted_header.lower() for blacklisted_header in BLACKLISTED_HEADERS
                    ):
                        pass
                    else:
                        query_string[key] = query_params[key][0]

    for header_name, header_value in headers.items():
        header_name_lower = header_name.lower()
        if header_name_lower.startswith("x-amz-") or header_name_lower.startswith("content-"):
            if is_v2 and header_name_lower in query_params:
                sign_headers[header_name] = header_value
            if is_v4 and header_name_lower in query_params["X-Amz-SignedHeaders"][0]:
                sign_headers[header_name] = header_value

    # Preparnig dictionary of request to build AWSRequest's object of the botocore
    request_url = "{}://{}{}".format(parsed.scheme, parsed.netloc, parsed.path)
    # Fix https://github.com/localstack/localstack/issues/3912
    # urlencode method replaces white spaces with plus sign cause signature calculation to fail
    query_string_encoded = (
        urlencode(query_string, quote_via=urlparse.quote, safe=" ") if query_string else None
    )
    request_url = "%s?%s" % (request_url, query_string_encoded) if query_string else request_url
    if forwarded_for:
        request_url = re.sub("://[^/]+", "://%s" % forwarded_for, request_url)

    bucket_name = extract_bucket_name(headers, parsed.path)

    request_dict = {
        "url_path": parsed.path,
        "query_string": query_string,
        "method": method,
        "headers": sign_headers,
        "body": b"",
        "url": request_url,
        "context": {
            "is_presign_request": True,
            "use_global_endpoint": True,
            "signing": {"bucket": bucket_name},
        },
    }

    # Support for virtual host addressing style in signature version 2
    # We don't need to do this in v4 as we already concerting it to the virtual addressing style.
    # v2 require path base styled request_dict and v4 require virtual styled request_dict

    if uses_host_addressing(headers) and is_v2:
        request_dict["url_path"] = "/{}{}".format(bucket_name, request_dict["url_path"])
        parsed_url = urlparse.urlparse(request_url)
        request_dict["url"] = "{}://{}:{}{}".format(
            parsed_url.scheme,
            S3_VIRTUAL_HOSTNAME,
            config.EDGE_PORT,
            request_dict["url_path"],
        )
        request_dict["url"] = (
            "%s?%s" % (request_dict["url"], query_string_encoded)
            if query_string
            else request_dict["url"]
        )

    if not is_v2 and any(p in query_params for p in SIGNATURE_V2_PARAMS):
        response = requests_error_response_xml_signature_calculation(
            code=403,
            message="Query-string authentication requires the Signature, Expires and AWSAccessKeyId parameters",
            code_string="AccessDenied",
        )
    elif is_v2 and not is_v4:
        response = authenticate_presign_url_signv2(
            method, path, headers, data, url, query_params, request_dict
        )

    if not is_v4 and any(p in query_params for p in SIGNATURE_V4_PARAMS):
        response = requests_error_response_xml_signature_calculation(
            code=403,
            message="Query-string authentication requires the X-Amz-Algorithm, \
                X-Amz-Credential, X-Amz-Date, X-Amz-Expires, \
                X-Amz-SignedHeaders and X-Amz-Signature parameters.",
            code_string="AccessDenied",
        )

    elif is_v4 and not is_v2:
        response = authenticate_presign_url_signv4(
            method, path, headers, data, url, query_params, request_dict
        )

    if response is not None:
        LOGGER.info("Presign signature calculation failed: %s", response)
        return response
    LOGGER.debug("Valid presign url.")