Skip to content

Commit 7bf8090

Browse files
Merge pull request #15 from Rwx-G/feat/release-0.9.0
feat/release-0.9.0: security hardening, .deb auto-update, test coverage
2 parents 8d6d3af + 0262262 commit 7bf8090

47 files changed

Lines changed: 3811 additions & 74 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.9.0] - 2026-03-09
11+
12+
### Added
13+
14+
- `buncker verify` command to re-hash all blobs and detect silent corruption (bit-rot)
15+
- `/admin/health` endpoint returning store integrity, TLS cert expiry, disk space, and uptime
16+
- TLS support in BunckerServer via `ssl.SSLContext` (server now serves HTTPS when configured)
17+
- `gc --report` now shows which images become non-pullable if candidates are deleted (`gc_impact_report`)
18+
- `POST /admin/gc/impact` endpoint to analyze image impact before executing GC
19+
- `gc --execute` requires `--yes` flag or interactive confirmation to prevent accidental deletions
20+
- Fetch rate limiting: `buncker-fetch` now retries on HTTP 429 using `Retry-After` header instead of failing immediately
21+
- Rate limit observability: logs warning when `RateLimit-Remaining` drops below 10 on successful responses
22+
- Manifest auto-refresh: `buncker-fetch` tracks manifest digests across fetches and warns when upstream content changes
23+
- Dockerfile ARG substitution now supports `${VAR:-default}` and `${VAR:+replacement}` syntax
24+
- Security hardening documentation (mnemonic encryption, `/v2/*` risks, admin API protections)
25+
- Mnemonic at-rest encryption using machine-id derived AES key (`BUNCKER_MNEMONIC_ENC` in `/etc/buncker/env`)
26+
- `buncker-fetch fetch --deb <path>` flag to bundle a `.deb` update in the encrypted response
27+
- `.deb` auto-extraction on `buncker import`: update files saved to `store/updates/` with notification
28+
29+
### Fixed
30+
31+
- CA certificate missing `KeyUsage` and `SubjectKeyIdentifier` extensions (Python 3.14 compatibility)
32+
- Server certificate missing `AuthorityKeyIdentifier` extension
33+
- `blob_integrity_error` and `metadata_update_failed` logs missing audit fields (client_ip, auth_level, user_agent)
34+
- Integration test Phase 2 now uses HTTPS URLs and `-k` flag for self-signed certs
35+
- `procps` added to offline Dockerfile for reliable daemon process management
36+
37+
### Security
38+
39+
- `/admin/health` accessible with read-only token (same level as `/admin/status`)
40+
- Mnemonic encrypted at rest with PBKDF2-derived key from `/etc/machine-id` (no longer stored in cleartext)
41+
1042
## [0.8.1] - 2026-03-08
1143

1244
### Added
@@ -218,7 +250,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
218250
- GitHub templates for issues (bug report, feature request) and pull requests
219251
- Conventional Commits convention and branching strategy documented
220252

221-
[Unreleased]: https://github.com/Rwx-G/Buncker/compare/v0.8.1...HEAD
253+
[Unreleased]: https://github.com/Rwx-G/Buncker/compare/v0.9.0...HEAD
254+
[0.9.0]: https://github.com/Rwx-G/Buncker/compare/v0.8.1...v0.9.0
222255
[0.8.1]: https://github.com/Rwx-G/Buncker/compare/v0.8.0...v0.8.1
223256
[0.8.0]: https://github.com/Rwx-G/Buncker/compare/v0.7.0...v0.8.0
224257
[0.7.0]: https://github.com/Rwx-G/Buncker/compare/v0.6.1...v0.7.0

README.md

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<img src="https://img.shields.io/badge/python-%3E%3D3.11-3776AB.svg?logo=python&logoColor=white" alt="Python">
99
<img src="https://img.shields.io/badge/platform-Debian%2FUbuntu-A81D33.svg?logo=debian&logoColor=white" alt="Platform">
1010
<img src="https://img.shields.io/badge/packaging-.deb-orange.svg" alt="Packaging">
11-
<img src="https://img.shields.io/badge/status-v0.8.1-brightgreen.svg" alt="Status">
12-
<img src="https://img.shields.io/badge/coverage-%E2%89%A580%25-brightgreen.svg" alt="Coverage">
11+
<img src="https://img.shields.io/badge/status-v0.9.0-brightgreen.svg" alt="Status">
12+
<img src="https://img.shields.io/badge/coverage-%E2%89%A590%25-brightgreen.svg" alt="Coverage">
1313
</p>
1414

1515
---
@@ -74,10 +74,10 @@ Download the latest `.deb` files from [GitHub Releases](https://github.com/Rwx-G
7474

7575
```bash
7676
# Offline machine
77-
sudo dpkg -i buncker_0.8.1_all.deb
77+
sudo dpkg -i buncker_0.9.0_all.deb
7878

7979
# Online machine
80-
sudo dpkg -i buncker-fetch_0.8.1_all.deb
80+
sudo dpkg -i buncker-fetch_0.9.0_all.deb
8181
```
8282

8383
If dependencies are missing, fix them with:
@@ -140,13 +140,18 @@ without manual re-entry.
140140

141141
The operator works directly on the buncker server. Transfers go through USB.
142142

143-
**1. Analyze a Dockerfile and generate a transfer request**
143+
**1. Prepare a transfer request from a Dockerfile**
144144

145145
```bash
146-
buncker analyze ./Dockerfile --build-arg NODE_VERSION=20
147-
buncker generate-manifest --output /media/usb/
146+
buncker prepare ./Dockerfile --build-arg NODE_VERSION=20 --output /media/usb/
147+
# Combines analyze + generate-manifest in one step
148148
```
149149

150+
This analyzes the Dockerfile, identifies missing layers, and writes the
151+
encrypted transfer request to the USB drive in a single command.
152+
You can also run `buncker analyze` and `buncker generate-manifest` separately
153+
if you need to inspect the analysis before generating the request.
154+
150155
**2. Online machine - pair and fetch**
151156

152157
```bash
@@ -328,8 +333,9 @@ references as in Approach 1.
328333
| `buncker generate-manifest` | Generate an encrypted transfer request |
329334
| `buncker import [file.tar.enc]` | Import an encrypted transfer response (auto-scans `transfer_path` if omitted, `--cleanup` deletes file after success) |
330335
| `buncker status` | Show registry status (blob count, store size) |
336+
| `buncker verify` | Re-hash all blobs and detect silent corruption (bit-rot) |
331337
| `buncker gc --report` | List inactive blobs eligible for garbage collection |
332-
| `buncker gc --execute` | Delete reported inactive blobs |
338+
| `buncker gc --execute` | Delete reported inactive blobs (requires `--yes` or interactive confirmation) |
333339
| `buncker rotate-keys` | Generate a new mnemonic and deprecate old keys |
334340
| `buncker export-ca` | Print CA certificate to stdout (TLS mode only) |
335341
| `buncker api-setup` | Generate API tokens and activate TLS for LAN access |
@@ -369,6 +375,7 @@ references as in Approach 1.
369375
| `--output <path>` | Output directory for `fetch` response |
370376
| `--parallelism N` | Parallel downloads for `fetch` (default: 4) |
371377
| `--older-than Nd` | Cache clean threshold (default: `30d`) |
378+
| `--deb <path>` | Include a `.deb` update file in the `fetch` response |
372379

373380
## Maintenance
374381

@@ -423,6 +430,72 @@ sudo tail -f /var/log/buncker/buncker.log
423430
sudo journalctl -u buncker -f
424431
```
425432

433+
## Security Hardening
434+
435+
### Mnemonic storage (`/etc/buncker/env`)
436+
437+
`buncker setup` encrypts the mnemonic with a PBKDF2-derived key from
438+
`/etc/machine-id` and stores the ciphertext as `BUNCKER_MNEMONIC_ENC=<base64>`
439+
in `/etc/buncker/env` (mode 0600, owned by root). The daemon decrypts it
440+
automatically on startup. This prevents direct exposure if the file is
441+
read by an attacker without access to the machine-id.
442+
443+
On sensitive deployments, consider additional protections:
444+
445+
- **LUKS encryption** - place the buncker data directory on a LUKS
446+
encrypted partition for full at-rest protection
447+
- **TPM-backed encryption** - use `systemd-creds` or `clevis` to seal
448+
`/etc/buncker/env` to the machine's TPM, so it can only be decrypted
449+
on that specific host
450+
- **Manual entry** - remove `/etc/buncker/env` and enter the mnemonic via
451+
stdin on each daemon start (set `BUNCKER_MNEMONIC` env or pipe it).
452+
This provides the strongest protection but requires manual intervention
453+
on every restart
454+
455+
### Backup and recovery
456+
457+
Buncker state consists of the blob store and configuration files. To back up:
458+
459+
```bash
460+
# Back up store and config
461+
rsync -a /var/lib/buncker/ /backup/buncker-store/
462+
rsync -a /etc/buncker/ /backup/buncker-config/
463+
```
464+
465+
To restore, copy the files back and restart the daemon.
466+
467+
**Mnemonic recovery**: if the mnemonic is lost and `/etc/buncker/env` is
468+
unavailable, the only option is `buncker rotate-keys` to generate a new
469+
mnemonic. There is no way to extract the original mnemonic from the config.
470+
After rotation, re-pair the online machine with `buncker-fetch pair`.
471+
472+
### OCI endpoints (`/v2/*`) are unauthenticated
473+
474+
The OCI Distribution API endpoints (`/v2/`, `/v2/<name>/manifests/`,
475+
`/v2/<name>/blobs/`) are always unauthenticated, even when API auth is
476+
enabled. This is by design - Docker clients need direct access to pull
477+
images without Bearer token configuration.
478+
479+
**Implications:**
480+
481+
- Any machine on the offline LAN can pull images from buncker
482+
- Image content is not confidential in most air-gapped scenarios (the
483+
threat model protects integrity and provenance, not secrecy)
484+
- If you need to restrict OCI access, use network-level controls
485+
(firewall rules, VLAN segmentation) to limit which hosts can reach
486+
port 5000
487+
488+
### Admin API protection
489+
490+
When API auth is enabled (`buncker api-setup`):
491+
492+
- TLS is mandatory (the daemon refuses to start without it)
493+
- Admin endpoints require the admin token (analyze, import, GC execute)
494+
- Read-only endpoints accept either token (status, logs, health, GC report)
495+
- Token values are never logged (only `auth_level` appears in audit trail)
496+
- All admin API calls are logged with `client_ip`, `auth_level`, and
497+
`user_agent` for forensic review
498+
426499
## Troubleshooting
427500

428501
| Problem | Cause | Solution |
@@ -465,14 +538,21 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for full development setup and guidelines
465538

466539
| Feature | Description | Status |
467540
|---------|-------------|--------|
468-
| Import cleanup flag | `--cleanup` flag on `buncker import` to delete .tar.enc after successful import | Done |
469-
| Streamlined `api-setup` | Auto-export ca.pem to `/etc/buncker/ca.pem` and display cert fingerprint during setup | Done |
470-
| Disk space in `status` | Show store disk usage and available space in `buncker status` and `/admin/status` | Done |
471-
| Health-check endpoint | `/admin/health` returning store integrity, cert expiry, and disk space | Planned |
472-
| Store integrity check | `buncker verify` command to re-hash all blobs and detect silent corruption (bit-rot) | Planned |
473-
| GC impact report | `gc --report` shows which images become non-pullable if candidates are deleted | Planned |
474-
| Fetch rate limiting | Auto-pace blob downloads based on registry `RateLimit-*` headers | Planned |
475-
| Manifest auto-refresh | buncker-fetch re-downloads manifests on every fetch and warns if upstream digest changed | Planned |
541+
| API auth & LAN clients | Bearer tokens (admin + readonly), TLS, endpoint access control, audit trail | Done |
542+
| Remote operations | curl-based analyze, generate-manifest download, PUT streaming import with resume | Done |
543+
| Health-check endpoint | `/admin/health` returning store integrity, cert expiry, and disk space | Done |
544+
| Store integrity check | `buncker verify` command to re-hash all blobs and detect silent corruption (bit-rot) | Done |
545+
| GC impact report | `gc --report` shows which images become non-pullable if candidates are deleted | Done |
546+
| GC execute confirmation | `gc --execute` requires `--yes` flag or interactive confirmation to prevent accidents | Done |
547+
| Fetch rate limiting | Auto-pace blob downloads based on registry `RateLimit-*` headers | Done |
548+
| Manifest auto-refresh | buncker-fetch re-downloads manifests on every fetch and warns if upstream digest changed | Done |
549+
| Resolver ARG edge cases | Support for `${VAR:-default}` and `${VAR:+replacement}` syntax, complex `--platform` patterns | Done |
550+
| Security hardening docs | Document `/etc/buncker/env` encryption recommendations and `/v2/*` unauthenticated access risks | Done |
551+
| Quick start with `prepare` | Feature `buncker prepare` in main README workflow instead of separate analyze + generate-manifest | Done |
552+
| Docker Compose support | `buncker analyze --compose docker-compose.yml` to extract all images from multi-service projects | Planned |
553+
| .deb GPG signature verification | Verify GPG signature on `.deb` updates included in transfer responses before installation | Planned |
554+
| Log rotation | Built-in log rotation policy or logrotate config for `/var/log/buncker/` | Planned |
555+
| buncker-fetch on Windows | PyInstaller binary or WSL2 documentation for online-side Windows operators | Planned |
476556

477557
## License
478558

buncker/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Buncker - Offline Docker Registry."""
22

3-
__version__ = "0.8.1"
3+
__version__ = "0.9.0"

0 commit comments

Comments
 (0)