Skip to content

Commit

Permalink
keycloak dev environment
Browse files Browse the repository at this point in the history
  • Loading branch information
eguzki committed Jan 26, 2024
1 parent dd4c3c6 commit e5bbcf2
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 0 deletions.
76 changes: 76 additions & 0 deletions dev-environments/keycloak-env/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec
.DEFAULT_GOAL := gateway
MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
WORKDIR := $(patsubst %/,%,$(dir $(MKFILE_PATH)))
DOCKER ?= $(shell which docker 2> /dev/null || echo "docker")

gateway: ## run gateway configured to keycloak integration
$(DOCKER) compose -f docker-compose.yml run --service-ports gateway

keycloak-data: ## Keycloak provisioning
# Keycloak 23.0.4 REST API reference
# https://www.keycloak.org/docs-api/23.0.4/rest-api/
# Init CLI authenticated session
$(DOCKER) compose -p keycloak-env exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \
--server http://127.0.0.1:8080 --realm master --user admin --password adminpass
# realm basic
$(DOCKER) compose -p keycloak-env exec keycloak /opt/keycloak/bin/kcadm.sh create realms \
--server http://127.0.0.1:8080 \
-s realm=basic \
-s enabled=true
# Issuer client (only used because it is being configured in the oidc_issuer_endpoint property)
$(DOCKER) compose -p keycloak-env exec keycloak /opt/keycloak/bin/kcadm.sh create clients \
-r basic \
--server http://127.0.0.1:8080 \
-s clientId=oidc-issuer-for-3scale \
-s enabled=true \
-s protocol=openid-connect \
-s publicClient=false \
-s standardFlowEnabled=false \
-s directAccessGrantsEnabled=false \
-s serviceAccountsEnabled=true \
-s clientAuthenticatorType=client-secret \
-s secret=oidc-issuer-for-3scale-secret
# client my-client
$(DOCKER) compose -p keycloak-env exec keycloak /opt/keycloak/bin/kcadm.sh create clients \
-r basic \
--server http://127.0.0.1:8080 \
-s clientId=my-client \
-s enabled=true \
-s protocol=openid-connect \
-s publicClient=true \
-s directAccessGrantsEnabled=true \
-s clientAuthenticatorType=client-secret \
-s secret=my-client-secret
# user bob
$(DOCKER) compose -p keycloak-env exec keycloak /opt/keycloak/bin/kcadm.sh create users \
--server http://127.0.0.1:8080 \
-r basic \
-s enabled=true \
-s emailVerified=true \
-s username=bob
# user bob credentials
$(DOCKER) compose -p keycloak-env exec keycloak /opt/keycloak/bin/kcadm.sh set-password \
-r basic \
--admin-root http://127.0.0.1:8080/admin \
--username bob \
--new-password bobpass

token: ## User bob gets token. Requires `curl` and `jq` installed
# Do not indent these comments below. This make target is used as
# export ACCESS_TOKEN=$(make token)
# If indented, make will echo the comments
# Token is requested from the APIcast container to get a token with correct issuer
# "iss": "http://keycloak:8080/realms/basic"
# Requesting the token from localhost outside docker compose would lead to
# oidc.lua:203: verify(): [jwt] failed verification for token, reason: Claim 'iss' ('http://127.0.0.1:9090/realms/basic') returned failure
@$(DOCKER) compose -p keycloak-env exec gateway curl -H "Content-Type: application/x-www-form-urlencoded" \
-d 'grant_type=password' \
-d 'client_id=my-client' \
-d 'username=bob' \
-d 'password=bobpass' "http://keycloak:8080/realms/basic/protocol/openid-connect/token" | jq -r '.access_token'

clean:
$(DOCKER) compose down --volumes --remove-orphans
$(DOCKER) compose -f docker-compose.yml down --volumes --remove-orphans
66 changes: 66 additions & 0 deletions dev-environments/keycloak-env/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Using 3scale API Gateway with OpenID Connect

User (jwt) -> APIcast --> upstream plain HTTP 1.1 upstream

APIcast configured with plain HTTP 1.1 upstream server equipped with traffic rely agent (socat)

## Run the gateway

Running local `apicast-test` docker image

```sh
make gateway
```

Running custom apicast image

```sh
make gateway IMAGE_NAME=quay.io/3scale/apicast:latest
```

Traffic between the proxy and upstream can be inspected looking at logs from `example.com` service

```
docker compose -p keycloak-env logs -f example.com
```

## Keycloak provisioning

```sh
make keycloak-data
```

Admin web app available at `http://127.0.0.1:9090`, user: `admin`, pass: `adminpass`.

Access to the Keycloak CLI

```sh
docker compose -p keycloak-env exec keycloak /bin/bash
```
Use the CLI

```sh
/opt/keycloak/bin/kcadm.sh --help
```

## Testing

### Get JWT

As user `bob` with password `p`, get a JWT for the `my-client` client.

```sh
export ACCESS_TOKEN=$(make token)
```

### Run request

```sh
curl -v --resolve stg.example.com:8080:127.0.0.1 -H "Authorization: Bearer ${ACCESS_TOKEN}" "http://stg.example.com:8080"
```

## Clean env

```sh
make clean
```
123 changes: 123 additions & 0 deletions dev-environments/keycloak-env/apicast-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"services": [
{
"id": 2,
"backend_version": "oauth",
"account_id": 2,
"name": "API",
"description": null,
"txt_support": null,
"created_at": "2024-01-16T13:46:50Z",
"updated_at": "2024-01-19T22:13:30Z",
"logo_file_name": null,
"logo_content_type": null,
"logo_file_size": null,
"state": "incomplete",
"intentions_required": false,
"terms": null,
"buyers_manage_apps": true,
"buyers_manage_keys": true,
"custom_keys_enabled": true,
"buyer_plan_change_permission": "request",
"buyer_can_select_plan": false,
"notification_settings": null,
"default_application_plan_id": 7,
"default_service_plan_id": 5,
"tenant_id": 2,
"system_name": "api",
"mandatory_app_key": true,
"buyer_key_regenerate_enabled": true,
"referrer_filters_required": false,
"deployment_option": "self_managed",
"kubernetes_service_link": null,
"proxiable?": true,
"backend_authentication_type": "service_token",
"backend_authentication_value": "my_token",
"proxy": {
"service_id": 2,
"id": 2,
"tenant_id": 2,
"endpoint": "https://prod.example.com:443",
"deployed_at": null,
"auth_app_key": "app_key",
"auth_app_id": "app_id",
"auth_user_key": "user_key",
"credentials_location": "query",
"error_auth_failed": "Authentication failed",
"error_auth_missing": "Authentication parameters missing",
"created_at": "2024-01-16T13:46:51Z",
"updated_at": "2024-01-19T22:13:30Z",
"error_status_auth_failed": 403,
"error_headers_auth_failed": "text/plain; charset=us-ascii",
"error_status_auth_missing": 403,
"error_headers_auth_missing": "text/plain; charset=us-ascii",
"error_no_match": "No Mapping Rule matched",
"error_status_no_match": 404,
"error_headers_no_match": "text/plain; charset=us-ascii",
"secret_token": "Shared_secret_sent_from_proxy_to_API_backend_00000000",
"hostname_rewrite": "",
"oauth_login_url": null,
"sandbox_endpoint": "https://stg.example.com:443",
"api_test_path": "/",
"api_test_success": null,
"apicast_configuration_driven": true,
"oidc_issuer_endpoint": "http://oidc-issuer-for-3scale:oidc-issuer-for-3scale-secret@keycloak:8080/realms/basic",
"lock_version": 4,
"authentication_method": "oidc",
"oidc_issuer_type": "keycloak",
"error_headers_limits_exceeded": "text/plain; charset=us-ascii",
"error_status_limits_exceeded": 429,
"error_limits_exceeded": "Usage limit exceeded",
"staging_domain": "stg.example.com",
"production_domain": "prod.example.com",
"endpoint_port": 443,
"api_backend": "http://example.com/get",
"valid?": true,
"service_backend_version": "oauth",
"hosts": [
"prod.example.com",
"stg.example.com"
],
"backend": {
"endpoint": "http://127.0.0.1:8081",
"host": "backend"
},
"policy_chain": [
{
"name": "token_introspection",
"version": "builtin",
"configuration": {
"auth_type": "use_3scale_oidc_issuer_endpoint"
}
},
{
"name": "apicast",
"version": "builtin",
"configuration": {}
}
],
"jwt_claim_with_client_id": "azp",
"jwt_claim_with_client_id_type": "plain",
"proxy_rules": [
{
"id": 2,
"proxy_id": 2,
"http_method": "GET",
"pattern": "/",
"metric_id": 6,
"metric_system_name": "hits",
"delta": 1,
"tenant_id": 2,
"redirect_url": null,
"position": 1,
"last": false,
"owner_id": 2,
"owner_type": "Proxy",
"parameters": [],
"querystring_parameters": {}
}
]
}
}
]
}
47 changes: 47 additions & 0 deletions dev-environments/keycloak-env/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
version: '3.8'
services:
gateway:
image: ${IMAGE_NAME:-apicast-test}
depends_on:
- example.com
- two.upstream
- keycloak
environment:
THREESCALE_CONFIG_FILE: /tmp/config.json
THREESCALE_DEPLOYMENT_ENV: staging
APICAST_CONFIGURATION_LOADER: lazy
APICAST_WORKERS: 1
APICAST_LOG_LEVEL: debug
APICAST_CONFIGURATION_CACHE: "0"
expose:
- "8080"
- "8090"
ports:
- "8080:8080"
- "8090:8090"
volumes:
- ./apicast-config.json:/tmp/config.json
example.com:
image: alpine/socat:1.7.4.4
container_name: example.com
command: "-d -v -d TCP-LISTEN:80,reuseaddr,fork TCP:two.upstream:80"
expose:
- "80"
restart: unless-stopped
two.upstream:
image: kennethreitz/httpbin
expose:
- "80"
keycloak:
image: quay.io/keycloak/keycloak:23.0.4
container_name: keycloak
command: "start-dev"
expose:
- "8080"
ports:
- "9090:8080"
restart: unless-stopped
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: adminpass
12 changes: 12 additions & 0 deletions gateway/conf.d/backend.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,15 @@ location /transactions/authrep.xml {

echo "transactions authrep!";
}

location /transactions/oauth_authrep.xml {
access_by_lua_block {
local delay = tonumber(ngx.var.arg_delay) or 0

if delay > 0 then
ngx.sleep(delay)
end
}

echo "transactions oauth_authrep!";
}

0 comments on commit e5bbcf2

Please sign in to comment.