created handlers for certificate manipulation in vault. Inserted device mTLS guards for public faced endpoints
This commit is contained in:
86
server/internal/vault/pki.go
Normal file
86
server/internal/vault/pki.go
Normal file
@@ -0,0 +1,86 @@
|
||||
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; 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)
|
||||
}
|
||||
Reference in New Issue
Block a user