1package dependency
2
3import (
4	"fmt"
5	"log"
6	"net/url"
7	"strings"
8	"time"
9
10	"github.com/hashicorp/vault/api"
11	"github.com/pkg/errors"
12)
13
14var (
15	// Ensure implements
16	_ Dependency = (*VaultReadQuery)(nil)
17)
18
19// VaultReadQuery is the dependency to Vault for a secret
20type VaultReadQuery struct {
21	stopCh  chan struct{}
22	sleepCh chan time.Duration
23
24	rawPath     string
25	queryValues url.Values
26	secret      *Secret
27	isKVv2      *bool
28	secretPath  string
29
30	// vaultSecret is the actual Vault secret which we are renewing
31	vaultSecret *api.Secret
32}
33
34// NewVaultReadQuery creates a new datacenter dependency.
35func NewVaultReadQuery(s string) (*VaultReadQuery, error) {
36	s = strings.TrimSpace(s)
37	s = strings.Trim(s, "/")
38	if s == "" {
39		return nil, fmt.Errorf("vault.read: invalid format: %q", s)
40	}
41
42	secretURL, err := url.Parse(s)
43	if err != nil {
44		return nil, err
45	}
46
47	return &VaultReadQuery{
48		stopCh:      make(chan struct{}, 1),
49		sleepCh:     make(chan time.Duration, 1),
50		rawPath:     secretURL.Path,
51		queryValues: secretURL.Query(),
52	}, nil
53}
54
55// Fetch queries the Vault API
56func (d *VaultReadQuery) Fetch(clients *ClientSet, opts *QueryOptions,
57) (interface{}, *ResponseMetadata, error) {
58	select {
59	case <-d.stopCh:
60		return nil, nil, ErrStopped
61	default:
62	}
63	select {
64	case dur := <-d.sleepCh:
65		time.Sleep(dur)
66	default:
67	}
68
69	firstRun := d.secret == nil
70
71	if !firstRun && vaultSecretRenewable(d.secret) {
72		err := renewSecret(clients, d)
73		if err != nil {
74			return nil, nil, errors.Wrap(err, d.String())
75		}
76	}
77
78	err := d.fetchSecret(clients, opts)
79	if err != nil {
80		return nil, nil, errors.Wrap(err, d.String())
81	}
82
83	if !vaultSecretRenewable(d.secret) {
84		dur := leaseCheckWait(d.secret)
85		log.Printf("[TRACE] %s: non-renewable secret, set sleep for %s", d, dur)
86		d.sleepCh <- dur
87	}
88
89	return respWithMetadata(d.secret)
90}
91
92func (d *VaultReadQuery) fetchSecret(clients *ClientSet, opts *QueryOptions,
93) error {
94	opts = opts.Merge(&QueryOptions{})
95	vaultSecret, err := d.readSecret(clients, opts)
96	if err == nil {
97		printVaultWarnings(d, vaultSecret.Warnings)
98		d.vaultSecret = vaultSecret
99		// the cloned secret which will be exposed to the template
100		d.secret = transformSecret(vaultSecret)
101	}
102	return err
103}
104
105func (d *VaultReadQuery) stopChan() chan struct{} {
106	return d.stopCh
107}
108
109func (d *VaultReadQuery) secrets() (*Secret, *api.Secret) {
110	return d.secret, d.vaultSecret
111}
112
113// CanShare returns if this dependency is shareable.
114func (d *VaultReadQuery) CanShare() bool {
115	return false
116}
117
118// Stop halts the given dependency's fetch.
119func (d *VaultReadQuery) Stop() {
120	close(d.stopCh)
121}
122
123// String returns the human-friendly version of this dependency.
124func (d *VaultReadQuery) String() string {
125	return fmt.Sprintf("vault.read(%s)", d.rawPath)
126}
127
128// Type returns the type of this dependency.
129func (d *VaultReadQuery) Type() Type {
130	return TypeVault
131}
132
133func (d *VaultReadQuery) readSecret(clients *ClientSet, opts *QueryOptions) (*api.Secret, error) {
134	vaultClient := clients.Vault()
135
136	// Check whether this secret refers to a KV v2 entry if we haven't yet.
137	if d.isKVv2 == nil {
138		mountPath, isKVv2, err := isKVv2(vaultClient, d.rawPath)
139		if err != nil {
140			log.Printf("[WARN] %s: failed to check if %s is KVv2, "+
141				"assume not: %s", d, d.rawPath, err)
142			isKVv2 = false
143			d.secretPath = d.rawPath
144		} else if isKVv2 {
145			d.secretPath = addPrefixToVKVPath(d.rawPath, mountPath, "data")
146		} else {
147			d.secretPath = d.rawPath
148		}
149		d.isKVv2 = &isKVv2
150	}
151
152	queryString := d.queryValues.Encode()
153	log.Printf("[TRACE] %s: GET %s", d, &url.URL{
154		Path:     "/v1/" + d.secretPath,
155		RawQuery: queryString,
156	})
157	vaultSecret, err := vaultClient.Logical().ReadWithData(d.secretPath,
158		d.queryValues)
159
160	if err != nil {
161		return nil, errors.Wrap(err, d.String())
162	}
163	if vaultSecret == nil || deletedKVv2(vaultSecret) {
164		return nil, fmt.Errorf("no secret exists at %s", d.secretPath)
165	}
166	return vaultSecret, nil
167}
168
169func deletedKVv2(s *api.Secret) bool {
170	switch md := s.Data["metadata"].(type) {
171	case map[string]interface{}:
172		return md["deletion_time"] != ""
173	}
174	return false
175}
176