Skip to content

Commit fea21ef

Browse files
committed
adding python file
1 parent 829915b commit fea21ef

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed

devolv_oidc_onboard.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
import time
5+
import argparse
6+
7+
import boto3
8+
from botocore.exceptions import ClientError
9+
10+
OIDC_URL = "https://token.actions.githubusercontent.com"
11+
OIDC_AUDIENCE = "sts.amazonaws.com"
12+
THUMBPRINT = "6938fd4d98bab03faadb97b34396831e3780aea1"
13+
14+
def wait_for_role(iam_client, role_name, max_attempts=10, delay=2):
15+
waiter = iam_client.get_waiter('role_exists')
16+
try:
17+
waiter.wait(RoleName=role_name, WaiterConfig={'Delay': delay, 'MaxAttempts': max_attempts})
18+
return True
19+
except ClientError as e:
20+
print(f"Error waiting for role: {e}")
21+
return False
22+
23+
def wait_for_policy(iam_client, policy_arn, max_attempts=10, delay=2):
24+
for _ in range(max_attempts):
25+
try:
26+
iam_client.get_policy(PolicyArn=policy_arn)
27+
return True
28+
except ClientError as e:
29+
if e.response['Error']['Code'] == 'NoSuchEntity':
30+
time.sleep(delay)
31+
continue
32+
raise
33+
return False
34+
35+
def ensure_github_oidc_provider_exists(iam_client, account_id, org_name):
36+
provider_arn = f"arn:aws:iam::{account_id}:oidc-provider/token.actions.githubusercontent.com"
37+
try:
38+
existing_providers = iam_client.list_open_id_connect_providers()['OpenIDConnectProviderList']
39+
if any(p['Arn'] == provider_arn for p in existing_providers):
40+
print(f"✅ OIDC provider for GitHub already exists (used for {org_name})")
41+
return
42+
print(f"🔧 Creating GitHub OIDC provider for {org_name}...")
43+
iam_client.create_open_id_connect_provider(
44+
Url=OIDC_URL,
45+
ClientIDList=[OIDC_AUDIENCE],
46+
ThumbprintList=[THUMBPRINT]
47+
)
48+
print(f"✅ GitHub OIDC provider created (used for {org_name})")
49+
except ClientError as e:
50+
print(f"❌ Failed to ensure GitHub OIDC provider: {e}")
51+
raise
52+
53+
def main():
54+
parser = argparse.ArgumentParser(description="Set up GitHub OIDC role + policy")
55+
parser.add_argument("--github-org", required=True, help="GitHub organization name")
56+
args = parser.parse_args()
57+
58+
org_name = args.github_org
59+
role_name = f"{org_name}-DevolvRole"
60+
policy_name = f"{org_name}-DevolvPolicy"
61+
62+
iam_client = boto3.client('iam')
63+
sts_client = boto3.client('sts')
64+
65+
try:
66+
account_id = sts_client.get_caller_identity()['Account']
67+
except ClientError as e:
68+
print(f"❌ Error getting account ID: {e}")
69+
return
70+
71+
ensure_github_oidc_provider_exists(iam_client, account_id, org_name)
72+
73+
trust_policy = {
74+
"Version": "2012-10-17",
75+
"Statement": [{
76+
"Effect": "Allow",
77+
"Principal": {
78+
"Federated": f"arn:aws:iam::{account_id}:oidc-provider/token.actions.githubusercontent.com"
79+
},
80+
"Action": "sts:AssumeRoleWithWebIdentity",
81+
"Condition": {
82+
"StringEquals": {
83+
"token.actions.githubusercontent.com:aud": OIDC_AUDIENCE
84+
},
85+
"StringLike": {
86+
"token.actions.githubusercontent.com:sub": f"repo:{org_name}/*"
87+
}
88+
}
89+
}]
90+
}
91+
92+
try:
93+
iam_client.create_role(
94+
RoleName=role_name,
95+
AssumeRolePolicyDocument=json.dumps(trust_policy)
96+
)
97+
if wait_for_role(iam_client, role_name):
98+
print(f"✅ Role created: {role_name}")
99+
else:
100+
print("❌ Failed to confirm role creation")
101+
return
102+
except ClientError as e:
103+
if e.response['Error']['Code'] == 'EntityAlreadyExists':
104+
print(f"ℹ️ Role {role_name} already exists")
105+
else:
106+
print(f"❌ Error creating role: {e}")
107+
return
108+
109+
policy_document = {
110+
"Version": "2012-10-17",
111+
"Statement": [
112+
{
113+
"Sid": "AllowIAMPolicyDriftActions",
114+
"Effect": "Allow",
115+
"Action": [
116+
"iam:GetPolicy",
117+
"iam:GetPolicyVersion",
118+
"iam:ListPolicyVersions",
119+
"iam:CreatePolicyVersion",
120+
"iam:DeletePolicyVersion"
121+
],
122+
"Resource": "*"
123+
},
124+
{
125+
"Sid": "AllowSTSAssumeRoleForDevolvCIRole",
126+
"Effect": "Allow",
127+
"Action": [
128+
"sts:AssumeRole",
129+
"sts:TagSession"
130+
],
131+
"Resource": f"arn:aws:iam::{account_id}:role/{role_name}"
132+
}
133+
]
134+
}
135+
136+
try:
137+
response = iam_client.create_policy(
138+
PolicyName=policy_name,
139+
PolicyDocument=json.dumps(policy_document)
140+
)
141+
policy_arn = response['Policy']['Arn']
142+
if wait_for_policy(iam_client, policy_arn):
143+
print(f"✅ Policy created: {policy_name}")
144+
else:
145+
print("❌ Failed to confirm policy creation")
146+
return
147+
except ClientError as e:
148+
if e.response['Error']['Code'] == 'EntityAlreadyExists':
149+
policy_arn = f"arn:aws:iam::{account_id}:policy/{policy_name}"
150+
print(f"ℹ️ Policy {policy_name} already exists")
151+
else:
152+
print(f"❌ Error creating policy: {e}")
153+
return
154+
155+
try:
156+
iam_client.attach_role_policy(RoleName=role_name, PolicyArn=policy_arn)
157+
print(f"✅ Policy attached to role: {role_name}")
158+
except ClientError as e:
159+
print(f"❌ Error attaching policy to role: {e}")
160+
return
161+
162+
print("\n🎉 Setup complete!")
163+
print(f"🔑 Role: arn:aws:iam::{account_id}:role/{role_name}")
164+
print(f"🔑 Policy: arn:aws:iam::{account_id}:policy/{policy_name}")
165+
print(f"🔑 OIDC provider: token.actions.githubusercontent.com (used for {org_name})")
166+
print("\n👉 Add this to your GitHub Actions workflow:\n")
167+
print("permissions:")
168+
print(" contents: write")
169+
print(" issues: write")
170+
print(" pull-requests: write\n")
171+
print("- uses: aws-actions/configure-aws-credentials@v2")
172+
print(" with:")
173+
print(f" role-to-assume: arn:aws:iam::{account_id}:role/{role_name}")
174+
print(" aws-region: <your-region> # Replace with your AWS region (e.g. us-east-1)")
175+
176+
if __name__ == "__main__":
177+
main()

0 commit comments

Comments
 (0)