first commit
This commit is contained in:
@@ -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)"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.claude
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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/&/\&/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 |
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)"
|
||||||
|
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"
|
"${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"
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Executable
+82
@@ -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 ""
|
||||||
@@ -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 ""
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user