package main import ( "bytes" "crypto/tls" "crypto/x509" "encoding/json" "fmt" "io" "net/http" "os" "strings" "github.com/spf13/cobra" ) func certCommand() *cobra.Command { cmd := &cobra.Command{ Use: "cert", Short: "Certificate management", } cmd.AddCommand(certRenewCommand()) return cmd } func certRenewCommand() *cobra.Command { var installFlag string var caCertFlag string cmd := &cobra.Command{ Use: "renew ", Short: "Request a new certificate from Metacrypt", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { hostname := args[0] addr := os.Getenv("METACRYPT_ADDR") if addr == "" { addr = "https://metacrypt.svc.mcp.metacircular.net:8443" } token := os.Getenv("METACRYPT_TOKEN") if token == "" { return fmt.Errorf("METACRYPT_TOKEN environment variable is required") } // Build request body. reqBody := map[string]any{ "mount": "pki", "operation": "issue", "path": "web", "data": map[string]any{ "issuer": "web", "common_name": hostname, "profile": "server", "dns_names": []string{hostname}, "ttl": "2160h", }, } bodyBytes, err := json.Marshal(reqBody) if err != nil { return fmt.Errorf("marshal request: %w", err) } // Configure TLS. tlsCfg := &tls.Config{ MinVersion: tls.VersionTLS13, } if caCertFlag != "" { caPEM, err := os.ReadFile(caCertFlag) if err != nil { return fmt.Errorf("read CA cert: %w", err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(caPEM) { return fmt.Errorf("failed to parse CA cert") } tlsCfg.RootCAs = pool } else { tlsCfg.InsecureSkipVerify = true } client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsCfg, }, } url := addr + "/v1/engine/request" fmt.Printf(">>> POST %s\n", url) req, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes)) if err != nil { return fmt.Errorf("create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) resp, err := client.Do(req) if err != nil { return fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("read response: %w", err) } if resp.StatusCode != http.StatusOK { return fmt.Errorf("metacrypt returned %d: %s", resp.StatusCode, string(respBody)) } var result map[string]any if err := json.Unmarshal(respBody, &result); err != nil { return fmt.Errorf("parse response: %w", err) } certPEM, _ := result["cert_pem"].(string) chainPEM, _ := result["chain_pem"].(string) keyPEM, _ := result["key_pem"].(string) if certPEM == "" || keyPEM == "" { return fmt.Errorf("response missing cert_pem or key_pem") } // Combine leaf and chain into full cert. fullCert := certPEM if chainPEM != "" { fullCert = certPEM + "\n" + chainPEM } // Parse and display cert details. tlsCert, err := tls.X509KeyPair([]byte(fullCert), []byte(keyPEM)) if err != nil { return fmt.Errorf("parse cert: %w", err) } leaf, err := x509.ParseCertificate(tlsCert.Certificate[0]) if err != nil { return fmt.Errorf("parse leaf cert: %w", err) } fmt.Printf("Certificate issued:\n") fmt.Printf(" Serial: %s\n", leaf.SerialNumber.Text(16)) fmt.Printf(" CN: %s\n", leaf.Subject.CommonName) fmt.Printf(" Expires: %s\n", leaf.NotAfter.Format("2006-01-02 15:04:05 UTC")) // Install to remote node if requested. if installFlag != "" { parts := strings.SplitN(installFlag, ":", 2) if len(parts) != 2 { return fmt.Errorf("--install must be : (e.g. mcp:/srv/mc-proxy/tls)") } node := parts[0] remotePath := parts[1] cfg, err := loadCfg() if err != nil { return err } nodeCfg, err := cfg.FindNode(node) if err != nil { return err } host := nodeCfg.Host if nodeCfg.User != "" { host = nodeCfg.User + "@" + nodeCfg.Host } // Write temp files and SCP them. certFile, err := os.CreateTemp("", "cert-*.pem") if err != nil { return fmt.Errorf("create temp cert: %w", err) } defer os.Remove(certFile.Name()) keyFile, err := os.CreateTemp("", "key-*.pem") if err != nil { return fmt.Errorf("create temp key: %w", err) } defer os.Remove(keyFile.Name()) if _, err := certFile.WriteString(fullCert); err != nil { return fmt.Errorf("write temp cert: %w", err) } certFile.Close() if _, err := keyFile.WriteString(keyPEM); err != nil { return fmt.Errorf("write temp key: %w", err) } keyFile.Close() certDest := host + ":" + remotePath + "/cert.pem" keyDest := host + ":" + remotePath + "/key.pem" if err := run("scp", certFile.Name(), certDest); err != nil { return fmt.Errorf("scp cert: %w", err) } if err := run("scp", keyFile.Name(), keyDest); err != nil { return fmt.Errorf("scp key: %w", err) } fmt.Printf("Installed cert and key to %s:%s/\n", node, remotePath) } return nil }, } cmd.Flags().StringVar(&installFlag, "install", "", "install cert to :") cmd.Flags().StringVar(&caCertFlag, "ca-cert", "", "CA certificate for Metacrypt TLS verification") return cmd }