1package domain
2
3import (
4 "fmt"
5 "path"
6 "slices"
7 "strings"
8)
9
10// AppSecretFileDir is the container directory holding file-delivered secrets.
11const AppSecretFileDir = "/run/secrets"
12
13// SecretFileEnvKey returns the env key that points to a file-delivered secret.
14func SecretFileEnvKey(key string) string { return key + "_FILE" }
15
16// SecretFilePath returns the container path of a file-delivered secret.
17func SecretFilePath(key string) string { return AppSecretFileDir + "/" + key }
18
19// SecretAsFile reports whether the secret env key is delivered as a file.
20func (s AppService) SecretAsFile(key string) bool {
21 return slices.Contains(s.SecretFiles, key)
22}
23
24// validateSecretFiles checks the file-delivered secret keys of one service.
25// <KEY>_FILE collisions with public env are checked by
26// checkEnvSecretKeyDisjoint, which sees both app and service env.
27func (s *AppService) validateSecretFiles() error {
28 if !slices.IsSorted(s.SecretFiles) {
29 return fmt.Errorf("%w: service %q secret file keys must be sorted", ErrInvalidAppSpec, s.Name)
30 }
31 seen := make(map[string]struct{}, len(s.SecretFiles))
32 for _, key := range s.SecretFiles {
33 if _, ok := s.Secrets[key]; !ok {
34 return fmt.Errorf("%w: service %q secret file key %q is not a declared secret", ErrInvalidAppSpec, s.Name, key)
35 }
36 if _, dup := seen[key]; dup {
37 return fmt.Errorf("%w: service %q secret file key %q is duplicated", ErrInvalidAppSpec, s.Name, key)
38 }
39 seen[key] = struct{}{}
40 fileKey := SecretFileEnvKey(key)
41 if _, ok := s.Secrets[fileKey]; ok {
42 return fmt.Errorf("%w: service %q secret key %q collides with a secret file key", ErrInvalidAppSpec, s.Name, fileKey)
43 }
44 }
45 if len(s.SecretFiles) == 0 {
46 return nil
47 }
48 for _, b := range s.Binds {
49 if underSecretFileDir(b.Path) {
50 return fmt.Errorf("%w: service %q bind %q must not mount at or under %s when secrets are delivered as files", ErrInvalidAppSpec, s.Name, b.Name, AppSecretFileDir)
51 }
52 }
53 for _, v := range s.Volumes {
54 if underSecretFileDir(v.Path) {
55 return fmt.Errorf("%w: service %q volume %q must not mount at or under %s when secrets are delivered as files", ErrInvalidAppSpec, s.Name, v.Name, AppSecretFileDir)
56 }
57 }
58 return nil
59}
60
61func underSecretFileDir(p string) bool {
62 p = path.Clean(p)
63 return p == AppSecretFileDir || strings.HasPrefix(p, AppSecretFileDir+"/")
64}