Skip to main content
DI
pamcloud-securitydevops

Privileged Access Management for Modern Cloud Infrastructure

Traditional PAM was built for on-premise servers with static credentials. Modern cloud infrastructure demands a new approach—just-in-time access, zero standing privileges, and secrets management woven into CI/CD pipelines. Here is how to build it.

Deepak GuptaJanuary 20, 202614 min read
Share:

PAM Was Built for a Different World

Traditional Privileged Access Management was designed for a world of persistent servers, static administrator accounts, and well-defined network perimeters. A PAM solution would vault the root or Administrator password, record terminal sessions, and require check-out/check-in for credential use.

That model does not map to modern cloud infrastructure. Consider what "privileged access" means today:

  • An engineer assumes an IAM role in AWS to debug a production Lambda function
  • A CI/CD pipeline uses a service account to deploy containers to a Kubernetes cluster
  • A site reliability engineer accesses a production database through a connection proxy
  • A Terraform apply modifies infrastructure across three cloud providers
  • A developer queries production logs through an observability platform
None of these scenarios involve a human typing ssh root@server and entering a password from a vault. The infrastructure is ephemeral, the access is programmatic, the identities are often non-human, and the "privilege" is defined by cloud IAM policies—not by operating system accounts.

Modern PAM must adapt to this reality or become irrelevant.

Cloud PAM Architecture

{/* Title */} PAM Architecture for Cloud-Native Infrastructure {/* Human identities */} Human Identities • Engineers • SREs • DB Admins • Security Ops {/* Machine identities */} Machine Identities • CI/CD Pipelines • Service Accounts • Kubernetes Pods • Terraform Runners {/* Central PAM Platform */} Cloud PAM Platform {/* PAM Components */} Identity Verification + MFA Policy Engine (OPA / Cedar) JIT Access Secrets Vault Session Proxy Audit + Recording Zero Standing Privileges Engine {/* Arrows from identities to PAM */} {/* Target environments */} Cloud IAM AWS IAM Roles / Azure RBAC GCP Service Accounts Kubernetes RBAC Roles / ClusterRoles Service Account Tokens Databases PostgreSQL / MySQL roles Connection proxies (PgBouncer) {/* Arrows from PAM to targets */} {/* Labels on arrows */} Short-lived role assumption Scoped tokens Ephemeral creds {/* Bottom: Observability */} Observability Layer Session Recording • Command Logging • Anomaly Detection • SIEM Export • Compliance Reports {/* Workflow at bottom */} Access Request Workflow Request Slack / CLI / Portal Policy Check Auto-approve or route Approval Manager / peer / auto Provision JIT credential creation Auto-Revoke TTL expires → remove {/* Time indicator */} t=0 t=TTL (1-8 hours) Zero Standing Privileges: access exists only for the duration needed

Just-in-Time Access: The Core Pattern

Just-in-time (JIT) access is the most important pattern in modern PAM. Instead of granting permanent ("standing") privileges that persist until someone remembers to revoke them, JIT access provisions the exact permissions needed, for the exact duration needed, with automatic revocation.

{/* Title */} Just-in-Time Access Flow {/* Timeline */} {/* Phase 1: Request */} 1. Access Request Engineer: "I need prod DB read access for 2h to debug TICKET-4521" t = 0 min {/* Phase 2: Evaluate */} 2. Policy Evaluation ✓ User has role: SRE ✓ Within on-call hours ✓ Valid ticket attached t = 1 min {/* Phase 3: Provision */} 3. JIT Provision Create temp DB user Grant SELECT only Set TTL = 2 hours t = 2 min {/* Active session bar */} Active session {/* Phase 4: Revoke */} 4. Auto-Revoke TTL expired or manual Drop DB user Log session summary t = 2 hours {/* Arrows between phases */} {/* Bottom: Key properties */} JIT Access Properties Scoped Minimum permissions for the specific task Time-bound Auto-expires after requested duration Justified Linked to ticket/incident for audit trail Audited All actions recorded and reviewable

Implementing JIT Access for AWS

Here is a practical implementation of JIT IAM role access using AWS STS with automatic revocation:

import { STSClient, AssumeRoleCommand } from '@aws-sdk/client-sts';
import {
  IAMClient,
  AttachRolePolicyCommand,
  DetachRolePolicyCommand,
  CreatePolicyCommand,
  DeletePolicyCommand,
} from '@aws-sdk/client-iam';

interface JITAccessRequest {
userId: string;
justification: string;
ticketId: string;
targetRole: string;
permissions: string[]; // e.g., ["rds:DescribeDBInstances", "rds-data:ExecuteStatement"]
resources: string[]; // e.g., ["arn:aws:rds:us-east-1:123:db/prod-db"]
durationSeconds: number;
}

async function grantJITAccess(request: JITAccessRequest) {
const iam = new IAMClient({});
const sts = new STSClient({});

// Step 1: Create a scoped, inline policy
const policyDocument = {
Version: '2012-10-17',
Statement: [{
Effect: 'Allow',
Action: request.permissions,
Resource: request.resources,
Condition: {
// Restrict to specific source IP (optional)
'IpAddress': { 'aws:SourceIp': await getUserIp(request.userId) },
// Add MFA requirement
'Bool': { 'aws:MultiFactorAuthPresent': 'true' },
},
}],
};

// Step 2: Create temporary policy
const policy = await iam.send(new CreatePolicyCommand({
PolicyName: jit-${request.userId}-${Date.now()},
PolicyDocument: JSON.stringify(policyDocument),
Tags: [
{ Key: 'jit-request', Value: request.ticketId },
{ Key: 'expires-at', Value: new Date(
Date.now() + request.durationSeconds * 1000
).toISOString() },
],
}));

// Step 3: Attach to target role
await iam.send(new AttachRolePolicyCommand({
RoleName: request.targetRole,
PolicyArn: policy.Policy!.Arn!,
}));

// Step 4: Assume the role with session duration
const credentials = await sts.send(new AssumeRoleCommand({
RoleArn: arn:aws:iam::123456789:role/${request.targetRole},
RoleSessionName: jit-${request.userId},
DurationSeconds: Math.min(request.durationSeconds, 3600),
Tags: [
{ Key: 'ticket', Value: request.ticketId },
{ Key: 'justification', Value: request.justification.slice(0, 256) },
],
}));

// Step 5: Schedule cleanup
await scheduleRevocation(policy.Policy!.Arn!, request.targetRole, request.durationSeconds);

// Step 6: Audit log
await auditLog({
event: 'jit_access_granted',
userId: request.userId,
targetRole: request.targetRole,
permissions: request.permissions,
resources: request.resources,
duration: request.durationSeconds,
ticketId: request.ticketId,
expiresAt: new Date(Date.now() + request.durationSeconds * 1000).toISOString(),
});

return credentials.Credentials;
}

Secrets Management in CI/CD Pipelines

CI/CD pipelines are one of the most dangerous attack surfaces for privileged access. A compromised pipeline can deploy malicious code, exfiltrate secrets, or modify infrastructure. Key principles:

Never Store Secrets in Code or Environment Variables

# WRONG - secrets in environment variables persist in process lists and crash dumps
env:
  DATABASE_PASSWORD: ${{ secrets.DB_PASSWORD }}

BETTER - fetch secrets at runtime from a vault

steps:
- name: Retrieve secrets
uses: hashicorp/vault-action@v2
with:
url: https://vault.internal.example.com
method: jwt
role: ci-deploy-role
# Vault generates short-lived database credentials on demand
secrets: |
database/creds/deploy-role username | DB_USERNAME ;
database/creds/deploy-role password | DB_PASSWORD

Workload Identity for Pipelines

Modern CI/CD platforms support workload identity federation—the pipeline authenticates to cloud providers using its platform identity (e.g., GitHub Actions OIDC token) rather than long-lived service account keys:

# GitHub Actions — workload identity federation with AWS
permissions:
  id-token: write   # Required for OIDC token
  contents: read

steps:
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-deploy
aws-region: us-east-1
# No secrets! GitHub's OIDC token proves pipeline identity

This eliminates the need to store cloud credentials as repository secrets entirely. The trust relationship is configured once in the cloud provider's IAM, and each pipeline run gets short-lived credentials scoped to exactly what it needs.

Kubernetes RBAC and Privileged Access

Kubernetes introduces unique PAM challenges because it has its own RBAC system that operates independently of cloud IAM. A user might have zero permissions in AWS but full cluster-admin in Kubernetes if RBAC is misconfigured.

Principle of Least Privilege in Kubernetes

# WRONG - overly broad ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dev-team-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin    # Full cluster access!
subjects:
  - kind: Group
    name: dev-team

RIGHT - scoped Role in specific namespace

apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: dev-team-debugger namespace: production rules: - apiGroups: [""] resources: ["pods", "pods/log"] verbs: ["get", "list"] # Read-only - apiGroups: [""] resources: ["pods/exec"] verbs: ["create"] # Exec for debugging # Further restrict with admission controllers
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: dev-team-debugger-binding namespace: production roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: dev-team-debugger subjects: - kind: Group name: dev-team

Pod Security and Service Account Hardening

Every Kubernetes pod runs with a service account. By default, this service account has a mounted token that can access the Kubernetes API. Lock this down:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-service-account
  namespace: production
automountServiceAccountToken: false  # Don't mount API token by default

apiVersion: v1 kind: Pod metadata: name: app-pod spec: serviceAccountName: app-service-account securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 seccompProfile: type: RuntimeDefault containers: - name: app securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"]

Zero Standing Privileges: The End State

The ultimate goal of modern PAM is zero standing privileges (ZSP)—a state where no human or machine identity has persistent privileged access to any system. All privileged access is:

  • Requested with justification and context
  • Evaluated against policy (auto-approved for low-risk, peer-approved for high-risk)
  • Provisioned just-in-time with minimum scope
  • Monitored with session recording and anomaly detection
  • Revoked automatically when the time window expires
This aligns directly with the Zero Trust principle described in Zero Trust Networks: never trust, always verify, and grant the least privilege necessary.

Measuring Your ZSP Progress

Track these metrics to measure your journey toward zero standing privileges:

MetricStarting StateTarget State
Standing admin accountsHundredsZero
Average privilege durationPermanent< 4 hours
Percent JIT-provisioned access0%> 95%
Long-lived service account keysHundredsZero (use workload identity)
Time to revoke after offboardingDays-weeks< 1 hour
Percent of access with justification0%100%

Session Recording and Forensics

Modern PAM must record privileged sessions for compliance and forensic analysis. But traditional screen recording of SSH sessions is insufficient for cloud-native environments. Modern session recording captures:

  • API calls: Every AWS/GCP/Azure API call made during a privileged session
  • Kubernetes commands: Every kubectl command and API server request
  • Database queries: Every SQL statement executed against production databases
  • Infrastructure changes: Every Terraform plan and apply, with diff output
These recordings should be stored immutably (write-once storage), indexed for search, and integrated with your SIEM for correlation with other security events.

Getting Started: A Phased Approach

Phase 1: Inventory and Visibility (Weeks 1-4)

  • Catalog all privileged accounts across cloud providers, Kubernetes clusters, and databases
  • Identify standing privileges and their owners
  • Implement audit logging for all privileged access

Phase 2: Secrets Migration (Weeks 5-8)

  • Move all secrets from code, config files, and CI/CD variables to a secrets vault (HashiCorp Vault, AWS Secrets Manager)
  • Implement workload identity federation for CI/CD pipelines
  • Rotate all long-lived credentials

Phase 3: JIT Access (Weeks 9-16)

  • Deploy a JIT access platform (Teleport, StrongDM, Indent, or custom)
  • Migrate high-risk access (production databases, cloud admin roles) to JIT
  • Implement approval workflows and policy-based auto-approval

Phase 4: Zero Standing Privileges (Ongoing)

As documented in Identity Attack Vectors, privileged credentials remain the most sought-after target for attackers. Every standing privilege is an opportunity waiting to be exploited. The path to zero standing privileges is the path to a defensible security posture.

Enjoyed this article?

Subscribe for more expert insights on digital identity, IAM, and security.