package cli import ( "crypto/x509" "encoding/pem" "fmt" "os" "path/filepath" "strings" "time" "github.com/spf13/cobra" "gitea.mynodes.xyz/ilker/llmgw/internal/tlsconf" ) // defaultPKIDir is where the authority lives on the machine that issues // certificates. Only that machine ever holds ca-key.pem; everything else gets // ca.pem and its own leaf. const defaultPKIDir = "/etc/llmgw/pki" func newCertCmd() *cobra.Command { cmd := &cobra.Command{ Use: "cert", Short: "Issue the certificates the control plane and agents authenticate with", Long: "llmgw's internal connections use mutual TLS, and a public authority cannot\n" + "supply them: Let's Encrypt and its peers stopped issuing certificates\n" + "carrying the clientAuth usage, so nothing they hand out can authenticate an\n" + "agent. These commands run a small private authority instead.\n\n" + "They work on files and talk to nothing, so they run wherever you keep the\n" + "authority — not necessarily on a cluster machine.", } cmd.AddCommand(newInitCACmd(), newIssueServerCmd(), newIssueClientCmd(), newCertInspectCmd()) return cmd } func newInitCACmd() *cobra.Command { var ( dir string cn string force bool ) cmd := &cobra.Command{ Use: "init-ca", Short: "Create the cluster's certificate authority", Long: "Writes ca.pem and ca-key.pem. Copy ca.pem to every machine; leave\n" + "ca-key.pem here, because whoever holds it can mint any node's identity.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { keyPath := filepath.Join(dir, "ca-key.pem") if _, err := os.Stat(keyPath); err == nil && !force { // Overwriting the key orphans every certificate ever issued from // it, across every machine, with no warning until connections // start failing. return fmt.Errorf("%s already exists; re-running would invalidate every "+ "certificate issued from it (pass --force if that is what you want)", keyPath) } ca, err := tlsconf.NewCA(cn) if err != nil { return err } if err := ca.Write(dir); err != nil { return err } cmd.Printf("authority %q written to %s\n", cn, dir) cmd.Printf(" ca.pem trust this everywhere (valid until %s)\n", ca.Cert.NotAfter.Format(time.DateOnly)) cmd.Printf(" ca-key.pem keep it here, mode 0600\n") return nil }, } cmd.Flags().StringVar(&dir, "dir", defaultPKIDir, "directory holding the authority") cmd.Flags().StringVar(&cn, "cn", "llmgw cluster CA", "common name for the authority") cmd.Flags().BoolVar(&force, "force", false, "replace an existing authority") return cmd } func newIssueServerCmd() *cobra.Command { var ( dir string out string hosts []string name string ) cmd := &cobra.Command{ Use: "issue-server ", Short: "Issue the control plane's certificate", Long: "Every address an agent might dial must appear in --host, including bare\n" + "IP addresses. Hostname verification reads subject alternative names and\n" + "ignores the common name entirely, so a certificate that names the host\n" + "only in its subject is rejected by every client.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ca, err := loadCA(dir) if err != nil { return err } leaf, err := ca.IssueServer(args[0], hosts) if err != nil { return err } if name == "" { name = "server" } if err := leaf.Write(out, name); err != nil { return err } return describeIssued(cmd, out, name, hosts) }, } cmd.Flags().StringVar(&dir, "dir", defaultPKIDir, "directory holding the authority") cmd.Flags().StringVar(&out, "out", ".", "directory to write the certificate into") cmd.Flags().StringVar(&name, "name", "server", "base filename for the certificate") cmd.Flags().StringArrayVar(&hosts, "host", nil, "address agents dial; repeatable (IP or DNS name)") return cmd } func newIssueClientCmd() *cobra.Command { var dir, out string cmd := &cobra.Command{ Use: "issue-client ", Short: "Issue one agent's certificate", Long: "The node id becomes the certificate's common name, and the control plane\n" + "refuses any agent whose claimed id does not match it. That is what makes a\n" + "node's identity something it proves rather than something it picks.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ca, err := loadCA(dir) if err != nil { return err } leaf, err := ca.IssueClient(args[0]) if err != nil { return err } if err := leaf.Write(out, args[0]); err != nil { return err } return describeIssued(cmd, out, args[0], nil) }, } cmd.Flags().StringVar(&dir, "dir", defaultPKIDir, "directory holding the authority") cmd.Flags().StringVar(&out, "out", ".", "directory to write the certificate into") return cmd } func newCertInspectCmd() *cobra.Command { return &cobra.Command{ Use: "inspect ", Short: "Print what a certificate actually says", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cert, err := readCert(args[0]) if err != nil { return err } cmd.Printf("subject %s\n", cert.Subject) cmd.Printf("issuer %s\n", cert.Issuer) cmd.Printf("valid %s .. %s", cert.NotBefore.Format(time.DateOnly), cert.NotAfter.Format(time.DateOnly)) if d := time.Until(cert.NotAfter); d < 0 { cmd.Printf(" (EXPIRED)") } else { cmd.Printf(" (%d days left)", int(d.Hours()/24)) } cmd.Println() if cert.IsCA { cmd.Println("role certificate authority") } if u := usageNames(cert.ExtKeyUsage); u != "" { cmd.Printf("usage %s\n", u) } if sans := sanNames(cert); sans != "" { cmd.Printf("hosts %s\n", sans) } else if !cert.IsCA && !hasUsage(cert, x509.ExtKeyUsageClientAuth) { cmd.Println("hosts none — no client will accept this as a server certificate") } return nil }, } } func loadCA(dir string) (*tlsconf.CA, error) { ca, err := tlsconf.LoadCA(filepath.Join(dir, "ca.pem"), filepath.Join(dir, "ca-key.pem")) if err != nil { if os.IsNotExist(err) { return nil, fmt.Errorf("no authority in %s — run 'llmgw ctl cert init-ca --dir %s' first", dir, dir) } return nil, err } return ca, nil } func describeIssued(cmd *cobra.Command, dir, name string, hosts []string) error { cert, err := readCert(filepath.Join(dir, name+".pem")) if err != nil { return err } cmd.Printf("issued %s\n", cert.Subject.CommonName) cmd.Printf(" %s\n", filepath.Join(dir, name+".pem")) cmd.Printf(" %s\n", filepath.Join(dir, name+"-key.pem")) if len(hosts) > 0 { cmd.Printf(" hosts: %s\n", strings.Join(hosts, ", ")) } cmd.Printf(" expires %s\n", cert.NotAfter.Format(time.DateOnly)) return nil } func readCert(path string) (*x509.Certificate, error) { b, err := os.ReadFile(path) if err != nil { return nil, err } block, _ := pem.Decode(b) if block == nil { return nil, fmt.Errorf("%s is not PEM", path) } return x509.ParseCertificate(block.Bytes) } func hasUsage(cert *x509.Certificate, want x509.ExtKeyUsage) bool { for _, u := range cert.ExtKeyUsage { if u == want { return true } } return false } func usageNames(usages []x509.ExtKeyUsage) string { var out []string for _, u := range usages { switch u { case x509.ExtKeyUsageServerAuth: out = append(out, "serverAuth (identifies the control plane)") case x509.ExtKeyUsageClientAuth: out = append(out, "clientAuth (identifies an agent)") default: out = append(out, fmt.Sprintf("%v", u)) } } return strings.Join(out, ", ") } func sanNames(cert *x509.Certificate) string { names := append([]string{}, cert.DNSNames...) for _, ip := range cert.IPAddresses { names = append(names, ip.String()) } return strings.Join(names, ", ") }