[PKI release] Sync current main into certificates - #80
Merged
Conversation
* Serve doc images from Cloudflare R2 instead of the repo Images committed into docs/public/img/ bloated PRs and repo history. They're now stored in the shared "websites-images" R2 bucket (prefix "atom-docs") and served through a hand-written Cloudflare Worker (docs/worker/index.ts) added as `main` alongside the static-assets binding -- this site is a fully static Next.js export with no server runtime, so neither @cloudflare/next-on-pages nor @opennextjs/cloudflare applies. Authoring is unchanged: MDX content already referenced doc images by their final /img/... path (no relative-path convention existed here to begin with), so nothing about writing  needed to change. - source.config.ts disables fumadocs' remarkImage plugin, which otherwise needs the file on local disk at build time. - docs/components/doc-image.tsx renders the result as a plain, zoomable <img> (fumadocs-ui's ImageZoom wrapping a plain element, not next/image) -- no width or height required, so there's nothing to keep in sync when images change. ImageZoom's src/alt are passed explicitly (not just to the inner <img>), since its zoomed-in view reads the image from those props directly, not from `children`. - Dropped lib/image-dimensions.json and the manifest-sync logic that used to live in scripts/publish-image.mjs -- simplified back to just upload + purge. All 88 images have already been uploaded to the real R2 bucket and spot-checked byte-for-byte against the originals. * Cache R2-proxied doc images at Cloudflare's edge env.IMAGES_BUCKET.get() is an R2 binding call, not an HTTP subrequest. Workers run before Cloudflare's cache in the request pipeline, so a Response the Worker constructs and returns is never automatically written into the edge cache, no matter what Cache-Control header is set on it -- that only happens via explicit Cache API use, or a zone Cache Rule intercepting it. Neither was happening here, so despite s-maxage=31536000 being set, every request (every visitor, every edge location) was a live R2 read. Fixed by writing responses into the Workers Cache API (caches.default) after the first R2 read, keyed by the request's own URL unmodified (so it stays purgeable by the existing purge-by-URL call in scripts/publish-image.mjs on every upload). This also adds Range/206 support as a side effect: cache.match() automatically serves 206 Partial Content for a Range request against a cached 200 response. Bumped browser max-age from 300s to 3600s while leaving s-maxage at a year -- purge-on-publish already invalidates the edge instantly on every upload, so there's no freshness benefit to a short edge TTL. This repo already depends on @cloudflare/workers-types (unlike the sister sites), so Cache/CacheStorage/ExecutionContext are used via their ambient global types instead of local structural interfaces. Same fix as absmach/website#178, applied here since this repo's worker/index.ts uses the identical binding-without-caching pattern. * Fix CI: pin js-yaml past CVE-2026-59870 (quadratic CPU on !!omap) pnpm audit --prod --audit-level high was failing in CI on js-yaml 4.3.0, pulled in transitively via fumadocs-mdx (.>fumadocs-mdx>js-yaml). fumadocs-mdx's own declared range ("^4.1.0") already permits the patched 4.3.1, so this is pinned via a pnpm.overrides entry rather than waiting on fumadocs-mdx to bump its lockfile-resolved version itself. Also pins sharp and postcss to the versions they were already resolving to before this change (^0.35.3 and ^8.5.18, both already past their own advisories' patched thresholds) -- introducing the js-yaml override alone caused pnpm to re-resolve the whole dependency graph and land on older, vulnerable versions of both (sharp 0.35.3->0.34.5, plus a stray duplicate postcss@8.4.31 no longer deduped with the top-level 8.5.18), surfacing 2 more high-severity findings that don't exist in a plain install of main. Pinning all three keeps the lockfile at exactly the versions already known safe, with no other dependency churn (confirmed via lockfile diff). Pre-existing, unrelated to the R2 image-caching work on this branch -- confirmed the same js-yaml advisory already fails audit on a clean install of main.
* Serve the broker auth callout directly
Atom implements FluxMQ's fluxmq.auth.v1.AuthService, so a broker can
authenticate clients and authorize topics against Atom with no adapter
service in between.
The proto is vendored verbatim from FluxMQ. Its package line is part of
the wire contract — the path a broker dials is derived from it — so it
must not be renamed. How a topic names an object is configuration
(ATOM_BROKER_TOPIC_TEMPLATE), which keeps any particular deployment's
topic vocabulary out of Atom; an adapter service is still the right
answer where the mapping needs more than a grammar.
Two invariants the implementation rests on:
- Denials are answers, not errors. Every rejection returns a successful
RPC carrying a false verdict; only infrastructure failure returns a
gRPC error. A broker wraps this callout in a circuit breaker, and a
tripped breaker rejects every client connection, so one device
retrying a stale password must not be able to take the broker's whole
auth path down. Rate limiting is on that list because it is the
failure a bad client can trigger at will.
- Tenant comes from the subject, not from config or the topic.
Authenticate resolves the identifier across tenants and the entity's
own tenant comes back with it, so the zero-configuration case needs
no tenant in the topic and no username grammar. A {tenant} template
segment only scopes alias resolution; it is deliberately not checked
against the subject's tenant, because cross-tenant grants are
legitimate and that call belongs to the PDP.
Off by default. It is the only gRPC service here with no bearer token to
check, so it authenticates its caller at the transport via the
listener's mTLS client CA; mounted on a plaintext listener, anything
that can reach the port could authenticate and authorize as any
principal. A startup warning fires if it is enabled without a client CA.
* Admit broker operational topics without the PDP
A broker carries topics that address no object — a health probe such as
hc/<tenant> names nothing Atom can resolve — so no policy could describe
them and every request for one was denied. ATOM_BROKER_TOPIC_ALLOW lists
topics authorized without consulting the PDP.
This is the only authorization bypass in the callout, so it defaults to
empty and its patterns are ordinary MQTT filters: an operator writes the
narrowest shape that covers the operational topic rather than reaching
for a prefix.
A broker '#' covers its position and everything below it, so only a
pattern that is itself '#' there is broad enough to admit it. Letting
'+' match it would silently widen the bypass past what the operator
wrote — hc/+ would admit a subscription to the whole hc subtree.
* Serve the broker callout under a vendor-neutral package
The wire path a broker dials is derived from the proto package, so serving
`fluxmq.auth.v1` put one implementation's name in the public surface of
every peer that speaks the contract — including Atom's. Nothing in the
messages is FluxMQ-specific; the same shape covers MQTT, AMQP, CoAP and
HTTP, and other brokers and providers implement it.
Renaming costs nothing structurally. The Go import path upstream is
derived from the file's location rather than the package, so consumers
keep the same imports and symbols, and only the dialled path changes.
It is still a breaking wire change: a broker dialling the new path
against a service serving the old one gets UNIMPLEMENTED. Atom, the
brokers, and any adapter service have to move together.
* Detect drift in the vendored broker contract
Atom implements a proto it does not own, and nothing rebuilds the copy.
An upstream change was therefore discovered at runtime — as an
UNIMPLEMENTED from a renamed service, or worse, as a field that still
decodes but no longer means what Atom thinks it means.
scripts/check-vendored-proto.sh diffs the vendored file against the ref
pinned in proto/broker/v1/REF, and CI runs it. For that diff to stay
trustworthy the copy has to be byte-identical, so Atom's own notes moved
out of the proto and into VENDOR.md beside it: a check that has to
forgive expected differences stops catching the one that matters.
Vendoring a second proto into the buf module had also broken two things
that nothing here runs, so neither had surfaced:
- buf lint failed on twenty-odd violations in the vendored file. Its
style is upstream's, and Atom cannot fix it without breaking the
byte-for-byte match, so it is excluded from lint and breaking.
- buf generate silently replaced apidocs/grpc-reference.md with the
broker contract, dropping Atom's own gRPC surface from the docs.
protoc-gen-doc writes one file per invocation, so a second package
does not extend that file. Generation is now scoped to Atom's protos.
make proto regenerates both outputs. The asymmetry is worth knowing: the
Rust bindings are not checked in — build.rs runs tonic-build into OUT_DIR
on every compile — but grpc-reference.md is, and goes stale silently.
* Correct the package a test's doc comment names
The vendored contract moved to `broker.auth.v1`, but this file still named
`fluxmq.auth.v1` — a package no peer serves any more. It was the last
reference to the old name left in the repository.
Drop the attribution to FluxMQ along with it. Calling the service FluxMQ's
is what the rename set out to undo: the contract is the broker's, and Atom
serves it for whichever broker dials.
* Fix formatting
Signed-off-by: dusan <borovcanindusan1@gmail.com>
---------
Signed-off-by: dusan <borovcanindusan1@gmail.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
atom-docs | 3b61107 | Commit Preview URL Branch Preview URL |
Aug 12 2026, 08:49 AM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Resolve the
certificates → mainrelease conflict by merging currentmainintocertificatesbefore final review.Preserved work
From
main(5ccbc779bbe3feaf9790188e541164630519bfbe):From
certificates(7bf4cf328b09801689c4f34bcc86c7a06cfaf58c):Conflict resolution
Git had one content conflict, in
src/config.rs, where both branches added adjacent environment loaders. The resolution retains all three independent loaders:enrollment_from_env;pki_lifecycle_from_env;broker_auth_from_env.The Rust workflow retains both the newer
Vendored proto matches upstreamstep and the complete PKI/SoftHSM/smoke gates.The combined test matrix also exposed a sub-second boundary in the PR-015 lifecycle fixture: the synthetic expired authority could be assigned
not_after <= not_before. The fixture now expires one second before the sweep time, preserving the intended overdue case without constructing an invalid authority interval.Validation
Validated head:
46bc4286f267f4ad671db1dfd5b831fe17e2459bValidated tree:
af15c05d0a2889fc84e90b906d18b7ddb8b31381cargo fmt --check;git diff --check.Merge requirement
This PR must be merged with a merge commit, not squash/rebase, so
mainremains an ancestor ofcertificatesand release PR #79 becomes conflict-free.