aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go372
1 files changed, 372 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..9d12c9f
--- /dev/null
+++ b/main.go
@@ -0,0 +1,372 @@
1package main
2
3import (
4 "bytes"
5 "crypto/hmac"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "errors"
10 "flag"
11 "fmt"
12 "io/ioutil"
13 "net"
14 "net/http"
15 "os"
16 "os/user"
17 "strconv"
18 "time"
19
20 "github.com/aws/aws-sdk-go/aws/credentials"
21 "github.com/aws/aws-sdk-go/aws/ec2metadata"
22 "github.com/gorilla/handlers"
23 "github.com/gorilla/mux"
24 "github.com/sid77/drop"
25 jww "github.com/spf13/jwalterweatherman"
26)
27
28var (
29 DEFAULT_VALUES map[string]*string
30)
31
32func init() {
33 DEFAULT_VALUES = map[string]*string{
34 "domain": StringPtr("amazonaws.com"),
35 "partition": StringPtr("aws"),
36 "ami-launch-index": StringPtr("0"),
37 "ami-manifest-path": StringPtr("(unknown)"),
38 "instance-action": StringPtr("none"),
39 "profile": StringPtr("default-hvm"),
40 "security-groups": StringPtr("default"),
41 }
42}
43
44func StringPtr(v string) *string {
45 return &v
46}
47
48type appContext struct {
49 PrivateIP *net.IP
50 Region *string
51 MacAddr *net.HardwareAddr
52 AvailabilityZone *string
53 Hostname *string
54 InstanceId *string
55 InstanceType *string
56 AccountId *int64
57 ImageId *string
58 ReservationId *string
59 InstanceProfileId *string
60 RoleARN *string
61 BootstrapSecret *string
62 CredentialHandler CredentialHandler
63}
64
65func (c *appContext) FormatAZ() string {
66 return fmt.Sprintf("%s%s", *c.Region, *c.AvailabilityZone)
67}
68
69func GetKeyHandler(content map[string]*string, key string) http.Handler {
70 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71 w.Write([]byte(*content[key]))
72 })
73}
74
75func AvailabilityZoneHandler(w http.ResponseWriter, r *http.Request) {
76 ctx := getAppCtx(r)
77 fmt.Fprintf(w, ctx.FormatAZ())
78}
79
80func IAMCredentialHandler(w http.ResponseWriter, r *http.Request) {
81 ctx := getAppCtx(r)
82 vars := mux.Vars(r)
83
84 name, err := parseRoleName(*ctx.RoleARN)
85 if err != nil {
86 jww.ERROR.Printf("Error parsing role name in IAMCredentialHandler: %s", err)
87 http.Error(w, err.Error(), http.StatusInternalServerError)
88 }
89
90 if vars["profile"] != name {
91 http.NotFound(w, r)
92 return
93 }
94
95 writeHTTPJson(w, <-ctx.CredentialHandler.Output(), "IAMCredentialHandler")
96}
97
98func StatusHandler(w http.ResponseWriter, r *http.Request) {
99 ctx := getAppCtx(r)
100 writeHTTPJson(w, ctx.CredentialHandler.InGoodState(), "IAMCredentialHandler")
101}
102
103type bootstrapInput struct {
104 AccessKeyId string
105 SecretAccessKey string
106 Token string
107 Signature string
108}
109
110func validateSignature(r *bootstrapInput, key *string) bool {
111 buf := bytes.Buffer{}
112 // Alphabetical order matters here
113 buf.WriteString(fmt.Sprintf("AccessKeyId%s", r.AccessKeyId))
114 buf.WriteString(fmt.Sprintf("SecretAccessKey%s", r.SecretAccessKey))
115
116 // Only hash token if it was presented
117 if r.Token != "" {
118 buf.WriteString(fmt.Sprintf("Token%s", r.Token))
119 }
120
121 mac := hmac.New(sha256.New, []byte(*key))
122 mac.Write(buf.Bytes())
123 expected := mac.Sum(nil)
124
125 sig, err := hex.DecodeString(r.Signature)
126 if err != nil {
127 return false
128 }
129
130 return hmac.Equal(expected, sig)
131}
132
133func BootstrapCredentialHandler(w http.ResponseWriter, r *http.Request) {
134 ctx := getAppCtx(r)
135 d := json.NewDecoder(r.Body)
136 var cred bootstrapInput
137
138 err := d.Decode(&cred)
139 if err != nil {
140 jww.ERROR.Printf("Error decoding bootstrap JSON: %s", err)
141 http.Error(w, err.Error(), http.StatusInternalServerError)
142 }
143
144 if !validateSignature(&cred, ctx.BootstrapSecret) {
145 jww.ERROR.Printf("Invalid signature for bootstrapping credentials")
146 http.Error(w, "Not allowed", http.StatusForbidden)
147 return
148 }
149
150 creds := credentials.NewStaticCredentials(cred.AccessKeyId, cred.SecretAccessKey, cred.Token)
151 ctx.CredentialHandler.SetBootstrapCredential(creds)
152}
153
154func IAMInfoHandler(w http.ResponseWriter, r *http.Request) {
155 ctx := getAppCtx(r)
156
157 p := &ec2metadata.EC2IAMInfo{
158 Code: "Success",
159 LastUpdated: time.Now().UTC().Round(time.Second),
160 InstanceProfileArn: *ctx.RoleARN,
161 InstanceProfileID: *ctx.InstanceProfileId,
162 }
163
164 writeHTTPJson(w, p, "IAMInfoHandler")
165}
166
167func InstanceProfileListHandler(w http.ResponseWriter, r *http.Request) {
168 ctx := getAppCtx(r)
169
170 if !ctx.CredentialHandler.InGoodState() {
171 jww.ERROR.Printf("Credential handler in a bad state")
172 fmt.Fprintf(w, "")
173 return
174 }
175
176 name, err := parseRoleName(*ctx.RoleARN)
177 if err != nil {
178 jww.ERROR.Printf("Error parsing role name in InstanceProfileListHandler: %s", err)
179 http.Error(w, err.Error(), http.StatusInternalServerError)
180 }
181
182 fmt.Fprintf(w, name)
183}
184
185func IdentityDocumentHandler(w http.ResponseWriter, r *http.Request) {
186 ctx := getAppCtx(r)
187
188 id := &ec2metadata.EC2InstanceIdentityDocument{
189 PrivateIP: ctx.PrivateIP.String(),
190 DevpayProductCodes: nil,
191 AvailabilityZone: ctx.FormatAZ(),
192 Version: "2010-08-31",
193 InstanceID: *ctx.InstanceId,
194 BillingProducts: nil,
195 InstanceType: *ctx.InstanceType,
196 ImageID: *ctx.ImageId,
197 AccountID: strconv.FormatInt(*ctx.AccountId, 10),
198 Architecture: "x86_64",
199 KernelID: "",
200 RamdiskID: "",
201 PendingTime: time.Now().Round(time.Second),
202 Region: *ctx.Region,
203 }
204
205 writeHTTPJson(w, id, "IdentityDocumentHandler")
206}
207
208func buildMetadataHandler(ctx *appContext, defaults map[string]*string) http.Handler {
209 r := mux.NewRouter()
210
211 // Static Data Handlers
212 r.Handle("/latest/meta-data/services/domain", GetKeyHandler(defaults, "domain"))
213 r.Handle("/latest/meta-data/services/partition", GetKeyHandler(defaults, "partition"))
214 r.Handle("/latest/meta-data/ami-launch-index", GetKeyHandler(defaults, "ami-launch-index"))
215 r.Handle("/latest/meta-data/ami-manifest-path", GetKeyHandler(defaults, "ami-manifest-path"))
216 r.Handle("/latest/meta-data/instance-action", GetKeyHandler(defaults, "instance-action"))
217 r.Handle("/latest/meta-data/profile", GetKeyHandler(defaults, "profile"))
218 r.Handle("/latest/meta-data/security-groups", GetKeyHandler(defaults, "security-groups"))
219
220 // Machine-specific Pseudo-static Handlers
221 r.Handle("/latest/meta-data/mac", ContextPrintingHandler{ctx, "MacAddr"})
222 r.Handle("/latest/meta-data/hostname", ContextPrintingHandler{ctx, "Hostname"})
223 r.Handle("/latest/meta-data/local-hostname", ContextPrintingHandler{ctx, "Hostname"})
224 r.Handle("/latest/meta-data/local-ipv4", ContextPrintingHandler{ctx, "PrivateIP"})
225 r.Handle("/latest/meta-data/instance-id", ContextPrintingHandler{ctx, "InstanceId"})
226 r.Handle("/latest/meta-data/instance-type", ContextPrintingHandler{ctx, "InstanceType"})
227 r.Handle("/latest/meta-data/ami-id", ContextPrintingHandler{ctx, "ImageId"})
228 r.Handle("/latest/meta-data/reservation-id", ContextPrintingHandler{ctx, "ReservationId"})
229
230 // Context-specific Handlers
231 r.Handle("/latest/meta-data/placement/availability-zone", ContextAwareHandler{ctx, AvailabilityZoneHandler})
232 r.Handle("/latest/dynamic/instance-identity/document", ContextAwareHandler{ctx, IdentityDocumentHandler})
233
234 // IAM Credential Handlers
235 r.Handle("/latest/meta-data/iam/info", ContextAwareHandler{ctx, IAMInfoHandler})
236 r.Handle("/latest/meta-data/iam/security-credentials/", ContextAwareHandler{ctx, InstanceProfileListHandler})
237 r.Handle("/latest/meta-data/iam/security-credentials/{profile}", ContextAwareHandler{ctx, IAMCredentialHandler})
238
239 return handlers.LoggingHandler(os.Stdout, SecurityHandler{r})
240}
241
242func buildAdminHandler(ctx *appContext) http.Handler {
243 r := mux.NewRouter()
244
245 r.Handle("/bootstrap/creds", ContextAwareHandler{ctx, BootstrapCredentialHandler}).Methods("POST")
246 r.Handle("/status", ContextAwareHandler{ctx, StatusHandler}).Methods("GET")
247
248 return handlers.LoggingHandler(os.Stdout, r)
249}
250
251type UserArgs struct {
252 regionAZ string
253 InstanceType string
254 Region string
255 AvailabilityZone string
256 RoleARN string
257 User string
258 AccountNumber int64
259 BootstrapSecret string
260}
261
262func parseArgs() (*UserArgs, error) {
263 a := &UserArgs{}
264
265 flag.StringVar(&a.regionAZ, "region", "us-west-2a", "region and availability zone")
266 flag.StringVar(&a.InstanceType, "instance", "t2.micro", "instance type")
267 flag.StringVar(&a.RoleARN, "arn", "", "ARN of role to assume")
268 flag.StringVar(&a.User, "user", "nobody", "run-as user after port binding")
269 flag.StringVar(&a.BootstrapSecret, "secret", "", "bootstrap signing secret")
270
271 flag.Parse()
272
273 _, err := user.Lookup(a.User)
274 if err != nil {
275 return nil, err
276 }
277
278 region, az, err := parseRegionAZ(a.regionAZ)
279 if err != nil {
280 return nil, err
281 }
282 a.Region = region
283 a.AvailabilityZone = az
284
285 act, err := parseAccountFromARN(a.RoleARN)
286 if err != nil {
287 return nil, err
288 }
289 a.AccountNumber = act
290
291 if a.BootstrapSecret == "" {
292 return nil, errors.New("Bootstrap secret must be set")
293 }
294
295 return a, nil
296}
297
298func initialBootstrap(file string, handler CredentialHandler) {
299 bd, err := ioutil.ReadFile(file)
300 if err != nil {
301 jww.ERROR.Printf("Error reading bootstrap file: %s", err.Error())
302 return
303 }
304
305 var cred bootstrapInput
306 err = json.Unmarshal(bd, &cred)
307 if err != nil {
308 jww.ERROR.Printf("Error decoding bootstrap JSON: %s", err)
309 return
310 }
311
312 creds := credentials.NewStaticCredentials(cred.AccessKeyId, cred.SecretAccessKey, cred.Token)
313 handler.SetBootstrapCredential(creds)
314}
315
316func main() {
317 jww.SetStdoutThreshold(jww.LevelInfo)
318
319 args, err := parseArgs()
320 if err != nil {
321 fmt.Println(err.Error())
322 return
323 }
324
325 // Bind this port first because it requires privilges then drop the
326 // privileges before we do anything else
327 metalistener, err := net.Listen("tcp", "169.254.169.254:80")
328 if err != nil {
329 jww.FATAL.Printf("Error setting up listener: %s", err.Error())
330 return
331 }
332
333 if err := drop.DropPrivileges(args.User); err != nil {
334 jww.FATAL.Printf("Unable to drop privileges")
335 return
336 }
337
338 iface, err := getInterfaceContext()
339 if err != nil {
340 jww.FATAL.Printf("Error getting network interface info: %s", err)
341 return
342 }
343
344 credHandler := NewCredentialHandler(
345 &args.Region,
346 &args.RoleARN,
347 iface.PrimaryHostname,
348 )
349
350 ctx := &appContext{
351 PrivateIP: iface.PrimaryIP,
352 Region: &args.Region,
353 MacAddr: iface.MacAddr,
354 AvailabilityZone: &args.AvailabilityZone,
355 Hostname: iface.PrimaryHostname,
356 InstanceId: generateInstanceId(iface.PrimaryHostname),
357 InstanceType: &args.InstanceType,
358 AccountId: &args.AccountNumber,
359 ReservationId: generatePlausibleId("r"),
360 ImageId: generatePlausibleId("ami"),
361 InstanceProfileId: generatePlausibleProfileId(),
362 RoleARN: &args.RoleARN,
363 BootstrapSecret: &args.BootstrapSecret,
364 CredentialHandler: credHandler,
365 }
366
367 initialBootstrap("/etc/mmds/bootstrap-cred.json", credHandler)
368
369 go credHandler.Start()
370 go http.ListenAndServe(":8000", buildAdminHandler(ctx))
371 ListenAndServeRaw(metalistener, buildMetadataHandler(ctx, DEFAULT_VALUES))
372}