1package ec2metadata
2
3import (
4	"encoding/json"
5	"fmt"
6	"net/http"
7	"strconv"
8	"strings"
9	"time"
10
11	"github.com/aws/aws-sdk-go/aws"
12	"github.com/aws/aws-sdk-go/aws/awserr"
13	"github.com/aws/aws-sdk-go/aws/request"
14	"github.com/aws/aws-sdk-go/internal/sdkuri"
15)
16
17// getToken uses the duration to return a token for EC2 metadata service,
18// or an error if the request failed.
19func (c *EC2Metadata) getToken(ctx aws.Context, duration time.Duration) (tokenOutput, error) {
20	op := &request.Operation{
21		Name:       "GetToken",
22		HTTPMethod: "PUT",
23		HTTPPath:   "/api/token",
24	}
25
26	var output tokenOutput
27	req := c.NewRequest(op, nil, &output)
28	req.SetContext(ctx)
29
30	// remove the fetch token handler from the request handlers to avoid infinite recursion
31	req.Handlers.Sign.RemoveByName(fetchTokenHandlerName)
32
33	// Swap the unmarshalMetadataHandler with unmarshalTokenHandler on this request.
34	req.Handlers.Unmarshal.Swap(unmarshalMetadataHandlerName, unmarshalTokenHandler)
35
36	ttl := strconv.FormatInt(int64(duration/time.Second), 10)
37	req.HTTPRequest.Header.Set(ttlHeader, ttl)
38
39	err := req.Send()
40
41	// Errors with bad request status should be returned.
42	if err != nil {
43		err = awserr.NewRequestFailure(
44			awserr.New(req.HTTPResponse.Status, http.StatusText(req.HTTPResponse.StatusCode), err),
45			req.HTTPResponse.StatusCode, req.RequestID)
46	}
47
48	return output, err
49}
50
51// GetMetadata uses the path provided to request information from the EC2
52// instance metadata service. The content will be returned as a string, or
53// error if the request failed.
54func (c *EC2Metadata) GetMetadata(p string) (string, error) {
55	return c.GetMetadataWithContext(aws.BackgroundContext(), p)
56}
57
58// GetMetadataWithContext uses the path provided to request information from the EC2
59// instance metadata service. The content will be returned as a string, or
60// error if the request failed.
61func (c *EC2Metadata) GetMetadataWithContext(ctx aws.Context, p string) (string, error) {
62	op := &request.Operation{
63		Name:       "GetMetadata",
64		HTTPMethod: "GET",
65		HTTPPath:   sdkuri.PathJoin("/meta-data", p),
66	}
67	output := &metadataOutput{}
68
69	req := c.NewRequest(op, nil, output)
70
71	req.SetContext(ctx)
72
73	err := req.Send()
74	return output.Content, err
75}
76
77// GetUserData returns the userdata that was configured for the service. If
78// there is no user-data setup for the EC2 instance a "NotFoundError" error
79// code will be returned.
80func (c *EC2Metadata) GetUserData() (string, error) {
81	return c.GetUserDataWithContext(aws.BackgroundContext())
82}
83
84// GetUserDataWithContext returns the userdata that was configured for the service. If
85// there is no user-data setup for the EC2 instance a "NotFoundError" error
86// code will be returned.
87func (c *EC2Metadata) GetUserDataWithContext(ctx aws.Context) (string, error) {
88	op := &request.Operation{
89		Name:       "GetUserData",
90		HTTPMethod: "GET",
91		HTTPPath:   "/user-data",
92	}
93
94	output := &metadataOutput{}
95	req := c.NewRequest(op, nil, output)
96	req.SetContext(ctx)
97
98	err := req.Send()
99	return output.Content, err
100}
101
102// GetDynamicData uses the path provided to request information from the EC2
103// instance metadata service for dynamic data. The content will be returned
104// as a string, or error if the request failed.
105func (c *EC2Metadata) GetDynamicData(p string) (string, error) {
106	return c.GetDynamicDataWithContext(aws.BackgroundContext(), p)
107}
108
109// GetDynamicDataWithContext uses the path provided to request information from the EC2
110// instance metadata service for dynamic data. The content will be returned
111// as a string, or error if the request failed.
112func (c *EC2Metadata) GetDynamicDataWithContext(ctx aws.Context, p string) (string, error) {
113	op := &request.Operation{
114		Name:       "GetDynamicData",
115		HTTPMethod: "GET",
116		HTTPPath:   sdkuri.PathJoin("/dynamic", p),
117	}
118
119	output := &metadataOutput{}
120	req := c.NewRequest(op, nil, output)
121	req.SetContext(ctx)
122
123	err := req.Send()
124	return output.Content, err
125}
126
127// GetInstanceIdentityDocument retrieves an identity document describing an
128// instance. Error is returned if the request fails or is unable to parse
129// the response.
130func (c *EC2Metadata) GetInstanceIdentityDocument() (EC2InstanceIdentityDocument, error) {
131	return c.GetInstanceIdentityDocumentWithContext(aws.BackgroundContext())
132}
133
134// GetInstanceIdentityDocumentWithContext retrieves an identity document describing an
135// instance. Error is returned if the request fails or is unable to parse
136// the response.
137func (c *EC2Metadata) GetInstanceIdentityDocumentWithContext(ctx aws.Context) (EC2InstanceIdentityDocument, error) {
138	resp, err := c.GetDynamicDataWithContext(ctx, "instance-identity/document")
139	if err != nil {
140		return EC2InstanceIdentityDocument{},
141			awserr.New("EC2MetadataRequestError",
142				"failed to get EC2 instance identity document", err)
143	}
144
145	doc := EC2InstanceIdentityDocument{}
146	if err := json.NewDecoder(strings.NewReader(resp)).Decode(&doc); err != nil {
147		return EC2InstanceIdentityDocument{},
148			awserr.New(request.ErrCodeSerialization,
149				"failed to decode EC2 instance identity document", err)
150	}
151
152	return doc, nil
153}
154
155// IAMInfo retrieves IAM info from the metadata API
156func (c *EC2Metadata) IAMInfo() (EC2IAMInfo, error) {
157	return c.IAMInfoWithContext(aws.BackgroundContext())
158}
159
160// IAMInfoWithContext retrieves IAM info from the metadata API
161func (c *EC2Metadata) IAMInfoWithContext(ctx aws.Context) (EC2IAMInfo, error) {
162	resp, err := c.GetMetadataWithContext(ctx, "iam/info")
163	if err != nil {
164		return EC2IAMInfo{},
165			awserr.New("EC2MetadataRequestError",
166				"failed to get EC2 IAM info", err)
167	}
168
169	info := EC2IAMInfo{}
170	if err := json.NewDecoder(strings.NewReader(resp)).Decode(&info); err != nil {
171		return EC2IAMInfo{},
172			awserr.New(request.ErrCodeSerialization,
173				"failed to decode EC2 IAM info", err)
174	}
175
176	if info.Code != "Success" {
177		errMsg := fmt.Sprintf("failed to get EC2 IAM Info (%s)", info.Code)
178		return EC2IAMInfo{},
179			awserr.New("EC2MetadataError", errMsg, nil)
180	}
181
182	return info, nil
183}
184
185// Region returns the region the instance is running in.
186func (c *EC2Metadata) Region() (string, error) {
187	return c.RegionWithContext(aws.BackgroundContext())
188}
189
190// RegionWithContext returns the region the instance is running in.
191func (c *EC2Metadata) RegionWithContext(ctx aws.Context) (string, error) {
192	ec2InstanceIdentityDocument, err := c.GetInstanceIdentityDocumentWithContext(ctx)
193	if err != nil {
194		return "", err
195	}
196	// extract region from the ec2InstanceIdentityDocument
197	region := ec2InstanceIdentityDocument.Region
198	if len(region) == 0 {
199		return "", awserr.New("EC2MetadataError", "invalid region received for ec2metadata instance", nil)
200	}
201	// returns region
202	return region, nil
203}
204
205// Available returns if the application has access to the EC2 Metadata service.
206// Can be used to determine if application is running within an EC2 Instance and
207// the metadata service is available.
208func (c *EC2Metadata) Available() bool {
209	return c.AvailableWithContext(aws.BackgroundContext())
210}
211
212// AvailableWithContext returns if the application has access to the EC2 Metadata service.
213// Can be used to determine if application is running within an EC2 Instance and
214// the metadata service is available.
215func (c *EC2Metadata) AvailableWithContext(ctx aws.Context) bool {
216	if _, err := c.GetMetadataWithContext(ctx, "instance-id"); err != nil {
217		return false
218	}
219
220	return true
221}
222
223// An EC2IAMInfo provides the shape for unmarshaling
224// an IAM info from the metadata API
225type EC2IAMInfo struct {
226	Code               string
227	LastUpdated        time.Time
228	InstanceProfileArn string
229	InstanceProfileID  string
230}
231
232// An EC2InstanceIdentityDocument provides the shape for unmarshaling
233// an instance identity document
234type EC2InstanceIdentityDocument struct {
235	DevpayProductCodes      []string  `json:"devpayProductCodes"`
236	MarketplaceProductCodes []string  `json:"marketplaceProductCodes"`
237	AvailabilityZone        string    `json:"availabilityZone"`
238	PrivateIP               string    `json:"privateIp"`
239	Version                 string    `json:"version"`
240	Region                  string    `json:"region"`
241	InstanceID              string    `json:"instanceId"`
242	BillingProducts         []string  `json:"billingProducts"`
243	InstanceType            string    `json:"instanceType"`
244	AccountID               string    `json:"accountId"`
245	PendingTime             time.Time `json:"pendingTime"`
246	ImageID                 string    `json:"imageId"`
247	KernelID                string    `json:"kernelId"`
248	RamdiskID               string    `json:"ramdiskId"`
249	Architecture            string    `json:"architecture"`
250}
251