diff --git a/simples3.go b/simples3.go index 3d30d2d..e5e41a6 100644 --- a/simples3.go +++ b/simples3.go @@ -23,6 +23,8 @@ import ( ) const ( + imdsTokenURL = "http://169.254.169.254/latest/api/token" + imdsTokenHeader = "X-aws-ec2-metadata-token" securityCredentialsURL = "http://169.254.169.254/latest/meta-data/iam/security-credentials/" // AMZMetaPrefix to prefix metadata key. @@ -89,12 +91,13 @@ type UploadInput struct { // UploadResponse receives the following XML // in case of success, since we set a 201 response from S3. // Sample response: -// -// https://s3.amazonaws.com/link-to-the-file -// s3-bucket -// development/8614bd40-691b-4668-9241-3b342c6cf429/image.jpg -// "32-bit-tag" -// +// +// +// https://s3.amazonaws.com/link-to-the-file +// s3-bucket +// development/8614bd40-691b-4668-9241-3b342c6cf429/image.jpg +// "32-bit-tag" +// type UploadResponse struct { Location string `xml:"Location"` Bucket string `xml:"Bucket"` @@ -149,12 +152,55 @@ func NewUsingIAM(region string) (*S3, error) { }, securityCredentialsURL, region) } +// fetchIMDSToken retrieves an IMDSv2 token from the +// EC2 instance metadata service. It returns a token +// only if IMDSv2 is enabled in the EC2 instance metadata +// configuration, otherwise returns an error. +func fetchIMDSToken(cl *http.Client) (string, error) { + req, err := http.NewRequest(http.MethodPut, imdsTokenURL, nil) + if err != nil { + return "", err + } + req.Header.Set("X-aws-ec2-metadata-token-ttl-seconds", "21600") + + resp, err := cl.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to fetch IMDSv2 token: %s", resp.Status) + } + + token, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(token), nil +} + // fetchIAMData fetches the IAM data from the given URL. // In case of a normal AWS setup, baseURL would be securityCredentialsURL. // You can use this method, to manually fetch IAM data from a custom // endpoint and pass it to SetIAMData. func fetchIAMData(cl *http.Client, baseURL string) (IAMResponse, error) { - resp, err := cl.Get(baseURL) + var token string + token, err := fetchIMDSToken(cl) + // useIMDSv2 if no errors were returned from + // the fetchIMDSToken method call. + useIMDSv2 := err == nil + + req, err := http.NewRequest(http.MethodGet, baseURL, nil) + if err != nil { + return IAMResponse{}, err + } + if useIMDSv2 { + req.Header.Set(imdsTokenHeader, token) + } + + resp, err := cl.Do(req) if err != nil { return IAMResponse{}, err } @@ -169,7 +215,15 @@ func fetchIAMData(cl *http.Client, baseURL string) (IAMResponse, error) { return IAMResponse{}, err } - resp, err = http.Get(baseURL + "/" + string(role)) + req, err = http.NewRequest(http.MethodGet, baseURL+"/"+string(role), nil) + if err != nil { + return IAMResponse{}, err + } + if useIMDSv2 { + req.Header.Set(imdsTokenHeader, token) + } + + resp, err = cl.Do(req) if err != nil { return IAMResponse{}, err }