mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
V2
This commit is contained in:
@@ -9,28 +9,70 @@ metadata:
|
||||
|
||||
# AWS IAM
|
||||
|
||||
Manage identity and access in AWS.
|
||||
Manage identity and access in AWS with least-privilege policies, roles, federation, and permission boundaries.
|
||||
|
||||
## IAM Policies
|
||||
## When to Use This Skill
|
||||
|
||||
- Creating roles for EC2 instances, Lambda functions, or ECS tasks
|
||||
- Writing custom IAM policies with least-privilege access
|
||||
- Setting up OIDC federation for GitHub Actions or other CI/CD systems
|
||||
- Implementing permission boundaries for delegated administration
|
||||
- Auditing access with IAM Access Analyzer and credential reports
|
||||
- Configuring cross-account access with assume-role patterns
|
||||
- Enforcing MFA and session policies
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- AWS CLI v2 installed and configured
|
||||
- IAM permissions: `iam:*` (or scoped to specific actions for least privilege)
|
||||
- For OIDC: ability to create identity providers (`iam:CreateOpenIDConnectProvider`)
|
||||
- AWS Organizations access for Service Control Policies (SCPs)
|
||||
|
||||
## IAM Policy Structure
|
||||
|
||||
Every IAM policy follows the same JSON structure. Always specify the minimum actions and resources required.
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:GetObject",
|
||||
"s3:PutObject"
|
||||
],
|
||||
"Resource": "arn:aws:s3:::my-bucket/*"
|
||||
}]
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "AllowS3ReadWrite",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:GetObject",
|
||||
"s3:PutObject",
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::my-app-bucket",
|
||||
"arn:aws:s3:::my-app-bucket/*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"s3:x-amz-server-side-encryption": "aws:kms"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sid": "DenyUnencryptedUploads",
|
||||
"Effect": "Deny",
|
||||
"Action": "s3:PutObject",
|
||||
"Resource": "arn:aws:s3:::my-app-bucket/*",
|
||||
"Condition": {
|
||||
"StringNotEquals": {
|
||||
"s3:x-amz-server-side-encryption": "aws:kms"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Create Role
|
||||
## Create and Manage Roles
|
||||
|
||||
```bash
|
||||
# Create role with trust policy
|
||||
# Create an EC2 instance role with trust policy
|
||||
aws iam create-role \
|
||||
--role-name EC2AppRole \
|
||||
--assume-role-policy-document '{
|
||||
@@ -40,58 +82,366 @@ aws iam create-role \
|
||||
"Principal": {"Service": "ec2.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole"
|
||||
}]
|
||||
}' \
|
||||
--tags '[{"Key":"Team","Value":"platform"},{"Key":"Environment","Value":"production"}]'
|
||||
|
||||
# Create and attach an inline policy
|
||||
aws iam put-role-policy \
|
||||
--role-name EC2AppRole \
|
||||
--policy-name s3-access \
|
||||
--policy-document '{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Action": ["s3:GetObject", "s3:PutObject"],
|
||||
"Resource": "arn:aws:s3:::my-app-bucket/*"
|
||||
}]
|
||||
}'
|
||||
|
||||
# Attach policy
|
||||
# Attach a managed policy
|
||||
aws iam attach-role-policy \
|
||||
--role-name EC2AppRole \
|
||||
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
||||
--policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
|
||||
|
||||
# Create instance profile and associate the role
|
||||
aws iam create-instance-profile --instance-profile-name EC2AppProfile
|
||||
aws iam add-role-to-instance-profile \
|
||||
--instance-profile-name EC2AppProfile \
|
||||
--role-name EC2AppRole
|
||||
|
||||
# Create a Lambda execution role
|
||||
aws iam create-role \
|
||||
--role-name LambdaExecRole \
|
||||
--assume-role-policy-document '{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "lambda.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole"
|
||||
}]
|
||||
}'
|
||||
|
||||
aws iam attach-role-policy \
|
||||
--role-name LambdaExecRole \
|
||||
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
|
||||
```
|
||||
|
||||
## Service-Linked Roles
|
||||
## Cross-Account Access
|
||||
|
||||
```bash
|
||||
# For services like ECS, RDS
|
||||
aws iam create-service-linked-role \
|
||||
--aws-service-name ecs.amazonaws.com
|
||||
# In Account B: create role that Account A can assume
|
||||
aws iam create-role \
|
||||
--role-name CrossAccountReadRole \
|
||||
--assume-role-policy-document '{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": "arn:aws:iam::111111111111:root"},
|
||||
"Action": "sts:AssumeRole",
|
||||
"Condition": {
|
||||
"StringEquals": {"sts:ExternalId": "unique-external-id-12345"}
|
||||
}
|
||||
}]
|
||||
}'
|
||||
|
||||
# In Account A: assume the role
|
||||
aws sts assume-role \
|
||||
--role-arn arn:aws:iam::222222222222:role/CrossAccountReadRole \
|
||||
--role-session-name cross-account-session \
|
||||
--external-id unique-external-id-12345
|
||||
|
||||
# Use the temporary credentials
|
||||
export AWS_ACCESS_KEY_ID="ASIAXXX"
|
||||
export AWS_SECRET_ACCESS_KEY="xxx"
|
||||
export AWS_SESSION_TOKEN="xxx"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
## OIDC Federation for GitHub Actions
|
||||
|
||||
```bash
|
||||
# Create the GitHub OIDC identity provider
|
||||
aws iam create-open-id-connect-provider \
|
||||
--url https://token.actions.githubusercontent.com \
|
||||
--client-id-list sts.amazonaws.com \
|
||||
--thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1"
|
||||
|
||||
# Create a role for GitHub Actions with repo-scoped trust
|
||||
aws iam create-role \
|
||||
--role-name GitHubActionsDeployRole \
|
||||
--assume-role-policy-document '{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
|
||||
},
|
||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
|
||||
},
|
||||
"StringLike": {
|
||||
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
|
||||
}
|
||||
}
|
||||
}]
|
||||
}'
|
||||
|
||||
# Attach deployment permissions to the role
|
||||
aws iam attach-role-policy \
|
||||
--role-name GitHubActionsDeployRole \
|
||||
--policy-arn arn:aws:iam::123456789012:policy/DeploymentPolicy
|
||||
```
|
||||
|
||||
GitHub Actions workflow usage:
|
||||
|
||||
```yaml
|
||||
security_practices:
|
||||
- Use roles, not long-term credentials
|
||||
- Implement least privilege
|
||||
- Enable MFA
|
||||
- Regular access reviews
|
||||
- Use IAM Access Analyzer
|
||||
- Implement SCPs for organizations
|
||||
# .github/workflows/deploy.yml
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployRole
|
||||
aws-region: us-east-1
|
||||
- run: aws sts get-caller-identity
|
||||
```
|
||||
|
||||
## Policy Conditions
|
||||
## Permission Boundaries
|
||||
|
||||
```json
|
||||
{
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:RequestedRegion": "us-east-1"
|
||||
},
|
||||
"Bool": {
|
||||
"aws:MultiFactorAuthPresent": "true"
|
||||
}
|
||||
}
|
||||
```bash
|
||||
# Create a permission boundary policy
|
||||
aws iam create-policy \
|
||||
--policy-name DeveloperBoundary \
|
||||
--policy-document '{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "AllowedServices",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:*",
|
||||
"lambda:*",
|
||||
"dynamodb:*",
|
||||
"sqs:*",
|
||||
"sns:*",
|
||||
"logs:*",
|
||||
"cloudwatch:*",
|
||||
"ecr:*",
|
||||
"ecs:*"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "DenyIAMChanges",
|
||||
"Effect": "Deny",
|
||||
"Action": [
|
||||
"iam:CreateUser",
|
||||
"iam:DeleteUser",
|
||||
"iam:CreateRole",
|
||||
"iam:DeleteRole",
|
||||
"iam:AttachRolePolicy",
|
||||
"iam:PutRolePermissionsBoundary",
|
||||
"iam:DeleteRolePermissionsBoundary"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "DenyOutsideRegion",
|
||||
"Effect": "Deny",
|
||||
"Action": "*",
|
||||
"Resource": "*",
|
||||
"Condition": {
|
||||
"StringNotEquals": {
|
||||
"aws:RequestedRegion": ["us-east-1", "us-west-2"]
|
||||
},
|
||||
"ForAnyValue:StringNotLike": {
|
||||
"aws:PrincipalArn": "arn:aws:iam::*:role/admin-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}'
|
||||
|
||||
# Create a role with the permission boundary
|
||||
aws iam create-role \
|
||||
--role-name DeveloperRole \
|
||||
--assume-role-policy-document file://trust-policy.json \
|
||||
--permissions-boundary "arn:aws:iam::123456789012:policy/DeveloperBoundary"
|
||||
```
|
||||
|
||||
## IAM Access Analyzer and Auditing
|
||||
|
||||
```bash
|
||||
# Create an IAM Access Analyzer
|
||||
aws accessanalyzer create-analyzer \
|
||||
--analyzer-name account-analyzer \
|
||||
--type ACCOUNT
|
||||
|
||||
# List findings (externally accessible resources)
|
||||
aws accessanalyzer list-findings \
|
||||
--analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/account-analyzer
|
||||
|
||||
# Generate credential report
|
||||
aws iam generate-credential-report
|
||||
aws iam get-credential-report --output text --query Content | base64 -d > credential-report.csv
|
||||
|
||||
# Find users with console access but no MFA
|
||||
aws iam list-users --query "Users[].UserName" --output text | while read user; do
|
||||
mfa=$(aws iam list-mfa-devices --user-name "$user" --query "MFADevices" --output text)
|
||||
if [ -z "$mfa" ]; then
|
||||
echo "NO MFA: $user"
|
||||
fi
|
||||
done
|
||||
|
||||
# List all policies attached to a role
|
||||
aws iam list-attached-role-policies --role-name EC2AppRole
|
||||
aws iam list-role-policies --role-name EC2AppRole
|
||||
|
||||
# Get the last-accessed services for a role
|
||||
aws iam generate-service-last-accessed-details --arn arn:aws:iam::123456789012:role/EC2AppRole
|
||||
# Then retrieve results with the returned JobId
|
||||
aws iam get-service-last-accessed-details --job-id "job-id-from-above"
|
||||
|
||||
# Simulate a policy to test access
|
||||
aws iam simulate-principal-policy \
|
||||
--policy-source-arn arn:aws:iam::123456789012:role/EC2AppRole \
|
||||
--action-names s3:GetObject s3:PutObject \
|
||||
--resource-arns arn:aws:s3:::my-app-bucket/data.json
|
||||
```
|
||||
|
||||
## Terraform IAM Role with OIDC
|
||||
|
||||
```hcl
|
||||
# OIDC provider for GitHub Actions
|
||||
resource "aws_iam_openid_connect_provider" "github" {
|
||||
url = "https://token.actions.githubusercontent.com"
|
||||
client_id_list = ["sts.amazonaws.com"]
|
||||
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
|
||||
}
|
||||
|
||||
# Role for GitHub Actions
|
||||
resource "aws_iam_role" "github_actions" {
|
||||
name = "GitHubActionsDeployRole"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Federated = aws_iam_openid_connect_provider.github.arn
|
||||
}
|
||||
Action = "sts:AssumeRoleWithWebIdentity"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
|
||||
}
|
||||
StringLike = {
|
||||
"token.actions.githubusercontent.com:sub" = "repo:my-org/my-repo:*"
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
permissions_boundary = aws_iam_policy.boundary.arn
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "deploy" {
|
||||
role = aws_iam_role.github_actions.name
|
||||
policy_arn = aws_iam_policy.deployment.arn
|
||||
}
|
||||
|
||||
# Permission boundary
|
||||
resource "aws_iam_policy" "boundary" {
|
||||
name = "DeveloperBoundary"
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowedServices"
|
||||
Effect = "Allow"
|
||||
Action = ["s3:*", "lambda:*", "dynamodb:*", "ecs:*", "logs:*"]
|
||||
Resource = "*"
|
||||
},
|
||||
{
|
||||
Sid = "DenyIAMEscalation"
|
||||
Effect = "Deny"
|
||||
Action = ["iam:CreateUser", "iam:CreateRole", "iam:AttachRolePolicy"]
|
||||
Resource = "*"
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
## Service Control Policies (Organizations)
|
||||
|
||||
- Follow least privilege
|
||||
- Use IAM roles for applications
|
||||
- Enable CloudTrail for auditing
|
||||
- Regular credential rotation
|
||||
- Use permission boundaries
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "DenyRootAccount",
|
||||
"Effect": "Deny",
|
||||
"Action": "*",
|
||||
"Resource": "*",
|
||||
"Condition": {
|
||||
"StringLike": {
|
||||
"aws:PrincipalArn": "arn:aws:iam::*:root"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sid": "RequireIMDSv2",
|
||||
"Effect": "Deny",
|
||||
"Action": "ec2:RunInstances",
|
||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||
"Condition": {
|
||||
"StringNotEquals": {
|
||||
"ec2:MetadataHttpTokens": "required"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sid": "DenyRegionsOutsideUS",
|
||||
"Effect": "Deny",
|
||||
"Action": "*",
|
||||
"Resource": "*",
|
||||
"Condition": {
|
||||
"StringNotEquals": {
|
||||
"aws:RequestedRegion": ["us-east-1", "us-west-2"]
|
||||
},
|
||||
"ForAnyValue:StringNotLike": {
|
||||
"aws:PrincipalArn": ["arn:aws:iam::*:role/OrganizationAdmin"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Cause | Fix |
|
||||
|---|---|---|
|
||||
| Access Denied on API call | Missing or incorrect policy | Use `simulate-principal-policy` to test; check resource ARN format |
|
||||
| Role cannot be assumed | Trust policy does not include the caller | Verify Principal in trust policy matches caller ARN |
|
||||
| OIDC federation fails | Thumbprint or audience mismatch | Verify OIDC provider URL, client ID list, and condition keys |
|
||||
| Permission boundary blocks action | Boundary does not include the action | Add the action to the boundary; effective = identity AND boundary |
|
||||
| Credential report shows stale keys | Keys not rotated in 90+ days | Rotate keys; disable unused access keys |
|
||||
| Service-linked role creation fails | Organization SCP blocks iam:CreateServiceLinkedRole | Add exception in SCP for the specific service |
|
||||
| Cross-account assume role fails | Missing ExternalId or wrong account | Verify ExternalId matches; check account number in Principal |
|
||||
| MFA condition not enforced | Condition key not in policy | Add `aws:MultiFactorAuthPresent` condition |
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [terraform-aws](../terraform-aws/) - IaC deployment
|
||||
- [access-review](../../../compliance/governance/access-review/) - Access auditing
|
||||
- [terraform-aws](../terraform-aws/) - IaC deployment of IAM resources
|
||||
- [aws-ec2](../aws-ec2/) - Instance profiles and roles
|
||||
- [aws-lambda](../aws-lambda/) - Lambda execution roles
|
||||
- [aws-ecs-fargate](../aws-ecs-fargate/) - ECS task and execution roles
|
||||
- [access-review](../../../compliance/governance/access-review/) - Access auditing and governance
|
||||
|
||||
Reference in New Issue
Block a user