diff --git a/.github/workflows/aws-proxy.yml b/.github/workflows/aws-proxy.yml
deleted file mode 100644
index 64361937..00000000
--- a/.github/workflows/aws-proxy.yml
+++ /dev/null
@@ -1,101 +0,0 @@
-name: LocalStack AWS Proxy Extension Tests
-
-on:
- push:
- paths:
- - aws-proxy/**
- branches:
- - main
- pull_request:
- paths:
- - .github/workflows/aws-proxy.yml
- - aws-proxy/**
- workflow_dispatch:
-
-jobs:
- tests-aws-proxy:
- name: Run extension tests
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Set up Python 3.x
- uses: actions/setup-python@v4
- with:
- python-version: '3.13'
-
- - name: Use Node.js
- uses: actions/setup-node@v3
- with:
- node-version: 20
-
- - name: Enable Corepack and set Yarn version
- run: |
- corepack enable
- corepack prepare yarn@3.2.3 --activate
- shell: bash
-
- - name: Set up Terraform CLI
- uses: hashicorp/setup-terraform@v2
-
- - name: Run linter
- run: |
- cd aws-proxy
- make install
- make lint
-
- - name: Install LocalStack and extension
- env:
- LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
- run: |
- set -e
- cd aws-proxy
- docker pull localstack/localstack-pro &
- docker pull public.ecr.aws/lambda/python:3.8 &
-
- # install latest CLI packages
- pip install --upgrade localstack
-
- # install dependencies
- sudo apt-get update
- sudo apt-get install -y libsasl2-dev
-
- # build and install extension
- localstack extensions init
- (
- make build
- make enable
- )
-
- # install awslocal/tflocal command lines
- pip install awscli-local[ver1]
- pip install terraform-local
-
- find /home/runner/.cache/localstack/volume/lib/extensions/python_venv/lib/python3.*/site-packages/aws*
- ls -la /home/runner/.cache/localstack/volume/lib/extensions/python_venv/lib/python3.*/site-packages/aws*
- DEBUG=1 GATEWAY_SERVER=hypercorn localstack start -d
- localstack wait
-
- - name: Run integration tests
- env:
- AWS_DEFAULT_REGION: us-east-1
- AWS_ACCESS_KEY_ID: ${{ secrets.TEST_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }}
- run: |
- cd aws-proxy
- make test
-
- - name: Deploy and test sample app
- env:
- AWS_DEFAULT_REGION: us-east-1
- AWS_ACCESS_KEY_ID: ${{ secrets.TEST_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }}
- LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
- run: |
- cd aws-proxy/example
- make test
-
- - name: Print LocalStack logs
- if: always()
- run: localstack logs
diff --git a/aws-proxy/AGENTS.md b/aws-proxy/AGENTS.md
deleted file mode 100644
index bd2d8078..00000000
--- a/aws-proxy/AGENTS.md
+++ /dev/null
@@ -1,64 +0,0 @@
-# AI Agent Instructions for LocalStack AWS Proxy Extension
-
-This repo is a LocalStack extension (plugin) that enables a "proxy mode" - proxying requests for certain AWS services (e.g., S3) to the upstream real AWS cloud, while handling the remaining services locally.
-
-You are an AI agent tasked with adding additional functionality or test coverage to this repo.
-
-## General Instructions
-
-* You can assume that the LocalStack container is running locally, with the proxy extension installed and enabled.
-* You can assume that test AWS credentials are configured in the shell environment where the AI agent is running.
-* Do *not* touch any files outside the working directory (basedir of this file)!
-* You can create new files (no need to prompt for confirmation)
-* You can make modifications to files (no need to prompt for confirmation)
-* You can delete existing files if needed, but only after user confirmation
-* You can call different `make` targets (e.g., `make test`) in this repo (no need to prompt for confirmation)
-* For each new file created or existing file modified, add a header comment to the file, something like `# Note: This file has been (partially or fully) generated by an AI agent.`
-* The proxy tests are executed against real AWS and may incur some costs, so rather than executing the entire test suite or entire modules, focus the testing on individual test functions within a module only.
-* Before claiming success, always double-check against real AWS (via `aws` CLI commands) that everything has been cleaned up and there are no leftover resources from the proxy tests.
-* Never add any `print(..)` statements to the code - use a logger to report any status to the user, if required.
-* To format/lint the codebase you can run `make format` and `make lint`.
-
-## Testing
-
-The proxy functionality is covered by integration tests in the `tests/` folder, one file for each different AWS service (e.g., SQS, S3, etc).
-
-To add a test, follow the pattern in the existing tests.
-It usually involves creating two boto3 clients, one for the LocalStack connection, and one for the real upstream AWS cloud.
-We then run API requests with both clients and assert that the results are identical, thereby ensuring that the proxy functionality is working properly.
-
-To run a single test via `pytest` (say, `test_my_logic` in `test_s3.py`), use the following command:
-```
-TEST_PATH=tests/test_s3.py::test_my_logic make test
-```
-
-### Read-Only Mode Support
-
-Some services have operations that are functionally read-only (don't modify state) but don't follow the standard naming conventions (`Describe*`, `Get*`, `List*`, `Query*`). When adding tests or support for a new service with `read_only: true` configuration, check the [AWS Service Authorization Reference](https://docs.aws.amazon.com/service-authorization/latest/reference/) for the service and identify any operations that:
-- Are classified as "Read" access level but don't match the standard prefixes
-- Evaluate or simulate something without modifying state (e.g., `Evaluate*`, `Simulate*`, `Test*`, `Check*`, `Validate*`)
-
-If you find such operations, add them to the service-specific rules in `aws_proxy/server/aws_request_forwarder.py` in the `_is_read_request` method. This ensures that read-only proxy configurations correctly forward these operations rather than blocking them.
-
-**IMPORTANT**: This step is mandatory when adding a new service. Failure to identify non-standard read-only operations will cause `read_only: true` configurations to incorrectly block legitimate read requests.
-
-Example services with non-standard read-only operations:
-- **AppSync**: `EvaluateCode`, `EvaluateMappingTemplate`
-- **IAM**: `SimulateCustomPolicy`, `SimulatePrincipalPolicy`
-- **Cognito**: `InitiateAuth`
-- **DynamoDB**: `Scan`, `BatchGetItem`, `PartiQLSelect`
-
-When adding new integration tests, consider the following:
-* Include a mix of positive and negative assertions (i.e., presence and absence of resources).
-* Include a mix of different configuration options, e.g., the `read_only: true` flag can be specified in the proxy service configuration YAML, enabling read-only mode (which should be covered by tests as well).
-* Include some tests that cover matching of resource names (the config YAML allows to specify ARN regex patterns), to ensure the proxy is able to selectively forward requests to certain matching AWS resources only.
-* Make sure to either use fixtures (preferred), or reliable cleanups for removing the resources; several fixtures for creating AWS resources are available in the `localstack.testing.pytest.fixtures` module
-* If a test uses multiple resources with interdependencies (e.g., an SQS queue connected to an SNS topic), then the test needs to ensure that both resource types are proxied (i.e., created in real AWS), to avoid a situation where a resource in AWS is attempting to reference a local resource in LocalStack (using account ID `000000000000` in their ARN).
-* When waiting for the creation status of a resource, use the `localstack.utils.sync.retry(..)` utility function, rather than a manual `for` loop.
-* Avoid using `time.sleep()` in tests. Instead, use `localstack.utils.sync.retry(..)` to poll for the expected state. This makes tests more robust and avoids unnecessary delays when resources become available faster than expected.
-
-## Fixing or Enhancing Logic in the Proxy
-
-Notes:
-* The AWS proxy is running as a LocalStack Extension, and the tests are currently set up in a way that they assume the container to be running with the Extension in dev mode. Hence, in order to make actual changes to the proxy logic, we'll need to restart the LocalStack main container. You can either ask me (the user) to restart the container whenever you're making changes in the core logic, or alternatively remove the `localstack-main` container, and then run `EXTENSION_DEV_MODE=1 DEBUG=1 localstack start -d` again to restart the container, which may reveal some error logs, stack traces, etc.
-* If the proxy raises errors or something seems off, you can grab and parse the output of the LocalStack container via `localstack logs`.
diff --git a/aws-proxy/MANIFEST.in b/aws-proxy/MANIFEST.in
deleted file mode 100644
index dfedfcc6..00000000
--- a/aws-proxy/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include pyproject.toml
-recursive-exclude aws_proxy/frontend *
diff --git a/aws-proxy/Makefile b/aws-proxy/Makefile
deleted file mode 100644
index ce74ec8f..00000000
--- a/aws-proxy/Makefile
+++ /dev/null
@@ -1,79 +0,0 @@
-VENV_BIN = python3 -m venv
-VENV_DIR ?= .venv
-VENV_ACTIVATE = $(VENV_DIR)/bin/activate
-VENV_RUN = . $(VENV_ACTIVATE)
-TEST_PATH ?= tests
-PIP_CMD ?= pip
-FRONTEND_FOLDER = aws_proxy/frontend
-COREPACK_EXISTS := $(shell command -v corepack)
-YARN_EXISTS := $(shell command -v yarn)
-
-usage: ## Show this help
- @grep -Fh "##" $(MAKEFILE_LIST) | grep -Fv fgrep | sed -e 's/:.*##\s*/##/g' | awk -F'##' '{ printf "%-25s %s\n", $$1, $$2 }'
-
-clean: ## Clean up
- rm -rf .venv/
- rm -rf build/
- rm -rf .eggs/
- rm -rf *.egg-info/
-
-format: ## Run ruff to format the whole codebase
- ($(VENV_RUN); python -m ruff format .; python -m ruff check --output-format=full --fix .)
-
-lint: ## Run code linter to check code style
- ($(VENV_RUN); python -m ruff check --output-format=full . && python -m ruff format --check .)
-
-test: ## Run tests
- $(VENV_RUN); python -m pytest $(PYTEST_ARGS) $(TEST_PATH)
-
-entrypoints: install build-frontend ## Generate plugin entrypoints for Python package
- $(VENV_RUN); pip install --upgrade plux; python -m plux entrypoints --exclude '**/node_modules/**'
-
-install-build-deps: venv ## Install build dependencies
- $(VENV_RUN); pip install --upgrade build setuptools wheel plux setuptools_scm
-
-build: install-build-deps build-frontend entrypoints ## Build the extension
- $(VENV_RUN); python -m build --no-isolation . --outdir dist
- @# make sure that the entrypoints are contained in the dist folder and are non-empty
- @test -s localstack_extension_aws_proxy.egg-info/entry_points.txt || (echo "Entrypoints were not correctly created! Aborting!" && exit 1)
-
-enable: $(wildcard ./dist/localstack_extension_aws_proxy-*.tar.gz) ## Enable the extension in LocalStack
- $(VENV_RUN); \
- pip uninstall --yes localstack-extension-aws-proxy; \
- localstack extensions -v install file://$?
-
-publish: clean-dist venv build
- $(VENV_RUN); pip install --upgrade twine; twine upload dist/*
-
-check-frontend-deps:
- @if [ -z "$(YARN_EXISTS)" ]; then \
- npm install --global yarn; \
- fi
- @if [ -z "$(COREPACK_EXISTS)" ]; then \
- npm install -g corepack; \
- fi
-
-install-frontend: check-frontend-deps ## Install dependencies of the frontend
- cd $(FRONTEND_FOLDER) && yarn install
-
-build-frontend: # Build the React app
- @if [ ! -d "$(FRONTEND_FOLDER)/node_modules" ]; then \
- $(MAKE) install-frontend; \
- fi
- cd $(FRONTEND_FOLDER); rm -rf build && NODE_ENV=prod npm run build
-
-start-frontend: ## Start the frontend in dev mode (hot reload)
- cd $(FRONTEND_FOLDER); yarn start
-
-venv:
- test -d .venv || $(VENV_BIN) .venv
-
-install: install-frontend venv ## Install dependencies
- $(VENV_RUN); pip install -e .
- $(VENV_RUN); pip install -e .[test]
- touch $(VENV_DIR)/bin/activate
-
-clean-dist: clean
- rm -rf dist/
-
-.PHONY: build clean clean-dist dist install publish test
diff --git a/aws-proxy/README.md b/aws-proxy/README.md
index 3a0e9d6f..8c4b7c9f 100644
--- a/aws-proxy/README.md
+++ b/aws-proxy/README.md
@@ -1,13 +1,11 @@
-AWS Cloud Proxy Extension (experimental)
-========================================
-[](https://app.localstack.cloud/extensions/remote?url=git+https://github.com/localstack/localstack-extensions/#egg=localstack-extension-aws-proxy&subdirectory=aws-proxy)
+AWS Cloud Proxy Extension
+=========================
A LocalStack extension to proxy and integrate AWS resources into your local machine.
This enables one flavor of "hybrid" or "remocal" setups where you can easily bridge the gap between LocalStack (local resources) and remote AWS (resources in the real cloud).
-⚠️ Please note that this extension is experimental and still under active development.
-
-⚠️ Note: Given that the scope of this extension has recently changed (see [below](#resource-replicator-cli-deprecated)) - it has been renamed from `aws-replicator` to `aws-proxy`.
+For a step-by-step tutorial, please refer to the documentation:
+https://docs.localstack.cloud/aws/tutorials/aws-proxy-localstack-extension/
## Prerequisites
@@ -15,23 +13,24 @@ This enables one flavor of "hybrid" or "remocal" setups where you can easily bri
* Docker
* Python
-## AWS Cloud Proxy
+## Installation
-The AWS Cloud Proxy can be used to forward certain API calls in LocalStack to real AWS, in order to enable seamless transition between local and remote resources.
+Install the extension via the LocalStack CLI:
-**Warning:** Be careful when using the proxy - make sure to _never_ give access to production accounts or any critical/sensitive data!
+```bash
+localstack extensions install localstack-extension-aws-proxy
+```
-### Usage
+After installation, restart LocalStack for the extension to take effect.
-#### Using curl (API)
+## Usage
-The proxy can be enabled and disabled via the LocalStack internal API. This is the recommended approach.
+### Enable the proxy
-1. Start LocalStack and install the AWS Proxy extension (restart LocalStack after installation).
+Enable the proxy for specific services (e.g., DynamoDB, S3, Cognito) by posting a configuration along with your AWS credentials:
-2. Enable the proxy for specific services (e.g., DynamoDB, S3, Cognito) by posting a configuration along with your AWS credentials:
-```
-$ curl -X POST http://localhost:4566/_localstack/aws/proxies \
+```bash
+curl -X POST http://localhost:4566/_localstack/aws/proxies \
-H 'Content-Type: application/json' \
-d '{
"config": {
@@ -49,114 +48,29 @@ $ curl -X POST http://localhost:4566/_localstack/aws/proxies \
}'
```
-3. Check the proxy status:
-```
-$ curl http://localhost:4566/_localstack/aws/proxies/status
-```
+### Check proxy status
-4. Disable the proxy:
+```bash
+curl http://localhost:4566/_localstack/aws/proxies/status
```
-$ curl -X POST http://localhost:4566/_localstack/aws/proxies/status \
- -H 'Content-Type: application/json' \
- -d '{"status": "disabled"}'
-```
-
-5. Now, when issuing an API call against LocalStack (e.g., via `awslocal`), the invocation gets forwarded to real AWS and should return data from your real cloud resources.
-
-#### Using the LocalStack Web App
-
-You can also configure the proxy from the LocalStack Web App at https://app.localstack.cloud. Navigate to your instance and use the AWS Proxy extension settings to enable/disable the proxy and manage credentials.
-Alternatively, the extension exposes a local configuration UI at http://localhost:4566/_localstack/aws-proxy/index.html (requires starting LocalStack with `EXTRA_CORS_ALLOWED_ORIGINS=https://aws-proxy.localhost.localstack.cloud:4566`). Use this Web UI to define the proxy configuration (in YAML syntax) and AWS credentials, then save the configuration. To clean up the running proxy container, click "disable" in the UI.
+### Disable the proxy
-
-
-### Resource-specific proxying
-
-As an alternative to forwarding _all_ requests for a particular service, you can also proxy only requests for _specific_ resources to AWS.
-
-For example, assume we own an S3 bucket `my-s3-bucket` in AWS, then we can use the following configuration to forward any requests to `s3://my-s3-bucket` to real AWS, while still handling requests to all other buckets locally in LocalStack:
-```
-services:
- s3:
- resources:
- # list of ARNs of S3 buckets to proxy to real AWS
- - '.*:my-s3-bucket'
- operations:
- # list of operation name regex patterns (optional)
- - 'Get.*'
- - 'Put.*'
- # optionally, specify that only read requests should be allowed (Get*/List*/Describe*, etc)
- read_only: false
- # optionally, allow invoke/execute operations (e.g., Lambda invocations) alongside read_only mode.
- # execute operations have side-effects and are deliberately excluded from read_only by default.
- execute: false
+```bash
+curl -X POST http://localhost:4566/_localstack/aws/proxies/status \
+ -H 'Content-Type: application/json' \
+ -d '{"status": "disabled"}'
```
-Pass this configuration in the `config` field of the `POST /_localstack/aws/proxies` request body (as shown above).
+Once enabled, API calls against LocalStack (e.g., via `awslocal`) are forwarded to real AWS and return data from your real cloud resources.
-If we then perform local operations against the S3 bucket `my-s3-bucket`, the proxy will forward the request and will return the results from real AWS:
-```
-$ awslocal s3 ls s3://my-s3-bucket
-2023-05-14 15:53:40 148 my-file-1.txt
-2023-05-15 10:24:43 22 my-file-2.txt
-```
+## Configuration
-Any other S3 requests targeting other buckets will be run against the local state in LocalStack itself, for example:
-```
-$ awslocal s3 mb s3://test123
-make_bucket: test123
-...
-```
+The following environment variables can be passed to the LocalStack container to customize behavior:
-A more comprehensive sample, involving local Lambda functions combined with remote SQS queues and S3 buckets, can be found in the `example` folder of this repo.
-
-### Configuration
-
-In addition to the proxy services configuration shown above, the following configs can be used to customize the behavior of the extension itself (simply pass them as environment variables to the main LocalStack container):
-* `PROXY_CLEANUP_CONTAINERS`: whether to clean up (remove) the proxy Docker containers once they shut down (default `1`). Can be set to `0` to help debug issues, e.g., if a proxy container starts up and exits immediately.
-* `PROXY_LOCALSTACK_HOST`: the target host to use when the proxy container connects to the LocalStack main container (automatically determined by default)
-* `PROXY_DOCKER_FLAGS`: additional flags that should be passed when creating the proxy Docker containers
-
-## Resource Replicator CLI (deprecated)
-
-Note: Previous versions of this extension also offered a "replicate" mode to copy/clone (rather than proxy) resources from an AWS account into the local instance.
-This functionality has been removed from this extension, and is now available directly in the LocalStack Pro image (see [here](https://docs.localstack.cloud/aws/tooling/aws-replicator)).
-
-If you wish to access the deprecated instructions, they can be found [here](https://github.com/localstack/localstack-extensions/blob/fe0c97e8a9d94f72c80358493e51ce6c1da535dc/aws-replicator/README.md#resource-replicator-cli).
-
-## Change Log
-
-* `0.2.4`: Replace deprecated `localstack aws proxy` CLI command with direct Python/HTTP-based proxy startup; update README with curl-based usage instructions
-* `0.2.3`: Enhance proxy support and tests for several services (API Gateway v1/v2, CloudWatch, AppSync, Kinesis, KMS, SNS, Cognito-IDP)
-* `0.2.2`: Refactor UI to use WebAppExtension pattern
-* `0.2.1`: Restructure project to use pyproject.toml
-* `0.2.0`: Rename extension from `localstack-extension-aws-replicator` to `localstack-extension-aws-proxy`
-* `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
-* `0.1.20`: Fix logic for proxying S3 requests with `*.s3.amazonaws.com` host header
-* `0.1.19`: Print human-readable message for invalid regexes in resource configs; fix logic for proxying S3 requests with host-based addressing
-* `0.1.18`: Update environment check to use SDK Docker client and enable starting the proxy from within Docker (e.g., from the LS main container as part of an init script)
-* `0.1.17`: Add basic support for ARN-based pattern-matching for `secretsmanager` resources
-* `0.1.16`: Update imports for localstack >=3.6 compatibility
-* `0.1.15`: Move localstack dependency installation to extra since it's provided at runtime
-* `0.1.14`: Install missing dependencies into proxy container for localstack >=3.4 compatibility
-* `0.1.13`: Add compatibility with localstack >=3.4; add http2-server; migrate to localstack auth login
-* `0.1.12`: Modify aws credentials text field type to password
-* `0.1.11`: Fix broken imports after recent upstream CloudFormation changes
-* `0.1.10`: Add `REPLICATOR_PROXY_DOCKER_FLAGS` option to pass custom flags to proxy Docker containers
-* `0.1.9`: Enhance proxy networking and add `REPLICATOR_LOCALSTACK_HOST` config option
-* `0.1.8`: Add `REPLICATOR_CLEANUP_PROXY_CONTAINERS` option to skip removing proxy containers for debugging
-* `0.1.7`: Add rolo dependency to tests
-* `0.1.6`: Adjust config to support `LOCALSTACK_AUTH_TOKEN` in addition to legacy API keys
-* `0.1.5`: Minor fix to accommodate recent upstream changes
-* `0.1.4`: Fix imports of `bootstrap.auth` modules for v3.0 compatibility
-* `0.1.3`: Adjust code imports for recent LocalStack v3.0 module changes
-* `0.1.2`: Remove deprecated ProxyListener for starting local aws-replicator proxy server
-* `0.1.1`: Add simple configuration Web UI
-* `0.1.0`: Initial version of extension
+* `PROXY_CLEANUP_CONTAINERS`: whether to remove proxy Docker containers on shutdown (default `1`). Set to `0` to keep containers for debugging.
+* `PROXY_LOCALSTACK_HOST`: the target host used by the proxy container to connect to LocalStack (auto-detected by default).
+* `PROXY_DOCKER_FLAGS`: additional flags passed when creating proxy Docker containers.
## License
diff --git a/aws-proxy/aws_proxy/__init__.py b/aws-proxy/aws_proxy/__init__.py
deleted file mode 100644
index 747d8603..00000000
--- a/aws-proxy/aws_proxy/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-name = "aws-proxy"
diff --git a/aws-proxy/aws_proxy/client/__init__.py b/aws-proxy/aws_proxy/client/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/aws-proxy/aws_proxy/client/auth_proxy.py b/aws-proxy/aws_proxy/client/auth_proxy.py
deleted file mode 100644
index 10e793b7..00000000
--- a/aws-proxy/aws_proxy/client/auth_proxy.py
+++ /dev/null
@@ -1,524 +0,0 @@
-# Note/disclosure: This file has been partially modified by an AI agent.
-import json
-import logging
-import os
-import re
-from functools import cache
-from typing import Dict, Optional, Tuple
-from urllib.parse import urlparse, urlunparse
-
-import boto3
-import requests
-from botocore.awsrequest import AWSPreparedRequest
-from botocore.model import OperationModel, ServiceModel
-from botocore.session import get_session as get_botocore_session
-from localstack.aws.protocol.parser import create_parser
-from localstack.aws.spec import load_service
-from localstack import config as localstack_config
-from localstack.config import external_service_url
-from localstack.constants import (
- AWS_REGION_US_EAST_1,
- DOCKER_IMAGE_NAME_PRO,
- LOCALHOST_HOSTNAME,
-)
-from localstack.http import Request
-from localstack.utils.aws.aws_responses import requests_response
-from localstack.utils.bootstrap import setup_logging
-from localstack.utils.collections import select_attributes
-from localstack.utils.container_utils.container_client import PortMappings
-from localstack.utils.docker_utils import (
- DOCKER_CLIENT,
- reserve_available_container_port,
-)
-from localstack.utils.files import new_tmp_file, save_file
-from localstack.utils.functions import run_safe
-from localstack.utils.net import get_docker_host_from_container, get_free_tcp_port
-from localstack.utils.serving import Server
-from localstack.utils.strings import short_uid, to_bytes, to_str, truncate
-from requests import Response
-
-from aws_proxy import config as repl_config
-from aws_proxy.client.utils import truncate_content
-from aws_proxy.config import HANDLER_PATH_PROXIES
-from aws_proxy.shared.constants import HEADER_HOST_ORIGINAL, SERVICE_NAME_MAPPING
-from aws_proxy.shared.models import AddProxyRequest, ProxyConfig
-
-LOG = logging.getLogger(__name__)
-
-LOG.setLevel(logging.INFO)
-if localstack_config.DEBUG:
- LOG.setLevel(logging.DEBUG)
-
-
-# TODO make configurable
-CLI_PIP_PACKAGE = "localstack-extension-aws-proxy"
-# note: enable the line below temporarily for testing:
-# CLI_PIP_PACKAGE = "git+https://github.com/localstack/localstack-extensions/@branch#egg=localstack-extension-aws-proxy&subdirectory=aws-proxy"
-
-CONTAINER_NAME_PREFIX = "ls-aws-proxy-"
-CONTAINER_CONFIG_FILE = "/tmp/ls.aws.proxy.yml"
-CONTAINER_LOG_FILE = "/tmp/ls-aws-proxy.log"
-
-# default bind host if `bind_host` is not specified for the proxy
-DEFAULT_BIND_HOST = "127.0.0.1"
-
-
-class AuthProxyAWS(Server):
- def __init__(self, config: ProxyConfig, port: int = None):
- self.config = config
- port = port or get_free_tcp_port()
- super().__init__(port=port)
-
- def do_run(self):
- self.register_in_instance()
- self.run_server()
-
- def run_server(self):
- # note: keep import here, to avoid runtime errors
- from .http2_server import run_server
-
- bind_host = self.config.get("bind_host") or DEFAULT_BIND_HOST
- proxy = run_server(
- port=self.port, bind_addresses=[bind_host], handler=self.proxy_request
- )
- proxy.join()
-
- @classmethod
- def start_from_config_file(cls, config_file: str, port: int):
- config = json.load(open(config_file))
- config["bind_host"] = "0.0.0.0"
- cls(config, port=port).run_server()
-
- @staticmethod
- def register_proxy(
- config_file: str, port: int, localstack_host: str, localstack_port: int = 4566
- ):
- config = json.load(open(config_file))
- url = f"http://{localstack_host}:{localstack_port}{HANDLER_PATH_PROXIES}"
- requests.post(url, json={"port": port, "config": config})
-
- def proxy_request(self, request: Request, data: bytes) -> Response:
- parsed = self._extract_region_and_service(request.headers)
- if not parsed:
- return requests_response("", status_code=400)
- region_name, service_name = parsed
-
- # Map service names based on request context
- service_name = self._get_service_name(service_name, request.path)
-
- query_string = to_str(request.query_string or "")
-
- LOG.debug(
- "Proxying request to %s (%s): %s %s %s",
- service_name,
- region_name,
- request.method,
- request.path,
- query_string,
- )
-
- # Convert Quart headers to a dict for the LocalStack Request
- headers_dict = dict(request.headers)
- request = Request(
- body=data,
- method=request.method,
- headers=headers_dict,
- path=request.path,
- query_string=query_string,
- )
- session = boto3.Session()
- client = session.client(service_name, region_name=region_name)
-
- # fix headers (e.g., "Host") and create client
- self._fix_headers(request, service_name)
- self._fix_host_and_path(request, service_name)
-
- # create request and request dict
- operation_model, aws_request, request_dict = self._parse_aws_request(
- request, service_name, region_name=region_name, client=client
- )
-
- # adjust request dict and fix certain edge cases in the request
- self._adjust_request_dict(service_name, request_dict)
-
- headers_truncated = {
- k: truncate(to_str(v)) for k, v in dict(aws_request.headers).items()
- }
- LOG.debug(
- "Sending request for service %s to AWS: %s %s - %s - %s",
- service_name,
- request.method,
- aws_request.url,
- truncate_content(request_dict.get("body"), max_length=500),
- headers_truncated,
- )
- try:
- # send request to upstream AWS
- result = client._endpoint.make_request(operation_model, request_dict)
-
- # create response object - TODO: to be replaced with localstack.http.Response over time
- response = requests_response(
- result[0].content,
- status_code=result[0].status_code,
- headers=dict(result[0].headers),
- )
-
- LOG.debug(
- "Received response for service %s from AWS: %s - %s",
- service_name,
- response.status_code,
- truncate_content(response.content, max_length=500),
- )
- return response
- except Exception as e:
- if LOG.isEnabledFor(logging.DEBUG):
- LOG.exception(
- "Error when making request to AWS service %s: %s", service_name, e
- )
- return requests_response("", status_code=400)
-
- def register_in_instance(self):
- port = getattr(self, "port", None)
- if not port:
- raise Exception("Proxy currently not running")
- url = f"{external_service_url()}{HANDLER_PATH_PROXIES}"
- data = AddProxyRequest(port=port, config=self.config)
- LOG.debug("Registering new proxy in main container via: %s", url)
- try:
- response = requests.post(url, json=data)
- assert response.ok
- return response
- except Exception:
- LOG.warning(
- "Unable to register auth proxy - is LocalStack running with the extension enabled?"
- )
- raise
-
- def deregister_from_instance(self):
- """Deregister this proxy from the LocalStack instance."""
- port = getattr(self, "port", None)
- if not port:
- return
- url = f"{external_service_url()}{HANDLER_PATH_PROXIES}/{port}"
- LOG.debug("Deregistering proxy from main container via: %s", url)
- try:
- response = requests.delete(url)
- return response
- except Exception as e:
- LOG.debug("Unable to deregister auth proxy: %s", e)
-
- def _parse_aws_request(
- self, request: Request, service_name: str, region_name: str, client
- ) -> Tuple[OperationModel, AWSPreparedRequest, Dict]:
- # Get botocore's service model for making the actual AWS request
- botocore_service_model = self._get_botocore_service_model(service_name)
-
- # Check if request uses JSON protocol (X-Amz-Target header) while service model
- # uses RPC v2 CBOR. In this case, we need to parse the request manually since
- # create_parser would reject the X-Amz-Target header for RPC v2 services.
- x_amz_target = request.headers.get("X-Amz-Target") or request.headers.get(
- "X-Amzn-Target"
- )
- if x_amz_target and botocore_service_model.protocol == "smithy-rpc-v2-cbor":
- # Extract operation name from X-Amz-Target (format: "ServiceName.OperationName")
- operation_name = x_amz_target.split(".")[-1]
- operation_model = botocore_service_model.operation_model(operation_name)
- # Parse JSON body
- parsed_request = json.loads(to_str(request.data)) if request.data else {}
- else:
- # Use LocalStack's parser for other protocols
- localstack_service_model = load_service(service_name)
- parser = create_parser(localstack_service_model)
- ls_operation_model, parsed_request = parser.parse(request)
- operation_model = botocore_service_model.operation_model(
- ls_operation_model.name
- )
-
- request_context = {
- "client_region": region_name,
- "has_streaming_input": operation_model.has_streaming_input,
- "auth_type": operation_model.auth_type,
- "client_config": client.meta.config,
- }
- parsed_request = {} if parsed_request is None else parsed_request
- parsed_request = {k: v for k, v in parsed_request.items() if v is not None}
-
- # get endpoint info
- endpoint_info = client._resolve_endpoint_ruleset(
- operation_model, parsed_request, request_context
- )
- # switch for https://github.com/boto/botocore/commit/826b78c54dd87b9da368e9ab6017d8c4823b28c1
- if len(endpoint_info) == 3:
- endpoint_url, additional_headers, properties = endpoint_info
- if properties:
- request_context["endpoint_properties"] = properties
- else:
- endpoint_url, additional_headers = endpoint_info
-
- # create request dict
- request_dict = client._convert_to_request_dict(
- parsed_request,
- operation_model,
- endpoint_url=endpoint_url,
- context=request_context,
- headers=additional_headers,
- )
-
- # TODO: fix for switch between path/host addressing
- # Note: the behavior seems to be different across botocore versions. Seems to be working
- # with 1.29.97 (fix below not required) whereas newer versions like 1.29.151 require the fix.
- if service_name == "s3":
- request_url = request_dict["url"]
- url_parsed = list(urlparse(request_url))
- path_parts = url_parsed[2].strip("/").split("/")
- bucket_subdomain_prefix = f"://{path_parts[0]}.s3."
- if bucket_subdomain_prefix in request_url:
- prefix = f"/{path_parts[0]}"
- url_parsed[2] = url_parsed[2].removeprefix(prefix)
- request_dict["url_path"] = request_dict["url_path"].removeprefix(prefix)
- # replace empty path with "/" (seems required for signature calculation)
- request_dict["url_path"] = request_dict["url_path"] or "/"
- url_parsed[2] = url_parsed[2] or "/"
- # re-construct final URL
- request_dict["url"] = urlunparse(url_parsed)
-
- aws_request = client._endpoint.create_request(request_dict, operation_model)
-
- return operation_model, aws_request, request_dict
-
- def _adjust_request_dict(self, service_name: str, request_dict: Dict):
- """Apply minor fixes to the request dict, which seem to be required in the current setup."""
-
- req_body = request_dict.get("body")
- body_str = run_safe(lambda: to_str(req_body)) or ""
-
- # TODO: this custom fix should not be required - investigate and remove!
- if (
- "'
- f"{region}"
- )
-
- if service_name == "sqs" and isinstance(req_body, dict):
- account_id = self._query_account_id_from_aws()
- if "QueueUrl" in req_body:
- queue_name = req_body["QueueUrl"].split("/")[-1]
- req_body["QueueUrl"] = (
- f"https://queue.amazonaws.com/{account_id}/{queue_name}"
- )
- if "QueueOwnerAWSAccountId" in req_body:
- req_body["QueueOwnerAWSAccountId"] = account_id
- if service_name == "sqs" and request_dict.get("url"):
- req_json = run_safe(lambda: json.loads(body_str)) or {}
- account_id = self._query_account_id_from_aws()
- queue_name = req_json.get("QueueName")
- if account_id and queue_name:
- request_dict["url"] = (
- f"https://queue.amazonaws.com/{account_id}/{queue_name}"
- )
- req_json["QueueOwnerAWSAccountId"] = account_id
- request_dict["body"] = to_bytes(json.dumps(req_json))
-
- def _fix_headers(self, request: Request, service_name: str):
- if service_name == "s3":
- # fix the Host header, to avoid bucket addressing issues
- host = request.headers.get("Host") or ""
- regex = r"^(https?://)?([0-9.]+|localhost)(:[0-9]+)?"
- if re.match(regex, host):
- request.headers["Host"] = re.sub(
- regex, rf"\1s3.{LOCALHOST_HOSTNAME}", host
- )
- request.headers.pop("Content-Length", None)
- request.headers.pop("x-localstack-request-url", None)
- request.headers.pop("X-Forwarded-For", None)
- request.headers.pop("X-Localstack-Tgt-Api", None)
- request.headers.pop("X-Moto-Account-Id", None)
- request.headers.pop("Remote-Addr", None)
-
- def _fix_host_and_path(self, request: Request, service_name: str):
- if service_name == "s3":
- # fix the path and prepend the bucket name, to avoid bucket addressing issues
- regex_base_domain = rf"((amazonaws\.com)|({LOCALHOST_HOSTNAME}))"
- host = request.headers.pop(HEADER_HOST_ORIGINAL, None)
- host = host or request.headers.get("Host") or ""
- match = re.match(rf"(.+)\.s3\..*{regex_base_domain}", host)
- if match:
- # prepend the bucket name (extracted from the host) to the path of the request (path-based addressing)
- request.path = f"/{match.group(1)}{request.path}"
-
- def _extract_region_and_service(self, headers) -> Optional[Tuple[str, str]]:
- auth_header = headers.pop("Authorization", "")
- parts = auth_header.split("Credential=", maxsplit=1)
- if len(parts) < 2:
- return
- parts = parts[1].split("/")
- if len(parts) < 5:
- return
- return parts[2], parts[3]
-
- def _get_service_name(self, service_name: str, path: str) -> str:
- """Map AWS signing service names to boto3 client names based on request context."""
- # Map AWS signing names to boto3 client names
- service_name = SERVICE_NAME_MAPPING.get(service_name, service_name)
- # API Gateway v2 uses 'apigateway' as signing name but needs 'apigatewayv2' client
- if service_name == "apigateway" and path.startswith("/v2/"):
- return "apigatewayv2"
- # CloudWatch uses 'monitoring' as signing name
- if service_name == "monitoring":
- return "cloudwatch"
- return service_name
-
- @cache
- def _query_account_id_from_aws(self) -> str:
- session = boto3.Session()
- sts_client = session.client("sts")
- result = sts_client.get_caller_identity()
- return result["Account"]
-
- @staticmethod
- @cache
- def _get_botocore_service_model(service_name: str):
- """
- Get the botocore service model for a service. This is used instead of LocalStack's
- load_service() to ensure protocol compatibility, as LocalStack may use newer protocol
- versions (e.g., smithy-rpc-v2-cbor) while clients use older protocols (e.g., query).
- """
- session = get_botocore_session()
- loader = session.get_component("data_loader")
- api_data = loader.load_service_model(service_name, "service-2")
- return ServiceModel(api_data)
-
-
-def start_aws_auth_proxy(config: ProxyConfig, port: int = None) -> AuthProxyAWS:
- setup_logging()
- proxy = AuthProxyAWS(config, port=port)
- proxy.start()
- return proxy
-
-
-def start_aws_auth_proxy_in_container(
- config: ProxyConfig, env_vars: dict = None, port: int = None, quiet: bool = False
-):
- """
- Run the auth proxy in a separate local container. This can help in cases where users
- are running into version/dependency issues on their host machines.
- """
- # TODO: Currently running a container and installing the extension on the fly - we
- # should consider building pre-baked images for the extension in the future. Also,
- # the new packaged CLI binary can help us gain more stability over time...
-
- logging.getLogger("localstack.utils.container_utils.docker_cmd_client").setLevel(
- logging.INFO
- )
- logging.getLogger("localstack.utils.docker_utils").setLevel(logging.INFO)
- logging.getLogger("localstack.utils.run").setLevel(logging.INFO)
-
- print("Proxy container is starting up...")
-
- # determine port mapping
- localstack_config.PORTS_CHECK_DOCKER_IMAGE = DOCKER_IMAGE_NAME_PRO
- port = port or reserve_available_container_port()
- ports = PortMappings()
- ports.add(port, port)
-
- # create container
- container_name = f"{CONTAINER_NAME_PREFIX}{short_uid()}"
- image_name = DOCKER_IMAGE_NAME_PRO
- # add host mapping for localstack.cloud to localhost to prevent the health check from failing
- additional_flags = (
- repl_config.PROXY_DOCKER_FLAGS
- + " --add-host=localhost.localstack.cloud:host-gateway"
- )
- DOCKER_CLIENT.create_container(
- image_name,
- name=container_name,
- entrypoint="",
- command=[
- "bash",
- "-c",
- f"touch {CONTAINER_LOG_FILE}; tail -f {CONTAINER_LOG_FILE}",
- ],
- ports=ports,
- additional_flags=additional_flags,
- )
-
- # start container in detached mode
- DOCKER_CLIENT.start_container(container_name, attach=False)
-
- # install extension CLI package
- venv_activate = ". .venv/bin/activate"
- command = [
- "bash",
- "-c",
- # TODO: manually installing quart/h11/hypercorn as a dirty quick fix for now. To be fixed!
- f"{venv_activate}; pip install h11 hypercorn quart; pip install --upgrade --no-deps '{CLI_PIP_PACKAGE}'",
- ]
- DOCKER_CLIENT.exec_in_container(container_name, command=command)
-
- # create config file in container
- config_file_host = new_tmp_file()
- save_file(config_file_host, json.dumps(config))
- DOCKER_CLIENT.copy_into_container(
- container_name, config_file_host, container_path=CONTAINER_CONFIG_FILE
- )
-
- # prepare environment variables
- env_var_names = [
- "DEBUG",
- "AWS_SECRET_ACCESS_KEY",
- "AWS_ACCESS_KEY_ID",
- "AWS_SESSION_TOKEN",
- "AWS_DEFAULT_REGION",
- "LOCALSTACK_AUTH_TOKEN",
- ]
- env_vars = env_vars or os.environ
- env_vars = select_attributes(dict(env_vars), env_var_names)
-
- # Determine target hostname - we make the host configurable via PROXY_LOCALSTACK_HOST,
- # and if not configured then use get_docker_host_from_container() as a fallback.
- target_host = repl_config.PROXY_LOCALSTACK_HOST
- if not repl_config.PROXY_LOCALSTACK_HOST:
- target_host = get_docker_host_from_container()
- env_vars["LOCALSTACK_HOST"] = target_host
-
- try:
- print("Proxy container is ready.")
- # Start proxy server in background using Python directly (no CLI dependency)
- start_server_cmd = (
- f"{venv_activate}; "
- f'python -c "from aws_proxy.client.auth_proxy import AuthProxyAWS; '
- f"AuthProxyAWS.start_from_config_file('{CONTAINER_CONFIG_FILE}', {port})\" "
- f">> {CONTAINER_LOG_FILE} 2>&1 &"
- )
- # Wait for proxy server to start, then register with LocalStack
- register_cmd = (
- f"sleep 2 && {venv_activate}; "
- f'python -c "from aws_proxy.client.auth_proxy import AuthProxyAWS; '
- f"AuthProxyAWS.register_proxy('{CONTAINER_CONFIG_FILE}', {port}, '{target_host}')\" "
- f">> {CONTAINER_LOG_FILE} 2>&1"
- )
- command = f"{start_server_cmd} {register_cmd}"
- DOCKER_CLIENT.exec_in_container(
- container_name,
- command=["bash", "-c", command],
- env_vars=env_vars,
- interactive=False,
- )
- except KeyboardInterrupt:
- pass
- except Exception as e:
- LOG.info("Error: %s", e)
- finally:
- try:
- if repl_config.CLEANUP_PROXY_CONTAINERS:
- DOCKER_CLIENT.remove_container(container_name, force=True)
- except Exception as e:
- if "already in progress" not in str(e):
- raise
diff --git a/aws-proxy/aws_proxy/client/cli.py b/aws-proxy/aws_proxy/client/cli.py
deleted file mode 100644
index f3ece0ea..00000000
--- a/aws-proxy/aws_proxy/client/cli.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import re
-import sys
-
-# Note: This CLI plugin is deprecated and may be removed in a future version.
-# Use the LocalStack internal API or the LocalStack Web App to configure the proxy instead.
-
-import click
-import yaml
-from localstack_cli.cli import LocalstackCli, LocalstackCliPlugin, console
-from localstack_cli.pro.core.cli.aws import aws
-from localstack_cli.pro.core.config import is_auth_token_configured
-from localstack.utils.files import load_file
-
-from aws_proxy.shared.models import ProxyConfig, ProxyServiceConfig
-
-
-class AwsProxyPlugin(LocalstackCliPlugin):
- name = "aws-proxy"
-
- def should_load(self) -> bool:
- return is_auth_token_configured()
-
- def attach(self, cli: LocalstackCli) -> None:
- group: click.Group = cli.group
- if not group.get_command(ctx=None, cmd_name="aws"):
- group.add_command(aws)
- aws.add_command(cmd_aws_proxy)
-
-
-@click.command(name="proxy", help="Start up an authentication proxy against real AWS")
-@click.option(
- "-s",
- "--services",
- help="Comma-delimited list of services to proxy (e.g., sqs,s3)",
- required=False,
-)
-@click.option(
- "-c",
- "--config",
- help="Path to config file for detailed proxy configurations",
- required=False,
-)
-@click.option(
- "--host",
- help="Network bind host to expose the proxy process on (default: 127.0.0.1)",
- required=False,
-)
-@click.option(
- "--container",
- help="Run the proxy in a container and not on the host",
- required=False,
- is_flag=True,
-)
-@click.option(
- "-p",
- "--port",
- help="Custom port to run the proxy on (by default a random port is used)",
- required=False,
-)
-def cmd_aws_proxy(services: str, config: str, container: bool, port: int, host: str):
- from aws_proxy.client.auth_proxy import start_aws_auth_proxy_in_container
-
- config_json: ProxyConfig = {"services": {}}
- if config:
- config_json = yaml.load(load_file(config), Loader=yaml.SafeLoader)
- if host:
- config_json["bind_host"] = host
- if services:
- services = _split_string(services)
- for service in services:
- config_json["services"][service] = ProxyServiceConfig(resources=".*")
- try:
- if container:
- return start_aws_auth_proxy_in_container(config_json)
-
- # note: deferring the import here, to avoid import errors in CLI context
- from aws_proxy.client.auth_proxy import start_aws_auth_proxy
-
- proxy = start_aws_auth_proxy(config_json, port=port)
- proxy.join()
- except Exception as e:
- console.print(f"Unable to start and register auth proxy: {e}")
- sys.exit(1)
-
-
-def _split_string(string):
- return [s.strip().lower() for s in re.split(r"[\s,]+", string) if s.strip()]
diff --git a/aws-proxy/aws_proxy/client/http2_server.py b/aws-proxy/aws_proxy/client/http2_server.py
deleted file mode 100644
index 9155fbb4..00000000
--- a/aws-proxy/aws_proxy/client/http2_server.py
+++ /dev/null
@@ -1,325 +0,0 @@
-# TODO: currently this is only used for the auth_proxy. replace at some point with the more modern gateway
-# server
-import asyncio
-import collections.abc
-import logging
-import os
-import ssl
-import threading
-import traceback
-from typing import Callable, List, Tuple
-
-import h11
-from hypercorn import utils as hypercorn_utils
-from hypercorn.asyncio import serve, tcp_server
-from hypercorn.config import Config
-from hypercorn.events import Closed
-from hypercorn.protocol import http_stream
-from localstack import config
-from localstack.utils.asyncio import ensure_event_loop, run_coroutine, run_sync
-from localstack.utils.files import load_file
-from localstack.utils.http import uses_chunked_encoding
-from localstack.utils.run import FuncThread
-from localstack.utils.sync import retry
-from localstack.utils.threads import TMP_THREADS
-from quart import Quart
-from quart import app as quart_app
-from quart import asgi as quart_asgi
-from quart import make_response, request
-from quart import utils as quart_utils
-from quart.app import _cancel_all_tasks
-
-LOG = logging.getLogger(__name__)
-
-HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"]
-
-# flag to avoid lowercasing all header names (e.g., some AWS S3 SDKs depend on "ETag" response header)
-RETURN_CASE_SENSITIVE_HEADERS = True
-
-# default max content length for HTTP server requests (256 MB)
-DEFAULT_MAX_CONTENT_LENGTH = 256 * 1024 * 1024
-
-# cache of SSL contexts (indexed by cert file names)
-SSL_CONTEXTS = {}
-SSL_LOCK = threading.RLock()
-
-
-def setup_quart_logging():
- # set up loggers to avoid duplicate log lines in quart
- for name in ["quart.app", "quart.serving"]:
- log = logging.getLogger(name)
- log.setLevel(logging.INFO if config.DEBUG else logging.WARNING)
- for hdl in list(log.handlers):
- log.removeHandler(hdl)
-
-
-def apply_patches():
- def InformationalResponse_init(self, *args, **kwargs):
- if kwargs.get("status_code") == 100 and not kwargs.get("reason"):
- # add missing "100 Continue" keyword which makes boto3 HTTP clients fail/hang
- kwargs["reason"] = "Continue"
- InformationalResponse_init_orig(self, *args, **kwargs)
-
- InformationalResponse_init_orig = h11.InformationalResponse.__init__
- h11.InformationalResponse.__init__ = InformationalResponse_init
-
- # skip error logging for ssl.SSLError in hypercorn tcp_server.py _read_data()
-
- async def _read_data(self) -> None:
- try:
- return await _read_data_orig(self)
- except Exception:
- await self.protocol.handle(Closed())
-
- _read_data_orig = tcp_server.TCPServer._read_data
- tcp_server.TCPServer._read_data = _read_data
-
- # skip error logging for ssl.SSLError in hypercorn tcp_server.py _close()
-
- async def _close(self) -> None:
- try:
- return await _close_orig(self)
- except ssl.SSLError:
- return
-
- _close_orig = tcp_server.TCPServer._close
- tcp_server.TCPServer._close = _close
-
- # avoid SSL context initialization errors when running multiple server threads in parallel
-
- def create_ssl_context(self, *args, **kwargs):
- with SSL_LOCK:
- key = "%s%s" % (self.certfile, self.keyfile)
- if key not in SSL_CONTEXTS:
- # perform retries to circumvent "ssl.SSLError: [SSL] PEM lib (_ssl.c:4012)"
- def _do_create():
- SSL_CONTEXTS[key] = create_ssl_context_orig(self, *args, **kwargs)
-
- retry(_do_create, retries=3, sleep=0.5)
- return SSL_CONTEXTS[key]
-
- create_ssl_context_orig = Config.create_ssl_context
- Config.create_ssl_context = create_ssl_context
-
- # apply patch for case-sensitive header names (e.g., some AWS S3 SDKs depend on "ETag" case-sensitive header)
-
- def _encode_headers(headers):
- if RETURN_CASE_SENSITIVE_HEADERS:
- return [(key.encode(), value.encode()) for key, value in headers.items()]
- return [
- (key.lower().encode(), value.encode()) for key, value in headers.items()
- ]
-
- quart_asgi._encode_headers = quart_asgi.encode_headers = _encode_headers
- quart_app.encode_headers = quart_utils.encode_headers = _encode_headers
-
- def build_and_validate_headers(headers):
- validated_headers = []
- for name, value in headers:
- if name[0] == b":"[0]:
- raise ValueError("Pseudo headers are not valid")
- header_name = (
- bytes(name) if RETURN_CASE_SENSITIVE_HEADERS else bytes(name).lower()
- )
- validated_headers.append((header_name.strip(), bytes(value).strip()))
- return validated_headers
-
- hypercorn_utils.build_and_validate_headers = build_and_validate_headers
- http_stream.build_and_validate_headers = build_and_validate_headers
-
- # avoid "h11._util.LocalProtocolError: Too little data for declared Content-Length" for certain status codes
-
- def suppress_body(method, status_code):
- if status_code == 412:
- return False
- return suppress_body_orig(method, status_code)
-
- suppress_body_orig = hypercorn_utils.suppress_body
- hypercorn_utils.suppress_body = suppress_body
- http_stream.suppress_body = suppress_body
-
-
-class HTTPErrorResponse(Exception):
- def __init__(self, *args, code=None, **kwargs):
- super(HTTPErrorResponse, self).__init__(*args, **kwargs)
- self.code = code
-
-
-def get_async_generator_result(result):
- gen, headers = result, {}
- if isinstance(result, tuple) and len(result) >= 2:
- gen, headers = result[:2]
- if not isinstance(gen, (collections.abc.Generator, collections.abc.AsyncGenerator)):
- return
- return gen, headers
-
-
-def run_server(
- port: int,
- bind_addresses: List[str],
- handler: Callable = None,
- asynchronous: bool = True,
- ssl_creds: Tuple[str, str] = None,
- max_content_length: int = None,
- send_timeout: int = None,
-):
- """
- Run an HTTP2-capable Web server on the given port, processing incoming requests via a `handler` function.
- :param port: port to bind to
- :param bind_addresses: addresses to bind to
- :param handler: callable that receives the request and returns a response
- :param asynchronous: whether to start the server asynchronously in the background
- :param ssl_creds: optional tuple with SSL cert file names (cert file, key file)
- :param max_content_length: maximum content length of uploaded payload
- :param send_timeout: timeout (in seconds) for sending the request payload over the wire
- """
-
- ensure_event_loop()
- app = Quart(__name__, static_folder=None)
- app.config["MAX_CONTENT_LENGTH"] = max_content_length or DEFAULT_MAX_CONTENT_LENGTH
- if send_timeout:
- app.config["BODY_TIMEOUT"] = send_timeout
-
- @app.route("/", methods=HTTP_METHODS, defaults={"path": ""})
- @app.route("/", methods=HTTP_METHODS)
- async def index(path=None):
- response = await make_response("{}")
- if handler:
- data = await request.get_data()
- try:
- result = await run_sync(handler, request, data)
- if isinstance(result, Exception):
- raise result
- except Exception as e:
- LOG.warning(
- "Error in proxy handler for request %s %s: %s %s",
- request.method,
- request.url,
- e,
- traceback.format_exc(),
- )
- response.status_code = 500
- if isinstance(e, HTTPErrorResponse):
- response.status_code = e.code or response.status_code
- return response
- if result is not None:
- # check if this is an async generator (for HTTP2 push event responses)
- async_gen = get_async_generator_result(result)
- if async_gen:
- return async_gen
- # prepare and return regular response
- is_chunked = uses_chunked_encoding(result)
- result_content = result.content or ""
- response = await make_response(result_content)
- response.status_code = result.status_code
- if is_chunked:
- response.headers.pop("Content-Length", None)
- result.headers.pop("Server", None)
- result.headers.pop("Date", None)
- headers = {
- k: str(v).replace("\n", r"\n") for k, v in result.headers.items()
- }
- response.headers.update(headers)
- # set multi-value headers
- multi_value_headers = getattr(result, "multi_value_headers", {})
- for key, values in multi_value_headers.items():
- for value in values:
- response.headers.add_header(key, value)
- # set default headers, if required
- if not is_chunked and request.method not in ["OPTIONS", "HEAD"]:
- response_data = await response.get_data()
- response.headers["Content-Length"] = str(len(response_data or ""))
- if "Connection" not in response.headers:
- response.headers["Connection"] = "close"
- # fix headers for OPTIONS requests (possible fix for Firefox requests)
- if request.method == "OPTIONS":
- response.headers.pop("Content-Type", None)
- if not response.headers.get("Cache-Control"):
- response.headers["Cache-Control"] = "no-cache"
- return response
-
- def run_app_sync(*args, loop=None, shutdown_event=None):
- kwargs = {}
- config = Config()
- cert_file_name, key_file_name = ssl_creds or (None, None)
- if cert_file_name:
- kwargs["certfile"] = cert_file_name
- config.certfile = cert_file_name
- if key_file_name:
- kwargs["keyfile"] = key_file_name
- config.keyfile = key_file_name
- setup_quart_logging()
- config.h11_pass_raw_headers = True
- config.bind = [f"{bind_address}:{port}" for bind_address in bind_addresses]
- config.workers = len(bind_addresses)
- loop = loop or ensure_event_loop()
- run_kwargs = {}
- if shutdown_event:
- run_kwargs["shutdown_trigger"] = shutdown_event.wait
- try:
- try:
- return loop.run_until_complete(serve(app, config, **run_kwargs))
- except Exception as e:
- LOG.info(
- "Error running server event loop on port %s: %s %s",
- port,
- e,
- traceback.format_exc(),
- )
- if "SSL" in str(e):
- c_exists = os.path.exists(cert_file_name)
- k_exists = os.path.exists(key_file_name)
- c_size = len(load_file(cert_file_name)) if c_exists else 0
- k_size = len(load_file(key_file_name)) if k_exists else 0
- LOG.warning(
- "Unable to create SSL context. Cert files exist: %s %s (%sB), %s %s (%sB)",
- cert_file_name,
- c_exists,
- c_size,
- key_file_name,
- k_exists,
- k_size,
- )
- raise
- finally:
- try:
- _cancel_all_tasks(loop)
- loop.run_until_complete(loop.shutdown_asyncgens())
- finally:
- asyncio.set_event_loop(None)
- loop.close()
-
- class ProxyThread(FuncThread):
- def __init__(self):
- FuncThread.__init__(self, self.run_proxy, None, name="proxy-thread")
- self.shutdown_event = None
- self.loop = None
-
- def run_proxy(self, *args):
- self.loop = ensure_event_loop()
- self.shutdown_event = asyncio.Event()
- run_app_sync(loop=self.loop, shutdown_event=self.shutdown_event)
-
- def stop(self, quiet=None):
- event = self.shutdown_event
-
- async def set_event():
- event.set()
-
- run_coroutine(set_event(), self.loop)
- super().stop(quiet)
-
- def run_in_thread():
- thread = ProxyThread()
- thread.start()
- TMP_THREADS.append(thread)
- return thread
-
- if asynchronous:
- return run_in_thread()
-
- return run_app_sync()
-
-
-# apply patches on startup
-apply_patches()
diff --git a/aws-proxy/aws_proxy/client/resource_types.json b/aws-proxy/aws_proxy/client/resource_types.json
deleted file mode 100644
index 9d153f08..00000000
--- a/aws-proxy/aws_proxy/client/resource_types.json
+++ /dev/null
@@ -1,442 +0,0 @@
-[
- {"TypeName": "AWS::ACMPCA::Certificate"},
- {"TypeName": "AWS::ACMPCA::CertificateAuthority"},
- {"TypeName": "AWS::ACMPCA::CertificateAuthorityActivation"},
- {"TypeName": "AWS::APS::RuleGroupsNamespace"},
- {"TypeName": "AWS::APS::Workspace"},
- {"TypeName": "AWS::AccessAnalyzer::Analyzer"},
- {"TypeName": "AWS::Amplify::App"},
- {"TypeName": "AWS::Amplify::Branch"},
- {"TypeName": "AWS::Amplify::Domain"},
- {"TypeName": "AWS::AmplifyUIBuilder::Component"},
- {"TypeName": "AWS::AmplifyUIBuilder::Theme"},
- {"TypeName": "AWS::ApiGateway::Account"},
- {"TypeName": "AWS::ApiGateway::ApiKey"},
- {"TypeName": "AWS::ApiGateway::Authorizer"},
- {"TypeName": "AWS::ApiGateway::BasePathMapping"},
- {"TypeName": "AWS::ApiGateway::ClientCertificate"},
- {"TypeName": "AWS::ApiGateway::Deployment"},
- {"TypeName": "AWS::ApiGateway::DocumentationVersion"},
- {"TypeName": "AWS::ApiGateway::DomainName"},
- {"TypeName": "AWS::ApiGateway::Method"},
- {"TypeName": "AWS::ApiGateway::Model"},
- {"TypeName": "AWS::ApiGateway::RequestValidator"},
- {"TypeName": "AWS::ApiGateway::Stage"},
- {"TypeName": "AWS::ApiGateway::UsagePlan"},
- {"TypeName": "AWS::ApiGatewayV2::VpcLink"},
- {"TypeName": "AWS::AppFlow::ConnectorProfile"},
- {"TypeName": "AWS::AppFlow::Flow"},
- {"TypeName": "AWS::AppRunner::Service"},
- {"TypeName": "AWS::AppStream::Application"},
- {"TypeName": "AWS::AppStream::Entitlement"},
- {"TypeName": "AWS::AppSync::DomainName"},
- {"TypeName": "AWS::AppSync::DomainNameApiAssociation"},
- {"TypeName": "AWS::ApplicationInsights::Application"},
- {"TypeName": "AWS::Athena::DataCatalog"},
- {"TypeName": "AWS::Athena::NamedQuery"},
- {"TypeName": "AWS::Athena::PreparedStatement"},
- {"TypeName": "AWS::Athena::WorkGroup"},
- {"TypeName": "AWS::AuditManager::Assessment"},
- {"TypeName": "AWS::AutoScaling::LifecycleHook"},
- {"TypeName": "AWS::AutoScaling::WarmPool"},
- {"TypeName": "AWS::Backup::BackupPlan"},
- {"TypeName": "AWS::Backup::BackupVault"},
- {"TypeName": "AWS::Backup::Framework"},
- {"TypeName": "AWS::Backup::ReportPlan"},
- {"TypeName": "AWS::Batch::ComputeEnvironment"},
- {"TypeName": "AWS::Batch::JobQueue"},
- {"TypeName": "AWS::Batch::SchedulingPolicy"},
- {"TypeName": "AWS::Budgets::BudgetsAction"},
- {"TypeName": "AWS::CE::CostCategory"},
- {"TypeName": "AWS::Cassandra::Keyspace"},
- {"TypeName": "AWS::Cassandra::Table"},
- {"TypeName": "AWS::CertificateManager::Account"},
- {"TypeName": "AWS::Chatbot::SlackChannelConfiguration"},
- {"TypeName": "AWS::CloudFormation::HookDefaultVersion"},
- {"TypeName": "AWS::CloudFormation::HookTypeConfig"},
- {"TypeName": "AWS::CloudFormation::ResourceDefaultVersion"},
- {"TypeName": "AWS::CloudFormation::StackSet"},
- {"TypeName": "AWS::CloudFormation::TypeActivation"},
- {"TypeName": "AWS::CloudFront::CachePolicy"},
- {"TypeName": "AWS::CloudFront::CloudFrontOriginAccessIdentity"},
- {"TypeName": "AWS::CloudFront::Distribution"},
- {"TypeName": "AWS::CloudFront::Function"},
- {"TypeName": "AWS::CloudFront::KeyGroup"},
- {"TypeName": "AWS::CloudFront::OriginRequestPolicy"},
- {"TypeName": "AWS::CloudFront::PublicKey"},
- {"TypeName": "AWS::CloudFront::RealtimeLogConfig"},
- {"TypeName": "AWS::CloudFront::ResponseHeadersPolicy"},
- {"TypeName": "AWS::CloudTrail::Trail"},
- {"TypeName": "AWS::CloudWatch::CompositeAlarm"},
- {"TypeName": "AWS::CloudWatch::MetricStream"},
- {"TypeName": "AWS::CodeArtifact::Domain"},
- {"TypeName": "AWS::CodeArtifact::Repository"},
- {"TypeName": "AWS::CodeGuruProfiler::ProfilingGroup"},
- {"TypeName": "AWS::CodeStarConnections::Connection"},
- {"TypeName": "AWS::CodeStarNotifications::NotificationRule"},
- {"TypeName": "AWS::Config::AggregationAuthorization"},
- {"TypeName": "AWS::Config::ConfigurationAggregator"},
- {"TypeName": "AWS::Config::ConformancePack"},
- {"TypeName": "AWS::Config::OrganizationConformancePack"},
- {"TypeName": "AWS::Config::StoredQuery"},
- {"TypeName": "AWS::DataBrew::Dataset"},
- {"TypeName": "AWS::DataBrew::Job"},
- {"TypeName": "AWS::DataBrew::Project"},
- {"TypeName": "AWS::DataBrew::Recipe"},
- {"TypeName": "AWS::DataBrew::Ruleset"},
- {"TypeName": "AWS::DataBrew::Schedule"},
- {"TypeName": "AWS::DataSync::Agent"},
- {"TypeName": "AWS::DataSync::LocationEFS"},
- {"TypeName": "AWS::DataSync::LocationFSxLustre"},
- {"TypeName": "AWS::DataSync::LocationFSxONTAP"},
- {"TypeName": "AWS::DataSync::LocationFSxOpenZFS"},
- {"TypeName": "AWS::DataSync::LocationFSxWindows"},
- {"TypeName": "AWS::DataSync::LocationHDFS"},
- {"TypeName": "AWS::DataSync::LocationNFS"},
- {"TypeName": "AWS::DataSync::LocationObjectStorage"},
- {"TypeName": "AWS::DataSync::LocationS3"},
- {"TypeName": "AWS::DataSync::LocationSMB"},
- {"TypeName": "AWS::DataSync::Task"},
- {"TypeName": "AWS::Detective::Graph"},
- {"TypeName": "AWS::Detective::MemberInvitation"},
- {"TypeName": "AWS::DevOpsGuru::ResourceCollection"},
- {"TypeName": "AWS::DynamoDB::GlobalTable"},
- {"TypeName": "AWS::EC2::CapacityReservation"},
- {"TypeName": "AWS::EC2::CapacityReservationFleet"},
- {"TypeName": "AWS::EC2::CarrierGateway"},
- {"TypeName": "AWS::EC2::CustomerGateway"},
- {"TypeName": "AWS::EC2::DHCPOptions"},
- {"TypeName": "AWS::EC2::EC2Fleet"},
- {"TypeName": "AWS::EC2::FlowLog"},
- {"TypeName": "AWS::EC2::GatewayRouteTableAssociation"},
- {"TypeName": "AWS::EC2::Host"},
- {"TypeName": "AWS::EC2::IPAM"},
- {"TypeName": "AWS::EC2::IPAMPool"},
- {"TypeName": "AWS::EC2::IPAMScope"},
- {"TypeName": "AWS::EC2::InternetGateway"},
- {"TypeName": "AWS::EC2::LocalGatewayRouteTableVPCAssociation"},
- {"TypeName": "AWS::EC2::NatGateway"},
- {"TypeName": "AWS::EC2::NetworkAcl"},
- {"TypeName": "AWS::EC2::NetworkInsightsAccessScope"},
- {"TypeName": "AWS::EC2::NetworkInsightsAccessScopeAnalysis"},
- {"TypeName": "AWS::EC2::NetworkInsightsAnalysis"},
- {"TypeName": "AWS::EC2::NetworkInsightsPath"},
- {"TypeName": "AWS::EC2::NetworkInterface"},
- {"TypeName": "AWS::EC2::PrefixList"},
- {"TypeName": "AWS::EC2::RouteTable"},
- {"TypeName": "AWS::EC2::SpotFleet"},
- {"TypeName": "AWS::EC2::Subnet"},
- {"TypeName": "AWS::EC2::TransitGateway"},
- {"TypeName": "AWS::EC2::TransitGatewayAttachment"},
- {"TypeName": "AWS::EC2::TransitGatewayConnect"},
- {"TypeName": "AWS::EC2::TransitGatewayMulticastDomain"},
- {"TypeName": "AWS::EC2::TransitGatewayPeeringAttachment"},
- {"TypeName": "AWS::EC2::TransitGatewayVpcAttachment"},
- {"TypeName": "AWS::EC2::VPC"},
- {"TypeName": "AWS::EC2::VPCDHCPOptionsAssociation"},
- {"TypeName": "AWS::EC2::VPCPeeringConnection"},
- {"TypeName": "AWS::EC2::VPNGateway"},
- {"TypeName": "AWS::ECR::PullThroughCacheRule"},
- {"TypeName": "AWS::ECR::RegistryPolicy"},
- {"TypeName": "AWS::ECR::ReplicationConfiguration"},
- {"TypeName": "AWS::ECR::Repository"},
- {"TypeName": "AWS::ECS::CapacityProvider"},
- {"TypeName": "AWS::ECS::Cluster"},
- {"TypeName": "AWS::ECS::ClusterCapacityProviderAssociations"},
- {"TypeName": "AWS::ECS::PrimaryTaskSet"},
- {"TypeName": "AWS::ECS::Service"},
- {"TypeName": "AWS::ECS::TaskDefinition"},
- {"TypeName": "AWS::ECS::TaskSet"},
- {"TypeName": "AWS::EFS::AccessPoint"},
- {"TypeName": "AWS::EFS::FileSystem"},
- {"TypeName": "AWS::EFS::MountTarget"},
- {"TypeName": "AWS::EKS::Addon"},
- {"TypeName": "AWS::EKS::Cluster"},
- {"TypeName": "AWS::EKS::FargateProfile"},
- {"TypeName": "AWS::EKS::IdentityProviderConfig"},
- {"TypeName": "AWS::EKS::Nodegroup"},
- {"TypeName": "AWS::EMR::Studio"},
- {"TypeName": "AWS::EMR::StudioSessionMapping"},
- {"TypeName": "AWS::EMRContainers::VirtualCluster"},
- {"TypeName": "AWS::ElastiCache::GlobalReplicationGroup"},
- {"TypeName": "AWS::ElastiCache::User"},
- {"TypeName": "AWS::ElastiCache::UserGroup"},
- {"TypeName": "AWS::ElasticLoadBalancingV2::Listener"},
- {"TypeName": "AWS::ElasticLoadBalancingV2::ListenerRule"},
- {"TypeName": "AWS::EventSchemas::RegistryPolicy"},
- {"TypeName": "AWS::Events::ApiDestination"},
- {"TypeName": "AWS::Events::Archive"},
- {"TypeName": "AWS::Events::Connection"},
- {"TypeName": "AWS::Events::Endpoint"},
- {"TypeName": "AWS::Evidently::Experiment"},
- {"TypeName": "AWS::Evidently::Feature"},
- {"TypeName": "AWS::Evidently::Launch"},
- {"TypeName": "AWS::Evidently::Project"},
- {"TypeName": "AWS::FIS::ExperimentTemplate"},
- {"TypeName": "AWS::FMS::NotificationChannel"},
- {"TypeName": "AWS::FMS::Policy"},
- {"TypeName": "AWS::FinSpace::Environment"},
- {"TypeName": "AWS::Forecast::DatasetGroup"},
- {"TypeName": "AWS::FraudDetector::Detector"},
- {"TypeName": "AWS::FraudDetector::EntityType"},
- {"TypeName": "AWS::FraudDetector::EventType"},
- {"TypeName": "AWS::FraudDetector::Label"},
- {"TypeName": "AWS::FraudDetector::Outcome"},
- {"TypeName": "AWS::FraudDetector::Variable"},
- {"TypeName": "AWS::GameLift::Alias"},
- {"TypeName": "AWS::GameLift::Fleet"},
- {"TypeName": "AWS::GameLift::GameServerGroup"},
- {"TypeName": "AWS::GlobalAccelerator::Accelerator"},
- {"TypeName": "AWS::GlobalAccelerator::EndpointGroup"},
- {"TypeName": "AWS::GlobalAccelerator::Listener"},
- {"TypeName": "AWS::Glue::Registry"},
- {"TypeName": "AWS::Glue::Schema"},
- {"TypeName": "AWS::GreengrassV2::ComponentVersion"},
- {"TypeName": "AWS::GroundStation::Config"},
- {"TypeName": "AWS::GroundStation::MissionProfile"},
- {"TypeName": "AWS::HealthLake::FHIRDatastore"},
- {"TypeName": "AWS::IAM::InstanceProfile"},
- {"TypeName": "AWS::IAM::OIDCProvider"},
- {"TypeName": "AWS::IAM::Role"},
- {"TypeName": "AWS::IAM::SAMLProvider"},
- {"TypeName": "AWS::IAM::ServerCertificate"},
- {"TypeName": "AWS::IAM::VirtualMFADevice"},
- {"TypeName": "AWS::ImageBuilder::DistributionConfiguration"},
- {"TypeName": "AWS::ImageBuilder::ImagePipeline"},
- {"TypeName": "AWS::ImageBuilder::InfrastructureConfiguration"},
- {"TypeName": "AWS::Inspector::AssessmentTarget"},
- {"TypeName": "AWS::InspectorV2::Filter"},
- {"TypeName": "AWS::IoT::AccountAuditConfiguration"},
- {"TypeName": "AWS::IoT::Authorizer"},
- {"TypeName": "AWS::IoT::CACertificate"},
- {"TypeName": "AWS::IoT::Certificate"},
- {"TypeName": "AWS::IoT::CustomMetric"},
- {"TypeName": "AWS::IoT::Dimension"},
- {"TypeName": "AWS::IoT::DomainConfiguration"},
- {"TypeName": "AWS::IoT::FleetMetric"},
- {"TypeName": "AWS::IoT::Logging"},
- {"TypeName": "AWS::IoT::MitigationAction"},
- {"TypeName": "AWS::IoT::ProvisioningTemplate"},
- {"TypeName": "AWS::IoT::ResourceSpecificLogging"},
- {"TypeName": "AWS::IoT::RoleAlias"},
- {"TypeName": "AWS::IoT::ScheduledAudit"},
- {"TypeName": "AWS::IoT::SecurityProfile"},
- {"TypeName": "AWS::IoT::TopicRule"},
- {"TypeName": "AWS::IoT::TopicRuleDestination"},
- {"TypeName": "AWS::IoTAnalytics::Channel"},
- {"TypeName": "AWS::IoTAnalytics::Dataset"},
- {"TypeName": "AWS::IoTAnalytics::Datastore"},
- {"TypeName": "AWS::IoTAnalytics::Pipeline"},
- {"TypeName": "AWS::IoTEvents::AlarmModel"},
- {"TypeName": "AWS::IoTEvents::DetectorModel"},
- {"TypeName": "AWS::IoTEvents::Input"},
- {"TypeName": "AWS::IoTFleetHub::Application"},
- {"TypeName": "AWS::IoTSiteWise::AccessPolicy"},
- {"TypeName": "AWS::IoTSiteWise::Asset"},
- {"TypeName": "AWS::IoTSiteWise::AssetModel"},
- {"TypeName": "AWS::IoTSiteWise::Dashboard"},
- {"TypeName": "AWS::IoTSiteWise::Gateway"},
- {"TypeName": "AWS::IoTSiteWise::Portal"},
- {"TypeName": "AWS::IoTSiteWise::Project"},
- {"TypeName": "AWS::KMS::Alias"},
- {"TypeName": "AWS::KMS::Key"},
- {"TypeName": "AWS::KMS::ReplicaKey"},
- {"TypeName": "AWS::KafkaConnect::Connector"},
- {"TypeName": "AWS::Kendra::DataSource"},
- {"TypeName": "AWS::Kendra::Faq"},
- {"TypeName": "AWS::Kendra::Index"},
- {"TypeName": "AWS::Kinesis::Stream"},
- {"TypeName": "AWS::KinesisAnalyticsV2::Application"},
- {"TypeName": "AWS::KinesisFirehose::DeliveryStream"},
- {"TypeName": "AWS::KinesisVideo::SignalingChannel"},
- {"TypeName": "AWS::KinesisVideo::Stream"},
- {"TypeName": "AWS::LakeFormation::Tag"},
- {"TypeName": "AWS::Lambda::CodeSigningConfig"},
- {"TypeName": "AWS::Lambda::EventSourceMapping"},
- {"TypeName": "AWS::Lambda::Function"},
- {"TypeName": "AWS::Lambda::Url"},
- {"TypeName": "AWS::LicenseManager::Grant"},
- {"TypeName": "AWS::LicenseManager::License"},
- {"TypeName": "AWS::Lightsail::Alarm"},
- {"TypeName": "AWS::Lightsail::Bucket"},
- {"TypeName": "AWS::Lightsail::Certificate"},
- {"TypeName": "AWS::Lightsail::Container"},
- {"TypeName": "AWS::Lightsail::Database"},
- {"TypeName": "AWS::Lightsail::Disk"},
- {"TypeName": "AWS::Lightsail::Instance"},
- {"TypeName": "AWS::Lightsail::LoadBalancer"},
- {"TypeName": "AWS::Lightsail::LoadBalancerTlsCertificate"},
- {"TypeName": "AWS::Lightsail::StaticIp"},
- {"TypeName": "AWS::Logs::LogGroup"},
- {"TypeName": "AWS::Logs::MetricFilter"},
- {"TypeName": "AWS::Logs::QueryDefinition"},
- {"TypeName": "AWS::Logs::ResourcePolicy"},
- {"TypeName": "AWS::LookoutMetrics::AnomalyDetector"},
- {"TypeName": "AWS::LookoutVision::Project"},
- {"TypeName": "AWS::MSK::BatchScramSecret"},
- {"TypeName": "AWS::MSK::Cluster"},
- {"TypeName": "AWS::MSK::Configuration"},
- {"TypeName": "AWS::MWAA::Environment"},
- {"TypeName": "AWS::Macie::CustomDataIdentifier"},
- {"TypeName": "AWS::Macie::FindingsFilter"},
- {"TypeName": "AWS::Macie::Session"},
- {"TypeName": "AWS::MediaConnect::Flow"},
- {"TypeName": "AWS::MediaConnect::FlowEntitlement"},
- {"TypeName": "AWS::MediaConnect::FlowOutput"},
- {"TypeName": "AWS::MediaConnect::FlowSource"},
- {"TypeName": "AWS::MediaConnect::FlowVpcInterface"},
- {"TypeName": "AWS::MediaPackage::Channel"},
- {"TypeName": "AWS::MediaPackage::OriginEndpoint"},
- {"TypeName": "AWS::MediaPackage::PackagingGroup"},
- {"TypeName": "AWS::MemoryDB::ACL"},
- {"TypeName": "AWS::MemoryDB::Cluster"},
- {"TypeName": "AWS::MemoryDB::ParameterGroup"},
- {"TypeName": "AWS::MemoryDB::SubnetGroup"},
- {"TypeName": "AWS::MemoryDB::User"},
- {"TypeName": "AWS::NetworkFirewall::Firewall"},
- {"TypeName": "AWS::NetworkFirewall::FirewallPolicy"},
- {"TypeName": "AWS::NetworkFirewall::LoggingConfiguration"},
- {"TypeName": "AWS::NetworkFirewall::RuleGroup"},
- {"TypeName": "AWS::NetworkManager::Device"},
- {"TypeName": "AWS::NetworkManager::GlobalNetwork"},
- {"TypeName": "AWS::NetworkManager::Link"},
- {"TypeName": "AWS::NetworkManager::Site"},
- {"TypeName": "AWS::OpenSearchService::Domain"},
- {"TypeName": "AWS::OpsWorksCM::Server"},
- {"TypeName": "AWS::Personalize::Dataset"},
- {"TypeName": "AWS::QLDB::Stream"},
- {"TypeName": "AWS::QuickSight::Analysis"},
- {"TypeName": "AWS::QuickSight::Dashboard"},
- {"TypeName": "AWS::QuickSight::DataSet"},
- {"TypeName": "AWS::QuickSight::DataSource"},
- {"TypeName": "AWS::QuickSight::Template"},
- {"TypeName": "AWS::QuickSight::Theme"},
- {"TypeName": "AWS::RDS::DBProxy"},
- {"TypeName": "AWS::RDS::DBProxyEndpoint"},
- {"TypeName": "AWS::RDS::DBProxyTargetGroup"},
- {"TypeName": "AWS::RDS::EventSubscription"},
- {"TypeName": "AWS::RDS::GlobalCluster"},
- {"TypeName": "AWS::RUM::AppMonitor"},
- {"TypeName": "AWS::Redshift::Cluster"},
- {"TypeName": "AWS::Redshift::EndpointAccess"},
- {"TypeName": "AWS::Redshift::EndpointAuthorization"},
- {"TypeName": "AWS::Redshift::EventSubscription"},
- {"TypeName": "AWS::Redshift::ScheduledAction"},
- {"TypeName": "AWS::RedshiftServerless::Namespace"},
- {"TypeName": "AWS::RedshiftServerless::Workgroup"},
- {"TypeName": "AWS::RefactorSpaces::Route"},
- {"TypeName": "AWS::Rekognition::Collection"},
- {"TypeName": "AWS::Rekognition::Project"},
- {"TypeName": "AWS::Rekognition::StreamProcessor"},
- {"TypeName": "AWS::ResilienceHub::App"},
- {"TypeName": "AWS::ResilienceHub::ResiliencyPolicy"},
- {"TypeName": "AWS::ResourceGroups::Group"},
- {"TypeName": "AWS::RoboMaker::Fleet"},
- {"TypeName": "AWS::RoboMaker::Robot"},
- {"TypeName": "AWS::RoboMaker::RobotApplication"},
- {"TypeName": "AWS::RoboMaker::SimulationApplication"},
- {"TypeName": "AWS::RolesAnywhere::CRL"},
- {"TypeName": "AWS::RolesAnywhere::Profile"},
- {"TypeName": "AWS::RolesAnywhere::TrustAnchor"},
- {"TypeName": "AWS::Route53::CidrCollection"},
- {"TypeName": "AWS::Route53::HealthCheck"},
- {"TypeName": "AWS::Route53::HostedZone"},
- {"TypeName": "AWS::Route53::KeySigningKey"},
- {"TypeName": "AWS::Route53Resolver::FirewallDomainList"},
- {"TypeName": "AWS::Route53Resolver::FirewallRuleGroup"},
- {"TypeName": "AWS::Route53Resolver::FirewallRuleGroupAssociation"},
- {"TypeName": "AWS::Route53Resolver::ResolverRule"},
- {"TypeName": "AWS::S3::AccessPoint"},
- {"TypeName": "AWS::S3::Bucket"},
- {"TypeName": "AWS::S3::MultiRegionAccessPoint"},
- {"TypeName": "AWS::S3::MultiRegionAccessPointPolicy"},
- {"TypeName": "AWS::S3::StorageLens"},
- {"TypeName": "AWS::S3ObjectLambda::AccessPoint"},
- {"TypeName": "AWS::S3ObjectLambda::AccessPointPolicy"},
- {"TypeName": "AWS::S3Outposts::AccessPoint"},
- {"TypeName": "AWS::S3Outposts::Bucket"},
- {"TypeName": "AWS::S3Outposts::BucketPolicy"},
- {"TypeName": "AWS::SES::ConfigurationSet"},
- {"TypeName": "AWS::SES::ConfigurationSetEventDestination"},
- {"TypeName": "AWS::SES::ContactList"},
- {"TypeName": "AWS::SES::EmailIdentity"},
- {"TypeName": "AWS::SES::Template"},
- {"TypeName": "AWS::SQS::Queue"},
- {"TypeName": "AWS::SSM::Association"},
- {"TypeName": "AWS::SSM::Document"},
- {"TypeName": "AWS::SSM::ResourceDataSync"},
- {"TypeName": "AWS::SSMContacts::Contact"},
- {"TypeName": "AWS::SSMContacts::ContactChannel"},
- {"TypeName": "AWS::SSMIncidents::ReplicationSet"},
- {"TypeName": "AWS::SSMIncidents::ResponsePlan"},
- {"TypeName": "AWS::SSO::InstanceAccessControlAttributeConfiguration"},
- {"TypeName": "AWS::SSO::PermissionSet"},
- {"TypeName": "AWS::SageMaker::AppImageConfig"},
- {"TypeName": "AWS::SageMaker::Device"},
- {"TypeName": "AWS::SageMaker::DeviceFleet"},
- {"TypeName": "AWS::SageMaker::Domain"},
- {"TypeName": "AWS::SageMaker::Image"},
- {"TypeName": "AWS::SageMaker::ModelPackage"},
- {"TypeName": "AWS::SageMaker::ModelPackageGroup"},
- {"TypeName": "AWS::SageMaker::MonitoringSchedule"},
- {"TypeName": "AWS::SageMaker::Pipeline"},
- {"TypeName": "AWS::SageMaker::Project"},
- {"TypeName": "AWS::SageMaker::UserProfile"},
- {"TypeName": "AWS::ServiceCatalog::CloudFormationProvisionedProduct"},
- {"TypeName": "AWS::ServiceCatalog::ServiceAction"},
- {"TypeName": "AWS::ServiceCatalogAppRegistry::Application"},
- {"TypeName": "AWS::ServiceCatalogAppRegistry::AttributeGroup"},
- {"TypeName": "AWS::Signer::SigningProfile"},
- {"TypeName": "AWS::StepFunctions::Activity"},
- {"TypeName": "AWS::StepFunctions::StateMachine"},
- {"TypeName": "AWS::Synthetics::Canary"},
- {"TypeName": "AWS::Timestream::Database"},
- {"TypeName": "AWS::Timestream::ScheduledQuery"},
- {"TypeName": "AWS::Timestream::Table"},
- {"TypeName": "AWS::Transfer::Workflow"},
- {"TypeName": "AWS::WAFv2::IPSet"},
- {"TypeName": "AWS::WAFv2::LoggingConfiguration"},
- {"TypeName": "AWS::WAFv2::RegexPatternSet"},
- {"TypeName": "AWS::WAFv2::RuleGroup"},
- {"TypeName": "AWS::WAFv2::WebACL"},
- {"TypeName": "AWS::WAFv2::WebACLAssociation"},
- {"TypeName": "AWS::XRay::Group"},
- {"TypeName": "AWS::XRay::SamplingRule"},
- {"TypeName": "AWSQS::EKS::Cluster"},
- {"TypeName": "AWSQS::Kubernetes::Helm"},
- {"TypeName": "AWSQS::Kubernetes::Resource"},
- {"TypeName": "Atlassian::Opsgenie::Integration"},
- {"TypeName": "Atlassian::Opsgenie::Team"},
- {"TypeName": "Atlassian::Opsgenie::User"},
- {"TypeName": "ConfluentCloud::IAM::ServiceAccount"},
- {"TypeName": "Datadog::Dashboards::Dashboard"},
- {"TypeName": "Datadog::Integrations::AWS"},
- {"TypeName": "Datadog::Monitors::Downtime"},
- {"TypeName": "Datadog::Monitors::Monitor"},
- {"TypeName": "Datadog::SLOs::SLO"},
- {"TypeName": "FireEye::CloudIntegrations::Cloudwatch"},
- {"TypeName": "Generic::Database::Schema"},
- {"TypeName": "Generic::Transcribe::Vocabulary"},
- {"TypeName": "MongoDb::Atlas::AwsIamDatabaseUser"},
- {"TypeName": "PaloAltoNetworks::CloudNGFW::NGFW"},
- {"TypeName": "PaloAltoNetworks::CloudNGFW::RuleStack"},
- {"TypeName": "Snyk::Container::Helm"},
- {"TypeName": "Spot::Elastigroup::Group"},
- {"TypeName": "Sysdig::Helm::Agent"},
- {"TypeName": "TF::AD::Computer"},
- {"TypeName": "TF::AD::User"},
- {"TypeName": "TF::AWS::KeyPair"},
- {"TypeName": "TF::AWS::S3Bucket"},
- {"TypeName": "TF::AWS::S3BucketObject"},
- {"TypeName": "TF::Akamai::DnsRecord"},
- {"TypeName": "TF::AzureAD::Application"},
- {"TypeName": "TF::AzureAD::User"},
- {"TypeName": "TF::Cloudflare::Record"},
- {"TypeName": "TF::DigitalOcean::Droplet"},
- {"TypeName": "TF::GitHub::Repository"},
- {"TypeName": "TF::Google::StorageBucket"},
- {"TypeName": "TF::PagerDuty::Service"},
- {"TypeName": "TF::Random::String"},
- {"TypeName": "TF::Random::Uuid"},
- {"TypeName": "TrendMicro::CloudOneContainer::Helm"}
-]
diff --git a/aws-proxy/aws_proxy/client/utils.py b/aws-proxy/aws_proxy/client/utils.py
deleted file mode 100644
index cbaa557f..00000000
--- a/aws-proxy/aws_proxy/client/utils.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from typing import Union
-
-from localstack.utils.functions import run_safe
-from localstack.utils.strings import to_str, truncate
-
-
-# TODO: add to common utils
-def truncate_content(content: Union[str, bytes], max_length: int = None):
- max_length = max_length or 100
- if isinstance(content, bytes):
- content = run_safe(lambda: to_str(content)) or content
- return truncate(content, max_length=max_length)
diff --git a/aws-proxy/aws_proxy/config.py b/aws-proxy/aws_proxy/config.py
deleted file mode 100644
index 454156d5..00000000
--- a/aws-proxy/aws_proxy/config.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import os
-
-from localstack.config import is_env_not_false
-from localstack.constants import INTERNAL_RESOURCE_PATH
-
-# handler path within the internal /_localstack endpoint
-HANDLER_PATH_PROXY = f"{INTERNAL_RESOURCE_PATH}/aws/proxy"
-HANDLER_PATH_PROXIES = f"{INTERNAL_RESOURCE_PATH}/aws/proxies"
-
-# whether to clean up proxy containers (set to "0" to investigate startup issues)
-CLEANUP_PROXY_CONTAINERS = is_env_not_false("PROXY_CLEANUP_CONTAINERS")
-
-# additional Docker flags to pass to the proxy containers
-PROXY_DOCKER_FLAGS = (os.getenv("PROXY_DOCKER_FLAGS") or "").strip()
-
-# LS hostname to use for proxy Docker container to register itself at the main container
-PROXY_LOCALSTACK_HOST = (os.getenv("PROXY_LOCALSTACK_HOST") or "").strip()
diff --git a/aws-proxy/aws_proxy/frontend/.esbuild/esbuild.config.js b/aws-proxy/aws_proxy/frontend/.esbuild/esbuild.config.js
deleted file mode 100644
index 2ed5464d..00000000
--- a/aws-proxy/aws_proxy/frontend/.esbuild/esbuild.config.js
+++ /dev/null
@@ -1,99 +0,0 @@
-/* eslint-disable global-require */
-
-const esbuild = require('esbuild');
-const path = require('path');
-
-const SvgrPlugin = require('esbuild-plugin-svgr');
-const CopyPlugin = require('esbuild-plugin-copy').default;
-const CleanPlugin = require('esbuild-plugin-clean').default;
-const { NodeModulesPolyfillPlugin } = require('@esbuild-plugins/node-modules-polyfill');
-
-const packageJson = require('../package.json');
-const HtmlPlugin = require('./plugins/html');
-const { writeFileSync } = require('fs');
-
-const CURRENT_ENV = process.env.NODE_ENV || 'development.local';
-const BUILD_PATH = path.join(__dirname, '..', '..', 'server', 'static');
-
-const BUILD_CONFIG = {
- entryPoints: [
- path.join(__dirname, '..', 'src', 'index.tsx'),
- path.join(__dirname, '..', 'src', 'index.html'),
- ],
- assetNames: '[name]-[hash]',
- entryNames: '[name]-[hash]',
- outdir: BUILD_PATH,
- bundle: true,
- minify: !CURRENT_ENV.includes('development.local'),
- sourcemap: true,
- target: 'es2020',
- metafile: true,
- // splitting: true,
- // set in case file loader is added below
- plugins: [
- CleanPlugin({
- patterns: [`${BUILD_PATH}/*`, `!${BUILD_PATH}/index.html`],
- sync: true,
- verbose: false,
- options: {
- force: true
- }
- }),
- SvgrPlugin({
- prettier: false,
- svgo: false,
- svgoConfig: {
- plugins: [{ removeViewBox: false }],
- },
- titleProp: true,
- ref: true,
- }),
- CopyPlugin({
- copyOnStart: true,
- // https://github.com/LinbuduLab/nx-plugins/issues/57
- assets: [
- {
- from: ['./public/*'],
- to: ['./'],
- },
- ],
- }),
- NodeModulesPolyfillPlugin(),
- HtmlPlugin({
- filename: path.join(BUILD_PATH, 'index.html'),
- env: true,
- }),
- ],
- inject: [path.join(__dirname, 'esbuild.shims.js')],
- define: {
- // Define replacements for env vars starting with `REACT_APP_`
- ...Object.entries(process.env).reduce(
- (memo, [name, value]) => name.startsWith('REACT_APP_') ?
- { ...memo, [`process.env.${name}`]: JSON.stringify(value) } :
- memo,
- {},
- ),
- 'process.cwd': 'dummyProcessCwd',
- global: 'window',
- },
- external: [
- ...Object.keys(packageJson.devDependencies || {}),
- ],
- loader: {
- '.md': 'text',
- '.gif': 'dataurl',
- }
-};
-
-const build = async (overrides = {}) => {
- try {
- await esbuild.build({ ...BUILD_CONFIG, ...overrides });
- writeFileSync(path.join(BUILD_PATH, '__init__.py'),'')
- console.log('done building');
- } catch (e) {
- console.error(e);
- process.exit(1);
- }
-};
-
-module.exports = { build };
diff --git a/aws-proxy/aws_proxy/frontend/.esbuild/esbuild.shims.js b/aws-proxy/aws_proxy/frontend/.esbuild/esbuild.shims.js
deleted file mode 100644
index c44743a9..00000000
--- a/aws-proxy/aws_proxy/frontend/.esbuild/esbuild.shims.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import * as React from 'react';
-
-export { React };
-
-export function dummyProcessCwd() {
- return '';
-};
diff --git a/aws-proxy/aws_proxy/frontend/.esbuild/index.js b/aws-proxy/aws_proxy/frontend/.esbuild/index.js
deleted file mode 100644
index 66a0005a..00000000
--- a/aws-proxy/aws_proxy/frontend/.esbuild/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const { build, serve } = require('./esbuild.config');
-
-(async () => {
- if (process.argv.includes('--serve')) {
- await serve();
- } else if (process.argv.includes('--watch')) {
- await build({ watch: true });
- } else {
- await build();
- }
-})();
diff --git a/aws-proxy/aws_proxy/frontend/.esbuild/plugins/html/index.js b/aws-proxy/aws_proxy/frontend/.esbuild/plugins/html/index.js
deleted file mode 100644
index 2161d8e0..00000000
--- a/aws-proxy/aws_proxy/frontend/.esbuild/plugins/html/index.js
+++ /dev/null
@@ -1,84 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-const crypto = require('crypto');
-
-/**
- * @param {object} config
- * @param {string} config.filename - HTML file to process and override
- * @param {boolean} config.env - Whether to replace env vars or not (default - `false`)
- * @param {string} config.envPrefix - Limit env vars to pick (default - `REACT_APP_`)
- */
-const HtmlPlugin = (config) => ({
- name: 'html',
- setup(build) {
- build.onResolve({ filter: /\.html$/ }, args => ({
- path: path.resolve(args.resolveDir, args.path),
- namespace: 'html',
- }));
- build.onLoad({ filter: /.html/, namespace: 'html' }, (args) => {
- let htmlContent = fs.readFileSync(args.path).toString('utf-8');
-
- // replace env vars
- if (config.env) {
- const envPrefix = config.envPrefix || 'REACT_APP_';
- const envVars = Object.entries(process.env || {}).filter(([name]) => name.startsWith(envPrefix));
- htmlContent = envVars.reduce(
- (memo, [name, value]) => memo.replace(new RegExp(`%${name}%`, 'igm'), value),
- htmlContent,
- );
- }
-
- return {
- contents: htmlContent,
- loader: 'file'
- };
- });
-
- build.onEnd((result) => {
- const outFiles = Object.keys((result.metafile || {}).outputs);
- const jsFiles = outFiles.filter((p) => p.endsWith('.js'));
- const cssFiles = outFiles.filter((p) => p.endsWith('.css'));
- const htmlFiles = outFiles.filter((p) => p.endsWith('.html'));
-
- const headerAppends = cssFiles.reduce(
- (memo, p) => {
- const filename = p.split(path.sep).slice(-1)[0];
- return [...memo, ``];
- },
- [],
- );
-
- const bodyAppends = jsFiles.reduce(
- (memo, p) => {
- const filename = p.split(path.sep).slice(-1)[0];
- return [...memo, ``];
- },
- [],
- );
-
- for (const htmlFile of htmlFiles) {
- let htmlContent = fs.readFileSync(htmlFile).toString('utf-8');
-
- // replace env vars
- if (config.env) {
- const envPrefix = config.envPrefix || 'REACT_APP_';
- const envVars = Object.entries(process.env).filter(([name]) => name.startsWith(envPrefix));
-
- htmlContent = envVars.reduce(
- (memo, [name, value]) => memo.replace(new RegExp(`%${name}%`, 'igm'), value),
- htmlContent,
- );
- }
-
- // inject references to js and css files
- htmlContent = htmlContent
- .replace('', [...headerAppends, ''].join("\n"))
- .replace('