Просмотр исходного кода

Persist DataProtection keys on the config volume (#312)

DataProtection previously stored keys in the container user's profile
and staged writes through the process temp dir, so hardened Docker
setups (rootless/userns, snap confinement, read-only rootfs) hit
'Access to the path /tmp/ is denied' on key creation, and even default
setups lost keys on container recreation (breaking active browser
sessions after upgrades; read-only setups silently fell back to
ephemeral in-memory keys).

- Persist keys to {RPK_YAML_DIR}/.dataprotection with a fixed
  application name — on the mounted volume, keys survive recreation.
- Ship an app-owned /app/tmp and set TMPDIR to it so temp staging no
  longer depends on /tmp.
- Document the tmpfs mount needed for read-only deployments.

Verified in a container with --read-only: no ephemeral-keys fallback,
keys created on the volume, and a single key reused across container
recreation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tim Jones 1 день назад
Родитель
Сommit
1751b255c4
3 измененных файлов с 26 добавлено и 4 удалено
  1. 7 4
      RackPeek.Web/Dockerfile
  2. 11 0
      RackPeek.Web/Program.cs
  3. 8 0
      Shared.Rcl/wwwroot/raw_docs/install-guide.md

+ 7 - 4
RackPeek.Web/Dockerfile

@@ -48,10 +48,12 @@ RUN apt-get update \
     && apt-get install -y --no-install-recommends curl \
     && rm -rf /var/lib/apt/lists/*
 
-# Create shared config directory safely
-RUN mkdir -p /app/config \
-    && chown -R ${APP_UID}:0 /app/config \
-    && chmod -R g=u /app/config
+# Create shared config directory safely, plus an app-owned temp directory:
+# key persistence writes staging files via the process temp dir, and /tmp
+# can be unavailable in hardened/rootless Docker setups (#312).
+RUN mkdir -p /app/config /app/tmp \
+    && chown -R ${APP_UID}:0 /app/config /app/tmp \
+    && chmod -R g=u /app/config /app/tmp
 
 VOLUME ["/app/config"]
 
@@ -70,6 +72,7 @@ RUN if [ -f /usr/local/bin/rpk-dir/RackPeek ]; then \
 # Make sure ASP.NET binds correctly in containers
 ENV ASPNETCORE_URLS=http://+:8080
 ENV RPK_YAML_DIR=/app/config
+ENV TMPDIR=/app/tmp
 
 # Drop privileges
 USER ${APP_UID}

+ 11 - 0
RackPeek.Web/Program.cs

@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.DataProtection;
 using Microsoft.AspNetCore.Hosting.StaticWebAssets;
 using RackPeek.Domain;
 using RackPeek.Domain.Git;
@@ -47,6 +48,16 @@ public class Program {
             }
         }
 
+        // Persist DataProtection keys next to the config so they live on the
+        // mounted volume: they survive container recreation, and key writes
+        // no longer depend on a writable user profile or /tmp — both of
+        // which are unavailable in hardened Docker setups (#312).
+        var keysPath = Path.Combine(yamlPath, ".dataprotection");
+        Directory.CreateDirectory(keysPath);
+        builder.Services.AddDataProtection()
+            .PersistKeysToFileSystem(new DirectoryInfo(keysPath))
+            .SetApplicationName("RackPeek");
+
         builder.Services.ConfigureHttpJsonOptions(options => {
             options.SerializerOptions.Converters.Add(
                 new JsonStringEnumConverter());

+ 8 - 0
Shared.Rcl/wwwroot/raw_docs/install-guide.md

@@ -58,6 +58,14 @@ http://localhost:8080
 
 This uses a **named volume**, which avoids permission issues and is recommended for most users.
 
+RackPeek stores its ASP.NET DataProtection keys under `config/.dataprotection` on the same volume, so browser sessions survive container upgrades. If you run the container with a **read-only root filesystem**, also mount a tmpfs for the app's temp directory:
+
+```yaml
+    read_only: true
+    tmpfs:
+      - /app/tmp:uid=1654,gid=0
+```
+
 ---
 
 ## Portainer