Esempio n. 1
0
def get_api_from_headers(headers, method=None, path=None, data=None):
    """Determine API and backend port based on "Authorization" or "Host" headers."""

    # initialize result
    result = API_UNKNOWN, 0

    target = headers.get("x-amz-target", "")
    host = headers.get("host", "")
    auth_header = headers.get("authorization", "")

    if not auth_header and not target and "." not in host:
        return result[0], result[1], path, host

    path = path or "/"

    # https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
    try:
        service = extract_service_name_from_auth_header(headers)
        assert service
        result = service, get_service_port_for_account(service, headers)
    except Exception:
        pass

    result_before = result

    # Fallback rules and route customizations applied below
    if host.endswith("cloudfront.net"):
        path = path or "/"
        result = "cloudfront", config.PORT_CLOUDFRONT
    elif target.startswith("AWSCognitoIdentityProviderService") or "cognito-idp." in host:
        result = "cognito-idp", config.PORT_COGNITO_IDP
    elif target.startswith("AWSCognitoIdentityService") or "cognito-identity." in host:
        result = "cognito-identity", config.PORT_COGNITO_IDENTITY
    elif result[0] == "s3" or uses_host_addressing(headers):
        result = "s3", config.PORT_S3
    elif result[0] == "states" in auth_header or host.startswith("states."):
        result = "stepfunctions", config.PORT_STEPFUNCTIONS
    elif "route53." in host:
        result = "route53", config.PORT_ROUTE53
    elif result[0] == "monitoring":
        result = "cloudwatch", config.PORT_CLOUDWATCH
    elif result[0] == "email":
        result = "ses", config.PORT_SES
    elif result[0] == "execute-api" or ".execute-api." in host:
        result = "apigateway", config.PORT_APIGATEWAY
    elif target.startswith("Firehose_"):
        result = "firehose", config.PORT_FIREHOSE
    elif target.startswith("DynamoDB_"):
        result = "dynamodb", config.PORT_DYNAMODB
    elif target.startswith("DynamoDBStreams") or host.startswith("streams.dynamodb."):
        # Note: DDB streams requests use ../dynamodb/.. auth header, hence we also need to update result_before
        result = result_before = "dynamodbstreams", config.PORT_DYNAMODBSTREAMS
    elif result[0] == "EventBridge" or target.startswith("AWSEvents"):
        result = "events", config.PORT_EVENTS
    elif target.startswith("ResourceGroupsTaggingAPI_"):
        result = "resourcegroupstaggingapi", config.PORT_RESOURCEGROUPSTAGGINGAPI
    elif result[0] == "resource-groups":
        result = "resource-groups", config.PORT_RESOURCE_GROUPS

    return result[0], result_before[1] or result[1], path, host
Esempio n. 2
0
 def test_uses_host_address(self):
     addresses = [
         ({"host": f"https://aws.{LOCALHOST}:4566"}, False),
         # attention: This is **not** a host style reference according to s3 specs but a special case from our side
         ({"host": f"https://aws.{LOCALHOST}.localstack.cloud:4566"}, True),
         ({"host": f"https://{LOCALHOST}.aws:4566"}, False),
         ({"host": f"https://{LOCALHOST}.swa:4566"}, False),
         ({"host": f"https://swa.{LOCALHOST}:4566"}, False),
         ({"host": "https://bucket.s3.localhost.localstack.cloud"}, True),
         ({"host": "bucket.s3.eu-west-1.amazonaws.com"}, True),
         ({"host": "https://s3.eu-west-1.localhost.localstack.cloud/bucket"}, False),
         ({"host": "https://s3.eu-west-1.localhost.localstack.cloud/bucket/key"}, False),
         ({"host": "https://s3.localhost.localstack.cloud/bucket"}, False),
         ({"host": "https://bucket.s3.eu-west-1.localhost.localstack.cloud/key"}, True),
         (
             {
                 "host": "https://bucket.s3.eu-west-1.localhost.localstack.cloud/key/key/content.png"
             },
             True,
         ),
         ({"host": "https://s3.localhost.localstack.cloud/bucket/key"}, False),
         ({"host": "https://bucket.s3.eu-west-1.localhost.localstack.cloud"}, True),
         ({"host": "https://bucket.s3.localhost.localstack.cloud/key"}, True),
         ({"host": "bucket.s3.eu-west-1.amazonaws.com"}, True),
         ({"host": "bucket.s3.amazonaws.com"}, True),
         ({"host": "notabucket.amazonaws.com"}, False),
         ({"host": "s3.amazonaws.com"}, False),
         ({"host": "s3.eu-west-1.amazonaws.com"}, False),
     ]
     for headers, expected_result in addresses:
         self.assertEqual(expected_result, s3_utils.uses_host_addressing(headers))
Esempio n. 3
0
 def subdomain_based_buckets(self, request):
     return s3_utils.uses_host_addressing(request.headers)
Esempio n. 4
0
def get_api_from_headers(headers, method=None, path=None, data=None):
    """Determine API and backend port based on "Authorization" or "Host" headers."""

    # initialize result
    result = API_UNKNOWN, 0

    target = headers.get("x-amz-target", "")
    host = headers.get("host", "")
    auth_header = headers.get("authorization", "")

    if not auth_header and not target and "." not in host:
        return result[0], result[1], path, host

    path = path or "/"

    # https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
    try:
        service = extract_service_name_from_auth_header(headers)
        assert service
        result = service, get_service_port_for_account(service, headers)
    except Exception:
        pass

    result_before = result

    # Fallback rules and route customizations applied below
    if host.endswith("cloudfront.net"):
        path = path or "/"
        result = "cloudfront", config.service_port("cloudfront")
    elif target.startswith(
            "AWSCognitoIdentityProviderService") or "cognito-idp." in host:
        result = "cognito-idp", config.service_port("cognito-idp")
    elif target.startswith(
            "AWSCognitoIdentityService") or "cognito-identity." in host:
        result = "cognito-identity", config.service_port("cognito-identity")
    elif result[0] == "s3" or uses_host_addressing(headers):
        result = "s3", config.service_port("s3")
    elif result[0] == "states" in auth_header or host.startswith("states."):
        result = "stepfunctions", config.service_port("stepfunctions")
    elif "route53." in host:
        result = "route53", config.service_port("route53")
    elif result[0] == "monitoring":
        result = "cloudwatch", config.service_port("cloudwatch")
    elif result[0] == "email":
        result = "ses", config.service_port("ses")
    elif result[0] == "execute-api" or ".execute-api." in host:
        result = "apigateway", config.service_port("apigateway")
    elif target.startswith("Firehose_"):
        result = "firehose", config.service_port("firehose")
    elif target.startswith("DynamoDB_"):
        result = "dynamodb", config.service_port("dynamodb")
    elif target.startswith("DynamoDBStreams") or host.startswith(
            "streams.dynamodb."):
        # Note: DDB streams requests use ../dynamodb/.. auth header, hence we also need to update result_before
        result = result_before = "dynamodbstreams", config.service_port(
            "dynamodbstreams")
    elif result[0] == "EventBridge" or target.startswith("AWSEvents"):
        result = "events", config.service_port("events")
    elif target.startswith("ResourceGroupsTaggingAPI_"):
        result = "resourcegroupstaggingapi", config.service_port(
            "resourcegroupstaggingapi")
    elif result[0] == "resource-groups":
        result = "resource-groups", config.service_port("resource-groups")
    elif result[0] == "es" and path is not None and not path.startswith(
            "/2015-01-01/"):
        # For OpenSearch, the auth header points to the API ("es").
        # However, if the path does _not_ start with /2015-01-01 (the API version path prefix for the only ES API
        # version) it is a request to the opensearch API.
        result = "opensearch", config.service_port("opensearch")

    return result[0], result_before[1] or result[1], path, host
Esempio n. 5
0
def legacy_rules(request: Request) -> Optional[str]:
    """
    *Legacy* rules which migrate routing logic which will become obsolete with the ASF Gateway.
    All rules which are implemented here should be migrated to the new router once these services are migrated to ASF.

    TODO: These custom rules should become obsolete by migrating these to use the http/router.py
    """

    path = request.path
    method = request.method
    host = hostname_from_url(request.host)

    # API Gateway invocation URLs
    # TODO: deprecated with #6040, where API GW user routes are served through the gateway directly
    if ("/%s/" % PATH_USER_REQUEST) in request.path or (
            host.endswith(LOCALHOST_HOSTNAME) and "execute-api" in host):
        return "apigateway"

    # DynamoDB shell URLs
    if path.startswith("/shell") or path.startswith("/dynamodb/shell"):
        return "dynamodb"

    # TODO The remaining rules here are special S3 rules - needs to be discussed how these should be handled.
    #      Some are similar to other rules and not that greedy, others are nearly general fallbacks.
    stripped = path.strip("/")
    if method in ["GET", "HEAD"] and stripped:
        # assume that this is an S3 GET request with URL path `/<bucket>/<key ...>`
        return "s3"

    # detect S3 URLs
    if stripped and "/" not in stripped:
        if method == "PUT":
            # assume that this is an S3 PUT bucket request with URL path `/<bucket>`
            return "s3"
        if method == "POST" and "key" in request.values:
            # assume that this is an S3 POST request with form parameters or multipart form in the body
            return "s3"

    # detect S3 requests sent from aws-cli using --no-sign-request option
    if "aws-cli/" in str(request.user_agent):
        return "s3"

    # detect S3 pre-signed URLs (v2 and v4)
    values = request.values
    if any(value in values for value in [
            "AWSAccessKeyId",
            "Signature",
            "X-Amz-Algorithm",
            "X-Amz-Credential",
            "X-Amz-Date",
            "X-Amz-Expires",
            "X-Amz-SignedHeaders",
            "X-Amz-Signature",
    ]):
        return "s3"

    # S3 delete object requests
    if method == "POST" and "delete" in values:
        data_bytes = to_bytes(request.data)
        if b"<Delete" in data_bytes and b"<Key>" in data_bytes:
            return "s3"

    # Put Object API can have multiple keys
    if stripped.count("/") >= 1 and method == "PUT":
        # assume that this is an S3 PUT bucket object request with URL path `/<bucket>/object`
        # or `/<bucket>/object/object1/+`
        return "s3"

    # detect S3 requests with "AWS id:key" Auth headers
    auth_header = request.headers.get("Authorization") or ""
    if auth_header.startswith("AWS "):
        return "s3"

    if uses_host_addressing(request.headers):
        # Note: This needs to be the last rule (and therefore is not in the host rules), since it is incredibly greedy
        return "s3"
Esempio n. 6
0
        def _is_vhost_address(request: HttpRequest) -> bool:
            from localstack.services.s3.s3_utils import uses_host_addressing

            return uses_host_addressing(request.headers)