AlmaLinux / AlmaLinux/build-system

Require and verify RPM file (IMA) signatures for selected platforms

オープン
#546 コメント 0 件 リアクション 0 件 担当者 1 名 @anfimovdm が担当を希望しています GitHub で見る
主要言語
言語のデータがありません
スター
32
フォーク
11
PR マージ指標
30日以内にマージされた PR はありません

説明

# Require and verify RPM file (IMA) signatures for selected platforms

## Summary

ALBS can sign files inside RPMs (`rpmsign --signfiles`), but nothing verifies that it actually happened — a package can go through the whole pipeline with no file signatures and every stage reports success.

Proposal, in two parts:

1. **albs-web-server** — include the platform in the sign task payload, so the sign node knows what it is signing.
2. **albs-sign-node** — a new config option listing the platforms whose packages must carry file signatures. For those platforms the node refuses to run a task that was not created with `sign_files`, and verifies that the signed packages really carry file signatures.

Keeping the switch in the node's own config (rather than platform config or the DB) keeps dev and community deployments untouched — the option is simply absent there and behaviour is identical to today.

## Current state

`Signer._check_signature()` (`sign_node/signer.py:186`, called at `signer.py:497`) already runs a post-sign verification, but it only inspects `RPMTAG_SIGGPG` / `RPMTAG_SIGPGP` — the **header** signature — and only asserts it was made by the expected key. It never looks at `RPMTAG_FILESIGNATURES`.

File signing is opt-in and silently defaults to off:

- `sign_node/signer.py:390` — `sign_files = task.get('sign_files', False)`
- `sign_node/package_sign.py:94-99` — `--signfiles --fskpath ` appended only when true
- the value comes from `sign_keys.add_files_signature` (`alws/crud/sign_task.py:290`), a nullable per-key column absent from `SignKeyCreate`, `SignKeyUpdate`, the `SignKey` response schema and albs-frontend — settable only by raw SQL

And the node cannot scope any rule by distro today: the sign task payload has **no platform field**. Packages carry only `id`, `name`, `cas_hash`, `arch`, `type`, `download_url` (`alws/crud/sign_task.py:302-309`, `:319-327`); the task carries `id`, `build_id`, `keyid`, `sign_files`.

## Part 1 — albs-web-server: return the platform in the sign task payload

A build can target several platforms (`alws/schemas/build_schema.py:129`), so one sign task can span platforms — the platform therefore belongs on each package, not on the task.

Everything needed is already loaded. `get_available_sign_task()` (`alws/crud/sign_task.py:234`) eager-loads `BuildTaskArtifact.build_task` for source and binary RPMs and reads `…artifact.build_task.platform_id` a few lines below to pick the repo (`:299`, `:316`). `BuildTask.platform` is a plain relationship (`alws/models.py:595`), so extending the existing `selectinload` chain gives the name too.

Changes:

- extend the eager load: `selectinload(BuildTaskArtifact.build_task).selectinload(BuildTask.platform)`;
- add `platform_id` and `platform_name` to each entry in `packages` (`:302-309`, `:319-327`);
- add both as **optional** fields on `SignRpmInfo` (`alws/schemas/sign_schema.py:67`).

Compatibility:

- older sign nodes read the payload with `.get()` and ignore unknown keys — no impact;
- the node copies each package dict into its completion payload (`sign_node/signer.py:416-419`), so the new keys round-trip back into `SignedRpmInfo`, where pydantic ignores unknown fields. Harmless, but they can be popped alongside `download_url` if we prefer a clean payload.

Nothing about this change alters behaviour on its own — it only makes the platform visible to the node.

## Part 2 — albs-sign-node: new config option

Following the existing `yubikey_keyids` style (`sign_node/config.py:58`, schema at `:90`):

```yaml
# Platforms whose packages must carry file (IMA) signatures.
# Absent or empty -> current behaviour, nothing changes.
require_files_signature_platforms:
- AlmaLinux-10
```

Platform **names** rather than ids, since ids are per-environment and names are stable and readable.

Behaviour:

1. **Refuse tasks that would produce unsigned files.** If any package in the task belongs to a listed platform and the task was created with `sign_files: false`, fail the task immediately with an explicit error:

```python
sign_files = task.get('sign_files', False)
required = set(self.__config.require_files_signature_platforms or [])
task_platforms = {p.get('platform_name') for p in task['packages']}
matched = task_platforms & required
if matched and not sign_files:
raise SignError(
f'Task {task["id"]}: platform(s) {sorted(matched)} require file '
f'signatures, but the task was created with sign_files=false'
)
```

The node deliberately does **not** override `sign_files` and sign anyway: the web server stays the single source of truth, and a cleared `add_files_signature` surfaces immediately instead of being silently compensated for.

Implementation note: this check must live **inside** the `try:` block at `signer.py:409`, not next to the `sign_files` assignment at `:390`. Anything raised before the `try` escapes `_sign_build()` uncaught, so `finally: self._report_signed_build(...)` never runs and the task stays `IN_PROGRESS` instead of failing cleanly with a visible message.

2. **Fail on missing platform info.** If the option is set but the payload carries no `platform_name` (older web server), fail the task with an explicit error rather than silently skipping — silently skipping is exactly the failure mode this issue exists to remove.

3. **Verify after signing.** Defence in depth — `sign_files: true` does not prove the signatures landed. Extend `_check_signature()` with a `NO_FILE_SIGNATURE` status: read `RPMTAG_FILESIGNATURES` / `RPMTAG_FILESIGNATURELENGTH` and fail the task through the same path as the existing `Package %s is not signed` error, before upload.

4. **Only regular files count.** Directories and symlinks legitimately have empty signature entries. Filter on `FILEMODES & S_IFMT == S_IFREG` — otherwise a correctly signed package fails the check. Real example: `almalinux-release` ships `/etc/centos-release` and `/etc/os-release` as symlinks, both with empty `%{FILESIGNATURES}` entries in an otherwise fully signed package. Packages with no regular files at all (`basesystem`) must pass too.

5. **Source RPMs are included.** SRPMs do carry file signatures when file signing is on — e.g. `rootfiles-8.1-41.el10.src.rpm` (signed 2025-03-04) has signatures on every source and on the spec file — so `arch: src` packages are subject to the same requirement.

6. **Everything else unchanged.** Platforms not listed behave exactly as today, so dev envs, community sign nodes and other distros are unaffected.

## Rollout order

Because the node now refuses rather than compensates, enabling a platform whose key still has `add_files_signature` NULL/false stops signing for that platform outright. Sequence:

1. deploy the web-server change (platform in payload);
2. set `add_files_signature` for the platform's sign key and confirm tasks produce file signatures;
3. add the platform to `require_files_signature_platforms` on the sign nodes.

## Acceptance criteria

- [ ] Sign task payload includes `platform_id` and `platform_name` per package; older nodes are unaffected.
- [ ] `sign_node.yml` accepts `require_files_signature_platforms`; absent/empty means current behaviour.
- [ ] A task for a listed platform with `sign_files: false` fails immediately, before downloading/signing, with an explicit error.
- [ ] That failure is reported to the web server (task ends `FAILED` with the message visible), not left hanging `IN_PROGRESS`.
- [ ] A task for a listed platform whose signed packages lack file signatures fails before upload.
- [ ] Symlinks, directories and packages with no regular files produce no false positives.
- [ ] A task spanning listed and unlisted platforms is handled correctly.
- [ ] Tests, web server: payload contains platform for source and binary RPMs, multi-platform build.
- [ ] Tests, node: `sign_files` false + platform listed, signed-with-files, signed-without-files, symlink-only/metapackage, platform-not-listed, platform info absent from payload.

## Prerequisite

`/etc/pki/ima/ima-sign.key` (the default `files_sign_cert_path`, `sign_node/config.py:78`) is not provisioned by albs-deploy on any sign node. A missing key makes `rpmsign --signfiles` fail the entire task, so key provisioning must land on the target nodes before the option is enabled.

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。