Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
Specific blocks of IP addresses have a designated purpose as listed under the IANA special-purpose address registry. Any networking infrastructure needs to adhere to these standards. Hence, cloud services use an appropriate block according to their use case. 169.254.169.254 is one such IP address accessible in cloud machines.
In this tutorial, we’ll study the meaning of 169.254.169.254 within a virtual computer on the cloud. Firstly, we’ll examine the address block that this IP address belongs to. Then, we’ll explore how cloud service providers use it. Lastly, we’ll review related security issues and steps to defend our cloud machines against them.
All scripts in this tutorial were run on a Debian 11 (Bullseye) OS and Bash shell.
A link-local IP address represents a host accessible within the same physical or logical network link.
This means that packets exchanged with such a host contain an unmodified IP header and payload. As per RFC 6890, IETF has reserved the block from 169.254.0.0 to 169.254.255.255 (both inclusive) for this purpose. The CIDR notation 169.254.0.0/16 denotes this block. Amongst these addresses, IETF has reserved the first and last 256 addresses for future use. Thus, there are 65024 usable link-local IP addresses.
This block exhibits certain features as described in RFC 3927:
A host uses a pseudo-random number generator to select an IP address within this block for an interface. Then, it undergoes a process to claim this address as its own.
ARP is used in the claiming process. This process is also called Automatic Private IP Address (APIPA) configuration.
A host follows these steps:
Now, the host can start using that interface for new connections.
A routing table comprises a list of entries for communicating with other devices within the same network. Thus, the presence of 169.254.169.254 in the routing table indicates that a link-local device is connected.
Let’s check the IP routing table of a cloud instance on Azure:
$ ip route
default via 10.2.0.1 dev eth0
10.2.0.0/24 dev eth0 proto kernel scope link src 10.2.0.4
168.63.129.16 via 10.2.0.1 dev eth0
169.254.169.254 via 10.2.0.1 dev eth0
In this output, we can see that 169.254.169.254 is accessible over the interface eth0.
So far, we’ve talked about link-local IPv4 addresses. However, the corresponding link-local IPv6 block, denoted by fe80::0/10, plays a prominent role in IPv6.
Every interface configured to use IPv6 must possess a link-local IPv6 address. This address is used for scenarios like IPv6 address auto-configuration and neighbor discovery as defined in RFC 4862.
Cloud service providers control physical infrastructure on the cloud. Thus, they can configure devices having local access to any instance.
Link-local IP addresses are suitable for auto-configuration services due to many reasons:
Due to these reasons, services required for virtual computers on the cloud, such as Network Time Protocol (NTP) and Instance Metadata Service (IMDS), use link-local IP addresses.
There may arise a need to access the metadata of virtual computers running on the cloud programmatically. For instance, this could be metadata like its network configuration, availability zone, device type, and other information. This information is available through Instance Metadata Service (IMDS).
IMDS runs outside the cloud instance due to these reasons:
IMDS is accessible via 169.254.169.254 in most cloud platforms. We can make HTTP requests from within the instance to access IMDS.
AWS offers Elastic Cloud Compute (EC2) as a virtual computer on the cloud. When creating an EC2 instance, we can configure metadata settings under the “Advanced details” section.
AWS IMDS comes in two versions: IMDSv1 and IMDSv2. The latter is more secure than the former. By default, both are enabled for EC2. We can access them via 169.254.169.254.
AWS provides a complete list of instance metadata categories. For now, let’s make an HTTP request to the root endpoint. We’ll specify the API version in the request path. We can access the latest API version using the value “latest”. Let’s use curl for making this request:
$ curl -s "http://169.254.169.254/latest/meta-data"
ami-id
...
public-hostname
public-ipv4
public-keys/
...
system
The output is in text format, analogous to directory listing. Let’s extract the public IPv4 address by providing the respective path:
$ curl -s "http://169.254.169.254/latest/meta-data/public-ipv4"
54.57.132.214
Thus, we obtained the public IPv4 address of the EC2 instance using AWS IMDSv1. Let’s try the same using IMDSv2:
$ TOKEN=`curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 3600"` && \
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/public-ipv4"
54.57.132.214
Here, we first obtained a token for invoking the API. Then, we used that token in the subsequent request.
AWS recommends enabling only IMDSv2 to enhance security.
Virtual Machine (VM) is a cloud instance service on Microsoft Azure. VMs and scale set instances support Azure IMDS. Apart from instance metadata, this IMDS allows fetching metadata for scheduled events, load balancers, and other details related to the instance.
Azure IMDS requires an API version as a query parameter. It also needs a header Metadata: true. Furthermore, we’ll use the –noproxy option because IMDS doesn’t support proxied requests. Lastly, we’ll use jq to format the JSON output. Let’s make a query to the root endpoint:
$ curl -s -H "Metadata:true" --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | jq
{
"compute": {
...
},
"network": {
"interface": [
{
"ipv4": {
"ipAddress": [
{
"privateIpAddress": "10.2.0.4",
"publicIpAddress": "20.185.37.134"
}
],
...
},
...
}
]
}
}
Above, we highlight the IPv4 metadata within a JSON. Alternately, we can obtain an output similar to AWS using a query parameter format=text:
$ curl -s -H "Metadata:true" --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=2021-02-01&format=text"
compute/
network/
Here, we can see the root-level keys from the previous JSON output.
Let’s extract the public IPv4 address using its path:
$ curl -s -H "Metadata:true" --noproxy "*" "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2021-02-01&format=text"
20.185.37.134
Thus, we used the JSON path to obtain the public IPv4 address. This command works for instances associated with a basic SKU public IP address. On the other hand, it doesn’t work for the ones associated with a standard SKU. For such VMs, we can use the load balancer metadata instead:
$ curl -s -H "Metadata:true" --noproxy "*" "http://169.254.169.254:80/metadata/loadbalancer?api-version=2020-10-01" | jq '.loadbalancer.publicIpAddresses[0].frontendIpAddress' -j
20.185.37.134
We accessed the value using jq by specifying the path to the key. Here, the -j option returns a raw string without a new line.
Compute Engine is a virtual computer service on GCP. Although the metadata IP address is still 169.254.169.254, we can access it using the host metadata.google.internal. Additionally, GCP IMDS allows access to the project metadata to which the instance belongs.
GCP IMDS requires a header Metadata-Flavor: Google. Let’s try the root endpoint:
$ curl -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/"
instance/
oslogin/
project/
The above output is similar to the one on AWS. Let’s extract the public IPv4 address by specifying the path in kebab case:
$ curl -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"
34.171.124.2
Thus, we fetched the public IPv4 address using GCP IMDS.
IBM Cloud offers IMDS as part of its VPC Virtual Server solution. IMDS is off by default. Moreover, it requires an access token.
Oracle Cloud also offers IMDS as part of its Compute instance. They recommend upgrading to IMDSv2 and turning off IMDSv1.
IMDS is accessible from within a cloud instance via HTTP requests. Thus, any service or application running on that instance or logged-in user can access IMDS.
Moreover, IMDS access requires no additional authentication on most of these clouds. However, metadata may contain sensitive information like secret keys or tokens. Threat actors can steal this information using attacks like Server-Side Request Forgery (SSRF).
When a server makes HTTP requests based on user input without proper validation, SSRF vulnerability arises. An external user could manipulate the running server to make requests to IMDS. If the IMDS response body becomes available to the user, sensitive information may get exposed.
The first line of defense is to ensure that services or applications inside the cloud instance aren’t running with root privileges. Then, we’ll allow only the root user of the cloud instance to access IMDS. Let’s create an iptables firewall rule to enforce this restriction:
$ sudo iptables --append OUTPUT --proto tcp --destination 169.254.169.254 --match owner ! --uid-owner root --jump REJECT
Hereafter, any non-root service or application running within the instance can’t access IMDS. The same goes for any user logged in to the device without root privileges.
There are a few additional defensive measures we can take:
By applying such defensive measures, we can secure our cloud services from security issues posed by IMDS.
In this article, we studied the meaning of 169.254.169.254 on the cloud. Initially, we learned about the link-local IP address block 169.254.0.0/16 and its importance in IP address auto-configuration.
Then, we explored how multiple cloud service providers use 169.254.169.254 as IMDS for programmatic access to metadata within cloud instances.
Lastly, we examined security issues posed by IMDS and the best practices to defend against them by restricting access to specific users.