Search code examples
pythonamazon-web-servicesamazon-ec2boto3

How to resolve "401 - Unauthorized" when trying to access metadata from ec2 instance?


I'm using a python script to launch an ec2 instance from which I want to display some metadata from the instance to the webpage. Yet I'm getting a 401 - Unauthorized error in place of where I am calling the metadata. I presume it is because of IMDSv2 being enabled but I have tried to disable it. However, it might be due to something I am unaware of.

echo "instance.metadata.v2.enabled = false" >> /etc/nitro/eni.cfg

This is my current script

import boto3
import webbrowser
import time

ec2 = boto3.resource('ec2')

myInstance = ec2.create_instances(
    ImageId='ami-0f34c5ae932e6f0e4',
    KeyName='mykey',
    MinCount=1,
    MaxCount=1,
    SecurityGroupIds=['sg-070cacabf704f07ef'],
    InstanceType='t2.nano',
    UserData=f"""#!/bin/bash
yum update -y
yum install httpd -y
systemctl enable httpd
systemctl start httpd

# Disable IMDSv2 and enable IMDSv1
echo "instance.metadata.v2.enabled = false" >> /etc/nitro/eni.cfg

# Fetch instance metadata using IMDSv1
instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
ami_id=$(curl -s http://169.254.169.254/latest/meta-data/ami-id)
instance_type=$(curl -s http://169.254.169.254/latest/meta-data/instance-type)

# Create an HTML file with the metadata displayed
cat > /var/www/html/index.html <<EOF
<!DOCTYPE html>
<html>
<head>
    <title>Instance Metadata</title>
</head>
<body>
    <h1>Hello World</h1>
    <p>Instance ID: $instance_id</p>
    <p>AMI ID: $ami_id</p>
    <p>Instance Type: $instance_type</p>
</body>
</html>
EOF
    """,
    TagSpecifications=[
        {
            'ResourceType': 'instance',
            'Tags': [
                {
                    'Key': 'Name',
                    'Value': 'MyInstance'
                }
            ]
        }
    ]
)

# Wait for the instance to be running
myInstance[0].wait_until_running()
myInstance[0].reload()

# Retrieve the instance ID, AMI ID, and instance type
instance_id = myInstance[0].id
image_id = myInstance[0].image_id
instance_type = myInstance[0].instance_type

# Wait for the instance to be fully ready
time.sleep(30)

# Get the public IP address of the instance
public_ip = myInstance[0].public_ip_address

print('Instance ID:', instance_id)
print('Public IP Address:', public_ip)

# Open web browser with the public IP
webbrowser.open_new_tab(f'http://{public_ip}/')

print('Opened web browser')

Solution

  • According to documentation:

    When IMDSv2 is optional, then both IMDSv2 and IMDSv1 will work.

    I suspect that there might be something odd with changing IMDSv in flight, so I'd suggest to run this machine with IMDSv1. Trying to overwrite it in the instance, it gives me permission-error.

    Solution for this is passing additional argument to ec2.create_instances:

    myInstance = ec2.create_instances(
        ImageId='ami-0f34c5ae932e6f0e4',
        MetadataOptions={
            'HttpTokens': 'optional',
        },
        [...]
    )
    

    You can of course remove the part about changing IMDSv in UserData.