Saturday, 8 June 2024

Access types in Amazon EKS

 



Types of access in EKS


Grant access to Kubernetes APIs



Our cluster has an Kubernetes API endpoint. Kubectl uses this API. We can authenticate to this API using two types of identities:
  • An AWS Identity and Access Management (IAM) principal (role or user)
  • A user in our own OpenID Connect (OIDC) provider
    • Requires authentication to our OIDC provider
    • setup: Authenticate users for your cluster from an OpenID Connect identity provider - Amazon EKS
    • We can associate one OIDC identity provider to our cluster.
    • Kubernetes doesn't provide an OIDC identity provider. We can use an existing public OIDC identity provider, or we can run our own identity provider.
    • The issuer URL of the OIDC identity provider must be publicly accessible, so that Amazon EKS can discover the signing keys. Amazon EKS doesn't support OIDC identity providers with self-signed certificates.
    • Before we can associate an OIDC identity provider with our cluster, we need the following information from our provider:
      • Issuer URL - The URL of the OIDC identity provider that allows the API server to discover public signing keys for verifying tokens. The URL must begin with https:// and should correspond to the iss claim in the provider's OIDC ID tokens. In accordance with the OIDC standard, path components are allowed but query parameters are not. Typically the URL consists of only a host name, like https://server.example.org or https://example.com. This URL should point to the level below .well-known/openid-configuration and must be publicly accessible over the internet.
      • Client ID (also known as audience) - The ID for the client application that makes authentication requests to the OIDC identity provider.
We can't disable IAM authentication to our cluster, because it's still required for joining nodes to a cluster. OIDC authentication is optional. Both can be enabled on cluster at the same time. 


IAM OIDC identity providers are entities in IAM that describe an external identity provider (IdP) service that supports the OpenID Connect (OIDC) standard, such as Google or Salesforce. You use an IAM OIDC identity provider when you want to establish trust between an OIDC-compatible IdP and your AWS account. This is useful when creating a mobile app or web application that requires access to AWS resources, but you don't want to create custom sign-in code or manage your own user identities. 

You can create and manage an IAM OIDC identity provider using the:AWS Management Console, the AWS Command Line Interface, the Tools for Windows PowerShell, or the IAM API.

After you create an IAM OIDC identity provider, you must create one or more IAM roles. A role is an identity in AWS that doesn't have its own credentials (as a user does). But in this context, a role is dynamically assigned to a federated user that is authenticated by your organization's IdP. The role permits your organization's IdP to request temporary security credentials for access to AWS. The policies assigned to the role determine what the federated users are allowed to do in AWS.  


Grant Kubernetes workloads access to AWS


 A workload is an application running in one or more Kubernetes pods.

A Kubernetes service account provides an identity for processes that run in a Pod.

If your Pod needs access to AWS services, you can map the service account to an IAM identity to grant that access.

Granting IAM permissions to workloads on Amazon Elastic Kubernetes Service clusters


Amazon EKS provides two ways to grant IAM permissions to workloads that run in Amazon EKS clusters:
  • IAM roles for service accounts (IRSA)
    • Allows pods to directly use IAM Roles (no need to inject into pods IAM User access credentials anymore)
    • We define the trust relationship between an IAM role and Kubernetes service account (that's a type of account in Kubernetes) in the role's trust policy.
    • Each EKS cluster has an OpenID Connect (OIDC) issuer URL associated with it. 
    • To use/enable IRSA a unique OpenID Connect provider needs to be created for each EKS cluster in IAM. 
  • EKS Pod Identities


In 2014, AWS Identity and Access Management added support for federated identities using OpenID Connect (OIDC). This feature allows you to authenticate AWS API calls with supported identity providers and receive a valid OIDC JSON web token (JWT). You can pass this token to the AWS STS AssumeRoleWithWebIdentity API operation and receive IAM temporary role credentials. You can use these credentials to interact with any AWS service, including Amazon S3 and DynamoDB.

Each JWT token is signed by a signing key pair. The keys are served on the OIDC provider managed by Amazon EKS and the private key rotates every 7 days. Amazon EKS keeps the public keys until they expire. If you connect external OIDC clients, be aware that you need to refresh the signing keys before the public key expires. 

Kubernetes has long used service accounts as its own internal identity system. Pods can authenticate with the Kubernetes API server using an auto-mounted token (which was a non-OIDC JWT) that only the Kubernetes API server could validate. These legacy service account tokens don't expire, and rotating the signing key is a difficult process. In Kubernetes version 1.12, support was added for a new ProjectedServiceAccountToken feature. This feature is an OIDC JSON web token that also contains the service account identity and supports a configurable audience.

Amazon EKS hosts a public OIDC discovery endpoint for each cluster that contains the signing keys for the ProjectedServiceAccountToken JSON web tokens so external systems, such as IAM, can validate and accept the OIDC tokens that are issued by Kubernetes.

OIDC federation access allows you to assume IAM roles via the Secure Token Service (STS), enabling authentication with an OIDC provider, receiving a JSON Web Token (JWT), which in turn can be used to assume an IAM role. Kubernetes, on the other hand, can issue so-called projected service account tokens, which happen to be valid OIDC JWTs for pods. Our setup equips each pod with a cryptographically-signed token that can be verified by STS against the OIDC provider of your choice to establish the pod’s identity.

new credential provider ”sts:AssumeRoleWithWebIdentity”


IRSA authentication
EKS OIDC IdP-signed JWT gets auto-mounted on each pod which uses service account.
AWS SDK sends AssumeRoleWithWebIdentity request containing the desired role and JWT.
STS uses IAM IdP associated to EKS OIDC IdP in order to verify identity of the pod.  


To use/enable IRSA:
  • 1) a unique OpenID Connect provider needs to be created for each EKS cluster in IAM. [Create an IAM OIDC provider for your cluster - Amazon EKS]
    • To use IAM roles for service accounts, an IAM OIDC provider must exist for your cluster's OIDC issuer URL.
    • If your cluster supports IAM roles for service accounts, it has an OpenID Connect (OIDC) issuer URL associated with it. 
    • You can view this URL in the Amazon EKS console, or you can use the following AWS CLI command to retrieve it.
$ aws eks describe-cluster --name cluster_name --query "cluster.identity.oidc.issuer" --output text

The expected output is as follows:

https://oidc.eks.<region-code>.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E

Any OIDC provider implementation needs to have a public OIDC issuer URL (see Issuer URL in OpenID Connect Discovery should be a working URL? - Stack Overflow). So for each cluster we'll have one implementation of OIDC provider.


Your Identity Provider’s Discovery Endpoint contains important configuration information. The OIDC discovery endpoint will always end with /.well-known/openid-configuration as described in the 
OpenID Provider Configuration Request documentation.

You can confirm that the discovery endpoint is correct by entering it in a browser window. If there is a JSON object with metadata about the connection returned, the endpoint is correct.


Obtain the thumbprint for an OpenID Connect identity provider - AWS Identity and Access Management
  • 2) Configure a Kubernetes service account to assume an IAM role
  • 3) Configure Pods to use a Kubernetes service account 
  • 4) Use a supported AWS SDK 

Just like we can create OIDC Identity Provider in IAM for representing an external, 3rd party OIDC Provider so we can allow access to AWS for a user authenticated with that 3rd party OIDC Provider, we can also create OIDC Identity Provider in IAM for representing an internal, EKS OIDC Provider which is available for each cluster (each cluster has its own provider). When EKS cluster is created, its OIDC Provider is also created with two pieces of data available:
  • OIDC Provider issuer
    • has its url which is used for discovery - see the screenshot above
  • OIDC Provider server TLS certificate
    • This certificate protects the url above (OIDC Provider issuer url) and is used for clients to verify the identity of OIDC Provider server
    • TLS certificate is necessary for establishing secure communication with the OIDC provider.

In Terraform, this certificate can be obtained like here:

data "tls_certificate" "example" {
  url = aws_eks_cluster.example.identity[0].oidc[0].issuer
}

To create  OIDC Identity Provider (IdP) in IAM for this cluster-specific OIDC Provider we can use aws_iam_openid_connect_provider :


resource "aws_iam_openid_connect_provider" "example" {
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [data.tls_certificate.example.certificates[0].sha1_fingerprint]
  url             = aws_eks_cluster.example.identity[0].oidc[0].issuer
}

This resource requires few pieces of information:
  • url  - Describes which OIDC IdP this resource represents. 
    • The URL of the identity provider. Corresponds to the iss claim.
  • thumbprint_list - Describes how will clients communicate with OIDC IdP (servers). HTTP communication goes through TLS secure channel so we need to know the identity of certificates to use.
    •  A list of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s).
    • When we create an OpenID Connect (OIDC) identity provider in IAM, IAM requires the thumbprint for the top intermediate certificate authority (CA) that signed the certificate used by the external identity provider (IdP). The thumbprint is a signature for the CA's certificate that was used to issue the certificate for the OIDC-compatible IdP. When we create an IAM OIDC identity provider, we are trusting identities authenticated by that IdP to have access to our AWS account. By using the CA's certificate thumbprint, we trust any certificate issued by that CA with the same DNS name as the one registered. This eliminates the need to update trusts in each account when we renew the IdP's signing certificate.
  • client_id_list - Describes which clients can use this OIDC IdP
    • A list of client IDs (also known as audiences). When a mobile or web app registers with an OpenID Connect provider, they establish a value that identifies the application. (This is the value that's sent as the client_id parameter on OAuth requests.)

AWS Security Token Service (STS)

  • Web service that enables you to request temporary, limited-privilege credentials for users
  • Available as a global service
  • All AWS STS requests go to a single endpoint at https://sts.amazonaws.com
  • Supports the following actions (requests):
    • AssumeRole
      • Returns a set of temporary security credentials that you can use to access AWS resources. These temporary credentials consist of an access key ID, a secret access key, and a security token. For example, user can authenticate via company's SSO and on AWS sign-on page can get these credentials that can be copied to ~/.aws/credentials under a profile and then this profile is used when accessing AWS.
    • AssumeRoleWithSAML
    • AssumeRoleWithWebIdentity
      • Issues a role session (temporary session)
      • Returns a set of temporary security credentials for users who have been authenticated in a mobile or web application with a web identity provider. Example providers include the OAuth 2.0 providers Login with Amazon and Facebook, or any OpenID Connect-compatible identity provider such as Google or Amazon Cognito federated identities.
      • Calling AssumeRoleWithWebIdentity does not require the use of AWS security credentials. Therefore, you can distribute an application (for example, on mobile devices) that requests temporary security credentials without including long-term AWS credentials in the application. You also don't need to deploy server-based proxy services that use long-term AWS credentials. Instead, the identity of the caller is validated by using a token from the web identity provider. 
      • The temporary security credentials returned by this API consist of an access key ID, a secret access key, and a security token. Applications can use these temporary security credentials to sign calls to AWS service API operations.
      • For example, user can authenticate via company's SSO and on AWS sign-on page can get these credentials that can be copied to ~/.aws/credentials under a profile and then this profile is used when accessing AWS.
      • By default, the temporary security credentials created by AssumeRoleWithWebIdentity last for one hour. However, you can use the optional DurationSeconds parameter to specify the duration of your session. You can provide a value from 900 seconds (15 minutes) up to the maximum session duration setting for the role. This setting can have a value from 1 hour to 12 hours.
      • Required parameters: 
        • RoleArn - The Amazon Resource Name (ARN) of the role that the caller is assuming.
        • RoleSessionName - An identifier for the assumed role session. Typically, you pass the name or identifier that is associated with the user who is using your application. That way, the temporary security credentials that your application will use are associated with that user. This session name is included as part of the ARN and assumed role ID in the AssumedRoleUser response element.
        • WebIdentityToken - The OAuth 2.0 access token or OpenID Connect ID token that is provided by the identity provider. Your application must get this token by authenticating the user who is using your application with a web identity provider before the application makes an AssumeRoleWithWebIdentity call. Timestamps in the token must be formatted as either an integer or a long integer. Only tokens with RSA algorithms (RS256) are supported. 
    • DecodeAuthorizationMessage
    • GetAccessKeyInfo
    • GetCallerIdentity
    • GetFederationToken
    • GetSessionToken


Example: Create an IAM role and associate it with a Kubernetes service account


Our custom service account that we have in cluster, my-service-account requires permission to e.g. launch EC2 instances. We need to assign certain IAM role to this service account (IRSA). 

We've created OIDC IdP in IAM for OIDC IdP associated with our cluster: 

arn:aws:iam::111122223333:oidc-provider/oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE

Prepare the policies for the role that the IdP-authenticated user will assume. As with any role, a role for a service account includes two policies:
  • trust policy that specifies who can assume the role
  • permissions policy that specifies the AWS actions and resources that the role owner is allowed or denied access to
Trust Policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::111122223333:oidc-provider/oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:default:my-service-account",
                    "oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}

Principal here is OIDC session principal which is a role session principal, see https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html#sts-session-principals.

This allows anyone who's authenticated with this OIDC IdP and who has my-service-account as the subject (user) and sts.amazonaws.com as the audience (client) in the WebIdentityToken (sent as the parameter of this request)  to be able to assume the role that has this policy attached to it.

Our service account authenticates with cluster's OIDC provider (IdP) and from it gets the token. This is Identity Token mentioned in OpenID Connect (OIDC) | My Public Notepad and it contains sub (subject identity, service account in our case) and aud (audience - client - who'll be using this token; STS in our case) claims.

It then sends AssumeRoleWithWebIdentity request with this token and role it requires (e.g. role for creating EC2 instances) to STS. STS (Client) then uses this token against EKS cluster IdP to identify user (service account) and finally to grant it a role. 


Applications in a Pod’s containers can use an AWS SDK or the AWS CLI to make API requests to AWS services (for example: to read from an S3 bucket or write to a DynamoDB table). Applications must sign their AWS API requests with AWS credentials. 

The credentials are associated with IAM (Identity and Access Management) identities. IAM defines what your application is allowed to do (read S3? write to DynamoDB? etc.)

This signing process proves that the request comes from an authorized source. 

Example: Our Pod needs to read a file from S3:
  • Our application code uses the AWS SDK to call s3.getObject().
  • The SDK automatically signs this request using AWS credentials.
  • AWS checks if those credentials have permission to read that S3 bucket.
  • If authorized, AWS returns the file.

How can pod securely get (temporary) AWS credentials?

This is typically solved using methods like IAM Roles for Service Accounts (IRSA) in EKS, which allows Pods to assume IAM roles without embedding credentials in your code or container images.

How it works:
  • You create an IAM role with the permissions your application needs
  • You associate this IAM role with a Kubernetes Service Account
  • Your Pod uses that Service Account
  • AWS automatically provides temporary credentials to your Pod via a webhook and projected volume
  • The AWS SDK automatically detects and uses these credentials

Why it's secure:
  • No credentials stored in code or container images
  • Credentials are temporary and automatically rotated
  • Fine-grained control: different Pods can have different permissions
  • Follows the principle of least privilege
Example setup:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app-sa
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
---
apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  serviceAccountName: my-app-sa
  containers:
  - name: app
    image: my-app:latest

Here's a complete step-by-step guide to create and configure IAM Roles for Service Accounts (IRSA) in EKS:

Prerequisites
  1. An EKS cluster with OIDC provider enabled
  2. AWS CLI and kubectl configured
  3. Appropriate AWS permissions to create IAM roles

Step 1: Enable OIDC Provider for Your EKS Cluster

First, check if your cluster already has an OIDC provider:

aws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer" --output text

If it returns a URL, you need to create an IAM OIDC provider:

# Set your cluster name
export CLUSTER_NAME=my-cluster
export AWS_REGION=us-east-1

# Create OIDC provider
eksctl utils associate-iam-oidc-provider \
    --cluster $CLUSTER_NAME \
    --region $AWS_REGION \
    --approve

Or manually via AWS CLI:

# Get OIDC ID
OIDC_ID=$(aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text | cut -d '/' -f 5)

# Check if it exists
aws iam list-open-id-connect-providers | grep $OIDC_ID

# Create if it doesn't exist
eksctl utils associate-iam-oidc-provider --cluster $CLUSTER_NAME --approve


Step 2: Create an IAM Policy


Create a policy with the permissions your application needs:

# Create a policy document
cat > my-app-policy.json <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::my-bucket",
                "arn:aws:s3:::my-bucket/*"
            ]
        }
    ]
}
EOF

# Create the policy
aws iam create-policy \
    --policy-name MyAppS3Policy \
    --policy-document file://my-app-policy.json


Note the policy ARN from the output (e.g., arn:aws:iam::123456789012:policy/MyAppS3Policy)

Step 3: Create the IAM Role with Trust Relationship


Option A: Using eksctl (Easiest)

eksctl create iamserviceaccount \
    --name my-app-sa \
    --namespace default \
    --cluster $CLUSTER_NAME \
    --region $AWS_REGION \
    --attach-policy-arn arn:aws:iam::123456789012:policy/MyAppS3Policy \
    --approve \
    --override-existing-serviceaccounts

This command:
  • Creates the IAM role
  • Creates the Kubernetes Service Account
  • Sets up the trust relationship automatically
  • Annotates the Service Account with the role ARN

Option B: Manual Creation (More Control)

1. Create the trust policy document:

# Get your account ID and OIDC provider
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export OIDC_PROVIDER=$(aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")

# Create trust policy
cat > trust-policy.json <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "${OIDC_PROVIDER}:sub": "system:serviceaccount:default:my-app-sa",
                    "${OIDC_PROVIDER}:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}
EOF

2. Create the IAM role:

aws iam create-role \
    --role-name my-app-role \
    --assume-role-policy-document file://trust-policy.json \
    --description "Role for my-app in EKS"

3. Attach the policy to the role:

bashaws iam attach-role-policy \
    --role-name my-app-role \
    --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/MyAppS3Policy

Step 4: Create Kubernetes Service Account

If you used eksctl, skip this step. Otherwise:

# Get the role ARN
export ROLE_ARN=$(aws iam get-role --role-name my-app-role --query Role.Arn --output text)

# Create the Service Account
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app-sa
  namespace: default
  annotations:
    eks.amazonaws.com/role-arn: ${ROLE_ARN}
EOF

Step 5: Use the Service Account in Your Pod

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      serviceAccountName: my-app-sa
      containers:
      - name: my-app
        image: my-app:latest
        env:
        - name: AWS_REGION
          value: us-east-1


Step 6: Verify It Works

Deploy your application and check:

# Deploy
kubectl apply -f my-app-deployment.yaml

# Check the pod
kubectl get pods

# Exec into the pod and test
kubectl exec -it <pod-name> -- bash

# Inside the pod, check environment variables
env | grep AWS

# You should see:
# AWS_ROLE_ARN=arn:aws:iam::123456789012:role/my-app-role
# AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token

# Test AWS access
aws s3 ls s3://my-bucket/

Troubleshooting

Check Service Account annotation:
bashkubectl describe sa my-app-sa -n default

Check pod environment variables:
bashkubectl exec <pod-name> -- env | grep AWS

Check if the token file exists:
bashkubectl exec <pod-name> -- ls -la /var/run/secrets/eks.amazonaws.com/serviceaccount/

Test IAM role assumption:
bashkubectl exec <pod-name> -- aws sts get-caller-identity

Key Points to Remember

Namespace matters: The trust policy specifies system:serviceaccount:NAMESPACE:SERVICE_ACCOUNT_NAME
One role per Service Account: Each SA can only be annotated with one IAM role
Multiple pods can use the same SA: All pods using the same SA get the same permissions
Automatic credential refresh: The AWS SDK handles token rotation automatically
No manual credential management: Never need to handle access keys

This setup provides secure, temporary, automatically-rotated credentials without ever exposing long-lived AWS access keys!


How does IAM Roles for Service Accounts (IRSA)  differ from any other regular IAM role?


IRSA roles are regular IAM roles with a special trust policy that allows them to be assumed by Kubernetes Service Accounts.

Key Difference: The Trust Policy

Regular IAM Role Trust Policy


A typical IAM role might trust:

EC2 instances:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

(this means that EC2 instance can assume this role; role has a trust in EC2)

AWS accounts or users:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:user/john"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}


IRSA Role Trust Policy



An IRSA role trusts the EKS OIDC provider and specific Kubernetes Service Accounts:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:default:my-app-sa",
          "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}


Detailed Comparison:



How IRSA Works


1. OIDC Provider Setup

When you enable IRSA, AWS creates an OpenID Connect (OIDC) provider that trusts your EKS cluster:

# The OIDC provider looks like:
arn:aws:iam::123456789012:oidc-provider/oidc.eks.region.amazonaws.com/id/CLUSTER_ID

This acts as a federation bridge between Kubernetes and AWS IAM.

2. Token Issuance

When a Pod starts with a Service Account:

Kubernetes API server generates a JWT (JSON Web Token)
This token is signed by the Kubernetes cluster's private key
The token is mounted into the Pod at /var/run/secrets/eks.amazonaws.com/serviceaccount/token

3. Token Contents

The JWT contains claims like:

{
  "iss": "https://oidc.eks.us-east-1.amazonaws.com/id/CLUSTER_ID",
  "sub": "system:serviceaccount:default:my-app-sa",
  "aud": ["sts.amazonaws.com"],
  "exp": 1234567890
}

iss (issuer): The EKS OIDC provider URL
sub (subject): The Kubernetes Service Account (namespace:sa-name)
aud (audience): Who the token is for (AWS STS)
exp (expiration): Token lifetime (typically 1 hour)

4. Role Assumption Process

┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│   Pod with  │         │  AWS STS     │         │  IAM Role   │
│   SA Token  │         │  (Security   │         │  (IRSA)     │
│             │         │   Token Svc) │         │             │
└──────┬──────┘         └──────┬───────┘         └──────┬──────┘
       │                       │                        │
       │ 1. AWS SDK reads      │                        │
       │    JWT token from     │                        │
       │    mounted file       │                        │
       │                       │                        │
       │ 2. AssumeRoleWithWebIdentity                   │
       │    (token + role ARN) │                        │
       ├──────────────────────>│                        │
       │                       │                        │
       │                       │ 3. Validates token     │
       │                       │    with OIDC provider  │
       │                       │                        │
       │                       │ 4. Checks trust policy │
       │                       ├───────────────────────>│
       │                       │                        │
       │                       │ 5. Token matches       │
       │                       │<───────────────────────┤
       │                       │                        │
       │ 6. Returns temporary  │                        │
       │    AWS credentials    │                        │
       │<──────────────────────┤                        │
       │    (AccessKeyId,      │                        │
       │     SecretAccessKey,  │                        │
       │     SessionToken)     │                        │
       │                       │                        │
       │ 7. Use credentials    │                        │
       │    to call AWS APIs   │                        │
       │                       │                        │

What Makes IRSA Special

1. Fine-Grained Security

The trust policy condition specifies exactly which Service Account can assume the role:

"Condition": {
  "StringEquals": {
    "oidc.eks....:sub": "system:serviceaccount:NAMESPACE:SA_NAME"
  }
}

This means:

Only that specific Service Account in that specific namespace can use this role
Other pods, even on the same node, cannot access these credentials

2. No Shared Credentials

Unlike EC2 instance roles where all pods on a node share the same credentials:

Each pod gets its own identity
Different pods can have different permissions
True multi-tenancy on the same cluster

3. Short-Lived Credentials

JWT tokens expire (typically 1 hour)
AWS temporary credentials expire (configurable, default 1 hour)
Automatically rotated by the AWS SDK
No long-lived access keys to manage or leak

4. No Instance Metadata Service

Traditional EC2 roles use the instance metadata service (IMDS):
bashcurl http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name
IRSA doesn't use IMDS at all - it uses the mounted JWT token:
bashcat /var/run/secrets/eks.amazonaws.com/serviceaccount/token
The Permissions Side (Same as Regular Roles)

Once the role is assumed, the permissions work identically:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}

This part is no different from any other IAM role. The only difference is who can assume the role (the trust policy).

Quick Comparison Example

EC2 Instance Role (Traditional)
EC2 Instance → Instance Profile → IAM Role → Permissions
└─ All containers on this instance share the same role

IRSA

Pod → Service Account → JWT Token → IAM Role → Permissions
└─ Each pod can have a different role based on its Service Account

Summary

IRSA roles ARE regular IAM roles, but with these characteristics:

✅ Trust Policy: Uses federated identity (OIDC) instead of AWS services
✅ Assume Method: Uses AssumeRoleWithWebIdentity instead of AssumeRole
✅ Authentication: Uses Kubernetes-issued JWT tokens instead of AWS credentials
✅ Granularity: Per-pod permissions instead of per-node
✅ Security: Short-lived, auto-rotated credentials with no shared access

The "magic" is in the trust relationship and the OIDC federation - everything else is standard IAM!




Troubleshooting



"error": "fetching instance types using ec2.DescribeInstanceTypes,
WebIdentityErr: failed to retrieve credentials\ncaused by: AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity\n\tstatus code: 403

References:


No comments: