release: public alpha 0.6.0 baseline
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
# Extension Release Artifacts
|
||||
|
||||
Status: digest verification, OpenSSH publisher signing, and local atomic
|
||||
activation implemented; production host provisioning and activation are not yet
|
||||
implemented.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
./tools/package-plugin.py examples/hello-command --output dist
|
||||
```
|
||||
|
||||
The builder:
|
||||
|
||||
1. validates `plugin.toml`;
|
||||
2. applies the static source policy;
|
||||
3. rejects symlinks and unsupported runtime files;
|
||||
4. bounds file count and expanded size;
|
||||
5. hashes canonical relative paths and bytes;
|
||||
6. creates a deterministic gzip/tar artifact;
|
||||
7. writes a canonical release record.
|
||||
|
||||
Output names are content-addressed:
|
||||
|
||||
```text
|
||||
<plugin-id>-<version>-<source-digest-prefix>.tar.gz
|
||||
<plugin-id>-<version>-<source-digest-prefix>.release.json
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```sh
|
||||
./tools/verify-release.py \
|
||||
dist/<artifact>.tar.gz \
|
||||
dist/<artifact>.release.json
|
||||
```
|
||||
|
||||
Verification does not extract the archive. It checks:
|
||||
|
||||
- artifact name, size, and SHA-256;
|
||||
- regular-file-only tar members;
|
||||
- no absolute paths or `..` traversal;
|
||||
- fixed `plugin/` archive root;
|
||||
- bounded expanded size and file count;
|
||||
- exact file list;
|
||||
- canonical source digest.
|
||||
|
||||
## Determinism
|
||||
|
||||
File modification times, owners, groups, and host paths are not included in the
|
||||
artifact. Identical reviewed inputs produce identical artifact bytes and
|
||||
digests.
|
||||
|
||||
If a content-addressed destination already exists with different bytes, the
|
||||
builder fails instead of replacing it.
|
||||
|
||||
## Release Record
|
||||
|
||||
The JSON record includes:
|
||||
|
||||
- schema version `2`;
|
||||
- plugin ID, manifest author, and plugin version;
|
||||
- SDK API version;
|
||||
- source digest;
|
||||
- artifact file name;
|
||||
- artifact SHA-256 and size;
|
||||
- exact runtime file list.
|
||||
|
||||
## Publisher Signing
|
||||
|
||||
Signing uses the OpenSSH `sshsig` format with namespace
|
||||
`jwebot-release-v1`. Its domain-separated payload binds the publisher
|
||||
principal, release filename, and exact canonical release JSON. The release JSON
|
||||
already binds the artifact digest, size, source digest, author, and file list.
|
||||
|
||||
Create an Ed25519 publisher key outside the repository:
|
||||
|
||||
```sh
|
||||
ssh-keygen -t ed25519 -f ~/.config/jwebot/publisher -C jwebot-publisher
|
||||
```
|
||||
|
||||
Create an OpenSSH allowed-signers file containing only reviewed public keys:
|
||||
|
||||
```text
|
||||
example-publisher ssh-ed25519 AAAA... jwebot-publisher
|
||||
```
|
||||
|
||||
The verifier intentionally accepts only simple entries with explicit
|
||||
lowercase publisher principals and Ed25519 keys. Wildcards, signer options, and
|
||||
other key algorithms are rejected.
|
||||
|
||||
Build and sign in one step:
|
||||
|
||||
```sh
|
||||
./tools/package-plugin.py examples/hello-command \
|
||||
--output dist \
|
||||
--signing-key ~/.config/jwebot/publisher \
|
||||
--publisher example-publisher
|
||||
```
|
||||
|
||||
An existing canonical release record can be signed separately:
|
||||
|
||||
```sh
|
||||
./tools/sign-release.py dist/<release>.release.json \
|
||||
--private-key ~/.config/jwebot/publisher \
|
||||
--publisher example-publisher
|
||||
```
|
||||
|
||||
The signing operation first verifies the referenced artifact's name, digest,
|
||||
size, archive structure, file list, and source digest. It refuses to sign a
|
||||
record whose sibling artifact is missing or inconsistent.
|
||||
|
||||
The private key must never be committed, copied into an extension artifact, or
|
||||
installed on production host. The target receives only the reviewed public-key
|
||||
allowed-signers file.
|
||||
|
||||
## Security Boundary
|
||||
|
||||
Digest verification proves content integrity relative to the release record.
|
||||
Publisher verification proves that the exact canonical record was signed by a
|
||||
key authorized for the declared publisher principal. The manifest `author`
|
||||
identifies who wrote the plugin; signature `publisher` identifies who approved
|
||||
and released those exact bytes.
|
||||
|
||||
Before production activation, a later phase must still add:
|
||||
|
||||
- reviewed public-key provisioning on production host;
|
||||
- isolated extension-host health checks;
|
||||
- target Debian `systemd-analyze verify` and socket protocol checks.
|
||||
|
||||
production host must never execute a mutable branch checkout or ordinary `git pull`.
|
||||
|
||||
## Local Activation Simulation
|
||||
|
||||
Activation requires an explicit state root:
|
||||
|
||||
```sh
|
||||
./tools/activate-release.py \
|
||||
dist/<artifact>.tar.gz \
|
||||
dist/<artifact>.release.json \
|
||||
--signature dist/<release>.release.sig.json \
|
||||
--allowed-signers ~/.config/jwebot/allowed_signers \
|
||||
--publisher example-publisher \
|
||||
--state-root /tmp/jwebot-extension-state
|
||||
```
|
||||
|
||||
The activator:
|
||||
|
||||
- verifies the artifact before writing release data;
|
||||
- verifies the publisher signature against the explicit trust file;
|
||||
- installs under `releases/<full-source-digest>/`;
|
||||
- preserves the signature record beside the installed release record;
|
||||
- writes files with private permissions;
|
||||
- re-verifies an already installed release before reuse;
|
||||
- starts the isolated Host and requires a matching health response before
|
||||
switching `current`;
|
||||
- switches a relative `current` symlink atomically;
|
||||
- preserves the prior verified target as `previous`;
|
||||
- serializes changes with a file lock.
|
||||
|
||||
Rollback:
|
||||
|
||||
```sh
|
||||
./tools/rollback-release.py \
|
||||
--state-root /tmp/jwebot-extension-state \
|
||||
--allowed-signers ~/.config/jwebot/allowed_signers \
|
||||
--publisher example-publisher
|
||||
```
|
||||
|
||||
Rollback re-verifies the destination content and publisher signature, then
|
||||
atomically swaps `current` and `previous`. It does not execute plugin code.
|
||||
|
||||
Unsigned activation is rejected by default. Tests may opt into the explicit
|
||||
`--allow-unsigned-local` flag; that flag cannot be combined with signature
|
||||
arguments and must never be used for production state.
|
||||
|
||||
Local packaging tests that cannot launch the installed Host may use
|
||||
`--skip-health-check-local`. This is also an explicit non-production override;
|
||||
signed production activation fails closed when no health check is supplied.
|
||||
|
||||
These commands have no default production path. A caller must provide
|
||||
`--state-root`. The repository does not invoke them against production host.
|
||||
Reference in New Issue
Block a user