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
-15
View File
@@ -1,15 +0,0 @@
{
"permissions": {
"allow": [
"Bash(kubectl describe *)",
"Bash(helm show *)",
"Bash(kubectl get *)",
"Bash(kubectl logs *)",
"Bash(curl -s -o /dev/null -w \"%{http_code} %{content_type} %{size_download}\\\\n\" -H \"Accept-Encoding: gzip\" http://10.42.0.44:8080/styles.cc735d5acda5c758.css)",
"Bash(curl -s -o /dev/null -w \"%{http_code} %{content_type} %{size_download}\\\\n\" -H \"Accept-Encoding: gzip\" -H \"X-Forwarded-Proto: https\" http://10.42.0.44:8080/styles.cc735d5acda5c758.css)",
"Bash(curl -sv http://10.42.0.44:8080/styles.cc735d5acda5c758.css)",
"Bash(curl -sk -o /dev/null -w \"%{http_code} %{content_type}\\\\n\" --resolve console.gravitee.sttlab.pc:443:192.168.1.18 https://console.gravitee.sttlab.pc/styles.cc735d5acda5c758.css)",
"Bash(kill %1)"
]
}
}
+1
View File
@@ -0,0 +1 @@
.claude
+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 |
+30
View File
@@ -0,0 +1,30 @@
---
apiVersion: gravitee.io/v1alpha1
kind: Application
metadata:
name: test-app
namespace: gravitee-apim
spec:
name: test-app
description: "OIDC Authorization Code + PKCE — Keycloak realm sttlab"
contextRef:
name: gravitee-management-context
namespace: gravitee-apim
settings:
app:
type: JWT
clientId: test-app
---
apiVersion: gravitee.io/v1alpha1
kind: Subscription
metadata:
name: test-app-keycloak-jwt
namespace: gravitee-apim
spec:
api:
name: task-management-api
namespace: gravitee-apim
application:
name: test-app
namespace: gravitee-apim
plan: JWT_PLAN_PREMIUM
+30
View File
@@ -0,0 +1,30 @@
---
apiVersion: gravitee.io/v1alpha1
kind: Application
metadata:
name: test-backend
namespace: gravitee-apim
spec:
name: test-backend
description: "OAuth2 client_credentials — Keycloak realm sttlab"
contextRef:
name: gravitee-management-context
namespace: gravitee-apim
settings:
app:
type: JWT
clientId: test-backend
---
apiVersion: gravitee.io/v1alpha1
kind: Subscription
metadata:
name: test-backend-keycloak-jwt
namespace: gravitee-apim
spec:
api:
name: task-management-api
namespace: gravitee-apim
application:
name: test-backend
namespace: gravitee-apim
plan: JWT_PLAN_PREMIUM
+102 -22
View File
@@ -23,6 +23,7 @@ Internal backends:
**Stack:** **Stack:**
- **Gravitee APIM 4.x** — Management API (Jetty), Gateway (Vert.x/Netty), Console UI and Developer Portal (Angular/nginx) - **Gravitee APIM 4.x** — Management API (Jetty), Gateway (Vert.x/Netty), Console UI and Developer Portal (Angular/nginx)
- **Gravitee Kubernetes Operator (GKO)** — GitOps management of APIs via CRDs
- **MongoDB 8.x** (Bitnami) — management and rate limiting persistence - **MongoDB 8.x** (Bitnami) — management and rate limiting persistence
- **Elasticsearch 8.x** (Elastic) — analytics and reporting - **Elasticsearch 8.x** (Elastic) — analytics and reporting
- **cert-manager** — internal self-signed PKI with automatic renewal - **cert-manager** — internal self-signed PKI with automatic renewal
@@ -84,35 +85,33 @@ All secrets are created by `secrets-create.sh` before the first deployment.
| Secret | Contents | Consumed by | | Secret | Contents | Consumed by |
|--------|----------|-------------| |--------|----------|-------------|
| `mongodb-credentials` | root/gravitee MongoDB passwords | Bitnami MongoDB chart | | `mongodb-credentials` | root/gravitee MongoDB passwords | Bitnami MongoDB chart |
| `gravitee-mongodb-uri` | `GRAVITEE_MANAGEMENT_MONGODB_URI`, `GRAVITEE_RATELIMIT_MONGODB_URI` | api, gateway (env) | | `gravitee-mongodb-uri` | `GRAVITEE_MANAGEMENT_MONGODB_URI`, `GRAVITEE_RATELIMIT_MONGODB_URI` | api, gateway (Secret Manager) |
| `gravitee-jwt` | `GRAVITEE_JWT_SECRET` | api (env) | | `gravitee-jwt` | `GRAVITEE_JWT_SECRET` | api (Secret Manager) |
| `gravitee-admin` | `admin-password-plain`, `admin-password-bcrypt` | api (env) | | `gravitee-admin` | `admin-password-plain`, `admin-password-bcrypt` | api (Secret Manager) |
| `gravitee-es-master-credentials` | ES `username`, `password` | api, gateway (env) | | `gravitee-es-master-credentials` | ES `username`, `password` | api, gateway (Secret Manager) |
| `gravitee-jks-password` | keystore password | api, gateway (env) + cert-manager | | `gravitee-encryption` | `api-properties-encryption-secret` | api, gateway (Secret Manager) |
| `gravitee-jks-password` | keystore password | api, gateway (`JAVA_OPTS` env + Secret Manager for `ssl.keystore.password`) + cert-manager |
| `gravitee-ca-trust` | `ca.crt` only — no tls.crt/key | nginx ingress `proxy-ssl-secret` | | `gravitee-ca-trust` | `ca.crt` only — no tls.crt/key | nginx ingress `proxy-ssl-secret` |
> `gravitee-ca-trust` is created by `secrets-create.sh` after the certificates are ready (it reads `ca.crt` from `gravitee-ca-tls`). It must contain **only** `ca.crt` — if `tls.crt`/`tls.key` were present, nginx would present them as a client certificate, triggering unintended mTLS toward backends. > `gravitee-ca-trust` is created by `secrets-create.sh` after the certificates are ready (it reads `ca.crt` from `gravitee-ca-tls`). It must contain **only** `ca.crt` — if `tls.crt`/`tls.key` were present, nginx would present them as a client certificate, triggering unintended mTLS toward backends.
> `gravitee-ca-trust` is a dedicated secret containing only `ca.crt`. The `proxy-ssl-secret` nginx annotation presents `tls.crt`/`tls.key` as a client certificate if they exist, which would unintentionally trigger mTLS toward backends.
### Credential injection ### Credential injection
All credentials are injected via `env[].valueFrom.secretKeyRef` (no `envFrom`). The rule is: Credentials are resolved at runtime by the **Gravitee Secret Manager** (Kubernetes provider, enabled via `secrets.kubernetes`). Helm values reference secrets using the `secret://kubernetes/<secret-name>:<key>` syntax, which is rendered verbatim into `gravitee.yml` and resolved by Gravitee on startup.
- **Non-sensitive config** (endpoints, flags) → `value:` directly in `env`
- **Secrets** (passwords, tokens, URIs with credentials) → `valueFrom.secretKeyRef`
Example from `apim-values.yml`:
```yaml ```yaml
env: # In apim-values.yml — resolved by Gravitee Secret Manager at runtime
- name: GRAVITEE_MANAGEMENT_MONGODB_URI # credentials + TLS embedded in URI jwtSecret: "secret://kubernetes/gravitee-jwt:GRAVITEE_JWT_SECRET"
valueFrom: mongo:
secretKeyRef: uri: "secret://kubernetes/gravitee-mongodb-uri:GRAVITEE_MANAGEMENT_MONGODB_URI"
name: gravitee-mongodb-uri es:
key: GRAVITEE_MANAGEMENT_MONGODB_URI security:
- name: GRAVITEE_ANALYTICS_ELASTICSEARCH_ENDPOINTS_0 # non-sensitive username: "secret://kubernetes/gravitee-es-master-credentials:username"
value: "https://gravitee-es-master.gravitee-apim.svc.cluster.local:9200" password: "secret://kubernetes/gravitee-es-master-credentials:password"
``` ```
The only secret still injected as a Kubernetes env var is `gravitee-jks-password` (`JKS_PASSWORD`), because it is needed in `JAVA_OPTS` before the JVM starts — before the Gravitee Secret Manager is available.
--- ---
## Internal TLS ## Internal TLS
@@ -131,9 +130,7 @@ MongoDB runs with `--tlsAllowConnectionsWithoutCertificates` (no client mTLS) bu
### Elasticsearch ### Elasticsearch
HTTPS with basic auth. Server certificate signed by the Gravitee CA, validated by the same JVM truststore. Username/password injected via Gravitee property-path env vars: HTTPS with basic auth. Server certificate signed by the Gravitee CA, validated by the same JVM truststore. The HTTPS endpoint and credentials are configured in `apim-values.yml` via `es.endpoints` / `es.security.*` and resolved at runtime by the Gravitee Secret Manager.
- `GRAVITEE_ANALYTICS_ELASTICSEARCH_SECURITY_USERNAME/PASSWORD` → Management API
- `GRAVITEE_REPORTERS_ELASTICSEARCH_SECURITY_USERNAME/PASSWORD` → Gateway
### nginx → backends (proxy TLS) ### nginx → backends (proxy TLS)
@@ -208,6 +205,18 @@ helm upgrade --install elasticsearch elastic/elasticsearch -n gravitee-apim -f e
# 6. Deploy Gravitee # 6. Deploy Gravitee
helm upgrade --install graviteeio-apim graviteeio/apim -n gravitee-apim -f apim-values.yml helm upgrade --install graviteeio-apim graviteeio/apim -n gravitee-apim -f apim-values.yml
# 7. Install GKO (after APIM is ready)
# Create GKO credentials secret first (see GKO section below)
helm upgrade --install gko graviteeio/gko \
--namespace gravitee-apim \
--set manager.httpClient.trustStore.path=/etc/ssl/certs/gravitee-ca.crt \
--set-json 'manager.volumes=[{"name":"gravitee-ca","secret":{"secretName":"gravitee-ca-tls","items":[{"key":"ca.crt","path":"gravitee-ca.crt"}]}}]' \
--set-json 'manager.volumeMounts=[{"name":"gravitee-ca","mountPath":"/etc/ssl/certs/gravitee-ca.crt","subPath":"gravitee-ca.crt","readOnly":true}]'
# 8. Apply GKO resources
kubectl apply -f gko-management-context.yaml
kubectl apply -f gko-api.yaml
``` ```
### Rebuild workloads only (secrets and certs preserved) ### Rebuild workloads only (secrets and certs preserved)
@@ -250,6 +259,77 @@ kubectl get secret gravitee-admin -n gravitee-apim \
--- ---
## Gravitee Kubernetes Operator (GKO)
GKO enables GitOps-driven API management: APIs, plans and subscriptions are declared as Kubernetes CRDs and automatically synced to the Management API.
### Installation
```bash
helm upgrade --install gko graviteeio/gko \
--namespace gravitee-apim \
--set manager.httpClient.trustStore.path=/etc/ssl/certs/gravitee-ca.crt \
--set-json 'manager.volumes=[{"name":"gravitee-ca","secret":{"secretName":"gravitee-ca-tls","items":[{"key":"ca.crt","path":"gravitee-ca.crt"}]}}]' \
--set-json 'manager.volumeMounts=[{"name":"gravitee-ca","mountPath":"/etc/ssl/certs/gravitee-ca.crt","subPath":"gravitee-ca.crt","readOnly":true}]'
```
The Gravitee CA is mounted into the GKO pod so its HTTP client trusts the Management API's certificate (port 83, HTTPS). Without this, GKO would fail to connect when validating or reconciling CRDs.
### GKO secrets
| Secret | Contents | Purpose |
|--------|----------|---------|
| `gko-management-credentials` | `username`, `password` | GKO → Management API auth via `ManagementContext.spec.auth.secretRef` |
Create after deploying APIM:
```bash
ADMIN_PLAIN=$(kubectl get secret gravitee-admin -n gravitee-apim \
-o jsonpath='{.data.admin-password-plain}' | base64 -d)
kubectl create secret generic gko-management-credentials -n gravitee-apim \
--from-literal=username=admin \
--from-literal=password="${ADMIN_PLAIN}"
```
### CRDs
| CRD | Description |
|-----|-------------|
| `ManagementContext` | Connection to a Gravitee Management API instance |
| `ApiV4Definition` | Gravitee v4 API (proxy, message, native) |
| `ApiDefinition` | Gravitee v2 API (legacy) |
| `Application` | Consumer application |
| `ApiResource` | Shared resource (cache, OAuth2, etc.) |
| `SharedPolicyGroup` | Reusable policy group |
| `Subscription` | API subscription |
### ManagementContext
`gko-management-context.yaml` — connects GKO to the local Management API:
```yaml
spec:
baseUrl: https://graviteeio-apim-api.gravitee-apim.svc.cluster.local:83
environmentId: DEFAULT
organizationId: DEFAULT
auth:
secretRef:
name: gko-management-credentials
namespace: gravitee-apim
```
> Port 83 is the Kubernetes service port (maps to container port 8083).
### Deploying an API
```bash
kubectl apply -f gko-management-context.yaml
kubectl apply -f gko-api.yaml
kubectl get apiv4definition -n gravitee-apim
```
The `processingStatus: Completed` and `state: STARTED` in the status confirm the API is live on the gateway.
---
## Security notes ## Security notes
- The Gravitee CA is self-signed. Replace with a Vault PKI Issuer in production (see comment in `certificates.yml`). - The Gravitee CA is self-signed. Replace with a Vault PKI Issuer in production (see comment in `certificates.yml`).
@@ -7,6 +7,15 @@
adminAccountEnable: true adminAccountEnable: true
adminPasswordBcrypt: "${GRAVITEE_ADMIN_PASSWORD_BCRYPT}" adminPasswordBcrypt: "${GRAVITEE_ADMIN_PASSWORD_BCRYPT}"
# ============================================================
# Kubernetes Secret Provider
# ============================================================
secrets:
kubernetes:
enabled: true
namespace: gravitee-apim
timeoutMs: 3000
# ============================================================ # ============================================================
# API Gateway (data plane) - 2 replicas # API Gateway (data plane) - 2 replicas
# ============================================================ # ============================================================
@@ -58,6 +67,11 @@ gateway:
secretKeyRef: secretKeyRef:
name: gravitee-jks-password name: gravitee-jks-password
key: password key: password
- name: GRAVITEE_API_PROPERTIES_ENCRYPTION_SECRET
valueFrom:
secretKeyRef:
name: gravitee-encryption
key: api-properties-encryption-secret
- name: JAVA_OPTS - name: JAVA_OPTS
value: "-Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStore=/run/secrets/tls/truststore.p12 -Djavax.net.ssl.trustStorePassword=$(JKS_PASSWORD)" value: "-Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStore=/run/secrets/tls/truststore.p12 -Djavax.net.ssl.trustStorePassword=$(JKS_PASSWORD)"
@@ -164,6 +178,11 @@ api:
secretKeyRef: secretKeyRef:
name: gravitee-jks-password name: gravitee-jks-password
key: password key: password
- name: GRAVITEE_API_PROPERTIES_ENCRYPTION_SECRET
valueFrom:
secretKeyRef:
name: gravitee-encryption
key: api-properties-encryption-secret
- name: JAVA_OPTS - name: JAVA_OPTS
value: "-Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStore=/run/secrets/tls/truststore.p12 -Djavax.net.ssl.trustStorePassword=$(JKS_PASSWORD)" value: "-Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStore=/run/secrets/tls/truststore.p12 -Djavax.net.ssl.trustStorePassword=$(JKS_PASSWORD)"
@@ -186,7 +205,7 @@ api:
resources: resources:
requests: requests:
cpu: 200m cpu: 200m
memory: 768Mi memory: 2Gi
limits: limits:
cpu: 1000m cpu: 1000m
memory: 2Gi memory: 2Gi
+268
View File
@@ -0,0 +1,268 @@
# Gravitee APIM OSS - prod-like single-node k3s deployment
# - Domain: gravitee.sttlab.pc
# - Ingress: nginx
# - TLS everywhere (ingress + internal component HTTPS)
# - Credentials resolved at runtime via Gravitee Secret Manager (kubernetes provider)
adminAccountEnable: true
adminPasswordBcrypt: "secret://kubernetes/gravitee-admin:admin-password-bcrypt"
jwtSecret: "secret://kubernetes/gravitee-jwt:GRAVITEE_JWT_SECRET"
# ============================================================
# MongoDB (management + ratelimit, same URI)
# ============================================================
mongo:
uri: "secret://kubernetes/gravitee-mongodb-uri:GRAVITEE_MANAGEMENT_MONGODB_URI"
# ============================================================
# Elasticsearch
# ============================================================
es:
endpoints:
- "https://gravitee-es-master.gravitee-apim.svc.cluster.local:9200"
security:
enabled: true
username: "secret://kubernetes/gravitee-es-master-credentials:username"
password: "secret://kubernetes/gravitee-es-master-credentials:password"
# ============================================================
# Kubernetes Secret Provider
# ============================================================
secrets:
kubernetes:
enabled: true
namespace: gravitee-apim
timeoutMs: 3000
# ============================================================
# API Gateway (data plane) - 2 replicas
# ============================================================
gateway:
enabled: true
replicaCount: 2
api:
properties:
encryption:
secret: "secret://kubernetes/gravitee-encryption:api-properties-encryption-secret"
extraVolumes: |
- name: gateway-internal-tls
secret:
secretName: gateway-internal-tls
items:
- key: keystore.p12
path: keystore.p12
- key: truststore.p12
path: truststore.p12
extraVolumeMounts: |
- name: gateway-internal-tls
mountPath: /run/secrets/tls
readOnly: true
env:
- name: JKS_PASSWORD
valueFrom:
secretKeyRef:
name: gravitee-jks-password
key: password
- name: JAVA_OPTS
value: "-Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStore=/run/secrets/tls/truststore.p12 -Djavax.net.ssl.trustStorePassword=$(JKS_PASSWORD)"
# Enable HTTPS on the gateway listener (port 8082)
ssl:
enabled: true
keystore:
type: pkcs12
path: /run/secrets/tls/keystore.p12
password: "secret://kubernetes/gravitee-jks-password:password"
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
service:
type: ClusterIP
externalPort: 443
internalPort: 8082
ingress:
enabled: true
ingressClassName: nginx
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/proxy-ssl-verify: "on"
nginx.ingress.kubernetes.io/proxy-ssl-secret: "gravitee-apim/gravitee-ca-trust"
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_ssl_name gateway.gravitee.sttlab.pc;
hosts:
- gateway.gravitee.sttlab.pc
path: /
pathType: Prefix
tls:
- hosts:
- gateway.gravitee.sttlab.pc
secretName: gateway-tls
autoscaling:
enabled: false
# ============================================================
# Management API (control plane) - 1 replica
# ============================================================
api:
enabled: true
replicaCount: 1
api:
properties:
encryption:
secret: "secret://kubernetes/gravitee-encryption:api-properties-encryption-secret"
extraVolumes: |
- name: api-internal-tls
secret:
secretName: api-internal-tls
items:
- key: keystore.p12
path: keystore.p12
- key: truststore.p12
path: truststore.p12
extraVolumeMounts: |
- name: api-internal-tls
mountPath: /run/secrets/tls
readOnly: true
env:
- name: JKS_PASSWORD
valueFrom:
secretKeyRef:
name: gravitee-jks-password
key: password
- name: JAVA_OPTS
value: "-Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStore=/run/secrets/tls/truststore.p12 -Djavax.net.ssl.trustStorePassword=$(JKS_PASSWORD)"
# Enable HTTPS on Management API + Portal API listeners
http:
services:
core:
http:
enabled: true
port: 18083
host: 0.0.0.0
ssl:
enabled: true
keystore:
type: pkcs12
path: /run/secrets/tls/keystore.p12
password: "secret://kubernetes/gravitee-jks-password:password"
resources:
requests:
cpu: 200m
memory: 2Gi
limits:
cpu: 1000m
memory: 2Gi
ingress:
management:
enabled: true
ingressClassName: nginx
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/proxy-ssl-verify: "on"
nginx.ingress.kubernetes.io/proxy-ssl-secret: "gravitee-apim/gravitee-ca-trust"
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_ssl_name api.gravitee.sttlab.pc;
path: /management
pathType: Prefix
hosts:
- api.gravitee.sttlab.pc
tls:
- hosts:
- api.gravitee.sttlab.pc
secretName: api-tls
portal:
enabled: true
ingressClassName: nginx
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/proxy-ssl-verify: "on"
nginx.ingress.kubernetes.io/proxy-ssl-secret: "gravitee-apim/gravitee-ca-trust"
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_ssl_name api.gravitee.sttlab.pc;
path: /portal
pathType: Prefix
hosts:
- api.gravitee.sttlab.pc
tls:
- hosts:
- api.gravitee.sttlab.pc
secretName: api-tls
# ============================================================
# Management UI (Console) - 1 replica
# ============================================================
ui:
enabled: true
replicaCount: 1
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
ingress:
enabled: true
ingressClassName: nginx
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
hosts:
- console.gravitee.sttlab.pc
path: /(.*)
pathType: ImplementationSpecific
tls:
- hosts:
- console.gravitee.sttlab.pc
secretName: console-tls
# ============================================================
# Developer Portal UI - 1 replica
# ============================================================
portal:
enabled: true
replicaCount: 1
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
ingress:
enabled: true
ingressClassName: nginx
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$1
hosts:
- portal.gravitee.sttlab.pc
path: /(.*)
pathType: ImplementationSpecific
tls:
- hosts:
- portal.gravitee.sttlab.pc
secretName: portal-tls
+6 -2
View File
@@ -32,8 +32,12 @@ kubectl get ingressclass nginx >/dev/null 2>&1 || {
echo "==> Step 1/6 : Create namespace" echo "==> Step 1/6 : Create namespace"
kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f - kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f -
echo "==> Step 2/6 : Create credential secrets (idempotent)" echo "==> Step 2/6 : Create credential secrets (first install only)"
"${SCRIPT_DIR}/secrets-create.sh" if kubectl -n "${NAMESPACE}" get secret gravitee-admin >/dev/null 2>&1; then
echo " Secrets already exist — skipping. Delete them manually to recreate."
else
"${SCRIPT_DIR}/secrets-create.sh"
fi
echo "==> Step 3/6 : Apply cert-manager Issuers + Certificates" echo "==> Step 3/6 : Apply cert-manager Issuers + Certificates"
kubectl apply -f "${SCRIPT_DIR}/certificates.yml" kubectl apply -f "${SCRIPT_DIR}/certificates.yml"
+55
View File
@@ -0,0 +1,55 @@
apiVersion: batch/v1
kind: Job
metadata:
name: gravitee-init-settings
namespace: gravitee-apim
annotations:
helm.sh/hook: post-install,post-upgrade
helm.sh/hook-weight: "10"
helm.sh/hook-delete-policy: before-hook-creation
spec:
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: OnFailure
containers:
- name: init-settings
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
set -e
MGMT_URL="https://graviteeio-apim-api.gravitee-apim.svc.cluster.local:83"
echo "Waiting for Management API..."
until curl -sk -u "${ADMIN_USER}:${ADMIN_PASSWORD}" \
"${MGMT_URL}/management/v2/ui/bootstrap" | grep -q "baseURL"; do
sleep 5
done
echo "Fetching current settings..."
SETTINGS=$(curl -sk -u "${ADMIN_USER}:${ADMIN_PASSWORD}" \
"${MGMT_URL}/management/organizations/DEFAULT/environments/DEFAULT/settings")
echo "Updating portal entrypoint..."
UPDATED=$(echo "$SETTINGS" | sed 's|"entrypoint":"[^"]*"|"entrypoint":"https://gateway.gravitee.sttlab.pc"|')
curl -sk -u "${ADMIN_USER}:${ADMIN_PASSWORD}" \
-X POST \
-H "Content-Type: application/json" \
-d "$UPDATED" \
"${MGMT_URL}/management/organizations/DEFAULT/environments/DEFAULT/settings"
echo "Done."
env:
- name: ADMIN_USER
valueFrom:
secretKeyRef:
name: gravitee-admin
key: admin-username
- name: ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: gravitee-admin
key: admin-password-plain
+13
View File
@@ -0,0 +1,13 @@
apiVersion: gravitee.io/v1alpha1
kind: ManagementContext
metadata:
name: gravitee-management-context
namespace: gravitee-apim
spec:
baseUrl: https://graviteeio-apim-api.gravitee-apim.svc.cluster.local:83
environmentId: DEFAULT
organizationId: DEFAULT
auth:
secretRef:
name: gko-management-credentials
namespace: gravitee-apim
@@ -23,6 +23,9 @@ tls:
extraFlags: extraFlags:
- "--tlsAllowConnectionsWithoutCertificates" - "--tlsAllowConnectionsWithoutCertificates"
updateStrategy:
type: Recreate
persistence: persistence:
enabled: true enabled: true
size: 8Gi size: 8Gi
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# Create all credential secrets before the first helm install.
# Skips any secret that already exists — delete it first to regenerate.
set -euo pipefail
NS="gravitee-apim"
# Ensure namespace exists
kubectl create namespace "${NS}" --dry-run=client -o yaml | kubectl apply -f -
secret_exists() {
kubectl -n "${NS}" get secret "$1" >/dev/null 2>&1
}
echo "==> Creating MongoDB credentials"
if secret_exists mongodb-credentials; then
echo " mongodb-credentials already exists, skipping"
else
MONGO_ROOT_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 16)
MONGO_GRAVITEE_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 16)
kubectl -n "${NS}" create secret generic mongodb-credentials \
--from-literal=mongodb-root-password="${MONGO_ROOT_PASSWORD}" \
--from-literal=mongodb-passwords="${MONGO_GRAVITEE_PASSWORD}" \
--from-literal=mongodb-replica-set-key=''
fi
echo "==> Creating MongoDB URI secret"
if secret_exists gravitee-mongodb-uri; then
echo " gravitee-mongodb-uri already exists, skipping"
else
MONGO_GRAVITEE_PASSWORD=$(kubectl -n "${NS}" get secret mongodb-credentials \
-o jsonpath='{.data.mongodb-passwords}' | base64 -d)
MONGO_URI="mongodb://gravitee:${MONGO_GRAVITEE_PASSWORD}@mongodb.gravitee-apim.svc.cluster.local:27017/gravitee?tls=true&authSource=gravitee"
kubectl -n "${NS}" create secret generic gravitee-mongodb-uri \
--from-literal=GRAVITEE_MANAGEMENT_MONGODB_URI="${MONGO_URI}" \
--from-literal=GRAVITEE_RATELIMIT_MONGODB_URI="${MONGO_URI}"
fi
echo "==> Creating Gravitee admin credentials"
if secret_exists gravitee-admin; then
echo " gravitee-admin already exists, skipping"
else
GRAVITEE_ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 16)
ADMIN_BCRYPT=$(htpasswd -bnBC 10 "" "${GRAVITEE_ADMIN_PASSWORD}" | tr -d ':\n')
kubectl -n "${NS}" create secret generic gravitee-admin \
--from-literal=admin-username='admin' \
--from-literal=admin-password-plain="${GRAVITEE_ADMIN_PASSWORD}" \
--from-literal=admin-password-bcrypt="${ADMIN_BCRYPT}"
fi
echo "==> Creating JKS keystore password"
if secret_exists gravitee-jks-password; then
echo " gravitee-jks-password already exists, skipping"
else
JKS_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 20)
kubectl -n "${NS}" create secret generic gravitee-jks-password \
--from-literal=password="${JKS_PASSWORD}"
fi
echo "==> Creating JWT signing secret"
if secret_exists gravitee-jwt; then
echo " gravitee-jwt already exists, skipping"
else
JWT_SECRET=$(openssl rand -base64 48 | tr -d '\n')
kubectl -n "${NS}" create secret generic gravitee-jwt \
--from-literal=GRAVITEE_JWT_SECRET="${JWT_SECRET}"
fi
echo "==> Creating API properties encryption key"
if secret_exists gravitee-encryption; then
echo " gravitee-encryption already exists, skipping"
else
ENCRYPTION_KEY=$(openssl rand -hex 16)
kubectl -n "${NS}" create secret generic gravitee-encryption \
--from-literal=api-properties-encryption-secret="${ENCRYPTION_KEY}"
fi
echo ""
echo "==> Done. Secrets in namespace ${NS}:"
kubectl -n "${NS}" get secrets | grep -E 'mongodb-credentials|gravitee-mongodb-uri|gravitee-admin|gravitee-jwt|gravitee-jks-password|gravitee-ca-trust'
echo ""
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env bash
# Create all credential secrets manually before helm install.
# Run once. Re-running with new values requires `kubectl delete secret` first.
set -euo pipefail
NS="gravitee-apim"
MONGO_ROOT_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 16)
MONGO_GRAVITEE_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 16)
GRAVITEE_ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 16)
# Ensure namespace exists
kubectl create namespace "${NS}" --dry-run=client -o yaml | kubectl apply -f -
echo "==> Creating MongoDB credentials"
# Used by both the MongoDB chart and the Gravitee chart (consumer)
kubectl -n "${NS}" create secret generic mongodb-credentials \
--from-literal=mongodb-root-password=${MONGO_ROOT_PASSWORD} \
--from-literal=mongodb-passwords=${MONGO_GRAVITEE_PASSWORD} \
--from-literal=mongodb-replica-set-key='' \
--dry-run=client -o yaml | kubectl apply -f -
# Full MongoDB URIs injected via env var override into Gravitee components.
# GRAVITEE_MANAGEMENT_MONGODB_URI overrides management.mongodb.uri in api.
# GRAVITEE_RATELIMIT_MONGODB_URI overrides ratelimit.mongodb.uri in gateway.
MONGO_URI="mongodb://gravitee:${MONGO_GRAVITEE_PASSWORD}@mongodb.gravitee-apim.svc.cluster.local:27017/gravitee?tls=true&authSource=gravitee"
kubectl -n "${NS}" create secret generic gravitee-mongodb-uri \
--from-literal=GRAVITEE_MANAGEMENT_MONGODB_URI="${MONGO_URI}" \
--from-literal=GRAVITEE_RATELIMIT_MONGODB_URI="${MONGO_URI}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "==> Creating Gravitee admin credentials"
ADMIN_BCRYPT=$(htpasswd -bnBC 10 "" "${GRAVITEE_ADMIN_PASSWORD}" | tr -d ':\n')
kubectl -n "${NS}" create secret generic gravitee-admin \
--from-literal=admin-username='admin' \
--from-literal=admin-password-plain="${GRAVITEE_ADMIN_PASSWORD}" \
--from-literal=admin-password-bcrypt="${ADMIN_BCRYPT}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "==> Creating JKS keystore password (used by cert-manager keystores and JAVA_OPTS)"
JKS_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 20)
kubectl -n "${NS}" create secret generic gravitee-jks-password \
--from-literal=password="${JKS_PASSWORD}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "==> Creating JWT signing secret (used by Management API)"
JWT_SECRET=$(openssl rand -base64 48 | tr -d '\n')
kubectl -n "${NS}" create secret generic gravitee-jwt \
--from-literal=GRAVITEE_JWT_SECRET="${JWT_SECRET}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "==> Creating CA trust secret for nginx ingress proxy-ssl-secret"
# Contains only ca.crt (no tls.crt/key) to avoid nginx presenting the CA as a client cert.
kubectl -n "${NS}" get secret gravitee-ca-tls -o jsonpath='{.data.ca\.crt}' | base64 -d | \
kubectl -n "${NS}" create secret generic gravitee-ca-trust \
--from-file=ca.crt=/dev/stdin \
--dry-run=client -o yaml | kubectl apply -f -
echo ""
echo "==> Done. Secrets created in namespace ${NS}:"
kubectl -n "${NS}" get secrets | grep -E 'mongodb-credentials|gravitee-mongodb-uri|gravitee-admin|gravitee-jwt|gravitee-jks-password|gravitee-ca-trust'
echo ""