package vault import ( "context" "encoding/json" "fmt" "time" vault "github.com/hashicorp/vault-client-go" ) type PKIClient struct { client *vault.Client mount string // e.g. "pki_iot" role string // e.g. "device" } type SignResponse struct { Certificate string `json:"certificate"` IssuingCA string `json:"issuing_ca"` CAChain []string `json:"ca_chain"` PrivateKey string `json:"private_key,omitempty"` PrivateKeyType string `json:"private_key_type,omitempty"` SerialNumber string `json:"serial_number"` } func NewPKI(addr, token, mount, role string, timeout time.Duration) (*PKIClient, error) { client, err := vault.New( vault.WithAddress(addr), vault.WithRequestTimeout(timeout), ) if err != nil { return nil, fmt.Errorf("vault new: %w", err) } if err := client.SetToken(token); err != nil { return nil, fmt.Errorf("set token: %w", err) } return &PKIClient{client: client, mount: mount, role: role}, nil } // SignCSR calls: /v1//sign/ func (p *PKIClient) SignCSR(ctx context.Context, csrPEM, uriSAN string, ttl string) (*SignResponse, error) { if p.client == nil { return nil, fmt.Errorf("vault client is nil") } path := fmt.Sprintf("/%s/sign/%s", p.mount, p.role) req := map[string]any{ "csr": csrPEM, "uri_sans": uriSAN, // e.g. "urn:device:" } if ttl != "" { req["ttl"] = ttl // e.g. "720h" } resp, err := p.client.Write(ctx, path, req) if err != nil { return nil, err } if resp == nil || resp.Data == nil { return nil, fmt.Errorf("vault sign: empty response") } // resp.Data contains the fields we need var out SignResponse if err := mapToStruct(resp.Data, &out); err != nil { return nil, err } return &out, nil } // RebuildCRL triggers CRL regeneration (rotate). // HCP: POST /v1//crl/rotate or read /crl to force render; we’ll call rotate when available, // else read to ensure CRL is (re)generated. func (p *PKIClient) RebuildCRL(ctx context.Context) error { _, _ = p.client.Write(ctx, fmt.Sprintf("/%s/crl/rotate", p.mount), nil) // best effort _, err := p.client.Read(ctx, fmt.Sprintf("/%s/crl", p.mount)) return err } func mapToStruct(m map[string]any, out any) error { b, err := json.Marshal(m) if err != nil { return err } return json.Unmarshal(b, out) }