From 5cd47079ee762125817612d2bf82a0bca07da3ad Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 5 Nov 2025 06:37:21 +0000 Subject: preparing to build grasp-audit against git-relay --- .../2025-11-04-authorization-flow-diagram.txt | 299 +++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt (limited to 'docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt') diff --git a/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt b/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt new file mode 100644 index 0000000..10fb529 --- /dev/null +++ b/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt @@ -0,0 +1,299 @@ +╔═══════════════════════════════════════════════════════════════════════════════╗ +║ GIT PUSH AUTHORIZATION FLOW (INLINE) ║ +╚═══════════════════════════════════════════════════════════════════════════════╝ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ CLIENT: git push │ +└────────────────────────────────┬────────────────────────────────────────────┘ + │ + │ HTTP POST + │ /npub/repo.git/git-receive-pack + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ACTIX-WEB ROUTER (git-http-backend) │ +│ │ +│ Route: /{namespace}/{repo}/git-receive-pack │ +│ Handler: git_receive_pack() │ +└────────────────────────────────┬────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ STEP 1: RESOLVE REPOSITORY PATH │ +│ │ +│ GitConfig::rewrite("/npub/repo") → /data/git/npub/repo.git │ +└────────────────────────────────┬────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ STEP 2: VALIDATE REPOSITORY EXISTS │ +│ │ +│ ✓ Check HEAD exists │ +│ ✓ Check config exists │ +│ ✓ Check bare = true │ +│ │ +│ ❌ If not: Return 400 "Repository not found" │ +└────────────────────────────────┬────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ STEP 3: READ REQUEST BODY (MODIFIED) │ +│ │ +│ • Read full request body into memory │ +│ • Decode gzip if Content-Encoding: gzip │ +│ • Store in body_data: Vec │ +└────────────────────────────────┬────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ STEP 4: PARSE REF UPDATES (NEW!) │ +│ │ +│ parse_receive_pack_request(&body_data) → Vec │ +│ │ +│ Git Pack Protocol: │ +│ ┌──────────────────────────────────────────────────────────────┐ │ +│ │ 0000000000000000000000000000000000000000 a1b2c3d4e5f6... │ │ +│ │ refs/heads/main\0 report-status\n │ │ +│ │ │ │ +│ │ old_oid: 0000... (new branch) │ │ +│ │ new_oid: a1b2c3d4e5f6... │ │ +│ │ ref_name: refs/heads/main │ │ +│ └──────────────────────────────────────────────────────────────┘ │ +│ │ +│ Result: RefUpdate { │ +│ old_oid: "0000...", │ +│ new_oid: "a1b2c3d4e5f6...", │ +│ ref_name: "refs/heads/main" │ +│ } │ +└────────────────────────────────┬────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ STEP 5: VALIDATE AUTHORIZATION (NEW!) │ +│ │ +│ validator.validate_push(npub, identifier, &ref_updates).await │ +│ │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ PushValidator::validate_push() │ │ +│ │ │ │ +│ │ 1. Get latest state event from Nostr relay │ │ +│ │ • Query: kind=30618, d=identifier, author=npub │ │ +│ │ • Extract refs from state event │ │ +│ │ │ │ +│ │ 2. For each ref update: │ │ +│ │ • If refs/heads/* or refs/tags/*: │ │ +│ │ - Check state event has matching ref │ │ +│ │ - Check new_oid matches state event oid │ │ +│ │ - ❌ Reject if mismatch │ │ +│ │ • If refs/nostr/*: │ │ +│ │ - ✅ Always allow (PRs) │ │ +│ │ │ │ +│ │ 3. Get maintainers (recursive) │ │ +│ │ • Extract maintainers from announcement │ │ +│ │ • Recursively resolve maintainer sets │ │ +│ │ • Check if pusher is in maintainer list │ │ +│ │ • ❌ Reject if not maintainer │ │ +│ │ │ │ +│ │ 4. Return Ok(()) or Err(message) │ │ +│ └────────────────────────────────────────────────────────────┘ │ +│ │ +│ ❌ If validation fails: │ +│ Return 403 Forbidden │ +│ { │ +│ "error": "unauthorized", │ +│ "message": "Push rejected: refs/heads/main points to ..., state has..."│ +│ } │ +└────────────────────────────────┬────────────────────────────────────────────┘ + │ + │ ✅ AUTHORIZED + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ STEP 6: SPAWN GIT RECEIVE-PACK (EXISTING) │ +│ │ +│ Command::new("git") │ +│ .arg("receive-pack") │ +│ .arg("--stateless-rpc") │ +│ .arg(".") │ +│ .current_dir(&repo_path) │ +│ .spawn() │ +│ │ +│ • Write body_data to git stdin │ +│ • Stream git stdout back to client │ +└────────────────────────────────┬────────────────────────────────────────────┘ + │ + │ Stream response + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ CLIENT: git push success │ +└─────────────────────────────────────────────────────────────────────────────┘ + + +╔═══════════════════════════════════════════════════════════════════════════════╗ +║ COMPARISON: BEFORE vs AFTER ║ +╚═══════════════════════════════════════════════════════════════════════════════╝ + +┌─────────────────────────────────┬─────────────────────────────────────────┐ +│ BEFORE (git-http-backend) │ AFTER (our fork) │ +├─────────────────────────────────┼─────────────────────────────────────────┤ +│ 1. Resolve path │ 1. Resolve path │ +│ 2. Check bare repo │ 2. Check bare repo │ +│ 3. Read request body │ 3. Read request body │ +│ 4. Spawn git immediately ❌ │ 4. Parse ref updates ← NEW │ +│ 5. Stream response │ 5. Validate authorization ← NEW │ +│ │ 6. Spawn git (if authorized) │ +│ │ 7. Stream response │ +└─────────────────────────────────┴─────────────────────────────────────────┘ + +┌─────────────────────────────────┬─────────────────────────────────────────┐ +│ AUTHORIZATION │ METHOD │ +├─────────────────────────────────┼─────────────────────────────────────────┤ +│ ❌ None │ No validation │ +│ ⚠️ Git hooks (pre-receive) │ After git accepts push │ +│ ✅ Inline (our approach) │ Before git touches repository │ +└─────────────────────────────────┴─────────────────────────────────────────┘ + + +╔═══════════════════════════════════════════════════════════════════════════════╗ +║ KEY MODIFICATIONS NEEDED ║ +╚═══════════════════════════════════════════════════════════════════════════════╝ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ FILE: src/actix/git_receive_pack.rs │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ CHANGE 1: Add validator parameter │ +│ ──────────────────────────────────────────────────────────────────────── │ +│ pub async fn git_receive_pack( │ +│ request: HttpRequest, │ +│ mut payload: Payload, │ +│ service: web::Data, │ +│ + validator: web::Data, // ← ADD THIS │ +│ ) -> impl Responder { │ +│ │ +│ CHANGE 2: Parse ref updates after reading body │ +│ ──────────────────────────────────────────────────────────────────────── │ +│ // Read and decode body (existing) │ +│ let body_data = read_and_decode_body(&mut payload, &request).await?; │ +│ │ +│ + // Parse ref updates (NEW) │ +│ + let ref_updates = parse_receive_pack_request(&body_data)?; │ +│ │ +│ CHANGE 3: Validate before spawning git │ +│ ──────────────────────────────────────────────────────────────────────── │ +│ + // Extract repo info from path │ +│ + let (npub, identifier) = extract_repo_info(&request.uri().path())?; │ +│ + │ +│ + // Validate authorization │ +│ + if let Err(e) = validator.validate_push(&npub, &identifier, │ +│ + &ref_updates).await { │ +│ + return HttpResponse::Forbidden() │ +│ + .json(json!({ │ +│ + "error": "unauthorized", │ +│ + "message": e.to_string(), │ +│ + })); │ +│ + } │ +│ │ +│ // Spawn git (existing, unchanged) │ +│ let mut cmd = Command::new("git"); │ +│ // ... rest of existing code ... │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ NEW FILE: src/git/protocol.rs │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ pub struct RefUpdate { │ +│ pub old_oid: String, │ +│ pub new_oid: String, │ +│ pub ref_name: String, │ +│ } │ +│ │ +│ pub fn parse_receive_pack_request(body: &[u8]) -> Result> { │ +│ // Parse git pack protocol │ +│ // Extract ref updates from pkt-line format │ +│ } │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ NEW FILE: src/git/authorization.rs │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ pub struct PushValidator { │ +│ storage: Storage, │ +│ } │ +│ │ +│ impl PushValidator { │ +│ pub async fn validate_push( │ +│ &self, │ +│ npub: &str, │ +│ identifier: &str, │ +│ updates: &[RefUpdate], │ +│ ) -> Result<()> { │ +│ // Query Nostr relay for state event │ +│ // Validate each ref update │ +│ // Check maintainer permissions │ +│ } │ +│ } │ +└─────────────────────────────────────────────────────────────────────────────┘ + + +╔═══════════════════════════════════════════════════════════════════════════════╗ +║ BENEFITS OF INLINE AUTH ║ +╚═══════════════════════════════════════════════════════════════════════════════╝ + +✅ BETTER ERROR MESSAGES + • Return 403 with JSON error details + • Show exactly which ref failed validation + • Show expected vs. actual commit + • Better developer experience + +✅ SIMPLER DEPLOYMENT + • No git hooks to manage + • No symlinks or hook installation + • Single binary handles everything + • Easier to test + +✅ TIGHTER INTEGRATION + • Direct access to Nostr relay state + • Shared storage layer + • No IPC between components + • Atomic validation + +✅ EASIER TESTING + • Pure Rust unit tests + • Mock validator for testing + • No subprocess coordination + • Deterministic behavior + +✅ SECURITY + • Validation before git touches repo + • Can't bypass by manipulating hooks + • Centralized authorization logic + • Audit trail in application logs + + +╔═══════════════════════════════════════════════════════════════════════════════╗ +║ TIMELINE ║ +╚═══════════════════════════════════════════════════════════════════════════════╝ + +Week 1: Foundation +├─ Day 1-2: Fork git-http-backend, set up integration +├─ Day 3-4: Add git2, implement GitRepository +└─ Day 5: Add protocol parsing module + +Week 2: Authorization +├─ Day 1-2: Implement PushValidator +├─ Day 3-4: Modify git_receive_pack handler +└─ Day 5: Integration tests + +Week 3: Polish +├─ Day 1-2: Add CORS support +├─ Day 3-4: Error handling improvements +└─ Day 5: E2E tests with real git + +Week 4: Compliance +├─ Day 1-3: GRASP-01 compliance testing +├─ Day 4: Performance testing +└─ Day 5: Documentation + + +Status: ✅ Analysis complete, ready to implement -- cgit v1.2.3