diff --git a/CHANGELOG.md b/CHANGELOG.md index d344edcb3..2009063e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) @@ -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 diff --git a/dev-environments/keycloak-env/Makefile b/dev-environments/keycloak-env/Makefile new file mode 100644 index 000000000..87695cedb --- /dev/null +++ b/dev-environments/keycloak-env/Makefile @@ -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 diff --git a/dev-environments/keycloak-env/README.md b/dev-environments/keycloak-env/README.md new file mode 100644 index 000000000..79ed08b13 --- /dev/null +++ b/dev-environments/keycloak-env/README.md @@ -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 +``` diff --git a/dev-environments/keycloak-env/apicast-config.json b/dev-environments/keycloak-env/apicast-config.json new file mode 100644 index 000000000..071296cda --- /dev/null +++ b/dev-environments/keycloak-env/apicast-config.json @@ -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": {} + } + ] + } + } + ] +} diff --git a/dev-environments/keycloak-env/docker-compose.yml b/dev-environments/keycloak-env/docker-compose.yml new file mode 100644 index 000000000..3fdbd0114 --- /dev/null +++ b/dev-environments/keycloak-env/docker-compose.yml @@ -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 diff --git a/gateway/conf.d/backend.conf b/gateway/conf.d/backend.conf index edbba1b1f..8c9da83fa 100644 --- a/gateway/conf.d/backend.conf +++ b/gateway/conf.d/backend.conf @@ -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!"; +}