-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmetadata-service.go
169 lines (147 loc) · 4.31 KB
/
metadata-service.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package main
import (
"encoding/json"
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/aws/aws-sdk-go/service/sts"
)
// Service implements a background service
type Service interface {
/*
Start should create any resources that the service requires,
including background goroutines that service requests through
the Service's public API.
*/
Start() error
/*
If any special cleanup needs to occur for this Service to cleanly
shut down, it should be implemented here.
*/
Stop() error
}
/*
MetadataService extends Service to include information about public
port numbers for testing purposes.
*/
type MetadataService interface {
Service
Port() int
}
// CredentialsSource is used for retreiving and renewing AWS credentials
type CredentialsSource interface {
GetCredentials() (*sts.Credentials, error)
}
/*
metadataService is the internal implementation of the public interface.
It serves as a reference implementation of the EC2 HTTP API for workstations.
*/
type metadataService struct {
listener net.Listener
creds CredentialsSource
}
func (mds *metadataService) Start() error {
go mds.listen()
return nil
}
/*
This actually creates the HTTP listener and blocks on it.
Spawned in the background.
*/
func (mds *metadataService) listen() {
handler := http.NewServeMux()
handler.HandleFunc("/latest/meta-data/iam/security-credentials/", mds.enumerateRoles)
handler.HandleFunc("/latest/meta-data/iam/security-credentials/ims", mds.getCredentials)
handler.HandleFunc("/latest/meta-data/instance-id", mds.getInstanceID)
handler.HandleFunc("/latest/meta-data/placement/availability-zone", mds.getAvailabilityZone)
handler.HandleFunc("/latest/meta-data/public-hostname", mds.getPublicDNS)
err := http.Serve(mds.listener, handler)
if err != nil {
if strings.HasSuffix(err.Error(), "use of closed network connection") {
// this happens when Close() is called, and it's normal
return
}
panic(err)
}
}
/*
Stops the HTTP server and closes all extant connections.
*/
func (mds *metadataService) Stop() error {
return mds.listener.Close()
}
/*
Returns the port number currently in use by the HTTP server.
Only really used in tests.
*/
func (mds *metadataService) Port() int {
return mds.listener.Addr().(*net.TCPAddr).Port
}
/*
Enumerates the available instance profiles on this fake instance.
Seems like Amazon only supports one.
*/
func (mds *metadataService) enumerateRoles(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "ims")
}
/*
Return fake data for programs that depend on data from the metadata service.
These fields are constructed to be obviously wrong and would never be found in the
production environment.
*/
func (mds *metadataService) getInstanceID(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "i-deadbeef")
}
func (mds *metadataService) getAvailabilityZone(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "eu-west-1")
}
func (mds *metadataService) getPublicDNS(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "ec2-0-0-0-0.eu-west-1.compute.amazonaws.com")
}
/*
Returns credentials for interested clients.
*/
func (mds *metadataService) getCredentials(w http.ResponseWriter, r *http.Request) {
creds, err := mds.creds.GetCredentials()
if err != nil {
http.Error(w, err.Error(), 500)
return
}
resp := &securityCredentialsResponse{
Code: "Success",
LastUpdated: time.Now().UTC().Format(time.RFC3339),
Type: "AWS-HMAC",
AccessKeyID: *creds.AccessKeyId,
SecretAccessKey: *creds.SecretAccessKey,
Token: *creds.SessionToken,
Expiration: creds.Expiration.UTC().Format(time.RFC3339),
}
respBody, err := json.Marshal(resp)
if err != nil {
panic(err)
}
w.Write(respBody)
}
/*
NewMetadataService returns a properly-initialized metadataService for use.
*/
func NewMetadataService(listener net.Listener, creds CredentialsSource) (MetadataService, error) {
return &metadataService{
listener: listener,
creds: creds,
}, nil
}
/*
Structure encoded as JSON for credential clients.
*/
type securityCredentialsResponse struct {
Code string `json:"Code"`
LastUpdated string `json:"LastUpdated"`
Type string `json:"Type"`
AccessKeyID string `json:"AccessKeyId"`
SecretAccessKey string `json:"SecretAccessKey"`
Token string `json:"Token"`
Expiration string `json:"Expiration"`
}