docs/PRODUCTION.md + docs/PRODUCTION_IMAGE_PLAN.md
yourlabs/ords : build, audit, runbookCe qui a été implémenté dans le dépôt (image Phase 1, CI Phase 2, Helm Phase 3) et ce qui reste à faire sur votre registre — épinglage, contrôles d’audit, Kubernetes, et pourquoi une image de prod n’est pas « l’image lab scannée puis poussée ».
État des lieux
| Capacité | Statut |
|---|---|
| Digest d’image de base épinglé | ✅ deploy/docker/versions.env |
| Version ORDS épinglée + vérification SHA256 | ✅ |
| Utilisateur non-root (54321) | ✅ |
| Pas de mot de passe DB par défaut dans les scripts prod | ✅ init-config.sh |
| Fail-closed si JWT/pools manquants | ✅ |
| Séparation init / serve | ✅ |
| Healthcheck tolérant au 401 MCP | ✅ |
| CI lint + Trivy + SBOM | ✅ .github/workflows/ci.yml |
| Helm chart + NetworkPolicy | ✅ deploy/k8s/ords-mcp |
| Push cosign vers le registre | ⚪ À activer dans la CI quand les credentials existent |
| Valeurs IdP/DB d’entreprise | ⚪ À fournir via valeurs/secrets Helm |
Build
./deploy/docker/build.sh
docker images 'yourlabs/ords*'
# → yourlabs/ords:mcp-26.2.2.204.1619-<gitsha>
docker compose up -d oracle à la racine du dépôt).deploy/pools/smoke + secrets (gitignorés).ORDS_AUTO_INIT=true : deploy/docker/docker-compose.prod-smoke.yml.values.yaml JWT audience = URL publique du MCP (même chaîne qu’OpenCode ; hôte dédié /mcp — reverse proxy).helm upgrade --install ords-mcp ./deploy/k8s/ords-mcp -f values-prod.yaml.Le conteneur init exécute init ; le conteneur principal exécute serve seul.
database_list
· 4) promouvoir le digest en prod (jamais :latest).
Outils embarqués
Utilisateur non-root oracle, tout sur PATH :
| Outil | Binaire | Source |
|---|---|---|
| SQL*Plus | sqlplus | Instant Client Basic Light + SQL*Plus (IC_* dans versions.env) |
| SQLcl | sql et sqlcl | zip SQLcl (SQLCL_*) |
| Python | python3 | python3 Ubuntu + pip |
| python-oracledb | import oracledb | PyPI épinglé ORACLEDB_VERSION (mode thin par défaut ; Instant Client disponible pour le mode thick) |
sqlplus a besoin des libs Instant Client (LD_LIBRARY_PATH=/opt/oracle/instantclient). SQLcl
utilise le JRE de l’image. python-oracledb est en mode thin et n’en a pas besoin.
deploy/docker/versions.env (ORDS_* et/ou IC_*, SQLCL_*, ORACLEDB_VERSION)docs/PRODUCTION_IMAGE_PLAN.md
Une image de prod ORDS MCP n’est pas « l’image lab scannée puis poussée ». C’est :
eclipse-temurin:21.0.x_x-jre-jammy@sha256:… (digest only en CI)echo "${ORDS_SHA256} /tmp/ords.zip" | sha256sum -c - — idéalement COPY depuis le cache d’artefacts CI (pas d’egress Oracle pendant le build)ca-certificates (+ curl si besoin healthcheck)oracle uid/gid 54321 ; /opt/oracle/ords en lecture seule en K8s, /etc/ords/config volume RW, /tmp emptyDirords-mcp serve # défaut : suppose la config déjà présente
ords-mcp init # one-shot : écrit JWT + pools depuis env/fichiers, exit 0
ords-mcp serve --check # valide la config, exit non-zéro si incomplète
Ne pas réécrire les mots de passe des pools à chaque redémarrage de pod, sauf ORDS_RECONFIGURE=true explicite.
ORDS_JWT_ISSUER
ORDS_JWT_AUDIENCE # doit être égal à l'URL publique du MCP
ORDS_JWT_JWKS_URL
ORDS_JWT_ROLE_CLAIM # ex. /roles2
ORDS_MCP_SCOPE # urn:oracle:dbtools:ords:mcpserver:all
ORDS_POOLS_FILE # chemin vers pools.yaml
Interdit : mots de passe par défaut type HrMcp_ChangeMe1.
# pools.yaml (ConfigMap ; secrets par référence)
pools:
- name: mcp-fin
description: Finance read-only
hostname: fin-scan.db.svc
port: 1521
service: FIN.PDB
username: FIN_MCP_RO
passwordSecretKey: fin-mcp-ro-password # depuis env ou fichier
role: POOL.FIN
scope: urn:oracle:dbtools:ords:mcpserver:all
Le conteneur init rend la config ORDS + ords config secret --password-stdin.
Sondes
| Sonde | Suggestion |
|---|---|
| Liveness | TCP 8080 ou chemin HTTP qui n’exige pas de JWT |
| Readiness | HTTP confirmant que Jetty est up ; port admin séparé optionnel |
| À éviter | curl -f http://127.0.0.1:8080/mcp — le 401 (JWT requis) fait échouer -f |
CI/CD
# Étapes : lint → build → verify → scan → sbom → sign → push
jobs:
build-ords-mcp:
steps:
- checkout
- source deploy/docker/versions.env
- download ORDS zip; echo "$ORDS_SHA256 ords.zip" | sha256sum -c
- docker build \
--build-arg TEMURIN_IMAGE \
--build-arg ORDS_SHA256 \
-t $REGISTRY/ords-mcp:$ORDS_VERSION-$GIT_SHA \
-f deploy/docker/Dockerfile.ords .
- trivy image --exit-code 1 --severity CRITICAL,HIGH $IMAGE
- syft $IMAGE -o cyclonedx-json > sbom.cdx.json
- cosign sign --yes $IMAGE
- cosign attach sbom --sbom sbom.cdx.json $IMAGE
- docker push $IMAGE
- upload-artifact scan+sbom
docker inspect User ≠ root, aucun secret dans Config.Env, checksum zip.trivyignoremain ; staging même digest après smoke ; prod digest épinglé uniquement (jamais :latest), changement via PRKubernetes
Deployment replicas 2+, RollingUpdate, serviceAccount dédiésecurityContext : runAsNonRoot 54321, fsGroup 54321, seccomp RuntimeDefault, drop ALL, readOnlyRootFilesysteminitContainer = ords-mcp init (lit pools.yaml + fichiers secrets → /etc/ords/config) ; conteneur = ords-mcp serveMODULE/ACTION de session DB, DBTOOLS$MCP_LOG, métriques JVM, alertes (sondes KO, pic 5xx, egress bloqué, cert expirant)Sécurité
| Contrôle | Lab aujourd’hui | Cible production |
|---|---|---|
| Non-root | Oui | Oui + securityContext K8s |
| Digest de base épinglé | Non | Oui |
| Version ORDS + SHA256 | Non (latest) | Oui |
| Mots de passe DB par défaut | Oui (lab) | Supprimés |
| Secrets dans l’image | Non | Non |
| Porte de scan d’image | Non | Oui |
| SBOM + signature | Non | Oui |
| TLS vers les clients | Non (HTTP lab) | Ingress/HTTPS |
| NetworkPolicy | Non | Oui |
| Reconfig à chaque démarrage | Oui | Init une fois |
| Healthcheck vs 401 JWT | Fragile | Sonde corrigée |
Déploiement :latest flottant | Local uniquement | Interdit en prod |
| Test | Critère de passage |
|---|---|
/mcp non authentifié | 401 + WWW-Authenticate |
| Jeton rôle A | database_list = pool A uniquement |
| Jeton rôle B | pool B uniquement |
| Jeton sans permission MCP | 403 |
| Jeton sans claim de rôle | 401 |
| JWKS injoignable (simulation) | fail-closed (pas de MCP ouvert) |
Plan d’implémentation
| Phase | Durée | Contenu |
|---|---|---|
| 0 · Décision | 1–2 j | Registre, CI, scanner, standard de signature ; IdP + schéma d’URL MCP public ; version ORDS à épingler |
| 1 · Image durcie | 3–5 j | versions.env ; Dockerfile.ords ; entrypoint init/serve sans mots de passe par défaut ; pools.yaml ; sondes corrigées ; hadolint/shellcheck |
| 2 · Audits CI | 3–5 j | PR : gitleaks, hadolint, shellcheck ; build + porte Trivy + SBOM Syft ; cosign sign + push dev ; conservation artefacts |
| 3 · Runtime K8s | 1–2 sem | Helm/Kustomize ; NetworkPolicy, PDB, HPA (opt.) ; ExternalSecrets ; staging du digest ; smoke OAuth + isolation |
| 4 · Prêt prod | 1–2 sem | Revue sécurité / threat model ; runbook + alertes ; process de promotion digest ; sauvegarde/restauration config ; DR depuis git + versions.env |