1package dependency 2 3import ( 4 "crypto/sha1" 5 "fmt" 6 "io" 7 "log" 8 "net/url" 9 "sort" 10 "strings" 11 "time" 12 13 "github.com/hashicorp/vault/api" 14 "github.com/pkg/errors" 15) 16 17var ( 18 // Ensure implements 19 _ Dependency = (*VaultWriteQuery)(nil) 20) 21 22// VaultWriteQuery is the dependency to Vault for a secret 23type VaultWriteQuery struct { 24 stopCh chan struct{} 25 sleepCh chan time.Duration 26 27 path string 28 data map[string]interface{} 29 dataHash string 30 secret *Secret 31 32 // vaultSecret is the actual Vault secret which we are renewing 33 vaultSecret *api.Secret 34} 35 36// NewVaultWriteQuery creates a new datacenter dependency. 37func NewVaultWriteQuery(s string, d map[string]interface{}) (*VaultWriteQuery, error) { 38 s = strings.TrimSpace(s) 39 s = strings.Trim(s, "/") 40 if s == "" { 41 return nil, fmt.Errorf("vault.write: invalid format: %q", s) 42 } 43 44 return &VaultWriteQuery{ 45 stopCh: make(chan struct{}, 1), 46 sleepCh: make(chan time.Duration, 1), 47 path: s, 48 data: d, 49 dataHash: sha1Map(d), 50 }, nil 51} 52 53// Fetch queries the Vault API 54func (d *VaultWriteQuery) Fetch(clients *ClientSet, opts *QueryOptions, 55) (interface{}, *ResponseMetadata, error) { 56 select { 57 case <-d.stopCh: 58 return nil, nil, ErrStopped 59 default: 60 } 61 select { 62 case dur := <-d.sleepCh: 63 time.Sleep(dur) 64 default: 65 } 66 67 firstRun := d.secret == nil 68 69 if !firstRun && vaultSecretRenewable(d.secret) { 70 err := renewSecret(clients, d) 71 if err != nil { 72 return nil, nil, errors.Wrap(err, d.String()) 73 } 74 } 75 76 opts = opts.Merge(&QueryOptions{}) 77 vaultSecret, err := d.writeSecret(clients, opts) 78 if err != nil { 79 return nil, nil, errors.Wrap(err, d.String()) 80 } 81 82 // vaultSecret == nil when writing to KVv1 engines 83 if vaultSecret == nil { 84 return respWithMetadata(d.secret) 85 } 86 87 printVaultWarnings(d, vaultSecret.Warnings) 88 d.vaultSecret = vaultSecret 89 // cloned secret which will be exposed to the template 90 d.secret = transformSecret(vaultSecret) 91 92 if !vaultSecretRenewable(d.secret) { 93 dur := leaseCheckWait(d.secret) 94 log.Printf("[TRACE] %s: non-renewable secret, set sleep for %s", d, dur) 95 d.sleepCh <- dur 96 } 97 98 return respWithMetadata(d.secret) 99} 100 101// meet renewer interface 102func (d *VaultWriteQuery) stopChan() chan struct{} { 103 return d.stopCh 104} 105 106func (d *VaultWriteQuery) secrets() (*Secret, *api.Secret) { 107 return d.secret, d.vaultSecret 108} 109 110// CanShare returns if this dependency is shareable. 111func (d *VaultWriteQuery) CanShare() bool { 112 return false 113} 114 115// Stop halts the given dependency's fetch. 116func (d *VaultWriteQuery) Stop() { 117 close(d.stopCh) 118} 119 120// String returns the human-friendly version of this dependency. 121func (d *VaultWriteQuery) String() string { 122 return fmt.Sprintf("vault.write(%s -> %s)", d.path, d.dataHash) 123} 124 125// Type returns the type of this dependency. 126func (d *VaultWriteQuery) Type() Type { 127 return TypeVault 128} 129 130// sha1Map returns the sha1 hash of the data in the map. The reason this data is 131// hashed is because it appears in the output and could contain sensitive 132// information. 133func sha1Map(m map[string]interface{}) string { 134 keys := make([]string, 0, len(m)) 135 for k, _ := range m { 136 keys = append(keys, k) 137 } 138 sort.Strings(keys) 139 140 h := sha1.New() 141 for _, k := range keys { 142 io.WriteString(h, fmt.Sprintf("%s=%q", k, m[k])) 143 } 144 145 return fmt.Sprintf("%.4x", h.Sum(nil)) 146} 147 148func (d *VaultWriteQuery) printWarnings(warnings []string) { 149 for _, w := range warnings { 150 log.Printf("[WARN] %s: %s", d, w) 151 } 152} 153 154func (d *VaultWriteQuery) writeSecret(clients *ClientSet, opts *QueryOptions) (*api.Secret, error) { 155 log.Printf("[TRACE] %s: PUT %s", d, &url.URL{ 156 Path: "/v1/" + d.path, 157 RawQuery: opts.String(), 158 }) 159 160 data := d.data 161 162 _, isv2, _ := isKVv2(clients.Vault(), d.path) 163 if isv2 { 164 data = map[string]interface{}{"data": d.data} 165 } 166 167 vaultSecret, err := clients.Vault().Logical().Write(d.path, data) 168 if err != nil { 169 return nil, errors.Wrap(err, d.String()) 170 } 171 // vaultSecret is always nil when KVv1 engine (isv2==false) 172 if isv2 && vaultSecret == nil { 173 return nil, fmt.Errorf("no secret exists at %s", d.path) 174 } 175 176 return vaultSecret, nil 177} 178