<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eric Johnson</title>
    <description>The latest articles on DEV Community by Eric Johnson (@ejohn20).</description>
    <link>https://dev.to/ejohn20</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F836140%2F1ac3b4c8-7691-4b01-aa97-31f05a3c860d.jpeg</url>
      <title>DEV Community: Eric Johnson</title>
      <link>https://dev.to/ejohn20</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ejohn20"/>
    <language>en</language>
    <item>
      <title>Using AWS Outbound Identity Federation for Azure Resources</title>
      <dc:creator>Eric Johnson</dc:creator>
      <pubDate>Fri, 19 Dec 2025 17:30:20 +0000</pubDate>
      <link>https://dev.to/aws-builders/using-aws-outbound-identity-federation-for-azure-resources-2d7c</link>
      <guid>https://dev.to/aws-builders/using-aws-outbound-identity-federation-for-azure-resources-2d7c</guid>
      <description>&lt;p&gt;AWS re:Invent 2025 saw several new security capabilities added for security teams to use in their AWS architecture designs. One of these new capabilities, &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound_getting_started.html" rel="noopener noreferrer"&gt;IAM Outbound Identity Federation&lt;/a&gt;, addresses a missing piece in our open source &lt;a href="https://github.com/pumasecurity/nymeria" rel="noopener noreferrer"&gt;Nymeria cross-cloud workload identity&lt;/a&gt; project design. This blog explores the AWS IAM Outbound Identity Federation configuration and the changes we've made to Nymeria, allowing a virtual machine running in AWS to access external cloud resources using an AWS-signed OpenID Connect identity token.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Identity Federation Limitations
&lt;/h2&gt;

&lt;p&gt;Unlike Google Cloud's &lt;a href="https://docs.cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds#aws" rel="noopener noreferrer"&gt;workload identity pool AWS provider&lt;/a&gt;, Azure managed identity does not support IAM authentication with pre-signed URLs. Azure &lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity" rel="noopener noreferrer"&gt;managed identity&lt;/a&gt; only supports external OpenID Connect (OIDC) providers, which means that workloads running in AWS could not federate into an Azure environment without using another service that can convert OIDC tokens (e.g., AWS EKS or Cognito).&lt;/p&gt;

&lt;p&gt;Up until now, the Puma Security &lt;a href="https://github.com/pumasecurity/nymeria" rel="noopener noreferrer"&gt;Nymeria cross-cloud workload identity&lt;/a&gt; project had examples for federating from AWS Lambda functions and EKS pods into both Azure and Google Cloud storage resources. With the &lt;em&gt;2025 pre:Invent&lt;/em&gt; release of &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound_getting_started.html" rel="noopener noreferrer"&gt;IAM Outbound Identity Federation&lt;/a&gt;, we've now added support for an AWS EC2 instance to federate into an Azure storage resource.&lt;/p&gt;

&lt;p&gt;The following diagram illustrates the architecture design for an AWS EC2 instance using AWS Outbound Identity Federation to access an Azure Blob storage container. The EC2 instance can now request a signed OIDC token from AWS IAM using the &lt;em&gt;sts:GetWebIdentityToken&lt;/em&gt; API. The AWS-signed OIDC token can then be used to log in to the Azure tenant's user-assigned managed identity. Finally, the EC2 instance can use the Azure access token to access the storage container and blob objects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9fe8rx2q9eb1lhpb6sl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9fe8rx2q9eb1lhpb6sl.png" alt="Nymeria AWS IAM Outbound Identity Federation" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Outbound Identity Federation Setup
&lt;/h2&gt;

&lt;p&gt;Before you can use the AWS IAM Outbound Identity Federation feature, you must enable the Outbound Identity Federation capability in your AWS account. This can be done using the AWS Management Console or the AWS Terraform provider's (v6.26.0 or later) new &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_outbound_web_identity_federation" rel="noopener noreferrer"&gt;aws_iam_outbound_web_identity_federation&lt;/a&gt; resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# enables account level web identity federation
resource "aws_iam_outbound_web_identity_federation" "this" {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying the configuration, you will see that the Outbound Identity Federation feature is enabled in the AWS IAM console, and a unique &lt;em&gt;Token Issuer URL&lt;/em&gt; is generated for your AWS account. The token issuer URL will be important later when we configure the Azure managed identity's trust relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwr280d5k0bvdv0zcxnyz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwr280d5k0bvdv0zcxnyz.png" alt="AWS IAM Outbound Identity Federation Configuration" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Outbound Identity Federation Permissions
&lt;/h2&gt;

&lt;p&gt;To use the Outbound Identity Federation feature, create an AWS IAM role and an instance profile to attach to the EC2 instance. The role's permissions need to include the &lt;em&gt;sts:GetWebIdentityToken&lt;/em&gt; permission to request an outbound identity token from AWS IAM.&lt;/p&gt;

&lt;p&gt;The following Terraform configuration shows how to create an IAM policy document that includes the &lt;em&gt;sts:GetWebIdentityToken&lt;/em&gt; permission.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data "aws_iam_policy_document" "cross_cloud" {
  statement {
    sid    = "AllowTokenVending"
    effect = "Allow"
    actions = [
      "sts:GetWebIdentityToken",
    ]
    resources = [
      "*",
    ]
  }
}

resource "aws_iam_policy" "cross_cloud" {
  name        = "nymeria-cross-cloud-token-${random_string.unique_id.result}"
  path        = "/"
  description = "IAM policy for Nymeria VM"
  policy      = data.aws_iam_policy_document.cross_cloud.json

  tags = {
    Product = "Nymeria"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the EC2 instance running, connect to the instance using the SSM session manager or using SSH. Once connected, use the AWS CLI to request an outbound federated identity token with the following attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;audience&lt;/em&gt;: Use the default Azure federation audience &lt;em&gt;api://AzureADTokenExchange&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;signing algorithm&lt;/em&gt;: Use &lt;em&gt;RS256&lt;/em&gt; for RSA with SHA-256 signatures&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;duration seconds&lt;/em&gt;: Set the token validity period to 300 seconds (5 minutes)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sts get-web-identity-token &lt;span class="nt"&gt;--audience&lt;/span&gt; api://AzureADTokenExchange &lt;span class="nt"&gt;--signing-algorithm&lt;/span&gt; RS256 &lt;span class="nt"&gt;--duration-seconds&lt;/span&gt; 300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will show the signed OIDC token in the &lt;em&gt;WebIdentityToken&lt;/em&gt; attribute and the token's expiration time in the &lt;em&gt;Expiration&lt;/em&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"WebIdentityToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJraWQiOiJSU0FfMCIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Expiration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-12-19T04:06:07.577000+00:00"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Azure Managed Identity Configuration
&lt;/h2&gt;

&lt;p&gt;Now that we know how to request an AWS outbound identity token, we can configure the Azure managed identity to trust the AWS outbound identity provider. This involves extracting the necessary claims from the AWS identity token and using those claims to configure the Azure managed identity's federated credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Outbound Identity Token Claims
&lt;/h3&gt;

&lt;p&gt;Start by decoding the AWS identity token using &lt;em&gt;jq&lt;/em&gt;. We will need to extract the token's &lt;em&gt;iss&lt;/em&gt; (issuer) and &lt;em&gt;sub&lt;/em&gt; (subject) claims to configure the Azure managed identity's trust relationship.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws sts get-web-identity-token &lt;span class="nt"&gt;--audience&lt;/span&gt; api://AzureADTokenExchange &lt;span class="nt"&gt;--signing-algorithm&lt;/span&gt; RS256 &lt;span class="nt"&gt;--duration-seconds&lt;/span&gt; 300 | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.WebIdentityToken'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

jq &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s1"&gt;'split(".") | .[1] | @base64d | fromjson'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$JWT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decoded token payload will look similar to the following example. The subject (sub) claim uniquely identifies the AWS IAM Role that the token was create for, the issuer (iss) points to the AWS account's outbound federated identity provider, and audience (aud) points to the service that will be consuming the token (in this case Azure). The AWS outbound federation token also includes additional AWS metadata in the &lt;em&gt;&lt;a href="https://sts.amazonaws.com/" rel="noopener noreferrer"&gt;https://sts.amazonaws.com/&lt;/a&gt;&lt;/em&gt; claim, including the AWS VPC ID, Organization Id AWS account ID, OU Path, source region, source instance, and principal tags. These additional claims can be used to create advanced claim filters when configuring trust relationships with other cloud providers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: Azure's managed identity federation does not support advanced claim filters, such as those found in the &lt;em&gt;&lt;a href="https://sts.amazonaws.com/" rel="noopener noreferrer"&gt;https://sts.amazonaws.com/&lt;/a&gt;&lt;/em&gt; claim. Only the &lt;em&gt;iss&lt;/em&gt;, &lt;em&gt;aud&lt;/em&gt;, &lt;em&gt;sub&lt;/em&gt; claims will be needed when configuring Azure managed identity's trust configuration below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api://AzureADTokenExchange"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/nymeria-cross-cloud-9zo9h8c5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"https://sts.amazonaws.com/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ec2_instance_source_vpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vpc-1234567890123456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ec2_role_delivery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"org_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"o-abcd1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aws_account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789012"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ou_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"o-abcd1234/r-abc/ou-abc-def/"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"original_session_exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-12-19T10:21:06Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ec2_source_instance_arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ec2:us-east-2:123456789012:instance/i-12345678901234567"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"principal_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/nymeria-cross-cloud-9zo9h8c5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"principal_tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nymeria"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ec2_instance_source_private_ipv4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10.142.128.107"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://53e20c38-5c03-41f7-8baa-a67e81974de0.tokens.sts.global.api.aws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1766118898&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1766118598&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jti"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1640bab7-b35b-4410-b4ee-04d219abbf33"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Azure Managed Identity Trust Configuration
&lt;/h3&gt;

&lt;p&gt;To establish trust in the Azure tenant, start by creating a user assigned managed identity in a resource group with permissions to read data from the cross cloud storage account.&lt;/p&gt;

&lt;p&gt;The following Terraform configuration uses the &lt;em&gt;azurerm_user_assigned_identity&lt;/em&gt; resource to create the user assigned managed identity. Then, the &lt;em&gt;azurerm_role_assignment&lt;/em&gt; resource grants the &lt;em&gt;Storage Blob Data Reader&lt;/em&gt; role to the managed identity to read data from the cross cloud storage account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_user_assigned_identity" "cross_cloud" {
  name                = "cross-cloud-vm-${random_string.unique_id.result}"
  location            = var.location
  resource_group_name = azurerm_resource_group.federated_identity.name
}

resource "azurerm_role_assignment" "cross_cloud_blob_reader" {
  principal_id         = azurerm_user_assigned_identity.cross_cloud.principal_id
  scope                = azurerm_storage_account.cross_cloud.id
  role_definition_name = "Storage Blob Data Reader"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, configure the user assigned managed identity's federated credentials to trust the AWS outbound identity provider. The &lt;em&gt;var.aws_account_issuer&lt;/em&gt; variable is set to the AWS accounts issuer &lt;em&gt;&lt;a href="https://53e20c38-5c03-41f7-8baa-a67e81974de0.tokens.sts.global.api.aws" rel="noopener noreferrer"&gt;https://53e20c38-5c03-41f7-8baa-a67e81974de0.tokens.sts.global.api.aws&lt;/a&gt;&lt;/em&gt;. The &lt;em&gt;var.aws_iam_role_arn&lt;/em&gt; variable is set to the AWS IAM role ARN &lt;em&gt;arn:aws:iam::123456789012:role/nymeria-cross-cloud-9zo9h8c5&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_federated_identity_credential" "aws" {
  name                = "nymeria-aws"
  resource_group_name = var.azure_resource_group_name
  parent_id           = var.azure_managed_identity_id

  issuer   = var.aws_account_issuer
  audience = ["api://AzureADTokenExchange"]
  subject  = var.aws_iam_role_arn
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accessing Azure Blob Storage from AWS EC2
&lt;/h2&gt;

&lt;p&gt;With the AWS outbound identity federation and Azure managed identity trust relationship configured, the EC2 instance can now access the Azure Blob storage container. To test the access to the storage instance, start by requesting a new AWS outbound identity token and storing the value in an environment variable called &lt;em&gt;JWT&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws sts get-web-identity-token &lt;span class="nt"&gt;--audience&lt;/span&gt; api://AzureADTokenExchange &lt;span class="nt"&gt;--signing-algorithm&lt;/span&gt; RS256 &lt;span class="nt"&gt;--duration-seconds&lt;/span&gt; 300 | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.WebIdentityToken'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, set the following environment variables to your Azure tenant id, user assigned managed identity client id, and the name of the storage account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_TENANT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-azure-tenant-id"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_MANAGED_IDENTITY_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-managed-identity-client-id"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_STORAGE_ACCOUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-cross-cloud-storage-account"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, run the &lt;em&gt;az login&lt;/em&gt; command to authenticate to the Azure tenant using the AWS outbound identity token. After successfully logging in, use the &lt;em&gt;az storage blob list&lt;/em&gt; command to list the blobs in the storage container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az login &lt;span class="nt"&gt;--service-principal&lt;/span&gt; &lt;span class="nt"&gt;--tenant&lt;/span&gt; &lt;span class="nv"&gt;$AZURE_TENANT_ID&lt;/span&gt; &lt;span class="nt"&gt;--username&lt;/span&gt; &lt;span class="nv"&gt;$AZURE_MANAGED_IDENTITY_CLIENT_ID&lt;/span&gt; &lt;span class="nt"&gt;--federated-token&lt;/span&gt; &lt;span class="nv"&gt;$JWT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will confirm that you have successfully authenticated into the Azure tenant using the AWS outbound identity token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cloudName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AzureCloud"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"homeTenantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-azure-tenant-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-azure-subscription-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isDefault"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"managedByTenants"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-azure-subscription-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tenantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-azure-tenant-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-managed-identity-client-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"servicePrincipal"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, list the blobs in the Azure storage account container to confirm that access to the storage account is working as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az storage blob list &lt;span class="nt"&gt;--auth-mode&lt;/span&gt; login &lt;span class="nt"&gt;--account-name&lt;/span&gt; &lt;span class="nv"&gt;$AZURE_STORAGE_ACCOUNT&lt;/span&gt; &lt;span class="nt"&gt;--container-name&lt;/span&gt; assets | jq &lt;span class="s1"&gt;'.[].name'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AWS IAM Outbound Identity Federation provides a new way for workloads running in AWS to securely access external cloud resources using short-lived, signed OIDC tokens. With the addition of this new feature, the Puma Security &lt;a href="https://github.com/pumasecurity/nymeria" rel="noopener noreferrer"&gt;Nymeria&lt;/a&gt; cross-cloud workload identity project now supports federating from AWS EC2 instances into both Azure and Google cloud storage resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.wiz.io/blog/top-aws-re-invent-announcements-for-security-teams-in-2025" rel="noopener noreferrer"&gt;Wiz Blog: Top AWS re:Invent Announcements for Security Teams in 2025 by Scott Piper&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/aws/simplify-access-to-external-services-using-aws-iam-outbound-identity-federation/" rel="noopener noreferrer"&gt;AWS News Blog: Simplify access to external services using AWS IAM Outbound Identity Federation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_outbound_web_identity_federation" rel="noopener noreferrer"&gt;Terraform: new aws_iam_outbound_web_identity_federation resource&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/pumasecurity/nymeria" rel="noopener noreferrer"&gt;Nymeria Workshop GitHub Repository&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  About The Author
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/eric-m-johnson/" rel="noopener noreferrer"&gt;Eric Johnson's&lt;/a&gt; experience includes performing cloud security architecture and design, cloud native and Kubernetes assessments, infrastructure as code automation, application security automation, web and mobile application penetration testing, secure development lifecycle consulting, and secure code review assessments.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>identity</category>
      <category>security</category>
    </item>
    <item>
      <title>Auditing AWS EKS Pod Permissions</title>
      <dc:creator>Eric Johnson</dc:creator>
      <pubDate>Thu, 29 Feb 2024 20:50:47 +0000</pubDate>
      <link>https://dev.to/aws-builders/auditing-aws-eks-pod-permissions-4637</link>
      <guid>https://dev.to/aws-builders/auditing-aws-eks-pod-permissions-4637</guid>
      <description>&lt;p&gt;Applications running in EKS often require access to AWS resources, including S3 buckets, DynamoDB tables, Secrets Manager secrets, KMS keys, SQS queues, and other resources. As a security auditor, mapping EKS pods in a cluster to assigned IAM policy permissions can be challenging. In this post, we will review three different ways to audit EKS pod permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  EKS Node IAM Role Permissions
&lt;/h2&gt;

&lt;p&gt;EKS avoids provisioning long-lived access keys to the cluster’s nodes by using an instance profile to pass IAM role credentials to the EC2 instance. Services running on the EKS node can request the role’s temporary access keys using the &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html"&gt;EC2 Instance Metadata Service (IMDS)&lt;/a&gt;. By default, all pods running on the node can also communicate with the IMDS. This allows pods to request temporary access keys for the node's IAM role and access AWS resources.&lt;/p&gt;

&lt;p&gt;Consider the EKS node in the diagram below. The node’s &lt;em&gt;kubelet&lt;/em&gt; makes AWS API calls for the Kubernetes control plane to function, which typically requires the &lt;em&gt;AmazonEKSWorkerNodePolicy&lt;/em&gt;, &lt;em&gt;AmazonEKS_CNI_Policy&lt;/em&gt;, &lt;em&gt;AmazonEC2ContainerRegistryReadOnly&lt;/em&gt;, and &lt;em&gt;CloudWatchAgentServerPolicy&lt;/em&gt; managed policies. Knowing the &lt;em&gt;web&lt;/em&gt; pod needs access to an S3 bucket and a Secrets Manager secret, the appropriate permissions are attached to the node’s IAM Role as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8t1xb293isqcyo3zhmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8t1xb293isqcyo3zhmg.png" alt="AWS EKS Node Image Permissions" width="800" height="1241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;web&lt;/em&gt; pod's path to accessing the AWS APIs is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The development team deploys the &lt;em&gt;web&lt;/em&gt; pod in the &lt;em&gt;dm&lt;/em&gt; namespace on the EKS node.&lt;/li&gt;
&lt;li&gt;The process running in the &lt;em&gt;web&lt;/em&gt; container requests the IAM role's temporary credentials from the instance metadata service (&lt;em&gt;&lt;a href="http://169.254.169.254/latest/meta-data/iam/security-credentials/dm-eks-node-role"&gt;http://169.254.169.254/latest/meta-data/iam/security-credentials/dm-eks-node-role&lt;/a&gt;&lt;/em&gt;). The response includes a temporary AWS Access Key Id, Secret Access Key, and Session Token for authenticating to AWS APIs using the EKS node's IAM Role.&lt;/li&gt;
&lt;li&gt;The process running in the web container invokes the S3 and Secrets Manager API using the temporary credentials.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, what’s the problem? Mixing the &lt;em&gt;kubelet’s&lt;/em&gt; control plane permissions with permissions for other workloads can create privilege escalation issues. To start, the EKS node has a number of AWS managed policies, including the &lt;em&gt;AmazonEKS_CNI_Policy&lt;/em&gt; managed policy, which could allow the &lt;em&gt;web&lt;/em&gt; pod to create, delete, and attach network interfaces from the nodes in the cluster. These permissions are unlikely to be required for the &lt;em&gt;web&lt;/em&gt; pod to operate.&lt;/p&gt;

&lt;p&gt;Now, let's consider a scenario where a new pod, &lt;em&gt;evil&lt;/em&gt;, is deployed into a different namespace called &lt;em&gt;sabre&lt;/em&gt;. The next diagram shows the &lt;em&gt;evil&lt;/em&gt; pod requesting temporary credentials from the node's instance metadata service. The temporary credentials will also include the web pod's permission to the S3 and Secrets Manager!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vr1j83nc6o8z00p8039.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vr1j83nc6o8z00p8039.png" alt="AWS EKS Node Privilege Escalation" width="800" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Blocking Pod IMDS Access
&lt;/h3&gt;

&lt;p&gt;Given the privilege escalation issues granting pod permissions at the node level, ensure your EKS administrators disable pod access to the node’s IMDS endpoint. This will avoid mixing node and pod level permissions, and make it much easier for auditors to understand an individual pod’s permissions. This can be done using either of the following methods:&lt;/p&gt;

&lt;h4&gt;
  
  
  IMDSv2 Hop Limit
&lt;/h4&gt;

&lt;p&gt;Enforce IMDS version (IMDSv2) on the cluster nodes and set the response hop limit to one (1). This ensures that IMDS responses cannot be received by the pod’s network interface. The following Terraform configuration sets the appropriate IMDSv2 configuration on the node’s &lt;em&gt;aws_launch_template&lt;/em&gt; resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# IMDSv2 w/ hop limit set to 1 disables pod access to the IMDS&lt;/span&gt;
&lt;span class="nx"&gt;metadata_options&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;http_endpoint&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"enabled"&lt;/span&gt;
  &lt;span class="nx"&gt;http_tokens&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"required"&lt;/span&gt;
  &lt;span class="nx"&gt;http_put_response_hop_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;instance_metadata_tags&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"enabled"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Network Policy
&lt;/h4&gt;

&lt;p&gt;Blocking IMDS access cluster-wide could cause problems if pods running in the cluster actually need access to the IMDS. Applying a &lt;em&gt;NetworkPolicy&lt;/em&gt; to namespaces in the cluster that should not have access to the IMDS can provide a more flexible alternative. The following Calico &lt;em&gt;NetworkPolicy&lt;/em&gt; allows the &lt;em&gt;evil&lt;/em&gt; pod outbound access to networks outside the cluster, but explicitly blocks access to the IMDS endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crd.projectcalico.org/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NetworkPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;egress-all&lt;/span&gt;
 &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sabre&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Egress&lt;/span&gt;
 &lt;span class="na"&gt;egress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deny&lt;/span&gt;
     &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;nets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;169.254.169.254/32"&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
     &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;nets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0/0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IAM Roles for Service Accounts (IRSA)
&lt;/h2&gt;

&lt;p&gt;Now that the pods cannot inherit permissions from the EKS node, workloads will need an IAM Role assigned directly to the pod. In AWS EKS, the first option for granting pod-level access to the AWS APIs is called IAM Roles for Service Accounts (IRSA). IRSA uses the EKS cluster’s OpenID Connect (OIDC) identity provider to federate into an AWS identity provider and assume a role. The updated diagram below shows the new authentication flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg6u9xetigh9zsgtr5ltd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg6u9xetigh9zsgtr5ltd.png" alt="AWS EKS Node IRSA" width="800" height="1036"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;web&lt;/em&gt; pod's path to accessing the AWS APIs is now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The development team deploys a new Kubernetes ServiceAccount &lt;em&gt;web-sa&lt;/em&gt; in the &lt;em&gt;dm&lt;/em&gt; namespace. A special annotation &lt;em&gt;eks.amazonaws.com/role-arn&lt;/em&gt; is set on the service account mapping the Kubernetes service account to an IAM Role ARN.&lt;/li&gt;
&lt;li&gt;The development team adjusts the &lt;em&gt;web&lt;/em&gt; deployment to assign the new &lt;em&gt;web-sa&lt;/em&gt; service account to the &lt;em&gt;web&lt;/em&gt; pods.&lt;/li&gt;
&lt;li&gt;As EKS creates the &lt;em&gt;web&lt;/em&gt; pod, a mutating web hook automatically injects two new environment variables: &lt;em&gt;AWS_ROLE_ARN&lt;/em&gt; and &lt;em&gt;AWS_WEB_IDENTITY_TOKEN_FILE&lt;/em&gt;. The process running in the web container uses the new environment variables to call the &lt;em&gt;sts:AssumeRoleWithWebIdentity&lt;/em&gt; API using the service account identity token (JWT) and obtain temporary access keys.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Auditing IRSA Pod Permissions
&lt;/h3&gt;

&lt;p&gt;All EKS service accounts using IRSA will contain the &lt;em&gt;eks.amazonaws.com/role-arn&lt;/em&gt; annotation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;eks.amazonaws.com/role-arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:iam::123456789012:role/dm-web-eks-pod-role"&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web-sa"&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dm"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Auditing IAM policies assigned to pods using IRSA can be done by enumerating all of the EKS cluster’s service accounts with the &lt;em&gt;eks.amazonaws.com/role-arn&lt;/em&gt; annotation, parsing the IAM Role ARN, and then enumerating the IAM Role’s policy attachments. The &lt;em&gt;&lt;a href="https://gist.github.com/ejohn20/928c6de2fa7659062c8299f49c6b6884"&gt;aws-eks-audit-irsa-pods&lt;/a&gt;&lt;/em&gt; script uses the &lt;em&gt;kubectl&lt;/em&gt; and &lt;em&gt;aws&lt;/em&gt; command line interfaces to discover IRSA pod permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; sa_metadata&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;service_account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .name &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sa_metadata&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .namespace &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sa_metadata&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;role_arn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .rolearn &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sa_metadata&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;role_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.rolearn | split("/") | .[1]'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sa_metadata&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Service Account: system:serviceaccount:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;service_account&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Role ARN: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;role_arn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Policy Attachments:"&lt;/span&gt;
  aws iam list-attached-role-policies &lt;span class="nt"&gt;--role-name&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;role_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .&lt;span class="s1"&gt;'AttachedPolicies[].PolicyArn'&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;kubectl get serviceaccounts &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | 
  jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'.items[] | select(.metadata.annotations."eks.amazonaws.com/role-arn" != null) | 
  {name: .metadata.name, namespace: .metadata.namespace, rolearn: .metadata.annotations."eks.amazonaws.com/role-arn"}'&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output shows the Kubernetes service account, IAM Role ARN, and the IAM policy attachments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Service Account: system:serviceaccount:dm:web-sa
Role ARN: arn:aws:iam::123456789012:role/dm-web-eks-pod-role
Policy Attachments:
arn:aws:iam::123456789012:policy/dm-web-eks-pod-policy

Service Account: system:serviceaccount:kube-system:aws-load-balancer-controller
Role ARN: arn:aws:iam::123456789012:role/dm-eks-alb-controller-role
Policy Attachments:
arn:aws:iam::123456789012:policy/dm-eks-alb-controller-policy

Service Account: system:serviceaccount:kube-system:ebs-csi-controller-sa
Role ARN: arn:aws:iam::123456789012:role/dm-eks-ebs-csi-role
Policy Attachments:
arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  EKS Pod Identity
&lt;/h2&gt;

&lt;p&gt;Launched in late 2023, EKS Pod Identity offers an additional option for assigning permissions to pods. EKS Pod Identity is specific to EKS, and does not translate into other managed Kubernetes offerings (e.g., AKS, GKE). After installing the EKS Pod Identity Agent cluster add on, IAM Role to Kubernetes service &lt;em&gt;associations&lt;/em&gt; are defined on the EKS cluster instead of the Kubernetes service account resource. For more details, see Christophe Tafani-Dereeper’s post titled &lt;a href="https://securitylabs.datadoghq.com/articles/eks-pod-identity-deep-dive/"&gt;Deep dive into the new Amazon EKS Pod Identity feature&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following Terraform configuration sets an EKS pod identity association using the &lt;em&gt;aws_eks_pod_identity_association&lt;/em&gt; resource. The resource creates an association between the &lt;em&gt;sabre&lt;/em&gt; namespace’s &lt;em&gt;web-sa&lt;/em&gt; service account and the &lt;em&gt;dm_web_eks_pod&lt;/em&gt; IAM Role. As the pod is being created, the EKS Pod Identity mutating webhook will automatically inject the &lt;em&gt;AWS_CONTAINER_CREDENTIALS_FULL_URI&lt;/em&gt; and &lt;em&gt;AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE&lt;/em&gt; environment variables. Processes running in the container can use the AWS_CONTAINER_CREDENTIALS_FULL_URI, which will have a value similar to &lt;em&gt;&lt;a href="http://169.254.170.23/v1/credentials"&gt;http://169.254.170.23/v1/credentials&lt;/a&gt;&lt;/em&gt;, to request temporary credentials for the IAM Role. Invoking the endpoint requires the &lt;em&gt;Authorization&lt;/em&gt; header to have the identity token found in the &lt;em&gt;AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE&lt;/em&gt; volume mount (/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_eks_pod_identity_association"&lt;/span&gt; &lt;span class="s2"&gt;"dm_web_pod_identity"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sabre"&lt;/span&gt;
  &lt;span class="nx"&gt;service_account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-sa"&lt;/span&gt;
  &lt;span class="nx"&gt;role_arn&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dm_web_eks_pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auditing Pod Identity Permissions
&lt;/h3&gt;

&lt;p&gt;Auditing IAM policies assigned using EKS Pod Identity can be done by enumerating all of the EKS cluster’s associations, finding the IAM Role, and then enumerating the IAM Role’s policy attachments. The &lt;em&gt;&lt;a href="https://gist.github.com/ejohn20/212ef7ea95d6dd92bda8b77e8b3b3881"&gt;aws-eks-audit-pod-identity-pods&lt;/a&gt;&lt;/em&gt; script uses only the &lt;em&gt;aws&lt;/em&gt; command line interfaces to discover pod permissions. The &lt;em&gt;kubectl&lt;/em&gt; command line interface is not needed because the associations live at the cluster level, not at the Kubernetes service account level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;CLUSTER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; pod_identity_assn&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;association_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .associationId &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pod_identity_assn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;service_account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .serviceAccount &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pod_identity_assn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .namespace &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pod_identity_assn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;association&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws eks describe-pod-identity-association &lt;span class="nt"&gt;--cluster&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLUSTER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--association-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;association_id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;role_arn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.association.roleArn'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;association&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;role_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.association.roleArn | split("/") | .[1]'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;association&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Kubernetes Service Account: system:serviceaccount:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;service_account&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Role ARN: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;role_arn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Policy Attachments:"&lt;/span&gt;
  aws iam list-attached-role-policies &lt;span class="nt"&gt;--role-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;role_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .&lt;span class="s1"&gt;'AttachedPolicies[].PolicyArn'&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;aws eks list-pod-identity-associations &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLUSTER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'.associations[]'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output shows the Kubernetes service account, IAM Role ARN, and the IAM policy attachments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Kubernetes Service Account: system:serviceaccount:sabre:web-sa
Role ARN: arn:aws:iam::192792859965:role/dm-web-eks-pod-identity-role
Policy Attachments:
arn:aws:iam::192792859965:policy/dm-web-eks-pod-policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Auditing permissions assigned directly to EKS pods can be challenging and confusing with multiple options for creating associations between IAM Roles and Kubernetes service accounts. The audit scripts above are two helper scripts that live in my local bin directory for easily enumerating Kubernetes service accounts with access to the AWS APIs. &lt;/p&gt;

&lt;p&gt;Datadog also maintains the &lt;a href="https://github.com/DataDog/managed-kubernetes-auditing-toolkit"&gt;Managed Kubernetes Auditing Toolkit&lt;/a&gt; (MKAT), which can be installed to perform similar permission checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/create-node-role.html"&gt;EKS Node IAM Role&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/whitepapers/latest/security-practices-multi-tenant-saas-applications-eks/restrict-the-use-of-host-networking-and-block-access-to-instance-metadata-service.html"&gt;EKS Restrict Use of IMDS&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html"&gt;IAM Roles for Service Accounts&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html"&gt;EKS Pod Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/ejohn20"&gt;EKS Auditing Scripts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/DataDog/managed-kubernetes-auditing-toolkit"&gt;Datadog Managed Kubernetes Auditing Toolkit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  About the Author
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/eric-m-johnson/"&gt;Eric Johnson&lt;/a&gt; is a Co-founder and Principal Security Engineer at Puma Security and a Senior Instructor with the SANS Institute. His experience includes cloud security assessments, cloud infrastructure automation, cloud architecture, static source code analysis, web and mobile application penetration testing, secure development lifecycle consulting, and secure code review assessments. Eric is the lead author and an instructor for &lt;a href="//sans.org/sec540"&gt;SEC540: Cloud Security and DevSecOps Automation&lt;/a&gt; and a co-author and instructor for both &lt;a href="//sans.org/sec549"&gt;SEC549: Cloud Security Architecture&lt;/a&gt;, and &lt;a href="//sans.org/sec510"&gt;SEC510: Cloud Security Controls and Mitigations&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>identity</category>
      <category>aws</category>
      <category>eks</category>
      <category>security</category>
    </item>
  </channel>
</rss>
