diff --git a/builtin/providers/aws/auth_helpers.go b/builtin/providers/aws/auth_helpers.go index b5ee2d5eeb96..8e9fb55aa486 100644 --- a/builtin/providers/aws/auth_helpers.go +++ b/builtin/providers/aws/auth_helpers.go @@ -19,8 +19,46 @@ import ( const DefaultMetadataApiHost = "http://169.254.169.254:80" +type IamInfoFromMetadata struct { + Code string + LastUpdated time.Time + InstanceProfileArn string + InstanceProfileId string +} + func getAccountId(iamconn *iam.IAM, authProviderName string) (string, error) { - // Try IAM GetUser + // If we have creds from instance profile, we can use metadata API + if authProviderName == "EC2RoleProvider" { + log.Println("[DEBUG] Trying to get account ID via AWS Metadata API") + url := buildMetadataUrl("/latest/meta-data/iam/info") + c := http.Client{ + Timeout: 3 * time.Second, + } + + resp, err := c.Get(url) + if err != nil { + return "", fmt.Errorf("Failed getting account ID via EC2 metadata API: %s", err) + } + + if !responseContainsEC2Header(resp) { + return "", fmt.Errorf("Expected EC2 metadata API at %q, no EC2 headers found.", url) + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Failed reading EC2 metadata API response from %q: %s", url, err) + } + info := IamInfoFromMetadata{} + err = json.Unmarshal(body, &info) + if err != nil { + return "", fmt.Errorf("Failed parsing EC2 metadata API response from %q: %s", url, err) + } + + return parseAccountIdFromArn(info.InstanceProfileArn) + } + + // Then try IAM GetUser log.Println("[DEBUG] Trying to get account ID via iam:GetUser") outUser, err := iamconn.GetUser(nil) if err == nil { diff --git a/builtin/providers/aws/auth_helpers_test.go b/builtin/providers/aws/auth_helpers_test.go index 263f3e1eda0c..4ae7b1788337 100644 --- a/builtin/providers/aws/auth_helpers_test.go +++ b/builtin/providers/aws/auth_helpers_test.go @@ -30,6 +30,25 @@ func TestAuthParseAccountIdFromArn(t *testing.T) { } } +func TestAuthIamInfoFromMetadataMarshalling(t *testing.T) { + v := IamInfoFromMetadata{} + err := json.Unmarshal(testMetadataOutput, &v) + if err != nil { + t.Fatalf("Expected no error from unmarshall: %s", err) + } + + expectedCode := "Success" + if v.Code != expectedCode { + t.Fatalf("Expected Code to equal %q (given %q)", expectedCode, v.Code) + } + + expectedArn := "arn:aws:iam::101636750127:instance-profile/aws-elasticbeanstalk-ec2-role" + if v.InstanceProfileArn != expectedArn { + t.Fatalf("Expected InstanceProfileArn to equal %q (given %q)", + expectedArn, v.InstanceProfileArn) + } +} + func TestAWSConfig_shouldError(t *testing.T) { resetEnv := unsetEnv(t) defer resetEnv() @@ -414,3 +433,10 @@ const aws_routes = ` ] } ` + +var testMetadataOutput = []byte(`{ + "Code" : "Success", + "LastUpdated" : "2015-10-28T16:49:39Z", + "InstanceProfileArn" : "arn:aws:iam::101636750127:instance-profile/aws-elasticbeanstalk-ec2-role", + "InstanceProfileId" : "AIPAREU6ES7PV7RYRYPIC" +}`)