Accept encoded JSON settings for CARLOS environment files
2 files changed,
+60
−3
internal/home/runtime.go+28 −2internal/home/runtime_test.go+32 −1
CI passed — run details
diff --git a/internal/home/runtime.go b/internal/home/runtime.go| index bb48e6e..d8f7b30 100644 |
| --- a/internal/home/runtime.go |
| +++ b/internal/home/runtime.go |
| @@ -69,7 +69,10 @@ func LoadRuntime(dbPath string, logger *slog.Logger) (Runtime, error) { |
| sum := sha256.Sum256(pub) |
| cfg.keyID = hex.EncodeToString(sum[:]) |
| cfg.keys = map[string][]byte{cfg.keyID: pub} |
| - rawManifest := os.Getenv("HOME_CONSUMERS") |
| + rawManifest, err := exchangeJSON("HOME_CONSUMERS", 8192) |
| + if err != nil { |
| + return Runtime{}, err |
| + } |
| // Sixteen pilot origins keep the exact CSP manifest within ordinary header |
| // limits. Production registration needs a separately reviewed scalable path. |
| if len(rawManifest) > 8192 || json.Unmarshal([]byte(rawManifest), &cfg.consumers) != nil || len(cfg.consumers) == 0 || len(cfg.consumers) > 16 { |
| @@ -95,7 +98,10 @@ func LoadRuntime(dbPath string, logger *slog.Logger) (Runtime, error) { |
| return Runtime{}, err |
| } |
| var encoded map[string]string |
| - raw := os.Getenv("HOME_ASSERTION_KEYS") |
| + raw, err := exchangeJSON("HOME_ASSERTION_KEYS", 4096) |
| + if err != nil { |
| + return Runtime{}, err |
| + } |
| if len(raw) > 4096 || json.Unmarshal([]byte(raw), &encoded) != nil || len(encoded) == 0 || len(encoded) > 8 { |
| return Runtime{}, errExchangeConfig |
| } |
| @@ -149,3 +155,23 @@ func NewRuntime(cfg Runtime, d *db.DB, logger *slog.Logger) (*App, error) { |
| a.runtime = &cfg |
| return a, nil |
| } |
| + |
| +// exchangeJSON accepts an explicitly encoded alternative for platforms whose |
| +// environment-file format cannot carry JSON quotes. Both forms together fail. |
| +func exchangeJSON(name string, limit int) (string, error) { |
| + raw, encoded := os.Getenv(name), os.Getenv(name+"_BASE64") |
| + if encoded == "" { |
| + if len(raw) > limit { |
| + return "", errExchangeConfig |
| + } |
| + return raw, nil |
| + } |
| + if raw != "" || len(encoded) > base64.StdEncoding.EncodedLen(limit) { |
| + return "", errExchangeConfig |
| + } |
| + b, err := base64.StdEncoding.Strict().DecodeString(encoded) |
| + if err != nil || len(b) > limit || base64.StdEncoding.EncodeToString(b) != encoded { |
| + return "", errExchangeConfig |
| + } |
| + return string(b), nil |
| +} |
diff --git a/internal/home/runtime_test.go b/internal/home/runtime_test.go| index 3b852de..84036af 100644 |
| --- a/internal/home/runtime_test.go |
| +++ b/internal/home/runtime_test.go |
| @@ -13,7 +13,7 @@ import ( |
| func runtimeEnv(t *testing.T) (string, string) { |
| t.Helper() |
| setSecretEnv(t, nil) |
| - for _, k := range []string{"HOME_ROLE", "HOME_ASSERTION_KEY", "HOME_ASSERTION_KEYS", "HOME_CONSUMERS", "HOME_CREDENTIAL_ORIGIN", "HOME_DIRECTORY_ORIGIN"} { |
| + for _, k := range []string{"HOME_CONSUMERS_BASE64", "HOME_ASSERTION_KEYS_BASE64", "HOME_ROLE", "HOME_ASSERTION_KEY", "HOME_ASSERTION_KEYS", "HOME_CONSUMERS", "HOME_CREDENTIAL_ORIGIN", "HOME_DIRECTORY_ORIGIN"} { |
| t.Setenv(k, "") |
| } |
| t.Setenv("HOME_CREDENTIAL_ORIGIN", Origin) |
| @@ -93,3 +93,34 @@ func TestDirectoryRuntimeRefusesMalformedKeys(t *testing.T) { |
| } |
| } |
| } |
| + |
| +func TestRuntimeEncodedPlatformConfiguration(t *testing.T) { |
| + private, keys := runtimeEnv(t) |
| + t.Setenv("HOME_ROLE", "directory") |
| + t.Setenv("HOME_ASSERTION_KEYS_BASE64", base64.StdEncoding.EncodeToString([]byte(keys))) |
| + if _, err := LoadRuntime("directory.db", quietLogger()); err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Setenv("HOME_ASSERTION_KEYS", keys) |
| + if _, err := LoadRuntime("directory.db", quietLogger()); err != errExchangeConfig { |
| + t.Fatal("ambiguous keys accepted") |
| + } |
| + t.Setenv("HOME_ASSERTION_KEYS", "") |
| + for _, bad := range []string{"%%%", base64.StdEncoding.EncodeToString([]byte(keys)) + "\n", base64.StdEncoding.EncodeToString([]byte(`{"key":null}`))} { |
| + t.Setenv("HOME_ASSERTION_KEYS_BASE64", bad) |
| + if _, err := LoadRuntime("directory.db", quietLogger()); err != errExchangeConfig { |
| + t.Fatal("bad encoded keys accepted") |
| + } |
| + } |
| + t.Setenv("HOME_ROLE", "credential") |
| + t.Setenv("HOME_ASSERTION_KEY", private) |
| + manifest := `{"directory":"https://oficina.works"}` |
| + t.Setenv("HOME_CONSUMERS_BASE64", base64.StdEncoding.EncodeToString([]byte(manifest))) |
| + if _, err := LoadRuntime("credential.db", quietLogger()); err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Setenv("HOME_CONSUMERS", manifest) |
| + if _, err := LoadRuntime("credential.db", quietLogger()); err != errExchangeConfig { |
| + t.Fatal("ambiguous manifest accepted") |
| + } |
| +} |