-
Notifications
You must be signed in to change notification settings - Fork 14
add initial version of keycloak extension #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
HarshCasper
wants to merge
2
commits into
main
Choose a base branch
from
keycloak
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,347
−0
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| name: LocalStack Keycloak Extension Tests | ||
|
|
||
| on: | ||
| push: | ||
| paths: | ||
| - keycloak/** | ||
| branches: | ||
| - main | ||
| pull_request: | ||
| paths: | ||
| - .github/workflows/keycloak.yml | ||
| - keycloak/** | ||
| workflow_dispatch: | ||
|
|
||
| env: | ||
| LOCALSTACK_DISABLE_EVENTS: "1" | ||
| LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }} | ||
|
|
||
| jobs: | ||
| integration-tests: | ||
| name: Run Integration Tests | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup LocalStack and extension | ||
| run: | | ||
| cd keycloak | ||
|
|
||
| docker pull localstack/localstack-pro & | ||
| docker pull quay.io/keycloak/keycloak:26.0 & | ||
| pip install localstack | ||
|
|
||
| make install | ||
| make dist | ||
| localstack extensions -v install file://$(ls ./dist/localstack_extension_keycloak-*.tar.gz) | ||
|
|
||
| DEBUG=1 localstack start -d | ||
| localstack wait | ||
|
|
||
| - name: Run integration tests | ||
| run: | | ||
| cd keycloak | ||
| make test | ||
|
|
||
| - name: Print logs | ||
| if: always() | ||
| run: | | ||
| localstack logs | ||
| localstack stop |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| .venv/ | ||
| *.egg-info/ | ||
| dist/ | ||
| build/ | ||
| __pycache__/ | ||
| *.pyc | ||
| .eggs/ | ||
| .pytest_cache/ | ||
| .ruff_cache/ | ||
| cdk.out/ | ||
| plan.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| VENV_BIN = python3 -m venv | ||
| VENV_DIR ?= .venv | ||
| VENV_ACTIVATE = $(VENV_DIR)/bin/activate | ||
| VENV_RUN = . $(VENV_ACTIVATE) | ||
| TEST_PATH ?= tests | ||
|
|
||
| usage: ## Shows usage for this Makefile | ||
| @cat Makefile | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' | ||
|
|
||
| venv: $(VENV_ACTIVATE) | ||
|
|
||
| $(VENV_ACTIVATE): pyproject.toml | ||
| test -d .venv || $(VENV_BIN) .venv | ||
| $(VENV_RUN); pip install --upgrade pip setuptools plux | ||
| $(VENV_RUN); pip install -e .[dev] | ||
| touch $(VENV_DIR)/bin/activate | ||
|
|
||
| clean: | ||
| rm -rf .venv/ | ||
| rm -rf build/ | ||
| rm -rf .eggs/ | ||
| rm -rf *.egg-info/ | ||
|
|
||
| install: venv ## Install dependencies | ||
| $(VENV_RUN); python -m plux entrypoints | ||
|
|
||
| dist: venv ## Create distribution | ||
| $(VENV_RUN); python -m build | ||
|
|
||
| publish: clean-dist venv dist ## Publish extension to pypi | ||
| $(VENV_RUN); pip install --upgrade twine; twine upload dist/* | ||
|
|
||
| entrypoints: venv ## Generate plugin entrypoints for Python package | ||
| $(VENV_RUN); python -m plux entrypoints | ||
|
|
||
| format: ## Run ruff to format the codebase | ||
| $(VENV_RUN); python -m ruff format .; make lint | ||
|
|
||
| lint: ## Run ruff to lint the codebase | ||
| $(VENV_RUN); python -m ruff check --output-format=full . | ||
|
|
||
| test: ## Run integration tests (requires LocalStack running with the Extension installed) | ||
| $(VENV_RUN); pytest $(PYTEST_ARGS) $(TEST_PATH) | ||
|
|
||
| deploy: ## Deploy the sample app CDK stack | ||
| cd sample-app/cdk && pip install -r requirements.txt && cdklocal bootstrap && cdklocal deploy --all --require-approval never | ||
|
|
||
| test-sample: ## Run sample app API tests (requires LocalStack running with deployed stack) | ||
| @echo "Getting token from Keycloak..." | ||
| @TOKEN=$$(curl -s -X POST \ | ||
| "http://keycloak.localhost.localstack.cloud:4566/realms/localstack/protocol/openid-connect/token" \ | ||
| -d "grant_type=client_credentials" \ | ||
| -d "client_id=localstack-client" \ | ||
| -d "client_secret=localstack-client-secret" | jq -r '.access_token') && \ | ||
| API_ID=$$(awslocal apigateway get-rest-apis --query 'items[0].id' --output text) && \ | ||
| API_URL="http://localhost:4566/_aws/execute-api/$${API_ID}/prod" && \ | ||
| echo "API URL: $${API_URL}" && \ | ||
| echo "\n=== List users ===" && \ | ||
| curl -s -H "Authorization: Bearer $${TOKEN}" "$${API_URL}/users" | jq . && \ | ||
| echo "\n=== Create user ===" && \ | ||
| curl -s -X POST "$${API_URL}/users" \ | ||
| -H "Authorization: Bearer $${TOKEN}" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{"username": "testuser", "email": "test@example.com", "name": "Test User"}' | jq . && \ | ||
| echo "\n=== Get user ===" && \ | ||
| curl -s -H "Authorization: Bearer $${TOKEN}" "$${API_URL}/users/testuser" | jq . && \ | ||
| echo "\n=== Delete user ===" && \ | ||
| curl -s -X DELETE -H "Authorization: Bearer $${TOKEN}" "$${API_URL}/users/testuser" | jq . && \ | ||
| echo "\n=== Sample app tests completed ===" | ||
|
|
||
| clean-dist: clean | ||
| rm -rf dist/ | ||
|
|
||
| .PHONY: clean clean-dist dist install publish usage venv format test lint entrypoints deploy test-sample |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| # Keycloak on LocalStack | ||
|
|
||
| This repo contains a [LocalStack Extension](https://github.com/localstack/localstack-extensions) that runs [Keycloak](https://www.keycloak.org/) alongside LocalStack for identity and access management with local AWS applications. | ||
|
|
||
| This Extension: | ||
|
|
||
| - Spins up a Keycloak instance on LocalStack startup. | ||
| - Auto-registers Keycloak as an OIDC identity provider in LocalStack IAM. | ||
| - Ships with a default realm (`localstack`) ready for OAuth2/OIDC flows. | ||
| - Exchanges Keycloak JWTs for temporary AWS credentials via `AssumeRoleWithWebIdentity`. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Docker | ||
| - LocalStack Pro | ||
| - `localstack` CLI | ||
| - `make` | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| localstack extensions install "git+https://github.com/localstack/localstack-extensions.git#egg=localstack-keycloak&subdirectory=keycloak" | ||
| ``` | ||
|
|
||
| ## Install local development version | ||
|
|
||
| To install the extension into LocalStack in developer mode, you will need Python 3.11, and create a virtual environment in the extensions project. | ||
|
|
||
| ```bash | ||
| make install | ||
| ``` | ||
|
|
||
| Then, to enable the extension for LocalStack, run | ||
|
|
||
| ```bash | ||
| localstack extensions dev enable . | ||
| ``` | ||
|
|
||
| You can then start LocalStack with `EXTENSION_DEV_MODE=1` to load all enabled extensions: | ||
|
|
||
| ```bash | ||
| EXTENSION_DEV_MODE=1 localstack start | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| Start LocalStack: | ||
|
|
||
| ```bash | ||
| localstack start | ||
| ``` | ||
|
|
||
| Keycloak will be available at: | ||
|
|
||
| | Endpoint | URL | | ||
| |----------|-----| | ||
| | Admin Console | http://localhost:8080/admin | | ||
| | Token Endpoint | http://keycloak.localhost.localstack.cloud:4566/realms/localstack/protocol/openid-connect/token | | ||
| | JWKS URL | http://keycloak.localhost.localstack.cloud:4566/realms/localstack/protocol/openid-connect/certs | | ||
|
|
||
| Keycloak ports are exposed directly on the host for easy access: | ||
|
|
||
| - **Admin Console & HTTP (8080)**: `http://localhost:8080` - Use this for the admin UI and direct API access | ||
| - **Management (9000)**: `http://localhost:9000` - Health and metrics endpoints (Keycloak 26+) | ||
|
|
||
| The gateway URL (`keycloak.localhost.localstack.cloud:4566`) is available for token endpoints and OIDC flows. | ||
|
|
||
| - **Default Admin Credentials**: `admin` / `admin` | ||
| - **Health check**: `curl http://localhost:9000/health/ready` | ||
|
|
||
| ### Get an Access Token | ||
|
|
||
| ```bash | ||
| TOKEN=$(curl -s -X POST \ | ||
| "http://keycloak.localhost.localstack.cloud:4566/realms/localstack/protocol/openid-connect/token" \ | ||
| -d "grant_type=client_credentials" \ | ||
| -d "client_id=localstack-client" \ | ||
| -d "client_secret=localstack-client-secret" | jq -r '.access_token') | ||
| ``` | ||
|
|
||
| ### Exchange Token for AWS Credentials | ||
|
|
||
| ```bash | ||
| # Create IAM role that trusts Keycloak | ||
| cat > trust-policy.json << 'EOF' | ||
| { | ||
| "Version": "2012-10-17", | ||
| "Statement": [{ | ||
| "Effect": "Allow", | ||
| "Principal": { | ||
| "Federated": "arn:aws:iam::000000000000:oidc-provider/keycloak.localhost.localstack.cloud:4566/realms/localstack" | ||
| }, | ||
| "Action": "sts:AssumeRoleWithWebIdentity" | ||
| }] | ||
| } | ||
| EOF | ||
|
|
||
| awslocal iam create-role \ | ||
| --role-name KeycloakAuthRole \ | ||
| --assume-role-policy-document file://trust-policy.json | ||
|
|
||
| # Exchange Keycloak token for AWS credentials | ||
| awslocal sts assume-role-with-web-identity \ | ||
| --role-arn arn:aws:iam::000000000000:role/KeycloakAuthRole \ | ||
| --role-session-name test-session \ | ||
| --web-identity-token "$TOKEN" | ||
| ``` | ||
|
|
||
| ## Configuration | ||
|
|
||
| | Environment Variable | Default | Description | | ||
| |---------------------|---------|-------------| | ||
| | `KEYCLOAK_REALM` | `localstack` | Name of the default realm | | ||
| | `KEYCLOAK_VERSION` | `26.0` | Keycloak Docker image version | | ||
| | `KEYCLOAK_REALM_FILE` | - | Path to custom realm JSON file | | ||
| | `KEYCLOAK_DEFAULT_USER` | - | Username for auto-created test user | | ||
| | `KEYCLOAK_DEFAULT_PASSWORD` | - | Password for auto-created test user | | ||
| | `KEYCLOAK_OIDC_AUDIENCE` | `localstack-client` | Audience claim for OIDC provider | | ||
| | `KEYCLOAK_FLAGS` | - | Additional flags for Keycloak start command | | ||
|
|
||
| > **Note**: When using `localstack start`, prefix environment variables with `LOCALSTACK_` (e.g., `LOCALSTACK_KEYCLOAK_REALM`). | ||
|
|
||
| ### Custom Realm Configuration | ||
|
|
||
| Use your own realm JSON file with pre-configured users, roles, and clients. | ||
|
|
||
| ```bash | ||
| # The path must be an absolute HOST path for Docker mount | ||
| # Use LOCALSTACK_ prefix when running via CLI | ||
| LOCALSTACK_KEYCLOAK_REALM_FILE=/path/to/my-realm.json localstack start | ||
| ``` | ||
|
|
||
| See [`quickstart/sample-realm.json`](quickstart/sample-realm.json) for a realm template and [`quickstart/README.md`](quickstart/README.md) for a step-by-step guide. | ||
|
|
||
| ### Create Test Users | ||
|
|
||
| ```bash | ||
| # Auto-create a test user on startup (use LOCALSTACK_ prefix with CLI) | ||
| LOCALSTACK_KEYCLOAK_DEFAULT_USER=testuser LOCALSTACK_KEYCLOAK_DEFAULT_PASSWORD=password123 localstack start | ||
| ``` | ||
|
|
||
| ## Default Client | ||
|
|
||
| The extension creates a default client `localstack-client` with: | ||
|
|
||
| - **Client Secret**: `localstack-client-secret` | ||
| - **Flows**: Authorization Code, Client Credentials, Direct Access Grants | ||
| - **Service Account Roles**: `admin`, `user` | ||
|
|
||
| The service account for `localstack-client` is automatically assigned the `admin` realm role, enabling full access when using client credentials flow. | ||
|
|
||
| ## Sample Application | ||
|
|
||
| See the `sample-app/` directory for a complete example demonstrating: | ||
|
|
||
| - API Gateway with Lambda Authorizer | ||
| - JWT validation with Keycloak | ||
| - Role-based access control | ||
| - DynamoDB user management | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### Keycloak takes a long time to start | ||
|
|
||
| Keycloak typically takes 30-60 seconds to fully start. The extension waits for the health check to pass before marking LocalStack as ready. | ||
|
|
||
| ### Health check endpoint returns 404 | ||
|
|
||
| In Keycloak 26+, the health endpoint is on port 9000: | ||
|
|
||
| ```bash | ||
| curl http://localhost:9000/health/ready | ||
| ``` | ||
|
|
||
| ### View Keycloak logs | ||
|
|
||
| ```bash | ||
| docker logs ls-ext-keycloak | ||
| ``` | ||
|
|
||
| ## Development | ||
|
|
||
| ```bash | ||
| # Install dependencies | ||
| make install | ||
|
|
||
| # Run tests (requires LocalStack with extension running) | ||
| make test | ||
|
|
||
| # Format code | ||
| make format | ||
|
|
||
| # Lint | ||
| make lint | ||
| ``` | ||
|
|
||
| ## License | ||
|
|
||
| Apache License 2.0 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| name = "localstack_keycloak" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: The
egghere needs to match the extension name. We could either rename the extension tolocalstack-keycloakinpyproject.toml, or update the command here to uselocalstack-keycloak. (We don't currently follow a consistent pattern, but I would probably prefer the former, aslocalstack-keycloakseems a bit shorter, and we've also used this pattern for the wiremock extension recently.)