Skip to main content
Career Paths
Concepts
Service Auth Iam
The Simplified Tech

Role-based learning paths to help you master cloud engineering with clarity and confidence.

Product

  • Career Paths
  • Interview Prep
  • Scenarios
  • AI Features
  • Cloud Comparison
  • Pricing

Community

  • Join Discord

Account

  • Dashboard
  • Credits
  • Updates
  • Sign in
  • Sign up
  • Contact Support

Stay updated

Get the latest learning tips and updates. No spam, ever.

Terms of ServicePrivacy Policy

© 2026 TheSimplifiedTech. All rights reserved.

BackBack
Interactive Explainer

Service-to-Service Authentication

Why hardcoded credentials are a ticking clock, how IAM roles and instance profiles eliminate them, and how OIDC federation enables GitHub Actions and Kubernetes workloads to authenticate to AWS without storing any long-lived secrets.

Relevant for:JuniorMid-levelSenior
Why this matters at your level
Junior

Understands why IAM user access keys are unsafe for production. Can create an IAM role with a trust policy and attach it to an EC2 instance or Lambda. Knows the AWS SDK credential chain order. Never hardcodes credentials in application code.

Mid-level

Implements OIDC federation for GitHub Actions. Designs least-privilege IAM roles for each service using specific ARNs and conditions. Sets up cross-account assume-role for deployment pipelines. Enforces IMDSv2 on all EC2 instances.

Senior

Audits IAM access across the organisation using IAM Access Analyzer, AWS Config, and Access Advisor. Designs the cross-account role trust architecture for multi-account organisations. Sets the SCPs that prevent IAM users from being created in member accounts. Owns the incident response runbook for credential exposure events.

Service-to-Service Authentication

Why hardcoded credentials are a ticking clock, how IAM roles and instance profiles eliminate them, and how OIDC federation enables GitHub Actions and Kubernetes workloads to authenticate to AWS without storing any long-lived secrets.

~5 min read
Be the first to complete!
LIVECredential Exposure -- Public Repository -- August 2020
Breaking News
T+0 min

Developer commits .env file containing AWS IAM user access key (AKIA...) to a public GitHub repository.

WARNING
T+4 min

Automated credential scanner operated by a threat actor detects the exposed key in the public repository.

CRITICAL
T+7 min

Attacker launches EC2 instances in the developer's AWS account for cryptocurrency mining using the stolen credentials.

CRITICAL
T+12 hrs

Developer notices a $7,000 AWS bill spike. Investigation reveals EC2 crypto mining and S3 data exfiltration.

CRITICAL
T+12 hrs

Developer revokes the compromised IAM key. Mining stops. S3 exfiltration damage assessment begins.

—to credential theft after public commit
—AWS bill from crypto mining
—exposure window before detection
—alerts before the AWS bill spike

The question this raises

If any engineer on your team accidentally committed production AWS credentials to a public repo right now, how many minutes before it would be used -- and do you even have an inventory of all active IAM user access keys?

Test your assumption first

Your ECS task is running with a task role that has s3:GetObject access. You need to test it locally. You set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables on your laptop to an IAM user with the same permissions. Everything works. Later, on a colleague's machine, the application fails with "Access Denied" when calling S3. Your colleague has no AWS environment variables set. What is most likely happening?

Lesson outline

Why this matters

The Root Problem: Long-Lived Credentials

An IAM user access key (AKIA...) is a long-lived static credential. It does not expire unless you explicitly rotate or revoke it. It can be stored in ~/.aws/credentials, .env files, CI/CD environment variables, Docker images, and git history. Any one of these locations can be breached. The problem is not that developers are careless -- it is that long-lived credentials are fundamentally unsafe to handle. The solution is to eliminate them entirely.

No IAM user access keys in production -- use roles

IAM roles issue short-lived temporary credentials (valid 15 minutes to 12 hours) via the AWS Security Token Service (STS). There is no credential to rotate, no secret to store, and no key that can be committed to git. If a role's temporary credential leaks, it expires automatically. This is the entire value proposition: replace the problem class (long-lived static secrets) with something that has a time limit.

Where long-lived credentials appear and what to replace them with

  • EC2 application code — Replace IAM user key + secret with EC2 instance profile (IAM role attached to the instance)
  • ECS/Fargate tasks — Replace env var secrets with ECS task role
  • Lambda functions — Replace hardcoded credentials with Lambda execution role (always present by default)
  • GitHub Actions CI — Replace stored access keys with OIDC federation with AWS (no stored credentials)
  • On-premises scripts — Replace IAM user keys with IAM Roles Anywhere (certificate-based, STS-issued temp credentials)
Aha!

IAM Roles: How Temporary Credentials Work

An IAM role is not a user -- it is a set of permissions that can be assumed by a trusted entity. When an EC2 instance has an instance profile (which contains a role), the AWS SDK on that instance can call the EC2 metadata service (169.254.169.254/latest/meta-data/iam/security-credentials/) to receive temporary credentials. These credentials are automatically rotated every hour. Your application code never stores a key -- it just calls the SDK, which fetches fresh credentials automatically.

How STS assume-role works

→

01

Your code calls any AWS SDK method (e.g. s3.getObject()).

→

02

The SDK checks for credentials in order: environment variables, then ~/.aws/credentials, then instance metadata service (IMDS).

→

03

The IMDS returns a temporary access key, secret key, and session token -- valid for up to 1 hour.

→

04

The SDK makes the AWS API call using these temporary credentials.

05

After ~50 minutes, the SDK automatically refreshes the credentials before they expire. Your code never sees a rotation event.

1

Your code calls any AWS SDK method (e.g. s3.getObject()).

2

The SDK checks for credentials in order: environment variables, then ~/.aws/credentials, then instance metadata service (IMDS).

3

The IMDS returns a temporary access key, secret key, and session token -- valid for up to 1 hour.

4

The SDK makes the AWS API call using these temporary credentials.

5

After ~50 minutes, the SDK automatically refreshes the credentials before they expire. Your code never sees a rotation event.

The credential chain order matters for debugging

If your application is using the wrong credentials, it is because something earlier in the chain is overriding the instance profile. Check: (1) Is AWS_ACCESS_KEY_ID set in environment variables? (2) Is there a ~/.aws/credentials file on the instance? Both override the instance profile. Clear or unset them -- the instance profile should always be the credential source for production workloads.

Pattern

OIDC Federation -- Zero Credentials for CI/CD

OpenID Connect (OIDC) federation allows external systems -- GitHub Actions, GitLab CI, Kubernetes service accounts -- to authenticate to AWS without any stored secrets. Instead, the CI system presents a signed JWT (JSON Web Token) issued by its identity provider. AWS verifies the JWT signature using the provider's public key and exchanges it for temporary STS credentials.

Setting up GitHub Actions OIDC with AWS

→

01

In AWS IAM: create an OIDC identity provider for token.actions.githubusercontent.com with audience sts.amazonaws.com.

→

02

Create an IAM role with a trust policy that allows the OIDC provider to assume it, constrained to a specific GitHub org/repo/branch.

→

03

In your GitHub Actions workflow: add permissions: id-token: write. Use the aws-actions/configure-aws-credentials action with role-to-assume pointing to your IAM role ARN.

→

04

The action requests a JWT from GitHub, presents it to AWS STS, and receives temporary credentials. The workflow now has AWS access for its duration. No secrets stored anywhere.

05

The trust policy condition StringLike: token.actions.githubusercontent.com:sub: repo:myorg/myrepo:ref:refs/heads/main ensures only the specified repository and branch can assume the role.

1

In AWS IAM: create an OIDC identity provider for token.actions.githubusercontent.com with audience sts.amazonaws.com.

2

Create an IAM role with a trust policy that allows the OIDC provider to assume it, constrained to a specific GitHub org/repo/branch.

3

In your GitHub Actions workflow: add permissions: id-token: write. Use the aws-actions/configure-aws-credentials action with role-to-assume pointing to your IAM role ARN.

4

The action requests a JWT from GitHub, presents it to AWS STS, and receives temporary credentials. The workflow now has AWS access for its duration. No secrets stored anywhere.

5

The trust policy condition StringLike: token.actions.githubusercontent.com:sub: repo:myorg/myrepo:ref:refs/heads/main ensures only the specified repository and branch can assume the role.

Scope the trust policy to branch and repo, not just org

An OIDC trust policy that allows any branch of any repo in your GitHub organisation means a developer who can push to any branch in any repo can assume the production deployment role. Always add the :sub condition scoped to repo:org/repo:ref:refs/heads/main (or your release branch). This ensures only the specific branch that should deploy to production can assume the production role.

Pattern

Cross-Account Access -- Assume Role Across Accounts

In a multi-account architecture, services in one account frequently need to access resources in another -- a deployment pipeline in a shared tooling account needs to deploy to the production account, or a monitoring account needs to read CloudWatch metrics from all other accounts. This is done with cross-account assume-role.

Cross-account assume-role pattern

  • In the target account — Create an IAM role with a trust policy that allows the source account (or specific role in the source account) to assume it. Grant only the permissions needed.
  • In the source account — The calling role needs sts:AssumeRole permission for the target role's ARN.
  • In code — Call sts.assumeRole({ RoleArn: "arn:aws:iam::TARGET_ACCOUNT_ID:role/RoleName", RoleSessionName: "deploy-session" }) to receive temporary credentials, then use them for target account API calls.
  • Principle of least privilege — The cross-account role should have only the permissions needed for the specific task. A CI/CD role that deploys ECS services should not have S3 access to customer data buckets.

External ID prevents confused deputy attacks

If you are building a SaaS product that assumes roles in your customers' accounts, always require an ExternalId in the trust policy. Without ExternalId, any AWS account that knows your role ARN can trick your service into assuming a customer's role (the confused deputy attack). ExternalId is a shared secret between you and the customer -- your service only assumes the role if the correct ExternalId is present in the AssumeRole request.

Exam Answer vs. Production Reality

1 / 4

IAM roles vs IAM users

📖 What the exam expects

IAM users have permanent credentials (username/password, access keys). IAM roles have no credentials -- they are assumed by trusted entities and issue temporary credentials via STS.

Toggle between what certifications teach and what production actually requires

How this might come up in interviews

Mid-level and senior cloud engineer and security engineer interviews. Often framed as "how do you secure your CI/CD pipeline?" or incident post-mortems asking why a developer was able to commit credentials that had production access.

Common questions:

  • Why should production services never use IAM user access keys? What do you use instead?
  • Walk me through how OIDC federation works for GitHub Actions deploying to AWS.
  • What is the confused deputy attack and how does ExternalId prevent it?
  • A developer is getting "Access Denied" on S3 from an EC2 instance with an instance profile that has S3 access. What do you check first?
  • How do you audit which IAM users in your account still have active access keys?

Try this question: Do any of your production services currently use IAM user access keys? Have you set up OIDC federation for your CI/CD pipelines? Do you enforce IMDSv2 on all EC2 instances?

Strong answer: Has eliminated all IAM user access keys from production in previous roles. Set up OIDC federation for CI/CD. Knows the credential chain order by heart. Mentions IAM Access Analyzer for least-privilege auditing. Enforces IMDSv2 via SCP or instance option.

Red flags: Believes rotating access keys every 90 days makes them "safe enough." Cannot explain the credential chain order. Thinks OIDC is only for browser-based SSO. Does not know what a trust policy is.

Ready to see how this works in the cloud?

Switch to Career Paths for structured paths (e.g. Developer, DevOps) and provider-specific lessons.

View role-based paths

Discussion

Questions? Discuss in the community or start a thread below.

Join Discord

In-app Q&A

Sign in to start or join a thread.

Sign in to track your progress and mark lessons complete.

Discussion

Questions? Discuss in the community or start a thread below.

Join Discord

In-app Q&A

Sign in to start or join a thread.