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