Skip to content

Commit 1e10af2

Browse files
Refactor authz resource tree
1 parent 82629a6 commit 1e10af2

File tree

10 files changed

+132
-152
lines changed

10 files changed

+132
-152
lines changed

.secrets.baseline

Lines changed: 6 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@
9898
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
9999
"min_level": 2
100100
},
101+
{
102+
"path": "detect_secrets.filters.gibberish.should_exclude_secret",
103+
"limit": 3.7
104+
},
101105
{
102106
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
103107
},
@@ -126,88 +130,6 @@
126130
"path": "detect_secrets.filters.heuristic.is_templated_secret"
127131
}
128132
],
129-
"results": {
130-
".github/workflows/ci.yml": [
131-
{
132-
"type": "Secret Keyword",
133-
"filename": ".github/workflows/ci.yml",
134-
"hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0",
135-
"is_verified": false,
136-
"line_number": 15
137-
}
138-
],
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-
],
148-
"docs/local_installation.md": [
149-
{
150-
"type": "Secret Keyword",
151-
"filename": "docs/local_installation.md",
152-
"hashed_secret": "08d2e98e6754af941484848930ccbaddfefe13d6",
153-
"is_verified": false,
154-
"line_number": 90
155-
}
156-
],
157-
"docs/s3.md": [
158-
{
159-
"type": "Secret Keyword",
160-
"filename": "docs/s3.md",
161-
"hashed_secret": "08d2e98e6754af941484848930ccbaddfefe13d6",
162-
"is_verified": false,
163-
"line_number": 55
164-
}
165-
],
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-
],
175-
"tests/conftest.py": [
176-
{
177-
"type": "Base64 High Entropy String",
178-
"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",
189-
"is_verified": false,
190-
"line_number": 24
191-
}
192-
],
193-
"tests/test-gen3workflow-config.yaml": [
194-
{
195-
"type": "Secret Keyword",
196-
"filename": "tests/test-gen3workflow-config.yaml",
197-
"hashed_secret": "900a7331f7bf83bff0e1b2c77f471b4a5145da0f",
198-
"is_verified": false,
199-
"line_number": 5
200-
}
201-
],
202-
"tests/test_s3_endpoint.py": [
203-
{
204-
"type": "Secret Keyword",
205-
"filename": "tests/test_s3_endpoint.py",
206-
"hashed_secret": "08d2e98e6754af941484848930ccbaddfefe13d6",
207-
"is_verified": false,
208-
"line_number": 75
209-
}
210-
]
211-
},
212-
"generated_at": "2026-02-25T19:02:33Z"
133+
"results": {},
134+
"generated_at": "2026-02-26T22:44:12Z"
213135
}

docs/authorization.md

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,47 @@
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+
A(services) --> B(workflow);
16+
B --> C(gen3-workflow);
17+
C --> D(tasks);
18+
C --> E(storage);
19+
D --> F(_user1_);
20+
D --> G(_user2_);
21+
E --> M(_user1_);
22+
E --> N(_user2_);
23+
F --> H(tasks);
24+
H --> I(_task1_);
25+
H --> J(_task2_);
26+
G --> K(tasks);
27+
K --> L(_task3_);
28+
```
829

930
## GA4GH TES
1031

1132
- 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`.
33+
- To view a task, users need `read` access to resource `/services/workflow/gen3-workflow/tasks/<user ID>/<task ID>` on service `gen3-workflow`.
34+
- To cancel a task, users need `delete` access to resource `/services/workflow/gen3-workflow/tasks/<user ID>/<task ID>` on service `gen3-workflow`.
35+
- 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`.
36+
- 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`.
37+
- However, sharing task _inputs/outputs_ in the user's S3 bucket is not supported. Currently, users can only access their own S3 bucket.
1638

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.
39+
## Storage
40+
- 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`.
41+
- The Funnel workers have access to `/services/workflow/gen3-workflow/storage` so they can manage files in all the user buckets.
42+
- 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.
2043

21-
#### Authorization configuration example
44+
## Authorization configuration example
45+
46+
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.
2247

2348
```yaml
2449
users:
@@ -29,7 +54,7 @@ users:
2954
clients:
3055
funnel-plugin-client:
3156
policies:
32-
- gen3_workflow_user
57+
- gen3_workflow_storage_admin
3358

3459
authz:
3560
resources:
@@ -48,18 +73,18 @@ authz:
4873
- gen3_workflow_creator
4974
resource_paths:
5075
- /services/workflow/gen3-workflow/tasks
51-
- id: gen3_workflow_admin
76+
- id: gen3_workflow_task_reader_admin
5277
description: Allows access to view tasks created by all users
5378
role_ids:
5479
- gen3_workflow_reader
5580
resource_paths:
5681
- /services/workflow/gen3-workflow/tasks
57-
- id: workflow_storage_deleter
58-
description: Allows delete access to the user's own S3 bucket
82+
- id: gen3_workflow_storage_admin
83+
description: Allows access to manage all the user buckets
5984
role_ids:
60-
- workflow_storage_deleter
85+
- gen3_workflow_admin
6186
resource_paths:
62-
- /services/workflow/gen3-workflow
87+
- /services/workflow/gen3-workflow/storage
6388

6489
roles:
6590
- id: gen3_workflow_reader
@@ -74,10 +99,10 @@ authz:
7499
action:
75100
service: gen3-workflow
76101
method: create
77-
- id: workflow_storage_deleter
78-
permissions:
79-
- id: workflow_storage_deleter
80-
action:
81-
service: gen3-workflow
82-
method: delete
102+
- id: gen3_workflow_admin
103+
permissions:
104+
- id: gen3_workflow_admin_action
105+
action:
106+
service: gen3-workflow
107+
method: *
83108
```

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
```

gen3workflow/auth.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -123,38 +123,47 @@ async def grant_user_access_to_their_own_tasks(
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):
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):
134136
# if the user already has access to their own tasks, 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/routes/ga4gh_tes.py

Lines changed: 4 additions & 1 deletion
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"] = (

gen3workflow/routes/s3.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,18 @@ async def s3_endpoint(path: str, request: Request):
171171
if request.method == "GET" and not path:
172172
return await get_status(request)
173173

174-
# extract the caller's access token from the request headers, and ensure the caller (user, or
175-
# client acting on behalf of the user) has access to run workflows
174+
# Extract the caller's access token from the request headers, and ensure the caller (user, or
175+
# client acting on behalf of the user) has access to the user's files.
176+
# Note: sharing task inputs/output is not supported. Currently, users can only access their own
177+
# S3 bucket.
176178
auth = Auth(api_request=request)
177179
user_id = await set_access_token_and_get_user_id(auth, request.headers)
178-
await auth.authorize("create", ["/services/workflow/gen3-workflow/tasks"])
180+
auth_verb = {"GET": "read", "HEAD": "read", "DELETE": "delete"}.get(
181+
request.method, "create"
182+
)
183+
await auth.authorize(
184+
auth_verb, [f"/services/workflow/gen3-workflow/tasks/{user_id}"]
185+
)
179186

180187
# get the name of the user's bucket and ensure the user is making a call to their own bucket
181188
logger.info(f"Incoming S3 request from user '{user_id}': '{request.method} {path}'")

gen3workflow/routes/storage.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ async def delete_user_bucket(request: Request, auth=Depends(Auth)) -> None:
4545
Amazon S3 processes bucket deletion asynchronously. The bucket may
4646
remain visible for a short period until deletion fully propagates.
4747
"""
48-
await auth.authorize("delete", ["/services/workflow/gen3-workflow/user-bucket"])
49-
5048
token_claims = await auth.get_token_claims()
5149
user_id = token_claims.get("sub")
50+
await auth.authorize(
51+
"delete", [f"/services/workflow/gen3-workflow/tasks/{user_id}"]
52+
)
5253
logger.info(f"User '{user_id}' deleting their storage bucket")
5354
deleted_bucket_name = aws_utils.cleanup_user_bucket(user_id, delete_bucket=True)
5455

@@ -80,10 +81,11 @@ async def empty_user_bucket(request: Request, auth=Depends(Auth)) -> None:
8081
"""
8182
Deletes all the objects from current user's S3 bucket
8283
"""
83-
await auth.authorize("delete", ["/services/workflow/gen3-workflow/user-bucket"])
84-
8584
token_claims = await auth.get_token_claims()
8685
user_id = token_claims.get("sub")
86+
await auth.authorize(
87+
"delete", [f"/services/workflow/gen3-workflow/tasks/{user_id}"]
88+
)
8789
logger.info(f"User '{user_id}' emptying their storage bucket")
8890
deleted_bucket_name = aws_utils.cleanup_user_bucket(user_id)
8991

0 commit comments

Comments
 (0)