Introduction: Credentials and EC2

Welcome to the final lesson of Developing with Core AWS Services. In this lesson, you will learn how AWS credentials work on EC2 instances and how IAM roles make it possible for your code to securely access AWS services. This ties together everything you've learned so far — from the boto3 Universal Pattern to working with S3, DynamoDB, SQS, and SNS — by revealing how your code automatically and securely obtains its credentials when running on AWS.

When you launch an EC2 instance, you often want it to interact with other AWS services, such as S3, DynamoDB, or Lambda. To do this, the instance needs credentials — just like you need a username and password to log in to a website. However, hardcoding credentials (putting them directly in your code) is risky and not recommended. If someone gets access to your code, they could also get your credentials and misuse your AWS account.

In this lesson, you will see how AWS provides a secure and automatic way for EC2 instances to get credentials using IAM roles and the EC2 metadata service.

Quick Recall: AWS Credentials and SDKs

Before we dive in, let's quickly remind ourselves what AWS credentials are and how SDKs use them.

AWS credentials are a pair of keys (an access key and a secret key) that allow you to make API calls to AWS services. In previous lessons, you have used these credentials with the AWS SDK for Python, called boto3. The SDK uses these credentials to sign requests and prove your identity to AWS.

For example, you might have seen code like this:

import boto3

# Create an S3 client using credentials from your environment
s3 = boto3.client('s3')

Here, boto3 automatically looks for credentials in several places, such as environment variables, configuration files, or the EC2 metadata service (which we'll cover next).

Hardcoding credentials in your code is not safe. Instead, AWS recommends using IAM roles, especially for EC2 instances.

How EC2 Gets Credentials: The Metadata Service

When you launch an EC2 instance, AWS provides a special service called the metadata service. This service is only accessible from inside the instance. It provides information about the instance, such as its ID, type, and, most importantly, temporary credentials if an IAM role is attached.

The metadata service is available at a special IP address: http://169.254.169.254/latest/meta-data/

You can use simple tools like curl to access this information from within the instance. For example:

curl http://169.254.169.254/latest/meta-data/instance-id

This command returns the instance ID, such as:

i-0123456789abcdef0

The metadata service provides much more than just the instance ID. You can explore various endpoints to get information about the instance's AMI, security groups, network interfaces, and more. Try running these commands directly on an EC2 instance to see the full range of available metadata:

# List all available metadata categories
curl http://169.254.169.254/latest/meta-data/

# Get specific information
curl http://169.254.169.254/latest/meta-data/ami-id
curl http://169.254.169.254/latest/meta-data/instance-type
curl http://169.254.169.254/latest/meta-data/placement/region

Running these commands directly will give you a complete view of what AWS-related information is accessible from within your instance.

If your instance has an IAM role attached, you can also get temporary credentials from the metadata service. These credentials are rotated automatically and are only valid for a short time, making them much safer than hardcoded keys.

IAM Roles for EC2: Secure Access in Action

An IAM role is a set of permissions that you can assign to AWS resources. When you attach an IAM role to an EC2 instance, AWS automatically provides temporary credentials to that instance through the metadata service.

This means your code running on the instance can access AWS services securely, without you ever needing to put credentials in your code.

For example, if you attach a role that allows access to S3, any code running on the instance can use boto3 to interact with S3, and the SDK will automatically use the credentials provided by the metadata service.

This is how AWS keeps your credentials safe and makes development easier.

Code Walkthrough: Discovering Credentials on EC2

Let's walk through a simple example that shows how an EC2 instance can access its own metadata and check for IAM role credentials. We'll build this step by step.

1. Writing a User Data Script

When you launch an EC2 instance, you can provide a user data script. This script runs automatically when the instance starts. Here's a simple Bash script that prints out some instance information and checks for an IAM role:

#!/bin/bash

echo "============ EC2 DEMO START ============"
echo "✅ User data script executing on EC2!"
echo "🔍 Instance Information:"

# Get metadata using curl
echo "   Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)"
echo "   AMI ID: $(curl -s http://169.254.169.254/latest/meta-data/ami-id)"  
echo "   Instance Type: $(curl -s http://169.254.169.254/latest/meta-data/instance-type)"
echo "   Region: $(curl -s http://169.254.169.254/latest/meta-data/placement/region)"

echo "🔍 IAM Role Check:"
if curl -s -f http://169.254.169.254/latest/meta-data/iam/security-credentials/ > /dev/null; then
    ROLE=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
    echo "   ✅ IAM Role: $ROLE"
else
    echo "   ❌ No IAM role attached"
fi

echo "💡 This demonstrates EC2 metadata service access!"
echo "============ EC2 DEMO END ============"

Explanation:

  • The script uses curl to fetch information from the metadata service.
  • It prints the instance ID, AMI ID, instance type, and region.
  • It checks if an IAM role is attached by trying to access the iam/security-credentials/ endpoint.
  • If a role is found, it prints the role name; otherwise, it says no role is attached.

Sample Output:

============ EC2 DEMO START ============
✅ User data script executing on EC2!
🔍 Instance Information:
   Instance ID: i-0123456789abcdef0
   AMI ID: ami-0c02fb55956c7d316
   Instance Type: t3.micro
   Region: us-west-2
🔍 IAM Role Check:
   ✅ IAM Role: MyEC2Role
💡 This demonstrates EC2 metadata service access!
============ EC2 DEMO END ============
2. Launching an EC2 Instance with the Script

Now, let's see how you can launch an EC2 instance and provide this script as user data using Python and boto3.

First, you need to load the script from a file:

def load_user_data_script():
    """Load the bash script for EC2 user data"""
    try:
        with open('ec2_demo.sh', 'r') as f:
            return f.read()
    except FileNotFoundError:
        print("❌ Error: ec2_demo.sh file not found")
        return None

Explanation:

  • This function reads the contents of ec2_demo.sh (the script above) so it can be passed to the EC2 instance as user data.

Next, you need a function to get the latest Amazon Linux 2 AMI ID:

def get_latest_amazon_linux_ami():
    """Get the latest Amazon Linux 2 AMI ID for the current region"""
    ssm = boto3.client('ssm')
    try:
        response = ssm.get_parameter(
            Name='/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
        )
        return response['Parameter']['Value']
    except Exception as e:
        print(f"❌ Error getting latest AMI: {e}")
        return None

Explanation:

  • This function uses the AWS Systems Manager Parameter Store to get the latest Amazon Linux 2 AMI ID.
  • AWS automatically maintains these parameters with the most current AMI IDs for each region.
  • This ensures you're always using an up-to-date AMI without hardcoding specific IDs.

Now you can launch the EC2 instance:

import boto3

def launch_ec2_demo():
    """Launch EC2 instance with simple bash demo"""
    print("🚀 Launching EC2 instance...")
    
    user_data = load_user_data_script()
    if not user_data:
        return None
    
    ami_id = get_latest_amazon_linux_ami()
    if not ami_id:
        return None
    
    print(f"📋 Using AMI: {ami_id}")
    
    ec2 = boto3.client('ec2')
    
    try:
        response = ec2.run_instances(
            ImageId=ami_id,
            MinCount=1,
            MaxCount=1,
            InstanceType='t3.micro',
            UserData=user_data,
            TagSpecifications=[
                {
                    'ResourceType': 'instance',
                    'Tags': [{'Key': 'Name', 'Value': 'metadata-demo'}]
                }
            ]
        )
        
        instance_id = response['Instances'][0]['InstanceId']
        print(f"✅ Launched instance: {instance_id}")
        return instance_id
        
    except Exception as e:
        print(f"❌ Error launching instance: {e}")
        return None

Explanation:

  • This function uses boto3 to launch a new EC2 instance.
  • It first gets the latest Amazon Linux 2 AMI ID dynamically, ensuring compatibility with the current region.
  • The UserData parameter is set to the contents of your script.
  • The instance will run the script on startup, printing out metadata and IAM role information.
3. Retrieving the Output

After the instance starts, you can retrieve the output of the user data script by checking the instance's console output:

import time

def wait_for_console_output(instance_id):
    """Wait for console output to appear and parse it"""
    print(f"\n⏳ Waiting for console output from {instance_id}...")
    print("💡 This can take 5-10 minutes - please be patient!")
    
    ec2 = boto3.client('ec2')
    
    # Wait for running state first
    waiter = ec2.get_waiter('instance_running')
    waiter.wait(InstanceIds=[instance_id])
    print("✅ Instance is running")
    
    # Wait for console output
    for attempt in range(15):  # Try for up to 15 minutes
        time.sleep(60)  # Wait 1 minute between attempts
        response = ec2.get_console_output(InstanceId=instance_id)
        console_output = response.get('Output', '')
        if console_output and 'EC2 DEMO START' in console_output:
            print("\n📋 Actual Demo Results from EC2:")
            print("=" * 60)
            print(console_output)
            print("=" * 60)
            return True
        else:
            print("   📭 No console output yet")
    print("⏰ Timeout waiting for demo output")
    return False

Explanation:

  • This function waits for the instance to start and then checks the console output for the results of your script.
  • It looks for the markers EC2 DEMO START and EC2 DEMO END to find your script's output.

Sample Output:

============ EC2 DEMO START ============
✅ User data script executing on EC2!
🔍 Instance Information:
   Instance ID: i-0123456789abcdef0
   AMI ID: ami-0c02fb55956c7d316
   Instance Type: t3.micro
   Region: us-west-2
🔍 IAM Role Check:
   ❌ No IAM role attached
💡 This demonstrates EC2 metadata service access!
============ EC2 DEMO END ============

If you attach an IAM role, you will see the role name instead of the "No IAM role attached" message.

Summary and What's Next

In this lesson, you learned:

  • Why EC2 instances need credentials to access AWS services.
  • The risks of hardcoding credentials and why it's not recommended.
  • How the EC2 metadata service provides instance information and, if available, IAM role credentials.
  • How IAM roles allow EC2 instances to securely and automatically get temporary credentials.
  • How to use a user data script and Python code to discover and display these credentials on a real EC2 instance.

You are now ready to practice these concepts in hands-on exercises. In the next section, you'll get to try launching EC2 instances, attaching IAM roles, and exploring the metadata service yourself. This will help you build a strong foundation for working securely and efficiently with AWS services from your EC2 instances.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal