/
credentials.go
120 lines (106 loc) · 3.01 KB
/
credentials.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package main
import (
"encoding/json"
"io"
"io/ioutil"
"log"
"math/rand"
"time"
vault "github.com/hashicorp/vault/api"
)
// AWSCredentials are the credentials served by the API
type AWSCredentials struct {
AccessKeyID string `json:"AccessKeyId"`
SecretAccessKey string `json:"SecretAccessKey"`
Token string `json:"Token"`
Expiration time.Time `json:"Expiration"`
}
// lease represents the part of the response from /v1/sys/leases/lookup we care about (the expire time)
type lease struct {
Data struct {
ExpireTime time.Time `json:"expire_time"`
} `json:"data"`
}
// CredentialsRenewer renews the credentials
type CredentialsRenewer struct {
Credentials chan<- *AWSCredentials
Errors chan<- error
AwsPath string
AwsRole string
KubePath string
KubeRole string
TokenPath string
VaultConfig *vault.Config
}
// Start the renewer
func (cr *CredentialsRenewer) Start() {
// Create Vault client
client, err := vault.NewClient(cr.VaultConfig)
if err != nil {
cr.Errors <- err
return
}
for {
// Reload vault configuration from the environment
if err := cr.VaultConfig.ReadEnvironment(); err != nil {
cr.Errors <- err
return
}
// Login into Vault via kube SA
jwt, err := ioutil.ReadFile(cr.TokenPath)
if err != nil {
cr.Errors <- err
return
}
secret, err := client.Logical().Write("auth/"+cr.KubePath+"/login", map[string]interface{}{
"jwt": string(jwt),
"role": cr.KubeRole,
})
if err != nil {
cr.Errors <- err
return
}
client.SetToken(secret.Auth.ClientToken)
// Get a credentials secret from vault for the role
secret, err = client.Logical().Read(cr.AwsPath + "/sts/" + cr.AwsRole)
if err != nil {
cr.Errors <- err
return
}
// Convert the secret's lease duration into a time.Duration
leaseDuration := time.Duration(secret.LeaseDuration) * time.Second
// Get the expiration date of the lease from vault
l := lease{}
req := client.NewRequest("PUT", "/v1/sys/leases/lookup")
if err = req.SetJSONBody(map[string]interface{}{
"lease_id": secret.LeaseID,
}); err != nil {
cr.Errors <- err
return
}
resp, err := client.RawRequest(req)
if err != nil {
cr.Errors <- err
return
}
err = json.NewDecoder(resp.Body).Decode(&l)
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
if err != nil {
cr.Errors <- err
return
}
log.Printf("new aws credentials: %s, expiring %s", secret.Data["access_key"].(string), l.Data.ExpireTime.Format("2006-01-02 15:04:05"))
// Send the new credentials down the channel
cr.Credentials <- &AWSCredentials{
AccessKeyID: secret.Data["access_key"].(string),
SecretAccessKey: secret.Data["secret_key"].(string),
Token: secret.Data["security_token"].(string),
Expiration: l.Data.ExpireTime,
}
// Used to generate random values for sleeping between renewals
random := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
// Sleep until its time to renew the creds
time.Sleep(sleepDuration(leaseDuration, random))
}
}