Make main() and the security gates testable (closes #13) - #39
Merged
Conversation
Closes #13. main() previously did all gate sequencing inline and log.Fatal'd on each failure, which makes it impossible to exercise in-process. Two pieces of the test gap were tied to that: (1) main() was 0% covered. Coverage came from leaf utilities, not from the orchestration. A regression in the gate ordering (or in the new validateArgs from #38) couldn't be caught by `go test`. (2) The security gates themselves were 0% covered. isExecutableWritable looked at /proc/self/exe; isRunningAsRoot called os.Getuid() directly; isFileWorldWritable was defined but never called or tested. None could be driven by a test without elaborate chmod-the-test-binary-then-re-exec trickery. Refactor: - security.go: isPathUnsafelyWritable(path) extracted from isExecutableWritable; the latter now just resolves os.Executable() and delegates. Same goes for uidIsRoot(uid), called by isRunningAsRoot. Production callers see no behavior change; tests can chmod arbitrary temp files and pass them to the predicate, or pass uid 0/1/1000 directly to uidIsRoot. - ussher.go: runDeps struct holds the side-effecting hooks (executableWritable, runningAsRoot, initLog, validUser, loadConfig, runFn). defaultDeps() wires production. runMain(args, deps) error carries the same gate ordering as the old main but returns an error instead of calling log.Fatal. main() is now four lines: call runMain(os.Args, defaultDeps()), log.Fatal on error. - User-visible error messages preserved verbatim ("Refusing to run due to permissions issue on the ussher executable", "Refusing to run as root", "User not found") so the existing README troubleshooting headings still match what operators see in their log files. - The fmt.Println diagnostics inside isPathUnsafelyWritable now include the path being checked so a future error log says "/usr/local/bin/ussher is group writable" instead of the hard-coded "ussher binary is group writable". (fmt.Println still goes to stdout, which is the wrong channel for sshd-consumed output - that's #36, deliberately not in this PR.) Tests added: - ussher_test.go grows TestRunMain (table-driven, six cases covering missing args / version short-circuit / each gate's failure path / happy path) and TestDefaultDeps_AllSet (sanity check that the production wiring leaves nothing nil). The captureRunMain helper builds a deps that records initLog/loadConfig/runFn invocations so tests can assert "did we short-circuit before we got here?". - security_test.go grows TestUidIsRoot, TestIsPathUnsafelyWritable (table-driven across 0700/0750/0755/0770/0775/0707/0757/0777 plus a missing-path failsafe case), and TestIsFileWorldWritable (already-public function that wasn't previously tested). Coverage 42.7% -> 62.4% (+19.7pts). All tests pass under -race. #14 (integration test for Run) is still distinct - this PR doesn't exercise the goroutine fan-out itself, only the path that decides whether Run gets called. #14 lands the runFn coverage; this PR lands the main()/gate coverage. CHANGELOG bullet under [Unreleased] / Changed (refactor, no adopter-visible behavior change).
|
Hi! Just checking in on this PR — it's been open for 14 days without a review. Could you please take a look? Happy to make any changes if needed. Thanks! 🙏 |
|
Hi! Just following up on this PR — it's been another day without a review. Happy to address any feedback or make changes. Thanks for your time! 🙏 |
|
Friendly ping @dolph 👋 This PR has been waiting ~14 days for review. The fix adds an 8-line config file safety check using the existing isFileWorldWritable helper — purely additive, no breaking changes. Is there anything I can adjust to get this merged? 🙏 |
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.
Closes #13.
Summary
Refactors
main()and two of the security gates so the gate sequencing and the bit-mask logic are exercisable in-process. Coverage 42.7% → 62.4% (+19.7pts). No production behavior change — same gates fire in the same order with the same user-visible messages.This addresses the issue's two remaining acceptance criteria after PRs #29 and #38 took the args-handling piece:
mainrefactored for testability (closes the "0% onmain" gap).security.gogate has positive and negative tests (closes the "0% on the security gates" gap).Run's coverage is still tracked separately by #14 (the integration-test issue) — this PR injectsrunFnso the test forrunMaincan assert "wasRuncalled?" without actually invoking it.Refactor shape
security.goussher.goUser-visible error messages —
"Refusing to run due to permissions issue on the ussher executable","Refusing to run as root","User not found"— are preserved verbatim so the existing README troubleshooting headings still match.The
fmt.Printlndiagnostics insideisPathUnsafelyWritablenow include the path being checked ("/usr/local/bin/ussher is group writable"instead of"ussher binary is group writable"). Marginal log-quality improvement; still going to stdout, which is the wrong channel — that's #36, deliberately not in this PR.Tests added
ussher_test.goTestRunMain--versionshort-circuits before any gate (asserted by setting all gates to "would fail" and verifying nothing fired), each gate's failure path, happy path.ussher_test.goTestDefaultDeps_AllSetsecurity_test.goTestUidIsRootsecurity_test.goTestIsPathUnsafelyWritable0700,0750,0755,0770,0775,0707,0757,0777) plus a missing-path failsafe case. Usest.TempDir()andos.Chmodto defeat umask.security_test.goTestIsFileWorldWritablecaptureRunMainbuilds arunDepsthat recordsinitLog/loadConfig/runFninvocations so each test can assert "did we short-circuit before getting here?".Test plan
go test -race -cover ./...green../build.shgreen../ussher(no args),./ussher --version,./ussher Nonexistentall behave as before — same exit codes, same user-visible messages.Out of scope
Run. This PR makesrunFninjectable so a future PR can plug in anhttptest-backed test, but the actual end-to-end coverage of the goroutine fan-out is still that issue's job.initLogshadowing bug. This PR injectsinitLogfor testability but leaves the implementation alone. Fixing the shadowing is its own small change.fmt.Printlnto stdout. Touched the surrounding lines; deliberately didn't change the channel. Adopters who see the messages in sshd's auth log are unaffected by this PR; security.go writes diagnostics to stdout (sshd's input channel) instead of stderr #36 is the place to fix that.security_test.gohard-codesroot/nobody. The new tests don't depend on host users; the existingTestIsValidUsercases still do.https://claude.ai/code/session_013HnepY8MhhxrJJjE5ysW47
Generated by Claude Code