v9.3.1
release-tag / release-image (push) Failing after 1m15s

This commit is contained in:
2026-08-31 22:17:18 +02:00
parent c6c25d26c3
commit 56170ccde9
19 changed files with 485 additions and 51 deletions
+49 -19
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@@ -25,7 +26,8 @@ type Config struct {
ListenAddr, BaseURL, DataDir, StacksDir, AppSecret string
SecureCookies, AuthDisabled bool
OIDCIssuer, OIDCClientID, OIDCClientSecret, OIDCRedirectURL, OIDCAdminGroup, OIDCOperatorGroup string
AgentToken string
AgentToken, HostRoot string
AllowHostUserManagement, AllowHostPermissionManagement bool
CheckConcurrency, RetentionDays, AuditRetentionDays int
HTTPTimeout time.Duration
}
@@ -51,25 +53,36 @@ func Load() (Config, error) {
if err != nil {
return Config{}, err
}
allowHostUserManagement, err := envBoolStrict("ALLOW_HOST_USER_MANAGEMENT", false)
if err != nil {
return Config{}, err
}
allowHostPermissionManagement, err := envBoolStrict("ALLOW_HOST_PERMISSION_MANAGEMENT", false)
if err != nil {
return Config{}, err
}
c := Config{
Mode: Mode(env("APP_MODE", "standalone")),
ListenAddr: env("LISTEN_ADDR", ":8080"),
BaseURL: strings.TrimRight(env("BASE_URL", "http://localhost:8080"), "/"),
DataDir: env("DATA_DIR", "/data"),
StacksDir: env("STACKS_DIR", "/stacks"),
AppSecret: os.Getenv("APP_SECRET"),
AuthDisabled: authDisabled,
OIDCIssuer: strings.TrimRight(os.Getenv("OIDC_ISSUER"), "/"),
OIDCClientID: os.Getenv("OIDC_CLIENT_ID"),
OIDCClientSecret: os.Getenv("OIDC_CLIENT_SECRET"),
OIDCRedirectURL: os.Getenv("OIDC_REDIRECT_URL"),
OIDCAdminGroup: env("OIDC_ADMIN_GROUP", "dockwatch-admins"),
OIDCOperatorGroup: env("OIDC_OPERATOR_GROUP", "dockwatch-operators"),
AgentToken: os.Getenv("AGENT_TOKEN"),
CheckConcurrency: checkConcurrency,
RetentionDays: retentionDays,
AuditRetentionDays: auditRetentionDays,
HTTPTimeout: time.Duration(httpTimeoutSeconds) * time.Second,
Mode: Mode(env("APP_MODE", "standalone")),
ListenAddr: env("LISTEN_ADDR", ":8080"),
BaseURL: strings.TrimRight(env("BASE_URL", "http://localhost:8080"), "/"),
DataDir: env("DATA_DIR", "/data"),
StacksDir: env("STACKS_DIR", "/stacks"),
AppSecret: os.Getenv("APP_SECRET"),
AuthDisabled: authDisabled,
OIDCIssuer: strings.TrimRight(os.Getenv("OIDC_ISSUER"), "/"),
OIDCClientID: os.Getenv("OIDC_CLIENT_ID"),
OIDCClientSecret: os.Getenv("OIDC_CLIENT_SECRET"),
OIDCRedirectURL: os.Getenv("OIDC_REDIRECT_URL"),
OIDCAdminGroup: env("OIDC_ADMIN_GROUP", "dockwatch-admins"),
OIDCOperatorGroup: env("OIDC_OPERATOR_GROUP", "dockwatch-operators"),
AgentToken: os.Getenv("AGENT_TOKEN"),
HostRoot: cleanOptionalPath(os.Getenv("HOST_ROOT")),
AllowHostUserManagement: allowHostUserManagement,
AllowHostPermissionManagement: allowHostPermissionManagement,
CheckConcurrency: checkConcurrency,
RetentionDays: retentionDays,
AuditRetentionDays: auditRetentionDays,
HTTPTimeout: time.Duration(httpTimeoutSeconds) * time.Second,
}
c.SecureCookies = strings.HasPrefix(c.BaseURL, "https://")
if c.OIDCRedirectURL == "" {
@@ -98,6 +111,15 @@ func Load() (Config, error) {
return c, errors.New("BASE_URL must be an absolute http(s) URL without credentials, query or fragment")
}
}
if c.HostRoot != "" && !filepath.IsAbs(c.HostRoot) {
return c, errors.New("HOST_ROOT must be an absolute path")
}
if c.AllowHostUserManagement && c.HostRoot == "" {
return c, errors.New("ALLOW_HOST_USER_MANAGEMENT=true requires HOST_ROOT")
}
if c.AllowHostPermissionManagement && c.HostRoot == "" {
return c, errors.New("ALLOW_HOST_PERMISSION_MANAGEMENT=true requires HOST_ROOT")
}
if c.Mode == ModeAgent {
if len(c.AgentToken) < 24 {
return c, errors.New("AGENT_TOKEN must be at least 24 characters in agent mode")
@@ -118,6 +140,14 @@ func (c Config) SecretFingerprint() string {
h := sha256.Sum256([]byte(c.AppSecret))
return base64.RawURLEncoding.EncodeToString(h[:6])
}
func cleanOptionalPath(v string) string {
v = strings.TrimSpace(v)
if v == "" {
return ""
}
return filepath.Clean(v)
}
func env(k, f string) string {
if v := os.Getenv(k); v != "" {
return v
+26
View File
@@ -16,3 +16,29 @@ func TestStrictBooleanParsing(t *testing.T) {
t.Fatal("expected invalid AUTH_DISABLED to fail")
}
}
func TestHostUserManagementRequiresHostRoot(t *testing.T) {
t.Setenv("APP_MODE", "agent")
t.Setenv("AGENT_TOKEN", "123456789012345678901234")
t.Setenv("ALLOW_HOST_USER_MANAGEMENT", "true")
t.Setenv("HOST_ROOT", "")
if _, err := Load(); err == nil {
t.Fatal("expected host user management without HOST_ROOT to fail")
}
}
func TestCleanOptionalHostRootPreservesFilesystemRoot(t *testing.T) {
if got := cleanOptionalPath("/"); got != "/" {
t.Fatalf("cleanOptionalPath(/) = %q", got)
}
}
func TestHostPermissionManagementRequiresHostRoot(t *testing.T) {
t.Setenv("AUTH_DISABLED", "true")
t.Setenv("APP_SECRET", "01234567890123456789012345678901")
t.Setenv("ALLOW_HOST_PERMISSION_MANAGEMENT", "true")
t.Setenv("HOST_ROOT", "")
if _, err := Load(); err == nil {
t.Fatal("expected host permission management without HOST_ROOT to fail")
}
}