Showing posts with label OIDC. Show all posts
Showing posts with label OIDC. Show all posts

Friday, 27 June 2025

GitHub Workflows and AWS




GitHub workflow can communicate with our AWS resources, directly (via AWS CLI commands) or indirectly (via e.g. Terraform AWS provider).

Before running AWS CLI commands, deploying AWS infrastructure with Terraform, or interacting with AWS services in any way we need to include a step which configures AWS credentials. It ensures that the workflow runner is authenticated with AWS and knows which region to target.

This step should contain configure-aws-credentials action provided by AWS. This action sets up the necessary environment variables so that AWS CLI commands and SDKs can authenticate with AWS services.

aws-region input sets the default AWS region to us-east-2 (Ohio). All AWS commands run in later steps will use this region unless overridden.

We can use either IAM user or OIDC (temp token) authentication.

IAM User Authentication


If using IAM user authentication, we can store user's credentials in a dedicated GitHub secrets:

env:
    AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    AWS_REGION: us-east-2

// Define this step before steps which are accessing AWS:

- name: Configure AWS Credentials
     uses: aws-actions/configure-aws-credentials@v2
     with:
        aws-region: ${{ env.AWS_REGION }}

 OpenID Connect (OIDC) Authentication


In this authentication, configure-aws-credentials GitHub Action uses GitHub's OpenID Connect (OIDC) for secure authentication with AWS. It leverages the OIDC token provided by GitHub to request temporary AWS credentials from AWS STS, eliminating the need to store long-lived AWS access keys in GitHub Secrets. 

Note that we now need to grant the workflow run a permissions for write access to the id-token:
id-token: write allows the workflow to request and use OpenID Connect (OIDC) tokens. The write level is required for actions that need to generate or use OIDC tokens to authenticate with external systems. Granting id-token: write is essential for workflows that use OIDC-based authentication, such as securely assuming AWS IAM roles via GitHub Actions. This enables secure, short-lived authentication to AWS and other cloud providers. This permission is a security best practice for modern CI/CD workflows that use OIDC to authenticate with cloud providers, reducing the need for static secrets.


env:
    AWS_REGION: us-east-2

permissions:
  id-token: write # aws-actions/configure-aws-credentials (OIDC)

...
- name: Configure AWS Credentials
    uses: aws-actions/configure-aws-credentials@v4
    with:
        role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
        role-session-name: my-app
        aws-region:  ${{ env.AWS_REGION }}



Here's how it works: 
  1. GitHub OIDC Provider: GitHub acts as an OIDC provider, issuing signed JWTs (JSON Web Tokens) to workflows that request them.
  2. configure-aws-credentials Action: This action, when invoked in a GitHub Actions workflow, receives the JWT from the OIDC provider.
  3. AWS STS Request: The action then uses the JWT to request temporary security credentials from AWS Security Token Service (STS).
  4. Credential Injection: AWS STS returns temporary credentials (access key ID, secret access key, and session token) which the action injects as environment variables into the workflow's execution environment.
  5. AWS SDKs and CLI: AWS SDKs and the AWS CLI automatically detect and use these environment variables for authenticating with AWS services.

Benefits of using OIDC with configure-aws-credentials:
  • Enhanced Security: Eliminates the need to store long-lived AWS access keys, reducing the risk of compromise.
  • Simplified Credential Management: Automatic retrieval and injection of temporary credentials, simplifying workflow setup and maintenance.
  • Improved Auditing: Provides better traceability of actions performed within AWS, as the identity is linked to the GitHub user or organization. 

Before using the action:
  • Configure an OpenID Connect provider in AWS: We need to establish an OIDC trust relationship between GitHub and our AWS account.
  • Create an IAM role in AWS: Define the permissions for the role that the configure-aws-credentials action will assume.
  • Set up the GitHub workflow: Configure the configure-aws-credentials action with the appropriate parameters, such as the AWS region and the IAM role to assume. 

In an OpenID Connect (OIDC) authentication scenario, the aws-actions/configure-aws-credentials action creates the following environment variables when assuming a role with temporary credentials: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN. These variables are used by the AWS SDK and CLI to interact with AWS resources. 

Here's a breakdown:
  • AWS_ACCESS_KEY_ID: This environment variable stores the access key ID of the temporary credentials. 
  • AWS_SECRET_ACCESS_KEY: This environment variable stores the secret access key of the temporary credentials. 
  • AWS_SESSION_TOKEN: This environment variable stores the session token associated with the temporary credentials, which is required for operations with AWS Security Token Service (STS). 

These environment variables are populated by the action after successful authentication with the OIDC provider and assuming the specified IAM role. The action retrieves the temporary credentials from AWS and makes them available to subsequent steps in the workflow. 


Once AWS authentication is done and this env variables are created, the next steps in the workflow can access our AWS resources, e.g. read secrets from AWS Secrets Manager:

- name: Read secrets from AWS Secrets Manager into environment variables
    uses: aws-actions/aws-secretsmanager-get-secrets@v2
    with:
        secret-ids: |
            my-secret
        parse-json-secrets: true

- name: deploy
    run: |
        echo $AWS_ACCESS_KEY_ID
        echo $AWS_SECRET_ACCESS_KEY
    env:
        MY_KEY: ${{ env.MY_SECRET_MY_KEY }}

This example assumes that in AWS secret my-secret we have a key MY_KEY, set to the secret value we want to fetch and use.

Saturday, 8 June 2024

Access types in Amazon EKS

 



Types of access in EKS


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.  


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)
    • Old way
    • 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
    • Modern approach

IAM roles for service accounts (IRSA)


What are Service Accounts?


An AWS EKS cluster service account is a Kubernetes identity assigned to pods, allowing them to authenticate with the (cluster) API server. EKS enhances this by enabling service accounts to assume IAM roles (IRSA - IAM Roles for Service Accounts), providing fine-grained, secure AWS permission access to containers without managing long-lived credentials. 

Key Aspects of EKS Service Accounts:
  • Identity & Security: Acts as a non-human identity within the cluster for applications to interact with Kubernetes APIs or external AWS services.
  • IRSA (IAM Roles for Service Accounts): EKS creates an OIDC provider, allowing Kubernetes service accounts to map directly to AWS IAM roles. Pods use this association to get temporary credentials for services like S3, DynamoDB, etc..
  • EKS Pod Identity: A newer, simpler alternative to IRSA that uses an agent to manage credentials directly, bypassing the need for OIDC configuration.
  • Management: Defined within Kubernetes manifests (ServiceAccount resource) and linked to IAM policies via OIDC provider setup or EKS Pod Identity Associations. 

Why Use EKS Service Accounts?


Instead of assigning IAM roles to worker nodes (which gives all pods the same privileges), service accounts follow the principle of least privilege, giving specific permissions only to the pods that require them.


In brief, how IRSA authentication works?


When an AWS SDK inside a pod needs to access a resource (like an S3 bucket for Loki), it doesn't just send a simple "I am Loki" message. It performs a complex cryptographic handshake called OIDC Federation.

Here is exactly what is sent, what is inside the token, and how STS "connects the dots."

1. What the SDK sends to STS

The SDK calls the AssumeRoleWithWebIdentity API. It sends three primary things:
  • RoleArn: The ARN of the IAM Role it wants to assume (e.g., your geeiq-prod-monitoring-k8s-grafana-loki role).
  • RoleSessionName: A unique name for the session (usually the pod name).
  • WebIdentityToken: This is the most important part—the actual "Identity Card" (JWT) mounted at /var/run/secrets/eks.amazonaws.com/serviceaccount/token.

2. What is inside that Token?

The token is a JSON Web Token (JWT) signed by your EKS cluster's private key. If you were to decode it, you would see a payload like this:


{
  "iss": "https://oidc.eks.us-east-2.amazonaws.com/id/570323FD881F43322B5CD6D7693E76DB",
  "sub": "system:serviceaccount:grafana-loki:grafana-loki",
  "aud": "sts.amazonaws.com",
  "exp": 1740000000,
  "iat": 1739996400,
  "kubernetes.io/namespace": "grafana-loki",
  "kubernetes.io/serviceaccount/name": "grafana-loki",
  "kubernetes.io/pod/name": "loki-write-0"
}

  • iss (Issuer): The URL of your EKS OIDC server. This tells STS exactly who issued the token.
  • sub (Subject): The unique identity of the pod (Namespace + ServiceAccount name).
  • aud (Audience): This specifies who the token is intended for. This is why your previous IAM fix was so important. If the token says the audience is sts.amazonaws.com, but your IAM policy doesn't require it, the handshake fails.

3. How does STS know which IAM OIDC Provider to talk to?

This is where the "match" we looked at earlier happens:

  1. Extracting the Issuer: STS looks at the iss field in the incoming token.
  2. Lookup: STS searches your AWS Account's IAM Identity Providers for a provider that matches that iss URL character-for-character.
  3. Signature Verification: Once STS finds the matching IAM Provider, it uses the Thumbprint (the SSL fingerprint) to go to the EKS OIDC URL and download the Public Keys.
  4. Cryptographic Proof: STS uses those public keys to verify that the token was indeed signed by your specific EKS cluster's private key. If the signature is valid, STS knows the token hasn't been tampered with.
  5. Policy Validation: Finally, STS checks the Trust Relationship on the role you requested. It ensures that the sub and aud in the token match the StringEquals conditions in your role's JSON.

AWS SDK in pods needs to be authenticated against STS so AWS SDK on pods can access AWS resources.

Even though you create the Identity Provider resource in the IAM console, the EKS cluster is the one doing all the actual "work" of identifying the pods.

Think of it like a Passport Office and a Border Agent:
  • EKS (The Passport Office): The cluster holds the private key. It issues "passports" (JWT tokens) to pods. EKS is source of truth.
  • IAM (The Border Agent): IAM doesn't issue the passports, but it has a record (the Identity Provider resource) of what a "valid" passport from your specific cluster should look like.

1. How EKS becomes the "Issuer"

Every EKS cluster runs a tiny, public-facing web server called the OIDC Discovery Endpoint.
  • The URL: This is that https://oidc.eks... URL we discussed.
  • The Keys: If you visit that URL (specifically the /.well-known/openid-configuration path), EKS provides a list of public keys.
  • The Signing: When a pod starts, EKS creates a token and signs it using its private key. Only your cluster has this private key.

2. Why you create the resource in IAM

IAM is globally shared across your AWS account. It doesn't know your EKS cluster exists until you tell it. By creating the Identity Provider in IAM, you are essentially saying:

"Hey IAM, if someone shows up with a token signed by the keys found at this EKS URL, I want you to consider that a valid identity."

3. The Step-by-Step Identity Exchange

This is exactly how your application pod is supposed to get its e.g.  S3 permissions:

  1. The Token: EKS mounts a projected volume into the App pod containing a signed token (the "passport").
  2. The Request: App sends this token to AWS STS (Security Token Service) and says, "I want to assume the role mycorp-role"
  3. The Validation: STS looks at the iss (issuer) field in the token. It sees your EKS URL.
  4. The Trust Check: STS looks at IAM Identity Providers. It finds the one you created that matches that URL.
  5. The Signature Check: STS fetches the public key from the EKS URL and verifies that the token was actually signed by your cluster.
  6. The Condition Check: STS checks the Trust Relationship on the role to ensure the "Subject" matches system:serviceaccount:my-app:my-app.



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)  Every EKS cluster which supports IRSA has a unique URL - The OIDC Issuer URL.

Example: https://oidc.eks.us-east-2.amazonaws.com/id/EXAMPLEDATA123
  • If our cluster supports IAM roles for service accounts, it has an OpenID Connect (OIDC) issuer URL associated with it. 
  • We can view this URL in the Amazon EKS console, or we 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


2) A unique OpenID Connect provider (OIDC Provider, Identity Provider) needs to be created in IAM for each EKS cluster. [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. The Issuer URL generated by EKS cluster must be identical to the Provider URL defined in the IAM Identity Provider resource.

AWS uses this match to verify that the security token presented by pod actually came from a trusted source.

1. The "Exact String" Match

AWS IAM is extremely strict about the URL. If your EKS cluster issuer is:
https://oidc.eks.us-east-2.amazonaws.com/id/EXAMPLEDATA123

The Identity Provider in IAM must be configured with that exact string. If there is a trailing slash in one but not the other, or if it uses http instead of https, the match fails, and you get the WebIdentityErr you saw in your logs.


2. The "Thumbprint" (The Security Guard)

Matching the URL is only half the battle. AWS also checks a Thumbprint (a SHA1 hex string).

This is the fingerprint of the SSL certificate used by that URL.

If the certificate at the URL changes (which happens periodically when AWS updates its services), the thumbprint in your IAM Identity Provider must be updated to match.

If our my-app pods are failing with a WebIdentityErr, it's highly likely our cluster was updated/recreated, and the IAM Identity Provider is still holding an old URL or an old Thumbprint.


How to Verify the Match


You can check for a match yourself using the AWS CLI:

Step A: Get the Cluster's URL


aws eks describe-cluster --name <cluster_name> --query "cluster.identity.oidc.issuer" --output text

Step B: List IAM Providers


aws iam list-open-id-connect-providers

Step C: Compare

Pick the ARN from Step B that looks relevant and describe it:

aws iam get-open-id-connect-provider --open-id-connect-provider-arn <ARN_FROM_STEP_B>

Look for the Url field. If it does not match Step A character-for-character, your identity chain is broken.



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 (in IAM).


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.

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. 


Friday, 7 June 2024

OpenID Connect (OIDC)


OpenID Connect (OIDC)

  • Authentication protocol
    • Authentication is a secure process of establishing and communicating that the person operating an application or browser is who they claim to be.
  • Provides a secure and verifiable answer to the question “What is the identity of the person currently using the browser or mobile app that is connected?”
  • Based on the OAuth 2.0
    • OAuth 2.0, is a framework, specified by the IETF in RFCs 6749 and 6750 (published in 2012) designed to support the development of authentication and authorization protocols. It provides a variety of standardized message flows based on JSON and HTTP; OpenID Connect uses these to provide Identity services.
  • Simplifies:
    • user identity verification
      • based on the authentication performed by an Authorization Server
    • obtaining user profile information 
      • in an interoperable and REST-like manner
  • Specification extendable to support optional features like:
    • encryption of identity data
    • discovery of OpenID Providers
    • session logout
  • Benefits for developers:
    • Easy, reliable, secure
    • Removes the responsibility of setting, storing, and managing passwords - they are stored with OpenID providers
    • There are already system-level APIs built into the Android operating system to provide OIDC services
    • OIDC can also accessed by interacting with the built-in system browser on mobile and desktop platforms; a variety of libraries are under construction to simplify this process.
    • OIDC uses standard JSON Web Token (JWT) data structures when signatures are required. This makes OpenID Connect dramatically easier to implement, and in practice has resulted in much better interoperability.

Entities in the oidc system

  • OpenID Provider (OP)
    • Entity that has implemented the OpenID Connect and OAuth 2.0 protocols
    • Sometimes can be referred to by the role it plays, such as:
      • Identity provider (IDP, IdP) - IdentityServer
      • Security token service
      • Authorization server
    • Leading IdPs are currently large cloud services providers, such as Auth0, GitHub, GitLab, Google and Microsoft
  • Identity Token
    • The outcome of an authentication process
    • After successful authentication, OP returns it to the Client
    • It can contain additional identity data but at a bare minimum it contains the following claims:
      • iss -Issuer Identifier for the Issuer of the response. The iss value is a case-sensitive URL using the https scheme that contains scheme, host, and optionally, port number and path components and no query or fragment components.
      • sub - Subject Identifier. Identifier for the user at the issuer. A locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client, e.g., 24400320 or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4.
      • aud - Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. It MAY also contain identifiers for other audiences. In the general case, the aud value is an array of case-sensitive strings. In the common special case when there is one audience, the aud value MAY be a single case-sensitive string.
      • exp - Expiration time on or after which the ID Token MUST NOT be accepted by the RP when performing authentication with the OP. The processing of this parameter requires that the current date/time MUST be before the expiration date/time listed in the value. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value is a JSON [RFC8259] number representing the number of seconds from 1970-01-01T00:00:00Z as measured in UTC until the date/time.
      • iat - Time at which the JWT was issued. Its value is a JSON number representing the number of seconds from 1970-01-01T00:00:00Z as measured in UTC until the date/time
      • The full list of claims returned within the token: https://openid.net/specs/openid-connect-core-1_0.html#IDToken
  • Access Token
    • After successful authentication, OP usually returns it to the Client
  • User
    • person that is using a registered client to access resources
  • Client
    • also known as audiences
    • software that requests tokens for:
      • authenticating a user
      • accessing a resource (also often called a relying party or RP)
    • must be registered with the OP
      • ClientID is used to identify a client app to IdP servers e.g. for Google OAuth this is in form 1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com
      • 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.)
    • can be web, mobile, desktop application
  • Relying Party (RP)
    • resource that user wants to access
    • an application or website that outsources its user authentication function to an IDP
    • application (software) that requires end-user authentication or wants to get access to the user's account. 
    • It needs to get permission from the user before it can get access to the user's account 
      • OpenID Connect identifies a set of personal attributes that can be exchanged between Identity Providers and the apps that use them and includes an approval step (aka authorization) so that users can consent (or deny) the sharing of this information.
    • OIDC Relying Party is also called just a 'client' in OAuth terminology.  (source: OIDC Relying Party)

Approval step (scope authorization) dialog looks like this:

Source: Uploading to Dropbox from Google Drive - Stack Overflow


Source: OpenID Connect  |  Authentication  |  Google for Developers


 OpenID Connect protocol steps

  • User navigates to a website or web application (RP) via a browser
  • User clicks sign-in and types their username and password
  • RP (Client) sends a (authorisation) request to the OpenID Provider (OP)
  • OP authenticates the User and obtains authorization
  • OP responds with an Identity Token and usually an Access Token
  • RP can send a request with the Access Token to the User device
  • UserInfo Endpoint returns Claims about the User

source: OpenID Connect 1.0 - Orange Developer



Source: OpenID Connect Overview: OIDC Flow | OneLogin Developers



Here is a more detailed flow diagram:

Source: OpenID Connect (OIDC) | Cloud Sundial





Source: Plan a single sign-on deployment - Microsoft Entra ID | Microsoft Learn



Resources:


Wednesday, 11 May 2022

AWS Identity and Access Management (IAM)



AWS Identity and Access Management (IAM) is a web service that we use to:
  • securely control access to AWS resources
  • centrally manage permissions that control which AWS resources users can access
  • control who is authenticated (signed in) and authorized (has permissions) to use resources

We can already identify several entities here: users, resources, permissions. If we go to AWS Console and open an IAM Dashboard page we can see the full list of all IAM entities:



Let's explain each of them and also their relations.


Users

  • Root user - account owner that performs tasks requiring unrestricted access
    • created when you sign up for AWS for the first time
    • accessed by signing in with the email address and password that were used to create the account
    • has complete admin privileges
    • can be used to manage any service within AWS but this is not recommended; this user is like root user on Unix systems or admin on Windows and should be used only for special tasks
    • used to log in to AWS Management console where it can create other users (IAM users); this is actually recommended use of root user
  • (Regular) IAM user - user within an account that performs daily tasks 
    • an entity that we/root create in AWS to represent the person or application that uses it to interact with AWS in an account
    • consists of:
      • name
      • long-term credentials
    • 2 types of access can be configured for it:
      • Access to the AWS Management Console
        • requires username and password
      • Programmatic access used to interact programmatically in Terminal on Unix and PowerShell in Windows
        • requires Access Key ID and Secret Access Key
        • can't be used to log in to AWS Management Console

When user is created, AWS assigns to it the least privilege permissions.


Policies


Permissions define what user can and can't do and they are assigned to users and user groups. They are not attached to users directly but via IAM policies.

IAM policies:
  • define AWS permissions
  • get attached to:
    • users
    • user groups
    • roles
  • can be:
    • AWS-managed
    • custom (customer-managed)
Policy Example: AdministratorAccess policy allows admin access to all resources and services. It is managed by AWS.

IAM policies are defined in JSON format: IAM JSON policy elements reference - AWS Identity and Access Management. IAM policies can be created and managed in visual editor and using JSON.

AdministratorAccess policy:

{
    "Version": "2022-05-11",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

Asterisk (*) means "all". Allow all actions on all resources.

There are many AWS-managed policies. Each of them is meant to be attached to a user with specific job role. Examples:
  • AdministratorAccess - administrator
  • Billing - view billing information, setup and manage payments
  • DatabaseAdministrator - DB admin
  • NetworkAdministrator - network admin
  • ViewOnlyAccess - view-only user

User Groups



If multiple users need to have same permissions, they need to have attached multiple policies e.g. AmazonEC2FullAccess and AmazonS3FullAccess

Instead of attaching policies to each user, we can create IAM User Group, make these users its members and then attach these policies to this group. For example, we can create a user group named Admins and give that group administrative permissions. Any user in that group automatically has the permissions that are assigned to the group.

User group:
  • is a collection of IAM users
  • simplifies permissions management by allowing managing policies (grant, change, and remove permissions) of multiple users at once
Using groups is a best-practice way to manage users' permissions by job functions,  AWS service access or our custom permissions. We can still attach policies to individual users.


Roles


How to manage permissions for services? E.g. what if EC2 instance needs to access S3 bucket. By default, just like users, resources don't have permissions to access other resources. Unlike users, we can't attach IAM policies to resources. We need to create IAM roles

IAM roles define access permissions for a resource. We need to create e.g. S3Access role and attach to it IAM policy AmazonS3FullAccess that we used for the user group. Then we attach this role to EC2 instance of interest.

IAM roles are a secure way to grant permissions to entities that we trust:
  • IAM users in another account
  • Application code running on an EC2 instance that needs to perform actions on AWS resources
  • An AWS service that needs to act on resources in your account to provide its features
  • Users from a corporate directory who use identity federation with SAML

IAM roles are used to provide access:
  • from one AWS service to another
  • to IAM user belonging to another AWS account
  • to applications to interact with services in AWS
  • to users managed outside AWS e.g. by Active Directory
Example of custom-made policy: the one which allows user to create and delete tags on EC2 instance:

CreateEC2TagsPolicy:

{
    "Version": "2022-05-11",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteTags",
                "ec2:CreateTags"
            ],
            "Resource": "*"
        }
    ]
}

Example: CodeDeploy service Role


If we want to create a role for CodeDeploy so it can deploy new versions of the software on EC2 instances controlled by ASG we can name it as e.g. CodeDeployRole and then define for it:

Trust relationships, defined via Trust Policy, which tells which entities (Principals) can assume this role, under which conditions.

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

We'll then attach IAM Policy which defines which actions are allowed/not allowed on which resources. Example:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "autoscaling:CompleteLifecycleAction",
                "autoscaling:DeleteLifecycleHook",
                "autoscaling:DescribeAutoScalingGroups",
                "autoscaling:DescribeLifecycleHooks",
                "autoscaling:PutLifecycleHook",
                "autoscaling:RecordLifecycleActionHeartbeat",
                "autoscaling:TerminateInstanceInAutoScalingGroup",
                "codedeploy:*",
                "ec2:Describe*",
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": "*"
        }
    ]
}

Example: Role for EC2 instances


This role allows the EC2 instances to communicate with the CodeDeploy service.

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

We need to attach this policy to that role:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "codedeploy:*",
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": "*"
        }
    ]
}




IAM entities Identifiers


When we create IAM entities we assign to them friendly names. Furthermore, if we create these IAM resources via IAM API or AWS CLI, we can assign to them paths. 

Each resource gets assign an identifier in Amazon Resource Name (ARN) format: 

arn:partition:service:region:account:resource

Examples:

arn:aws:iam::123456789012:root
arn:aws:iam::123456789012:user/JohnSmith
arn:aws:iam::123456789012:user/engineering/devops/BojanKomazec

In the last example /engineering/devops/ is the path that was specified during user creation.



Creating a User and User Group in AWS Management Console


Log in to AWS Management Console with your root account.



Security, Identity and Compliance >> IAM
IAM service does not depend on the region so region is set to Global. Users, groups and roles are available in all regions.

Account Management >> Users >> Add User
We need to provide: 
  • User name
  • Desired Access type (both can be selected):
    • Programmatic access - enables access key ID and secret access key for the AWS API, CLI, SDK and other development tools
    • AWS Management Console access - enables a password that allows users to sign-in to the AWS Management Console
      • Require password reset - enable so user must create a new password at next sign-in. Users automatically get the IAMUserChangePassword policy to allow them to change their own password.
Next >> Permissions

Set permissions has 3 tabs:
  • Add user to group
  • Copy permissions from existing user
  • Attach existing policies directly
Next >> Tags

IAM Tags are key-value pairs you can add to your user. Tags can include user information such as an e-mail address, or can be descriptive, such as a job title. 

Next >> Review >> Create User

Add user - Success page: this is the only place and time when we can see/access/download the secret access key. We can download it within .csv file. 

Account Management >> Users: select user => Summary page
Summary page tabs:
  • Permissions
    • Add permissions (button); this can be done in 3 ways:
      • Add user to group
        • Create Group (button); shows list of all policies; we need to check those that we want to be attached to this group and then press button "Create group"
      • Copy permissions from existing user
      • Attach existing policies directly
        • check the desired policy 
    • Policies list:
      • for each policy we can see policy summary and JSON
  • Groups
  • Tags
  • Security Credentials
  • Access Advisor

Access Management >> Groups, select group => Summary page

Summary page has 3 tabs:
  • Users
  • Permissions
    • Attach Policy (button)
  • Access Advisor

Creating a Policy in AWS Management Console


Access Management >> Policies
Page shows:
  • Create Policy button - for creating a custom policy
    • Create Policy page has 2 tabs:
      • Visual Editor
        • We need to choose the service that the policy will be applicable for e.g. EC2
        • We then need to choose actions allowed in chosen service
          • Access level:
            • List
            • Read
            • Tagging
            • Write
            • Permissions management
        • We then need to choose resources this policy can be applied on
      • JSON
    • Review: 
      • set the name of the policy
      • set the description of the policy
  • list of all existing policies

Creating a Role in AWS Management Console


Access Management >> Roles; main page contains:
  • Create role (button)
    • Select type of trusted entity:
      • AWS Service (EC2, Lambda, ...) - allows AWS service to perform actions on our behalf e.g. EC2 or Lambda to call AWS services on our behalf
      • Another AWS account
      • Web identity (Cognito or OpenID provider)
      • SAML federation (corporate directory)
    • Add tags
    • Review
  • list of all roles

Programmatic Access


AWS CLI is an open source tool that allows interacting with AWS services using command line tools like Unix shell or Terminal and PowerShell in Windows. 

Installing AWS CLI on Mac



To verify installation:

% which aws
/usr/local/bin/aws

% aws --version
aws-cli/2.6.3 Python/3.9.11 Darwin/20.5.0 exe/x86_64 prompt/off


Configuring AWS CLI


% aws configure
AWS Access Key ID [None]: 
AWS Secret Access Key [None]: 
Default region name [None]: 
Default output format [None]: 

AWS Access Key ID and Secret Key are those that are downloaded when user was created. ID can be visible after that but secret key can be found only in downloaded file, it can be unveiled in AWS Management Console. 

For default region name we might want to put geographically closest region to our location. 

Default output format can be yaml, JSON, text or table.
 
$ ls  /home/bojan/.aws
config  credentials
 
$ cat  /home/bojan/.aws/config
[default]
region = eu-west-1
output = json

$ cat  /home/bojan/.aws/credentials
[default]
aws_access_key_id = ABC...DEF
aws_secret_access_key = DFCdfsg...sdceD

 
To check what region has been set:
 
$ aws configure get region 
eu-west-1
 
To check what Access Key ID and Secret Key have been set:
 
$ aws configure get aws_access_key_id

$ aws configure get aws_secret_access_key
 
 
How to maintain multiple AWS accounts on the same machine?
 
We need to use profiles. As we can see in the config files above, default is the default profile. To add a profile named e.g. profile2:

$ cat  /home/bojan/.aws/credentials
[default]
aws_access_key_id = ABC...DEF
aws_secret_access_key = DFCdfsg...sdceD

[profile2]
aws_access_key_id = xxx...xxx
aws_secret_access_key =xxx...xxx

In order to use profile2 we need to use --profile switch to specify it
 
$ aws [command] [sub-command] --profile profile2

To avoid repeating this, we can set AWS_PROFILE environment variable to desired profile:
 
$ export AWS_PROFILE=profile2
 

Using AWS CLI


AWS CLI command syntax:

$ aws
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:

  aws help
  aws <command> help
  aws <command> <subcommand> help



<command> is usually a service we want to interact with e.g. iam
<subcommand> specifies which operations to perform e.g. create-user
 

Example: creating a user


$ aws iam create-user --user-name test-user-1
{
    "User": {
        "Path": "/",
        "UserName": "test-user-1",
        "UserId": "AIDBBQ3OFFXCBT3AQWDK7",
        "Arn": "arn:aws:iam::136201378220:user/test-user-1",
        "CreateDate": "2022-05-12T11:55:25Z"
    }
}

Arn = Amazon Resource Name, a unique name assign to every resource in AWS

To see help for each command or subcommand:

$ aws iam help
$ aws iam create-user help

Example: list users

 
$ aws iam list-users
{
    "Users": [
          ...
         {
             "Path": "/",
             "UserName": "test-user-1",
             "UserId": "AIDBBQ3OFFXCBT3AQWDK7",
             "Arn": "arn:aws:iam::136201378220:user/test-user-1",
             "CreateDate": "2022-05-12T11:55:25Z"
        },
         ...
    ]
}
 

 
If using LocalStack:
 
$ aws --endpoint http://aws:4566 iam list-users
 

Example: deleting a user:

 
$ aws iam delete-user --user-name test-user-1

 

Example: attaching a policy to a user:

 
$ aws --endpoint http://aws:4566 iam attach-user-policy --user-name amelia --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
 
 

Example: creating a group:

 
$ aws --endpoint http://aws:4566 iam create-group --group-name project-calabria-developers               {
    "Group": {
        "Path": "/",
        "GroupName": "project-calabria-developers",
        "GroupId": "c2tuoijto26m2cvuensk",
        "Arn": "arn:aws:iam::000000000000:group/project-calabria-developers",
        "CreateDate": "2022-05-12T13:53:47.627000+00:00"
    }
}

Example: adding a user to a group:


$ aws --endpoint http://aws:4566 iam add-user-to-group --group-name project-calabria-developers --user-name meridith
 
 
 

Example: check policies attached to the group: 

 
$ aws --endpoint http://aws:4566 iam list-attached-group-policies --group-name project-calabria-developers 
 
 

Example: check policies attached to user:

 
$ aws --endpoint http://aws:4566 iam list-attached-user-policies --user-name meridith


Example: attach a policy to a group

 
$ aws --endpoint http://aws:4566 iam attach-group-policy --group-name project-calabria-developers --policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess 
 

How to check which AWS account is used by aws cli?

$ aws sts get-caller-identity
{
    "UserId": "AIDAQQ3OFFXCDDYYVTMN5",
    "Account": "046202377221",
    "Arn": "arn:aws:iam::046202377221:user/bojan.komazec"
}



How to authenticate to AWS via 3rd Party Identity Provider Services?



If we already manage user identities outside of AWS (e.g. via Google, Microsoft etc...) we can:
  • use Identity Providers (IdPs) instead of creating IAM users in our AWS account
  • give these external user identities permissions to use AWS resources in our account. This is useful if:
    • our organization already has its own identity system, such as a corporate user directory
    • we are creating a mobile app or web application that requires access to AWS resources

External IdP provides identity information to AWS using:
  • OpenID Connect (OIDC)
    • connects applications, like GitHub Actions, that do not run on AWS to AWS resources
  • SAML 2.0 (Security Assertion Markup Language 2.0)
    • Examples of well-known SAML identity providers are Shibboleth and Active Directory Federation Services
When we use an identity provider, we don't have to create custom sign-in code or manage our own user identities. The IdP provides that for us. Our external users sign in through an IdP, and we can give those external identities permissions to use AWS resources in our account. Identity providers help keep our AWS account secure because we don't have to distribute or embed long-term security credentials, such as access keys, in our application.

Best Practices





---