Skip to content

Commit faebb96

Browse files
bcpentabcpentaarielkr256
authored
Github Actions OIDC IAM Role Trust Relation (panther-labs#1456)
Co-authored-by: bcpenta <[email protected]> Co-authored-by: Ariel Ropek <[email protected]> Co-authored-by: Ariel <[email protected]>
1 parent ec1fa93 commit faebb96

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed

packs/aws.yml

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ PackDefinition:
7070
- AWS.IAM.PolicyModified
7171
- AWS.IAM.User.MFA
7272
- AWS.IAMUser.ReconAccessDenied
73+
- AWS.IAM.Role.GitHubActionsTrust
7374
- AWS.Password.Unused
7475
- AWS.PasswordPolicy.ComplexityGuidelines
7576
- AWS.PasswordPolicy.PasswordAgeLimit
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from panther_base_helpers import deep_get
2+
3+
4+
def policy(resource):
5+
assume_role_policy = deep_get(resource, "AssumeRolePolicyDocument", "Statement", default=[])
6+
is_valid = False
7+
8+
for statement in assume_role_policy:
9+
if statement.get(
10+
"Effect"
11+
) != "Allow" or "sts:AssumeRoleWithWebIdentity" not in statement.get("Action", []):
12+
continue
13+
14+
principal = deep_get(statement, "Principal", "Federated")
15+
if not principal or principal == "*":
16+
return False
17+
if "oidc-provider/token.actions.githubusercontent.com" not in principal:
18+
continue
19+
20+
# Validate the conditions only if the Principal is valid for GitHub Actions
21+
conditions = statement.get("Condition", {})
22+
audience = deep_get(conditions, "StringEquals", "token.actions.githubusercontent.com:aud")
23+
subject = deep_get(
24+
conditions, "StringLike", "token.actions.githubusercontent.com:sub", default=""
25+
) or deep_get(
26+
conditions, "StringEquals", "token.actions.githubusercontent.com:sub", default=""
27+
)
28+
29+
if (
30+
audience != "sts.amazonaws.com"
31+
or not subject.startswith("repo:")
32+
or ("*" in subject and not subject.startswith("repo:org/repo:*"))
33+
):
34+
return False
35+
36+
is_valid = True # Mark as valid if all checks pass
37+
38+
return is_valid
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
AnalysisType: policy
2+
Filename: aws_iam_role_github_actions_trust.py
3+
PolicyID: "AWS.IAM.Role.GitHubActionsTrust"
4+
DisplayName: "AWS IAM Role Trust Relationship for GitHub Actions"
5+
Enabled: true
6+
ResourceTypes:
7+
- AWS.IAM.Role
8+
Tags:
9+
- AWS
10+
- GitHub Actions
11+
- Identity & Access Management
12+
Severity: High
13+
Description: >
14+
This policy ensures that IAM roles used with GitHub Actions are securely configured to prevent unauthorized access to AWS resources.
15+
It validates trust relationships by checking for proper audience (aud) restrictions, ensuring it is set to sts.amazonaws.com, and subject (sub) conditions,
16+
confirming they are scoped to specific repositories or environments. Misconfigurations, such as overly permissive wildcards or missing conditions,
17+
can allow unauthorized repositories to assume roles, leading to potential data breaches or compliance violations.
18+
By enforcing these checks, the policy mitigates risks of exploitation, enhances security posture, and protects critical AWS resources from external threats.
19+
Runbook: >
20+
To fix roles flagged by this policy:
21+
1. Update the trust relationship of the flagged IAM role in the AWS Management Console or CLI.
22+
2. Add a Condition block with 'StringLike' or 'StringEquals' for 'token.actions.githubusercontent.com:sub'.
23+
3. Ensure the audience is set to 'sts.amazonaws.com'.
24+
4. Avoid overly permissive wildcards in the sub condition.
25+
Reference: >
26+
- https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html
27+
- https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers
28+
- https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
29+
Tests:
30+
- Name: Valid GitHub Actions Trust Relationship
31+
ExpectedResult: true
32+
Resource:
33+
{
34+
"AssumeRolePolicyDocument": {
35+
"Version": "2012-10-17",
36+
"Statement": [
37+
{
38+
"Effect": "Allow",
39+
"Action": "sts:AssumeRoleWithWebIdentity",
40+
"Principal": {
41+
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
42+
},
43+
"Condition": {
44+
"StringEquals": {
45+
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
46+
},
47+
"StringLike": {
48+
"token.actions.githubusercontent.com:sub": "repo:org/repo:*"
49+
}
50+
}
51+
}
52+
]
53+
}
54+
}
55+
56+
- Name: Missing Audience Condition
57+
ExpectedResult: false
58+
Resource:
59+
{
60+
"AssumeRolePolicyDocument": {
61+
"Version": "2012-10-17",
62+
"Statement": [
63+
{
64+
"Effect": "Allow",
65+
"Action": "sts:AssumeRoleWithWebIdentity",
66+
"Principal": {
67+
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
68+
},
69+
"Condition": {
70+
"StringLike": {
71+
"token.actions.githubusercontent.com:sub": "repo:org/repo:*"
72+
}
73+
}
74+
}
75+
]
76+
}
77+
}
78+
79+
- Name: Missing Subject Restriction
80+
ExpectedResult: false
81+
Resource:
82+
{
83+
"AssumeRolePolicyDocument": {
84+
"Version": "2012-10-17",
85+
"Statement": [
86+
{
87+
"Effect": "Allow",
88+
"Action": "sts:AssumeRoleWithWebIdentity",
89+
"Principal": {
90+
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
91+
},
92+
"Condition": {
93+
"StringEquals": {
94+
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
95+
}
96+
}
97+
}
98+
]
99+
}
100+
}
101+
102+
- Name: Overly Permissive Wildcard in Subject
103+
ExpectedResult: false
104+
Resource:
105+
{
106+
"AssumeRolePolicyDocument": {
107+
"Version": "2012-10-17",
108+
"Statement": [
109+
{
110+
"Effect": "Allow",
111+
"Action": "sts:AssumeRoleWithWebIdentity",
112+
"Principal": {
113+
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
114+
},
115+
"Condition": {
116+
"StringEquals": {
117+
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
118+
},
119+
"StringLike": {
120+
"token.actions.githubusercontent.com:sub": "*"
121+
}
122+
}
123+
}
124+
]
125+
}
126+
}
127+
128+
- Name: Valid Subject Restriction with Specific Environment
129+
ExpectedResult: true
130+
Resource:
131+
{
132+
"AssumeRolePolicyDocument": {
133+
"Version": "2012-10-17",
134+
"Statement": [
135+
{
136+
"Effect": "Allow",
137+
"Action": "sts:AssumeRoleWithWebIdentity",
138+
"Principal": {
139+
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
140+
},
141+
"Condition": {
142+
"StringEquals": {
143+
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
144+
"token.actions.githubusercontent.com:sub": "repo:org/repo:environment:prod"
145+
}
146+
}
147+
}
148+
]
149+
}
150+
}
151+
152+
- Name: Invalid Principal as Wildcard
153+
ExpectedResult: false
154+
Resource:
155+
{
156+
"AssumeRolePolicyDocument": {
157+
"Version": "2012-10-17",
158+
"Statement": [
159+
{
160+
"Effect": "Allow",
161+
"Action": "sts:AssumeRoleWithWebIdentity",
162+
"Principal": {
163+
"Federated": "*"
164+
},
165+
"Condition": {
166+
"StringEquals": {
167+
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
168+
},
169+
"StringLike": {
170+
"token.actions.githubusercontent.com:sub": "repo:org/repo:*"
171+
}
172+
}
173+
}
174+
]
175+
}
176+
}
177+
178+
- Name: Non-GitHub OIDC Principal
179+
ExpectedResult: false
180+
Resource:
181+
{
182+
"AssumeRolePolicyDocument": {
183+
"Version": "2012-10-17",
184+
"Statement": [
185+
{
186+
"Effect": "Allow",
187+
"Action": "sts:AssumeRoleWithWebIdentity",
188+
"Principal": {
189+
"Federated": "arn:aws:iam::123456789012:oidc-provider/accounts.google.com"
190+
},
191+
"Condition": {
192+
"StringEquals": {
193+
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
194+
},
195+
"StringLike": {
196+
"token.actions.githubusercontent.com:sub": "repo:org/repo:*"
197+
}
198+
}
199+
}
200+
]
201+
}
202+
}

0 commit comments

Comments
 (0)