From 2d143c065871e440384b963866d00ed4dc88a7ac Mon Sep 17 00:00:00 2001 From: bcpenta Date: Thu, 19 Dec 2024 09:47:27 -0500 Subject: [PATCH] Github Actions OIDC IAM Role Trust Relation --- .../aws_iam_role_github_actions_trust.py | 37 ++++ .../aws_iam_role_github_actions_trust.yml | 202 ++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 policies/aws_iam_policies/aws_iam_role_github_actions_trust.py create mode 100644 policies/aws_iam_policies/aws_iam_role_github_actions_trust.yml diff --git a/policies/aws_iam_policies/aws_iam_role_github_actions_trust.py b/policies/aws_iam_policies/aws_iam_role_github_actions_trust.py new file mode 100644 index 000000000..619ebca89 --- /dev/null +++ b/policies/aws_iam_policies/aws_iam_role_github_actions_trust.py @@ -0,0 +1,37 @@ +def policy(resource): + assume_role_policy = resource.get("AssumeRolePolicyDocument", {}).get("Statement", []) + + # Iterate through each statement in the trust policy + for statement in assume_role_policy: + # Check if the statement allows sts:AssumeRoleWithWebIdentity + if statement.get("Effect") == "Allow" and "sts:AssumeRoleWithWebIdentity" in statement.get("Action", []): + # Validate the Principal + principal = statement.get("Principal", {}).get("Federated") + if not principal: + return False # Invalid Principal + if principal == "*": + return False # Wildcard in Principal is insecure + if "oidc-provider/token.actions.githubusercontent.com" not in principal: + continue # Skip non-GitHub-related Principals + + # Validate the conditions only if the Principal is valid for GitHub Actions + conditions = statement.get("Condition", {}) + + # Check if the aud is correctly set + audience = conditions.get("StringEquals", {}).get("token.actions.githubusercontent.com:aud") + if audience != "sts.amazonaws.com": + return False + + # Check if the sub is properly restricted + subject = conditions.get("StringLike", {}).get("token.actions.githubusercontent.com:sub", "") or \ + conditions.get("StringEquals", {}).get("token.actions.githubusercontent.com:sub", "") + + if not subject.startswith("repo:"): + return False # Ensure sub references a repository + + if "*" in subject and not subject.startswith("repo:org/repo:*"): + return False # Disallow overly permissive wildcards + + return True # Valid GitHub Actions config + + return False # No valid statements found diff --git a/policies/aws_iam_policies/aws_iam_role_github_actions_trust.yml b/policies/aws_iam_policies/aws_iam_role_github_actions_trust.yml new file mode 100644 index 000000000..9dee27bb7 --- /dev/null +++ b/policies/aws_iam_policies/aws_iam_role_github_actions_trust.yml @@ -0,0 +1,202 @@ +AnalysisType: policy +Filename: aws_iam_role_github_actions_trust.py +PolicyID: "AWS.IAM.Role.GitHubActionsTrust" +DisplayName: "AWS IAM Role Trust Relationship for GitHub Actions" +Enabled: true +ResourceTypes: + - AWS.IAM.Role +Tags: + - AWS + - GitHub Actions + - Identity & Access Management +Severity: High +Description: > + This policy ensures that IAM roles used with GitHub Actions are securely configured to prevent unauthorized access to AWS resources. + It validates trust relationships by checking for proper audience (aud) restrictions, ensuring it is set to sts.amazonaws.com, and subject (sub) conditions, + confirming they are scoped to specific repositories or environments. Misconfigurations, such as overly permissive wildcards or missing conditions, + can allow unauthorized repositories to assume roles, leading to potential data breaches or compliance violations. + By enforcing these checks, the policy mitigates risks of exploitation, enhances security posture, and protects critical AWS resources from external threats. +Runbook: > + To fix roles flagged by this policy: + 1. Update the trust relationship of the flagged IAM role in the AWS Management Console or CLI. + 2. Add a Condition block with 'StringLike' or 'StringEquals' for 'token.actions.githubusercontent.com:sub'. + 3. Ensure the audience is set to 'sts.amazonaws.com'. + 4. Avoid overly permissive wildcards in the sub condition. +Reference: > + - https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html + - https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers + - https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services +Tests: + - Name: Valid GitHub Actions Trust Relationship + ExpectedResult: true + Resource: + { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRoleWithWebIdentity", + "Principal": { + "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" + }, + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" + }, + "StringLike": { + "token.actions.githubusercontent.com:sub": "repo:org/repo:*" + } + } + } + ] + } + } + + - Name: Missing Audience Condition + ExpectedResult: false + Resource: + { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRoleWithWebIdentity", + "Principal": { + "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" + }, + "Condition": { + "StringLike": { + "token.actions.githubusercontent.com:sub": "repo:org/repo:*" + } + } + } + ] + } + } + + - Name: Missing Subject Restriction + ExpectedResult: false + Resource: + { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRoleWithWebIdentity", + "Principal": { + "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" + }, + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" + } + } + } + ] + } + } + + - Name: Overly Permissive Wildcard in Subject + ExpectedResult: false + Resource: + { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRoleWithWebIdentity", + "Principal": { + "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" + }, + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" + }, + "StringLike": { + "token.actions.githubusercontent.com:sub": "*" + } + } + } + ] + } + } + + - Name: Valid Subject Restriction with Specific Environment + ExpectedResult: true + Resource: + { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRoleWithWebIdentity", + "Principal": { + "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" + }, + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", + "token.actions.githubusercontent.com:sub": "repo:org/repo:environment:prod" + } + } + } + ] + } + } + + - Name: Invalid Principal as Wildcard + ExpectedResult: false + Resource: + { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRoleWithWebIdentity", + "Principal": { + "Federated": "*" + }, + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" + }, + "StringLike": { + "token.actions.githubusercontent.com:sub": "repo:org/repo:*" + } + } + } + ] + } + } + + - Name: Non-GitHub OIDC Principal + ExpectedResult: false + Resource: + { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRoleWithWebIdentity", + "Principal": { + "Federated": "arn:aws:iam::123456789012:oidc-provider/accounts.google.com" + }, + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" + }, + "StringLike": { + "token.actions.githubusercontent.com:sub": "repo:org/repo:*" + } + } + } + ] + } + }