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 readCapConfig(t *testing.T, doc string) (*viper.Viper, Config) {
15 t.Helper()
16 v := viper.New()
17 v.SetConfigType("toml")
18 require.NoError(t, v.ReadConfig(strings.NewReader(doc)))
19 var cfg Config
20 require.NoError(t, v.Unmarshal(&cfg))
21 return v, cfg
22}
23
24func TestBuildAppCapabilityPolicies_ValidConfig(t *testing.T) {
25 v, cfg := readCapConfig(t, `
26[app_capabilities.vm-net]
27add = ["NET_ADMIN"]
28allowed_apps = ["myapp"]
29allowed_services = ["worker"]
30`)
31 require.NoError(t, validateCapabilityConfig(v, cfg))
32 policies, err := buildAppCapabilityPolicies(cfg)
33 require.NoError(t, err)
34 policy := policies["vm-net"]
35 assert.Equal(t, "vm-net", policy.Name)
36 assert.Equal(t, []string{"NET_ADMIN"}, policy.Add)
37 assert.Equal(t, []string{"myapp"}, policy.AllowedApps)
38 assert.Equal(t, []string{"worker"}, policy.AllowedServices)
39}
40
41func TestBuildAppCapabilityPolicies_Invalid(t *testing.T) {
42 apps, svcs := []string{"demo"}, []string{"worker"}
43 cases := []struct {
44 name string
45 grant AppCapabilityPolicy
46 reason string
47 }{
48 {"empty add", AppCapabilityPolicy{AllowedApps: apps, AllowedServices: svcs}, "add must not be empty"},
49 {"ALL", AppCapabilityPolicy{Add: []string{"ALL"}, AllowedApps: apps, AllowedServices: svcs}, "not allowed"},
50 {"SYS_ADMIN", AppCapabilityPolicy{Add: []string{"SYS_ADMIN"}, AllowedApps: apps, AllowedServices: svcs}, "SYS_ADMIN"},
51 {"duplicate", AppCapabilityPolicy{Add: []string{"NET_ADMIN", "NET_ADMIN"}, AllowedApps: apps, AllowedServices: svcs}, "duplicate"},
52 {"empty apps", AppCapabilityPolicy{Add: []string{"NET_ADMIN"}, AllowedServices: svcs}, "allowed apps must not be empty"},
53 {"empty services", AppCapabilityPolicy{Add: []string{"NET_ADMIN"}, AllowedApps: apps}, "allowed services must not be empty"},
54 }
55 for _, tc := range cases {
56 t.Run(tc.name, func(t *testing.T) {
57 _, err := buildAppCapabilityPolicies(Config{AppCapabilities: map[string]AppCapabilityPolicy{"net": tc.grant}})
58 require.Error(t, err)
59 assert.Contains(t, err.Error(), "app_capabilities.net")
60 assert.Contains(t, err.Error(), tc.reason)
61 assert.ErrorIs(t, err, domain.ErrCapabilityPolicy)
62 assert.NotContains(t, err.Error(), "demo", "app names are redacted")
63 assert.NotContains(t, err.Error(), "worker", "service names are redacted")
64 })
65 }
66}
67
68func TestValidateAppCapabilityKeys_UnknownKey(t *testing.T) {
69 v, _ := readCapConfig(t, `
70[app_capabilities.net]
71adds = ["NET_ADMIN"]
72allowed_apps = ["demo"]
73allowed_services = ["worker"]
74`)
75 err := validateAppCapabilityKeys(v.GetStringMap("app_capabilities"))
76 require.Error(t, err)
77 assert.Contains(t, err.Error(), "unknown key")
78 require.NoError(t, validateAppCapabilityKeys(nil))
79}
80
81func TestValidateAppPolicies_IncludesCapabilities(t *testing.T) {
82 v, cfg := readCapConfig(t, `
83[app_capabilities.net]
84add = ["SYS_ADMIN"]
85allowed_apps = ["demo"]
86allowed_services = ["worker"]
87`)
88 require.ErrorIs(t, validateAppPolicies(v, cfg), domain.ErrCapabilityPolicy)
89}
90
91func TestBuildAppCapabilityPolicies_CopiesSlicesAndDefaultsEmpty(t *testing.T) {
92 policies, err := buildAppCapabilityPolicies(Config{})
93 require.NoError(t, err)
94 assert.Empty(t, policies)
95
96 cfg := Config{AppCapabilities: map[string]AppCapabilityPolicy{
97 "net": {Add: []string{"NET_ADMIN"}, AllowedApps: []string{"demo"}, AllowedServices: []string{"worker"}},
98 }}
99 policies, err = buildAppCapabilityPolicies(cfg)
100 require.NoError(t, err)
101 policies["net"].Add[0] = "mutated"
102 assert.Equal(t, []string{"NET_ADMIN"}, cfg.AppCapabilities["net"].Add)
103}