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