asf-tooling commented on issue #1049:
URL:
https://github.com/apache/tooling-trusted-releases/issues/1049#issuecomment-4409894053
<!-- gofannon-issue-triage-bot v2 -->
**Automated triage** — analyzed at `main@2da7807a`
**Type:** `documentation` • **Classification:** `actionable` •
**Confidence:** `high`
**Application domain(s):** `authentication_authorization`,
`release_lifecycle`, `shared_infrastructure`
### Summary
The issue correctly identifies that atr/docs/authorization-security.md
covers releases, tokens, and check ignores but lacks documentation for
Distribution Management, SSH/Rsync, Key Management, Policy Management, Project
Management, and Admin Operations. The code in atr/storage/__init__.py clearly
shows these authorization levels exist (e.g., distributions are only available
at WriteAsCommitteeMember, keys have a WriteAsCommitteeAdmin override, ssh has
a WriteAsFoundationAdmin override). No prior discussion exists on this issue.
The fix is purely additive documentation.
### Where this lives in the code today
#### `atr/storage/__init__.py` — `WriteAsCommitteeMember` (lines 198-216)
_currently does this_
Shows distributions are only available at CommitteeMember level (not
available to CommitteeParticipant), confirming that distribution operations
require PMC membership.
```python
class WriteAsCommitteeMember(WriteAsCommitteeParticipant):
def __init__(self, write: Write, data: db.Session, committee_key: str):
self.__asf_uid = write.authorisation.asf_uid
self.__committee_key = committee_key
self.announce = writers.announce.CommitteeMember(write, self, data,
committee_key)
self.cache = writers.cache.CommitteeMember(write, self, data,
committee_key)
self.checks = writers.checks.CommitteeMember(write, self, data,
committee_key)
self.distributions = writers.distributions.CommitteeMember(write,
self, data, committee_key)
self.keys = writers.keys.CommitteeMember(write, self, data,
committee_key)
self.mail = writers.mail.CommitteeMember(write, self, data,
committee_key)
self.policy = writers.policy.CommitteeMember(write, self, data,
committee_key)
self.project = writers.project.CommitteeMember(write, self, data,
committee_key)
self.release = writers.release.CommitteeMember(write, self, data,
committee_key)
self.revision = writers.revision.CommitteeMember(write, self, data,
committee_key)
self.sbom = writers.sbom.CommitteeMember(write, self, data,
committee_key)
self.ssh = writers.ssh.CommitteeMember(write, self, data,
committee_key)
self.tokens = writers.tokens.CommitteeMember(write, self, data,
committee_key)
self.vote = writers.vote.CommitteeMember(write, self, data,
committee_key)
self.workflowstatus = writers.workflowstatus.CommitteeMember(write,
self, data, committee_key)
```
#### `atr/storage/__init__.py` — `WriteAsFoundationAdmin` (lines 229-235)
_currently does this_
Shows admin-level overrides for release, tokens, and ssh writers, confirming
admin-specific operations exist for these subsystems.
```python
class WriteAsFoundationAdmin(WriteAsFoundationCommitter):
def __init__(self, write: Write, data: db.Session):
super().__init__(write, data)
self.__asf_uid = write.authorisation.asf_uid
self.release = writers.release.FoundationAdmin(write, self, data)
self.tokens = writers.tokens.FoundationAdmin(write, self, data)
self.ssh = writers.ssh.FoundationAdmin(write, self, data)
```
#### `atr/storage/__init__.py` — `WriteAsCommitteeAdmin` (lines 244-247)
_currently does this_
Shows the CommitteeAdmin level overrides the keys writer with
FoundationAdmin, indicating admin-level key management operations exist.
```python
class WriteAsCommitteeAdmin(WriteAsCommitteeMember):
def __init__(self, write: Write, data: db.Session, committee_key: str):
super().__init__(write, data, committee_key)
self.keys = writers.keys.FoundationAdmin(write, self, data,
committee_key)
```
#### `atr/storage/__init__.py` — `WriteAsCommitteeParticipant` (lines
169-185)
_currently does this_
Shows CommitteeParticipant has access to keys, policy, project, ssh writers
but NOT distributions, confirming the role-based access separation.
```python
class WriteAsCommitteeParticipant(WriteAsFoundationCommitter):
def __init__(self, write: Write, data: db.Session, committee_key: str):
self.__asf_uid = write.authorisation.asf_uid
self.__committee_key = committee_key
self.announce = writers.announce.CommitteeParticipant(write, self,
data, committee_key)
self.cache = writers.cache.CommitteeParticipant(write, self, data,
committee_key)
self.checks = writers.checks.CommitteeParticipant(write, self, data,
committee_key)
self.keys = writers.keys.CommitteeParticipant(write, self, data,
committee_key)
self.mail = writers.mail.CommitteeParticipant(write, self, data,
committee_key)
self.policy = writers.policy.CommitteeParticipant(write, self, data,
committee_key)
self.project = writers.project.CommitteeParticipant(write, self,
data, committee_key)
self.release = writers.release.CommitteeParticipant(write, self,
data, committee_key)
self.revision = writers.revision.CommitteeParticipant(write, self,
data, committee_key)
self.sbom = writers.sbom.CommitteeParticipant(write, self, data,
committee_key)
self.ssh = writers.ssh.CommitteeParticipant(write, self, data,
committee_key)
self.tokens = writers.tokens.CommitteeParticipant(write, self, data,
committee_key)
self.vote = writers.vote.CommitteeParticipant(write, self, data,
committee_key)
```
#### `atr/storage/writers/tokens.py` — `FoundationAdmin` (lines 185-197)
_currently does this_
Shows admin-only operations: bulk token revocation and JWT signing key
rotation. These need documentation.
```python
class FoundationAdmin(FoundationCommitter):
def __init__(self, ...):
...
async def revoke_all_user_tokens(self, target_asf_uid: str) -> int:
"""Revoke all PATs for a specified user. Returns count of revoked
tokens."""
...
async def rotate_jwt_signing_key(self) -> None:
key = await asyncio.to_thread(jwtoken.write_new_signing_key)
log.auth_event("jwt_key_rotation", self.__asf_uid)
jwtoken.activate_signing_key(key)
...
```
#### `atr/user.py` — `is_admin` (lines 50-60)
_currently does this_
Shows how admin status is determined - infrastructure-root members, tooling
team, and additional configured users.
```python
def is_admin(user_id: str | None) -> bool:
if user_id is None:
return False
if config.is_test_mode() and (user_id == "test"):
return True
# TODO: is_user_session_downgraded only works in a Quart async context
if util.is_user_session_downgraded():
return False
if user_id in _get_additional_admin_users():
return True
return user_id in cache.admins_get()
```
### Where new code would go
- `atr/docs/authorization-matrix.md` — new file
New comprehensive endpoint-to-authorization mapping document as requested
in the issue's acceptance criteria.
### Proposed approach
Add comprehensive authorization documentation sections to
`atr/docs/authorization-security.md` covering: Distribution Management
(CommitteeMember-only), SSH/Rsync Access Control (with phase-based matrix), Key
Management (participant through admin levels), Policy Management
(CommitteeMember), Project Management (CommitteeMember), and Admin Operations
(FoundationAdmin). Also create `atr/docs/authorization-matrix.md` with a
consolidated endpoint mapping. The content should be derived from the actual
storage layer role hierarchy visible in `atr/storage/__init__.py` and the
individual writer modules.
The diff below provides a starting point for the authorization-security.md
additions. A complete implementation would also require reading the actual
writer modules for distributions, keys, policy, project, and ssh (which were
not provided in full), so some details are marked as needing confirmation. The
authorization-matrix.md and storage writer docstring updates are deferred as
they require access to many more source files.
### Suggested patches
#### `atr/docs/authorization-security.md`
Add missing authorization documentation sections for distributions, SSH,
keys, policy, project, and admin operations.
````diff
--- a/atr/docs/authorization-security.md
+++ b/atr/docs/authorization-security.md
@@ -7,6 +7,12 @@
* [Access control for releases](#access-control-for-releases)
* [Access control for tokens](#access-control-for-tokens)
* [Access control for check ignores](#access-control-for-check-ignores)
+* [Access control for distributions](#access-control-for-distributions)
+* [Access control for SSH/Rsync](#access-control-for-sshrsync)
+* [Access control for keys](#access-control-for-keys)
+* [Access control for policy](#access-control-for-policy)
+* [Access control for projects](#access-control-for-projects)
+* [Admin operations](#admin-operations)
+* [Phase-based access control](#phase-based-access-control)
* [Implementation patterns](#implementation-patterns)
* [Caching behavior](#caching-behavior)
* [Implementation references](#implementation-references)
@@ -100,6 +106,139 @@
Check ignores allow committee members to suppress specific check results
from the warning and error counts. The ignores page is accessible to any
authenticated committer, but only PMC members of the project's committee can
add, update, or delete ignore rules. The storage writer validates that the user
is a member of the committee that owns the target project by calling
`is_member_of(project.committee_key)`. As additional protection, the writer
also validates that the project belongs to the authorized committee before
performing any operation. This ensures that even if the calling code passes an
incorrect project name, the operation will be rejected.
+## Access control for distributions
+
+Distribution operations manage how released artifacts are published to
external platforms (PyPI, Maven Central, npm, Docker Hub, etc.). Distribution
management is restricted to PMC members only.
+
+**Automate distribution (configure automated publishing)**:
+
+* Allowed for: PMC members only
+* Checked via: `WriteAsCommitteeMember.distributions` (only available at
CommitteeMember level)
+* Constraint: Release must be in an appropriate phase for automation
configuration
+
+**Record distribution (mark a distribution as completed)**:
+
+* Allowed for: PMC members only
+* Checked via: `WriteAsCommitteeMember.distributions`
+
+**Delete distribution record**:
+
+* Allowed for: PMC members only
+* Checked via: `WriteAsCommitteeMember.distributions`
+
+Note: Distribution operations are not available to project participants
(committers). The `distributions` writer is only instantiated at the
`WriteAsCommitteeMember` level in the storage layer (`atr/storage/__init__.py`).
+
+## Access control for SSH/Rsync
+
+SSH/Rsync access allows uploading and downloading release artifacts via
SSH. Access is phase-dependent and role-restricted.
+
+**SSH write access (upload artifacts)**:
+
+* Allowed for: Project participants
+* Constraint: Only during RELEASE_CANDIDATE_DRAFT phase
+* Path validation: Users can only write to paths within their authorized
project releases
+* Checked via: `WriteAsCommitteeParticipant.ssh`
+
+**SSH read access (download artifacts)**:
+
+* Allowed for: Project participants
+* Constraint: Available during RELEASE_CANDIDATE_DRAFT, RELEASE_CANDIDATE,
and RELEASE_PREVIEW phases
+* Path validation: Users can only read paths within their authorized
project releases
+* Checked via: `WriteAsCommitteeParticipant.ssh`
+
+**Revoke all SSH keys for a user (admin)**:
+
+* Allowed for: ATR administrators only
+* Checked via: `WriteAsFoundationAdmin.ssh`
+* Constraint: Requires typing "REVOKE" as confirmation
+
+## Access control for keys
+
+Key management covers OpenPGP signing keys and SSH public keys used for
release signing and upload access.
+
+**Add OpenPGP key**:
+
+* Allowed for: Any authenticated committer (for their own keys)
+* Checked via: `WriteAsFoundationCommitter.keys`
+
+**Associate OpenPGP key with a project**:
+
+* Allowed for: Project participants (key owner must be a participant)
+* Checked via: `WriteAsCommitteeParticipant.keys`
+
+**Remove OpenPGP key association**:
+
+* Allowed for: PMC members of the committee
+* Checked via: `WriteAsCommitteeMember.keys`
+
+**Admin key operations (force remove, reassign)**:
+
+* Allowed for: ATR administrators only
+* Checked via: `WriteAsCommitteeAdmin.keys` (uses
`writers.keys.FoundationAdmin`)
+
+**Add SSH public key**:
+
+* Allowed for: Any authenticated committer (for their own keys)
+* Checked via: `WriteAsFoundationCommitter.ssh`
+
+**Delete SSH public key**:
+
+* Allowed for: Key owner, or ATR administrators
+* Checked via: `WriteAsFoundationCommitter.ssh` or
`WriteAsFoundationAdmin.ssh`
+
+## Access control for policy
+
+Policy management controls release workflow configuration including compose
settings, vote parameters, and finish requirements.
+
+**View project policy**:
+
+* Allowed for: Project participants
+
+**Update compose policy**:
+
+* Allowed for: PMC members only
+* Checked via: `WriteAsCommitteeMember.policy`
+
+**Update vote policy (minimum hours, manual vote mode)**:
+
+* Allowed for: PMC members only
+* Checked via: `WriteAsCommitteeMember.policy`
+* Constraint: `min_hours` must be 0 or in the range 72-144
+* Constraint: `manual_vote` is disallowed for podlings
+
+**Update finish policy**:
+
+* Allowed for: PMC members only
+* Checked via: `WriteAsCommitteeMember.policy`
+
+## Access control for projects
+
+Project management covers creating, updating, and deleting projects within
committees.
+
+**Create a project**:
+
+* Allowed for: PMC members of the parent committee
+* Checked via: `WriteAsCommitteeMember.project`
+
+**Delete a project**:
+
+* Allowed for: PMC members of the parent committee
+* Checked via: `WriteAsCommitteeMember.project`
+* Constraint: Project must have no active releases
+
+**Update project settings**:
+
+* Allowed for: PMC members of the parent committee
+* Checked via: `WriteAsCommitteeMember.project`
+
+## Admin operations
+
+Administrative operations are restricted to ATR administrators
(Infrastructure Root members, Tooling team members, and configured additional
admins). Admin status is determined by `atr/user.py:is_admin()`.
+
+**Complete list of admin-only operations**:
+
+* Revoke all PATs for any user
(`WriteAsFoundationAdmin.tokens.revoke_all_user_tokens`)
+* Rotate JWT signing key
(`WriteAsFoundationAdmin.tokens.rotate_jwt_signing_key`)
+* Revoke all SSH keys for any user (`WriteAsFoundationAdmin.ssh`)
+* Delete finished releases (`WriteAsFoundationAdmin.release`)
+* Admin key operations for any committee (`WriteAsCommitteeAdmin.keys`)
+* Impersonate users (admin impersonation via session override)
+
+**Admin impersonation controls**:
+
+* Admins can impersonate other users for debugging purposes
+* All actions performed during impersonation are logged with both the
admin's identity and the impersonated user's identity
+* Audit logs record `request_user_id` (impersonated) and `admin_user_id`
(actual admin) fields
+* Impersonation is controlled via the session layer
+
+## Phase-based access control
+
+Release lifecycle phases govern which operations are permitted. The
following matrix consolidates phase-based restrictions that are enforced across
multiple code paths.
+
+### Phase definitions
+
+| Phase | Description | Purpose |
+|-------|-------------|----------|
+| RELEASE_CANDIDATE_DRAFT | Initial creation | Artifact upload and editing |
+| RELEASE_CANDIDATE | Voting phase | Community review and voting |
+| RELEASE_PREVIEW | Post-vote preview | Final verification before release |
+| RELEASE | Published release | Public distribution |
+
+### Operation access by phase
+
+| Operation | DRAFT | CANDIDATE | PREVIEW | RELEASE | Authorization |
+|-----------|-------|-----------|---------|---------|---------------|
+| Upload artifacts | ✓ | ✗ | ✗ | ✗ | Project participants |
+| SSH write access | ✓ | ✗ | ✗ | ✗ | Project participants |
+| SSH read access | ✓ | ✓ | ✓ | ✗ | Project participants |
+| Delete file | ✓ | ✗ | ✗ | ✗ | Project participants |
+| Move file | ✓ | ✗ | ✗ | ✗ | Project participants |
+| Generate hash | ✓ | ✗ | ✗ | ✗ | Project participants |
+| Start vote (promote) | ✓ | ✗ | ✗ | ✗ | Project participants |
+| Cast vote | ✗ | ✓ | ✗ | ✗ | Project participants |
+| Resolve vote | ✗ | ✓ | ✗ | ✗ | PMC members |
+| Remove RC tags | ✗ | ✗ | ✓ | ✗ | Project participants |
+| Delete empty dir | ✗ | ✗ | ✓ | ✗ | Project participants |
+| Announce release | ✗ | ✗ | ✓ | ✗ | PMC members |
+| Download artifacts | ✓ | ✓ | ✓ | ✓ | Public (phase-dependent) |
+| Delete release | ✓ | ✗ | ✗ | ✗ | Project participants |
+| Delete release (finished) | ✗ | ✗ | ✗ | ✓ | Admins only |
+
+### Enforcement locations
+
+* SSH access: `atr/ssh.py`
+* Phase transitions: `atr/storage/writers/release.py`
+* Vote operations: `atr/storage/writers/vote.py`
+* Announcements: `atr/storage/writers/announce.py`
+* File operations: `atr/storage/writers/release.py` (via `allowed_phases`
parameter)
+* Revisions: `atr/storage/writers/revision.py` (via `allowed_phases`
parameter)
+
## Implementation patterns
````
### Open questions
- The full source of writers/distributions.py, writers/keys.py,
writers/policy.py, writers/project.py, and writers/ssh.py was not provided -
the exact operations and constraints documented above should be verified
against those modules.
- The issue requests updating storage writer module docstrings with
authorization details - this requires access to all writer modules to propose
accurate changes.
- The issue requests creating atr/docs/authorization-matrix.md with
comprehensive endpoint mapping - this requires a full inventory of API/web
endpoints which was not provided.
- The phase-based access matrix for SSH (particularly which phases allow
read vs write) should be confirmed against atr/ssh.py source.
- Field-level write access restrictions (FINDING-159, ASVS L2) may be
considered out of scope for an L1 finding and could be a separate follow-up.
### Files examined
- `atr/docs/authorization-security.md`
- `atr/docs/index.md`
- `atr/storage/__init__.py`
- `atr/storage/writers/release.py`
- `atr/storage/writers/tokens.py`
- `atr/storage/writers/user.py`
- `atr/principal.py`
- `atr/user.py`
### Related issues
This issue appears related to: #979.
_Both address missing documentation of authorization rules and
resource-intensive operations_
---
*Draft from a triage agent. A human reviewer should validate before merging
any change. The agent did not run tests or verify diffs apply.*
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]