docs/PRODUCTION.md + docs/PRODUCTION_IMAGE_PLAN.md

Image de production yourlabs/ords : build, audit, runbook

Ce 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

Ce que « prêt pour la production » veut dire ici

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

Compiler et vérifier

./deploy/docker/build.sh
docker images 'yourlabs/ords*'
# → yourlabs/ords:mcp-26.2.2.204.1619-<gitsha>

Smoke local contre l’Oracle du lab

  1. Base lab lancée (docker compose up -d oracle à la racine du dépôt).
  2. Fichiers de pool sous deploy/pools/smoke + secrets (gitignorés).
  3. JWT Auth0 exporté dans l’environnement.
  4. Compose avec ORDS_AUTO_INIT=true : deploy/docker/docker-compose.prod-smoke.yml.

Kubernetes

  1. Compiler et pousser l’image vers votre registre (tag + digest).
  2. Créer un secret avec les fichiers de mots de passe des pools.
  3. Remplir values.yaml JWT audience = URL publique du MCP (même chaîne qu’OpenCode ; hôte dédié /mcp — reverse proxy).
  4. 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.

✅ Audits à chaque release 1) CI verte (hadolint, shellcheck, gitleaks, Trivy HIGH+, artefact SBOM) · 2) noter le digest de l’image dans le ticket de changement · 3) smoke en staging : OAuth + isolation de rôle via database_list · 4) promouvoir le digest en prod (jamais :latest).

Outils embarqués

L’image embarque aussi des clients CLI

Utilisateur non-root oracle, tout sur PATH :

OutilBinaireSource
SQL*PlussqlplusInstant Client Basic Light + SQL*Plus (IC_* dans versions.env)
SQLclsql et sqlclzip SQLcl (SQLCL_*)
Pythonpython3python3 Ubuntu + pip
python-oracledbimport oracledbPyPI é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.

Mettre à niveau ORDS / clients

  1. Télécharger le(s) nouveau(x) zip ; calculer le SHA256
  2. Mettre à jour deploy/docker/versions.env (ORDS_* et/ou IC_*, SQLCL_*, ORACLEDB_VERSION)
  3. PR → CI → staging → digest prod

docs/PRODUCTION_IMAGE_PLAN.md

Ce que « prêt » veut dire en plus (le plan)

Une image de prod ORDS MCP n’est pas « l’image lab scannée puis poussée ». C’est :

  1. Image épinglée, vérifiée par somme de contrôle, minimale, non-root
  2. Audits CI : lint, porte CVE, SBOM, signature, provenance
  3. Runtime : secrets hors image, TLS, NetworkPolicy, config init-once
  4. Spécifique MCP : contrat JWT, pools en moindre privilège, audit SQL/session
  5. Processus : promotion par digest, conservation des preuves de build, feu vert juridique

Conception de l’image

Modes d’entrée

ords-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.

Config fail-closed

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.

Définition de pools, pilotée par les données

# 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

Health / readiness (et le piège du 401)

SondeSuggestion
LivenessTCP 8080 ou chemin HTTP qui n’exige pas de JWT
ReadinessHTTP confirmant que Jetty est up ; port admin séparé optionnel
À évitercurl -f http://127.0.0.1:8080/mcp — le 401 (JWT requis) fait échouer -f

CI/CD

Pipeline de build + audits

# É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
⚠️ Risque de dérive de claims Le lab recommande des tests de contrat sur la forme du JWT en CI contre une JWKS simulée — un IdP qui change ses claims casserait la sélection des pools silencieusement.

Kubernetes

Forme runtime en K8s

Sécurité

Matrice de contrôle & tests

ContrôleLab aujourd’huiCible production
Non-rootOuiOui + securityContext K8s
Digest de base épingléNonOui
Version ORDS + SHA256Non (latest)Oui
Mots de passe DB par défautOui (lab)Supprimés
Secrets dans l’imageNonNon
Porte de scan d’imageNonOui
SBOM + signatureNonOui
TLS vers les clientsNon (HTTP lab)Ingress/HTTPS
NetworkPolicyNonOui
Reconfig à chaque démarrageOuiInit une fois
Healthcheck vs 401 JWTFragileSonde corrigée
Déploiement :latest flottantLocal uniquementInterdit en prod

Tests d’intégration (staging)

TestCritère de passage
/mcp non authentifié401 + WWW-Authenticate
Jeton rôle Adatabase_list = pool A uniquement
Jeton rôle Bpool B uniquement
Jeton sans permission MCP403
Jeton sans claim de rôle401
JWKS injoignable (simulation)fail-closed (pas de MCP ouvert)

Plan d’implémentation

Phases recommandées

PhaseDuréeContenu
0 · Décision1–2 jRegistre, CI, scanner, standard de signature ; IdP + schéma d’URL MCP public ; version ORDS à épingler
1 · Image durcie3–5 jversions.env ; Dockerfile.ords ; entrypoint init/serve sans mots de passe par défaut ; pools.yaml ; sondes corrigées ; hadolint/shellcheck
2 · Audits CI3–5 jPR : gitleaks, hadolint, shellcheck ; build + porte Trivy + SBOM Syft ; cosign sign + push dev ; conservation artefacts
3 · Runtime K8s1–2 semHelm/Kustomize ; NetworkPolicy, PDB, HPA (opt.) ; ExternalSecrets ; staging du digest ; smoke OAuth + isolation
4 · Prêt prod1–2 semRevue sécurité / threat model ; runbook + alertes ; process de promotion digest ; sauvegarde/restauration config ; DR depuis git + versions.env
💡 Tranche immédiate suggérée (Phase 1 seulement) Épingler le digest Temurin + version/SHA256 ORDS ; supprimer les mots de passe par défaut ; scinder init/serve ; ajouter Trivy + hadolint en CI ; documenter les tags et « pas pour base de prod avant Phase 3 ».