Files
NewSmoop/server/internal/vault/pki.go

87 lines
2.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/<mount>/sign/<role>
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:<GUID>"
}
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/<mount>/crl/rotate or read /crl to force render; well 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)
}