77#
88# 1. Verifies the AWS CLI is available and reads the account ID
99# 2. Creates the openerrata-ci IAM user (skip if exists)
10- # 3. Attaches a least-privilege inline policy scoped to the resources
10+ # 3. Creates or updates a customer managed policy scoped to the resources
1111# Pulumi manages (S3 buckets, RDS instances, EC2 security groups,
12- # IAM users for blob-storage writers)
12+ # IAM users for blob-storage writers) and attaches it
1313# 4. Creates an access key (skip if one already exists)
1414# 5. Prints credentials as JSON to stdout
1515#
3232
3333set -euo pipefail
3434
35- SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) "
3635IAM_USER=" openerrata-ci"
3736POLICY_NAME=" openerrata-ci-pulumi-deploy"
3837
@@ -53,6 +52,8 @@ CALLER_IDENTITY="$(aws sts get-caller-identity --output json)"
5352ACCOUNT_ID=" $( echo " $CALLER_IDENTITY " | python3 -c " import sys,json; print(json.load(sys.stdin)['Account'])" ) "
5453echo " Account ID: ${ACCOUNT_ID} " >&2
5554
55+ POLICY_ARN=" arn:aws:iam::${ACCOUNT_ID} :policy/${POLICY_NAME} "
56+
5657# ── 2. Create IAM user ────────────────────────────────────────────────
5758
5859if aws iam get-user --user-name " $IAM_USER " & > /dev/null; then
6667 Key=purpose,Value=ci-deploy
6768fi
6869
69- # ── 3. Attach inline policy ───────────────────────────────────────────
70+ # ── 3. Create or update managed policy and attach ────────────────────
71+
72+ # Uses a managed policy (6144-byte limit) instead of an inline policy
73+ # (2048-byte limit) to accommodate the full set of resource ARNs that
74+ # AWS validates during RDS and EC2 operations.
7075
71- echo " Attaching inline policy ${POLICY_NAME} ..." >&2
76+ echo " Preparing managed policy ${POLICY_NAME} ..." >&2
7277
73- # The policy document uses $ACCOUNT_ID from the shell; all other $ signs
74- # in ARN patterns are literal and must be escaped in the heredoc.
78+ # The policy document uses $ACCOUNT_ID from the shell.
7579POLICY_DOCUMENT=" $( cat << EOF
7680{
7781 "Version": "2012-10-17",
@@ -172,7 +176,12 @@ POLICY_DOCUMENT="$(cat <<EOF
172176 "rds:AddTagsToResource",
173177 "rds:RemoveTagsFromResource"
174178 ],
175- "Resource": "arn:aws:rds:*:${ACCOUNT_ID} :db:openerrata-*"
179+ "Resource": [
180+ "arn:aws:rds:*:${ACCOUNT_ID} :db:openerrata-*",
181+ "arn:aws:rds:*:${ACCOUNT_ID} :subgrp:*",
182+ "arn:aws:rds:*:${ACCOUNT_ID} :pg:*",
183+ "arn:aws:rds:*:${ACCOUNT_ID} :og:*"
184+ ]
176185 },
177186 {
178187 "Sid": "RdsMutateSubnetGroups",
@@ -191,12 +200,63 @@ POLICY_DOCUMENT="$(cat <<EOF
191200EOF
192201) "
193202
194- aws iam put-user-policy \
195- --user-name " $IAM_USER " \
196- --policy-name " $POLICY_NAME " \
197- --policy-document " $POLICY_DOCUMENT "
203+ # Remove the old inline policy if it exists (migration from inline to managed).
204+ if aws iam get-user-policy --user-name " $IAM_USER " --policy-name " $POLICY_NAME " & > /dev/null; then
205+ echo " Removing legacy inline policy ..." >&2
206+ aws iam delete-user-policy --user-name " $IAM_USER " --policy-name " $POLICY_NAME "
207+ fi
208+
209+ # Create or update the managed policy.
210+ if aws iam get-policy --policy-arn " $POLICY_ARN " & > /dev/null; then
211+ echo " Updating existing managed policy ..." >&2
212+
213+ # Managed policies have a 5-version limit. Delete the oldest non-default
214+ # version before creating a new one to stay under the cap.
215+ OLD_VERSIONS=" $( aws iam list-policy-versions --policy-arn " $POLICY_ARN " --output json \
216+ | python3 -c "
217+ import sys, json
218+ versions = json.load(sys.stdin)['Versions']
219+ non_default = [v['VersionId'] for v in versions if not v['IsDefaultVersion']]
220+ non_default.sort()
221+ print('\n'.join(non_default))
222+ " ) "
223+
224+ VERSION_COUNT=" $( aws iam list-policy-versions --policy-arn " $POLICY_ARN " --output json \
225+ | python3 -c " import sys, json; print(len(json.load(sys.stdin)['Versions']))" ) "
226+
227+ if [ " $VERSION_COUNT " -ge 5 ] && [ -n " $OLD_VERSIONS " ]; then
228+ OLDEST=" $( echo " $OLD_VERSIONS " | head -1) "
229+ echo " Deleting oldest policy version ${OLDEST} to make room ..." >&2
230+ aws iam delete-policy-version --policy-arn " $POLICY_ARN " --version-id " $OLDEST "
231+ fi
232+
233+ aws iam create-policy-version \
234+ --policy-arn " $POLICY_ARN " \
235+ --policy-document " $POLICY_DOCUMENT " \
236+ --set-as-default > /dev/null
237+ else
238+ echo " Creating managed policy ..." >&2
239+ aws iam create-policy \
240+ --policy-name " $POLICY_NAME " \
241+ --policy-document " $POLICY_DOCUMENT " \
242+ --description " Least-privilege policy for openerrata CI/CD Pulumi deployments" \
243+ --tags Key=managedBy,Value=bootstrap Key=purpose,Value=ci-deploy > /dev/null
244+ fi
245+
246+ # Ensure the policy is attached to the user.
247+ if ! aws iam list-attached-user-policies --user-name " $IAM_USER " --output json \
248+ | python3 -c "
249+ import sys, json
250+ policies = json.load(sys.stdin)['AttachedPolicies']
251+ sys.exit(0 if any(p['PolicyArn'] == '$POLICY_ARN ' for p in policies) else 1)
252+ " ; then
253+ echo " Attaching managed policy to ${IAM_USER} ..." >&2
254+ aws iam attach-user-policy --user-name " $IAM_USER " --policy-arn " $POLICY_ARN "
255+ else
256+ echo " Managed policy already attached to ${IAM_USER} ." >&2
257+ fi
198258
199- echo " Policy ${POLICY_NAME} attached ." >&2
259+ echo " Policy ${POLICY_NAME} ready ." >&2
200260
201261# ── 4. Create access key ──────────────────────────────────────────────
202262
0 commit comments