From 9e748b008cdcb9675acb321f55dae4589e23f68d Mon Sep 17 00:00:00 2001 From: Christian Landgren Date: Mon, 18 May 2026 01:24:51 +0200 Subject: [PATCH 1/3] fix(k8s): fix SSH host key permissions for non-root user - Add fsGroup: 1001 to pod security context - Change defaultMode from 0600 to 0640 for ssh-host-key secret - Create /app/data/ssh-keys as writable directory in Dockerfile - Allow nodejs user (UID 1001) to read mounted SSH host key --- Dockerfile | 6 +++--- k8s/app/base/deployment.yaml | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index a418cb1..68840b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,9 +25,9 @@ COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./ -# Generate SSH host key if not mounted -RUN mkdir -p /app/ssh-keys && \ - chown nodejs:nodejs /app/ssh-keys +# Create writable directories +RUN mkdir -p /app/ssh-keys /app/data/ssh-keys && \ + chown -R nodejs:nodejs /app/ssh-keys /app/data # Expose ports EXPOSE 3000 2222 diff --git a/k8s/app/base/deployment.yaml b/k8s/app/base/deployment.yaml index d566919..2305112 100644 --- a/k8s/app/base/deployment.yaml +++ b/k8s/app/base/deployment.yaml @@ -14,6 +14,8 @@ spec: labels: app: memory spec: + securityContext: + fsGroup: 1001 containers: - name: memory image: ghcr.io/berget-ai/memory:1.0.0 @@ -67,6 +69,6 @@ spec: - name: ssh-host-key secret: secretName: memory-ssh-host-key - defaultMode: 0600 + defaultMode: 0640 imagePullSecrets: - name: regcred From b597a653e078964beb4e6376af500b92a4069a09 Mon Sep 17 00:00:00 2001 From: Christian Landgren Date: Mon, 18 May 2026 12:22:51 +0200 Subject: [PATCH 2/3] refactor(ssh): require mounted secret instead of generating host key - Remove fallback key generation logic - Fail fast with clear error if memory-ssh-host-key secret is missing - Mount point: /app/ssh-keys/ssh_host_ed25519_key - Remove unused data directory creation in Dockerfile --- Dockerfile | 6 +++--- src/ssh-server.ts | 38 +++++++++++--------------------------- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index 68840b2..b8bc1f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,9 +25,9 @@ COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./ -# Create writable directories -RUN mkdir -p /app/ssh-keys /app/data/ssh-keys && \ - chown -R nodejs:nodejs /app/ssh-keys /app/data +# Ensure ssh-keys mount point exists with correct ownership +RUN mkdir -p /app/ssh-keys && \ + chown nodejs:nodejs /app/ssh-keys # Expose ports EXPOSE 3000 2222 diff --git a/src/ssh-server.ts b/src/ssh-server.ts index 368c42c..fc192a7 100644 --- a/src/ssh-server.ts +++ b/src/ssh-server.ts @@ -1,5 +1,5 @@ import { Server } from "ssh2"; -import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"; +import { readFileSync, existsSync } from "fs"; import { join } from "path"; import { generateKeyPairSync } from "crypto"; @@ -69,34 +69,18 @@ function generatePrompt(session: SessionState): string { } export function createSSHServer(config: SSHConfig): Server { - // Try mounted secret first, fallback to writable directory - const mountedKeyPath = join(process.cwd(), "ssh-keys", "ssh_host_ed25519_key"); - const generatedKeysDir = join(process.cwd(), "data", "ssh-keys"); - const generatedKeyPath = join(generatedKeysDir, "ssh_host_rsa_key"); - - let hostKey: Buffer; - - if (existsSync(mountedKeyPath)) { - console.log("[SSH] Using mounted host key"); - hostKey = readFileSync(mountedKeyPath); - } else if (existsSync(generatedKeyPath)) { - console.log("[SSH] Using generated host key"); - hostKey = readFileSync(generatedKeyPath); - } else { - console.log("[SSH] Generating host key..."); - if (!existsSync(generatedKeysDir)) { - mkdirSync(generatedKeysDir, { recursive: true }); - } - const { privateKey } = generateKeyPairSync("rsa", { - modulusLength: 2048, - publicKeyEncoding: { type: "pkcs1", format: "pem" }, - privateKeyEncoding: { type: "pkcs1", format: "pem" }, - }); - writeFileSync(generatedKeyPath, privateKey, { mode: 0o600 }); - hostKey = Buffer.from(privateKey); - console.log("[SSH] Host key generated"); + const hostKeyPath = join(process.cwd(), "ssh-keys", "ssh_host_ed25519_key"); + + if (!existsSync(hostKeyPath)) { + throw new Error( + `SSH host key not found at ${hostKeyPath}. ` + + `Ensure secret 'memory-ssh-host-key' is mounted at /app/ssh-keys` + ); } + console.log("[SSH] Using mounted host key"); + const hostKey = readFileSync(hostKeyPath); + const server = new Server( { hostKeys: [hostKey], From b0fb8a18c24c41b3041d28b75d8410f4b1e652cf Mon Sep 17 00:00:00 2001 From: Christian Landgren Date: Mon, 18 May 2026 12:34:16 +0200 Subject: [PATCH 3/3] fix(ssh): remove unused generateKeyPairSync import --- src/ssh-server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ssh-server.ts b/src/ssh-server.ts index fc192a7..1b38e60 100644 --- a/src/ssh-server.ts +++ b/src/ssh-server.ts @@ -1,7 +1,6 @@ import { Server } from "ssh2"; import { readFileSync, existsSync } from "fs"; import { join } from "path"; -import { generateKeyPairSync } from "crypto"; interface SSHConfig { port: number;