Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions infrastructure/iam/apply.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
#
# apply.sh — Apply all IAM policies in this directory to their matching roles.
#
# Each JSON file in this directory is treated as a role policy document. The
# filename (minus .json) is BOTH the target IAM role name AND the inline
# policy name. This keeps the mapping trivial: one file == one role == one
# policy. If you need multiple policies per role, put them in multiple files
# and accept the duplicate role name.
#
# This is intentionally low-ceremony — no CloudFormation, no Terraform. For
# a 5-module infra-light project, a flat JSON directory + idempotent apply
# script is the right amount of rigor. If the blast radius grows, migrate
# to CloudFormation/Terraform.
#
# Usage:
# ./infrastructure/iam/apply.sh # apply every policy
# ./infrastructure/iam/apply.sh github-actions-lambda-deploy # one role
# ./infrastructure/iam/apply.sh --dry-run # print planned commands
#
# Prerequisites:
# - AWS CLI configured with iam:PutRolePolicy on the target roles
# - The target IAM roles already exist (this script only updates inline
# policies; it does NOT create the roles themselves, because the trust
# policies differ and are outside the scope of a simple flat-file
# approach)

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REGION="${AWS_REGION:-us-east-1}"

DRY_RUN=0
TARGET_ROLE=""

for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=1 ;;
-h|--help)
grep '^#' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*) TARGET_ROLE="$arg" ;;
esac
done

apply_one() {
local file="$1"
local role
role="$(basename "$file" .json)"
local policy_name="${role}-policy"

# Validate JSON locally before shipping it to IAM
if ! python3 -c "import json; json.load(open('$file'))" 2>/dev/null; then
echo "ERROR: $file is not valid JSON — skipping" >&2
return 1
fi

echo "Applying $file -> role=$role policy=$policy_name"
if [ "$DRY_RUN" = 1 ]; then
echo " [dry-run] aws iam put-role-policy --role-name $role --policy-name $policy_name --policy-document file://$file --region $REGION"
return 0
fi

aws iam put-role-policy \
--role-name "$role" \
--policy-name "$policy_name" \
--policy-document "file://$file" \
--region "$REGION"
echo " OK"
}

cd "$SCRIPT_DIR"

if [ -n "$TARGET_ROLE" ]; then
file="${TARGET_ROLE}.json"
if [ ! -f "$file" ]; then
echo "ERROR: $file not found in $SCRIPT_DIR" >&2
exit 1
fi
apply_one "$file"
else
shopt -s nullglob
files=( *.json )
if [ ${#files[@]} -eq 0 ]; then
echo "No .json policy files found in $SCRIPT_DIR"
exit 0
fi
for file in "${files[@]}"; do
apply_one "$file"
done
fi
70 changes: 70 additions & 0 deletions infrastructure/iam/github-actions-lambda-deploy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRAuth",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "ECRPush",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:BatchGetImage",
"ecr:DescribeImages",
"ecr:DescribeRepositories"
],
"Resource": [
"arn:aws:ecr:us-east-1:711398986525:repository/alpha-engine-research-runner",
"arn:aws:ecr:us-east-1:711398986525:repository/alpha-engine-research-alerts",
"arn:aws:ecr:us-east-1:711398986525:repository/alpha-engine-predictor",
"arn:aws:ecr:us-east-1:711398986525:repository/alpha-engine-data-collector",
"arn:aws:ecr:us-east-1:711398986525:repository/alpha-engine-health-check"
]
},
{
"Sid": "LambdaUpdate",
"Effect": "Allow",
"Action": [
"lambda:UpdateFunctionCode",
"lambda:UpdateFunctionConfiguration",
"lambda:GetFunction",
"lambda:GetFunctionConfiguration",
"lambda:PublishVersion",
"lambda:UpdateAlias"
],
"Resource": [
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-research-runner",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-research-alerts",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-predictor-inference",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-data-collector",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-health-check"
]
},
{
"Sid": "LambdaInvokeCanary",
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-research-runner",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-research-runner:*",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-research-alerts",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-research-alerts:*",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-predictor-inference",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-predictor-inference:*",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-data-collector",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-data-collector:*",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-health-check",
"arn:aws:lambda:us-east-1:711398986525:function:alpha-engine-health-check:*"
]
}
]
}
Loading