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