Skip to content

Commit 5425da3

Browse files
authored
feat: LLM conversation reassembly and viewer UI (#10)
* feat: MITM TLS interception for HTTP content inspection Add TLS man-in-the-middle capability so greyproxy can decrypt and log HTTPS request/response content flowing through the proxy. - Add `greyproxy cert generate/install` CLI for CA certificate management - Auto-inject MITM cert paths into HTTP/SOCKS5 handler metadata on startup - Enable sniffing by default in greyproxy.yml for both proxy services - Add OnHTTPRoundTrip callback to Sniffer for decrypted traffic hooks - Wire [MITM] log output in both HTTP and SOCKS5 handlers - Fix GenerateCertificate to auto-detect key type (ECDSA/Ed25519/RSA) instead of hardcoding SHA256WithRSA * feat: Phase 1 observability — capture and display MITM HTTP transactions Add end-to-end HTTP transaction recording for MITM-intercepted requests: - DB: migration for http_transactions table with indexes - Models: HttpTransaction, HttpTransactionJSON, HttpTransactionCreateInput - CRUD: create, get, query with filtering (container, destination, method, date range) - API: GET /api/transactions (list) and GET /api/transactions/:id (detail with body) - UI: Traffic page with HTMX table, filters, pagination, expandable row details - Hook: GlobalHTTPRoundTripHook in sniffer + bridge via gostx.SetGlobalMitmHook - Wire: program.go connects MITM hook to DB storage and EventBus - Fix: auther srcAddrKey type mismatch — use xctx.SrcAddrFromContext for correct client IP resolution (was causing unknown-unknown container names) - Fix: remove custom lt/gt template funcs that shadowed Go builtins and broke int64 comparisons in traffic table (only first row rendered) - Tests: API handler tests (9), HTMX route tests (10), CRUD tests (5), plugin tests * docs: add vibedocs with MITM proposal as 001-mitm.md * feat: Phase 2 MITM - pending HTTP request approval, URL pattern matching, and UI * feat: auto-detect Linux distro for CA cert install Detect whether the system uses update-ca-trust (Arch, Fedora, RHEL) or update-ca-certificates (Debian, Ubuntu) and run the install automatically with sudo, falling back to manual instructions on failure. * fix: increase the body capture to 2MB Most content was truncated. But i need to see what's the required size to support the 1M context from opus * feat: support content decompression * feat: experiment accross reassembly of http * feat: step-by-step turn rendering, tool result merging, subagent linking, and system prompt capture Restructure conversation turns from flat concatenated fields into ordered steps that preserve the actual back-and-forth flow (assistant -> tool calls -> tool results -> assistant). Tool results are now attached directly to their corresponding tool calls. Subagent conversations are linked from parent Agent tool calls. Full system prompt is captured in output JSON. * feat: track conversation viewer and auto-copy to output directory Move viewer.html to cmd/assembleconv/ so it's version-controlled. The assembler now copies it into the output dir as index.html on each run. * feat: incremental conversation assembler with API server and viewer assemble2.py reads directly from greyproxy.db (bypassing the export step), tracks a watermark for incremental processing, stores results in conversation.db, and serves a REST API with a live web viewer. * feat: native conversation dissector with Anthropic support and dashboard UI Port the Python conversation assembler (assemble2.py) into Go as a native greyproxy feature. Introduces a Wireshark-inspired dissector framework for provider-specific HTTP body parsing, with Anthropic as the first dissector. - Dissector framework: interface + registry in internal/greyproxy/dissector/ - Anthropic dissector: parses Messages API requests, SSE responses, extracts session IDs, models, messages, system prompts, and tool calls - SSE parser shared across providers - Database: conversations, turns, processing_state tables (migration 7), conversation_id FK on http_transactions (migration 8) - Assembly engine: subscribes to EventTransactionNew, debounces, groups by session, classifies threads, builds turns with tool result merging, recovers SSE responses, links subagents. Backfills on startup. - REST API: /api/conversations, /api/conversations/:id, /api/conversations/:id/subagents - Conversations page: split-pane layout with sidebar list and detail panel, collapsible system prompts, thinking blocks, tool calls, subagent navigation, live updates via WebSocket - Test fixtures from real Anthropic API transactions * feat: improve conversation UI with markdown rendering, tool I/O display, URL state, and subagent linking * feat: subagent navigation, assembler versioning, and settings maintenance UI - Add "View subagent →" button on Agent tool calls linking to subagent conversations - Add "← Back to parent" navigation when viewing a subagent conversation - Improve linked subagents section with first prompt preview and model info - Auto-scroll to top when navigating between conversations - Add assembler version tracking with auto-rebuild on version change - Add Settings > Advanced > Maintenance section with manual rebuild button - Add Cache-Control: no-store on conversation detail HTMX responses * refactor: remove HTTP pending requests and HTTP-level rule fields Rules are back to domain/IP-level only (container, destination, port). Removes method_pattern, path_pattern, content_action from rules, the pending_http_requests table, and all associated API/UI/CRUD code. Migrations renumbered from 8 to 6. * feat: unified Activity view replacing separate Logs and Traffic pages Merge connection logs and HTTP transactions into a single time-ordered Activity page with UNION ALL query. Connections are deduplicated when HTTP transactions exist for the same destination, with rule info recovered via correlated subquery. Adds Live/Paused toggle for real-time updates, DevTools-style header display with Raw toggle, and full URL in expandable detail panes. * feat: replace CONN badge with lock/unlock icons for decoded vs undecoded traffic Use closed padlock + "TCP" for undecoded connections and open padlock + HTTP method for decoded traffic. Rename filter options to "Decoded (HTTP)" and "Undecoded (TCP)" to clarify why some traffic is not inspected. * feat: add Live/Paused toggle to conversations page and preserve scroll position Replace the static Refresh button with the same Live/Paused toggle pattern used in the Activity page. When paused, incoming events are buffered and shown as a badge count. Also preserve the conversation list scroll position on refresh. * chore: remove prototype scripts and planning documents Remove assemble2.py, viewer2.html (superseded by native Go assembler), and root-level planning/research markdown files that are dev artifacts. * chore: add Python and conversation.db exclusions to .gitignore Add __pycache__/, *.pyc, *.pyo patterns and /conversation.db to prevent development artifacts from being committed. * fix: escape SQL LIKE wildcards in session ID matching Session IDs containing % or _ were treated as LIKE wildcards, causing loadTransactionsForSessions to match unrelated rows. Add escapeLikePattern helper and ESCAPE '\' clause to the query. Tests demonstrate the vulnerability (unescaped % matches all rows) and verify the fix (escaped % matches none). * fix: add mutex to conversation assembler to prevent race conditions RebuildAllConversations (called from HTTP API) and processNewTransactions (called from event loop and debounce timer) could run concurrently, causing TOCTOU races on the last_processed_id cursor. Add sync.Mutex with a locked internal method to serialize all processing. * fix: handle errors instead of silently swallowing them in assembler - Log warnings for rows.Scan failures instead of silent continue - Log debug for json.Unmarshal failures on request bodies - Log warnings for invalid strconv parsing of assembler_version and last_processed_id - Handle time.Parse failures gracefully in heuristic grouping (keep entries in same group instead of using zero time) - Add truncateUTF8 helper to avoid splitting multi-byte characters when truncating thinking previews, system prompt summaries, and subagent first prompts * fix: eliminate data race in assembler debounce timer The pending boolean was read/written from both the main event loop goroutine and the time.AfterFunc callback without synchronization. Replace with a channel-based trigger: the timer sends a signal on a buffered channel, and the main select loop receives it to call processNewTransactions. This keeps all state access on the main goroutine. * feat: MITM TLS visibility, cert management, global toggle, and skip reason tracking Add end-to-end MITM interception visibility: skip reason tracking (no_cert, mitm_disabled, mitm_error, non_http_after_tls, etc.), certificate management API/UI, global MITM toggle in settings, and activity view dedup fixes. Key fixes: - RequestLog model was missing MitmSkipReason field (written but never read) - Handlers now set mitm_error when HandleTLS returns an error - UpdateLatestLogMitmSkipReason matches resolved_hostname (fixes IP vs SNI mismatch) - Activity dedup handles IP-based connection logs vs hostname-based HTTP transactions - Integration tests for HandleTLS covering 5 MITM scenarios * feat: MITM TLS visibility, cert management, global toggle, skip reason tracking, and integration tests Add end-to-end MITM interception visibility: skip reason tracking (no_cert, mitm_disabled, mitm_error, non_http_after_tls, etc.), certificate management API/UI, global MITM toggle in settings, and activity view fixes. Key fixes: - RequestLog model was missing MitmSkipReason field (written but never read) - Handlers now set mitm_error when HandleTLS returns an error - UpdateLatestLogMitmSkipReason matches resolved_hostname (fixes IP vs SNI mismatch) - Activity dedup handles IP-based connection logs vs hostname-based HTTP transactions - Integration tests for HandleTLS covering 5 MITM scenarios * feat: compact collapsible tool call cards in conversation UI - Tool calls now show as single-line cards with icon, name, and smart summary (file path, command description, pattern, etc.) - Click to expand and see full input/result with truncation controls - Add expand/collapse all tools button in conversation header - Extract tool_summary at dissection time from full input before the 300-char truncation, fixing broken previews for Edit/Write - Unify Live button style between activity and conversations pages - Fix activity Live button hover jump (overflow-hidden on ping dot) - Flatten step layout: remove per-HTTP-request borders and padding * fix: remove button hover translateY transform * fix: handle new Anthropic metadata.user_id JSON object format for session extraction The Anthropic API changed metadata.user_id from a plain string ("user_HASH_account_UUID_session_UUID") to a JSON object with a session_id field. This broke session ID extraction, leaving all new transactions unlinked to conversations. * chore: remove vibedocs directory * chore: remove exportlogs/assembleconv experiment * feat: cert install improvements and cert uninstall command - Print destination path on successful cert install - Check if cert is already installed, require -f to overwrite - Add `greyproxy cert uninstall` to remove CA from OS trust store - `greyproxy uninstall` now also removes the CA certificate * fix: remove environment variable hints section from settings UI * chore: ignore local log/script * fix: show all connections in activity log and eagerly report MITM skip reason Remove activity dedup logic that hid connection rows when HTTP transactions existed for the same destination. Old MITM-era transactions were permanently suppressing connection rows even after MITM was disabled. Fire OnMitmSkip callback immediately when MITM is skipped (before the blocking pipe), so long-lived connections get their mitm_skip_reason updated right away instead of waiting for connection close. * feat: show notice that MITM toggle only affects new connections * fix: redact real API key from testdata fixtures
1 parent 07d32a8 commit 5425da3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+11613
-91
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ _testmain.go
3131

3232
*.bak
3333

34+
# Python
35+
__pycache__/
36+
*.pyc
37+
*.pyo
38+
3439
cmd/greyproxy/greyproxy
3540
snap
3641

@@ -42,4 +47,9 @@ dist/
4247
/greyproxy.db
4348
/greyproxy.db-shm
4449
/greyproxy.db-wal
50+
/conversation.db
4551
/greyproxy
52+
exported_logs/
53+
/buildrun.sh
54+
/greyproxy.log
55+
/greyproxy.reload

cmd/greyproxy/cert.go

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
package main
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"crypto/rand"
7+
"crypto/x509"
8+
"crypto/x509/pkix"
9+
"encoding/pem"
10+
"fmt"
11+
"math/big"
12+
"os"
13+
"os/exec"
14+
"path/filepath"
15+
"runtime"
16+
"time"
17+
)
18+
19+
func handleCert(args []string) {
20+
if len(args) == 0 {
21+
fmt.Fprintf(os.Stderr, `Usage: greyproxy cert <command>
22+
23+
Commands:
24+
generate Generate CA certificate and key pair
25+
install Trust the CA certificate on the OS
26+
uninstall Remove the CA certificate from the OS trust store
27+
28+
Options:
29+
-f Force overwrite existing files (generate, install)
30+
`)
31+
os.Exit(1)
32+
}
33+
34+
switch args[0] {
35+
case "generate":
36+
force := len(args) > 1 && args[1] == "-f"
37+
handleCertGenerate(force)
38+
case "install":
39+
force := len(args) > 1 && args[1] == "-f"
40+
handleCertInstall(force)
41+
case "uninstall":
42+
handleCertUninstall()
43+
default:
44+
fmt.Fprintf(os.Stderr, "unknown cert command: %s\n", args[0])
45+
os.Exit(1)
46+
}
47+
}
48+
49+
func handleCertGenerate(force bool) {
50+
dataDir := greyproxyDataHome()
51+
certFile := filepath.Join(dataDir, "ca-cert.pem")
52+
keyFile := filepath.Join(dataDir, "ca-key.pem")
53+
54+
if !force {
55+
if _, err := os.Stat(certFile); err == nil {
56+
fmt.Fprintf(os.Stderr, "CA certificate already exists: %s\nUse -f to overwrite.\n", certFile)
57+
os.Exit(1)
58+
}
59+
if _, err := os.Stat(keyFile); err == nil {
60+
fmt.Fprintf(os.Stderr, "CA key already exists: %s\nUse -f to overwrite.\n", keyFile)
61+
os.Exit(1)
62+
}
63+
}
64+
65+
// Generate ECDSA P-256 key
66+
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
67+
if err != nil {
68+
fmt.Fprintf(os.Stderr, "failed to generate private key: %v\n", err)
69+
os.Exit(1)
70+
}
71+
72+
// Create self-signed CA certificate
73+
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
74+
if err != nil {
75+
fmt.Fprintf(os.Stderr, "failed to generate serial number: %v\n", err)
76+
os.Exit(1)
77+
}
78+
79+
template := &x509.Certificate{
80+
SerialNumber: serialNumber,
81+
Subject: pkix.Name{
82+
CommonName: "Greyproxy CA",
83+
},
84+
NotBefore: time.Now(),
85+
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
86+
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
87+
BasicConstraintsValid: true,
88+
IsCA: true,
89+
MaxPathLen: 0,
90+
MaxPathLenZero: true,
91+
}
92+
93+
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
94+
if err != nil {
95+
fmt.Fprintf(os.Stderr, "failed to create certificate: %v\n", err)
96+
os.Exit(1)
97+
}
98+
99+
// Ensure data directory exists
100+
if err := os.MkdirAll(dataDir, 0700); err != nil {
101+
fmt.Fprintf(os.Stderr, "failed to create data directory: %v\n", err)
102+
os.Exit(1)
103+
}
104+
105+
// Write certificate
106+
certOut, err := os.OpenFile(certFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
107+
if err != nil {
108+
fmt.Fprintf(os.Stderr, "failed to write certificate: %v\n", err)
109+
os.Exit(1)
110+
}
111+
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
112+
certOut.Close()
113+
fmt.Fprintf(os.Stderr, "failed to encode certificate: %v\n", err)
114+
os.Exit(1)
115+
}
116+
certOut.Close()
117+
118+
// Write private key
119+
keyBytes, err := x509.MarshalECPrivateKey(privateKey)
120+
if err != nil {
121+
fmt.Fprintf(os.Stderr, "failed to marshal private key: %v\n", err)
122+
os.Exit(1)
123+
}
124+
125+
keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
126+
if err != nil {
127+
fmt.Fprintf(os.Stderr, "failed to write key: %v\n", err)
128+
os.Exit(1)
129+
}
130+
if err := pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}); err != nil {
131+
keyOut.Close()
132+
fmt.Fprintf(os.Stderr, "failed to encode key: %v\n", err)
133+
os.Exit(1)
134+
}
135+
keyOut.Close()
136+
137+
fmt.Printf("CA certificate: %s\n", certFile)
138+
fmt.Printf("CA private key: %s\n", keyFile)
139+
fmt.Println("\nRun 'greyproxy cert install' to trust this CA on your system.")
140+
}
141+
142+
// linuxCertInstallInfo returns the destination path and update command
143+
// appropriate for the current Linux distribution.
144+
func linuxCertInstallInfo() (destPath, updateCmd string) {
145+
// Arch Linux, Fedora, RHEL, CentOS, openSUSE use update-ca-trust
146+
if _, err := exec.LookPath("update-ca-trust"); err == nil {
147+
return "/etc/ca-certificates/trust-source/anchors/greyproxy-ca.crt", "update-ca-trust"
148+
}
149+
// Debian, Ubuntu, and derivatives use update-ca-certificates
150+
return "/usr/local/share/ca-certificates/greyproxy-ca.crt", "update-ca-certificates"
151+
}
152+
153+
func isCertInstalled() bool {
154+
switch runtime.GOOS {
155+
case "darwin":
156+
err := exec.Command("security", "find-certificate", "-c", "Greyproxy CA").Run()
157+
return err == nil
158+
case "linux":
159+
destPath, _ := linuxCertInstallInfo()
160+
_, err := os.Stat(destPath)
161+
return err == nil
162+
default:
163+
return false
164+
}
165+
}
166+
167+
func certInstallLocation() string {
168+
switch runtime.GOOS {
169+
case "darwin":
170+
return "/Library/Keychains/System.keychain"
171+
case "linux":
172+
destPath, _ := linuxCertInstallInfo()
173+
return destPath
174+
default:
175+
return "(unknown)"
176+
}
177+
}
178+
179+
func handleCertInstall(force bool) {
180+
certFile := filepath.Join(greyproxyDataHome(), "ca-cert.pem")
181+
182+
if _, err := os.Stat(certFile); os.IsNotExist(err) {
183+
fmt.Fprintf(os.Stderr, "CA certificate not found: %s\nRun 'greyproxy cert generate' first.\n", certFile)
184+
os.Exit(1)
185+
}
186+
187+
if !force && isCertInstalled() {
188+
fmt.Fprintf(os.Stderr, "CA certificate is already installed at %s\nUse -f to overwrite.\n", certInstallLocation())
189+
os.Exit(1)
190+
}
191+
192+
switch runtime.GOOS {
193+
case "darwin":
194+
// Remove any existing Greyproxy CA cert to avoid errSecDuplicateItem (-25294)
195+
exec.Command("security", "delete-certificate", "-c", "Greyproxy CA").Run()
196+
197+
fmt.Println("Installing CA certificate into system trust store (requires sudo)...")
198+
cmd := exec.Command("sudo", "security", "add-trusted-cert",
199+
"-d", "-r", "trustRoot",
200+
"-k", "/Library/Keychains/System.keychain",
201+
certFile,
202+
)
203+
cmd.Stdout = os.Stdout
204+
cmd.Stderr = os.Stderr
205+
cmd.Stdin = os.Stdin
206+
if err := cmd.Run(); err != nil {
207+
fmt.Fprintf(os.Stderr, "\nAutomatic install failed. Please run manually:\n\n")
208+
fmt.Fprintf(os.Stderr, " sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain \"%s\"\n\n", certFile)
209+
os.Exit(1)
210+
}
211+
fmt.Printf("CA certificate installed and trusted in %s\n", certInstallLocation())
212+
213+
case "linux":
214+
destPath, updateCmd := linuxCertInstallInfo()
215+
fmt.Println("Installing CA certificate into system trust store (requires sudo)...")
216+
cpCmd := exec.Command("sudo", "cp", certFile, destPath)
217+
cpCmd.Stdout = os.Stdout
218+
cpCmd.Stderr = os.Stderr
219+
cpCmd.Stdin = os.Stdin
220+
if err := cpCmd.Run(); err != nil {
221+
fmt.Fprintf(os.Stderr, "\nAutomatic install failed. Please run manually:\n\n")
222+
fmt.Fprintf(os.Stderr, " sudo cp %s %s\n", certFile, destPath)
223+
fmt.Fprintf(os.Stderr, " sudo %s\n\n", updateCmd)
224+
os.Exit(1)
225+
}
226+
updCmd := exec.Command("sudo", updateCmd)
227+
updCmd.Stdout = os.Stdout
228+
updCmd.Stderr = os.Stderr
229+
updCmd.Stdin = os.Stdin
230+
if err := updCmd.Run(); err != nil {
231+
fmt.Fprintf(os.Stderr, "\nCertificate copied but trust update failed. Please run manually:\n\n")
232+
fmt.Fprintf(os.Stderr, " sudo %s\n\n", updateCmd)
233+
os.Exit(1)
234+
}
235+
fmt.Printf("CA certificate installed and trusted at %s\n", destPath)
236+
237+
default:
238+
fmt.Printf("CA certificate is at: %s\n", certFile)
239+
fmt.Printf("Please install it manually in your OS trust store.\n")
240+
}
241+
}
242+
243+
func handleCertUninstall() {
244+
switch runtime.GOOS {
245+
case "darwin":
246+
if !isCertInstalled() {
247+
fmt.Println("CA certificate is not installed in the system trust store.")
248+
return
249+
}
250+
fmt.Println("Removing CA certificate from system trust store...")
251+
cmd := exec.Command("security", "delete-certificate", "-c", "Greyproxy CA")
252+
cmd.Stdout = os.Stdout
253+
cmd.Stderr = os.Stderr
254+
if err := cmd.Run(); err != nil {
255+
fmt.Fprintf(os.Stderr, "\nAutomatic removal failed. Please run manually:\n\n")
256+
fmt.Fprintf(os.Stderr, " security delete-certificate -c \"Greyproxy CA\"\n\n")
257+
os.Exit(1)
258+
}
259+
fmt.Println("CA certificate removed from system trust store.")
260+
261+
case "linux":
262+
destPath, updateCmd := linuxCertInstallInfo()
263+
if _, err := os.Stat(destPath); os.IsNotExist(err) {
264+
fmt.Println("CA certificate is not installed in the system trust store.")
265+
return
266+
}
267+
fmt.Println("Removing CA certificate from system trust store (requires sudo)...")
268+
rmCmd := exec.Command("sudo", "rm", destPath)
269+
rmCmd.Stdout = os.Stdout
270+
rmCmd.Stderr = os.Stderr
271+
rmCmd.Stdin = os.Stdin
272+
if err := rmCmd.Run(); err != nil {
273+
fmt.Fprintf(os.Stderr, "\nAutomatic removal failed. Please run manually:\n\n")
274+
fmt.Fprintf(os.Stderr, " sudo rm %s\n", destPath)
275+
fmt.Fprintf(os.Stderr, " sudo %s\n\n", updateCmd)
276+
os.Exit(1)
277+
}
278+
updCmd := exec.Command("sudo", updateCmd)
279+
updCmd.Stdout = os.Stdout
280+
updCmd.Stderr = os.Stderr
281+
updCmd.Stdin = os.Stdin
282+
if err := updCmd.Run(); err != nil {
283+
fmt.Fprintf(os.Stderr, "\nCertificate removed but trust update failed. Please run manually:\n\n")
284+
fmt.Fprintf(os.Stderr, " sudo %s\n\n", updateCmd)
285+
os.Exit(1)
286+
}
287+
fmt.Printf("CA certificate removed from %s\n", destPath)
288+
289+
default:
290+
fmt.Println("Please remove the Greyproxy CA certificate manually from your OS trust store.")
291+
}
292+
}

cmd/greyproxy/install.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,15 @@ func handleUninstall(args []string) {
249249
binDst := installBinPath()
250250
label := serviceLabel()
251251

252+
certInstalled := isCertInstalled()
253+
252254
fmt.Printf("Ready to uninstall greyproxy. This will:\n")
253255
fmt.Printf(" 1. Stop the greyproxy service\n")
254256
fmt.Printf(" 2. Remove the %s\n", label)
255257
fmt.Printf(" 3. Remove %s\n", binDst)
258+
if certInstalled {
259+
fmt.Printf(" 4. Remove CA certificate from system trust store\n")
260+
}
256261

257262
if !force {
258263
fmt.Printf("\nProceed? [Y/n] ")
@@ -284,6 +289,11 @@ func handleUninstall(args []string) {
284289
os.Exit(1)
285290
}
286291
fmt.Printf("Removed %s\n", binDst)
292+
293+
// 4. Remove CA certificate from trust store
294+
if certInstalled {
295+
handleCertUninstall()
296+
}
287297
}
288298

289299
func copyBinary(src, dst string) error {

cmd/greyproxy/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ func main() {
142142
case "uninstall":
143143
handleUninstall(os.Args[2:])
144144

145+
case "cert":
146+
handleCert(os.Args[2:])
147+
145148
case "-V", "--version":
146149
fmt.Fprintf(os.Stdout, "greyproxy %s (%s %s/%s)\n built: %s\n commit: %s\n",
147150
version, runtime.Version(), runtime.GOOS, runtime.GOARCH, buildTime, gitCommit)
@@ -159,6 +162,7 @@ Usage: greyproxy <command>
159162
160163
Commands:
161164
serve Run the proxy server in foreground
165+
cert Manage MITM CA certificate (generate/install/uninstall)
162166
install Install binary and register as a background service [-f]
163167
uninstall Stop service, remove registration and binary [-f]
164168
service Manage the OS service (start/stop/restart/status/...)

0 commit comments

Comments
 (0)