Repository navigation
feat(init): keep the node clock in sync with a client-only SNTP - #271
Merged
Merged
Conversation
Bugs5382
marked this pull request as ready for review
September 29, 2026 21:18
This was referenced Sep 30, 2026
Bugs5382
force-pushed
the
feat/249-sntp-client
branch
from
September 30, 2026 15:23
c48825f to
cbd36c4
Compare
2 tasks done
Contributor
Author
|
Docs: CryptOS-PKI/docs#53 (merge together). |
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Bugs5382
force-pushed
the
feat/249-sntp-client
branch
from
September 30, 2026 16:34
cbd36c4 to
0b0515e
Compare
Contributor
Author
|
Merging for the v0.1.0 test run: CI green and mergeable at merge time. |
4 tasks done
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.
Rebased onto the licensed main and landed by the Linux box at the owner's request (2026-09-30). Rebased with
--signoffonto main0c657fb; every commit carries the DCO sign-off. Two conflicts, both kept from both sides:internal/node/node_test.go(main'sTestConfigStoreCurrentWithNoConfigIsErrNoConfigand this PR'sTestStatusProviderReportsTimeSync, side by side) andinternal/init/run.go(main's exportedIssuedRecorderplus this PR'swireClockGate). New files keep main's current license header; the relicensing in #291 restamps them.What and why
A node had no time sync: its clock was whatever the hypervisor RTC gave it at boot, and then it drifted (about 7 to 8 seconds of skew showed up in the alpha readiness run). For a CA the clock sets notBefore/notAfter, CRL and OCSP times and audit times. This adds a small client-only SNTPv4 in init (RFC 4330, and RFC 5905 section 14). It never serves time. Stdlib plus
golang.org/x/sys/unix, no new dependency.Configuration
network.ntp_servers: at most 3, IPv4 literals or hostnames (resolved through the node resolver at each poll). Empty falls back to the kernel DHCP lease, which the kernel exposes at/proc/net/ipconfig/ntp_servers(option 42; not in/proc/net/pnp). With neither, the node runs on its hardware clock.pki.allow_unsynced_clock: lifts the signing gate below.cryptosctl config applywarns when no server is set, and when a hostname server has nonetwork.nameservers.Behaviour (
internal/timesync)clock_settime), otherwise slewed (adjtimexADJ_OFFSET_SINGLESHOT).DENY/RSTRstops querying that server until reboot;RATEdoubles its poll interval. Timestamps are decoded into the NTP era nearest the current time, so the 2036 rollover is handled.sources disagree.clock-flooron the state volume, raised on every issuance, after every good sync and at a clean shutdown (only if the clock synced this boot). Slews are not floor-checked, since a slew never moves the clock backwards.STA_UNSYNCis cleared so the kernel's 11-minute mode keeps the RTC close.Signing gate (fail closed)
SignSubordinateand every leaf path (IssueLeaf, request SANs, ACME, EST) returnFailedPreconditionbefore the CA key is loaded. The gate opens at the first good sync and stays open for the boot.pki.allow_unsynced_clock: truelifts it, with a warning logged on every such signing.Status
NodeStatus.time_syncand aClock:line incryptosctl status(state, source, servers, then the last good sync or the last error).Logs go to the kernel log through
log/slog: info for the source, boot step and state changes; debug for each poll; trace for raw header fields; warn for rejected replies, kisses and disagreement; error for floor violations, refused backwards steps and clock syscall failures.Closes #249
Refs CryptOS-PKI/api#105
api pin.
github.com/CryptOS-PKI/apistays at apimain81494db(already on cryptos main), which carries the time-sync contract (CryptOS-PKI/api#106) and GetIssuedCertificate (CryptOS-PKI/api#104). The unserved SCEP/TSA stubs came in with #286, so this PR no longer touchesinternal/grpc,go.modorgo.sum.Merge order: #286 is merged. Merge #305 first, then this PR, then #288 and #291 (the relicensing).
Real-node validation: 8 of 8 pass on a QEMU + swtpm node built with
task imagefrom this head (cbd36c4), run against a controllable SNTP server and a DHCP server on a lab-only network:clock stepped offset=1h0m1.84s boot=true);Clock: SYNCED MACHINE_CONFIG ... stepped at boot; a signed certificate's notBefore matched the host to the second (after the 5-minute backdate).time pollthenclock slewingat the next poll; the offset went 49.9 ms, then 18.6 ms.clock stepped ... boot=false. Server 2 s behind:refused a backwards clock step on a running node,Clock: UNSYNCED ... refused a backwards step, and signing still worked.CONFIG_RTC_SYSTOHCis not set (the kernel has noRTC_CLASS); on x86 the kernel's 11-minute mode writes the CMOS clock throughGENERIC_CMOS_UPDATEanyway. arm64 will needRTC_CLASS,RTC_SYSTOHCand an RTC driver, a kernel config follow-up outside this PR.source=TIME_SOURCE_DHCP_LEASE,Clock: SYNCED DHCP_LEASE.FailedPreconditionnamingpki.allow_unsynced_clock, while/crland OCSP still answered. With the override, signing worked and loggedsigned on an unsynced clock. With no source:Clock: NOT_CONFIGUREDand no gate.clock step refused: server time ... is behind already-issued certificates; the clock did not move and signing stayed gated.RATE: the node logged each kiss and backed off (poll gaps 64 s, 128 s, 256 s), never faster than every 15 s.No defects were found, so no code changed beyond the rebase and the api pin. Two notes, neither a failure: the override warning's wording doubles up ("subordinate CA signing signed on an unsynced clock"), and
config applyreportsrequires_reboot=truefor anallow_unsynced_clock-only change, although the node reads it live.allow_unverified_revocation_urlbehaves the same way on main.Verification
How this was verified
gofmt,go vet ./...,golangci-lint run ./...(host andGOOS=linux): clean;GOOS=linux GOARCH=arm64 go build ./...: buildsgo test ./...with CGO (TPM simulator): pass;go test -raceoninternal/timesync: passgolic --dry: every file carries the license headertask ci(fmt, golangci-lint, vet,go test ./..., build) passes andtask licenseexits 0 on0b0515e(rebased onto main after feat(config): switch ACME and EST on and off through machine config #286)internal/timesync: packet encode/decode, every reply-rejection rule and each kiss code, offset and delay maths including the 2036 era boundary, median and disagreement, boot step versus slew, bounded boot on an unreachable server, forward step and refused backwards step after boot,DENY/RSTRstop andRATEback-off, back-off to 1024 s and never below 15 s, poll interval honoured, hostname resolution and failure, clock syscall failure, spoofed reply from another address ignored, floor file (persist, build time, corrupt file, issued notBefore, shutdown only when synced)internal/node: signing gate refuses every path while unsynced without loading the key, allows once synced, allows withallow_unsynced_clock, never gates without a source; issued notBefore reported;time_syncinGetStatusinternal/init: source selection (config, option 42 file with validation and the 3 cap, none), CRL and OCSP still generated while signing is gated, floor raised by issuance with no sourceinternal/config:ntp_serversvalidation, proto round trip, both warningscmd/cryptosctl: theClock:line, and theconfig applywarningsdocs/time-sync.md(new), README links it and listsinternal/timesyncBefore merging: add a closing comment summarizing what was actually done in this PR
(not just the checked boxes).