diff options
Diffstat (limited to 'credentials.go')
-rw-r--r-- | credentials.go | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/credentials.go b/credentials.go new file mode 100644 index 0000000..a9412da --- /dev/null +++ b/credentials.go | |||
@@ -0,0 +1,153 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "time" | ||
5 | |||
6 | "github.com/aws/aws-sdk-go/aws" | ||
7 | "github.com/aws/aws-sdk-go/aws/credentials" | ||
8 | "github.com/aws/aws-sdk-go/aws/session" | ||
9 | "github.com/aws/aws-sdk-go/service/sts" | ||
10 | jww "github.com/spf13/jwalterweatherman" | ||
11 | ) | ||
12 | |||
13 | // Try to refresh credentials 3 times an hour but in the worst case if the | ||
14 | // credential refresh fails twice try to get one last refresh in before the end | ||
15 | // of the hour when the credential expires. | ||
16 | const REFRESH_INTERVAL = time.Duration(19) * time.Minute | ||
17 | |||
18 | type CredentialHandler interface { | ||
19 | Start() | ||
20 | InGoodState() bool | ||
21 | SetBootstrapCredential(*credentials.Credentials) | ||
22 | Output() chan *IAMCredentials | ||
23 | } | ||
24 | |||
25 | type credentialHandler struct { | ||
26 | region *string | ||
27 | roleARN *string | ||
28 | sessionName *string | ||
29 | bootstrapCreds *credentials.Credentials | ||
30 | output chan *IAMCredentials | ||
31 | input chan *credentials.Credentials | ||
32 | } | ||
33 | |||
34 | func NewCredentialHandler(region, arn, name *string) CredentialHandler { | ||
35 | return &credentialHandler{ | ||
36 | region: region, | ||
37 | roleARN: arn, | ||
38 | sessionName: name, | ||
39 | output: make(chan *IAMCredentials), | ||
40 | input: make(chan *credentials.Credentials, 1), // 1-item buffer to allow pre-start bootstrapping | ||
41 | } | ||
42 | } | ||
43 | |||
44 | func (h *credentialHandler) Output() chan *IAMCredentials { | ||
45 | return h.output | ||
46 | } | ||
47 | |||
48 | func (h *credentialHandler) InGoodState() bool { | ||
49 | c := <-h.Output() | ||
50 | return c.Code == "Success" | ||
51 | } | ||
52 | |||
53 | func (h *credentialHandler) SetBootstrapCredential(bc *credentials.Credentials) { | ||
54 | h.input <- bc | ||
55 | } | ||
56 | |||
57 | func (h *credentialHandler) Start() { | ||
58 | c := &IAMCredentials{Code: "Failure"} | ||
59 | updateChan := make(chan *IAMCredentials) | ||
60 | |||
61 | ticker := time.NewTicker(REFRESH_INTERVAL) | ||
62 | defer ticker.Stop() | ||
63 | |||
64 | jww.INFO.Printf("Starting credential handler, awaiting bootstrap") | ||
65 | |||
66 | for { | ||
67 | select { | ||
68 | // Read and update bootstrap credentials | ||
69 | case h.bootstrapCreds = <-h.input: | ||
70 | go h.refreshCredential(nil, updateChan) | ||
71 | // HTTP handler requests credential | ||
72 | case h.output <- c: | ||
73 | // Time to refresh credentials | ||
74 | case <-ticker.C: | ||
75 | go h.refreshCredential(c.rawCredentials, updateChan) | ||
76 | // Updated credentials arrive | ||
77 | case up := <-updateChan: | ||
78 | if up == nil && c.Expiration.After(time.Now()) { | ||
79 | c = &IAMCredentials{Code: "Failure"} | ||
80 | } else { | ||
81 | c = up | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | |||
87 | func (h *credentialHandler) refreshCredential(creds *credentials.Credentials, out chan *IAMCredentials) { | ||
88 | jww.INFO.Printf("Attempting to obtain credentials") | ||
89 | |||
90 | if creds == nil && h.bootstrapCreds == nil { | ||
91 | jww.WARN.Printf("No session or bootstrap credentials available") | ||
92 | return | ||
93 | } | ||
94 | |||
95 | if creds != nil { | ||
96 | jww.DEBUG.Printf("Attempting to use session credentials") | ||
97 | |||
98 | c, err := h.assumeRole(creds) | ||
99 | if err != nil { | ||
100 | jww.WARN.Printf("Failed to obtain with session credentials: %s", err) | ||
101 | } else { | ||
102 | jww.INFO.Printf("Successfully obtained credentials") | ||
103 | out <- c | ||
104 | return | ||
105 | } | ||
106 | } | ||
107 | |||
108 | if h.bootstrapCreds != nil { | ||
109 | jww.DEBUG.Printf("Attempting to use bootstrap credentials") | ||
110 | |||
111 | c, err := h.assumeRole(h.bootstrapCreds) | ||
112 | if err != nil { | ||
113 | jww.WARN.Printf("Failed to obtain with bootstrap credentials: %s", err) | ||
114 | } else { | ||
115 | jww.INFO.Printf("Successfully obtained credentials") | ||
116 | out <- c | ||
117 | return | ||
118 | } | ||
119 | } | ||
120 | |||
121 | jww.ERROR.Printf("Failed to obtain credentials") | ||
122 | out <- nil | ||
123 | } | ||
124 | |||
125 | func (h *credentialHandler) assumeRole(creds *credentials.Credentials) (*IAMCredentials, error) { | ||
126 | ses := session.New(&aws.Config{ | ||
127 | Region: h.region, | ||
128 | Credentials: creds, | ||
129 | }) | ||
130 | |||
131 | assumed, err := sts.New(ses).AssumeRole(&sts.AssumeRoleInput{ | ||
132 | RoleArn: h.roleARN, | ||
133 | RoleSessionName: h.sessionName, | ||
134 | }) | ||
135 | if err != nil { | ||
136 | return nil, err | ||
137 | } | ||
138 | |||
139 | return &IAMCredentials{ | ||
140 | Code: "Success", | ||
141 | Type: "AWS-HMAC", | ||
142 | AccessKeyId: *assumed.Credentials.AccessKeyId, | ||
143 | SecretAccessKey: *assumed.Credentials.SecretAccessKey, | ||
144 | Token: *assumed.Credentials.SessionToken, | ||
145 | LastUpdated: time.Now().UTC().Round(time.Second), | ||
146 | Expiration: *assumed.Credentials.Expiration, | ||
147 | rawCredentials: credentials.NewStaticCredentials( | ||
148 | *assumed.Credentials.AccessKeyId, | ||
149 | *assumed.Credentials.SecretAccessKey, | ||
150 | *assumed.Credentials.SessionToken, | ||
151 | ), | ||
152 | }, nil | ||
153 | } | ||