Chaptive / scripts /create_iam_role.py
Jing997's picture
change app name
497bf08
"""Utility script to provision S3 read-only credentials for the Streamlit app.
This helper creates (or reuses) an IAM user and optional role that have
`s3:ListBucket` / `s3:GetObject` access to the `chaptive-rag` bucket. It then
prints the access key and secret so they can be set as environment variables for the Streamlit app.
Usage:
```bash
python streamlit/scripts/create_iam_role.py \
--entity-name streamlit-cache-reader \
--bucket chaptive-rag
```
The AWS account credentials used to run this script must already have IAM
permissions to create users/roles, attach policies, and create access keys.
"""
from __future__ import annotations
import argparse
import json
import sys
from typing import Any, Dict, Optional
import boto3
from botocore.exceptions import ClientError
S3_POLICY_NAME = "StreamlitS3ReadOnly"
DEFAULT_ENTITY_NAME = "streamlit-cache-reader"
DEFAULT_BUCKET = "chaptly-rag"
def _build_policy(bucket: str) -> Dict[str, Any]:
resource_arn = f"arn:aws:s3:::{bucket}"
object_arn = f"{resource_arn}/*"
return {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": resource_arn,
},
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": object_arn,
},
],
}
def ensure_user(iam, user_name: str) -> None:
try:
iam.get_user(UserName=user_name)
print(f"Reusing IAM user '{user_name}'.")
except ClientError as exc:
if exc.response["Error"]["Code"] == "NoSuchEntity":
iam.create_user(UserName=user_name)
print(f"Created IAM user '{user_name}'.")
else:
raise
def attach_inline_policy(iam, user_name: str, bucket: str) -> None:
policy_doc = json.dumps(_build_policy(bucket))
iam.put_user_policy(UserName=user_name, PolicyName=S3_POLICY_NAME, PolicyDocument=policy_doc)
print(f"Attached inline S3 read policy '{S3_POLICY_NAME}' to user '{user_name}'.")
def create_access_key(iam, user_name: str) -> Dict[str, str]:
response = iam.create_access_key(UserName=user_name)
access_key = response["AccessKey"]
return {
"AWS_ACCESS_KEY_ID": access_key["AccessKeyId"],
"AWS_SECRET_ACCESS_KEY": access_key["SecretAccessKey"],
}
def ensure_role(iam, role_name: str, bucket: str, assume_policy: Dict[str, Any]) -> None:
try:
iam.get_role(RoleName=role_name)
print(f"Reusing IAM role '{role_name}'.")
except ClientError as exc:
if exc.response["Error"]["Code"] == "NoSuchEntity":
iam.create_role(RoleName=role_name, AssumeRolePolicyDocument=json.dumps(assume_policy))
print(f"Created IAM role '{role_name}'.")
else:
raise
policy_doc = json.dumps(_build_policy(bucket))
iam.put_role_policy(RoleName=role_name, PolicyName=S3_POLICY_NAME, PolicyDocument=policy_doc)
print(f"Attached inline S3 read policy '{S3_POLICY_NAME}' to role '{role_name}'.")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Provision S3 read-only IAM credentials for Streamlit.")
parser.add_argument("--entity-name", default=DEFAULT_ENTITY_NAME, help="IAM user/role name to create or reuse.")
parser.add_argument("--bucket", default=DEFAULT_BUCKET, help="S3 bucket name (default: chaptive-rag).")
parser.add_argument(
"--create-role",
action="store_true",
help="Also create an IAM role with the same name and inline policy (optional).",
)
parser.add_argument(
"--role-trust",
default=None,
help="Path to a JSON file describing the assume-role trust policy (required if --create-role).",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
iam = boto3.client("iam")
ensure_user(iam, args.entity_name)
attach_inline_policy(iam, args.entity_name, args.bucket)
credentials = create_access_key(iam, args.entity_name)
print("\nAdd the following values to your Streamlit secrets:")
for key, value in credentials.items():
print(f"{key} = {value}")
print("AWS_REGION = ap-southeast-1")
print(f"CHAPTIVE_S3_BUCKET = {args.bucket}")
if args.create_role:
if not args.role_trust:
raise SystemExit("--role-trust JSON file is required when --create-role is set.")
with open(args.role_trust, "r", encoding="utf-8") as handle:
assume_policy = json.load(handle)
ensure_role(iam, args.entity_name, args.bucket, assume_policy)
print(
"\nIAM role created. Attach this role to an EC2/Lambda/Streamlit Cloud runner and expose temporary credentials via environment variables."
)
if __name__ == "__main__":
try:
main()
except ClientError as exc:
print(f"AWS error: {exc}")
sys.exit(1)
except KeyboardInterrupt:
print("Aborted by user.")
sys.exit(130)