Skip to content

Commit aeae1d8

Browse files
Use IMDSv2 instead of v1 when calling the EC2 Instance metadata.
1 parent 872a31b commit aeae1d8

File tree

3 files changed

+59
-14
lines changed

3 files changed

+59
-14
lines changed

codeguru_profiler_agent/agent_metadata/aws_ec2_instance.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,19 @@
99
EC2_HOST_NAME_URI = DEFAULT_EC2_METADATA_URI + "local-hostname"
1010
EC2_HOST_INSTANCE_TYPE_URI = DEFAULT_EC2_METADATA_URI + "instance-type"
1111

12+
# Used for IMDSv2 to retrieve API token that will be used to call the EC2 METADATA service.
13+
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
14+
# Bandit marks the following line as risky because it contains the word "token",
15+
# thought it doesn't contain any secret; ignoring with # nosec
16+
# https://bandit.readthedocs.io/en/latest/plugins/b105_hardcoded_password_string.html
17+
EC2_API_TOKEN_URI = "http://169.254.169.254/latest/api/token" # nosec
18+
EC2_METADATA_TOKEN_HEADER_KEY = 'X-aws-ec2-metadata-token' # nosec
19+
EC2_METADATA_TOKEN_TTL_HEADER_KEY = 'X-aws-ec2-metadata-token-ttl-seconds' # nosec
20+
EC2_METADATA_TOKEN_TTL_HEADER_VALUE = '21600' # nosec
21+
1222
logger = logging.getLogger(__name__)
1323

24+
1425
class AWSEC2Instance(FleetInfo):
1526
"""
1627
This class will get and parse the EC2 metadata if available.
@@ -27,12 +38,29 @@ def get_fleet_instance_id(self):
2738

2839
@classmethod
2940
def __look_up_host_name(cls):
30-
# The id of the fleet element. Eg. host name in ec2.
31-
return http_get(url=EC2_HOST_NAME_URI).read().decode()
41+
"""
42+
The id of the fleet element. Eg. host name in ec2.
43+
"""
44+
return cls.__look_up_with_IMDSv2(EC2_HOST_NAME_URI)
3245

3346
@classmethod
3447
def __look_up_instance_type(cls):
35-
return http_get(url=EC2_HOST_INSTANCE_TYPE_URI).read().decode()
48+
"""
49+
The type of the instance. Eg. m5.2xlarge
50+
"""
51+
return cls.__look_up_with_IMDSv2(EC2_HOST_INSTANCE_TYPE_URI)
52+
53+
@classmethod
54+
def __look_up_with_IMDSv2(cls, url):
55+
return http_get(url=url,
56+
headers={EC2_METADATA_TOKEN_HEADER_KEY: cls.__look_up_ec2_api_token()}) \
57+
.read().decode()
58+
59+
@classmethod
60+
def __look_up_ec2_api_token(cls):
61+
return http_get(url=EC2_API_TOKEN_URI,
62+
headers={EC2_METADATA_TOKEN_TTL_HEADER_KEY: EC2_METADATA_TOKEN_TTL_HEADER_VALUE}) \
63+
.read().decode()
3664

3765
@classmethod
3866
def look_up_metadata(cls):
@@ -45,10 +73,10 @@ def look_up_metadata(cls):
4573
log_exception(logger, "Unable to get Ec2 instance metadata, this is normal when running in a different "
4674
"environment (e.g. Fargate), profiler will still work")
4775
return None
48-
76+
4977
def serialize_to_map(self):
5078
return {
51-
"computeType": "aws_ec2_instance",
52-
"hostName": self.host_name,
79+
"computeType": "aws_ec2_instance",
80+
"hostName": self.host_name,
5381
"hostType": self.host_type
5482
}

codeguru_profiler_agent/agent_metadata/fleet_info.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1-
from abc import ABCMeta, abstractmethod
1+
import logging
2+
import os
23
import uuid
4+
5+
from abc import ABCMeta, abstractmethod
36
from urllib import request
4-
import os
7+
from urllib.request import Request
58

69
METADATA_URI_TIMEOUT_SECONDS = 3
710

8-
def http_get(url):
11+
logger = logging.getLogger(__name__)
12+
13+
14+
def http_get(url, headers={}):
915
# request.urlopen has been flagged as risky because it can be used to open local files if url starts with
1016
# file://, to protect us from that we add a check in the passed url.
1117
# With this check we can tell bandit (static analysis tool) to ignore this error with #nosec
18+
# https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b310-urllib-urlopen
19+
20+
logger.debug("Making a request to {} with headers set for these keys: {}".format(url, headers.keys()))
1221
if not url.startswith("http"):
1322
raise ValueError("url for metadata is not a valid http address. We will not try to get metadata")
14-
return request.urlopen(url, timeout=METADATA_URI_TIMEOUT_SECONDS) # nosec
23+
req = Request(url)
24+
for key in headers:
25+
req.add_header(key, headers[key])
26+
return request.urlopen(req, timeout=METADATA_URI_TIMEOUT_SECONDS) # nosec
1527

1628

1729
class FleetInfo(metaclass=ABCMeta): # pragma: no cover
@@ -23,6 +35,7 @@ def __init__(self):
2335
This id can be the hostname for an EC2 or the task ARN for Fargate.
2436
@return the id in string.
2537
"""
38+
2639
@abstractmethod
2740
def get_fleet_instance_id(self):
2841
pass
@@ -42,7 +55,7 @@ class DefaultFleetInfo(FleetInfo):
4255

4356
def __init__(self):
4457
self.fleet_instance_id = str(uuid.uuid4())
45-
try:
58+
try:
4659
# sched_getaffinity gives the number of logical cpus that the process can use on Unix systems.
4760
# If not available, we default to the cpu_count().
4861
self.vCPUs = len(os.sched_getaffinity(0))
@@ -54,7 +67,7 @@ def get_fleet_instance_id(self):
5467

5568
def serialize_to_map(self):
5669
return {
57-
"id": self.fleet_instance_id,
58-
"type": "UNKNOWN",
70+
"id": self.fleet_instance_id,
71+
"type": "UNKNOWN",
5972
"vCPUs": self.vCPUs
6073
}

test/unit/agent_metadata/test_aws_ec2_instance.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import httpretty
33
import sys
44
from codeguru_profiler_agent.agent_metadata.aws_ec2_instance import EC2_HOST_NAME_URI, \
5-
EC2_HOST_INSTANCE_TYPE_URI
5+
EC2_HOST_INSTANCE_TYPE_URI, EC2_API_TOKEN_URI
66
from codeguru_profiler_agent.agent_metadata.aws_ec2_instance import AWSEC2Instance
77

88

@@ -41,6 +41,10 @@ def around(self):
4141
httpretty.GET,
4242
EC2_HOST_INSTANCE_TYPE_URI,
4343
body="testHostType")
44+
httpretty.register_uri(
45+
httpretty.GET,
46+
EC2_API_TOKEN_URI,
47+
body="PARIOq_FXbIyL0maE9RcmrsyWtylvFh1ZDt0NrRUyNxeV1-DlpFpA==")
4448
yield
4549
httpretty.disable()
4650
httpretty.reset()

0 commit comments

Comments
 (0)