v0,28,2
This commit is contained in:
1 parent
d940f9995f
commit
65e082b9d1
61 files changed
+1225
-581
No files matched your search
+14
-10
@@ -88,7 +88,7 @@ and rechecks session validity. It does not queue an unlimited per-viewer event l
|
||||
or guarantee replay. The browser falls back to polling. An active dashboard renews
|
||||
idle activity, not the absolute session deadline.
|
||||
|
||||
Configuration history retains the newest 512 successful manual/configuration revisions in SQLite schema 8. `GET /api/config/history` is session-protected like the rest of the administration API; `limit` is bounded to 512 and `revision` selects one retained revision for diff/inspection. `POST /api/config/history/restore` also requires the normal CSRF/request-marker protections, rejects stale `expected_revision`, and never rewinds the revision counter: restoring revision 85 while current revision is 120 creates revision 121.
|
||||
Configuration history, introduced in SQLite schema 8 and retained in current schema 9, keeps the newest 512 successful manual/configuration revisions. `GET /api/config/history` is session-protected like the rest of the administration API; `limit` is bounded to 512 and `revision` selects one retained revision for diff/inspection. `POST /api/config/history/restore` also requires the normal CSRF/request-marker protections, rejects stale `expected_revision`, and never rewinds the revision counter: restoring revision 85 while current revision is 120 creates revision 121.
|
||||
|
||||
Configuration updates return 409 for a stale revision, 422 for validation failure
|
||||
and 429 when the eight-operation update admission bound is reached. Persistence and
|
||||
@@ -380,8 +380,8 @@ contracts and INTEGRATIONS_040.md for configuration examples.
|
||||
|
||||
## 0.22.3 certificate hot reload and external deployment
|
||||
|
||||
`POST /api/certificates/reload` is an authenticated, Origin/CSRF-protected control-plane
|
||||
operation. It re-reads every certificate/key file referenced by the current desired
|
||||
`POST /api/certificates/reload` is an administrator-session-only, Origin/CSRF-protected control-plane
|
||||
operation. Certificate API bearer tokens are intentionally not accepted by this global endpoint. It re-reads every certificate/key file referenced by the current desired
|
||||
configuration, validates a fresh immutable routing/TLS snapshot and publishes it at the
|
||||
**same configuration revision**. Invalid or mismatched material returns an error and the
|
||||
previous snapshot stays active. The response contains `reloaded`, `revision`, `public_tls`,
|
||||
@@ -389,7 +389,7 @@ previous snapshot stays active. The response contains `reloaded`, `revision`, `p
|
||||
handshakes use the refreshed certificates; established connections are not renegotiated.
|
||||
Administration TLS is startup-fixed and therefore reports when a process restart is needed.
|
||||
|
||||
`POST /api/certificates/{certificate_id}/reload` is the targeted variant for a manual/file-backed certificate (`issuer == null`). It validates the requested configured ID and atomically republishes the immutable TLS snapshot at the same configuration revision. It is intended for external renewers that replace the files already referenced by RustProxy. A manual entry may use simple filenames from `<data-dir>/certs` or two absolute external paths such as `/etc/letsencrypt/live/example/fullchain.pem` and `/etc/letsencrypt/live/example/privkey.pem`. Absolute paths are resolved again on every reload, so Certbot's `live/` symlinks follow the newly renewed generation. Application-managed ACME certificate IDs are rejected with `409` because their renewal pipeline owns those files. The endpoint accepts either the normal administrator session + CSRF flow or a scoped Certificate API token.
|
||||
`POST /api/certificates/{certificate_id}/reload` is the targeted variant for a manual/file-backed certificate (`issuer == null`). It validates the requested configured ID and atomically republishes the immutable TLS snapshot at the same configuration revision. It is intended for external renewers that replace the files already referenced by RustProxy. A manual entry may use simple filenames from `<data-dir>/certs` or two absolute external paths such as `/etc/letsencrypt/live/example/fullchain.pem` and `/etc/letsencrypt/live/example/privkey.pem`. Absolute paths are resolved again on every reload, so Certbot's `live/` symlinks follow the newly renewed generation. Application-managed ACME certificate IDs are rejected with `409` because their renewal pipeline owns those files. The endpoint accepts either the normal administrator session + CSRF flow or a Certificate API token whose grant explicitly contains that certificate ID.
|
||||
|
||||
`POST /api/certificates/deploy` accepts
|
||||
`{"certificate_id":"shared-cert","cert_pem":"...","key_pem":"..."}` under the same
|
||||
@@ -418,9 +418,11 @@ the generic PEM staging endpoint; deploy/upload request bodies never accept host
|
||||
paths. Absolute paths may only be saved explicitly in administrator TLS configuration as a
|
||||
manual certificate source, where they are validated and never returned as PEM contents.
|
||||
|
||||
## 0.23.0 scoped Certificate API credentials
|
||||
## 0.28.2 least-privilege Certificate API credentials
|
||||
|
||||
External automation no longer needs a full administrator browser session. From **Settings -> Certificate API**, an administrator can create multiple dedicated credentials (up to 32), each consisting of a Key ID, Bearer token and optional non-secret comment. Each Key ID can be rotated independently. The clear token is returned only by the create/rotation response; SQLite stores only its SHA-256 hash plus metadata/comment. Every credential has exactly two scopes: certificate deploy and certificate reload.
|
||||
External automation does not need a full administrator browser session. From **Settings -> Certificate API**, an administrator can create multiple dedicated credentials (up to 32), each consisting of a Key ID, Bearer token, optional non-secret comment and an explicit allowlist of 1-32 certificate IDs. New credentials are **reload-only by default**. Deploy is an independent opt-in permission.
|
||||
|
||||
The clear token is returned only by the create/rotation response; SQLite stores only its SHA-256 hash plus metadata and the grant. Credentials created before 0.28.2 migrate to a reload-only compatibility grant for all certificate IDs (`*`). Rotate those credentials after upgrading to replace the wildcard with explicit IDs.
|
||||
|
||||
API clients send both headers:
|
||||
|
||||
@@ -429,7 +431,7 @@ Authorization: Bearer <token>
|
||||
X-RustProxy-Certificate-Key: <key-id>
|
||||
```
|
||||
|
||||
Example Certbot/acme.sh post-renew reload for an external-path certificate:
|
||||
A reload-only token may call only the targeted endpoint for an allowlisted certificate:
|
||||
|
||||
```bash
|
||||
curl --fail --show-error -X POST \
|
||||
@@ -440,14 +442,16 @@ curl --fail --show-error -X POST \
|
||||
https://proxy-admin.example/api/certificates/shared-cert/reload
|
||||
```
|
||||
|
||||
A Certificate API credential is accepted only by `POST /api/certificates/deploy`, `POST /api/certificates/reload`, and `POST /api/certificates/{certificate_id}/reload`; it cannot read configuration, logs, metrics or any other administration endpoint. Non-loopback use requires the configured HTTPS administration surface. The normal administrator session + Origin/request-marker + CSRF path remains valid for the same operations. Credential management remains administrator-only: `GET /api/certificate-api` lists metadata, `PUT /api/certificate-api` creates/rotates one Key ID, `DELETE /api/certificate-api/{key_id}` revokes one token, and `DELETE /api/certificate-api` revokes all tokens.
|
||||
Certificate API tokens are **not** accepted by the global `POST /api/certificates/reload` endpoint. This prevents a compromised renewal token from forcing reads/reloads of unrelated certificate material. `POST /api/certificates/deploy` is available only when the credential was explicitly created with deploy permission, and the `certificate_id` in the request body must also be present in that credential's allowlist.
|
||||
|
||||
Example external deploy:
|
||||
A Certificate API credential cannot read configuration, logs, metrics, users or any other administration endpoint. Non-loopback use requires the configured HTTPS administration surface. The normal administrator session + Origin/request-marker + CSRF path remains valid for certificate operations. Credential management remains administrator-only: `GET /api/certificate-api` lists metadata/grants, `PUT /api/certificate-api` creates or rotates one Key ID, `DELETE /api/certificate-api/{key_id}` revokes one token, and `DELETE /api/certificate-api` revokes all tokens.
|
||||
|
||||
Example external deploy for a token that has **Also allow deploy** enabled and contains `shared-cert` in its allowlist:
|
||||
|
||||
```bash
|
||||
curl --fail --show-error \
|
||||
-H 'Authorization: Bearer TOKEN' \
|
||||
-H 'X-RustProxy-Certificate-Key: certbot-main' \
|
||||
-H 'X-RustProxy-Certificate-Key: deploy-hook' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-binary @deploy.json \
|
||||
https://proxy-admin.example/api/certificates/deploy
|
||||
|
||||
+8
-8
@@ -1,4 +1,4 @@
|
||||
# Administration API access - 0.28.1
|
||||
# Administration API access - 0.28.2
|
||||
|
||||
## Sign-in boundary
|
||||
|
||||
@@ -101,15 +101,15 @@ by Swagger UI. Native logout/password writes can invalidate the browser session;
|
||||
sign in again afterward. Ordinary API mutations still require the administrator session,
|
||||
Origin/request marker and CSRF token.
|
||||
|
||||
0.23.0 adds one deliberately narrow exception for certificate automation. An operator can
|
||||
create one or more **Certificate API Key ID + Bearer token** credentials in Settings -> Certificate API. Each credential may carry an operator comment and authorizes only:
|
||||
0.28.2 keeps the deliberately narrow Certificate API exception for automation and makes it least-privilege. An operator can create one or more **Certificate API Key ID + Bearer token** credentials in Settings -> Certificate API. Each new credential requires an explicit allowlist of 1-32 certificate IDs and is reload-only by default. Deploy access must be enabled separately.
|
||||
|
||||
- `POST /api/certificates/deploy`
|
||||
- `POST /api/certificates/reload`
|
||||
- `POST /api/certificates/{certificate_id}/reload`
|
||||
A reload-only credential authorizes only:
|
||||
|
||||
Both `Authorization: Bearer <token>` and `X-RustProxy-Certificate-Key: <key-id>` are required.
|
||||
The clear token is returned only on creation/rotation; the database stores its SHA-256 hash. Up to 32 Key IDs can coexist, and each can be revoked independently or all at once. The credential does not authorize configuration, logs, metrics, users or any other endpoint. Non-loopback use is accepted only on the configured HTTPS administration surface. Revocation from Settings invalidates that credential immediately.
|
||||
- `POST /api/certificates/{certificate_id}/reload` when that ID is allowlisted.
|
||||
|
||||
`POST /api/certificates/reload` is administrator-session only and never accepts a Certificate API token. `POST /api/certificates/deploy` accepts a Certificate API token only when the credential has explicit deploy permission and the request's `certificate_id` is also allowlisted.
|
||||
|
||||
Both `Authorization: Bearer <token>` and `X-RustProxy-Certificate-Key: <key-id>` are required. The clear token is returned only on creation/rotation; the database stores its SHA-256 hash. Up to 32 Key IDs can coexist, and each can be revoked independently or all at once. Credentials created before 0.28.2 migrate to reload-only compatibility access for all certificate IDs (`*`); rotate them to narrow the allowlist. The credential does not authorize configuration, logs, metrics, users or any other endpoint. Non-loopback use is accepted only on the configured HTTPS administration surface. Revocation from Settings invalidates that credential immediately.
|
||||
|
||||
Local host automation does not need this token. The installed binary can run
|
||||
`rust-proxy tls deploy ...` or `rust-proxy tls reload` directly; it obtains short-lived local
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ each Docker host, so the proxy container never needs Docker-socket access. See
|
||||
Build locally with a normal OCI builder:
|
||||
|
||||
```bash
|
||||
docker build -t rustproxy:0.28.1 .
|
||||
docker build -t rustproxy:0.28.2 .
|
||||
```
|
||||
|
||||
Persist `/var/lib/rustproxy`; it contains the SQLite database, certificates and the
|
||||
@@ -22,7 +22,7 @@ docker volume create rustproxy-data
|
||||
docker run --rm -d --name rustproxy \
|
||||
-p 8080:8080 \
|
||||
-v rustproxy-data:/var/lib/rustproxy \
|
||||
rustproxy:0.28.1
|
||||
rustproxy:0.28.2
|
||||
|
||||
docker exec rustproxy rust-proxy admin setup-token
|
||||
```
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Stored log explorer
|
||||
|
||||
RustProxy 0.28.1 provides an authenticated, read-only **Logs** workspace at `/logs`. The top bar has one **Logs** menu with separate **Stored logs** and **Live Logs** actions; the two data paths remain independent.
|
||||
RustProxy 0.28.2 provides an authenticated, read-only **Logs** workspace at `/logs`. The top bar has one **Logs** menu with separate **Stored logs** and **Live Logs** actions; the two data paths remain independent.
|
||||
|
||||
## Separation from Live Logs
|
||||
|
||||
|
||||
+40
-5
@@ -1,4 +1,4 @@
|
||||
# Installing and updating RustProxy 0.28.1
|
||||
# Installing and updating RustProxy 0.28.2
|
||||
|
||||
The scripts prepare a Linux/systemd installation. This source archive is **not a
|
||||
verified production build**: Rust/Cargo, real CA operations, live systemd startup
|
||||
@@ -201,7 +201,7 @@ The provider script is embedded in the RustProxy binary; this command is diagnos
|
||||
If another service already owns a certificate through the system Certbot, keep that lineage
|
||||
outside RustProxy's managed ACME profiles and create/use a **manual** RustProxy certificate id.
|
||||
|
||||
The preferred 0.28.1 workflow is to select **Existing server files / external paths** and then
|
||||
The preferred 0.28.2 workflow is to select **Existing server files / external paths** and then
|
||||
**External files (absolute paths)** in the Certificates panel. For example:
|
||||
|
||||
```text
|
||||
@@ -223,9 +223,44 @@ curl --fail --show-error -X POST \
|
||||
```
|
||||
|
||||
The `rustproxy` service identity must be able to traverse the parent directories and read both
|
||||
files. An external private key may be `0600` when owned by that identity or `0640` with a
|
||||
restricted group readable by RustProxy; world access, group write and executable bits are
|
||||
rejected. These external files are not included in a RustProxy backup.
|
||||
files. RustProxy 0.28.2 supports POSIX ACLs and does not require one exact chmod value. External
|
||||
certificate/key files must not be group/world-writable, and an external private key must not be
|
||||
world-readable. Named ACL read access for `rustproxy` is accepted; the operating system still
|
||||
performs the final access check when RustProxy opens the file. Every parent directory in the
|
||||
configured and resolved path must be owned by root or the RustProxy service user and must not be
|
||||
group/world-writable (root-owned sticky ancestors such as `/tmp` are the narrow exception).
|
||||
These external files are not included in a RustProxy backup.
|
||||
|
||||
The packaged systemd service keeps `/etc/letsencrypt` and `/etc/ssl` read-only. `ReadOnlyPaths=`
|
||||
does **not** grant Unix permissions, so use narrow ACLs instead of making private keys globally
|
||||
readable. For example:
|
||||
|
||||
```bash
|
||||
# Traversal only for the protected roots.
|
||||
setfacl -m u:rustproxy:--x /etc/ssl/
|
||||
setfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/
|
||||
|
||||
# Traversal only for this Certbot lineage.
|
||||
setfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/
|
||||
|
||||
# Read only the certificate/key material RustProxy actually needs.
|
||||
setfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem \
|
||||
/etc/letsencrypt/archive/example.com/privkey*.pem
|
||||
|
||||
# Optional default ACL for renewers that preserve inherited ACL masks.
|
||||
setfacl -m d:u:rustproxy:r-- /etc/letsencrypt/archive/example.com/
|
||||
|
||||
# Robust renewal-hook form: re-grant read access to the current generation before API reload.
|
||||
setfacl -m u:rustproxy:r-- \
|
||||
"$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)" \
|
||||
"$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)"
|
||||
```
|
||||
|
||||
Because `/etc/letsencrypt/live/.../*.pem` are symlinks into `archive/`, access must cover the
|
||||
resolved archive files. Some renewal tools recreate private keys as `0600` and may mask an
|
||||
inherited default ACL; therefore the renewal wrapper should reapply the named file ACL before
|
||||
calling the RustProxy reload API. Never grant `w` to `rustproxy` on the Certbot tree. Verify a deployment
|
||||
with `sudo -u rustproxy test -r /etc/letsencrypt/live/example.com/privkey.pem && echo OK`.
|
||||
|
||||
If you do not want the RustProxy service to read `/etc/letsencrypt`, keep the previous copy/deploy
|
||||
workflow instead. A Certbot deploy hook can publish every successful renewal into RustProxy's
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Deployment and configuration migration history
|
||||
|
||||
## 0.28.1 -> 0.28.2
|
||||
|
||||
SQLite schema 8 is upgraded automatically to schema 9. The additive migration creates `certificate_api_grants`. Existing Certificate API credentials become reload-only compatibility credentials covering all certificate IDs (`*`); rotate them in **Settings -> Certificate API** to replace the wildcard with explicit certificate IDs. New credentials require 1-32 explicit certificate IDs and are reload-only unless deploy permission is enabled.
|
||||
|
||||
The saved proxy configuration document and COLD cache format are unchanged. External certificate paths keep their 0.28.1 semantics, but 0.28.2 validates the full configured/resolved parent chain and accepts POSIX ACL read access without requiring an exact chmod value. The packaged systemd service marks `/etc/letsencrypt` and `/etc/ssl` read-only; Unix permissions/ACLs still need to grant the `rustproxy` user the required traversal/read access.
|
||||
|
||||
## 0.26.1 -> 0.26.2
|
||||
|
||||
No SQLite or saved-configuration migration is required. Version 0.26.2 changes only administration/log-explorer behavior and account-scoped view preferences. Existing JSONL and RAW log files remain readable, and old Stored Logs filter query parameters remain accepted by the API for compatibility even though the dashboard now uses one query field.
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@ This is the canonical index for the current project documentation. The authentic
|
||||
- [0.25.1 release audit](V0.25.1_AUDIT.md) - historical Traffic/module-split release scope and compiler-regression guards.
|
||||
- [0.25.0 specification audit](V0.25.0_AUDIT.md) - Web Server foundation point-by-point implementation status.
|
||||
- [0.25.0 pre-production security audit](V0.25.0_SECURITY_PREPROD_AUDIT.md) - threat-oriented source/configuration review, fixes made in-session and mandatory runtime security gates.
|
||||
- [0.28.2 implementation note](V0.28.2_IMPLEMENTATION.md) - external certificate ACL/path hardening and least-privilege Certificate API grants.
|
||||
- [0.28.1 implementation note](V0.28.1_IMPLEMENTATION.md) - external certificate paths, Certbot symlink reloads, backup semantics and certificate-source UX.
|
||||
- [0.28.0 implementation note](V0.28.0_IMPLEMENTATION.md) - manual/hot-reloaded GeoIP, listener status UX, cache Via/narrow invalidation and header-rule layout.
|
||||
- [0.27.3 implementation note](V0.27.3_IMPLEMENTATION.md) - HTTP/3 HEAD cache parity, COLD/rate-limit memory reduction, backup scope and package-path update.
|
||||
@@ -73,7 +74,7 @@ This is the canonical index for the current project documentation. The authentic
|
||||
|
||||
## Historical or version-labelled context
|
||||
|
||||
Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [V0.28.1_IMPLEMENTATION.md](V0.28.1_IMPLEMENTATION.md); [V0.28.0_IMPLEMENTATION.md](V0.28.0_IMPLEMENTATION.md) is retained as the prior GeoIP/listener/cache milestone, and [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md) is retained as the prior HTTP/3/cache-maintenance milestone, [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability.
|
||||
Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [V0.28.2_IMPLEMENTATION.md](V0.28.2_IMPLEMENTATION.md); [V0.28.1_IMPLEMENTATION.md](V0.28.1_IMPLEMENTATION.md) is retained as the prior external-path milestone; [V0.28.0_IMPLEMENTATION.md](V0.28.0_IMPLEMENTATION.md) is retained as the prior GeoIP/listener/cache milestone, and [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md) is retained as the prior HTTP/3/cache-maintenance milestone, [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability.
|
||||
|
||||
Older context documents such as [INTEGRATIONS_040](INTEGRATIONS_040.md), [MANAGEMENT](MANAGEMENT.md), [SECURITY_AUDIT](SECURITY_AUDIT.md), [ACME_RFC2136](ACME_RFC2136.md) and older version audits remain available for traceability but should not override the current functional/technical documents above.
|
||||
|
||||
|
||||
+31
-5
@@ -79,7 +79,7 @@ migration of its stored paths. A system-wide timer should not also manage these
|
||||
RustProxy-isolated profiles.
|
||||
|
||||
For a certificate intentionally owned by a separate system Certbot instead of this managed
|
||||
renewal subsystem, 0.28.1 can reference the Certbot lineage directly. Configure a manual
|
||||
renewal subsystem, 0.28.2 can reference the Certbot lineage directly. Configure a manual
|
||||
certificate id with both absolute paths, for example
|
||||
`/etc/letsencrypt/live/example.com/fullchain.pem` and
|
||||
`/etc/letsencrypt/live/example.com/privkey.pem`. RustProxy resolves the `live/` symlinks again on
|
||||
@@ -94,10 +94,36 @@ curl --fail --show-error -X POST \
|
||||
https://proxy-admin.example/api/certificates/shared-cert/reload
|
||||
```
|
||||
|
||||
The service identity must be able to traverse the Certbot directories and read the files. External
|
||||
private keys may be owner-only (`0600`) or group-readable (`0640`) for a restricted group; they
|
||||
must not be executable, group-writable or world-accessible. The referenced files are not copied
|
||||
into RustProxy backups.
|
||||
The service identity must be able to traverse the Certbot directories and read the files. RustProxy
|
||||
0.28.2 accepts named POSIX ACL read access and does not require an exact `0600`/`0640` mode.
|
||||
External certificate/key files must not be group/world-writable, and private keys must not be
|
||||
world-readable. Parent directories must be root- or RustProxy-owned and not group/world-writable
|
||||
(apart from root-owned sticky ancestors such as `/tmp`). The referenced files are not copied into
|
||||
RustProxy backups.
|
||||
|
||||
For a normal Certbot lineage, grant traversal without directory listing and read only the archive
|
||||
files. A default ACL is useful, but a renewer may create a new private key with mode `0600` and
|
||||
mask inherited ACL permissions. The reliable hook therefore reapplies the named read ACL to the
|
||||
newly resolved generation before calling the reload API:
|
||||
|
||||
```bash
|
||||
setfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/
|
||||
setfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/
|
||||
setfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem \
|
||||
/etc/letsencrypt/archive/example.com/privkey*.pem
|
||||
setfacl -m d:u:rustproxy:r-- /etc/letsencrypt/archive/example.com/
|
||||
|
||||
# Run these two resolved-file grants after renewal, before the API reload.
|
||||
setfacl -m u:rustproxy:r-- \
|
||||
"$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)" \
|
||||
"$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)"
|
||||
```
|
||||
|
||||
For external material below `/etc/ssl`, traversal of the protected root can be granted separately:
|
||||
|
||||
```bash
|
||||
setfacl -m u:rustproxy:--x /etc/ssl/
|
||||
```
|
||||
|
||||
If direct read access to the Certbot tree is undesirable, use the copy/deploy workflow instead:
|
||||
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ This is a settings fragment, not a replacement for the existing routing/vhost do
|
||||
A complete opt-in example is in `examples/cache-dashboard.json`.
|
||||
|
||||
The dashboard exposes the complete raw JSON editor under **Settings -> Manual config**.
|
||||
The same card exposes **Revision history**, which reads the newest 512 retained configuration revisions from SQLite schema 8 and groups them by month/day. Selecting a revision shows timestamp, actor and a bounded path-level before/after diff against the previous retained revision. An older retained revision can be restored from the same dialog; RustProxy validates it through the normal configuration update path and saves it as a new revision, preserving the prior history.
|
||||
The same card exposes **Revision history**, which reads the newest 512 retained configuration revisions from the configuration-history table introduced in SQLite schema 8 (current database schema 9) and groups them by month/day. Selecting a revision shows timestamp, actor and a bounded path-level before/after diff against the previous retained revision. An older retained revision can be restored from the same dialog; RustProxy validates it through the normal configuration update path and saves it as a new revision, preserving the prior history.
|
||||
The historical `/configuration` browser route is retained only as a compatibility alias and
|
||||
opens that Settings block; it is no longer a primary-navigation item. Prefer the structured
|
||||
section editors for routine changes so unrelated sections are not republished.
|
||||
|
||||
+20
-29
@@ -1,38 +1,29 @@
|
||||
# Verification report - RustProxy 0.28.1
|
||||
# Verification report - RustProxy 0.28.2
|
||||
|
||||
## Scope
|
||||
|
||||
RustProxy 0.28.1 adds absolute external certificate/key paths for manual certificates while preserving the existing managed filename mode under `<data-dir>/certs`. The intended renewal path is Certbot/acme.sh updating `/etc/letsencrypt/live/...` followed by `POST /api/certificates/{certificate_id}/reload`. External paths are canonicalized on every load/reload so Certbot `live/` symlinks follow the current archive generation, then the final file is opened as a bounded regular file. Mixed managed/external pairs are rejected, certificate/key matching remains mandatory and managed private keys retain owner-only checks, while external keys may be `0640` for a restricted service group but cannot be group-writable, executable or world-accessible.
|
||||
RustProxy 0.28.2 hardens the 0.28.1 external-certificate workflow. Absolute certificate/key paths are validated across the configured and resolved parent chains, the final file is opened with `O_NOFOLLOW`, POSIX ACL read access is supported without requiring an exact chmod value, private keys may not be world-readable, and external certificate/key files may not be group/world-writable. The packaged systemd service explicitly keeps `/etc/letsencrypt` and `/etc/ssl` read-only while normal Unix permissions/ACLs remain authoritative for access.
|
||||
|
||||
The targeted reload path remains revision-stable and republishes one immutable TLS snapshot. New public H1/H2/H3 handshakes use the refreshed material; invalid replacement files leave the previous snapshot active. The deploy endpoint refuses an ID configured with absolute external paths so a renewal wrapper cannot accidentally change storage ownership. Externally referenced files are excluded from RustProxy backups; only their configured paths survive, and the operator must restore or mount the external material separately.
|
||||
Certificate automation is also narrowed. SQLite schema 9 adds per-Key-ID grants: new Certificate API tokens require explicit certificate IDs, are reload-only by default, cannot call the global all-certificate reload endpoint, and require an explicit deploy grant before `POST /api/certificates/deploy` is accepted. Credentials from earlier releases migrate to reload-only wildcard compatibility grants and should be rotated to explicit IDs.
|
||||
|
||||
The certificate UI now distinguishes RustProxy-managed filenames from external absolute paths and provides explicit English/Polish storage guidance for ACME, Import and Existing-files modes. 0.28.1 retains all 0.28.0 GeoIP direct-source/MMDB/hot-swap, listener protocol-badge, ACME runtime-reporting, cache Via/narrow invalidation and Security-editor behavior. SQLite schema 8 and COLD cache format 3 remain unchanged.
|
||||
The release retains the 0.28.0 GeoIP direct-source/MMDB hot reload, listener protocol badges, cache Via/narrow invalidation, Security-editor layout and ACME runtime reporting. COLD cache format 3 and saved proxy configuration format remain unchanged.
|
||||
|
||||
## Verification status in this workspace
|
||||
|
||||
Rust/Cargo is not installed in this workspace. Rust-independent release checks can run here; compiler/type/runtime gates and real H2/H3 protocol execution require a Rust-enabled host.
|
||||
|
||||
## Release checks
|
||||
## Verification
|
||||
|
||||
| Check | Result |
|
||||
| --- | --- |
|
||||
| Rust/source module contracts | PASS |
|
||||
| GeoIP direct-source/optional-checksum/MMDB/manual endpoint/UI/hot-swap source contracts | PASS |
|
||||
| Cache Via/narrow invalidation source contracts | PASS |
|
||||
| Listener protocol-badge/header-editor UI source contracts | PASS |
|
||||
| ACME startup reporting contract (no speculative banner state; final INFO event) | PASS |
|
||||
| External certificate path / Certbot-symlink / managed-vs-external source contracts | PASS |
|
||||
| Documentation and language-pack checks | PASS |
|
||||
| Web Server, security and dependency source contracts | PASS |
|
||||
| `tools/checks/check_source.py` | PASS (compiler/runtime explicitly not verified) |
|
||||
| `tools/checks/release_smoke.py` | PASS |
|
||||
| Python syntax (`py_compile`) and direct-CSV installer validation checks | PASS |
|
||||
| Browser Web Service UI regression (`node tools/checks/web_service_ui_test.mjs`) | PASS |
|
||||
| Docker Agent (`go test ./...`) | PASS |
|
||||
| `./dev.sh test` static/pre-compiler stages | PASS; command then exits 127 at the Rust/Cargo gate |
|
||||
| Rust `cargo fmt/check/test`, linking and Rust unit tests (including external-path/Certbot-symlink reload, synthetic standard/sapics/custom-MMDB lookup fixtures, GeoIP hot-swap, cache Via and cache-revision tests) | NOT VERIFIED - Cargo/Rust is not installed in this workspace |
|
||||
| Live manual/automatic GeoIP direct-source download and real-world MMDB lookup | NOT VERIFIED - requires network access and a compiled 0.28.1 binary |
|
||||
| Live external Certbot-path reload through `POST /api/certificates/{certificate_id}/reload` | NOT VERIFIED - requires a compiled 0.28.1 binary plus readable external certificate material |
|
||||
| Live HTTP/2 + HTTP/3 protocol tests | NOT VERIFIED - requires a compiled 0.28.1 binary and QUIC-capable runtime |
|
||||
|---|---|
|
||||
| Source/static contracts | PASS - `tools/checks/check_source.py` |
|
||||
| Documentation/index/link checks | PASS - `tools/checks/docs_check.py` |
|
||||
| Browser-side JavaScript syntax/UI contracts | PASS - `./dev.sh test` pre-Cargo stages |
|
||||
| SQLite schema-9 migration and release smoke | PASS - `tools/checks/release_smoke.py`, including legacy-token grant migration |
|
||||
| Docker Agent Go tests | PASS - `go test ./...` |
|
||||
| Shell/Python/JSON syntax checks | PASS - release/static checks plus explicit shell/JSON validation |
|
||||
| `cargo fmt/check/test` | NOT VERIFIED - Rust/Cargo are not installed in this verification environment; `./dev.sh test` exits 127 only after all pre-Cargo gates pass |
|
||||
| Live Certbot ACL + targeted API reload | NOT VERIFIED - requires a compiled binary and host certificate material |
|
||||
| Live HTTP/2 + HTTP/3 protocol tests | NOT VERIFIED - requires a compiled binary and QUIC-capable runtime |
|
||||
|
||||
`./dev.sh test` is expected to exit with status 127 only when it reaches the Rust toolchain gate; all earlier release/static checks must complete successfully for this report to remain valid.
|
||||
## Security regression coverage
|
||||
|
||||
The source tests include Certbot-style `live/ -> archive/` symlink refresh behavior, acceptance of ACL-style group-mask bits, rejection of group/world-writable or world-readable private-key modes, and rejection of writable non-sticky parent directories. Administration tests cover targeted certificate-ID grants, denial of global reload for Certificate API tokens, rejection of non-allowlisted certificate IDs and independent token revocation.
|
||||
|
||||
See [V0.28.2_IMPLEMENTATION.md](V0.28.2_IMPLEMENTATION.md) for the operator ACL example and migration behavior.
|
||||
+1
-1
@@ -132,7 +132,7 @@ required when its selected certificate changed.
|
||||
|
||||
For an externally managed/manual certificate whose configured files were replaced in place, `POST /api/certificates/{certificate_id}/reload` provides the same revision-stable hot-publication contract while targeting that certificate ID. For absolute external paths RustProxy canonicalizes the path again on every reload, intentionally following Certbot `live/` symlinks to the current `archive/` generation before opening the final regular file. The TLS resolver is immutable, so RustProxy validates and republishes a complete snapshot atomically; this avoids partially updated SNI state. Managed ACME certificate IDs are not accepted by this manual reload path.
|
||||
|
||||
External absolute-path files are not copied into RustProxy full backups. Their path strings remain in configuration; restore or mount the certificate material separately on the destination host before starting TLS. The `rustproxy` service identity must be able to traverse every parent directory and read both files. External private keys may be owner-only (`0600`) or read-only for a restricted service group (`0640`); group-write, execute and any world permission are rejected.
|
||||
External absolute-path files are not copied into RustProxy full backups. Their path strings remain in configuration; restore or mount the certificate material separately on the destination host before starting TLS. The `rustproxy` service identity must be able to traverse every parent directory and read both files. RustProxy accepts POSIX ACL read access and does not require an exact chmod value. External certificate/key files must not be group/world-writable, and private keys must not be world-readable. The configured and resolved parent chains must be root- or RustProxy-owned and not group/world-writable, except for root-owned sticky ancestors such as `/tmp`. The final file is opened with `O_NOFOLLOW` after path validation.
|
||||
|
||||
## WebSocket behavior
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# RustProxy 0.28.2 implementation note
|
||||
|
||||
## External certificate path hardening
|
||||
|
||||
0.28.2 keeps the 0.28.1 direct-file workflow for Certbot/acme.sh certificates but changes validation from an exact-mode expectation to a security-policy check. POSIX ACLs are supported: a private key may be owner-readable or group/ACL-readable by the `rustproxy` service identity. RustProxy rejects a private key that is world-readable and rejects group/world write access on every external certificate/key file. Execute bits alone are not treated as key disclosure.
|
||||
|
||||
For an absolute external path RustProxy validates the configured parent chain before resolving the final file, resolves the final Certbot-style `live/*.pem` symlink, validates the resolved parent chain again, and opens the final regular file with `O_NOFOLLOW`. On Unix, parent directories must be owned by root or the running RustProxy service user and must not be group/world-writable. Root-owned sticky ancestors such as `/tmp` remain accepted for controlled test/custom trees.
|
||||
|
||||
This closes the writable-parent/path-redirection gap while preserving Certbot renewal semantics: after `certbot renew`, a targeted reload follows the current `live/ -> archive/` generation and publishes a new immutable TLS snapshot only after the pair validates.
|
||||
|
||||
## ACL example
|
||||
|
||||
The packaged systemd unit keeps RustProxy writable only in its data/log directories and explicitly marks `/etc/letsencrypt` and `/etc/ssl` read-only when those paths exist:
|
||||
|
||||
```ini
|
||||
ReadWritePaths=/var/lib/rustproxy /var/log/rustproxy
|
||||
ReadOnlyPaths=-/etc/letsencrypt -/etc/ssl
|
||||
```
|
||||
|
||||
`ReadOnlyPaths` does not grant Unix access. For traversal-only access to protected directories and read access to the actual PEM files, use narrow POSIX ACLs. Example:
|
||||
|
||||
```bash
|
||||
setfacl -m u:rustproxy:--x /etc/ssl/
|
||||
setfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/
|
||||
setfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/
|
||||
setfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem /etc/letsencrypt/archive/example.com/privkey*.pem
|
||||
setfacl -m d:u:rustproxy:r-- /etc/letsencrypt/archive/example.com/
|
||||
```
|
||||
|
||||
A default ACL on the archive lineage is useful, but a renewer may recreate a private key as `0600` and mask inherited named-user access. A renewal hook should therefore reapply `u:rustproxy:r--` to the current resolved `fullchain.pem` and `privkey.pem` targets before calling the reload API. Do not grant `w` to the `rustproxy` identity on the Certbot tree.
|
||||
|
||||
## Least-privilege Certificate API
|
||||
|
||||
SQLite schema 9 adds `certificate_api_grants`. New Certificate API credentials require 1-32 explicit certificate IDs and are reload-only unless **Also allow deploy** is enabled. Token authentication is accepted only for targeted `POST /api/certificates/{certificate_id}/reload`; the global `POST /api/certificates/reload` remains administrator-session only. Deploy tokens must have both explicit deploy permission and an allowlisted request certificate ID.
|
||||
|
||||
Credentials created before 0.28.2 migrate to reload-only compatibility grants covering all certificate IDs (`*`). Rotate those credentials in Settings -> Certificate API to replace the broad compatibility grant with explicit IDs.
|
||||
|
||||
Normal administrator session + CSRF access remains unchanged. Certificate API tokens still cannot read configuration or call unrelated administration APIs, and non-loopback token use still requires HTTPS.
|
||||
|
||||
## Compatibility
|
||||
|
||||
- SQLite schema: 9 (additive migration from schema 8).
|
||||
- Saved proxy configuration: unchanged from 0.28.1.
|
||||
- COLD cache format: 3, unchanged.
|
||||
- External certificate paths/backups: same semantics as 0.28.1; external PEM bytes are not copied into RustProxy backups.
|
||||
Reference in new issue
Block a user