1package app
2
3import (
4 "strings"
5 "testing"
6
7 "github.com/spf13/viper"
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10
11 "github.com/bnema/gordon/internal/domain"
12)
13
14func TestBuildAppMountPolicies_ValidConfigUnmarshal(t *testing.T) {
15 v := viper.New()
16 v.SetConfigType("toml")
17 require.NoError(t, v.ReadConfig(strings.NewReader(`
18[app_mounts.config]
19source = "/srv/gordon/binds/blog/config"
20read_only = true
21allowed_apps = ["blog", "docs"]
22allowed_services = ["web"]
23root = "/srv/gordon"
24`)))
25
26 var cfg Config
27 require.NoError(t, v.Unmarshal(&cfg))
28
29 policies, err := buildAppMountPolicies(cfg)
30 require.NoError(t, err)
31 require.Contains(t, policies, "config")
32 policy := policies["config"]
33 assert.Equal(t, "config", policy.Name)
34 assert.Equal(t, "/srv/gordon/binds/blog/config", policy.Source)
35 assert.True(t, policy.ReadOnly)
36 assert.Equal(t, []string{"blog", "docs"}, policy.AllowedApps)
37 assert.Equal(t, []string{"web"}, policy.AllowedServices)
38 assert.Equal(t, "/srv/gordon", policy.Root)
39}
40
41func TestBuildAppMountPolicies_Invalid(t *testing.T) {
42 cases := []struct {
43 name string
44 mount AppMountPolicy
45 reason string
46 }{
47 {"empty source", AppMountPolicy{AllowedApps: []string{"blog"}, AllowedServices: []string{"web"}}, "source must not be empty"},
48 {"relative source", AppMountPolicy{Source: "binds", AllowedApps: []string{"blog"}, AllowedServices: []string{"web"}}, "source must be absolute"},
49 {"empty allowed apps", AppMountPolicy{Source: "/srv/binds", AllowedServices: []string{"web"}}, "allowed apps must not be empty"},
50 {"empty allowed services", AppMountPolicy{Source: "/srv/binds", AllowedApps: []string{"blog"}}, "allowed services must not be empty"},
51 {"unclean root", AppMountPolicy{Source: "/srv/binds", AllowedApps: []string{"blog"}, AllowedServices: []string{"web"}, Root: "/srv/../etc"}, "root must be a normalized clean path"},
52 }
53 for _, tc := range cases {
54 t.Run(tc.name, func(t *testing.T) {
55 cfg := Config{AppMounts: map[string]AppMountPolicy{"config": tc.mount}}
56 _, err := buildAppMountPolicies(cfg)
57 require.Error(t, err)
58 assert.Contains(t, err.Error(), "app_mounts.config")
59 assert.Contains(t, err.Error(), tc.reason, "the field-level reason must survive redaction")
60 assert.ErrorIs(t, err, domain.ErrBindPolicy)
61 if tc.mount.Source != "" {
62 assert.NotContains(t, err.Error(), tc.mount.Source, "config errors must not echo the source path")
63 }
64 if tc.mount.Root != "" {
65 assert.NotContains(t, err.Error(), tc.mount.Root, "config errors must not echo the root path")
66 }
67 })
68 }
69}
70
71func TestRedactedBindPolicyReason_RedactsConfiguredValues(t *testing.T) {
72 _, err := buildAppMountPolicies(Config{AppMounts: map[string]AppMountPolicy{
73 "config": {
74 Source: "/srv/binds/blog/config",
75 AllowedApps: []string{"Blog"},
76 AllowedServices: []string{"web"},
77 },
78 }})
79
80 require.Error(t, err)
81 assert.Contains(t, err.Error(), "app name must be a DNS label")
82 assert.NotContains(t, err.Error(), "/srv/binds/blog/config", "configured paths must never be echoed")
83 assert.NotContains(t, err.Error(), "Blog", "configured names must never be echoed")
84}
85
86func TestBuildAppMountPolicies_DefaultRootIsSourceParent(t *testing.T) {
87 cfg := Config{AppMounts: map[string]AppMountPolicy{
88 "config": {
89 Source: "/srv/binds/config",
90 AllowedApps: []string{"blog"},
91 AllowedServices: []string{"web"},
92 },
93 }}
94 policies, err := buildAppMountPolicies(cfg)
95 require.NoError(t, err)
96 assert.Equal(t, "/srv/binds", policies["config"].Root)
97}
98
99func TestConfig_AppMountsDefaultsEmpty(t *testing.T) {
100 policies, err := buildAppMountPolicies(Config{})
101 require.NoError(t, err)
102 assert.Empty(t, policies)
103}