From 1541fd432e24731e0e2e1fa6e12648b3cd223158 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 18 Oct 2026 15:33:23 +0530 Subject: [PATCH] fix: use the system CA roots for -tls without -ca-cert promptctl push/publish -tls passed an empty -ca-cert straight to credentials.NewClientTLSFromFile, which failed with `open : The system cannot find the file specified.` A server with a public certificate could not be reached without pointing -ca-cert at the OS bundle. With -tls and no -ca-cert, promptctl now verifies the server against the system root pool. -ca-cert still selects a private CA. Fixes #4 Co-Authored-By: Claude Opus 5.5 --- README.md | 4 +++- cmd/promptctl/dial_test.go | 22 ++++++++++++++++++++++ cmd/promptctl/main.go | 27 +++++++++++++++++++-------- 3 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 cmd/promptctl/dial_test.go diff --git a/README.md b/README.md index 22a4bb0..4180be8 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,9 @@ flowchart LR subscribed agents are notified in the same step. Publishing is idempotent (unchanged content is a no-op), so only real changes fire a notification. Git stays the source of truth; the server is the live serving copy. -- `push` / `publish` accept `-tls` and `-ca-cert` for TLS servers. +- `push` / `publish` accept `-tls` for TLS servers. The server certificate is + verified against the system root CAs, or against `-ca-cert ` for a + private CA. ## Configuration diff --git a/cmd/promptctl/dial_test.go b/cmd/promptctl/dial_test.go new file mode 100644 index 0000000..8d290f7 --- /dev/null +++ b/cmd/promptctl/dial_test.go @@ -0,0 +1,22 @@ +package main + +import "testing" + +func TestTransportCreds(t *testing.T) { + c, err := transportCreds(false, "") + if err != nil || c.Info().SecurityProtocol != "insecure" { + t.Errorf("no -tls: got %v, %v; want insecure", c, err) + } + // -tls without -ca-cert verifies against the system roots instead of + // failing to open the file "". + c, err = transportCreds(true, "") + if err != nil { + t.Fatalf("-tls without -ca-cert: %v", err) + } + if p := c.Info().SecurityProtocol; p != "tls" { + t.Errorf("-tls without -ca-cert: protocol %q, want tls", p) + } + if _, err := transportCreds(true, "does-not-exist.pem"); err == nil { + t.Error("-tls -ca-cert with a missing file: want an error") + } +} diff --git a/cmd/promptctl/main.go b/cmd/promptctl/main.go index d9e6ad6..c4f3fea 100644 --- a/cmd/promptctl/main.go +++ b/cmd/promptctl/main.go @@ -13,6 +13,7 @@ package main import ( "context" + "crypto/tls" "errors" "flag" "fmt" @@ -187,7 +188,7 @@ func pushCmd(args []string) { fs := flag.NewFlagSet("push", flag.ExitOnError) server := fs.String("server", "", "after pushing, publish prompts to this live server (host:port)") useTLS := fs.Bool("tls", false, "use TLS for -server") - caCert := fs.String("ca-cert", "", "CA cert for TLS -server (optional)") + caCert := fs.String("ca-cert", "", "CA cert for TLS -server (default: system roots)") fs.Parse(args) validateAll() @@ -204,7 +205,7 @@ func publishCmd(args []string) { fs := flag.NewFlagSet("publish", flag.ExitOnError) server := fs.String("server", "", "live server address (host:port)") useTLS := fs.Bool("tls", false, "use TLS") - caCert := fs.String("ca-cert", "", "CA cert for TLS (optional)") + caCert := fs.String("ca-cert", "", "CA cert for TLS (default: system roots)") fs.Parse(args) if *server == "" { log.Fatal("publish needs -server host:port") @@ -237,12 +238,9 @@ func publishToServer(server string, useTLS bool, caCert string, paths []string) } func dial(addr string, useTLS bool, caCert string) *grpc.ClientConn { - creds := insecure.NewCredentials() - if useTLS { - var err error - if creds, err = credentials.NewClientTLSFromFile(caCert, ""); err != nil { - log.Fatal(err) - } + creds, err := transportCreds(useTLS, caCert) + if err != nil { + log.Fatal(err) } conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds)) if err != nil { @@ -251,6 +249,19 @@ func dial(addr string, useTLS bool, caCert string) *grpc.ClientConn { return conn } +// transportCreds picks the gRPC transport: plaintext, TLS verified against the +// system root CAs, or TLS verified against the -ca-cert file. +func transportCreds(useTLS bool, caCert string) (credentials.TransportCredentials, error) { + switch { + case !useTLS: + return insecure.NewCredentials(), nil + case caCert == "": + return credentials.NewTLS(&tls.Config{}), nil // nil RootCAs = system pool + default: + return credentials.NewClientTLSFromFile(caCert, "") + } +} + func authCtx() context.Context { ctx := context.Background() if token := os.Getenv("PRIOMPT_TOKEN"); token != "" {