Skip to content

Commit 3eee8cd

Browse files
authored
Merge pull request #212 from Daylily-Informatics/refactor/use-iam-groups
Refactor/use iam groups
2 parents 9699208 + 821781b commit 3eee8cd

10 files changed

+608
-78
lines changed

README.md

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,29 @@ From the `Iam -> Users` console, create a new user.
102102
- Review the confiirmation page, and click `Create user`.
103103
- On the next page, capture the `Console sign-in URL`, `username`, and `password`. You will need these to log in as the `daylily-service` user.
104104

105-
### Attach Permissiong & Policies To The `daylily-service` User
105+
### Attach Permissions & Policies via an IAM Group (recommended)
106106
_still as the admin user_
107107

108+
Daylily now prefers using an IAM *group* (default: `daylily-ephemeral-cluster`) and
109+
making the `daylily-service` user (and any other operators) members of that group.
110+
108111
#### Permissions
109112

110-
- Navigate to the `IAM -> Users` console, click on the `daylily-service` user.
111-
- Click on the `Add permissions` button, then select `Add permission`, `Attach policies directly`.
112-
- Search for `AmazonQDeveloperAccess` , select and add.
113-
- Search for `AmazonEC2SpotFleetAutoscaleRole`, select and add.
114-
- Search for `AmazonEC2SpotFleetTaggingRole`, select and add.
113+
- Navigate to `IAM -> User groups` and create a group named `daylily-ephemeral-cluster`.
114+
- Attach the following AWS managed policies to the **group**:
115+
- `AmazonQDeveloperAccess`
116+
- `AmazonEC2SpotFleetAutoscaleRole`
117+
- `AmazonEC2SpotFleetTaggingRole`
118+
- Add the `daylily-service` user to the group.
119+
120+
> Legacy note: attaching policies directly to the user still works, but is discouraged.
121+
122+
Migration (recommended):
123+
- Ensure `daylily-ephemeral-cluster` group exists and has the required policies attached.
124+
- Add `daylily-service` (and any other operators) to the group.
125+
- Optionally remove old direct user attachments once verified:
126+
- Managed policies: `aws iam list-attached-user-policies --user-name daylily-service` then `aws iam detach-user-policy ...`
127+
- Inline policies: `aws iam list-user-policies --user-name daylily-service` then `aws iam delete-user-policy ...`
115128

116129
#### Create Service Linked Role `VERY IMPORTANT`
117130

@@ -131,8 +144,8 @@ aws iam create-service-linked-role --aws-service-name spot.amazonaws.com
131144

132145
#### Inline Policy
133146
__**note:**__ [please consult the parallel cluster docs for fine grained permissions control, the below is a broad approach](https://docs.aws.amazon.com/parallelcluster/latest/ug/iam-roles-in-parallelcluster-v3.html).
134-
- Navigate to the `IAM -> Users` console, click on the `daylily-service` user.
135-
- Click on the `Add permissions` button, then select `create inline policy`.
147+
- Navigate to the `IAM -> User groups` console, click on the `daylily-ephemeral-cluster` group.
148+
- Click on `Add permissions` and select `Create inline policy`.
136149
- Click on the `JSON` bubble button.
137150
- Delete the auto-populated json in the editor window, and paste this json into the editor (replace 3 instances of <AWS_ACCOUNT_ID> with your new account number, an integer found in the upper right dropdown).
138151

@@ -141,6 +154,10 @@ __**note:**__ [please consult the parallel cluster docs for fine grained permiss
141154
- `click next`
142155
- Name the policy `daylily-service-cluster-policy` (not formally mandatory, but advised to bypass various warnings in future steps), then click `Create policy`.
143156

157+
Alternative (preferred): use the provided admin scripts to create/attach the Daylily policies to your group:
158+
- `bin/admin/daylily_ephemeral_cluster_bootstrap_global.sh --user daylily-service --group daylily-ephemeral-cluster`
159+
- `bin/admin/daylily_ephemeral_cluster_bootstrap_region.sh --region <REGION> --user daylily-service --group daylily-ephemeral-cluster`
160+
144161

145162
### Additional AWS Considerations (also will need _admin_ intervention)
146163
#### Quotas
@@ -1353,15 +1370,24 @@ All tools involved in `daylily-ephemeral-cluster` can be managed in such a way t
13531370
### One Time, Create Policy
13541371
```bash
13551372
export AWS_PROFILE=YOURADMINUSERPROFILE
1356-
aws iam create-policy \\n --policy-name DaylilyCostRead \\n --policy-document file://config/aws/generate_cluster_report.json
1357-
export AWS_PROFILE=<daylily-service>
1373+
aws iam create-policy \
1374+
--policy-name DaylilyCostRead \
1375+
--policy-document file://config/aws/generate_cluster_report.json
13581376
```
13591377
1360-
### Per User (ie: daylily-service)
1378+
### Per Group / User membership (recommended)
13611379
```bash
13621380
export AWS_PROFILE=YOURADMINUSERPROFILE
1363-
aws iam attach-user-policy \\n --user-name daylily-service \\n --policy-arn arn:aws:iam::108782052779:policy/DaylilyCostRead
1364-
export AWS_PROFILE=<daylily-service>
1381+
1382+
GROUP_NAME=daylily-ephemeral-cluster
1383+
USER_NAME=daylily-service
1384+
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
1385+
1386+
aws iam create-group --group-name "$GROUP_NAME" >/dev/null 2>&1 || true
1387+
aws iam attach-group-policy \
1388+
--group-name "$GROUP_NAME" \
1389+
--policy-arn "arn:aws:iam::${ACCOUNT_ID}:policy/DaylilyCostRead"
1390+
aws iam add-user-to-group --user-name "$USER_NAME" --group-name "$GROUP_NAME"
13651391
```
13661392
13671393
@@ -1370,4 +1396,4 @@ export AWS_PROFILE=<daylily-service>
13701396
13711397
_named in honor of Margaret Oakley Dahoff_
13721398
1373-
1399+
X

bin/admin/daylily_ephemeral_cluster_bootstrap_global.sh

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,31 @@ usage() {
66
Bootstrap global IAM policy for Daylily ephemeral cluster workflows.
77
88
This script creates or updates the global Daylily managed policy and optionally
9-
attaches it to a specified IAM user. Run this once per AWS account, or again if
9+
attaches it to a specified IAM *group* (recommended) and ensures the target IAM
10+
user is a member of that group. Run this once per AWS account, or again if
1011
updates to the policy are released.
1112
1213
USAGE:
13-
daylily_ephemeral_cluster_bootstrap_global.sh --user USERNAME [--profile PROFILE]
14+
daylily_ephemeral_cluster_bootstrap_global.sh \
15+
--user USERNAME \
16+
[--group GROUP] \
17+
[--profile PROFILE]
1418
1519
OPTIONS:
16-
--user IAM username to attach the Daylily global policy to (required)
20+
--user IAM username to grant access to (required)
21+
--group IAM group to attach the Daylily global policy to (default: daylily-ephemeral-cluster)
1722
--profile AWS CLI profile with admin rights (optional)
1823
USAGE
1924
}
2025

2126
USER_NAME=""
27+
GROUP_NAME="daylily-ephemeral-cluster"
2228
PROFILE="${AWS_PROFILE:-}"
2329

2430
while (( $# )); do
2531
case "$1" in
2632
--user) USER_NAME="${2:-}"; shift 2 ;;
33+
--group) GROUP_NAME="${2:-}"; shift 2 ;;
2734
--profile) PROFILE="${2:-}"; shift 2 ;;
2835
-h|--help) usage; exit 0 ;;
2936
*) echo "Unknown arg: $1" >&2; usage >&2; exit 2 ;;
@@ -32,6 +39,8 @@ done
3239

3340
: "${USER_NAME:?ERR: --user required}"
3441

42+
[[ -n "${GROUP_NAME}" ]] || { echo "ERR: --group cannot be empty" >&2; exit 2; }
43+
3544
AWS=(aws)
3645
[[ -n "$PROFILE" ]] && AWS+=(--profile "$PROFILE")
3746

@@ -117,10 +126,59 @@ create_or_update_policy() {
117126

118127
GLOBAL_ARN="$(create_or_update_policy "${GLOBAL_POLICY_NAME}" "${GLOBAL_POLICY_DOC}")"
119128

120-
>&2 echo "Attaching ${GLOBAL_POLICY_NAME} to user ${USER_NAME}"
121-
"${AWS[@]}" iam attach-user-policy --user-name "${USER_NAME}" --policy-arn "${GLOBAL_ARN}" || true
129+
ensure_user_exists() {
130+
local user="$1"
131+
"${AWS[@]}" iam get-user --user-name "${user}" >/dev/null 2>&1 || {
132+
echo "ERR: IAM user '${user}' not found." >&2
133+
exit 4
134+
}
135+
}
136+
137+
ensure_group_exists() {
138+
local group="$1"
139+
if ! "${AWS[@]}" iam get-group --group-name "${group}" >/dev/null 2>&1; then
140+
>&2 echo "Creating IAM group: ${group}"
141+
"${AWS[@]}" iam create-group --group-name "${group}" >/dev/null
142+
fi
143+
}
144+
145+
ensure_policy_attached_to_group() {
146+
local group="$1" policy_arn="$2"
147+
local attached
148+
attached=$(
149+
"${AWS[@]}" iam list-attached-group-policies --group-name "${group}" \
150+
--query "AttachedPolicies[?PolicyArn=='${policy_arn}'] | length(@)" --output text 2>/dev/null || echo "0"
151+
)
152+
if [[ "${attached}" == "0" ]]; then
153+
>&2 echo "Attaching ${GLOBAL_POLICY_NAME} to group ${group}"
154+
"${AWS[@]}" iam attach-group-policy --group-name "${group}" --policy-arn "${policy_arn}"
155+
else
156+
>&2 echo "${GLOBAL_POLICY_NAME} already attached to group ${group}"
157+
fi
158+
}
159+
160+
ensure_user_in_group() {
161+
local user="$1" group="$2"
162+
local in_group
163+
in_group=$(
164+
"${AWS[@]}" iam list-groups-for-user --user-name "${user}" \
165+
--query "Groups[?GroupName=='${group}'] | length(@)" --output text 2>/dev/null || echo "0"
166+
)
167+
if [[ "${in_group}" == "0" ]]; then
168+
>&2 echo "Adding user ${user} to group ${group}"
169+
"${AWS[@]}" iam add-user-to-group --user-name "${user}" --group-name "${group}"
170+
else
171+
>&2 echo "User ${user} already in group ${group}"
172+
fi
173+
}
174+
175+
ensure_user_exists "${USER_NAME}"
176+
ensure_group_exists "${GROUP_NAME}"
177+
ensure_policy_attached_to_group "${GROUP_NAME}" "${GLOBAL_ARN}"
178+
ensure_user_in_group "${USER_NAME}" "${GROUP_NAME}"
122179

123180
cat <<SUMMARY
124181
✅ Done.
125-
- ${GLOBAL_POLICY_NAME}: ${GLOBAL_ARN} (attached to ${USER_NAME})
182+
- ${GLOBAL_POLICY_NAME}: ${GLOBAL_ARN} (attached to IAM group ${GROUP_NAME})
183+
- IAM user ${USER_NAME} is a member of ${GROUP_NAME}
126184
SUMMARY

bin/admin/daylily_ephemeral_cluster_bootstrap_region.sh

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,34 @@ usage() {
66
Bootstrap region-scoped IAM policies for Daylily ephemeral cluster workflows.
77
88
This script creates or updates the Daylily region policy alongside the
9-
ParallelCluster Lambda adjust policy, and attaches the region policy to a given
10-
user. Run this once per AWS region in which you operate Daylily clusters.
9+
ParallelCluster Lambda adjust policy.
10+
11+
It attaches the region policy to an IAM *group* (recommended) and ensures the
12+
target IAM user is a member of that group. Run this once per AWS region in which
13+
you operate Daylily clusters.
1114
1215
USAGE:
1316
daylily_ephemeral_cluster_bootstrap_region.sh \\
14-
--region REGION --user USERNAME [--profile PROFILE]
17+
--region REGION --user USERNAME [--group GROUP] [--profile PROFILE]
1518
1619
OPTIONS:
1720
--region AWS region to scope the policy to (required)
18-
--user IAM username to attach the Daylily region policy to (required)
21+
--user IAM username to grant access to (required)
22+
--group IAM group to attach the Daylily region policy to (default: daylily-ephemeral-cluster)
1923
--profile AWS CLI profile with admin rights (optional)
2024
USAGE
2125
}
2226

2327
USER_NAME=""
2428
REGION=""
29+
GROUP_NAME="daylily-ephemeral-cluster"
2530
PROFILE="${AWS_PROFILE:-}"
2631

2732
while (( $# )); do
2833
case "$1" in
2934
--user) USER_NAME="${2:-}"; shift 2 ;;
3035
--region) REGION="${2:-}"; shift 2 ;;
36+
--group) GROUP_NAME="${2:-}"; shift 2 ;;
3137
--profile) PROFILE="${2:-}"; shift 2 ;;
3238
-h|--help) usage; exit 0 ;;
3339
*) echo "Unknown arg: $1" >&2; usage >&2; exit 2 ;;
@@ -36,6 +42,7 @@ done
3642

3743
: "${USER_NAME:?ERR: --user required}"
3844
: "${REGION:?ERR: --region required}"
45+
[[ -n "${GROUP_NAME}" ]] || { echo "ERR: --group cannot be empty" >&2; exit 2; }
3946

4047
AWS=(aws)
4148
[[ -n "$PROFILE" ]] && AWS+=(--profile "$PROFILE")
@@ -158,11 +165,60 @@ else
158165
done
159166
fi
160167

161-
echo "Attaching ${REGION_POLICY_NAME} to user ${USER_NAME}"
162-
"${AWS[@]}" iam attach-user-policy --user-name "${USER_NAME}" --policy-arn "${REGION_ARN}" || true
168+
ensure_user_exists() {
169+
local user="$1"
170+
"${AWS[@]}" iam get-user --user-name "${user}" >/dev/null 2>&1 || {
171+
echo "ERR: IAM user '${user}' not found." >&2
172+
exit 4
173+
}
174+
}
175+
176+
ensure_group_exists() {
177+
local group="$1"
178+
if ! "${AWS[@]}" iam get-group --group-name "${group}" >/dev/null 2>&1; then
179+
echo "Creating IAM group: ${group}"
180+
"${AWS[@]}" iam create-group --group-name "${group}" >/dev/null
181+
fi
182+
}
183+
184+
ensure_policy_attached_to_group() {
185+
local group="$1" policy_arn="$2"
186+
local attached
187+
attached=$(
188+
"${AWS[@]}" iam list-attached-group-policies --group-name "${group}" \
189+
--query "AttachedPolicies[?PolicyArn=='${policy_arn}'] | length(@)" --output text 2>/dev/null || echo "0"
190+
)
191+
if [[ "${attached}" == "0" ]]; then
192+
echo "Attaching ${REGION_POLICY_NAME} to group ${group}"
193+
"${AWS[@]}" iam attach-group-policy --group-name "${group}" --policy-arn "${policy_arn}"
194+
else
195+
echo "${REGION_POLICY_NAME} already attached to group ${group}"
196+
fi
197+
}
198+
199+
ensure_user_in_group() {
200+
local user="$1" group="$2"
201+
local in_group
202+
in_group=$(
203+
"${AWS[@]}" iam list-groups-for-user --user-name "${user}" \
204+
--query "Groups[?GroupName=='${group}'] | length(@)" --output text 2>/dev/null || echo "0"
205+
)
206+
if [[ "${in_group}" == "0" ]]; then
207+
echo "Adding user ${user} to group ${group}"
208+
"${AWS[@]}" iam add-user-to-group --user-name "${user}" --group-name "${group}"
209+
else
210+
echo "User ${user} already in group ${group}"
211+
fi
212+
}
213+
214+
ensure_user_exists "${USER_NAME}"
215+
ensure_group_exists "${GROUP_NAME}"
216+
ensure_policy_attached_to_group "${GROUP_NAME}" "${REGION_ARN}"
217+
ensure_user_in_group "${USER_NAME}" "${GROUP_NAME}"
163218

164219
cat <<SUMMARY
165220
✅ Done.
166-
- ${REGION_POLICY_NAME}: ${REGION_ARN} (attached to ${USER_NAME})
221+
- ${REGION_POLICY_NAME}: ${REGION_ARN} (attached to IAM group ${GROUP_NAME})
222+
- IAM user ${USER_NAME} is a member of ${GROUP_NAME}
167223
- ${ADJUST_POLICY_NAME}: ${ADJUST_ARN}
168224
SUMMARY

0 commit comments

Comments
 (0)