forked from hashicorp/consul-template
/
vault_read.go
171 lines (141 loc) · 4.57 KB
/
vault_read.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package dependency
import (
"fmt"
"log"
"net/url"
"strings"
"time"
"github.com/pkg/errors"
)
var (
// Ensure implements
_ Dependency = (*VaultReadQuery)(nil)
)
// VaultReadQuery is the dependency to Vault for a secret
type VaultReadQuery struct {
stopCh chan struct{}
path string
secret *Secret
}
// NewVaultReadQuery creates a new datacenter dependency.
func NewVaultReadQuery(s string) (*VaultReadQuery, error) {
s = strings.TrimSpace(s)
s = strings.Trim(s, "/")
if s == "" {
return nil, fmt.Errorf("vault.read: invalid format: %q", s)
}
return &VaultReadQuery{
stopCh: make(chan struct{}, 1),
path: s,
}, nil
}
// Fetch queries the Vault API
func (d *VaultReadQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) {
select {
case <-d.stopCh:
return nil, nil, ErrStopped
default:
}
opts = opts.Merge(&QueryOptions{})
// If this is not the first query and we have a lease duration, sleep until we
// try to renew.
if opts.WaitIndex != 0 && d.secret != nil && d.secret.LeaseDuration != 0 {
dur := vaultRenewDuration(d.secret.LeaseDuration)
log.Printf("[TRACE] %s: long polling for %s", d, dur)
select {
case <-d.stopCh:
return nil, nil, ErrStopped
case <-time.After(dur):
}
}
// Attempt to renew the secret. If we do not have a secret or if that secret
// is not renewable, we will attempt a (re-)read later.
if d.secret != nil && d.secret.LeaseID != "" && d.secret.Renewable {
log.Printf("[TRACE] %s: PUT %s", d, &url.URL{
Path: "/v1/sys/renew/" + d.secret.LeaseID,
RawQuery: opts.String(),
})
renewal, err := clients.Vault().Sys().Renew(d.secret.LeaseID, 0)
if err == nil {
log.Printf("[TRACE] %s: successfully renewed %s", d, d.secret.LeaseID)
// Print any warnings
d.printWarnings(renewal.Warnings)
secret := &Secret{
RequestID: renewal.RequestID,
LeaseID: renewal.LeaseID,
LeaseDuration: d.secret.LeaseDuration,
Renewable: renewal.Renewable,
Data: d.secret.Data,
}
// For some older versions of Vault, the renewal did not include the
// remaining lease duration, so just use the original lease duration,
// because it's the best we can do.
if renewal.LeaseDuration != 0 {
secret.LeaseDuration = renewal.LeaseDuration
}
d.secret = secret
// If the remaining time on the lease is less than or equal to our
// configured grace period, generate a new credential now. This will help
// minimize downtime, since Vault will revoke credentials immediately
// when their maximum TTL expires.
remaining := time.Duration(d.secret.LeaseDuration) * time.Second
if remaining <= opts.VaultGrace {
log.Printf("[DEBUG] %s: remaining lease (%s) < grace (%s), acquiring new",
d, remaining, opts.VaultGrace)
return d.readSecret(clients, opts)
}
return respWithMetadata(secret)
}
// The renewal failed for some reason.
log.Printf("[WARN] %s: failed to renew %s: %s", d, d.secret.LeaseID, err)
}
// If we got this far, we either didn't have a secret to renew, the secret was
// not renewable, or the renewal failed, so attempt a fresh read.
return d.readSecret(clients, opts)
}
// CanShare returns if this dependency is shareable.
func (d *VaultReadQuery) CanShare() bool {
return false
}
// Stop halts the given dependency's fetch.
func (d *VaultReadQuery) Stop() {
close(d.stopCh)
}
// String returns the human-friendly version of this dependency.
func (d *VaultReadQuery) String() string {
return fmt.Sprintf("vault.read(%s)", d.path)
}
// Type returns the type of this dependency.
func (d *VaultReadQuery) Type() Type {
return TypeVault
}
func (d *VaultReadQuery) printWarnings(warnings []string) {
for _, w := range warnings {
log.Printf("[WARN] %s: %s", d, w)
}
}
func (d *VaultReadQuery) readSecret(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) {
log.Printf("[TRACE] %s: GET %s", d, &url.URL{
Path: "/v1/" + d.path,
RawQuery: opts.String(),
})
vaultSecret, err := clients.Vault().Logical().Read(d.path)
if err != nil {
return nil, nil, errors.Wrap(err, d.String())
}
// The secret could be nil if it does not exist.
if vaultSecret == nil {
return nil, nil, fmt.Errorf("%s: no secret exists at %s", d, d.path)
}
// Print any warnings.
d.printWarnings(vaultSecret.Warnings)
// Create our cloned secret.
secret := &Secret{
LeaseID: vaultSecret.LeaseID,
LeaseDuration: leaseDurationOrDefault(vaultSecret.LeaseDuration),
Renewable: vaultSecret.Renewable,
Data: vaultSecret.Data,
}
d.secret = secret
return respWithMetadata(secret)
}