asf-tooling commented on issue #708:
URL:
https://github.com/apache/tooling-trusted-releases/issues/708#issuecomment-4410102548
<!-- gofannon-issue-triage-bot v2 -->
**Automated triage** — analyzed at `main@2da7807a`
**Type:** `new_feature` • **Classification:** `actionable` •
**Confidence:** `medium`
**Application domain(s):** `cryptographic_keys`
### Summary
Issue #708 requests a 'historical only' flag for OpenPGP keys that were
previously used to sign releases but are no longer valid for current use. The
flag would preserve these keys for record-keeping while excluding them from
active KEYS file generation. @dave2wave noted this should be implemented
alongside #911. Currently no such flag exists on the `PublicSigningKey` model.
The feature requires a new database column, UI to toggle it, and logic to
filter historical keys from active KEYS files.
### Where this lives in the code today
#### `atr/storage/writers/keys.py` — `FoundationCommitter.public_key_model`
(lines 235-261)
_extension point_
This is where PublicSigningKey models are constructed; a new `historical`
field would default to False here.
```python
def public_key_model(
self,
key: openpgp.PublicKey,
ldap_data: dict[str, str],
original_key_block: str | None = None,
) -> sql.PublicSigningKey:
uids = list(key.user_ids)
asf_uid = self.__uids_asf_uid(uids, ldap_data)
if not uids:
raise ValueError("No UIDs found in key")
# Use the original key block if available
ascii_armored = original_key_block if original_key_block else
key.to_armored()
expires_at = _key_expires_at(key)
return sql.PublicSigningKey(
fingerprint=key.fingerprint.lower(),
algorithm=_algorithm_id(key.public_key_algorithm),
length=_key_length(key),
created=datetime.datetime.fromtimestamp(key.created_at,
tz=datetime.UTC),
latest_self_signature=_latest_self_signature_created_at(key),
expires=expires_at,
primary_declared_uid=uids[0],
secondary_declared_uids=uids[1:],
apache_uid=asf_uid,
ascii_armored_key=ascii_armored,
)
```
#### `atr/storage/writers/keys.py` — `FoundationCommitter._keys_file_text`
(lines 269-287)
_needs modification_
KEYS file generation iterates all committee keys; needs filtering to exclude
historical-only keys from active KEYS files.
```python
async def _keys_file_text(self, committee: sql.Committee) -> str:
if not committee.public_signing_keys:
raise storage.AccessError(f"No keys found for committee
{committee.key} to generate KEYS file.", status=404)
sorted_keys = sorted(committee.public_signing_keys, key=lambda k:
k.fingerprint)
keys_content_list = []
for key in sorted_keys:
apache_uid = key.apache_uid.lower() if key.apache_uid else None
# TODO: What if there is no email?
email = util.email_from_uid(key.primary_declared_uid or "") or ""
comments = []
comments.append(f"Comment: {key.fingerprint.upper()}")
if (apache_uid is None) or (email == f"{apache_uid}@apache.org"):
comments.append(f"Comment: {email}")
else:
comments.append(f"Comment: {email} ({apache_uid})")
comment_lines = "\n".join(comments)
armored_key = key.ascii_armored_key
```
#### `atr/shared/keys.py` — `UpdateKeyCommitteesForm` (lines 105-109)
_extension point_
This form handles key detail updates; the historical flag toggle could be
added here or as a separate form.
```python
class UpdateKeyCommitteesForm(form.Form):
selected_committees: form.StrList = form.label(
"Associated PMCs",
widget=form.Widget.CUSTOM,
)
```
### Where new code would go
- `atr/models/sql.py` — in PublicSigningKey model class
Add a `historical: bool = False` column to the PublicSigningKey SQLModel
to persist the flag.
- `atr/storage/writers/keys.py` — after
FoundationCommitter.update_committee_associations
Add a method like `mark_key_historical(fingerprint, historical: bool)` to
toggle the flag.
- `atr/post/keys.py` — after details function
Add a POST handler for toggling the historical flag on a key.
### Proposed approach
The implementation requires adding a `historical` boolean column (defaulting
to `False`) to the `PublicSigningKey` model in `atr/models/sql.py`. This flag
indicates that a key is retained for record-keeping only and should not be
included in actively-generated KEYS files.
The `_keys_file_text` method in `atr/storage/writers/keys.py` should filter
out keys where `historical=True` when generating committee KEYS files (or
include them in a clearly-separated 'historical' section). A new writer method
`mark_key_historical` should allow toggling the flag. The key details page
(`atr/get/keys.py`) should display the historical status and provide a toggle
for the key owner. As @dave2wave suggested, this should be coordinated with
issue #911 for a cohesive implementation.
### Suggested patches
#### `atr/models/sql.py`
Add historical flag to PublicSigningKey model (exact location depends on
model definition, which is not in the provided files)
````diff
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ # In the PublicSigningKey class definition, add:
+ historical: bool = sqlmodel.Field(default=False, description="Key is
retained for historical record only and should not be used for current signing")
````
#### `atr/storage/writers/keys.py`
Filter out historical keys from active KEYS file generation
````diff
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ -229,7 +229,8 @@ class FoundationCommitter(GeneralPublic):
async def _keys_file_text(self, committee: sql.Committee) -> str:
if not committee.public_signing_keys:
raise storage.AccessError(f"No keys found for committee
{committee.key} to generate KEYS file.", status=404)
- sorted_keys = sorted(committee.public_signing_keys, key=lambda k:
k.fingerprint)
+ active_keys = [k for k in committee.public_signing_keys if not
k.historical]
+ sorted_keys = sorted(active_keys, key=lambda k: k.fingerprint)
keys_content_list = []
for key in sorted_keys:
````
#### `atr/storage/writers/keys.py`
Add method to toggle historical flag on a key
````diff
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ # After update_committee_associations method in FoundationCommitter class:
+ async def set_key_historical(self, fingerprint: str, historical: bool)
-> outcome.Outcome[sql.PublicSigningKey]:
+ """Mark a key as historical-only (not for current use) or restore
it to active status."""
+ try:
+ key = await self.__data.public_signing_key(
+ fingerprint=fingerprint,
+ apache_uid=self.__asf_uid,
+ _committees=True,
+ ).demand(storage.AccessError(f"Key not found: {fingerprint}",
status=404))
+ key.historical = historical
+ await self.__data.commit()
+ self.__write_as.append_to_audit_log(
+ action="key_set_historical",
+ asf_uid=self.__asf_uid,
+ fingerprint=key.fingerprint,
+ historical=historical,
+ )
+ # Regenerate KEYS files for affected committees
+ affected_committee_keys = {committee.key for committee in
key.committees}
+ for committee_key in sorted(affected_committee_keys):
+ await self._sync_committee_keys_file(committee_key)
+ return outcome.Result(key)
+ except Exception as e:
+ return outcome.Error(e)
````
### Open questions
- What is issue #911 that @dave2wave referenced? The implementation should
be coordinated with that issue.
- Should historical keys still appear in KEYS files in a separate section
(e.g., for verification of old releases), or be excluded entirely?
- The exact location of the `PublicSigningKey` model in `atr/models/sql.py`
is not visible in the provided files - the diff for that file needs to be
anchored to the actual model definition.
- Should there be an automatic mechanism to mark keys as historical when
they expire, or should this always be a manual action?
- Should PMC members (not just key owners) be able to mark keys as
historical for their committee?
### Files examined
- `atr/storage/writers/keys.py`
- `atr/storage/writers/ssh.py`
- `atr/post/keys.py`
- `atr/get/keys.py`
- `atr/shared/keys.py`
- `atr/tasks/keys.py`
---
*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]