Skip to content

Commit afefcf2

Browse files
authored
Merge pull request #34 from Comcast/handle_no_mount_reads
Add support for when the mount points are unreadable.
2 parents d596c2c + a3e9944 commit afefcf2

File tree

5 files changed

+395
-38
lines changed

5 files changed

+395
-38
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ build-local: test
2020
CGO_ENABLED=0 go build -ldflags "-X main.version=$(VERSION)" -o $(PROJECT_NAME)
2121

2222
clean:
23-
rm -r buildenv
23+
rm -rf buildenv
2424
rm -f *.tar.gz
2525
rm -rf pkg

README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
buildenv
22
========
33

4-
A tool for generating environment exports from a YAML file. Variables can be set in plain test, or by specifying vault key-value (version 2) paths and keys (`kv_secrets`) or the older generic / kv paths (`secrets`) where the key name "value" is assumed.
4+
A tool for generating environment exports from a YAML file. Variables can be set in plain test, or by specifying vault key-value (version 2) paths and keys (`kv_secrets`) or the older generic / kv paths (`secrets`) where the key name "value" is assumed. Buildenv will autodetect between version 2 and version 1 `kv_secret` paths _unless it can't read the mount details_. For that case, `kv_secrets` will assume version 2, and `kv1_secrets` will use version 1.
55

66
Usage
77
-----
@@ -29,6 +29,11 @@ kv_secrets:
2929
vars:
3030
KV_GENERIC: "value"
3131

32+
kv1_secrets:
33+
- path: "old/test"
34+
vars:
35+
KV1SPECIFIC: "value"
36+
3237
environments:
3338
stage:
3439
vars:
@@ -49,14 +54,15 @@ environments:
4954
5055
Output would look like this:
5156
52-
```
57+
```bash
5358
% buildenv -c -e stage -d ndc_one
5459
# Global Variables
5560
export GLOBAL="global"
5661
export KV2_ONE="1" # Path: secret/test, Key: one
5762
export KV2_TWO="2" # Path: secret/test, Key: two
5863
export KV1="old" # Path: old/test, Key: value
5964
export KV_GENERIC="generic" # Path: gen/test, Key: value
65+
export KV1SPECIFIC="old" # Path: old/test, Key: value
6066
export GENERIC_SECRET="generic" # Path: gen/test, Key: value
6167
export KV_SECRET="old" # Path: old/test, Key: value
6268
export KV2_SECRET="default" # Path: secret/oldstyle, Key: value

reader/reader.go

+123-34
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import (
1111
)
1212

1313
type Reader struct {
14-
client *vault.Client
15-
mounts Mounts
14+
client *vault.Client
15+
canDetectMounts bool
16+
mounts Mounts
1617
}
1718

1819
type EnvVars map[string]string
@@ -65,21 +66,21 @@ func (s KVSecretBlock) GetOutput(ctx context.Context, r *Reader) (OutputList, er
6566
}
6667
}
6768

68-
// The first thing we need to do is get the mount point for the KV engine
69+
// Get the Mount Point for the Secret
6970
mountPoint, secretPath := r.MountAndPath(s.Path)
7071
if mountPoint == "" {
7172
return nil, fmt.Errorf("no mount point found for path %s", s.Path)
7273
}
7374

74-
// V2 KV Secrets
75-
if r.mounts[mountPoint].Type == "kv" && r.mounts[mountPoint].Version == "2" {
75+
// Assume v2 if we can detect mounts and it's a KV engine, or if it's explicitly v2
76+
if !r.canDetectMounts || (r.mounts[mountPoint].Type == "kv" && r.mounts[mountPoint].Version == "2") {
7677
// Get Secret
7778
resp, err := r.client.Secrets.KvV2Read(ctx, secretPath, vault.WithMountPath(mountPoint))
7879
if err != nil {
7980
if vault.IsErrorStatus(err, http.StatusNotFound) {
80-
return nil, fmt.Errorf("secret does not exist: '%s'", s.Path)
81+
return nil, fmt.Errorf("kv2 secret does not exist: '%s'", s.Path)
8182
}
82-
return nil, fmt.Errorf("error reading path '%s': %w", s.Path, err)
83+
return nil, fmt.Errorf("error reading kv2 path '%s': %w", s.Path, err)
8384
}
8485
// For testing purposes, we want to order this
8586
envVars := []string{}
@@ -103,7 +104,7 @@ func (s KVSecretBlock) GetOutput(ctx context.Context, r *Reader) (OutputList, er
103104
// Treat it as a KVv1 secret
104105
resp, err := r.client.Secrets.KvV1Read(ctx, secretPath, vault.WithMountPath(mountPoint))
105106
if err != nil {
106-
return nil, fmt.Errorf("error reading path %s: %w", s.Path, err)
107+
return nil, fmt.Errorf("error reading kv1 path %s: %w", s.Path, err)
107108
}
108109
for varName, varKey := range s.Vars {
109110
if _, hasValue := resp.Data[varKey]; !hasValue {
@@ -133,23 +134,83 @@ func (s KVSecrets) GetOutput(ctx context.Context, r *Reader) (OutputList, error)
133134
return output, nil
134135
}
135136

137+
// KV1Secrets is a list of Key-Value Version 1 Secrets
138+
type KV1Secrets []KV1SecretBlock
139+
140+
type KV1SecretBlock struct {
141+
Path string
142+
Vars KVSecret
143+
}
144+
145+
func (s KV1SecretBlock) GetOutput(ctx context.Context, r *Reader) (OutputList, error) {
146+
output := OutputList{}
147+
148+
// Initialize the Vault Client if Necessary
149+
if r.client == nil {
150+
err := r.InitVault()
151+
if err != nil {
152+
return nil, err
153+
}
154+
}
155+
156+
// The first thing we need to do is get the mount point for the KV engine
157+
mountPoint, secretPath := r.MountAndPath(s.Path)
158+
if mountPoint == "" {
159+
return nil, fmt.Errorf("no mount point found for path %s", s.Path)
160+
}
161+
162+
// Treat it as a KVv1 secret
163+
resp, err := r.client.Secrets.KvV1Read(ctx, secretPath, vault.WithMountPath(mountPoint))
164+
if err != nil {
165+
return nil, fmt.Errorf("error reading kv1 path %s: %w", s.Path, err)
166+
}
167+
for varName, varKey := range s.Vars {
168+
if _, hasValue := resp.Data[varKey]; !hasValue {
169+
return nil, fmt.Errorf("key %s not found in path %s", varKey, s.Path)
170+
}
171+
val := fmt.Sprintf("%s", resp.Data[varKey])
172+
output = append(output, Output{
173+
Key: varName,
174+
Value: val,
175+
Comment: fmt.Sprintf("Path: %s, Key: %s", s.Path, varKey),
176+
})
177+
}
178+
179+
return output, nil
180+
}
181+
182+
func (s KV1Secrets) GetOutput(ctx context.Context, r *Reader) (OutputList, error) {
183+
output := OutputList{}
184+
for _, block := range s {
185+
blockOutput, err := block.GetOutput(ctx, r)
186+
if err != nil {
187+
return nil, err
188+
}
189+
output = append(output, blockOutput...)
190+
}
191+
return output, nil
192+
}
193+
136194
type DC struct {
137-
Vars EnvVars `yaml:"vars,omitempty"`
138-
Secrets Secrets `yaml:"secrets,omitempty"`
139-
KVSecrets KVSecrets `yaml:"kv_secrets,omitempty"`
195+
Vars EnvVars `yaml:"vars,omitempty"`
196+
Secrets Secrets `yaml:"secrets,omitempty"`
197+
KVSecrets KVSecrets `yaml:"kv_secrets,omitempty"`
198+
KV1Secrets KVSecrets `yaml:"kv1_secrets,omitempty"`
140199
}
141200

142201
type Environment struct {
143-
Vars EnvVars `yaml:"vars,omitempty"`
144-
Secrets Secrets `yaml:"secrets,omitempty"`
145-
KVSecrets KVSecrets `yaml:"kv_secrets,omitempty"`
146-
Dcs map[string]DC `yaml:"dcs,omitempty"`
202+
Vars EnvVars `yaml:"vars,omitempty"`
203+
Secrets Secrets `yaml:"secrets,omitempty"`
204+
KVSecrets KVSecrets `yaml:"kv_secrets,omitempty"`
205+
KV1Secrets KVSecrets `yaml:"kv1_secrets,omitempty"`
206+
Dcs map[string]DC `yaml:"dcs,omitempty"`
147207
}
148208

149209
type Variables struct {
150210
Vars EnvVars `yaml:"vars,omitempty"`
151211
Secrets Secrets `yaml:"secrets,omitempty"`
152212
KVSecrets KVSecrets `yaml:"kv_secrets,omitempty"`
213+
KV1Secrets KVSecrets `yaml:"kv1_secrets,omitempty"`
153214
Environments map[string]Environment `yaml:"environments,omitempty"`
154215
}
155216

@@ -192,29 +253,30 @@ func (r *Reader) InitVault() error {
192253
return err
193254
}
194255
r.client = vaultClient
256+
r.canDetectMounts = false
195257

196258
// Get mount info
197259
resp, err := vaultClient.System.MountsListSecretsEngines(context.Background())
198-
if err != nil {
199-
return fmt.Errorf("failure reading secret mounts: %w", err)
200-
}
201-
202-
mounts := Mounts{}
203-
for mount, details := range resp.Data {
204-
detailMap := details.(map[string]interface{})
205-
thisMount := MountInfo{
206-
Type: detailMap["type"].(string),
207-
}
208-
if options, hasOptions := detailMap["options"]; hasOptions && options != nil {
209-
optionMap := options.(map[string]interface{})
210-
if version, hasVersion := optionMap["version"]; hasVersion {
211-
thisMount.Version = version.(string)
260+
if err == nil {
261+
r.canDetectMounts = true
262+
mounts := Mounts{}
263+
for mount, details := range resp.Data {
264+
detailMap := details.(map[string]interface{})
265+
thisMount := MountInfo{
266+
Type: detailMap["type"].(string),
212267
}
268+
if options, hasOptions := detailMap["options"]; hasOptions && options != nil {
269+
optionMap := options.(map[string]interface{})
270+
if version, hasVersion := optionMap["version"]; hasVersion {
271+
thisMount.Version = version.(string)
272+
}
273+
}
274+
mounts[mount] = thisMount
213275
}
214-
mounts[mount] = thisMount
276+
277+
r.mounts = mounts
215278
}
216279

217-
r.mounts = mounts
218280
return nil
219281
}
220282

@@ -223,10 +285,16 @@ func NewReader() (*Reader, error) {
223285
}
224286

225287
func (r *Reader) MountAndPath(path string) (string, string) {
226-
for mount := range r.mounts {
227-
if strings.HasPrefix(path, mount) {
228-
return mount, strings.TrimPrefix(path, mount)
288+
if r.canDetectMounts {
289+
for mount := range r.mounts {
290+
if strings.HasPrefix(path, mount) {
291+
return mount, strings.TrimPrefix(path, mount)
292+
}
229293
}
294+
} else {
295+
// Take the first part of the path
296+
parts := strings.SplitN(path, "/", 2)
297+
return parts[0], parts[1]
230298
}
231299
return "", ""
232300
}
@@ -246,6 +314,11 @@ func (r *Reader) Read(ctx context.Context, input *Variables, env string, dc stri
246314
return nil, fmt.Errorf("kv secret error: %w", err)
247315
}
248316
output = append(output, kvOut...)
317+
kv1Out, err := input.KV1Secrets.GetOutput(ctx, r)
318+
if err != nil {
319+
return nil, fmt.Errorf("kv1 secret error: %w", err)
320+
}
321+
output = append(output, kv1Out...)
249322
secretOut, err := input.Secrets.GetOutput(ctx, r)
250323
if err != nil {
251324
return nil, fmt.Errorf("secret error: %w", err)
@@ -258,11 +331,19 @@ func (r *Reader) Read(ctx context.Context, input *Variables, env string, dc stri
258331
Comment: fmt.Sprintf("Environment: %s", env),
259332
})
260333
output = append(output, input.Environments[env].Vars.GetOutput()...)
334+
// KV (autodetect or v2)
261335
kvOut, err := input.Environments[env].KVSecrets.GetOutput(ctx, r)
262336
if err != nil {
263337
return nil, fmt.Errorf("kv secret error: %w", err)
264338
}
265339
output = append(output, kvOut...)
340+
// KV1
341+
kv1Out, err := input.Environments[env].KV1Secrets.GetOutput(ctx, r)
342+
if err != nil {
343+
return nil, fmt.Errorf("kv1 secret error: %w", err)
344+
}
345+
output = append(output, kv1Out...)
346+
// Secrets
266347
secretOut, err := input.Environments[env].Secrets.GetOutput(ctx, r)
267348
if err != nil {
268349
return nil, fmt.Errorf("secret error: %w", err)
@@ -276,11 +357,19 @@ func (r *Reader) Read(ctx context.Context, input *Variables, env string, dc stri
276357
Comment: fmt.Sprintf("Datacenter: %s", dc),
277358
})
278359
output = append(output, input.Environments[env].Dcs[dc].Vars.GetOutput()...)
360+
// KV (autodetect or v2)
279361
kvOut, err := input.Environments[env].Dcs[dc].KVSecrets.GetOutput(ctx, r)
280362
if err != nil {
281363
return nil, fmt.Errorf("kv secret error: %w", err)
282364
}
283365
output = append(output, kvOut...)
366+
// KV1
367+
kv1Out, err := input.Environments[env].Dcs[dc].KV1Secrets.GetOutput(ctx, r)
368+
if err != nil {
369+
return nil, fmt.Errorf("kv1 secret error: %w", err)
370+
}
371+
output = append(output, kv1Out...)
372+
// Secrets
284373
secretOut, err := input.Environments[env].Dcs[dc].Secrets.GetOutput(ctx, r)
285374
if err != nil {
286375
return nil, fmt.Errorf("secret error: %w", err)

0 commit comments

Comments
 (0)