From e8af3fa8a88c54e97aaff4eb01588377a486a43c Mon Sep 17 00:00:00 2001 From: nuwangeek Date: Fri, 9 Jan 2026 16:59:22 +0530 Subject: [PATCH 1/2] vault secret update after fixing issues --- .../script/store_secrets_in_vault.sh | 50 +- ....timestamp-1767946562610-7e8d2a8c1f401.mjs | 70 + ....timestamp-1767946574215-f7ac6ce2fedaa.mjs | 70 + docker-compose.yml | 109 +- docs/VAULT_SECURITY_ARCHITECTURE.md | 1330 +++++++++++++++++ .../vault/vault_client.py | 63 +- vault-init.sh | 263 +++- vault/agents/cron/cron-agent.hcl | 47 + vault/agents/gui/gui-agent.hcl | 47 + vault/agents/llm/agent.hcl | 16 +- 10 files changed, 1942 insertions(+), 123 deletions(-) create mode 100644 GUI/vite.config.ts.timestamp-1767946562610-7e8d2a8c1f401.mjs create mode 100644 GUI/vite.config.ts.timestamp-1767946574215-f7ac6ce2fedaa.mjs create mode 100644 docs/VAULT_SECURITY_ARCHITECTURE.md create mode 100644 vault/agents/cron/cron-agent.hcl create mode 100644 vault/agents/gui/gui-agent.hcl diff --git a/DSL/CronManager/script/store_secrets_in_vault.sh b/DSL/CronManager/script/store_secrets_in_vault.sh index dfc433b..8f0ecb5 100644 --- a/DSL/CronManager/script/store_secrets_in_vault.sh +++ b/DSL/CronManager/script/store_secrets_in_vault.sh @@ -1,20 +1,22 @@ #!/bin/bash -# Vault Secrets Storage Script +# Vault Secrets Storage Script (No Decryption) # This script stores LLM and embedding credentials in HashiCorp Vault +# WITHOUT decryption - uses raw values as received set -e # Exit on any error # Configuration -VAULT_ADDR="${VAULT_ADDR:-http://vault:8200}" -VAULT_TOKEN_FILE="/agent/out/token" +# Use VAULT_AGENT_URL which points to vault-agent-cron proxy +# The agent automatically injects the authentication token +VAULT_ADDR="${VAULT_AGENT_URL:-http://vault-agent-cron:8203}" # Logging function log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } -log "=== Starting Vault Secrets Storage ===" +log "=== Starting Vault Secrets Storage (No Decryption) ===" # Debug: Print received parameters log "Received parameters:" @@ -22,20 +24,9 @@ log " connectionId: $connectionId" log " llmPlatform: $llmPlatform" log " llmModel: $llmModel" log " deploymentEnvironment: $deploymentEnvironment" +log " Vault Address: $VAULT_ADDR" -# Read vault token -if [ ! -f "$VAULT_TOKEN_FILE" ]; then - log "ERROR: Vault token file not found at $VAULT_TOKEN_FILE" - exit 1 -fi - -VAULT_TOKEN=$(cat "$VAULT_TOKEN_FILE") -if [ -z "$VAULT_TOKEN" ]; then - log "ERROR: Vault token is empty" - exit 1 -fi - -log "Vault token loaded successfully" +# Note: No token required - vault agent proxy automatically injects authentication # Function to determine platform name get_platform_name() { @@ -113,7 +104,9 @@ store_aws_llm_secrets() { local vault_path=$1 local model=$(get_model_name) - log "Storing AWS LLM secrets..." + log "Storing AWS LLM secrets (raw values)..." + + # Use raw values directly (no decryption) # Build JSON payload local json_payload=$(cat < !x.hidden)) == null ? void 0 : _a.filter((x) => isHiddenFeaturesEnabled || x.hiddenMode !== "production"); + for (const a of arr) { + a.children = removeHidden(a.children, isHiddenFeaturesEnabled); + } + return arr; +} + +// vite.config.ts +var __vite_injected_original_dirname = "/app"; +var vite_config_default = defineConfig({ + envPrefix: "REACT_APP_", + plugins: [ + react(), + tsconfigPaths(), + svgr(), + { + name: "removeHiddenMenuItemsPlugin", + transform: (str, id) => { + if (!id.endsWith("/menu-structure.json")) + return str; + return removeHiddenMenuItems(str); + } + } + ], + base: "/rag-search", + build: { + outDir: "./build", + target: "es2015", + emptyOutDir: true + }, + server: { + headers: { + ...process.env.REACT_APP_CSP && { + "Content-Security-Policy": process.env.REACT_APP_CSP + } + }, + allowedHosts: ["est-rag-rtc.rootcode.software", "localhost", "127.0.0.1"] + }, + resolve: { + alias: { + "~@fontsource": path.resolve(__vite_injected_original_dirname, "node_modules/@fontsource"), + "@": `${path.resolve(__vite_injected_original_dirname, "./src")}` + } + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiLCAidml0ZVBsdWdpbi5qcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIi9hcHBcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9hcHAvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL2FwcC92aXRlLmNvbmZpZy50c1wiO2ltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gJ3ZpdGUnO1xuaW1wb3J0IHJlYWN0IGZyb20gJ0B2aXRlanMvcGx1Z2luLXJlYWN0JztcbmltcG9ydCB0c2NvbmZpZ1BhdGhzIGZyb20gJ3ZpdGUtdHNjb25maWctcGF0aHMnO1xuaW1wb3J0IHN2Z3IgZnJvbSAndml0ZS1wbHVnaW4tc3Zncic7XG5pbXBvcnQgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB7IHJlbW92ZUhpZGRlbk1lbnVJdGVtcyB9IGZyb20gJy4vdml0ZVBsdWdpbic7XG5cbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICBlbnZQcmVmaXg6ICdSRUFDVF9BUFBfJyxcbiAgcGx1Z2luczogW1xuICAgIHJlYWN0KCksXG4gICAgdHNjb25maWdQYXRocygpLFxuICAgIHN2Z3IoKSxcbiAgICB7XG4gICAgICBuYW1lOiAncmVtb3ZlSGlkZGVuTWVudUl0ZW1zUGx1Z2luJyxcbiAgICAgIHRyYW5zZm9ybTogKHN0ciwgaWQpID0+IHtcbiAgICAgICAgaWYoIWlkLmVuZHNXaXRoKCcvbWVudS1zdHJ1Y3R1cmUuanNvbicpKVxuICAgICAgICAgIHJldHVybiBzdHI7XG4gICAgICAgIHJldHVybiByZW1vdmVIaWRkZW5NZW51SXRlbXMoc3RyKTtcbiAgICAgIH0sXG4gICAgfSxcbiAgXSxcbiAgYmFzZTogJy9yYWctc2VhcmNoJyxcbiAgYnVpbGQ6IHtcbiAgICBvdXREaXI6ICcuL2J1aWxkJyxcbiAgICB0YXJnZXQ6ICdlczIwMTUnLFxuICAgIGVtcHR5T3V0RGlyOiB0cnVlLFxuICB9LFxuICBzZXJ2ZXI6IHtcbiAgICBoZWFkZXJzOiB7XG4gICAgICAuLi4ocHJvY2Vzcy5lbnYuUkVBQ1RfQVBQX0NTUCAmJiB7XG4gICAgICAgICdDb250ZW50LVNlY3VyaXR5LVBvbGljeSc6IHByb2Nlc3MuZW52LlJFQUNUX0FQUF9DU1AsXG4gICAgICB9KSxcbiAgICB9LFxuICAgIGFsbG93ZWRIb3N0czogWydlc3QtcmFnLXJ0Yy5yb290Y29kZS5zb2Z0d2FyZScsICdsb2NhbGhvc3QnLCAnMTI3LjAuMC4xJ10sXG5cbiAgfSxcbiAgcmVzb2x2ZToge1xuICAgIGFsaWFzOiB7XG4gICAgICAnfkBmb250c291cmNlJzogcGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJ25vZGVfbW9kdWxlcy9AZm9udHNvdXJjZScpLFxuICAgICAgJ0AnOiBgJHtwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnLi9zcmMnKX1gLFxuICAgIH0sXG4gIH0sXG59KTtcbiIsICJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL2FwcFwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL2FwcC92aXRlUGx1Z2luLmpzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9hcHAvdml0ZVBsdWdpbi5qc1wiO2V4cG9ydCBmdW5jdGlvbiByZW1vdmVIaWRkZW5NZW51SXRlbXMoc3RyKSB7XG4gIGNvbnN0IGJhZEpzb24gPSBzdHIucmVwbGFjZSgnZXhwb3J0IGRlZmF1bHQgWycsICdbJykucmVwbGFjZSgnXTsnLCAnXScpO1xuICBjb25zdCBjb3JyZWN0SnNvbiA9IGJhZEpzb24ucmVwbGFjZSgvKFsnXCJdKT8oW2EtejAtOUEtWl9dKykoWydcIl0pPzovZywgJ1wiJDJcIjogJyk7XG5cbiBjb25zdCBpc0hpZGRlbkZlYXR1cmVzRW5hYmxlZCA9IFxuICAgIHByb2Nlc3MuZW52LlJFQUNUX0FQUF9FTkFCTEVfSElEREVOX0ZFQVRVUkVTPy50b0xvd2VyQ2FzZSgpLnRyaW0oKSA9PT0gJ3RydWUnIHx8XG4gICAgcHJvY2Vzcy5lbnYuUkVBQ1RfQVBQX0VOQUJMRV9ISURERU5fRkVBVFVSRVM/LnRvTG93ZXJDYXNlKCkudHJpbSgpID09PSAnMSc7XG5cbiAgY29uc3QganNvbiA9IHJlbW92ZUhpZGRlbihKU09OLnBhcnNlKGNvcnJlY3RKc29uKSwgaXNIaWRkZW5GZWF0dXJlc0VuYWJsZWQpO1xuICBcbiAgY29uc3QgdXBkYXRlZEpzb24gPSBKU09OLnN0cmluZ2lmeShqc29uKTtcblxuICByZXR1cm4gJ2V4cG9ydCBkZWZhdWx0ICcgKyB1cGRhdGVkSnNvbiArICc7J1xufVxuXG5mdW5jdGlvbiByZW1vdmVIaWRkZW4obWVudUl0ZW1zLCBpc0hpZGRlbkZlYXR1cmVzRW5hYmxlZCkge1xuICBpZighbWVudUl0ZW1zKSByZXR1cm4gbWVudUl0ZW1zO1xuICBjb25zdCBhcnIgPSBtZW51SXRlbXNcbiAgICA/LmZpbHRlcih4ID0+ICF4LmhpZGRlbilcbiAgICA/LmZpbHRlcih4ID0+IGlzSGlkZGVuRmVhdHVyZXNFbmFibGVkIHx8IHguaGlkZGVuTW9kZSAhPT0gXCJwcm9kdWN0aW9uXCIpO1xuICBmb3IgKGNvbnN0IGEgb2YgYXJyKSB7XG4gICAgYS5jaGlsZHJlbiA9IHJlbW92ZUhpZGRlbihhLmNoaWxkcmVuLCBpc0hpZGRlbkZlYXR1cmVzRW5hYmxlZCk7XG4gIH1cbiAgcmV0dXJuIGFycjtcbn1cbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBOEwsU0FBUyxvQkFBb0I7QUFDM04sT0FBTyxXQUFXO0FBQ2xCLE9BQU8sbUJBQW1CO0FBQzFCLE9BQU8sVUFBVTtBQUNqQixPQUFPLFVBQVU7OztBQ0prTCxTQUFTLHNCQUFzQixLQUFLO0FBQXZPO0FBQ0UsUUFBTSxVQUFVLElBQUksUUFBUSxvQkFBb0IsR0FBRyxFQUFFLFFBQVEsTUFBTSxHQUFHO0FBQ3RFLFFBQU0sY0FBYyxRQUFRLFFBQVEsbUNBQW1DLFFBQVE7QUFFaEYsUUFBTSw0QkFDSCxhQUFRLElBQUkscUNBQVosbUJBQThDLGNBQWMsWUFBVyxZQUN2RSxhQUFRLElBQUkscUNBQVosbUJBQThDLGNBQWMsWUFBVztBQUV6RSxRQUFNLE9BQU8sYUFBYSxLQUFLLE1BQU0sV0FBVyxHQUFHLHVCQUF1QjtBQUUxRSxRQUFNLGNBQWMsS0FBSyxVQUFVLElBQUk7QUFFdkMsU0FBTyxvQkFBb0IsY0FBYztBQUMzQztBQUVBLFNBQVMsYUFBYSxXQUFXLHlCQUF5QjtBQWYxRDtBQWdCRSxNQUFHLENBQUM7QUFBVyxXQUFPO0FBQ3RCLFFBQU0sT0FBTSw0Q0FDUixPQUFPLE9BQUssQ0FBQyxFQUFFLFlBRFAsbUJBRVIsT0FBTyxPQUFLLDJCQUEyQixFQUFFLGVBQWU7QUFDNUQsYUFBVyxLQUFLLEtBQUs7QUFDbkIsTUFBRSxXQUFXLGFBQWEsRUFBRSxVQUFVLHVCQUF1QjtBQUFBLEVBQy9EO0FBQ0EsU0FBTztBQUNUOzs7QUR4QkEsSUFBTSxtQ0FBbUM7QUFRekMsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsV0FBVztBQUFBLEVBQ1gsU0FBUztBQUFBLElBQ1AsTUFBTTtBQUFBLElBQ04sY0FBYztBQUFBLElBQ2QsS0FBSztBQUFBLElBQ0w7QUFBQSxNQUNFLE1BQU07QUFBQSxNQUNOLFdBQVcsQ0FBQyxLQUFLLE9BQU87QUFDdEIsWUFBRyxDQUFDLEdBQUcsU0FBUyxzQkFBc0I7QUFDcEMsaUJBQU87QUFDVCxlQUFPLHNCQUFzQixHQUFHO0FBQUEsTUFDbEM7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUFBLEVBQ0EsTUFBTTtBQUFBLEVBQ04sT0FBTztBQUFBLElBQ0wsUUFBUTtBQUFBLElBQ1IsUUFBUTtBQUFBLElBQ1IsYUFBYTtBQUFBLEVBQ2Y7QUFBQSxFQUNBLFFBQVE7QUFBQSxJQUNOLFNBQVM7QUFBQSxNQUNQLEdBQUksUUFBUSxJQUFJLGlCQUFpQjtBQUFBLFFBQy9CLDJCQUEyQixRQUFRLElBQUk7QUFBQSxNQUN6QztBQUFBLElBQ0Y7QUFBQSxJQUNBLGNBQWMsQ0FBQyxpQ0FBaUMsYUFBYSxXQUFXO0FBQUEsRUFFMUU7QUFBQSxFQUNBLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMLGdCQUFnQixLQUFLLFFBQVEsa0NBQVcsMEJBQTBCO0FBQUEsTUFDbEUsS0FBSyxHQUFHLEtBQUssUUFBUSxrQ0FBVyxPQUFPLENBQUM7QUFBQSxJQUMxQztBQUFBLEVBQ0Y7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo= diff --git a/GUI/vite.config.ts.timestamp-1767946574215-f7ac6ce2fedaa.mjs b/GUI/vite.config.ts.timestamp-1767946574215-f7ac6ce2fedaa.mjs new file mode 100644 index 0000000..b770c4c --- /dev/null +++ b/GUI/vite.config.ts.timestamp-1767946574215-f7ac6ce2fedaa.mjs @@ -0,0 +1,70 @@ +// vite.config.ts +import { defineConfig } from "file:///app/node_modules/vite/dist/node/index.js"; +import react from "file:///app/node_modules/@vitejs/plugin-react/dist/index.mjs"; +import tsconfigPaths from "file:///app/node_modules/vite-tsconfig-paths/dist/index.mjs"; +import svgr from "file:///app/node_modules/vite-plugin-svgr/dist/index.mjs"; +import path from "path"; + +// vitePlugin.js +function removeHiddenMenuItems(str) { + var _a, _b; + const badJson = str.replace("export default [", "[").replace("];", "]"); + const correctJson = badJson.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '); + const isHiddenFeaturesEnabled = ((_a = process.env.REACT_APP_ENABLE_HIDDEN_FEATURES) == null ? void 0 : _a.toLowerCase().trim()) === "true" || ((_b = process.env.REACT_APP_ENABLE_HIDDEN_FEATURES) == null ? void 0 : _b.toLowerCase().trim()) === "1"; + const json = removeHidden(JSON.parse(correctJson), isHiddenFeaturesEnabled); + const updatedJson = JSON.stringify(json); + return "export default " + updatedJson + ";"; +} +function removeHidden(menuItems, isHiddenFeaturesEnabled) { + var _a; + if (!menuItems) + return menuItems; + const arr = (_a = menuItems == null ? void 0 : menuItems.filter((x) => !x.hidden)) == null ? void 0 : _a.filter((x) => isHiddenFeaturesEnabled || x.hiddenMode !== "production"); + for (const a of arr) { + a.children = removeHidden(a.children, isHiddenFeaturesEnabled); + } + return arr; +} + +// vite.config.ts +var __vite_injected_original_dirname = "/app"; +var vite_config_default = defineConfig({ + envPrefix: "REACT_APP_", + plugins: [ + react(), + tsconfigPaths(), + svgr(), + { + name: "removeHiddenMenuItemsPlugin", + transform: (str, id) => { + if (!id.endsWith("/menu-structure.json")) + return str; + return removeHiddenMenuItems(str); + } + } + ], + base: "/rag-search", + build: { + outDir: "./build", + target: "es2015", + emptyOutDir: true + }, + server: { + headers: { + ...process.env.REACT_APP_CSP && { + "Content-Security-Policy": process.env.REACT_APP_CSP + } + }, + allowedHosts: ["est-rag-rtc.rootcode.software", "localhost", "127.0.0.1"] + }, + resolve: { + alias: { + "~@fontsource": path.resolve(__vite_injected_original_dirname, "node_modules/@fontsource"), + "@": `${path.resolve(__vite_injected_original_dirname, "./src")}` + } + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiLCAidml0ZVBsdWdpbi5qcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIi9hcHBcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9hcHAvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL2FwcC92aXRlLmNvbmZpZy50c1wiO2ltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gJ3ZpdGUnO1xuaW1wb3J0IHJlYWN0IGZyb20gJ0B2aXRlanMvcGx1Z2luLXJlYWN0JztcbmltcG9ydCB0c2NvbmZpZ1BhdGhzIGZyb20gJ3ZpdGUtdHNjb25maWctcGF0aHMnO1xuaW1wb3J0IHN2Z3IgZnJvbSAndml0ZS1wbHVnaW4tc3Zncic7XG5pbXBvcnQgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB7IHJlbW92ZUhpZGRlbk1lbnVJdGVtcyB9IGZyb20gJy4vdml0ZVBsdWdpbic7XG5cbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICBlbnZQcmVmaXg6ICdSRUFDVF9BUFBfJyxcbiAgcGx1Z2luczogW1xuICAgIHJlYWN0KCksXG4gICAgdHNjb25maWdQYXRocygpLFxuICAgIHN2Z3IoKSxcbiAgICB7XG4gICAgICBuYW1lOiAncmVtb3ZlSGlkZGVuTWVudUl0ZW1zUGx1Z2luJyxcbiAgICAgIHRyYW5zZm9ybTogKHN0ciwgaWQpID0+IHtcbiAgICAgICAgaWYoIWlkLmVuZHNXaXRoKCcvbWVudS1zdHJ1Y3R1cmUuanNvbicpKVxuICAgICAgICAgIHJldHVybiBzdHI7XG4gICAgICAgIHJldHVybiByZW1vdmVIaWRkZW5NZW51SXRlbXMoc3RyKTtcbiAgICAgIH0sXG4gICAgfSxcbiAgXSxcbiAgYmFzZTogJy9yYWctc2VhcmNoJyxcbiAgYnVpbGQ6IHtcbiAgICBvdXREaXI6ICcuL2J1aWxkJyxcbiAgICB0YXJnZXQ6ICdlczIwMTUnLFxuICAgIGVtcHR5T3V0RGlyOiB0cnVlLFxuICB9LFxuICBzZXJ2ZXI6IHtcbiAgICBoZWFkZXJzOiB7XG4gICAgICAuLi4ocHJvY2Vzcy5lbnYuUkVBQ1RfQVBQX0NTUCAmJiB7XG4gICAgICAgICdDb250ZW50LVNlY3VyaXR5LVBvbGljeSc6IHByb2Nlc3MuZW52LlJFQUNUX0FQUF9DU1AsXG4gICAgICB9KSxcbiAgICB9LFxuICAgIGFsbG93ZWRIb3N0czogWydlc3QtcmFnLXJ0Yy5yb290Y29kZS5zb2Z0d2FyZScsICdsb2NhbGhvc3QnLCAnMTI3LjAuMC4xJ10sXG5cbiAgfSxcbiAgcmVzb2x2ZToge1xuICAgIGFsaWFzOiB7XG4gICAgICAnfkBmb250c291cmNlJzogcGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJ25vZGVfbW9kdWxlcy9AZm9udHNvdXJjZScpLFxuICAgICAgJ0AnOiBgJHtwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnLi9zcmMnKX1gLFxuICAgIH0sXG4gIH0sXG59KTtcbiIsICJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL2FwcFwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL2FwcC92aXRlUGx1Z2luLmpzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9hcHAvdml0ZVBsdWdpbi5qc1wiO2V4cG9ydCBmdW5jdGlvbiByZW1vdmVIaWRkZW5NZW51SXRlbXMoc3RyKSB7XG4gIGNvbnN0IGJhZEpzb24gPSBzdHIucmVwbGFjZSgnZXhwb3J0IGRlZmF1bHQgWycsICdbJykucmVwbGFjZSgnXTsnLCAnXScpO1xuICBjb25zdCBjb3JyZWN0SnNvbiA9IGJhZEpzb24ucmVwbGFjZSgvKFsnXCJdKT8oW2EtejAtOUEtWl9dKykoWydcIl0pPzovZywgJ1wiJDJcIjogJyk7XG5cbiBjb25zdCBpc0hpZGRlbkZlYXR1cmVzRW5hYmxlZCA9IFxuICAgIHByb2Nlc3MuZW52LlJFQUNUX0FQUF9FTkFCTEVfSElEREVOX0ZFQVRVUkVTPy50b0xvd2VyQ2FzZSgpLnRyaW0oKSA9PT0gJ3RydWUnIHx8XG4gICAgcHJvY2Vzcy5lbnYuUkVBQ1RfQVBQX0VOQUJMRV9ISURERU5fRkVBVFVSRVM/LnRvTG93ZXJDYXNlKCkudHJpbSgpID09PSAnMSc7XG5cbiAgY29uc3QganNvbiA9IHJlbW92ZUhpZGRlbihKU09OLnBhcnNlKGNvcnJlY3RKc29uKSwgaXNIaWRkZW5GZWF0dXJlc0VuYWJsZWQpO1xuICBcbiAgY29uc3QgdXBkYXRlZEpzb24gPSBKU09OLnN0cmluZ2lmeShqc29uKTtcblxuICByZXR1cm4gJ2V4cG9ydCBkZWZhdWx0ICcgKyB1cGRhdGVkSnNvbiArICc7J1xufVxuXG5mdW5jdGlvbiByZW1vdmVIaWRkZW4obWVudUl0ZW1zLCBpc0hpZGRlbkZlYXR1cmVzRW5hYmxlZCkge1xuICBpZighbWVudUl0ZW1zKSByZXR1cm4gbWVudUl0ZW1zO1xuICBjb25zdCBhcnIgPSBtZW51SXRlbXNcbiAgICA/LmZpbHRlcih4ID0+ICF4LmhpZGRlbilcbiAgICA/LmZpbHRlcih4ID0+IGlzSGlkZGVuRmVhdHVyZXNFbmFibGVkIHx8IHguaGlkZGVuTW9kZSAhPT0gXCJwcm9kdWN0aW9uXCIpO1xuICBmb3IgKGNvbnN0IGEgb2YgYXJyKSB7XG4gICAgYS5jaGlsZHJlbiA9IHJlbW92ZUhpZGRlbihhLmNoaWxkcmVuLCBpc0hpZGRlbkZlYXR1cmVzRW5hYmxlZCk7XG4gIH1cbiAgcmV0dXJuIGFycjtcbn1cbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBOEwsU0FBUyxvQkFBb0I7QUFDM04sT0FBTyxXQUFXO0FBQ2xCLE9BQU8sbUJBQW1CO0FBQzFCLE9BQU8sVUFBVTtBQUNqQixPQUFPLFVBQVU7OztBQ0prTCxTQUFTLHNCQUFzQixLQUFLO0FBQXZPO0FBQ0UsUUFBTSxVQUFVLElBQUksUUFBUSxvQkFBb0IsR0FBRyxFQUFFLFFBQVEsTUFBTSxHQUFHO0FBQ3RFLFFBQU0sY0FBYyxRQUFRLFFBQVEsbUNBQW1DLFFBQVE7QUFFaEYsUUFBTSw0QkFDSCxhQUFRLElBQUkscUNBQVosbUJBQThDLGNBQWMsWUFBVyxZQUN2RSxhQUFRLElBQUkscUNBQVosbUJBQThDLGNBQWMsWUFBVztBQUV6RSxRQUFNLE9BQU8sYUFBYSxLQUFLLE1BQU0sV0FBVyxHQUFHLHVCQUF1QjtBQUUxRSxRQUFNLGNBQWMsS0FBSyxVQUFVLElBQUk7QUFFdkMsU0FBTyxvQkFBb0IsY0FBYztBQUMzQztBQUVBLFNBQVMsYUFBYSxXQUFXLHlCQUF5QjtBQWYxRDtBQWdCRSxNQUFHLENBQUM7QUFBVyxXQUFPO0FBQ3RCLFFBQU0sT0FBTSw0Q0FDUixPQUFPLE9BQUssQ0FBQyxFQUFFLFlBRFAsbUJBRVIsT0FBTyxPQUFLLDJCQUEyQixFQUFFLGVBQWU7QUFDNUQsYUFBVyxLQUFLLEtBQUs7QUFDbkIsTUFBRSxXQUFXLGFBQWEsRUFBRSxVQUFVLHVCQUF1QjtBQUFBLEVBQy9EO0FBQ0EsU0FBTztBQUNUOzs7QUR4QkEsSUFBTSxtQ0FBbUM7QUFRekMsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsV0FBVztBQUFBLEVBQ1gsU0FBUztBQUFBLElBQ1AsTUFBTTtBQUFBLElBQ04sY0FBYztBQUFBLElBQ2QsS0FBSztBQUFBLElBQ0w7QUFBQSxNQUNFLE1BQU07QUFBQSxNQUNOLFdBQVcsQ0FBQyxLQUFLLE9BQU87QUFDdEIsWUFBRyxDQUFDLEdBQUcsU0FBUyxzQkFBc0I7QUFDcEMsaUJBQU87QUFDVCxlQUFPLHNCQUFzQixHQUFHO0FBQUEsTUFDbEM7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUFBLEVBQ0EsTUFBTTtBQUFBLEVBQ04sT0FBTztBQUFBLElBQ0wsUUFBUTtBQUFBLElBQ1IsUUFBUTtBQUFBLElBQ1IsYUFBYTtBQUFBLEVBQ2Y7QUFBQSxFQUNBLFFBQVE7QUFBQSxJQUNOLFNBQVM7QUFBQSxNQUNQLEdBQUksUUFBUSxJQUFJLGlCQUFpQjtBQUFBLFFBQy9CLDJCQUEyQixRQUFRLElBQUk7QUFBQSxNQUN6QztBQUFBLElBQ0Y7QUFBQSxJQUNBLGNBQWMsQ0FBQyxpQ0FBaUMsYUFBYSxXQUFXO0FBQUEsRUFFMUU7QUFBQSxFQUNBLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMLGdCQUFnQixLQUFLLFFBQVEsa0NBQVcsMEJBQTBCO0FBQUEsTUFDbEUsS0FBSyxHQUFHLEtBQUssUUFBUSxrQ0FBVyxPQUFPLENBQUM7QUFBQSxJQUMxQztBQUFBLEVBQ0Y7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo= diff --git a/docker-compose.yml b/docker-compose.yml index eae852a..a5aacc0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -117,7 +117,6 @@ services: volumes: - ./DSL/Resql:/DSL - ./shared:/shared - - ./DSL/DatasetGenerator/output_datasets:/app/output_datasets networks: - bykstack @@ -174,7 +173,7 @@ services: cron-manager: container_name: cron-manager image: cron-manager-python:latest - user: "root" + user: "1000:1000" volumes: - ./DSL/CronManager/DSL:/DSL - ./DSL/CronManager/script:/app/scripts @@ -184,15 +183,14 @@ services: - ./datasets:/app/datasets # Direct access to datasets folder for diff identifier operations - ./grafana-configs/loki_logger.py:/app/src/vector_indexer/loki_logger.py - ./.env:/app/.env:ro - - vault-agent-token:/agent/out:ro # Mount vault token for accessing vault secrets environment: - server.port=9010 - PYTHONPATH=/app:/app/src/vector_indexer - - VAULT_ADDR=http://vault:8200 + - VAULT_AGENT_URL=http://vault-agent-cron:8203 ports: - 9010:8080 depends_on: - - vault-agent-llm + - vault-agent-cron networks: - bykstack @@ -444,10 +442,8 @@ services: - vault-data:/vault/file - ./vault/config:/vault/config:ro - ./vault/logs:/vault/logs - expose: - - "8200" networks: - - bykstack + - vault-network # Only on vault-network for security restart: unless-stopped healthcheck: test: ["CMD", "sh", "-c", "wget -q -O- http://127.0.0.1:8200/v1/sys/health || exit 0"] @@ -468,14 +464,74 @@ services: volumes: - vault-data:/vault/data - vault-agent-creds:/agent/credentials - - vault-agent-token:/agent/out + - vault-agent-gui-token:/agent/gui-token + - vault-agent-cron-token:/agent/cron-token + - vault-agent-llm-token:/agent/llm-token - ./vault-init.sh:/vault-init.sh:ro networks: - - bykstack + - vault-network # Access vault + - bykstack # Access to write agent tokens entrypoint: ["/bin/sh"] - command: ["-c", "apk add --no-cache curl jq && chmod -R 755 /agent/credentials && chmod -R 770 /agent/out && chown -R vault:vault /agent/credentials /agent/out && su vault -s /bin/sh /vault-init.sh"] + command: + - -c + - | + apk add --no-cache curl jq uuidgen openssl + # Create and set permissions for all agent directories + mkdir -p /agent/credentials /agent/gui-token /agent/cron-token /agent/llm-token /agent/out + chown -R vault:vault /agent/credentials /agent/gui-token /agent/cron-token /agent/llm-token /agent/out + chmod 755 /agent/credentials /agent/gui-token /agent/cron-token /agent/llm-token /agent/out + # Run vault initialization as vault user + su vault -s /bin/sh /vault-init.sh restart: "no" + vault-agent-gui: + image: hashicorp/vault:1.20.3 + container_name: vault-agent-gui + command: ["vault", "agent", "-config=/agent/config/gui-agent.hcl", "-log-level=info"] + depends_on: + vault-init: + condition: service_completed_successfully + cap_add: + - IPC_LOCK + volumes: + - ./vault/agents/gui/gui-agent.hcl:/agent/config/gui-agent.hcl:ro + - vault-agent-creds:/agent/credentials:ro + - vault-agent-gui-token:/agent/gui-token + networks: + - vault-network # Access vault + - bykstack # Accessible by GUI service + restart: unless-stopped + healthcheck: + test: ["CMD", "sh", "-c", "test -f /agent/gui-token/token && test -s /agent/gui-token/token"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + + vault-agent-cron: + image: hashicorp/vault:1.20.3 + container_name: vault-agent-cron + command: ["vault", "agent", "-config=/agent/config/cron-agent.hcl", "-log-level=info"] + depends_on: + vault-init: + condition: service_completed_successfully + cap_add: + - IPC_LOCK + volumes: + - ./vault/agents/cron/cron-agent.hcl:/agent/config/cron-agent.hcl:ro + - vault-agent-creds:/agent/credentials:ro + - vault-agent-cron-token:/agent/cron-token + networks: + - vault-network # Access vault + - bykstack # Accessible by CronManager service + restart: unless-stopped + healthcheck: + test: ["CMD", "sh", "-c", "test -f /agent/cron-token/token && test -s /agent/cron-token/token"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + vault-agent-llm: image: hashicorp/vault:1.20.3 container_name: vault-agent-llm @@ -488,10 +544,17 @@ services: volumes: - ./vault/agents/llm/agent.hcl:/agent/config/agent.hcl:ro - vault-agent-creds:/agent/credentials:ro - - vault-agent-token:/agent/out + - vault-agent-llm-token:/agent/llm-token networks: - - bykstack + - vault-network # Access vault + - bykstack # Accessible by LLM service restart: unless-stopped + healthcheck: + test: ["CMD", "sh", "-c", "test -f /agent/llm-token/token && test -s /agent/llm-token/token"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s # LLM Orchestration Service llm-orchestration-service: @@ -506,17 +569,15 @@ services: - .env environment: - ENVIRONMENT=production - - VAULT_ADDR=http://vault:8200 - - VAULT_TOKEN=/agent/out/token + - VAULT_ADDR=http://vault-agent-llm:8201 + # VAULT_TOKEN not set - vault-agent-llm proxy handles authentication volumes: - ./src/llm_config_module/config:/app/src/llm_config_module/config:ro - ./src/optimization/optimized_modules:/app/src/optimization/optimized_modules - llm_orchestration_logs:/app/logs - - vault-agent-token:/agent/out:ro networks: - bykstack depends_on: - - vault - vault-agent-llm healthcheck: test: ["CMD", "curl", "-f", "http://llm-orchestration-service:8100/health"] @@ -602,12 +663,20 @@ volumes: name: cron_data vault-agent-creds: name: vault-agent-creds - vault-agent-token: - name: vault-agent-token + vault-agent-gui-token: + name: vault-agent-gui-token + vault-agent-cron-token: + name: vault-agent-cron-token + vault-agent-llm-token: + name: vault-agent-llm-token opensearch-data: name: opensearch-data networks: bykstack: name: bykstack - driver: bridge \ No newline at end of file + driver: bridge + vault-network: + name: vault-network + driver: bridge + internal: true # No external access - isolated network \ No newline at end of file diff --git a/docs/VAULT_SECURITY_ARCHITECTURE.md b/docs/VAULT_SECURITY_ARCHITECTURE.md new file mode 100644 index 0000000..e241a92 --- /dev/null +++ b/docs/VAULT_SECURITY_ARCHITECTURE.md @@ -0,0 +1,1330 @@ +# Vault Security Architecture + +## Overview + +This document provides a technical deep dive into the HashiCorp Vault security architecture implemented in the RAG-Module. The design follows a defense-in-depth strategy with multiple security layers to protect sensitive credentials used by LLM providers (AWS Bedrock, Azure OpenAI) and embedding services. + +### Security Principles + +1. **Zero Trust Network**: No service has direct access to Vault server +2. **Least Privilege**: Each service gets only the minimum required permissions +3. **Defense in Depth**: Multiple security layers (network, authentication, authorization) +4. **Secure by Default**: Deny-all policies with explicit allow rules +5. **Credential Isolation**: Secrets never exposed in environment variables or logs + +--- + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Docker Network: bykstack │ +│ (Application Layer - No Direct Vault Access) │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ GUI │ │ CronManager │ │ LLM Service │ │ +│ │ (Frontend) │ │ (Worker) │ │ Orchestrator │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ │ +│ │ :8202 │ :8203 │ :8201 │ +│ ▼ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ vault-agent │ │ vault-agent │ │ vault-agent │ │ +│ │ -gui │ │ -cron │ │ -llm │ │ +│ │ (Proxy) │ │ (Proxy) │ │ (Proxy) │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ │ +│ └───────────────────┴────────────────────┘ │ +│ │ │ +└─────────────────────────────┼─────────────────────────────────────────┘ + │ + │ Secured Connection + │ +┌─────────────────────────────▼─────────────────────────────────────────┐ +│ Docker Network: vault-network │ +│ (Internal Only - No External Access) │ +│ │ +│ ┌──────────────────┐ │ +│ │ Vault Server │ │ +│ │ (Core Vault) │ │ +│ │ Port: 8200 │ │ +│ └──────────────────┘ │ +│ │ +│ - KV v2 Secrets Engine │ +│ - AppRole Auth Method │ +│ - Policy Enforcement │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Network Security & Isolation + +### Dual-Network Architecture + +The system uses two isolated Docker networks to create a security boundary: + +#### 1. **vault-network** (Internal Network) +- **Purpose**: Vault core server isolation +- **Access**: Only Vault server and Vault agents +- **Configuration**: `internal: true` (no external routing) +- **Security Benefit**: Vault server is completely unreachable from outside containers + +#### 2. **bykstack** (Application Network) +- **Purpose**: Application services communication +- **Access**: All application containers and Vault agents +- **Security Benefit**: Applications can only reach Vault agents, never Vault directly + +### Why This Matters + +``` +❌ Without Network Isolation: + App → Vault (direct access with token) + Risk: Token compromise = full Vault access + +✅ With Network Isolation: + App → Vault Agent → Vault + Benefit: Agent handles auth, app never sees token +``` + +### Port Exposure Strategy + +| Service | Port | Network | Exposed to Host | Purpose | +|---------|------|---------|-----------------|---------| +| Vault Server | 8200 | vault-network | ❌ No | Core secrets storage | +| vault-agent-gui | 8202 | bykstack | ❌ No | GUI proxy | +| vault-agent-cron | 8203 | bykstack | ❌ No | CronManager proxy | +| vault-agent-llm | 8201 | bykstack | ❌ No | LLM service proxy | + +**Security Principle**: No Vault-related ports are exposed to the host machine, preventing external attacks. + +--- + +## Authentication Layer + +### AppRole Authentication Method + +Vault uses **AppRole** authentication - a machine-oriented authentication method designed for automated workflows. + +#### How AppRole Works + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ AppRole Authentication Flow │ +└─────────────────────────────────────────────────────────────────┘ + +1. Initialization Phase (vault-init container): + + vault-init + │ + ├─► Creates AppRole: "gui-service" + ├─► Creates AppRole: "cron-manager-service" + ├─► Creates AppRole: "llm-orchestration-service" + │ + └─► Generates credentials: + - role_id (static identifier) + - secret_id (secret credential, renewable) + +2. Credential Storage: + + Credentials written to shared Docker volumes: + /agent/credentials/gui_role_id + /agent/credentials/gui_secret_id + /agent/credentials/cron_role_id + /agent/credentials/cron_secret_id + /agent/credentials/llm_role_id + /agent/credentials/llm_secret_id + +3. Vault Agent Authentication: + + vault-agent-gui + │ + ├─► Reads: role_id + secret_id + ├─► Authenticates with Vault + └─► Receives: Vault token (automatically renewed) + +4. Token Management: + + vault-agent caches token and handles: + - Automatic renewal before expiration + - Token rotation on renewal failure + - Transparent injection into API requests +``` + +### Role-Based Identity Management + +Each service gets its own isolated identity: + +1. **gui-service AppRole** + - Identity: Frontend application + - Policy: gui-policy + - Permissions: Read encryption public key only + +2. **cron-manager-service AppRole** + - Identity: Background worker/scheduler + - Policy: cron-manager-policy + - Permissions: Full CRUD on secrets + encryption key access + +3. **llm-orchestration-service AppRole** + - Identity: LLM request handler + - Policy: llm-orchestration-policy + - Permissions: Read-only access to connection credentials + +### Credential Lifecycle + +``` +Timeline: Credential Generation and Rotation + +Day 0 (Initial Setup): + vault-init: Generate role_id + secret_id + ↓ + Write to: /agent/credentials/ + ↓ + vault-agent: Authenticate with Vault + ↓ + Receive: Token (TTL: 1 hour, renewable) + +Day 0+: Automatic Token Renewal: + vault-agent: Monitor token expiration + ↓ + Before expiry: Request token renewal + ↓ + Vault: Extend token lifetime (1 hour) + ↓ + Repeat: Continuous renewal cycle + +Container Restart: + vault-init: Check if Vault is sealed + ↓ + If unsealed: Regenerate secret_id only + ↓ + vault-agent: Re-authenticate with new secret_id + ↓ + New token issued and cached +``` + +**Security Benefit**: Short-lived tokens with automatic rotation limit the damage from token compromise. + +--- + +## Authorization & Policy Model + +### Policy-Based Access Control (PBAC) + +Vault enforces authorization through **policies** - declarative rules that define what each identity can access. + +#### Policy Architecture + +``` +┌────────────────────────────────────────────────────────────────┐ +│ Vault Policy Layer │ +└────────────────────────────────────────────────────────────────┘ + +Token (issued to vault-agent-gui) + │ + ├─► Associated Policy: "gui-policy" + │ + └─► Allowed Paths: + ✅ secret/data/encryption/public_key (read, list) + ❌ secret/data/encryption/private_key (denied) + ❌ secret/data/llm/connections/* (denied) + ❌ secret/data/embeddings/connections/* (denied) + + +Token (issued to vault-agent-cron) + │ + ├─► Associated Policy: "cron-manager-policy" + │ + └─► Allowed Paths: + ✅ secret/data/llm/connections/* (create, read, update, delete, list) + ✅ secret/data/embeddings/connections/* (create, read, update, delete, list) + ✅ secret/data/encryption/public_key (read, list) + ✅ secret/data/encryption/private_key (read, list) + + +Token (issued to vault-agent-llm) + │ + ├─► Associated Policy: "llm-orchestration-policy" + │ + └─► Allowed Paths: + ✅ secret/data/llm/connections/* (read, list) + ✅ secret/data/embeddings/connections/* (read, list) + ❌ secret/data/encryption/* (explicitly denied) +``` + +### Three-Tier Policy Structure + +#### Tier 1: GUI Policy (Minimal Permissions) +**Purpose**: Allow frontend to encrypt user input + +**Permissions**: +- ✅ Read: `secret/data/encryption/public_key` +- ✅ List: `secret/metadata/encryption/public_key` + +**Denied**: +- ❌ All other paths (deny-by-default) + +**Use Case**: Frontend fetches public key to encrypt sensitive credentials (API keys, access keys) before sending to backend. + +--- + +#### Tier 2: CronManager Policy (Secret Management) +**Purpose**: Write secrets to Vault and decrypt sensitive data + +**Permissions**: +- ✅ Create/Read/Update/Delete: `secret/data/llm/connections/*` +- ✅ Create/Read/Update/Delete: `secret/data/embeddings/connections/*` +- ✅ List: `secret/metadata/llm/connections/*` +- ✅ List: `secret/metadata/embeddings/connections/*` +- ✅ Read: `secret/data/encryption/public_key` (for verification) +- ✅ Read: `secret/data/encryption/private_key` (for decryption) + +**Use Case**: Receives encrypted credentials from GUI, decrypts them using private key, stores plaintext in Vault. + +--- + +#### Tier 3: LLM Orchestration Policy (Read-Only) +**Purpose**: Retrieve credentials to make LLM API calls + +**Permissions**: +- ✅ Read: `secret/data/llm/connections/*` +- ✅ Read: `secret/data/embeddings/connections/*` +- ✅ List: `secret/metadata/llm/connections/*` +- ✅ List: `secret/metadata/embeddings/connections/*` + +**Explicitly Denied**: +- ❌ Deny: `secret/data/encryption/*` (no access to encryption keys) + +**Use Case**: Fetch AWS/Azure credentials to authenticate with LLM providers. + +--- + +### Wildcard Path Patterns + +Policies use **glob-style wildcards** (`*`) to match nested paths: + +``` +Pattern: secret/data/llm/connections/* + +Matches: + ✅ secret/data/llm/connections/azure_openai/testing/14 + ✅ secret/data/llm/connections/azure_openai/production/gpt-4o + ✅ secret/data/llm/connections/aws_bedrock/testing/42 + ✅ secret/data/llm/connections/aws_bedrock/production/claude-3-sonnet + +Does NOT match: + ❌ secret/data/embeddings/connections/... + ❌ secret/data/encryption/... +``` + +**Security Benefit**: One policy rule covers all current and future connection secrets without requiring policy updates. + +### Deny-by-Default Security + +``` +Request Flow with Policy Enforcement: + +vault-agent-llm sends request: + GET /v1/secret/data/encryption/private_key + +Vault Policy Engine: + 1. Check token's associated policies: "llm-orchestration-policy" + 2. Evaluate rules in policy: + - Path: secret/data/encryption/* + - Capability: deny + 3. Decision: ❌ DENIED + +Response: HTTP 403 Forbidden + + +vault-agent-cron sends request: + GET /v1/secret/data/encryption/private_key + +Vault Policy Engine: + 1. Check token's associated policies: "cron-manager-policy" + 2. Evaluate rules in policy: + - Path: secret/data/encryption/* + - Capability: read + 3. Decision: ✅ ALLOWED + +Response: HTTP 200 OK + secret data +``` + +--- + +## Vault Agent Architecture + +### Why Vault Agents? + +Traditional approach has security risks: + +``` +❌ Direct Vault Access (Insecure): + +App Container + │ env: VAULT_TOKEN=test_token_hvs.abc123xyz... + │ env: VAULT_ADDR=http://vault:8200 + │ + └─► Direct HTTP call to Vault + - Token in environment (logged, visible) + - Token in memory dumps + - Token in application code + - No automatic renewal +``` + +Vault Agent proxy pattern solves these issues: + +``` +✅ Vault Agent Proxy (Secure): + +App Container + │ env: VAULT_ADDR=http://vault-agent-llm:8201 + │ (NO VAULT_TOKEN variable) + │ + └─► HTTP call to Vault Agent (no token) + │ + └─► Vault Agent injects token automatically + │ + └─► Vault validates token + │ + └─► Returns secret +``` + +### Proxy Pattern Benefits + +1. **Token Isolation**: Application never sees or handles tokens +2. **Automatic Renewal**: Agent manages token lifecycle transparently +3. **Simplified Code**: No token management logic in application +4. **Audit Trail**: All requests logged through agent layer +5. **Single Point of Auth**: Centralized authentication handling + +### Three-Agent Deployment + +#### Agent 1: vault-agent-gui +``` +Configuration: + - Listens: 0.0.0.0:8202 (within bykstack network) + - Auth: AppRole (gui-service) + - Policy: gui-policy + - Token Cache: /agent/gui-token/token + +Connected Services: + - GUI (React Frontend) + +Token Lifecycle: + - Default Lease: 768h (32 days) + - Auto-renewal: Before expiration +``` + +#### Agent 2: vault-agent-cron +``` +Configuration: + - Listens: 0.0.0.0:8203 (within bykstack network) + - Auth: AppRole (cron-manager-service) + - Policy: cron-manager-policy + - Token Cache: /agent/cron-token/token + +Connected Services: + - CronManager (Python worker) + +Token Lifecycle: + - Default Lease: 768h (32 days) + - Auto-renewal: Before expiration +``` + +#### Agent 3: vault-agent-llm +``` +Configuration: + - Listens: 0.0.0.0:8201 (within bykstack network) + - Auth: AppRole (llm-orchestration-service) + - Policy: llm-orchestration-policy + - Token Cache: /agent/llm-token/token + +Connected Services: + - LLM Orchestration Service (FastAPI) + +Token Lifecycle: + - Default Lease: 1h (shorter for higher security) + - Auto-renewal: Every ~45 minutes +``` + +### Token Caching and Auto-Renewal + +``` +Vault Agent Token Management Cycle: + +┌─────────────────────────────────────────────────────────────┐ +│ Token Lifecycle │ +└─────────────────────────────────────────────────────────────┘ + +T=0: Initial Authentication + vault-agent reads credentials + │ + ├─► POST /v1/auth/approle/login + │ Body: { role_id, secret_id } + │ + └─► Receives: { token, ttl: 3600s, renewable: true } + │ + └─► Cache token in: /agent/llm-token/token + + +T=45min: Proactive Renewal (75% of TTL) + vault-agent monitors expiration + │ + ├─► POST /v1/auth/token/renew-self + │ Header: X-Vault-Token: + │ + └─► Receives: { token, ttl: 3600s } (same token, extended) + │ + └─► Update cache: /agent/llm-token/token + + +T=59min: Renewal Failed (fallback) + If renewal fails: + │ + ├─► Re-authenticate from scratch + │ POST /v1/auth/approle/login + │ + └─► New token issued and cached + + +Application Request (anytime): + App sends: GET http://vault-agent-llm:8201/v1/secret/data/llm/... + │ + ├─► vault-agent intercepts request + ├─► Injects header: X-Vault-Token: + ├─► Forwards to: http://vault:8200/v1/secret/data/llm/... + │ + └─► Returns response to application +``` + +### Transparent Authentication Injection + +When an application makes a request: + +``` +Step 1: Application Code (No Token) +-------- +import requests +response = requests.get( + "http://vault-agent-llm:8201/v1/secret/data/llm/connections/azure_openai/testing/14" +) +# Note: No X-Vault-Token header sent! + + +Step 2: Vault Agent Intercepts +-------- +vault-agent-llm receives request: + - Request headers: { User-Agent: python-requests/2.31.0 } + - No token present + + +Step 3: Agent Adds Authentication +-------- +vault-agent-llm modifies request: + - Add header: X-Vault-Token: vault_token_test + - Forward to: http://vault:8200/v1/secret/data/llm/connections/azure_openai/testing/14 + + +Step 4: Vault Validates +-------- +Vault server: + - Checks token validity + - Looks up associated policy: llm-orchestration-policy + - Evaluates path permission: secret/data/llm/connections/* → ALLOWED + - Returns secret data + + +Step 5: Agent Forwards Response +-------- +vault-agent-llm returns to application: + - Status: 200 OK + - Body: { "data": { "data": { "endpoint": "...", "api_key": "..." } } } + + +Application receives response: + - Thinks it talked directly to Vault + - Never handled or saw the token + - Transparent proxy magic! ✨ +``` + +--- + +## Secret Storage Strategy + +### KV v2 Secrets Engine + +Vault uses the **Key-Value version 2** (KV v2) secrets engine, which provides: + +1. **Versioning**: Every secret write creates a new version +2. **Audit Trail**: Track who changed what and when +3. **Rollback**: Restore previous versions if needed +4. **Soft Delete**: Deleted secrets can be recovered +5. **Metadata**: Store additional context (tags, timestamps) + +### Path Hierarchy and Organization + +``` +vault (root) +└── secret/ (KV v2 mount point) + ├── llm/ + │ └── connections/ + │ ├── azure_openai/ + │ │ ├── testing/ + │ │ │ ├── 14 → { connection_id, endpoint, api_key, deployment_name } + │ │ │ ├── 15 → { ... } + │ │ │ └── 16 → { ... } + │ │ └── production/ + │ │ ├── gpt-4o → { connection_id, endpoint, api_key, ... } + │ │ └── gpt-4o-mini → { ... } + │ └── aws_bedrock/ + │ ├── testing/ + │ │ └── 14 → { connection_id, access_key, secret_key } + │ └── production/ + │ ├── claude-3-sonnet → { ... } + │ └── claude-3-opus → { ... } + │ + ├── embeddings/ + │ └── connections/ + │ ├── azure_openai/ + │ │ └── testing/ + │ │ └── 14 → { connection_id, endpoint, api_key, model } + │ └── aws_bedrock/ + │ └── testing/ + │ └── 14 → { connection_id, access_key, secret_key, model } + │ + └── encryption/ + ├── public_key → { key: "-----BEGIN PUBLIC KEY-----...", algorithm: "RSA-OAEP", ... } + └── private_key → { key: "-----BEGIN PRIVATE KEY-----...", algorithm: "RSA-OAEP", ... } +``` + +### Path Structure Logic + +**Testing Environment:** +``` +Pattern: secret/llm/connections/{platform}/{environment}/{connection_id} +Example: secret/llm/connections/azure_openai/testing/14 + +Why connection_id? + - Multiple test configurations per platform + - Easy to create/delete during development + - Unique identifier for each test setup +``` + +**Production Environment:** +``` +Pattern: secret/llm/connections/{platform}/{environment}/{model} +Example: secret/llm/connections/azure_openai/production/gpt-4o + +Why model name? + - One canonical credential per model + - Predictable path for application lookups + - Clear naming convention +``` + +### Version Control and Audit Trail + +KV v2 automatically versions every write: + +``` +Example: Updating Azure API Key + +Version 1 (Initial): + Path: secret/data/llm/connections/azure_openai/testing/14 + Data: { + "connection_id": "14", + "endpoint": "https://xxx.openai.azure.com/", + "api_key": "old-key-abc123", + "deployment_name": "gpt-4o-deployment" + } + Metadata: { + "version": 1, + "created_time": "2026-01-08T10:30:00Z", + "created_by": "cron-manager-service" + } + + +Version 2 (After Key Rotation): + Path: secret/data/llm/connections/azure_openai/testing/14 + Data: { + "connection_id": "14", + "endpoint": "https://xxx.openai.azure.com/", + "api_key": "new-key-xyz789", ← Updated + "deployment_name": "gpt-4o-deployment" + } + Metadata: { + "version": 2, + "created_time": "2026-01-09T14:20:00Z", + "created_by": "cron-manager-service" + } + + +Accessing Versions: + - Latest: GET /v1/secret/data/llm/connections/azure_openai/testing/14 + - Version 1: GET /v1/secret/data/llm/connections/azure_openai/testing/14?version=1 + - Version 2: GET /v1/secret/data/llm/connections/azure_openai/testing/14?version=2 +``` + +**Security Benefit**: Full audit trail of credential changes with rollback capability. + +--- + +## Access Control Matrix + +### Service-to-Secret Mapping + +| Service | LLM Connections | Embedding Connections | Public Key | Private Key | Token Lookup | +|---------|-----------------|----------------------|------------|-------------|--------------| +| **GUI (Frontend)** | ❌ Denied | ❌ Denied | ✅ Read | ❌ Denied | ✅ Read | +| **CronManager** | ✅ Full CRUD | ✅ Full CRUD | ✅ Read | ✅ Read | ✅ Read | +| **LLM Service** | ✅ Read | ✅ Read | ❌ Denied | ❌ Denied | ✅ Read | + +### Permission Boundaries + +#### GUI Service +``` +✅ Allowed Actions: + - GET secret/data/encryption/public_key + - LIST secret/metadata/encryption/public_key + - GET auth/token/lookup-self (verify own token) + +❌ Denied Actions: + - Any operation on secret/data/llm/* + - Any operation on secret/data/embeddings/* + - GET secret/data/encryption/private_key + - Any write operations + +Use Case Flow: + 1. User enters API key in frontend + 2. Frontend fetches public key from Vault + 3. Frontend encrypts API key with public key + 4. Sends encrypted data to backend +``` + +#### CronManager Service +``` +✅ Allowed Actions: + - CREATE/READ/UPDATE/DELETE secret/data/llm/connections/* + - CREATE/READ/UPDATE/DELETE secret/data/embeddings/connections/* + - LIST secret/metadata/llm/connections/* + - LIST secret/metadata/embeddings/connections/* + - GET secret/data/encryption/public_key + - GET secret/data/encryption/private_key + - GET auth/token/lookup-self + +❌ Denied Actions: + - Modify encryption keys (read-only) + - Access secrets outside defined paths + +Use Case Flow: + 1. Receives encrypted credentials from frontend + 2. Fetches private key from Vault + 3. Decrypts credentials locally + 4. Stores plaintext credentials in Vault + 5. Returns success/failure to frontend +``` + +#### LLM Orchestration Service +``` +✅ Allowed Actions: + - GET secret/data/llm/connections/* + - GET secret/data/embeddings/connections/* + - LIST secret/metadata/llm/connections/* + - LIST secret/metadata/embeddings/connections/* + - GET auth/token/lookup-self + +❌ Denied Actions (Explicit Deny): + - Any operation on secret/data/encryption/* (cannot access keys) + - Any write operations on secrets + - Any access to secrets outside defined paths + +Use Case Flow: + 1. Receives LLM request from user + 2. Determines required LLM provider (AWS/Azure) + 3. Fetches connection credentials from Vault + 4. Makes authenticated API call to LLM provider + 5. Returns LLM response to user +``` + +### Least Privilege Enforcement + +``` +Scenario: LLM Service Attempts Unauthorized Access + +Request: + GET http://vault-agent-llm:8201/v1/secret/data/encryption/private_key + +Vault Decision Chain: + 1. Token extracted from request: test_token_hvs.CAESIFS1ZfMfAtwYd9LJ27A1nzg... + 2. Token lookup: Associated policy = "llm-orchestration-policy" + 3. Path evaluation: secret/data/encryption/private_key + 4. Policy rule match: + path "secret/data/encryption/*" { + capabilities = ["deny"] + } + 5. Decision: ❌ DENY + +Response: + Status: 403 Forbidden + Body: { "errors": ["permission denied"] } + +Audit Log: + [2026-01-09T10:30:00Z] path=secret/data/encryption/private_key + action=read identity=llm-orchestration-service result=denied +``` + +**Security Principle**: Even if LLM service is compromised, attacker cannot access encryption keys. + +--- + +## Initialization & Bootstrapping + +### vault-init Container Workflow + +The `vault-init` container is a one-time initialization container that sets up Vault on first deployment: + +``` +┌────────────────────────────────────────────────────────────────┐ +│ vault-init Startup Sequence │ +└────────────────────────────────────────────────────────────────┘ + +Step 1: Wait for Vault Health + └─► Retry loop: Check http://vault:8200/v1/sys/health + └─► Wait until Vault server is responsive + +Step 2: Check Vault Status + └─► Is Vault initialized? + ├─► NO → First Time Setup (Steps 3-10) + └─► YES → Subsequent Deployment (Steps 11-12) + +═══════════════════════════════════════════════════════════════════ +FIRST TIME DEPLOYMENT +═══════════════════════════════════════════════════════════════════ + +Step 3: Initialize Vault + └─► POST /v1/sys/init + └─► Receives: + - Unseal keys (5 keys, threshold 3) + - Root token + +Step 4: Unseal Vault + └─► POST /v1/sys/unseal (3 times with different keys) + └─► Vault becomes operational + +Step 5: Enable KV v2 Secrets Engine + └─► POST /v1/sys/mounts/secret + Body: { type: "kv-v2" } + +Step 6: Enable AppRole Authentication + └─► POST /v1/sys/auth/approle + Body: { type: "approle" } + +Step 7: Create Policies + └─► POST /v1/sys/policies/acl/gui-policy + └─► POST /v1/sys/policies/acl/cron-manager-policy + └─► POST /v1/sys/policies/acl/llm-orchestration-policy + +Step 8: Create AppRoles + └─► POST /v1/auth/approle/role/gui-service + └─► POST /v1/auth/approle/role/cron-manager-service + └─► POST /v1/auth/approle/role/llm-orchestration-service + +Step 9: Generate Credentials + └─► GET /v1/auth/approle/role/gui-service/role-id + └─► POST /v1/auth/approle/role/gui-service/secret-id + └─► Repeat for cron-manager and llm-orchestration + +Step 10: Generate RSA Keypair + └─► openssl genrsa -out private_key.pem 2048 + └─► openssl rsa -pubout -in private_key.pem -out public_key.pem + └─► POST /v1/secret/data/encryption/public_key + └─► POST /v1/secret/data/encryption/private_key + +Step 11: Write Credentials to Shared Volumes + └─► /agent/credentials/gui_role_id + └─► /agent/credentials/gui_secret_id + └─► /agent/credentials/cron_role_id + └─► /agent/credentials/cron_secret_id + └─► /agent/credentials/llm_role_id + └─► /agent/credentials/llm_secret_id + +═══════════════════════════════════════════════════════════════════ +SUBSEQUENT DEPLOYMENT (Container Restart) +═══════════════════════════════════════════════════════════════════ + +Step 12: Check Vault Seal Status + └─► GET /v1/sys/seal-status + └─► If unsealed: Skip unseal steps + +Step 13: Regenerate Secret IDs Only + └─► POST /v1/auth/approle/role/gui-service/secret-id + └─► POST /v1/auth/approle/role/cron-manager-service/secret-id + └─► POST /v1/auth/approle/role/llm-orchestration-service/secret-id + └─► Write new secret_ids to /agent/credentials/ + +Note: role_ids remain unchanged (static identifiers) +Note: Existing secrets and policies preserved +Note: RSA keypair NOT regenerated (preserved) + +═══════════════════════════════════════════════════════════════════ +COMPLETION +═══════════════════════════════════════════════════════════════════ + +Step 14: Set File Permissions + └─► chown vault:vault /agent/credentials/* + └─► chmod 644 /agent/credentials/* + +Step 15: Exit Successfully + └─► Container stops with exit code 0 + └─► vault-agent containers start (depends_on: service_completed_successfully) +``` + +### Unseal Key Management + +Vault starts in a **sealed** state and must be unsealed before use: + +``` +Sealed State: + - Vault knows where data is stored + - Vault cannot decrypt data (encryption key unknown) + - All API operations return "Vault is sealed" + +Unsealing Process: + - Vault initialized with Shamir's Secret Sharing + - Master key split into 5 parts (configurable) + - Threshold: Any 3 of 5 keys can unseal + - Each unseal key submitted separately + +Security Model: + ✅ No single person can unseal Vault alone + ✅ Compromise of 2 keys is insufficient + ✅ Distributed trust across operators + +Current Implementation: + - All 5 unseal keys stored in vault-data volume + - Suitable for development/testing + - Production: Use Vault Auto-Unseal with AWS KMS, Azure Key Vault, etc. +``` + +**Security Trade-off**: Current setup prioritizes ease of deployment over maximum security. For production, implement auto-unseal with cloud HSM. + +### Root Token Handling + +The root token has unlimited access to Vault: + +``` +Root Token Lifecycle: + +1. Generation: + - Created during vault init + - Full superuser permissions + - Never expires + +2. Current Usage: + - Used by vault-init script only + - Performs initial setup tasks + - Not stored in application containers + +3. Security Best Practice: + - Revoke root token after initialization + - Use admin policies with limited scope instead + - Regenerate root token only for emergency recovery + +4. Production Recommendation: + - Revoke: vault token revoke + - Recreate if needed: vault operator generate-root +``` + +### Credential File Security + +``` +File Permissions on Shared Volume: + +/agent/credentials/ +├── gui_role_id (644 - readable by all agents) +├── gui_secret_id (644 - readable by all agents) +├── cron_role_id (644) +├── cron_secret_id (644) +├── llm_role_id (644) +└── llm_secret_id (644) + +Owner: vault:vault (UID 100, GID 1000) +Container Access: Read-only mounts in agent containers + +Security Considerations: + ✅ Files isolated within Docker volumes (not host filesystem) + ✅ Agent containers mount as read-only + ✅ Only vault-init has write access + ❌ All agents can read all credentials (volume-level isolation only) + +Improvement Opportunity: + - Use separate volumes per agent + - Mount only relevant credentials to each agent + - Reduces blast radius of container compromise +``` + +--- + +## Security Best Practices Implemented + +### 1. No Direct Vault Access from Applications + +``` +✅ Implemented Pattern: + +Application Container + └─► Vault Agent Proxy (same network) + └─► Vault Server (isolated network) + +Benefits: + - Application never handles tokens + - Token rotation transparent to app + - Reduced attack surface + - Centralized authentication audit +``` + +### 2. Token Environment Variable Isolation + +``` +✅ Implemented Pattern: + +docker-compose.yml (LLM Service): + environment: + - VAULT_ADDR=http://vault-agent-llm:8201 + # NO VAULT_TOKEN variable + +Why This Matters: + - Tokens not visible in `docker inspect` + - Not logged in container startup + - Not passed to child processes + - Prevents accidental leaks +``` + +### 3. Credential File Permissions + +``` +✅ Implemented Pattern: + +vault-init sets permissions: + chown vault:vault /agent/credentials/* + chmod 644 /agent/credentials/* + +Vault agents run as: + user: vault (UID 100) + +Why This Matters: + - Non-root user execution + - Principle of least privilege + - Reduced container escape impact +``` + +### 4. Container User Restrictions + +``` +✅ Implemented Pattern: + +cron-manager: + user: "1000:1000" # Non-root user + +vault containers: + user: vault (implicit) + +Benefits: + - Limits filesystem access + - Prevents privilege escalation + - Reduces blast radius of exploits +``` + +### 5. Health Check Strategies + +``` +✅ Implemented Pattern: + +Vault Server Health Check: + test: wget -q -O- http://127.0.0.1:8200/v1/sys/health + interval: 5s + retries: 20 + start_period: 10s + +Vault Agent Health Check: + test: test -f /agent/llm-token/token && test -s /agent/llm-token/token + interval: 10s + retries: 3 + start_period: 5s + +Benefits: + - Automated service recovery + - Dependency ordering (agents wait for Vault) + - Token presence validation + - Monitoring integration ready +``` + +--- + +## Operational Security + +### Container Restart Scenarios + +#### Scenario 1: Vault Server Restart + +``` +Event: docker-compose restart vault + +Impact: + ✅ Vault data persists (vault-data volume) + ✅ Vault automatically unseals (unseal keys in volume) + ✅ Policies and secrets intact + ✅ AppRole configurations intact + +Agent Behavior: + - Existing tokens remain valid + - Agents reconnect automatically + - No re-authentication needed (unless token expired) + +Downtime: + - ~5-10 seconds (health check dependent) +``` + +#### Scenario 2: Vault Agent Restart + +``` +Event: docker-compose restart vault-agent-llm + +Impact: + ✅ Credentials still available (vault-agent-creds volume) + ✅ Agent re-authenticates automatically + ✅ New token issued and cached + +Application Behavior: + - Brief connection failure during restart + - Retry logic handles transient errors + - No manual intervention required + +Downtime: + - ~3-5 seconds (agent startup time) +``` + +#### Scenario 3: Application Container Restart + +``` +Event: docker-compose restart llm-orchestration-service + +Impact: + ✅ No Vault changes needed + ✅ Vault agent still running + ✅ Tokens still valid + +Application Behavior: + - Reconnects to vault-agent-llm:8201 + - No authentication logic in app + - Immediate secret access + +Downtime: + - Application startup time only +``` + +#### Scenario 4: Full System Restart + +``` +Event: docker-compose down && docker-compose up + +Startup Order: + 1. vault (health check: vault ready) + 2. vault-init (runs setup, exits) + 3. vault-agent-* (wait for init completion) + 4. Applications (wait for agents) + +vault-init Behavior: + - Detects Vault already initialized + - Skips initialization steps + - Regenerates secret_ids only + - Updates credential files + +Result: + ✅ All services start with fresh credentials + ✅ Existing secrets preserved + ✅ No manual intervention needed +``` + +### Token Regeneration Strategy + +``` +Current Implementation: + +1. On Every Container Restart: + └─► vault-init regenerates secret_ids + └─► Vault agents get new tokens + └─► Old tokens remain valid until expiration + +2. Token Lifecycle: + └─► Issue: vault-agent authenticates + └─► Use: Application makes requests + └─► Renew: vault-agent extends TTL + └─► Expire: Automatic renewal failed + └─► Re-issue: vault-agent re-authenticates + +3. Security Benefits: + ✅ Short-lived tokens (1 hour for LLM, 32 days for others) + ✅ Automatic rotation on agent restart + ✅ No manual token management + ✅ Compromised tokens have limited lifetime +``` + +### Audit Logging Capabilities + +Vault provides comprehensive audit logging (not currently enabled in configuration): + +``` +Enabling Audit Logs: + +vault audit enable file file_path=/vault/logs/audit.log + +Logged Information: + - Timestamp of request + - Client identity (AppRole, token) + - Request path and method + - Success or failure + - Policy evaluation result + - Response data (hashed for secrets) + +Example Audit Entry: +{ + "time": "2026-01-09T10:30:00.123Z", + "type": "response", + "auth": { + "entity_id": "llm-orchestration-service", + "policies": ["llm-orchestration-policy"] + }, + "request": { + "path": "secret/data/llm/connections/azure_openai/testing/14", + "operation": "read" + }, + "response": { + "status": 200 + } +} + +Use Cases: + - Security incident investigation + - Compliance reporting + - Anomaly detection + - Access pattern analysis +``` + +### Monitoring Integration Points + +``` +Recommended Monitoring Metrics: + +1. Vault Health: + - Endpoint: http://vault:8200/v1/sys/health + - Metrics: sealed status, initialized status + +2. Token Usage: + - Endpoint: http://vault:8200/v1/auth/token/lookup-self + - Metrics: TTL remaining, creation time, policies + +3. Secret Access Patterns: + - Source: Audit logs + - Metrics: Access frequency, denied requests, unique clients + +4. Agent Health: + - Endpoint: Container health checks + - Metrics: Token file presence, agent uptime + +5. Application Errors: + - Source: Application logs + - Metrics: Vault connection failures, 403 errors, timeouts + +Integration with Grafana: + - Loki for log aggregation + - Prometheus for metrics (add vault exporter) + - Alertmanager for notifications +``` + +--- + +## Architecture Summary + +### Security Layers + +``` +┌──────────────────────────────────────────────────────────────┐ +│ Layer 1: Network Isolation │ +│ - Vault on internal network only │ +│ - No external routing │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Layer 2: Authentication (AppRole) │ +│ - Machine identities │ +│ - Role-based credentials │ +│ - Short-lived tokens │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Layer 3: Authorization (Policies) │ +│ - Path-based access control │ +│ - Least privilege enforcement │ +│ - Explicit deny rules │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Layer 4: Proxy Abstraction (Vault Agents) │ +│ - Token isolation │ +│ - Automatic renewal │ +│ - Transparent authentication │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Layer 5: Application Layer │ +│ - No token handling │ +│ - Simple HTTP calls │ +│ - Automatic retry logic │ +└──────────────────────────────────────────────────────────────┘ +``` + +### Defense in Depth + +``` +Attack Scenario: Compromised LLM Service Container + +Attacker Capabilities: + ❌ Cannot access Vault directly (network isolation) + ❌ Cannot read other service tokens (volume isolation) + ❌ Cannot access encryption keys (policy denial) + ✅ Can read LLM connection credentials (authorized) + +Blast Radius: + - Limited to LLM/embedding connection secrets + - Cannot modify secrets (read-only policy) + - Cannot access private encryption key + - Cannot pivot to other services + +Mitigation: + - Rotate compromised credentials immediately + - Revoke vault-agent-llm token + - Regenerate secret_id for llm-orchestration-service + - Restart llm-orchestration-service container +``` + +--- + +## Conclusion + +The Vault security architecture implements industry best practices for secrets management: + +✅ **Network Segmentation**: Vault isolated on internal network +✅ **Strong Authentication**: AppRole with renewable tokens +✅ **Granular Authorization**: Path-based policies with least privilege +✅ **Proxy Pattern**: Applications never handle tokens directly +✅ **Automated Operations**: Self-healing agents and token renewal +✅ **Audit Capability**: Full request logging available +✅ **Defense in Depth**: Multiple security layers prevent single point of failure + +This architecture provides a secure foundation for managing sensitive credentials in the RAG-Module while maintaining operational simplicity and developer productivity. diff --git a/src/llm_orchestrator_config/vault/vault_client.py b/src/llm_orchestrator_config/vault/vault_client.py index 3616940..c7dfe5c 100644 --- a/src/llm_orchestrator_config/vault/vault_client.py +++ b/src/llm_orchestrator_config/vault/vault_client.py @@ -6,6 +6,7 @@ from typing import Optional, Dict, Any, cast from loguru import logger import hvac +from hvac.exceptions import InvalidPath, Forbidden from llm_orchestrator_config.vault.exceptions import ( VaultConnectionError, @@ -20,9 +21,10 @@ def get_vault_client( vault_url: Optional[str] = None, - token_path: str = "/agent/out/token", + token_path: str = "/agent/llm-token/token", mount_point: str = "secret", timeout: int = 10, + use_token_file: bool = True, ) -> "VaultAgentClient": """Get or create singleton VaultAgentClient instance. @@ -31,9 +33,10 @@ def get_vault_client( Args: vault_url: Vault server URL (defaults to VAULT_ADDR env var) - token_path: Path to Vault Agent token file + token_path: Path to Vault Agent token file (only used if use_token_file=True) mount_point: KV v2 mount point timeout: Request timeout in seconds + use_token_file: Whether to read token from file (False when using vault agent proxy) Returns: Singleton VaultAgentClient instance @@ -48,6 +51,7 @@ def get_vault_client( token_path=token_path, mount_point=mount_point, timeout=timeout, + use_token_file=use_token_file, ) logger.info("Created singleton VaultAgentClient instance") @@ -60,22 +64,32 @@ class VaultAgentClient: def __init__( self, vault_url: Optional[str] = None, - token_path: str = "/agent/out/token", + token_path: str = "/agent/llm-token/token", mount_point: str = "secret", timeout: int = 10, - ): + use_token_file: bool = True, + ) -> None: """Initialize Vault Agent client. Args: vault_url: Vault server URL (defaults to VAULT_ADDR env var) - token_path: Path to Vault Agent token file + token_path: Path to Vault Agent token file (only used if use_token_file=True) mount_point: KV v2 mount point timeout: Request timeout in seconds + use_token_file: Whether to read token from file (False when using vault agent proxy) """ self.vault_url = vault_url or os.getenv("VAULT_ADDR", "http://vault:8200") self.token_path = Path(token_path) self.mount_point = mount_point self.timeout = timeout + self.use_token_file = use_token_file + + # Auto-detect proxy mode: if URL points to vault-agent, don't use token file + if "vault-agent" in self.vault_url.lower(): + self.use_token_file = False + logger.info( + "Detected vault agent proxy in URL, token authentication will be handled by proxy" + ) # Initialize hvac client self.client = hvac.Client( @@ -83,8 +97,11 @@ def __init__( timeout=timeout, ) - # Load token from Vault Agent - self._load_token() + # Load token from file only if not using proxy + if self.use_token_file: + self._load_token() + else: + logger.info("Using vault agent proxy - no token file required") logger.info(f"Vault Agent client initialized: {self.vault_url}") @@ -106,7 +123,7 @@ def _load_token(self) -> None: if not token: raise VaultTokenError("Vault Agent token file is empty") - # Log token info for debugging (first and last 4 chars only for security) + # Log token info for debugging (first and last 4 chars only) token_preview = f"{token[:4]}...{token[-4:]}" if len(token) > 8 else "****" logger.debug(f"Loaded token: {token_preview} (length: {len(token)})") @@ -114,7 +131,7 @@ def _load_token(self) -> None: logger.debug("Vault Agent token loaded successfully") except (OSError, IOError) as e: - raise VaultTokenError(f"Failed to read Vault Agent token: {e}") + raise VaultTokenError(f"Failed to read Vault Agent token: {e}") from e def is_authenticated(self) -> bool: """Check if client is authenticated with Vault. @@ -123,7 +140,15 @@ def is_authenticated(self) -> bool: True if authenticated, False otherwise """ try: - # Check if we have a token + # If using proxy mode, skip token checks + if not self.use_token_file: + logger.debug( + "Using vault agent proxy - skipping token authentication check" + ) + # Just verify vault is accessible + return self.is_vault_available() + + # Check token is available if not hasattr(self.client, "token") or not self.client.token: logger.debug("No token set on client") return False @@ -169,9 +194,7 @@ def is_vault_available(self) -> bool: # Try to get additional details from response body if available try: - if hasattr(response, "json") and callable( - getattr(response, "json") - ): + if hasattr(response, "json") and callable(response.json): health_data = response.json() logger.debug(f"Vault health details: {health_data}") except Exception as e: @@ -233,14 +256,14 @@ def get_secret(self, path: str) -> Optional[Dict[str, Any]]: logger.debug(f"Secret not found at path: {path}") return None - except hvac.exceptions.InvalidPath: + except InvalidPath: logger.debug(f"Secret not found at path: {path}") return None - except hvac.exceptions.Forbidden as e: - raise VaultSecretError(f"Access denied to secret path {path}: {e}") + except Forbidden as e: + raise VaultSecretError(f"Access denied to secret path {path}: {e}") from e except Exception as e: logger.error(f"Error retrieving secret from path {path}: {e}") - raise VaultSecretError(f"Failed to retrieve secret: {e}") + raise VaultSecretError(f"Failed to retrieve secret: {e}") from e def list_secrets(self, path: str) -> Optional[list[str]]: """List secrets at the given path. @@ -278,12 +301,12 @@ def list_secrets(self, path: str) -> Optional[list[str]]: logger.debug(f"No secrets found at path: {path}") return None - except hvac.exceptions.InvalidPath: + except InvalidPath: logger.debug(f"Path not found: {path}") return None except Exception as e: logger.error(f"Error listing secrets at path {path}: {e}") - raise VaultSecretError(f"Failed to list secrets: {e}") + raise VaultSecretError(f"Failed to list secrets: {e}") from e def refresh_token(self) -> bool: """Refresh token from Vault Agent. @@ -296,4 +319,4 @@ def refresh_token(self) -> bool: return self.is_authenticated() except Exception as e: logger.error(f"Failed to refresh Vault token: {e}") - return False + return False \ No newline at end of file diff --git a/vault-init.sh b/vault-init.sh index cd36e2d..63db07a 100644 --- a/vault-init.sh +++ b/vault-init.sh @@ -67,23 +67,70 @@ if [ ! -f "$INIT_FLAG" ]; then --header='Content-Type: application/json' \ "$VAULT_ADDR/v1/sys/auth/approle" >/dev/null 2>&1 || echo "AppRole already enabled" - # Create policy - echo "Creating llm-orchestration policy..." - POLICY='path "secret/metadata/llm/*" { capabilities = ["list", "delete"] } -path "secret/data/llm/*" { capabilities = ["create", "read", "update", "delete"] } -path "auth/token/lookup-self" { capabilities = ["read"] } -path "secret/metadata/embeddings/*" { capabilities = ["list", "delete"] } -path "secret/data/embeddings/*" { capabilities = ["create", "read", "update", "delete"] }' + # Create GUI policy - Read public encryption key only + echo "Creating gui-policy..." + GUI_POLICY='path "secret/data/encryption/public_key" { capabilities = ["read"] } +path "secret/metadata/encryption/public_key" { capabilities = ["read"] } +path "secret/data/encryption/private_key" { capabilities = ["deny"] } +path "secret/data/llm/*" { capabilities = ["deny"] } +path "secret/data/embeddings/*" { capabilities = ["deny"] }' - POLICY_JSON=$(echo "$POLICY" | jq -Rs '{"policy":.}') - wget -q -O- --post-data="$POLICY_JSON" \ + GUI_POLICY_JSON=$(echo "$GUI_POLICY" | jq -Rs '{"policy":.}') + wget -q -O- --post-data="$GUI_POLICY_JSON" \ --header="X-Vault-Token: $ROOT_TOKEN" \ --header='Content-Type: application/json' \ - "$VAULT_ADDR/v1/sys/policies/acl/llm-orchestration" >/dev/null + "$VAULT_ADDR/v1/sys/policies/acl/gui-policy" >/dev/null - # Create AppRole + # Create CronManager policy - Read encryption keys + Write LLM/Embedding secrets + echo "Creating cron-manager-policy..." + CRON_POLICY='path "secret/data/encryption/public_key" { capabilities = ["read"] } +path "secret/metadata/encryption/public_key" { capabilities = ["read"] } +path "secret/data/encryption/private_key" { capabilities = ["read"] } +path "secret/metadata/encryption/private_key" { capabilities = ["read"] } +path "secret/data/llm/connections/*" { capabilities = ["create", "read", "update", "delete"] } +path "secret/metadata/llm/connections/*" { capabilities = ["read", "list", "delete"] } +path "secret/data/embeddings/connections/*" { capabilities = ["create", "read", "update", "delete"] } +path "secret/metadata/embeddings/connections/*" { capabilities = ["read", "list", "delete"] } +path "auth/token/lookup-self" { capabilities = ["read"] }' + + CRON_POLICY_JSON=$(echo "$CRON_POLICY" | jq -Rs '{"policy":.}') + wget -q -O- --post-data="$CRON_POLICY_JSON" \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + --header='Content-Type: application/json' \ + "$VAULT_ADDR/v1/sys/policies/acl/cron-manager-policy" >/dev/null + + # Create LLM Orchestration policy - Read LLM/Embedding secrets only + echo "Creating llm-orchestration-policy..." + LLM_POLICY='path "secret/data/llm/connections/*" { capabilities = ["read", "list"] } +path "secret/metadata/llm/connections/*" { capabilities = ["read", "list"] } +path "secret/data/embeddings/connections/*" { capabilities = ["read", "list"] } +path "secret/metadata/embeddings/connections/*" { capabilities = ["read", "list"] } +path "secret/data/encryption/*" { capabilities = ["deny"] } +path "auth/token/lookup-self" { capabilities = ["read"] }' + + LLM_POLICY_JSON=$(echo "$LLM_POLICY" | jq -Rs '{"policy":.}') + wget -q -O- --post-data="$LLM_POLICY_JSON" \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + --header='Content-Type: application/json' \ + "$VAULT_ADDR/v1/sys/policies/acl/llm-orchestration-policy" >/dev/null + + # Create GUI AppRole + echo "Creating gui-service AppRole..." + wget -q -O- --post-data='{"token_policies":["gui-policy"],"token_no_default_policy":true,"token_ttl":"15m","token_max_ttl":"1h","secret_id_ttl":"24h","secret_id_num_uses":0,"bind_secret_id":true}' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + --header='Content-Type: application/json' \ + "$VAULT_ADDR/v1/auth/approle/role/gui-service" >/dev/null + + # Create CronManager AppRole + echo "Creating cron-manager-service AppRole..." + wget -q -O- --post-data='{"token_policies":["cron-manager-policy"],"token_no_default_policy":true,"token_ttl":"30m","token_max_ttl":"8h","secret_id_ttl":"24h","secret_id_num_uses":0,"bind_secret_id":true}' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + --header='Content-Type: application/json' \ + "$VAULT_ADDR/v1/auth/approle/role/cron-manager-service" >/dev/null + + # Create LLM Orchestration AppRole echo "Creating llm-orchestration-service AppRole..." - wget -q -O- --post-data='{"token_policies":["llm-orchestration"],"token_no_default_policy":true,"token_ttl":"1h","token_max_ttl":"24h","secret_id_ttl":"24h","secret_id_num_uses":0,"bind_secret_id":true}' \ + wget -q -O- --post-data='{"token_policies":["llm-orchestration-policy"],"token_no_default_policy":true,"token_ttl":"1h","token_max_ttl":"24h","secret_id_ttl":"24h","secret_id_num_uses":0,"bind_secret_id":true}' \ --header="X-Vault-Token: $ROOT_TOKEN" \ --header='Content-Type: application/json' \ "$VAULT_ADDR/v1/auth/approle/role/llm-orchestration-service" >/dev/null @@ -91,27 +138,108 @@ path "secret/data/embeddings/*" { capabilities = ["create", "read", "update", "d # Ensure credentials directory exists mkdir -p /agent/credentials - # Get role_id - echo "Getting role_id..." - ROLE_ID=$(wget -q -O- \ + # Get GUI credentials + echo "Getting GUI credentials..." + GUI_ROLE_ID=$(wget -q -O- \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/gui-service/role-id" | \ + grep -o '"role_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$GUI_ROLE_ID" > /agent/credentials/gui_role_id + + GUI_SECRET_ID=$(wget -q -O- --post-data='' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/gui-service/secret-id" | \ + grep -o '"secret_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$GUI_SECRET_ID" > /agent/credentials/gui_secret_id + + # Get CronManager credentials + echo "Getting CronManager credentials..." + CRON_ROLE_ID=$(wget -q -O- \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/cron-manager-service/role-id" | \ + grep -o '"role_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$CRON_ROLE_ID" > /agent/credentials/cron_role_id + + CRON_SECRET_ID=$(wget -q -O- --post-data='' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/cron-manager-service/secret-id" | \ + grep -o '"secret_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$CRON_SECRET_ID" > /agent/credentials/cron_secret_id + + # Get LLM Orchestration credentials + echo "Getting LLM Orchestration credentials..." + LLM_ROLE_ID=$(wget -q -O- \ --header="X-Vault-Token: $ROOT_TOKEN" \ "$VAULT_ADDR/v1/auth/approle/role/llm-orchestration-service/role-id" | \ grep -o '"role_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') - echo "$ROLE_ID" > /agent/credentials/role_id + echo "$LLM_ROLE_ID" > /agent/credentials/llm_role_id - # Generate secret_id - echo "Generating secret_id..." - SECRET_ID=$(wget -q -O- --post-data='' \ + LLM_SECRET_ID=$(wget -q -O- --post-data='' \ --header="X-Vault-Token: $ROOT_TOKEN" \ "$VAULT_ADDR/v1/auth/approle/role/llm-orchestration-service/secret-id" | \ grep -o '"secret_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') - echo "$SECRET_ID" > /agent/credentials/secret_id + echo "$LLM_SECRET_ID" > /agent/credentials/llm_secret_id + + # Set secure permissions + chmod 640 /agent/credentials/*_role_id + chmod 640 /agent/credentials/*_secret_id + + # Generate RSA keypair for credential encryption/decryption + echo "Generating RSA keypair for encryption..." + + # Create temp directory for key generation + TEMP_KEY_DIR="/tmp/vault-keys-$$" + mkdir -p "$TEMP_KEY_DIR" + + # Generate private key (RSA-2048) + if ! openssl genrsa -out "$TEMP_KEY_DIR/private.pem" 2048 2>/dev/null; then + echo "ERROR: Failed to generate private key" + exit 1 + fi + + # Extract public key from private key + if ! openssl rsa -in "$TEMP_KEY_DIR/private.pem" -pubout -out "$TEMP_KEY_DIR/public.pem" 2>/dev/null; then + echo "ERROR: Failed to extract public key" + exit 1 + fi + + echo "Keys generated successfully" + + # Read keys and escape for JSON + PRIVATE_KEY=$(cat "$TEMP_KEY_DIR/private.pem" | sed ':a;N;$!ba;s/\n/\\n/g') + PUBLIC_KEY=$(cat "$TEMP_KEY_DIR/public.pem" | sed ':a;N;$!ba;s/\n/\\n/g') + CREATED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ) + KEY_ID="rsa-keypair-$(date +%s)" + + # Store public key in Vault + echo "Storing public key in Vault..." + wget -q -O- --post-data='{"data":{"key":"'"$PUBLIC_KEY"'","algorithm":"RSA-OAEP","key_size":2048,"key_id":"'"$KEY_ID"'","created_at":"'"$CREATED_AT"'"}}' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + --header='Content-Type: application/json' \ + "$VAULT_ADDR/v1/secret/data/encryption/public_key" >/dev/null - chmod 644 /agent/credentials/role_id /agent/credentials/secret_id + # Store private key in Vault + echo "Storing private key in Vault..." + wget -q -O- --post-data='{"data":{"key":"'"$PRIVATE_KEY"'","algorithm":"RSA-OAEP","key_size":2048,"key_id":"'"$KEY_ID"'","created_at":"'"$CREATED_AT"'"}}' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + --header='Content-Type: application/json' \ + "$VAULT_ADDR/v1/secret/data/encryption/private_key" >/dev/null + + # Clean up temporary files + rm -rf "$TEMP_KEY_DIR" + echo "RSA keypair generated and stored successfully" + + # Store test LLM credentials for testing + echo "Creating test LLM credentials..." + wget -q -O- --post-data='{"data":{"access_key":"TEST_AWS_ACCESS_KEY","secret_key":"TEST_AWS_SECRET_KEY","environment":"production","model":"claude-3"}}' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + --header='Content-Type: application/json' \ + "$VAULT_ADDR/v1/secret/data/llm/connections/aws_bedrock/production/claude-3" >/dev/null # Mark as initialized touch "$INIT_FLAG" echo "=== First time setup complete ===" + else echo "=== SUBSEQUENT DEPLOYMENT ===" @@ -141,36 +269,71 @@ else sleep 2 echo "Vault unsealed" - - # Get root token - ROOT_TOKEN=$(grep -o '"root_token":"[^"]*"' "$UNSEAL_KEYS_FILE" | cut -d':' -f2 | tr -d '"') - export VAULT_TOKEN="$ROOT_TOKEN" - - # Ensure credentials directory exists - mkdir -p /agent/credentials - - # Regenerate secret_id after unseal - echo "Regenerating secret_id..." - SECRET_ID=$(wget -q -O- --post-data='' \ - --header="X-Vault-Token: $ROOT_TOKEN" \ - "$VAULT_ADDR/v1/auth/approle/role/llm-orchestration-service/secret-id" | \ - grep -o '"secret_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') - echo "$SECRET_ID" > /agent/credentials/secret_id - chmod 644 /agent/credentials/secret_id - - # Ensure role_id exists - if [ ! -f /agent/credentials/role_id ]; then - echo "Copying role_id..." - mkdir -p /agent/credentials - ROLE_ID=$(wget -q -O- \ - --header="X-Vault-Token: $ROOT_TOKEN" \ - "$VAULT_ADDR/v1/auth/approle/role/llm-orchestration-service/role-id" | \ - grep -o '"role_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') - echo "$ROLE_ID" > /agent/credentials/role_id - chmod 644 /agent/credentials/role_id - fi else - echo "Vault is unsealed. No action needed." + echo "Vault is already unsealed" + fi + + # Get root token + ROOT_TOKEN=$(grep -o '"root_token":"[^"]*"' "$UNSEAL_KEYS_FILE" | cut -d':' -f2 | tr -d '"') + export VAULT_TOKEN="$ROOT_TOKEN" + + # Ensure credentials directory exists + mkdir -p /agent/credentials + + # Always regenerate all secret_ids on restart + echo "Regenerating GUI secret_id..." + GUI_SECRET_ID=$(wget -q -O- --post-data='' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/gui-service/secret-id" | \ + grep -o '"secret_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$GUI_SECRET_ID" > /agent/credentials/gui_secret_id + + echo "Regenerating CronManager secret_id..." + CRON_SECRET_ID=$(wget -q -O- --post-data='' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/cron-manager-service/secret-id" | \ + grep -o '"secret_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$CRON_SECRET_ID" > /agent/credentials/cron_secret_id + + echo "Regenerating LLM secret_id..." + LLM_SECRET_ID=$(wget -q -O- --post-data='' \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/llm-orchestration-service/secret-id" | \ + grep -o '"secret_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$LLM_SECRET_ID" > /agent/credentials/llm_secret_id + + # Set permissions + chmod 640 /agent/credentials/*_secret_id + + # Ensure role_ids exist + if [ ! -f /agent/credentials/gui_role_id ]; then + echo "Copying GUI role_id..." + GUI_ROLE_ID=$(wget -q -O- \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/gui-service/role-id" | \ + grep -o '"role_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$GUI_ROLE_ID" > /agent/credentials/gui_role_id + chmod 640 /agent/credentials/gui_role_id + fi + + if [ ! -f /agent/credentials/cron_role_id ]; then + echo "Copying CronManager role_id..." + CRON_ROLE_ID=$(wget -q -O- \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/cron-manager-service/role-id" | \ + grep -o '"role_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$CRON_ROLE_ID" > /agent/credentials/cron_role_id + chmod 640 /agent/credentials/cron_role_id + fi + + if [ ! -f /agent/credentials/llm_role_id ]; then + echo "Copying LLM role_id..." + LLM_ROLE_ID=$(wget -q -O- \ + --header="X-Vault-Token: $ROOT_TOKEN" \ + "$VAULT_ADDR/v1/auth/approle/role/llm-orchestration-service/role-id" | \ + grep -o '"role_id":"[^"]*"' | cut -d':' -f2 | tr -d '"') + echo "$LLM_ROLE_ID" > /agent/credentials/llm_role_id + chmod 640 /agent/credentials/llm_role_id fi fi diff --git a/vault/agents/cron/cron-agent.hcl b/vault/agents/cron/cron-agent.hcl new file mode 100644 index 0000000..f2db227 --- /dev/null +++ b/vault/agents/cron/cron-agent.hcl @@ -0,0 +1,47 @@ +# Vault Agent Configuration for CronManager Service +# This agent provides CronManager with access to encryption keys and write access to secrets + +vault { + address = "http://vault:8200" + retry { + num_retries = 5 + } +} + +# Auto-authentication using AppRole +auto_auth { + method "approle" { + mount_path = "auth/approle" + config = { + role_id_file_path = "/agent/credentials/cron_role_id" + secret_id_file_path = "/agent/credentials/cron_secret_id" + remove_secret_id_file_after_reading = false + } + } + + # Write token to file for CronManager service to use + sink "file" { + config = { + path = "/agent/cron-token/token" + mode = 0640 + } + } +} + +# Caching configuration +cache { + default_lease_duration = "30m" # Medium TTL for CronManager +} + +# API proxy listener for CronManager service +listener "tcp" { + address = "0.0.0.0:8203" + tls_disable = true +} + +# API proxy configuration +api_proxy { + use_auto_auth_token = true + enforce_consistency = "always" + when_inconsistent = "forward" +} diff --git a/vault/agents/gui/gui-agent.hcl b/vault/agents/gui/gui-agent.hcl new file mode 100644 index 0000000..a28db87 --- /dev/null +++ b/vault/agents/gui/gui-agent.hcl @@ -0,0 +1,47 @@ +# Vault Agent Configuration for GUI Service +# This agent provides GUI with access to public encryption key only + +vault { + address = "http://vault:8200" + retry { + num_retries = 5 + } +} + +# Auto-authentication using AppRole +auto_auth { + method "approle" { + mount_path = "auth/approle" + config = { + role_id_file_path = "/agent/credentials/gui_role_id" + secret_id_file_path = "/agent/credentials/gui_secret_id" + remove_secret_id_file_after_reading = false + } + } + + # Write token to file for GUI service to use + sink "file" { + config = { + path = "/agent/gui-token/token" + mode = 0640 + } + } +} + +# Caching configuration +cache { + default_lease_duration = "15m" # Short-lived tokens for GUI +} + +# API proxy listener for GUI service +listener "tcp" { + address = "0.0.0.0:8202" + tls_disable = true +} + +# API proxy configuration +api_proxy { + use_auto_auth_token = true + enforce_consistency = "always" + when_inconsistent = "forward" +} diff --git a/vault/agents/llm/agent.hcl b/vault/agents/llm/agent.hcl index 4a0b410..d7237be 100644 --- a/vault/agents/llm/agent.hcl +++ b/vault/agents/llm/agent.hcl @@ -1,32 +1,34 @@ vault { address = "http://vault:8200" + retry { + num_retries = 5 + } } -pid_file = "/agent/out/pidfile" - auto_auth { method "approle" { mount_path = "auth/approle" config = { - role_id_file_path = "/agent/credentials/role_id" - secret_id_file_path = "/agent/credentials/secret_id" + role_id_file_path = "/agent/credentials/llm_role_id" + secret_id_file_path = "/agent/credentials/llm_secret_id" remove_secret_id_file_after_reading = false } } sink "file" { config = { - path = "/agent/out/token" + path = "/agent/llm-token/token" + mode = 0640 } } } cache { - default_lease_duration = "1h" + default_lease_duration = "1h" # Longer TTL for LLM service } listener "tcp" { - address = "127.0.0.1:8201" + address = "0.0.0.0:8201" # Listen on all interfaces tls_disable = true } From dd3fa8b15c8c0d42493da5474650c06db95c61f3 Mon Sep 17 00:00:00 2001 From: nuwangeek Date: Fri, 9 Jan 2026 17:02:07 +0530 Subject: [PATCH 2/2] fixed formatting issue --- src/llm_orchestrator_config/vault/vault_client.py | 2 +- tests/integration_tests/test_indexing.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llm_orchestrator_config/vault/vault_client.py b/src/llm_orchestrator_config/vault/vault_client.py index c7dfe5c..b0c3a3d 100644 --- a/src/llm_orchestrator_config/vault/vault_client.py +++ b/src/llm_orchestrator_config/vault/vault_client.py @@ -319,4 +319,4 @@ def refresh_token(self) -> bool: return self.is_authenticated() except Exception as e: logger.error(f"Failed to refresh Vault token: {e}") - return False \ No newline at end of file + return False diff --git a/tests/integration_tests/test_indexing.py b/tests/integration_tests/test_indexing.py index a792d2a..b134e5b 100644 --- a/tests/integration_tests/test_indexing.py +++ b/tests/integration_tests/test_indexing.py @@ -129,7 +129,7 @@ async def test_indexing_pipeline_e2e( break except requests.exceptions.RequestException: logger.debug( - f"LLM orchestration health check attempt {i+1}/{max_retries} failed" + f"LLM orchestration health check attempt {i + 1}/{max_retries} failed" ) time.sleep(2) else: