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