1package dependency
2
3import (
4	"log"
5	"math/rand"
6	"time"
7
8	"encoding/json"
9	"github.com/hashicorp/vault/api"
10)
11
12var (
13	// VaultDefaultLeaseDuration is the default lease duration in seconds.
14	VaultDefaultLeaseDuration = 5 * time.Minute
15)
16
17// Secret is the structure returned for every secret within Vault.
18type Secret struct {
19	// The request ID that generated this response
20	RequestID string
21
22	LeaseID       string
23	LeaseDuration int
24	Renewable     bool
25
26	// Data is the actual contents of the secret. The format of the data
27	// is arbitrary and up to the secret backend.
28	Data map[string]interface{}
29
30	// Warnings contains any warnings related to the operation. These
31	// are not issues that caused the command to fail, but that the
32	// client should be aware of.
33	Warnings []string
34
35	// Auth, if non-nil, means that there was authentication information
36	// attached to this response.
37	Auth *SecretAuth
38
39	// WrapInfo, if non-nil, means that the initial response was wrapped in the
40	// cubbyhole of the given token (which has a TTL of the given number of
41	// seconds)
42	WrapInfo *SecretWrapInfo
43}
44
45// SecretAuth is the structure containing auth information if we have it.
46type SecretAuth struct {
47	ClientToken string
48	Accessor    string
49	Policies    []string
50	Metadata    map[string]string
51
52	LeaseDuration int
53	Renewable     bool
54}
55
56// SecretWrapInfo contains wrapping information if we have it. If what is
57// contained is an authentication token, the accessor for the token will be
58// available in WrappedAccessor.
59type SecretWrapInfo struct {
60	Token           string
61	TTL             int
62	CreationTime    time.Time
63	WrappedAccessor string
64}
65
66//
67type renewer interface {
68	Dependency
69	stopChan() chan struct{}
70	secrets() (*Secret, *api.Secret)
71}
72
73func renewSecret(clients *ClientSet, d renewer) error {
74	log.Printf("[TRACE] %s: starting renewer", d)
75
76	secret, vaultSecret := d.secrets()
77	renewer, err := clients.Vault().NewRenewer(&api.RenewerInput{
78		Secret: vaultSecret,
79	})
80	if err != nil {
81		return err
82	}
83	go renewer.Renew()
84	defer renewer.Stop()
85
86	for {
87		select {
88		case err := <-renewer.DoneCh():
89			if err != nil {
90				log.Printf("[WARN] %s: failed to renew: %s", d, err)
91			}
92			log.Printf("[WARN] %s: renewer done (maybe the lease expired)", d)
93			return nil
94		case renewal := <-renewer.RenewCh():
95			log.Printf("[TRACE] %s: successfully renewed", d)
96			printVaultWarnings(d, renewal.Secret.Warnings)
97			updateSecret(secret, renewal.Secret)
98		case <-d.stopChan():
99			return ErrStopped
100		}
101	}
102}
103
104// leaseCheckWait accepts a secret and returns the recommended amount of
105// time to sleep.
106func leaseCheckWait(s *Secret) time.Duration {
107	// Handle whether this is an auth or a regular secret.
108	base := s.LeaseDuration
109	if s.Auth != nil && s.Auth.LeaseDuration > 0 {
110		base = s.Auth.LeaseDuration
111	}
112
113	// Handle if this is a certificate with no lease
114	if _, ok := s.Data["certificate"]; ok && s.LeaseID == "" {
115		if expInterface, ok := s.Data["expiration"]; ok {
116			if expData, err := expInterface.(json.Number).Int64(); err == nil {
117				base = int(expData - time.Now().Unix())
118				log.Printf("[DEBUG] Found certificate and set lease duration to %d seconds", base)
119			}
120		}
121	}
122
123	// Handle if this is a secret with a rotation period.  If this is a rotating secret,
124	// the rotating secret's TTL will be the duration to sleep before rendering the new secret.
125	var rotatingSecret bool
126	if _, ok := s.Data["rotation_period"]; ok && s.LeaseID == "" {
127		if ttlInterface, ok := s.Data["ttl"]; ok {
128			if ttlData, err := ttlInterface.(json.Number).Int64(); err == nil {
129				log.Printf("[DEBUG] Found rotation_period and set lease duration to %d seconds", ttlData)
130				// Add a second for cushion
131				base = int(ttlData) + 1
132				rotatingSecret = true
133			}
134		}
135	}
136
137	// Ensure we have a lease duration, since sometimes this can be zero.
138	if base <= 0 {
139		base = int(VaultDefaultLeaseDuration.Seconds())
140	}
141
142	// Convert to float seconds.
143	sleep := float64(time.Duration(base) * time.Second)
144
145	if vaultSecretRenewable(s) {
146		// Renew at 1/3 the remaining lease. This will give us an opportunity to retry
147		// at least one more time should the first renewal fail.
148		sleep = sleep / 3.0
149
150		// Use some randomness so many clients do not hit Vault simultaneously.
151		sleep = sleep * (rand.Float64() + 1) / 2.0
152	} else if !rotatingSecret {
153		// If the secret doesn't have a rotation period, this is a non-renewable leased
154		// secret.
155		// For non-renewable leases set the renew duration to use much of the secret
156		// lease as possible. Use a stagger over 85%-95% of the lease duration so that
157		// many clients do not hit Vault simultaneously.
158		sleep = sleep * (.85 + rand.Float64()*0.1)
159	}
160
161	return time.Duration(sleep)
162}
163
164// printVaultWarnings prints warnings for a given dependency.
165func printVaultWarnings(d Dependency, warnings []string) {
166	for _, w := range warnings {
167		log.Printf("[WARN] %s: %s", d, w)
168	}
169}
170
171// vaultSecretRenewable determines if the given secret is renewable.
172func vaultSecretRenewable(s *Secret) bool {
173	if s.Auth != nil {
174		return s.Auth.Renewable
175	}
176	return s.Renewable
177}
178
179// transformSecret transforms an api secret into our secret. This does not deep
180// copy underlying deep data structures, so it's not safe to modify the vault
181// secret as that may modify the data in the transformed secret.
182func transformSecret(theirs *api.Secret) *Secret {
183	var ours Secret
184	updateSecret(&ours, theirs)
185	return &ours
186}
187
188// updateSecret updates our secret with the new data from the api, careful to
189// not overwrite missing data. Renewals don't include the original secret, and
190// we don't want to delete that data accidentally.
191func updateSecret(ours *Secret, theirs *api.Secret) {
192	if theirs.RequestID != "" {
193		ours.RequestID = theirs.RequestID
194	}
195
196	if theirs.LeaseID != "" {
197		ours.LeaseID = theirs.LeaseID
198	}
199
200	if theirs.LeaseDuration != 0 {
201		ours.LeaseDuration = theirs.LeaseDuration
202	}
203
204	if theirs.Renewable {
205		ours.Renewable = theirs.Renewable
206	}
207
208	if len(theirs.Data) != 0 {
209		ours.Data = theirs.Data
210	}
211
212	if len(theirs.Warnings) != 0 {
213		ours.Warnings = theirs.Warnings
214	}
215
216	if theirs.Auth != nil {
217		if ours.Auth == nil {
218			ours.Auth = &SecretAuth{}
219		}
220
221		if theirs.Auth.ClientToken != "" {
222			ours.Auth.ClientToken = theirs.Auth.ClientToken
223		}
224
225		if theirs.Auth.Accessor != "" {
226			ours.Auth.Accessor = theirs.Auth.Accessor
227		}
228
229		if len(theirs.Auth.Policies) != 0 {
230			ours.Auth.Policies = theirs.Auth.Policies
231		}
232
233		if len(theirs.Auth.Metadata) != 0 {
234			ours.Auth.Metadata = theirs.Auth.Metadata
235		}
236
237		if theirs.Auth.LeaseDuration != 0 {
238			ours.Auth.LeaseDuration = theirs.Auth.LeaseDuration
239		}
240
241		if theirs.Auth.Renewable {
242			ours.Auth.Renewable = theirs.Auth.Renewable
243		}
244	}
245
246	if theirs.WrapInfo != nil {
247		if ours.WrapInfo == nil {
248			ours.WrapInfo = &SecretWrapInfo{}
249		}
250
251		if theirs.WrapInfo.Token != "" {
252			ours.WrapInfo.Token = theirs.WrapInfo.Token
253		}
254
255		if theirs.WrapInfo.TTL != 0 {
256			ours.WrapInfo.TTL = theirs.WrapInfo.TTL
257		}
258
259		if !theirs.WrapInfo.CreationTime.IsZero() {
260			ours.WrapInfo.CreationTime = theirs.WrapInfo.CreationTime
261		}
262
263		if theirs.WrapInfo.WrappedAccessor != "" {
264			ours.WrapInfo.WrappedAccessor = theirs.WrapInfo.WrappedAccessor
265		}
266	}
267}
268
269func isKVv2(client *api.Client, path string) (string, bool, error) {
270	// We don't want to use a wrapping call here so save any custom value and
271	// restore after
272	currentWrappingLookupFunc := client.CurrentWrappingLookupFunc()
273	client.SetWrappingLookupFunc(nil)
274	defer client.SetWrappingLookupFunc(currentWrappingLookupFunc)
275	currentOutputCurlString := client.OutputCurlString()
276	client.SetOutputCurlString(false)
277	defer client.SetOutputCurlString(currentOutputCurlString)
278
279	r := client.NewRequest("GET", "/v1/sys/internal/ui/mounts/"+path)
280	resp, err := client.RawRequest(r)
281	if resp != nil {
282		defer resp.Body.Close()
283	}
284	if err != nil {
285		// If we get a 404 we are using an older version of vault, default to
286		// version 1
287		if resp != nil && resp.StatusCode == 404 {
288			return "", false, nil
289		}
290
291		// anonymous requests may fail to access /sys/internal/ui path
292		// Vault v1.1.3 returns 500 status code but may return 4XX in future
293		if client.Token() == "" {
294			return "", false, nil
295		}
296
297		return "", false, err
298	}
299
300	secret, err := api.ParseSecret(resp.Body)
301	if err != nil {
302		return "", false, err
303	}
304	var mountPath string
305	if mountPathRaw, ok := secret.Data["path"]; ok {
306		mountPath = mountPathRaw.(string)
307	}
308	var mountType string
309	if mountTypeRaw, ok := secret.Data["type"]; ok {
310		mountType = mountTypeRaw.(string)
311	}
312	options := secret.Data["options"]
313	if options == nil {
314		return mountPath, false, nil
315	}
316	versionRaw := options.(map[string]interface{})["version"]
317	if versionRaw == nil {
318		return mountPath, false, nil
319	}
320	version := versionRaw.(string)
321	switch version {
322	case "", "1":
323		return mountPath, false, nil
324	case "2":
325		return mountPath, mountType == "kv", nil
326	}
327
328	return mountPath, false, nil
329}
330