This repository has been archived by the owner on Jul 12, 2023. It is now read-only.
/
resolver.go
145 lines (121 loc) · 4.26 KB
/
resolver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secrets
import (
"context"
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/google/exposure-notifications-server/pkg/logging"
"github.com/sethvargo/go-envconfig"
)
const (
// SecretPrefix is the prefix, that if the value of an env var starts with
// will be resolved through the configured secret store.
SecretPrefix = "secret://"
// FileSuffix is the suffix to use, if this secret path should be written to a file.
// only interpreted on environment variable values that start w/ secret://
FileSuffix = "?target=file"
)
// Resolver returns a function that fetches secrets from the secret manager. If
// the provided secret manager is nil, the function is nil, Otherwise, it looks
// for values prefixed with secret:// and resolves them as secrets. For slice
// functions, values separated by commas are processed as individual secrets.
func Resolver(sm SecretManager, config *Config) envconfig.MutatorFunc {
if sm == nil {
return nil
}
resolver := &secretResolver{
sm: sm,
dir: config.SecretsDir,
}
return func(ctx context.Context, key, value string) (string, error) {
vals := strings.Split(value, ",")
resolved := make([]string, len(vals))
for i, val := range vals {
s, err := resolver.resolve(ctx, key, val)
if err != nil {
return "", err
}
resolved[i] = s
}
return strings.Join(resolved, ","), nil
}
}
type secretResolver struct {
sm SecretManager
dir string
}
// resolve resolves an individual secretRef using the provided secret manager
// and configuration.
func (r *secretResolver) resolve(ctx context.Context, envName, secretRef string) (string, error) {
logger := logging.FromContext(ctx)
if !strings.HasPrefix(secretRef, SecretPrefix) {
return secretRef, nil
}
if r.sm == nil {
return "", fmt.Errorf("env requested secrets, but no secret manager is configured")
}
// Remove the prefix.
secretRef = strings.TrimPrefix(secretRef, SecretPrefix)
// Check if the value should be written to a file.
toFile := false
if strings.HasSuffix(secretRef, FileSuffix) {
toFile = true
secretRef = strings.TrimSuffix(secretRef, FileSuffix)
}
logger.Infof("resolving secret value for %q (toFile=%t)", envName, toFile)
secretVal, err := r.sm.GetSecretValue(ctx, secretRef)
if err != nil {
return "", fmt.Errorf("failed to resolve %q: %w", secretRef, err)
}
if toFile {
if err := r.ensureSecureDir(); err != nil {
return "", err
}
secretFileName := r.filenameForSecret(envName + "." + secretRef)
secretFilePath := filepath.Join(r.dir, secretFileName)
if err := ioutil.WriteFile(secretFilePath, []byte(secretVal), 0600); err != nil {
return "", fmt.Errorf("failed to write secret file for %q: %w", envName, err)
}
secretVal = secretFilePath
}
return secretVal, nil
}
// filenameForSecret returns the sha1 of the secret name.
func (r *secretResolver) filenameForSecret(name string) string {
return fmt.Sprintf("%x", sha1.Sum([]byte(name)))
}
// ensureSecureDir creates the parent secretsDir with minimal permissions. If
// the directory does not exist, it is created with 0700 permissions. If the
// directory exists but has broader permissions that 0700, an error is returned.
func (r *secretResolver) ensureSecureDir() error {
stat, err := os.Stat(r.dir)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to check if secure directory %q exists: %w", r.dir, err)
}
if os.IsNotExist(err) {
if err := os.MkdirAll(r.dir, 0700); err != nil {
return fmt.Errorf("failed to create secure directory %q: %w", r.dir, err)
}
} else {
if stat.Mode().Perm() != os.FileMode(0700) {
return fmt.Errorf("secure directory %q exists and is not restricted %v", r.dir, stat.Mode())
}
}
return nil
}