The example modules put their credentials where the programs read them

Every one of these declared `own-secrets` pointing at a path called
`.env` and then mounted it as `env-file`. The file's whole content is
the password. Docker reads that as a malformed line and the container
starts with no password set — which is not a failure to start, it is a
service running with the wrong credential.

They parsed, they resolved, and none of them could ever have worked.
That is what a manifest checked only by the parser buys.

Each now keeps the sealed file as what it is — a password, alone — and
declares a file beside it whose content says ${secret:name}. The host
fills the hole on the machine, which is the only place both halves
exist. The provisioners mount the bare file, because they read a
password file and always did.

Two tests, both driven from the manifests on disk rather than from
fixtures: every ${secret:x} must name something the module declared, and
nothing may read a bare password file as an env file. Injecting the
shipped bug reproduces it word for word.

Keycloak, Gitea and Mailu still cannot connect to their databases, for
the reason in 04-ISSUES/023 — the user name is the provisioner's
invention and the bound values cannot reach a config file. Their own
credentials are right now; that half was independent and is done.
This commit is contained in:
2026-09-01 02:52:00 +02:00
parent 0af3ea1acf
commit be2dca27ab
7 changed files with 150 additions and 24 deletions
+9 -3
View File
@@ -7,7 +7,7 @@
"postgres-database": {"name": "gitea"}
},
"binds": {"postgres-database": "/var/lib/gitea/database.json"},
"secrets": {"postgres-database": "/var/lib/gitea/database.env"},
"secrets": {"postgres-database": "/var/lib/gitea/database.secret"},
"capabilities": ["container-runtime"],
@@ -17,14 +17,20 @@
"why": "git over ssh. Not 22: the machine's own daemon holds that, and a module does not take it"}
],
"own-secrets": {"internal-token": "/var/lib/gitea/internal-token.secret"},
"resources": [
{"id": "state", "type": "directory", "path": "/var/lib/gitea", "mode": "0700"},
{"id": "server-env", "type": "file", "path": "/var/lib/gitea/server.env", "mode": "0600",
"content": "GITEA__security__INTERNAL_TOKEN=${secret:internal-token}\n"},
{"id": "server", "type": "container", "name": "gitea",
"image": "gitea@sha256:0000000000000000000000000000000000000000000000000000000000000000",
"env": {"DB_TYPE": "postgres", "USER_UID": "1000", "USER_GID": "1000"},
"env-file": ["/var/lib/gitea/database.env"],
"env-file": ["/var/lib/gitea/server.env"],
"ports": ["3000:3000", "2222:22"],
"volumes": ["/services/gitea/gitea:/data"]}
"volumes": ["/services/gitea/gitea:/data"],
"restart-on": ["server-env"]}
]
}
+16 -8
View File
@@ -7,7 +7,7 @@
"postgres-database": {"name": "keycloak"}
},
"binds": {"postgres-database": "/var/lib/keycloak/database.json"},
"secrets": {"postgres-database": "/var/lib/keycloak/database.env"},
"secrets": {"postgres-database": "/var/lib/keycloak/database.secret"},
"provides": [{"name": "oidc-client", "scope": "mesh"}],
"capabilities": ["container-runtime"],
@@ -24,12 +24,15 @@
"receives": {"oidc-client": "/var/lib/keycloak/grants/mesh.json"},
"grants": {"oidc-client": "/var/lib/keycloak/grants"},
"own-secrets": {"admin": "/var/lib/keycloak/admin.env"},
"own-secrets": {"admin": "/var/lib/keycloak/admin.secret"},
"resources": [
{"id": "state", "type": "directory", "path": "/var/lib/keycloak", "mode": "0700"},
{"id": "grants", "type": "directory", "path": "/var/lib/keycloak/grants", "mode": "0700"},
{"id": "admin-env", "type": "file", "path": "/var/lib/keycloak/admin.env", "mode": "0600",
"content": "KEYCLOAK_ADMIN=admin\nKEYCLOAK_ADMIN_PASSWORD=${secret:admin}\n"},
{"id": "net", "type": "network", "name": "keycloak"},
{"id": "server", "type": "container", "name": "keycloak",
@@ -37,8 +40,9 @@
"network": "keycloak",
"args": ["start-dev"],
"env": {"KC_DB": "postgres", "KC_HTTP_ENABLED": "true", "KC_HEALTH_ENABLED": "true"},
"env-file": ["/var/lib/keycloak/admin.env", "/var/lib/keycloak/database.env"],
"ports": ["8080:8080"]},
"env-file": ["/var/lib/keycloak/admin.env"],
"ports": ["8080:8080"],
"restart-on": ["admin-env"]},
{"id": "provisioner", "type": "container", "name": "mesh-provision-keycloak",
"image": "mesh-provision-keycloak@sha256:0000000000000000000000000000000000000000000000000000000000000000",
@@ -46,10 +50,14 @@
"env": {
"GRANTS": "/var/lib/keycloak/grants",
"MESH_KEYCLOAK_URL": "http://keycloak:8080",
"MESH_KEYCLOAK_REALM": "mesh"
"MESH_KEYCLOAK_REALM": "mesh",
"MESH_KEYCLOAK_ADMIN": "admin",
"MESH_KEYCLOAK_ADMIN_PASSWORD_FILE": "/run/secrets/admin"
},
"env-file": ["/var/lib/keycloak/admin.env"],
"volumes": ["/var/lib/keycloak/grants:/var/lib/keycloak/grants:ro"],
"restart-on": ["grants"]}
"volumes": [
"/var/lib/keycloak/grants:/var/lib/keycloak/grants:ro",
"/var/lib/keycloak/admin.secret:/run/secrets/admin:ro"
],
"restart-on": ["grants", "admin-env"]}
]
}
+10 -3
View File
@@ -13,14 +13,21 @@
],
"own-secrets": {
"secret-key": "/var/lib/mailu/secret.env",
"database": "/var/lib/mailu/database.env",
"admin": "/var/lib/mailu/admin.env"
"secret-key": "/var/lib/mailu/secret-key.secret",
"database": "/var/lib/mailu/database.secret",
"admin": "/var/lib/mailu/admin.secret"
},
"resources": [
{"id": "state", "type": "directory", "path": "/var/lib/mailu", "mode": "0700"},
{"id": "secret-env", "type": "file", "path": "/var/lib/mailu/secret.env", "mode": "0600",
"content": "SECRET_KEY=${secret:secret-key}\n"},
{"id": "database-env", "type": "file", "path": "/var/lib/mailu/database.env", "mode": "0600",
"content": "POSTGRES_USER=mailu\nPOSTGRES_DB=mailu\nPOSTGRES_PASSWORD=${secret:database}\nDB_USER=mailu\nDB_NAME=mailu\nDB_PW=${secret:database}\n"},
{"id": "admin-env", "type": "file", "path": "/var/lib/mailu/admin.env", "mode": "0600",
"content": "INITIAL_ADMIN_PW=${secret:admin}\n"},
{"id": "net", "type": "network", "name": "mailu"},
{"id": "resolver", "type": "container", "name": "mailu-resolver",
+8 -4
View File
@@ -16,12 +16,15 @@
"receives": {"s3-bucket": "/var/lib/minio/grants/mesh.json"},
"grants": {"s3-bucket": "/var/lib/minio/grants"},
"own-secrets": {"root": "/var/lib/minio/root.env"},
"own-secrets": {"root": "/var/lib/minio/root.secret"},
"resources": [
{"id": "state", "type": "directory", "path": "/var/lib/minio", "mode": "0700"},
{"id": "grants", "type": "directory", "path": "/var/lib/minio/grants", "mode": "0700"},
{"id": "root-env", "type": "file", "path": "/var/lib/minio/root.env", "mode": "0600",
"content": "MINIO_ROOT_USER=meshroot\nMINIO_ROOT_PASSWORD=${secret:root}\n"},
{"id": "net", "type": "network", "name": "minio"},
{"id": "server", "type": "container", "name": "minio",
@@ -30,7 +33,8 @@
"args": ["server", "/data", "--console-address", ":9001"],
"env-file": ["/var/lib/minio/root.env"],
"ports": ["9000:9000"],
"volumes": ["/services/minio/data/data1-1:/data"]},
"volumes": ["/services/minio/data/data1-1:/data"],
"restart-on": ["root-env"]},
{"id": "provisioner", "type": "container", "name": "mesh-provision-objectstore",
"image": "mesh-provision-objectstore@sha256:0000000000000000000000000000000000000000000000000000000000000000",
@@ -43,8 +47,8 @@
},
"volumes": [
"/var/lib/minio/grants:/var/lib/minio/grants:ro",
"/var/lib/minio/root.env:/run/secrets/root:ro"
"/var/lib/minio/root.secret:/run/secrets/root:ro"
],
"restart-on": ["grants"]}
"restart-on": ["grants", "root-env"]}
]
}
+96
View File
@@ -2,8 +2,10 @@ package modules
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
@@ -263,3 +265,97 @@ func TestTheObjectStoreEdgeFitsTogether(t *testing.T) {
consumer.Contributes[provision])
}
}
// Every hole an example leaves for a credential can be filled from what that module declared.
//
// **A manifest that parses is not a manifest that works.** These say `${secret:x}` in a file and
// declare `x` under `own-secrets`; if the two ever disagree the mesh refuses the whole declaration
// at push time, on the machine, with the module's name and nothing else to go on. Checking it here
// costs nothing and moves the answer to whoever edited the file.
//
// This is also the shape that was missing entirely until 2026-09-01: an own secret arrives as a
// file whose whole content is the password, and every one of these programs reads `KEY=value`. The
// manifests said `own-secrets` pointed at a `.env` and it did not — it pointed at a password.
func TestEveryCredentialHoleCanBeFilledByTheModuleThatLeftIt(t *testing.T) {
found, err := filepath.Glob("*.json")
if err != nil {
t.Fatal(err)
}
var checked int
for _, name := range found {
m := read(t, name)
has := map[string]bool{}
for own := range m.OwnSecrets {
has[own] = true
}
for required := range m.Secrets {
has[required] = true
}
for _, r := range m.Resources {
content, ok := r["content"].(string)
if !ok {
continue
}
for _, wanted := range secretsUsedForTest(content) {
checked++
if !has[wanted] {
t.Errorf(
"%s: %v says ${secret:%s}, and %s neither owns a secret by that name "+
"nor requires anything that grants one",
name, r["id"], wanted, m.Module)
}
}
}
}
if checked == 0 {
t.Fatal("no example puts a credential into a file, so this test proves nothing")
}
}
// A secret file is a password and nothing else, so nothing may read one as an env file.
//
// The fault this catches is the one these manifests shipped with: `own-secrets` pointing at a
// path called `.env`, mounted as `env-file`, holding a bare password. Docker reads that as a
// malformed line and the container starts with no password at all.
func TestNoContainerReadsABarePasswordAsAnEnvFile(t *testing.T) {
found, _ := filepath.Glob("*.json")
for _, name := range found {
m := read(t, name)
bare := map[string]bool{}
for _, where := range m.OwnSecrets {
bare[where] = true
}
for _, where := range m.Secrets {
bare[where] = true
}
for _, r := range m.Resources {
files, ok := r["env-file"].([]any)
if !ok {
continue
}
for _, f := range files {
if bare[fmt.Sprint(f)] {
t.Errorf(
"%s: %v reads %s as an env file, and that path holds a bare password — "+
"declare a file whose content says ${secret:...} and read that instead",
name, r["id"], f)
}
}
}
}
}
// The same expression the control plane and the host both match.
var placeholder = regexp.MustCompile(`\$\{secret:([a-z0-9][a-z0-9-]*)\}`)
func secretsUsedForTest(content string) []string {
var used []string
seen := map[string]bool{}
for _, m := range placeholder.FindAllStringSubmatch(content, -1) {
if !seen[m[1]] {
seen[m[1]] = true
used = append(used, m[1])
}
}
return used
}
+11 -6
View File
@@ -17,12 +17,16 @@
"receives": {"postgres-database": "/var/lib/postgres/grants/mesh.json"},
"grants": {"postgres-database": "/var/lib/postgres/grants"},
"own-secrets": {"superuser": "/var/lib/postgres/superuser.env"},
"own-secrets": {"superuser": "/var/lib/postgres/superuser.secret"},
"resources": [
{"id": "state", "type": "directory", "path": "/var/lib/postgres", "mode": "0700"},
{"id": "grants", "type": "directory", "path": "/var/lib/postgres/grants", "mode": "0700"},
{"id": "superuser-env", "type": "file", "path": "/var/lib/postgres/superuser.env",
"mode": "0600",
"content": "POSTGRES_PASSWORD=${secret:superuser}\n"},
{"id": "net", "type": "network", "name": "postgres"},
{"id": "server", "type": "container", "name": "postgres",
@@ -31,20 +35,21 @@
"env": {"POSTGRES_USER": "postgres", "POSTGRES_DB": "postgres"},
"env-file": ["/var/lib/postgres/superuser.env"],
"ports": ["5432:5432"],
"volumes": ["/services/postgres/db-data:/var/lib/postgresql/data"]},
"volumes": ["/services/postgres/db-data:/var/lib/postgresql/data"],
"restart-on": ["superuser-env"]},
{"id": "provisioner", "type": "container", "name": "mesh-provision-postgres",
"image": "mesh-provision-postgres@sha256:0000000000000000000000000000000000000000000000000000000000000000",
"network": "postgres",
"env": {
"GRANTS": "/var/lib/postgres/grants",
"MESH_PROVISION_POSTGRES": "postgres://postgres@postgres:5432/postgres?sslmode=disable"
"MESH_PROVISION_POSTGRES": "postgres://postgres@postgres:5432/postgres?sslmode=disable",
"MESH_PROVISION_PASSWORD_FILE": "/run/secrets/superuser"
},
"env-file": ["/var/lib/postgres/superuser.env"],
"volumes": [
"/var/lib/postgres/grants:/var/lib/postgres/grants:ro",
"/var/lib/postgres/superuser.env:/var/lib/postgres/superuser.env:ro"
"/var/lib/postgres/superuser.secret:/run/secrets/superuser:ro"
],
"restart-on": ["grants"]}
"restart-on": ["grants", "superuser-env"]}
]
}
Executable
BIN
View File
Binary file not shown.