diff --git a/api/types/installers/installer.sh.tmpl b/api/types/installers/installer.sh.tmpl index 8633b6c4fa323..01adfba6c1a92 100644 --- a/api/types/installers/installer.sh.tmpl +++ b/api/types/installers/installer.sh.tmpl @@ -3,14 +3,14 @@ set -eu on_ec2() { - EC2_STATUS=$(curl -o /dev/null -w "%{http_code}" -m5 -sS "http://169.254.169.254/latest/meta-data") - # EC2 metadata sometimes requires token access, so a successful hit may - # return unauthorized/forbidden. - [ "$EC2_STATUS" = "200" ] || [ "$EC2_STATUS" = "401" ] || [ "$EC2_STATUS" = "403" ] + IMDS_TOKEN=$(curl -m5 -sS -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300") + [ -z "$IMDS_TOKEN" ] && return 1 + EC2_STATUS=$(curl -o /dev/null -w "%{http_code}" -m5 -sS -H "X-aws-ec2-metadata-token: ${IMDS_TOKEN}" "http://169.254.169.254/latest/meta-data") + [ "$EC2_STATUS" = "200" ] } on_azure() { - AZURE_STATUS=$(curl -o /dev/null -w "%{http_code}" -m5 -sS -H "Metadata:true" --noproxy "*" "http://169.254.169.254/metadata/versions") + AZURE_STATUS=$(curl -o /dev/null -w "%{http_code}" -m5 -sS -H "Metadata: true" --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=2021-02-01") [ "$AZURE_STATUS" = "200" ] } @@ -53,10 +53,10 @@ on_azure() { echo "Unsupported distro: $ID" exit 1 fi - - if on_azure ; then - API_VERSION=$(curl -m5 -sS -H "Metadata:true" --noproxy "*" "http://169.254.169.254/metadata/versions" | jq -r ".apiVersions[-1]") - INSTANCE_INFO=$(curl -m5 -sS -H "Metadata:true" --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=$API_VERSION&format=json") + + if on_azure; then + API_VERSION=$(curl -m5 -sS -H "Metadata: true" --noproxy "*" "http://169.254.169.254/metadata/versions" | jq -r ".apiVersions[-1]") + INSTANCE_INFO=$(curl -m5 -sS -H "Metadata: true" --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=$API_VERSION&format=json") REGION="$(echo "$INSTANCE_INFO" | jq -r .compute.location)" RESOURCE_GROUP="$(echo "$INSTANCE_INFO" | jq -r .compute.resourceGroupName)" @@ -65,9 +65,9 @@ on_azure() { JOIN_METHOD=azure LABELS="teleport.internal/vm-id=${VM_ID},teleport.internal/subscription-id=${SUBSCRIPTION_ID},teleport.internal/region=${REGION},teleport.internal/resource-group=${RESOURCE_GROUP}" - elif on_ec2 ; then + elif on_ec2; then IMDS_TOKEN=$(curl -m5 -sS -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300") - INSTANCE_INFO=$(curl -m5 -sS -H "X-aws-ec2-metadata-token: ${IMDS_TOKEN}" http://169.254.169.254/latest/dynamic/instance-identity/document) + INSTANCE_INFO=$(curl -m5 -sS -H "X-aws-ec2-metadata-token: ${IMDS_TOKEN}" "http://169.254.169.254/latest/dynamic/instance-identity/document") ACCOUNT_ID="$(echo "$INSTANCE_INFO" | jq -r .accountId)" INSTANCE_ID="$(echo "$INSTANCE_INFO" | jq -r .instanceId)" diff --git a/lib/inventory/metadata/metadata.go b/lib/inventory/metadata/metadata.go index 0f101d9efa286..974913898bc09 100644 --- a/lib/inventory/metadata/metadata.go +++ b/lib/inventory/metadata/metadata.go @@ -257,12 +257,37 @@ func (c *fetchConfig) fetchCloudEnvironment() string { // the instance is running on AWS. // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html func (c *fetchConfig) awsHTTPGetSuccess() bool { - url := "http://169.254.169.254/latest/meta-data/" - req, err := http.NewRequestWithContext(c.context, http.MethodGet, url, nil) + url := "http://169.254.169.254/latest/api/token" + req, err := http.NewRequestWithContext(c.context, http.MethodPut, url, nil) + if err != nil { + return false + } + + req.Header.Add("X-aws-ec2-metadata-token-ttl-seconds", "300") + + const insecureSkipVerify = false + resp, err := c.httpDo(req, insecureSkipVerify) + if err != nil { + return false + } + defer resp.Body.Close() + + imdsToken, err := io.ReadAll(resp.Body) + if err != nil { + return false + } + + if resp.StatusCode != http.StatusOK || imdsToken == nil { + return false + } + + url = "http://169.254.169.254/latest/meta-data/" + req, err = http.NewRequestWithContext(c.context, http.MethodGet, url, nil) if err != nil { return false } + req.Header.Add("X-aws-ec2-metadata-token", string(imdsToken)) return c.httpReqSuccess(req) } diff --git a/lib/inventory/metadata/metadata_test.go b/lib/inventory/metadata/metadata_test.go index 2ab15118abbb9..47e9750e3ae16 100644 --- a/lib/inventory/metadata/metadata_test.go +++ b/lib/inventory/metadata/metadata_test.go @@ -283,18 +283,45 @@ func TestFetchCloudEnvironment(t *testing.T) { if insecureSkipVerify { return nil, trace.BadParameter("insecureSkipVerify should be false") } - if req.URL.String() != "http://169.254.169.254/latest/meta-data/" { - return nil, trace.NotFound("not found") + + if req.URL.String() == "http://169.254.169.254/latest/api/token" { + if req.Method != http.MethodPut { + return nil, trace.NotFound("not found") + } + if len(req.Header) != 1 { + return nil, trace.NotFound("not found") + } + if len(req.Header["X-Aws-Ec2-Metadata-Token-Ttl-Seconds"]) != 1 { + return nil, trace.NotFound("not found") + } + if req.Header["X-Aws-Ec2-Metadata-Token-Ttl-Seconds"][0] != "300" { + return nil, trace.NotFound("not found") + } + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(strings.NewReader("thisIsAFakeTestToken")), + }, nil } - if len(req.Header) != 0 { - return nil, trace.NotFound("not found") + + if req.URL.String() == "http://169.254.169.254/latest/meta-data/" { + if len(req.Header) != 1 { + return nil, trace.NotFound("not found") + } + if len(req.Header["X-Aws-Ec2-Metadata-Token"]) != 1 { + return nil, trace.NotFound("not found") + } + if req.Header["X-Aws-Ec2-Metadata-Token"][0] != "thisIsAFakeTestToken" { + return nil, trace.NotFound("not found") + } + return success, nil } - return success, nil + + return nil, trace.NotFound("not found") }, expected: "aws", }, { - desc: "gcp if on gcp ", + desc: "gcp if on gcp", httpDo: func(req *http.Request, insecureSkipVerify bool) (*http.Response, error) { if insecureSkipVerify { return nil, trace.BadParameter("insecureSkipVerify should be false")