first commit

This commit is contained in:
sttlab
2026-05-31 12:18:37 +00:00
parent 4f1c251ff8
commit e525b053ec
18 changed files with 974 additions and 104 deletions
+198
View File
@@ -0,0 +1,198 @@
apiVersion: gravitee.io/v1alpha1
kind: ApiV4Definition
metadata:
name: task-management-api
namespace: gravitee-apim
spec:
name: Task Management API
description: Task Management microservice — CRUD tasks with PostgreSQL backend
version: 1.0.0
type: PROXY
state: STARTED
visibility: PUBLIC
lifecycleState: PUBLISHED
contextRef:
name: gravitee-management-context
namespace: gravitee-apim
listeners:
- type: HTTP
hosts:
- gateway.gravitee.sttlab.pc
paths:
- path: /tasks-management
entrypoints:
- type: http-proxy
endpointGroups:
- name: default
type: http-proxy
endpoints:
- name: task-management
type: http-proxy
secondary: false
inheritConfiguration: false
configuration:
target: "[[ configmap `task-management-config/backend-url` ]]"
sharedConfigurationOverride:
ssl:
trustAll: false
hostnameVerifier: true
trustStore:
type: PEM
content: "[[ secret `task-management-tls/tls.crt` ]]"
sharedConfiguration: {}
plans:
API_KEY_PLAN:
name: API Key Plan
description: Access secured by API Key
security:
type: API_KEY
status: PUBLISHED
flows: []
JWT_PLAN_FREE:
name: "Free"
description: "JWT — 100 requests per day"
security:
type: JWT
configuration:
signature: RSA_RS256
publicKeyResolver: JWKS_URL
resolverParameter: "[[ configmap `task-management-config/keycloak-jwks-url` ]]"
useSystemProxy: false
extractClaims: true
userClaim: sub
clientIdClaim: azp
checkRequiredClaims: true
requiredClaims:
- name: iss
value: "[[ configmap `task-management-config/keycloak-issuer` ]]"
status: PUBLISHED
flows:
- name: quota
enabled: true
request:
- name: Quota
policy: quota
enabled: true
configuration:
addHeaders: true
quota:
limit: 100
periodTime: 1
periodTimeUnit: DAYS
response: []
JWT_PLAN_STANDARD:
name: "Standard"
description: "JWT — 10 000 requests per day"
security:
type: JWT
configuration:
signature: RSA_RS256
publicKeyResolver: JWKS_URL
resolverParameter: "[[ configmap `task-management-config/keycloak-jwks-url` ]]"
useSystemProxy: false
extractClaims: true
userClaim: sub
clientIdClaim: azp
checkRequiredClaims: true
requiredClaims:
- name: iss
value: "[[ configmap `task-management-config/keycloak-issuer` ]]"
status: PUBLISHED
flows:
- name: quota
enabled: true
request:
- name: Quota
policy: quota
enabled: true
configuration:
addHeaders: true
quota:
limit: 10000
periodTime: 1
periodTimeUnit: DAYS
response: []
JWT_PLAN_PREMIUM:
name: "Premium"
description: "JWT — unlimited"
security:
type: JWT
configuration:
signature: RSA_RS256
publicKeyResolver: JWKS_URL
resolverParameter: "[[ configmap `task-management-config/keycloak-jwks-url` ]]"
useSystemProxy: false
extractClaims: true
userClaim: sub
clientIdClaim: azp
checkRequiredClaims: true
requiredClaims:
- name: iss
value: "[[ configmap `task-management-config/keycloak-issuer` ]]"
status: PUBLISHED
flows: []
analytics:
enabled: true
logging:
mode:
entrypoint: true
endpoint: true
phase:
request: true
response: true
content:
headers: true
payload: true
messageHeaders: false
messagePayload: false
messageMetadata: false
flows:
- name: traffic-management
enabled: true
request:
- name: Rate Limit
policy: rate-limit
enabled: true
configuration:
addHeaders: true
rate:
limit: 100
periodTime: 1
periodTimeUnit: MINUTES
response: []
- name: request-transformation
enabled: true
request:
- name: inject-application-name
policy: transform-headers
enabled: true
configuration:
addHeaders:
- name: X-Application-Id
value: "{#context.attributes['application']}"
response: []
- name: response-transformation
enabled: true
request: []
response:
- name: headers-cleaning
policy: transform-headers
enabled: true
configuration:
removeHeaders:
- server
pages:
specifications:
name: specifications
type: FOLDER
published: true
swagger:
name: OpenAPI Specification
type: SWAGGER
parent: specifications
source:
type: http-fetcher
configuration:
url: "[[ configmap `task-management-config/openapi-url` ]]"
useSystemProxy: false
published: true
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: task-management-config
namespace: gravitee-apim
data:
keycloak-issuer: "https://keycloak.sttlab.eu/realms/sttlab"
keycloak-jwks-url: "https://keycloak.sttlab.eu/realms/sttlab/protocol/openid-connect/certs"
backend-url: "https://task-management.tasks.svc.cluster.local:8443"
openapi-url: "http://task-management.tasks.svc.cluster.local:8080/openapi.json"
+156
View File
@@ -0,0 +1,156 @@
# Task Management API — Test Procedure
## Overview
The API is exposed at `https://gateway.gravitee.sttlab.pc/tasks-management`.
Two OAuth2 / OIDC flows are supported, both validated via Keycloak realm `sttlab`.
| Flow | Client | Grant type | Use case |
|---|---|---|---|
| OAuth2 client credentials | `test-backend` | `client_credentials` | Service-to-service |
| OIDC Authorization Code + PKCE | `test-app` | `authorization_code` | User-facing app |
---
## Prerequisites
- `/etc/hosts` entry: `192.168.1.18 gateway.gravitee.sttlab.pc`
- Keycloak reachable at `https://keycloak.sttlab.eu`
- Gravitee Gateway running (`gravitee-apim` namespace)
Set the following environment variables before running the commands below:
```bash
export TEST_BACKEND_SECRET=<test-backend client secret>
export TEST_USER_PASSWORD=<test-user password>
```
---
## Test 1 — OAuth2 client_credentials (test-backend)
### Step 1 — Obtain a token
```bash
TOKEN=$(curl -s -X POST https://keycloak.sttlab.eu/realms/sttlab/protocol/openid-connect/token \
-d "client_id=test-backend" \
-d "client_secret=${TEST_BACKEND_SECRET}" \
-d "grant_type=client_credentials" \
-d "scope=tasks-full" \
| jq -r '.access_token')
```
To request read-only access, replace `tasks-full` with `tasks-read`.
### Step 2 — Call the API
**List tasks (GET):**
```bash
curl -sk https://gateway.gravitee.sttlab.pc/tasks-management/tasks \
-H "Authorization: Bearer ${TOKEN}"
```
Expected: `HTTP 200` with a JSON array of tasks.
**Create a task (POST):**
```bash
curl -sk -X POST https://gateway.gravitee.sttlab.pc/tasks-management/tasks \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"description": "Test task"}'
```
Expected: `HTTP 201` with the created task.
### Step 3 — Verify rejection without token
```bash
curl -sk -o /dev/null -w "%{http_code}" \
https://gateway.gravitee.sttlab.pc/tasks-management/tasks
```
Expected: `401`
---
## Test 2 — OIDC Authorization Code + PKCE (test-app / test-user)
Headless flow — no browser required. Keycloak's login form is submitted directly via curl.
### Step 1 — Generate PKCE parameters
```bash
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=\n' | tr '+/' '-_')
CODE_CHALLENGE=$(echo -n "${CODE_VERIFIER}" | openssl dgst -sha256 -binary | base64 | tr -d '=\n' | tr '+/' '-_')
```
### Step 2 — Fetch the login form and extract the action URL
```bash
curl -sc /tmp/kc-cookies.txt \
"https://keycloak.sttlab.eu/realms/sttlab/protocol/openid-connect/auth?response_type=code&client_id=test-app&redirect_uri=http://localhost:3000/callback&code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256&scope=openid%20tasks-read" \
-o /tmp/kc-login.html
LOGIN_URL=$(grep -o 'action="[^"]*"' /tmp/kc-login.html | sed 's/action="//;s/"$//;s/&amp;/\&/g')
```
### Step 3 — Submit credentials and capture the authorization code
```bash
REDIRECT=$(curl -s -b /tmp/kc-cookies.txt -c /tmp/kc-cookies.txt \
-X POST "${LOGIN_URL}" \
-d "username=test-user&password=${TEST_USER_PASSWORD}&credentialId=" \
-D - -o /dev/null | grep -i "^location:" | tr -d '\r' | sed 's/location: //i')
CODE=$(echo "${REDIRECT}" | grep -o 'code=[^&]*' | sed 's/code=//')
```
### Step 4 — Exchange the authorization code for a token
```bash
TOKEN=$(curl -s -X POST \
https://keycloak.sttlab.eu/realms/sttlab/protocol/openid-connect/token \
-d "grant_type=authorization_code" \
-d "code=${CODE}" \
-d "redirect_uri=http://localhost:3000/callback" \
-d "client_id=test-app" \
-d "code_verifier=${CODE_VERIFIER}" \
| jq -r '.access_token')
```
### Step 5 — Inspect the token
```bash
jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "${TOKEN}"
```
Verify the following claims:
| Claim | Expected value |
|---|---|
| `iss` | `https://keycloak.sttlab.eu/realms/sttlab` |
| `azp` | `test-app` |
| `preferred_username` | `test-user` |
| `scope` | contains `tasks-read` |
### Step 6 — Call the API
```bash
curl -sk https://gateway.gravitee.sttlab.pc/tasks-management/tasks \
-H "Authorization: Bearer ${TOKEN}"
```
Expected: `HTTP 200` with a JSON array of tasks.
---
## Troubleshooting
| Symptom | Likely cause |
|---|---|
| `401 Unauthorized` | Missing or expired token — request a new one |
| `401 Unauthorized` | Application not subscribed to plan — check GKO subscription |
| `invalid_scope` error from Keycloak | Scope not assigned as optional on the client — check Keycloak client scopes |
| Token obtained but gateway returns `401` | `azp` claim not matching any subscribed application in Gravitee |