fix: mount referentiel ia token as docker secret

This commit is contained in:
Manus
2026-09-30 16:25:53 +00:00
parent e9d25d9270
commit 5eb8c8b3ac
4 changed files with 40 additions and 1 deletions

View File

@@ -61,6 +61,7 @@ services:
# Passerelle centralisée : le jeton reste dans l'environnement serveur.
REFERENTIEL_IA_URL: ${REFERENTIEL_IA_URL:-}
REFERENTIEL_IA_TOKEN: ${REFERENTIEL_IA_TOKEN:-}
REFERENTIEL_IA_TOKEN_FILE: /run/secrets/referentiel_ia_token
UPLOAD_DIR: /app/uploads
# Chemin unique utilisé par le serveur et vérifié au démarrage.
STORAGE_BASE_PATH: /app/storage
@@ -81,6 +82,10 @@ services:
target: /app/storage
volume:
nocopy: true
secrets:
- source: referentiel_ia_token
target: referentiel_ia_token
mode: 0400
# L’application doit utiliser eth0 sur le réseau de données ; l’ordre évite
# toute inversion d’interface lors d’une recréation Docker multi-réseaux.
networks:
@@ -110,6 +115,10 @@ volumes:
external: true
name: ${STORAGE_VOLUME_NAME:-demat-facturation-dsi_demat_facturation_storage}
secrets:
referentiel_ia_token:
file: /opt/manus-secrets/demat-facturation-referentiel-ia-token
networks:
demat-facturation-internal:
driver: bridge

View File

@@ -1,3 +1,15 @@
import { readFileSync } from "node:fs";
/** Lit un secret monté par Docker, sans jamais exposer son contenu aux logs. */
export function readSecretFile(path: string | undefined): string {
if (!path) return "";
try {
return readFileSync(path, "utf8").trim();
} catch {
return "";
}
}
export const ENV = {
appId: process.env.VITE_APP_ID ?? "",
cookieSecret: process.env.JWT_SECRET ?? "",
@@ -10,5 +22,5 @@ export const ENV = {
mistralApiKey: process.env.MISTRAL_API_KEY ?? "",
geminiApiKey: process.env.GEMINI_API_KEY ?? "",
referentielIaUrl: process.env.REFERENTIEL_IA_URL ?? "",
referentielIaToken: process.env.REFERENTIEL_IA_TOKEN ?? "",
referentielIaToken: process.env.REFERENTIEL_IA_TOKEN ?? readSecretFile(process.env.REFERENTIEL_IA_TOKEN_FILE),
};

View File

@@ -41,5 +41,7 @@ describe("routage et persistance de déploiement", () => {
expect(composeFile).toContain("REFERENTIEL_IA_URL: ${REFERENTIEL_IA_URL:-}");
expect(composeFile).toContain("REFERENTIEL_IA_TOKEN: ${REFERENTIEL_IA_TOKEN:-}");
expect(composeFile).toContain("REFERENTIEL_IA_TOKEN_FILE: /run/secrets/referentiel_ia_token");
expect(composeFile).toContain("file: /opt/manus-secrets/demat-facturation-referentiel-ia-token");
});
});

View File

@@ -1,9 +1,13 @@
import { describe, expect, it } from "vitest";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
invokeReferentialIa,
testReferentialIaConnection,
type ReferentielIaConnection,
} from "./referentialIa";
import { readSecretFile } from "./_core/env";
const connection: ReferentielIaConnection = {
baseUrl: "https://referentiel-ia.recette.santinova-soft.org",
@@ -18,6 +22,18 @@ function jsonResponse(body: unknown, status = 200) {
}
describe("Référentiel IA", () => {
it("lit un jeton serveur depuis un fichier monté sans le journaliser", () => {
const directory = mkdtempSync(join(tmpdir(), "referentiel-ia-"));
const secretFile = join(directory, "token");
try {
writeFileSync(secretFile, " server-only-token\n", { mode: 0o600 });
expect(readSecretFile(secretFile)).toBe("server-only-token");
expect(readSecretFile(join(directory, "absent"))).toBe("");
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
it("transmet une requête OpenAI-compatible à la passerelle sélectionnée", async () => {
let receivedUrl = "";
let receivedInit: RequestInit | undefined;