app_mounts.go RawHistoryFolder
1package app
2
3import (
4 "fmt"
5 "path/filepath"
6 "regexp"
7 "strings"
8
9 "github.com/bnema/gordon/internal/domain"
10)
11
12// AppMountPolicy is one administrative bind policy declared under
13// [[app_mounts.<name>]]. The map key is the stable mount name an app manifest
14// bind references. Source is the only host path a bind may come from. Root,
15// when set, is the administrative boundary the resolved source must stay
16// under. AllowedApps and AllowedServices are exact, non-empty allowlists.
17type AppMountPolicy struct {
18 Source string `mapstructure:"source"`
19 ReadOnly bool `mapstructure:"read_only"`
20 AllowedApps []string `mapstructure:"allowed_apps"`
21 AllowedServices []string `mapstructure:"allowed_services"`
22 Root string `mapstructure:"root"`
23}
24
25// policyQuotedValue matches the quoted values domain validation embeds in
26// its messages, such as configured source paths or roots.
27var policyQuotedValue = regexp.MustCompile(`"[^"]*"`)
28
29// redactedBindPolicyReason keeps the field-level reason from a domain policy
30// validation error (for example "source must be absolute") while stripping
31// every quoted value so an invalid config never echoes host paths.
32func redactedBindPolicyReason(err error) string {
33 reason := policyQuotedValue.ReplaceAllString(err.Error(), "")
34 reason = strings.Join(strings.Fields(reason), " ")
35 // Keep only the innermost detail: the outer wrap repeats the generic
36 // violation and the policy name, which is already part of the mount key.
37 if idx := strings.LastIndex(reason, ": "); idx >= 0 {
38 reason = reason[idx+2:]
39 }
40 return reason
41}
42
43// buildAppMountPolicies converts configured app mounts into validated domain
44// policies. Failures name only the mount and the field-level reason: source
45// paths are never echoed.
46func buildAppMountPolicies(cfg Config) (map[string]domain.AppBindPolicy, error) {
47 policies := make(map[string]domain.AppBindPolicy, len(cfg.AppMounts))
48 for name, mount := range cfg.AppMounts {
49 root := mount.Root
50 if root == "" && filepath.IsAbs(mount.Source) {
51 // The source's parent is the least surprising implicit
52 // administrative boundary for the documented minimal form.
53 // A symlink source may resolve within it, never escape it.
54 root = filepath.Dir(mount.Source)
55 }
56 policy := domain.AppBindPolicy{
57 Name: name,
58 Source: mount.Source,
59 ReadOnly: mount.ReadOnly,
60 AllowedApps: mount.AllowedApps,
61 AllowedServices: mount.AllowedServices,
62 Root: root,
63 }
64 if err := policy.Validate(); err != nil {
65 return nil, fmt.Errorf("invalid app_mounts.%s: %w: %s", name, domain.ErrBindPolicy, redactedBindPolicyReason(err))
66 }
67 policies[name] = policy
68 }
69 return policies, nil
70}