diff --git a/aws-replicator/README.md b/aws-replicator/README.md index ce0c578..0d36ff0 100644 --- a/aws-replicator/README.md +++ b/aws-replicator/README.md @@ -126,6 +126,7 @@ If you wish to access the deprecated instructions, they can be found [here](http ## Change Log +* `0.1.25`: Fix dynamodb proxying for read-only mode. * `0.1.24`: Fix healthcheck probe for proxy container * `0.1.23`: Fix unpinned React.js dependencies preventing webui from loading * `0.1.22`: Fix auth-related imports that prevent the AWS proxy from starting diff --git a/aws-replicator/aws_replicator/server/aws_request_forwarder.py b/aws-replicator/aws_replicator/server/aws_request_forwarder.py index 4614916..a24ba04 100644 --- a/aws-replicator/aws_replicator/server/aws_request_forwarder.py +++ b/aws-replicator/aws_replicator/server/aws_request_forwarder.py @@ -188,6 +188,13 @@ def _is_read_request(self, context: RequestContext) -> bool: # service-specific rules if context.service.service_name == "cognito-idp" and operation_name == "InitiateAuth": return True + if context.service.service_name == "dynamodb" and operation_name in { + "Scan", + "Query", + "BatchGetItem", + "PartiQLSelect", + }: + return True # TODO: add more rules return False diff --git a/aws-replicator/setup.cfg b/aws-replicator/setup.cfg index 58b74be..bcb0d18 100644 --- a/aws-replicator/setup.cfg +++ b/aws-replicator/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = localstack-extension-aws-replicator -version = 0.1.24 +version = 0.1.25 summary = LocalStack AWS Proxy Extension description = Proxy AWS resources into your LocalStack instance long_description = file: README.md diff --git a/aws-replicator/tests/test_proxy_requests.py b/aws-replicator/tests/test_proxy_requests.py index 91cf288..0ce520c 100644 --- a/aws-replicator/tests/test_proxy_requests.py +++ b/aws-replicator/tests/test_proxy_requests.py @@ -10,7 +10,9 @@ from botocore.exceptions import ClientError from localstack.aws.connect import connect_to from localstack.utils.aws.arns import sqs_queue_arn, sqs_queue_url_for_arn +from localstack.utils.aws.resources import create_dynamodb_table from localstack.utils.net import wait_for_port_open +from localstack.utils.strings import short_uid from localstack.utils.sync import retry from aws_replicator.client.auth_proxy import start_aws_auth_proxy @@ -243,3 +245,81 @@ def test_sqs_requests(start_aws_proxy, cleanups): "Attributes" ]["QueueArn"] assert result == queue_arn_aws + + +class TestDynamoDBRequests: + region_name = "us-east-1" + + @pytest.fixture(scope="class") + def dynamodb_client_aws(self): + return boto3.client("dynamodb", region_name=self.region_name) + + @pytest.fixture + def create_dynamodb_table_aws(self, dynamodb_client_aws): + tables = [] + + def factory(**kwargs): + kwargs["client"] = dynamodb_client_aws + if "table_name" not in kwargs: + kwargs["table_name"] = f"test-table-{short_uid()}" + if "partition_key" not in kwargs: + kwargs["partition_key"] = "id" + + tables.append(kwargs["table_name"]) + + return create_dynamodb_table(**kwargs) + + yield factory + + # cleanup + for table in tables: + try: + dynamodb_client_aws.delete_table(TableName=table) + except Exception as e: + print(f"error cleaning up table {table}: {e}", table, e) + + def test_dynamodb_requests_read_only( + self, start_aws_proxy, create_dynamodb_table_aws, dynamodb_client_aws + ): + # create clients + dynamodb_client = connect_to(region_name=self.region_name).dynamodb + + # start proxy - only forwarding requests for read operations + config = ProxyConfig( + services={"dynamodb": {"resources": ".*", "read_only": True}}, + bind_host=PROXY_BIND_HOST, + ) + start_aws_proxy(config) + + # create table in AWS + table_name = f"test-table-{short_uid()}" + create_dynamodb_table_aws(table_name=table_name) + tables_aws = dynamodb_client_aws.list_tables()["TableNames"] + assert table_name in tables_aws + + # assert that local call for this table is proxied + tables_local = dynamodb_client.list_tables()["TableNames"] + assert table_name in tables_local + + item = {"id": {"S": "123"}, "value": {"S": "foobar"}} + # put item via AWS client + dynamodb_client_aws.put_item(TableName=table_name, Item=item) + + # get item via AWS client + result = dynamodb_client_aws.get_item(TableName=table_name, Key={"id": {"S": "123"}}) + assert result["Item"] == item + + # get item via local client + result = dynamodb_client.get_item(TableName=table_name, Key={"id": {"S": "123"}}) + assert result["Item"] == item + + # assert that scan operation is working + result = dynamodb_client.scan(TableName=table_name) + assert len(result["Items"]) == 1 + + # assert that write operation is NOT working - it's sent to localstack, which cannot find the table + item3 = {"id": {"S": "789"}, "value": {"S": "foobar3"}} + with pytest.raises(ClientError) as exc: + dynamodb_client.put_item(TableName=table_name, Item=item3) + + assert exc.match("ResourceNotFoundException")