refactor: extract git-signed-push crate for push-certificate verification
commit
ba8637frefactor: extract git-signed-push crate for push-certificate verification
Signed push now has both halves the plan called for: git-member holds the trust data model, git-signed-push holds the verification engine (cert parsing, nonce check, ssh-keygen -Y verify, role gating, the bootstrap-window rule). git-ents-server’s pre-receive hook shrinks to one call into the new crate.
feat: add git-signed-push crate (pre_receive, verify_certificate, identify_signer) docs: point conformance.adoc at git-signed-push/src/lib.rs Assisted-by: Claude:claude-sonnet-5
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
Cargo.lock
@@ -1511,6 +1511,7 @@
"git-comment",
"git-ents-core",
"git-member",
+ "git-signed-push",
"git-store",
"git-toolchain",
"gix-actor",
@@ -1538,6 +1539,16 @@
"thiserror 2.0.18",
]
+[[package]]
+name = "git-signed-push"
+version = "0.0.0"
+dependencies = [
+ "git-ents-core",
+ "git-member",
+ "git-store",
+ "tempfile",
+]
+
[[package]]
name = "git-store"
version = "0.0.0"
Cargo.toml
@@ -7,6 +7,7 @@
"crates/git-ents-core",
"crates/git-ents-server",
"crates/git-member",
+ "crates/git-signed-push",
"crates/git-store",
"crates/git-toolchain",
]
@@ -57,6 +58,7 @@
git-ents-core = { path = "crates/git-ents-core" }
git-ents-server = { path = "crates/git-ents-server" }
git-member = { path = "crates/git-member" }
+git-signed-push = { path = "crates/git-signed-push" }
git-store = { path = "crates/git-store" }
git-toolchain = { path = "crates/git-toolchain" }
gix = "0.84"
crates/git-ents-server/Cargo.toml
@@ -21,6 +21,7 @@
git-anchor = { workspace = true }
git-comment = { workspace = true }
git-member = { workspace = true }
+git-signed-push = { workspace = true }
git-store = { workspace = true }
git-toolchain = { workspace = true }
gix-actor = { workspace = true }
docs/spec/conformance.adoc
@@ -27,8 +27,8 @@
|`members.allowed-signers` |`git-member/src/members.rs` (`allowed_signers`, `member_lines`) |`members::renders_a_wildcard_allowed_signers_file`, `renders_the_validity_window_as_comma_joined_options`
|`revocations.ref` |`git-member/src/revocations.rs` |`revocations::store_then_load_round_trips_the_revocations`
|`revocations.ca` |`git-member/src/members.rs` (`without_revoked`, CA branch untouched) |`members::without_revoked_leaves_ca_members_untouched`
-|`auth.bootstrap` |`git-ents-server/src/verify.rs` (`pre_receive`, empty-members early return) |`pre_receive` integration tests (bootstrap case)
-|`auth.signed-push` |`git-ents-server/src/verify.rs` (`verify_certificate`) |`pre_receive::rejects_an_unsigned_push_when_signers_exist`, `rejects_a_push_signed_by_an_unknown_key`
+|`auth.bootstrap` |`git-signed-push/src/lib.rs` (`pre_receive`, empty-members early return) |`pre_receive` integration tests (bootstrap case)
+|`auth.signed-push` |`git-signed-push/src/lib.rs` (`verify_certificate`) |`pre_receive::rejects_an_unsigned_push_when_signers_exist`, `rejects_a_push_signed_by_an_unknown_key`
|`auth.nonce` |`git-ents-server/src/http.rs` (`backend_config_injects_nonce_seed_and_hooks_path`) |`http::tests::backend_config_injects_nonce_seed_and_hooks_path`
|`auth.client-setup` |`git-ents/src/main.rs` (`setup`, `ensure_key`) | _(manual/CLI UX, no automated test)_
|`cli.remote-admin` |`git-ents/src/main.rs` (`sync`, `push_signed`) | _(manual/CLI UX, no automated test)_
@@ -71,8 +71,8 @@
|`nonfunctional.no-unsafe` |workspace-wide (`#![forbid(unsafe_code)]`) |`cargo clippy --workspace --all-targets`
|`nonfunctional.object-store` |`git-store/src/lib.rs` (`Store::open`, common-dir odb) | _(see the module's own doc comment; exercised by hook integration tests)_
|`compat.git` |`git-ents-server/src/http.rs`, `web/git.rs` (subprocess invocations) | _(implicit in all integration tests)_
-|`compat.ssh-keygen` |`git-ents-server/src/verify.rs` (`verify_certificate`), `web/write.rs` (`verify_login_signature`) |`pre_receive::*`, `web_edit::*`
-|`compat.openssh-signed-push` |`git-ents-server/src/verify.rs` (`GIT_PUSH_CERT`, `GIT_PUSH_CERT_NONCE_STATUS`) |`pre_receive::rejects_a_push_signed_by_an_expired_key`
+|`compat.ssh-keygen` |`git-signed-push/src/lib.rs` (`verify_certificate`), `web/write.rs` (`verify_login_signature`) |`pre_receive::*`, `web_edit::*`
+|`compat.openssh-signed-push` |`git-signed-push/src/lib.rs` (`GIT_PUSH_CERT`, `GIT_PUSH_CERT_NONCE_STATUS`) |`pre_receive::rejects_a_push_signed_by_an_expired_key`
|`compat.sprite` |`git-ents-server/src/checks.rs` (`ensure_auth`, `ensure_sprite`) | _(requires a live Sprite; not covered by unit tests)_
|`compat.cgi` |`git-ents-server/src/http.rs` (`backend`, CGI env vars) |`server::push_then_clone_round_trip`
|`compat.edition` |workspace `Cargo.toml` (`edition = "2024"`, `publish = false`) | _(build-time)_
crates/git-ents-server/src/lib.rs
@@ -10,7 +10,6 @@
/// MIME-keyed document rendering (HTML and plain-text), shared by the web
/// UI and the `git-ents` CLI, which embeds this crate as a library.
pub mod render;
-mod verify;
mod web;
use std::net::SocketAddr;
@@ -119,7 +118,7 @@
/// wins over the hardcoded default.
pub fn run(args: Args) -> ExitCode {
if let Some(Command::PreReceive) = args.command {
- return match verify::pre_receive() {
+ return match git_signed_push::pre_receive() {
Ok(()) => ExitCode::SUCCESS,
Err(reason) => {
eprintln!("error: {reason}");
crates/git-ents-server/src/verify.rs → crates/git-signed-push/src/lib.rs
crates/git-signed-push/Cargo.toml
@@ -1,0 +1,18 @@
+[package]
+name = "git-signed-push"
+version = "0.0.0"
+edition.workspace = true
+publish.workspace = true
+license.workspace = true
+
+[dependencies]
+git-ents-core = { workspace = true }
+git-member = { workspace = true }
+git-store = { workspace = true }
+tempfile = { workspace = true }
+
+[dev-dependencies]
+git-store = { workspace = true, features = ["test-support"] }
+
+[lints]
+workspace = true