Skip to content

Dev environment: keycloak integration #1439

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

Merged
merged 2 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Fixed issue where the proxy policy could not handle requests with "Transfer-Encoding: chunked" header [PR #1403](https://github.com/3scale/APIcast/pull/1403) [THREESCALE-9542](https://issues.redhat.com/browse/THREESCALE-9542)

- Fixed custom-config.t conversion to APIcast::Blackbox [PR #1425](https://github.com/3scale/APIcast/pull/1425)

- Fixed resty-ctx.t conversion to APIcast::Blackbox [PR #1424](https://github.com/3scale/APIcast/pull/1424)

- Fixed backend-cache-handler.t conversion to APIcast::Blackbox [PR #1431](https://github.com/3scale/APIcast/pull/1431)

- Fixed apicast-mapping-rules.t conversion to APIcast::Blackbox [PR #1430](https://github.com/3scale/APIcast/pull/1430)

- gateway/src/apicast/http_proxy.lua: remove unused code [PR #1435](https://github.com/3scale/APIcast/pull/1435)

- Fixed token instrospection field removed [PR #1438](https://github.com/3scale/APIcast/pull/1438) [THREESCALE-10591](https://issues.redhat.com/browse/THREESCALE-10591)

### Added

- Detect number of CPU shares when running on Cgroups V2 [PR #1410](https://github.com/3scale/apicast/pull/1410) [THREESCALE-10167](https://issues.redhat.com/browse/THREESCALE-10167)
Expand All @@ -23,6 +35,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Added request unbuffered policy [PR #1408](https://github.com/3scale/APIcast/pull/1408) [THREESCALE-9542](https://issues.redhat.com/browse/THREESCALE-9542)

- Dev environment: keycloak [PR #1439](https://github.com/3scale/APIcast/pull/1439)

## [3.14.0] 2023-07-25

### Fixed
Expand Down
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!";
}