Skip to content

Commit 3720331

Browse files
Refactor authz resource tree (#103)
1 parent 78a6f7a commit 3720331

File tree

13 files changed

+246
-196
lines changed

13 files changed

+246
-196
lines changed

.secrets.baseline

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,6 @@
136136
"line_number": 15
137137
}
138138
],
139-
"alembic.ini": [
140-
{
141-
"type": "Basic Auth Credentials",
142-
"filename": "alembic.ini",
143-
"hashed_secret": "9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684",
144-
"is_verified": false,
145-
"line_number": 64
146-
}
147-
],
148139
"docs/local_installation.md": [
149140
{
150141
"type": "Secret Keyword",
@@ -163,31 +154,13 @@
163154
"line_number": 55
164155
}
165156
],
166-
"migrations/versions/e1886270d9d2_create_system_key_table.py": [
167-
{
168-
"type": "Hex High Entropy String",
169-
"filename": "migrations/versions/e1886270d9d2_create_system_key_table.py",
170-
"hashed_secret": "1df47988c41b70d5541f29636c48c6127cf593b8",
171-
"is_verified": false,
172-
"line_number": 16
173-
}
174-
],
175157
"tests/conftest.py": [
176158
{
177159
"type": "Base64 High Entropy String",
178160
"filename": "tests/conftest.py",
179-
"hashed_secret": "0dd78d9147bb410f0cb0199c5037da36594f77d8",
180-
"is_verified": false,
181-
"line_number": 195
182-
}
183-
],
184-
"tests/migrations/test_migration_e1886270d9d2.py": [
185-
{
186-
"type": "Hex High Entropy String",
187-
"filename": "tests/migrations/test_migration_e1886270d9d2.py",
188-
"hashed_secret": "1df47988c41b70d5541f29636c48c6127cf593b8",
161+
"hashed_secret": "06a9fa84e13b8f701d0c03235f675fee5e6fd736",
189162
"is_verified": false,
190-
"line_number": 24
163+
"line_number": 196
191164
}
192165
],
193166
"tests/test-gen3workflow-config.yaml": [
@@ -209,5 +182,5 @@
209182
}
210183
]
211184
},
212-
"generated_at": "2026-02-27T22:12:14Z"
185+
"generated_at": "2026-03-02T20:27:56Z"
213186
}

docs/authorization.md

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,45 @@
33
The Gen3 Workflow endpoints are protected by Arborist policies.
44

55
Contents:
6+
- [Authorization resources overview](#authorization-resources-overview)
7+
- [Storage](#storage)
68
- [GA4GH TES](#ga4gh-tes)
7-
- [Authorization configuration example](#authorization-configuration-example)
9+
- [Authorization configuration example](#authorization-configuration-example)
10+
11+
## Authorization resources overview
12+
13+
```mermaid
14+
graph TD;
15+
services --> workflow;
16+
workflow --> gen3-workflow;
17+
gen3-workflow --> tasks;
18+
gen3-workflow --> storage;
19+
tasks --> user1t(user1);
20+
tasks --> user2t(user2);
21+
storage --> user1;
22+
storage --> user2;
23+
user1t --> task1;
24+
user1t --> task2;
25+
user2t --> task3;
26+
```
827

928
## GA4GH TES
1029

1130
- To create a task, users need `create` access to resource `/services/workflow/gen3-workflow/tasks` on service `gen3-workflow`.
12-
- To view a task, users need `read` access to resource `/users/<user ID>/gen3-workflow/tasks/<task ID>` on service `gen3-workflow`.
13-
- Users are automatically granted access to `/users/<user ID>/gen3-workflow/tasks` so they can view their own tasks.
14-
- Admin access (the ability to see _all_ users’ tasks instead of just your own) can be granted to a user by granting them access to the parent resource `/services/workflow/gen3-workflow/tasks`.
15-
- This supports sharing tasks with others; for example, "user1" may share "taskA" with "user2" if the system grants "user2" access to `/users/user1/gen3-workflow/tasks/taskA`.
31+
- To view a task, users need `read` access to resource `/services/workflow/gen3-workflow/tasks/<user ID>/<task ID>` on service `gen3-workflow`.
32+
- To cancel a task, users need `delete` access to resource `/services/workflow/gen3-workflow/tasks/<user ID>/<task ID>` on service `gen3-workflow`.
33+
- Admin access (the ability to see _all_ users’ tasks instead of just your own) can be granted to a user by granting them access to the parent resource `/services/workflow/gen3-workflow/tasks`.
34+
- This supports sharing tasks with others; for example, "user1" may share "taskA" with "user2" if the system grants "user2" access to `/services/workflow/gen3-workflow/tasks/user1/taskA`.
35+
- However, sharing task _inputs/outputs_ in the user's S3 bucket is not supported. Currently, users can only access their own S3 bucket.
1636

17-
## Other Gen3-Workflow functionality
18-
- To download inputs and upload outputs, the Funnel workers need `create` access to resource `/services/workflow/gen3-workflow/tasks` on service `gen3-workflow`, like end-users.
19-
- To empty or delete their own S3 bucket, a user needs `delete` access to the resource `/services/workflow/gen3-workflow/user-bucket` on the `gen3-workflow` service -- a special privilege useful for automated testing but not intended for the average user.
37+
## Storage
38+
- To upload input files, download output files, and in general manage the files in their S3 bucket, users need `create`, `read` or `delete` access to resource `/services/workflow/gen3-workflow/storage/<user ID>` on service `gen3-workflow`.
39+
- The Funnel workers have access to `/services/workflow/gen3-workflow/storage` so they can manage files in all the user buckets.
40+
- To empty or delete their own S3 bucket (`/storage/user-bucket` endpoints), users need `delete` access to the resource `/services/workflow/gen3-workflow/storage/<user ID>` on the `gen3-workflow` service.
2041

21-
#### Authorization configuration example
42+
## Authorization configuration example
43+
44+
Users are automatically granted access to `/services/workflow/gen3-workflow/tasks/<user ID>` and to `/services/workflow/gen3-workflow/storage/<user ID>` so they can view and cancel their own tasks and manage files in their own bucket.
2245

2346
```yaml
2447
users:
@@ -29,7 +52,7 @@ users:
2952
clients:
3053
funnel-plugin-client:
3154
policies:
32-
- gen3_workflow_user
55+
- gen3_workflow_storage_admin
3356

3457
authz:
3558
resources:
@@ -40,6 +63,7 @@ authz:
4063
- name: gen3-workflow
4164
subresources:
4265
- name: tasks
66+
- name: storage
4367

4468
policies:
4569
- id: gen3_workflow_user
@@ -48,18 +72,18 @@ authz:
4872
- gen3_workflow_creator
4973
resource_paths:
5074
- /services/workflow/gen3-workflow/tasks
51-
- id: gen3_workflow_admin
75+
- id: gen3_workflow_task_reader_admin
5276
description: Allows access to view tasks created by all users
5377
role_ids:
5478
- gen3_workflow_reader
5579
resource_paths:
5680
- /services/workflow/gen3-workflow/tasks
57-
- id: workflow_storage_deleter
58-
description: Allows delete access to the user's own S3 bucket
81+
- id: gen3_workflow_storage_admin
82+
description: Allows access to manage all the user buckets
5983
role_ids:
60-
- workflow_storage_deleter
84+
- gen3_workflow_admin
6185
resource_paths:
62-
- /services/workflow/gen3-workflow
86+
- /services/workflow/gen3-workflow/storage
6387

6488
roles:
6589
- id: gen3_workflow_reader
@@ -74,10 +98,10 @@ authz:
7498
action:
7599
service: gen3-workflow
76100
method: create
77-
- id: workflow_storage_deleter
78-
permissions:
79-
- id: workflow_storage_deleter
80-
action:
81-
service: gen3-workflow
82-
method: delete
101+
- id: gen3_workflow_admin
102+
permissions:
103+
- id: gen3_workflow_admin_action
104+
action:
105+
service: gen3-workflow
106+
method: '*'
83107
```

docs/helm_chart_architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The Gen3 Workflow helm chart is [here](https://github.com/uc-cdis/gen3-helm/tree
88

99
```mermaid
1010
graph TD;
11-
A[Gen3 chart] --> B(Gen3 Workflow chart);
11+
A(Gen3 chart) --> B(Gen3 Workflow chart);
1212
A --> C(Gen3 Funnel chart);
1313
C --> D(OHSU Funnel chart);
1414
```

docs/local_installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Try out the API at <http://localhost:8080/_status> or <http://localhost:8080/doc
7171
7272
## Run Nextflow workflows with Gen3Workflow
7373

74-
- Hit the `/storage/info` endpoint to get your working directory
74+
- Hit the `/storage/setup` endpoint to get your working directory
7575
- Configure Nextflow. Example Nextflow configuration:
7676
```
7777
plugins {

docs/openapi.yaml

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -498,20 +498,48 @@ paths:
498498
- S3
499499
/storage/info:
500500
get:
501-
description: Get details about the current user's storage setup
502-
operationId: get_storage_info
501+
description: 'Return details about the current user''s storage setup.
502+
503+
This endpoint also serves as a mandatory "first time setup" for the user''s
504+
bucket
505+
506+
and authz.'
507+
operationId: storage_setup_2
508+
responses:
509+
'200':
510+
content:
511+
application/json:
512+
schema:
513+
additionalProperties: true
514+
title: Response Storage Setup 2
515+
type: object
516+
description: Successful Response
517+
security:
518+
- HTTPBearer: []
519+
summary: Storage Setup
520+
tags:
521+
- Storage
522+
/storage/setup:
523+
get:
524+
description: 'Return details about the current user''s storage setup.
525+
526+
This endpoint also serves as a mandatory "first time setup" for the user''s
527+
bucket
528+
529+
and authz.'
530+
operationId: storage_setup
503531
responses:
504532
'200':
505533
content:
506534
application/json:
507535
schema:
508536
additionalProperties: true
509-
title: Response Get Storage Info
537+
title: Response Storage Setup
510538
type: object
511539
description: Successful Response
512540
security:
513541
- HTTPBearer: []
514-
summary: Get Storage Info
542+
summary: Storage Setup
515543
tags:
516544
- Storage
517545
/storage/user-bucket:

gen3workflow/auth.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -118,43 +118,52 @@ async def authorize(
118118

119119
return authorized
120120

121-
async def grant_user_access_to_their_own_tasks(
121+
async def grant_user_access_to_their_own_data(
122122
self, username: str, user_id: str
123123
) -> None:
124124
"""
125125
Ensure the specified user exists in Arborist and has a policy granting them access to their
126-
own Gen3Workflow tasks ("read" and "delete" access to resource "/users/<user ID>/gen3-workflow/tasks" for service "gen3-workflow").
126+
own Gen3Workflow tasks and bucket storage.
127127
Args:
128128
username (str): The user's Gen3 username
129129
user_id (str): The user's unique Gen3 ID
130130
"""
131-
logger.info(f"Ensuring user '{user_id}' has access to their own tasks")
132-
resource_path = f"/users/{user_id}/gen3-workflow/tasks"
133-
if await self.authorize(method="read", resources=[resource_path], throw=False):
134-
# if the user already has access to their own tasks, return early
131+
logger.info(
132+
f"Ensuring user '{user_id}' has access to their own tasks and storage"
133+
)
134+
resource_path1 = f"/services/workflow/gen3-workflow/tasks/{user_id}"
135+
if await self.authorize(method="read", resources=[resource_path1], throw=False):
136+
# if the user already has access to their own data, return early
135137
return
136138

137-
logger.debug(f"Attempting to create resource '{resource_path}' in Arborist")
138-
parent_path = f"/users/{user_id}/gen3-workflow"
139+
parent_path = "/services/workflow/gen3-workflow/tasks"
140+
logger.debug(f"Attempting to create resource '{resource_path1}' in Arborist")
139141
resource = {
140-
"name": "tasks",
142+
"name": user_id,
141143
"description": f"Represents workflow tasks owned by user '{username}'",
142144
}
143145
await self.arborist_client.create_resource(
144146
parent_path, resource, create_parents=True
145147
)
146148

147-
role_id = "gen3-workflow_task_owner"
149+
resource_path2 = f"/services/workflow/gen3-workflow/storage/{user_id}"
150+
parent_path = "/services/workflow/gen3-workflow/storage"
151+
logger.debug(f"Attempting to create resource '{resource_path2}' in Arborist")
152+
resource = {
153+
"name": user_id,
154+
"description": f"Represents task storage owned by user '{username}'",
155+
}
156+
await self.arborist_client.create_resource(
157+
parent_path, resource, create_parents=True
158+
)
159+
160+
role_id = "gen3_workflow_admin"
148161
role = {
149162
"id": role_id,
150163
"permissions": [
151164
{
152-
"id": "gen3-workflow-reader",
153-
"action": {"service": "gen3-workflow", "method": "read"},
154-
},
155-
{
156-
"id": "gen3-workflow-deleter",
157-
"action": {"service": "gen3-workflow", "method": "delete"},
165+
"id": "gen3_workflow_admin_action",
166+
"action": {"service": "gen3-workflow", "method": "*"},
158167
},
159168
],
160169
}
@@ -168,13 +177,13 @@ async def grant_user_access_to_their_own_tasks(
168177
)
169178
await self.arborist_client.create_role(role)
170179

171-
policy_id = f"gen3-workflow_task_owner_sub-{user_id}"
180+
policy_id = f"gen3_workflow_user_sub_{user_id}"
172181
logger.debug(f"Attempting to create policy '{policy_id}' in Arborist")
173182
policy = {
174183
"id": policy_id,
175184
"description": f"policy created by gen3-workflow for user '{username}'",
176185
"role_ids": [role_id],
177-
"resource_paths": [resource_path],
186+
"resource_paths": [resource_path1, resource_path2],
178187
}
179188
await self.arborist_client.create_policy(policy, skip_if_exists=True)
180189

gen3workflow/aws_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def create_iam_role_for_bucket_access(user_id: str) -> str:
226226
if config["KMS_ENCRYPTION_ENABLED"]:
227227
_, kms_key_arn = get_existing_kms_key_for_bucket(bucket_name)
228228
if not kms_key_arn:
229-
err_msg = "Bucket misconfigured. Hit the `GET /storage/info` endpoint and try again."
229+
err_msg = "Bucket misconfigured. Hit the `GET /storage/setup` endpoint and try again."
230230
logger.error(
231231
f"No existing KMS key found for bucket '{bucket_name}'. {err_msg}"
232232
)

gen3workflow/routes/ga4gh_tes.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,10 @@ async def create_task(request: Request, auth=Depends(Auth)) -> dict:
149149
err_msg = f"Tags {sorted(reserved_tags)} are reserved for internal use only and cannot be used."
150150
logger.error(err_msg)
151151
raise HTTPException(HTTP_400_BAD_REQUEST, err_msg)
152-
body["tags"]["_AUTHZ"] = f"/users/{user_id}/gen3-workflow/tasks/TASK_ID_PLACEHOLDER"
152+
authz_resource = (
153+
f"/services/workflow/gen3-workflow/tasks/{user_id}/TASK_ID_PLACEHOLDER"
154+
)
155+
body["tags"]["_AUTHZ"] = authz_resource
153156
# TODO: Test running gen3-workflow locally and document this change
154157
if config["EKS_CLUSTER_NAME"]:
155158
body["tags"]["_FUNNEL_WORKER_ROLE_ARN"] = (
@@ -166,14 +169,6 @@ async def create_task(request: Request, auth=Depends(Auth)) -> dict:
166169
headers={"Authorization": f"bearer {auth.bearer_token.credentials}"},
167170
)
168171

169-
try:
170-
await auth.grant_user_access_to_their_own_tasks(
171-
username=username, user_id=user_id
172-
)
173-
except ArboristError as e:
174-
logger.error(e.message)
175-
raise HTTPException(e.code, e.message)
176-
177172
return res.json()
178173

179174

0 commit comments

Comments
 (0)