Detection rules › Panther
AWS IAM Role Trust Relationship for GitHub Actions
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.
Rule body yaml
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: false
ResourceTypes:
- AWS.IAM.Role
Tags:
- AWS
- GitHub Actions
- Identity & Access Management
- Configuration Required
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:*"
}
}
}
]
}
}
- Name: Non-GitHub IAM Role
ExpectedResult: true
Resource:
{
"AccountId": "123412341233",
"Arn": "arn:aws:iam::123412341233:role/DevAdministrator",
"AssumeRolePolicyDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"arn:aws:iam::12341523456:root\",\"arn:aws:iam::123412341233:root\"]},\"Action\":\"sts:AssumeRole\",\"Condition\":{\"Bool\":{\"aws:MultiFactorAuthPresent\":\"true\",\"aws:SecureTransport\":\"true\"},\"NumericLessThan\":{\"aws:MultiFactorAuthAge\":\"28800\"}}}]}",
"ManagedPolicyARNs": [
"arn:aws:iam::aws:policy/AdministratorAccess"
],
"ManagedPolicyNames": [
"AdministratorAccess"
],
"MaxSessionDuration": 28800,
"Name": "DevAdministrator",
"Path": "/",
"Region": "global",
"ResourceId": "arn:aws:iam::123412341233:role/DevAdministrator",
"ResourceType": "AWS.IAM.Role",
"TimeCreated": "2023-11-08T23:50:46Z"
}
- Name: Allowed repo
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:allowed-org-example/allowed-repo-example:*"
}
}
}
]
}
}
Detection logic
Rule logic imperative Python
import json
from panther_base_helpers import deep_get
ALLOWED_ORG_REPO_PAIRS = ["org/repo", "allowed-org-example/allowed-repo-example"]
def policy(resource):
if isinstance(resource.get("AssumeRolePolicyDocument"), str):
policy_document = json.loads(resource.get("AssumeRolePolicyDocument", {}))
else:
policy_document = resource.get("AssumeRolePolicyDocument", {})
assume_role_policy = policy_document.get("Statement", [])
for statement in assume_role_policy:
if (
statement.get("Effect") != "Allow"
or statement.get("Action") != "sts:AssumeRoleWithWebIdentity"
):
continue
principal = deep_get(statement, "Principal", "Federated")
audience = deep_get(
statement, "Condition", "StringEquals", "token.actions.githubusercontent.com:aud"
)
subject = deep_get(
statement,
"Condition",
"StringLike",
"token.actions.githubusercontent.com:sub",
default="",
) or deep_get(
statement,
"Condition",
"StringEquals",
"token.actions.githubusercontent.com:sub",
default="",
)
if subject.startswith("repo:"):
if any(
[
"oidc-provider/token.actions.githubusercontent.com" not in principal,
audience != "sts.amazonaws.com",
(
"*" in subject
and not any(
subject.startswith(f"repo:{org_repo}:*")
for org_repo in ALLOWED_ORG_REPO_PAIRS
)
),
]
):
return False
else:
if "oidc-provider/token.actions.githubusercontent.com" in principal:
return False
return True
The parser cannot express this rule's logic as a field filter; the imperative Python above is the detection.