1package app
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "runtime"
8 "testing"
9
10 "github.com/bnema/zerowrap"
11 "github.com/spf13/viper"
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14 "golang.org/x/sys/unix"
15
16 "github.com/bnema/gordon/internal/domain"
17)
18
19func TestBuildAuthConfig_UsesConfigEnabledFlag(t *testing.T) {
20 t.Setenv(TokenSecretEnvVar, "test-token-secret-that-is-at-least-32-bytes-long")
21
22 cfg := Config{}
23 cfg.Auth.Enabled = false
24 cfg.Auth.Type = "token"
25
26 authCfg, err := buildAuthConfig(context.Background(), cfg, domain.AuthTypeToken, domain.SecretsBackendUnsafe, t.TempDir(), zerowrap.Default())
27 require.NoError(t, err)
28 assert.False(t, authCfg.Enabled, "expected auth config enabled=false")
29}
30
31func TestResolveSecretsBackend_RejectsMissingBackend(t *testing.T) {
32 _, err := resolveSecretsBackend("")
33 require.Error(t, err)
34 assert.Contains(t, err.Error(), "auth.secrets_backend is required")
35}
36
37func TestResolveSecretsBackend_RejectsUnknownBackend(t *testing.T) {
38 _, err := resolveSecretsBackend("vault")
39 require.Error(t, err)
40 assert.Contains(t, err.Error(), `unsupported auth.secrets_backend "vault"`)
41}
42
43func TestResolveAuthType_RejectsPassword(t *testing.T) {
44 cfg := Config{}
45 cfg.Auth.Type = "password"
46 _, err := resolveAuthType(cfg)
47 require.Error(t, err)
48 assert.Contains(t, err.Error(), "unsupported auth.type")
49}
50
51func TestResolveAuthType_AcceptsToken(t *testing.T) {
52 cfg := Config{}
53 cfg.Auth.Type = "token"
54 authType, err := resolveAuthType(cfg)
55 require.NoError(t, err)
56 assert.Equal(t, domain.AuthTypeToken, authType)
57}
58
59func TestResolveAuthType_AcceptsEmpty(t *testing.T) {
60 cfg := Config{}
61 authType, err := resolveAuthType(cfg)
62 require.NoError(t, err)
63 assert.Equal(t, domain.AuthTypeToken, authType)
64}
65
66func TestLoadConfig_DoesNotDefaultSecretsBackendToUnsafe(t *testing.T) {
67 v := viper.New()
68
69 err := loadConfig(v, "")
70 require.NoError(t, err)
71 assert.Empty(t, v.GetString("auth.secrets_backend"), "expected empty default for auth.secrets_backend")
72}
73
74func TestLoadSecretUnsafeRejectsTraversalAndAbsolutePaths(t *testing.T) {
75 dataDir := t.TempDir()
76 secretsDir := filepath.Join(dataDir, "secrets")
77 require.NoError(t, os.MkdirAll(secretsDir, 0700))
78 require.NoError(t, os.WriteFile(filepath.Join(secretsDir, "token"), []byte("safe-secret"), 0600))
79 outside := filepath.Join(dataDir, "outside")
80 require.NoError(t, os.WriteFile(outside, []byte("outside-secret"), 0600))
81
82 for _, path := range []string{"../outside", filepath.Join(dataDir, "outside")} {
83 t.Run(path, func(t *testing.T) {
84 _, err := loadSecret(context.Background(), domain.SecretsBackendUnsafe, path, dataDir, zerowrap.Default())
85 require.Error(t, err)
86 assert.Contains(t, err.Error(), "invalid secret path")
87 })
88 }
89}
90
91func TestLoadSecretUnsafeAllowsRelativePathUnderSecretsDir(t *testing.T) {
92 dataDir := t.TempDir()
93 secretsDir := filepath.Join(dataDir, "secrets", "auth")
94 require.NoError(t, os.MkdirAll(secretsDir, 0700))
95 require.NoError(t, os.WriteFile(filepath.Join(secretsDir, "token"), []byte("safe-secret"), 0600))
96
97 secret, err := loadSecret(context.Background(), domain.SecretsBackendUnsafe, "auth/token", dataDir, zerowrap.Default())
98 require.NoError(t, err)
99 assert.Equal(t, "safe-secret", secret)
100}
101
102func TestLoadSecretUnsafeRejectsMissingCandidate(t *testing.T) {
103 dataDir := t.TempDir()
104 secretsDir := filepath.Join(dataDir, "secrets")
105 require.NoError(t, os.MkdirAll(secretsDir, 0700))
106
107 _, err := loadSecret(context.Background(), domain.SecretsBackendUnsafe, "missing", dataDir, zerowrap.Default())
108 require.Error(t, err)
109 assert.Contains(t, err.Error(), "failed to read secret file")
110}
111
112func TestLoadSecretUnsafeRejectsIntermediateSymlinkEscape(t *testing.T) {
113 dataDir := t.TempDir()
114 secretsDir := filepath.Join(dataDir, "secrets")
115 outsideDir := filepath.Join(dataDir, "outside")
116 require.NoError(t, os.MkdirAll(secretsDir, 0700))
117 require.NoError(t, os.MkdirAll(outsideDir, 0700))
118 require.NoError(t, os.WriteFile(filepath.Join(outsideDir, "token"), []byte("outside-secret"), 0600))
119 require.NoError(t, os.Symlink(outsideDir, filepath.Join(secretsDir, "linkdir")))
120
121 _, err := loadSecret(context.Background(), domain.SecretsBackendUnsafe, "linkdir/token", dataDir, zerowrap.Default())
122 require.Error(t, err)
123 assert.Contains(t, err.Error(), "failed to open secret path component")
124}
125
126func TestLoadSecretUnsafeRejectsNonRegularFile(t *testing.T) {
127 if runtime.GOOS == "windows" {
128 t.Skip("unix mkfifo is not available on windows")
129 }
130 dataDir := t.TempDir()
131 secretsDir := filepath.Join(dataDir, "secrets")
132 require.NoError(t, os.MkdirAll(secretsDir, 0700))
133 require.NoError(t, unix.Mkfifo(filepath.Join(secretsDir, "token"), 0600))
134
135 _, err := loadSecret(context.Background(), domain.SecretsBackendUnsafe, "token", dataDir, zerowrap.Default())
136 require.Error(t, err)
137 assert.Contains(t, err.Error(), "secret must be a regular file")
138}
139
140func TestLoadSecretUnsafeRejectsSymlinkEscape(t *testing.T) {
141 dataDir := t.TempDir()
142 secretsDir := filepath.Join(dataDir, "secrets")
143 require.NoError(t, os.MkdirAll(secretsDir, 0700))
144 outside := filepath.Join(dataDir, "outside")
145 require.NoError(t, os.WriteFile(outside, []byte("outside-secret"), 0600))
146 require.NoError(t, os.Symlink(outside, filepath.Join(secretsDir, "token")))
147
148 _, err := loadSecret(context.Background(), domain.SecretsBackendUnsafe, "token", dataDir, zerowrap.Default())
149 require.Error(t, err)
150 assert.Contains(t, err.Error(), "failed to read secret file")
151}