Files
zeekay f3fbb687d2 purge: rip remaining Infisical residue — docs, env examples, repo boilerplate
Following the earlier AdminShell+CollectionCRUD rewrite (PR #14) which
already deleted backend/, backend-go/, cloudformation/, helm-charts/,
nginx/, sink/, wasm/, upgrade-impact/, e2e/, migration/, package.json
and switched the frontend to a 184K Hanzo-first build, complete the
cleanup by deleting the remaining Infisical-era residue:

  - docs/                                   (1.0G Infisical mintlify docs)
  - .env.example, .env.dev.example,
    .env.migration.example, .env.test.example
                                            (legacy Postgres/Redis envs;
                                             no Go code references them)
  - .eslintignore, .husky/                  (TS toolchain leftovers)
  - CODE_OF_CONDUCT.md, CONTRIBUTING.md,
    SECURITY.md                             (Infisical repo boilerplate)

Working tree drops from ~1.0G to <600K of content (1.5G total is .git/
which `git gc --aggressive --prune=now` shrinks locally; the remote
keeps its history shape — anyone pulling gets the lean checkout).

Build + test verification:
  go build ./...                            (4 cmd binaries: green)
  go test ./... -count=1 -short             (all packages: ok)

Kept: cmd/, pkg/, sdk/, frontend/ (AdminShell), schema/, examples/,
root Go (audit.go, auth.go, consensus.go, embed.go, jwks.go, mount.go,
versioning.go + tests), Dockerfile, Dockerfile.kms-fetch, Makefile,
LLM.md, CLAUDE.md, DEPRECATED.md, SOVEREIGN-KMS-ARCHITECTURE.md,
TFHE-KMS-ARCHITECTURE.md, LICENSE, README.md, VERSION, .github/
(8 Hanzo workflows: build, build-kms-fetch, check-fe-ts-and-lint,
ci, pr-preview, release, validate-pr-title, workflow-sanity).
2026-06-07 14:23:12 -07:00

188 lines
4.3 KiB
Go

// Copyright (C) 2020-2026, Hanzo AI Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package kms
import (
"encoding/json"
"fmt"
"net/url"
)
// encryptedSecret is the JSON payload sent to and received from MPC nodes.
type encryptedSecret struct {
Key []byte `json:"key"` // encrypted secret name
Value []byte `json:"value"` // encrypted secret value
}
// secretListResponse is the JSON response from List.
type secretListResponse struct {
Secrets []encryptedSecret `json:"secrets"`
}
// Set encrypts value client-side with the CEK and sends the encrypted blob
// to the MPC nodes. The key (name) is also encrypted — the nodes never see
// plaintext names or values.
func (c *Client) Set(key string, value []byte) error {
cek, err := c.getCEK()
if err != nil {
return err
}
defer clear(cek)
aad := []byte(c.orgSlug)
encKey, err := sealAESGCM(cek, []byte(key), aad)
if err != nil {
return fmt.Errorf("kms: encrypt key: %w", err)
}
encValue, err := sealAESGCM(cek, value, aad)
if err != nil {
return fmt.Errorf("kms: encrypt value: %w", err)
}
payload := encryptedSecret{
Key: encKey,
Value: encValue,
}
data, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("kms: marshal secret: %w", err)
}
path := fmt.Sprintf("/v1/orgs/%s/zk/secrets", url.PathEscape(c.orgSlug))
return c.broadcastPost(path, data)
}
// Get retrieves an encrypted blob from the MPC nodes and decrypts it
// client-side with the CEK.
func (c *Client) Get(key string) ([]byte, error) {
cek, err := c.getCEK()
if err != nil {
return nil, err
}
defer clear(cek)
aad := []byte(c.orgSlug)
// Encrypt the key name for lookup (nodes store encrypted names).
encKey, err := sealAESGCM(cek, []byte(key), aad)
if err != nil {
return nil, fmt.Errorf("kms: encrypt key for lookup: %w", err)
}
path := fmt.Sprintf("/v1/orgs/%s/zk/secrets/%s",
url.PathEscape(c.orgSlug),
url.PathEscape(encodeBase64(encKey)),
)
body, err := c.quorumGet(path)
if err != nil {
return nil, fmt.Errorf("kms: get secret: %w", err)
}
var es encryptedSecret
if err := json.Unmarshal(body, &es); err != nil {
return nil, fmt.Errorf("kms: unmarshal secret response: %w", err)
}
plaintext, err := openAESGCM(cek, es.Value, aad)
if err != nil {
return nil, fmt.Errorf("kms: decrypt value: %w", err)
}
return plaintext, nil
}
// Delete removes a secret from all MPC nodes.
func (c *Client) Delete(key string) error {
cek, err := c.getCEK()
if err != nil {
return err
}
defer clear(cek)
aad := []byte(c.orgSlug)
encKey, err := sealAESGCM(cek, []byte(key), aad)
if err != nil {
return fmt.Errorf("kms: encrypt key for delete: %w", err)
}
path := fmt.Sprintf("/v1/orgs/%s/zk/secrets/%s",
url.PathEscape(c.orgSlug),
url.PathEscape(encodeBase64(encKey)),
)
// Broadcast DELETE to all nodes.
type result struct {
err error
}
ch := make(chan result, len(c.nodes))
for _, node := range c.nodes {
go func(addr string) {
ch <- result{err: c.deleteRequest(addr + path)}
}(node)
}
var successes int
var lastErr error
for range c.nodes {
r := <-ch
if r.err != nil {
lastErr = r.err
} else {
successes++
}
}
if successes < c.threshold {
return fmt.Errorf("kms: delete: only %d of %d nodes responded (need %d): %w",
successes, len(c.nodes), c.threshold, lastErr)
}
return nil
}
// List returns the names of all secrets for this org. Names are encrypted
// on the nodes and decrypted client-side.
func (c *Client) List() ([]string, error) {
cek, err := c.getCEK()
if err != nil {
return nil, err
}
defer clear(cek)
aad := []byte(c.orgSlug)
path := fmt.Sprintf("/v1/orgs/%s/zk/secrets", url.PathEscape(c.orgSlug))
body, err := c.quorumGet(path)
if err != nil {
return nil, fmt.Errorf("kms: list secrets: %w", err)
}
var resp secretListResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("kms: unmarshal list response: %w", err)
}
names := make([]string, 0, len(resp.Secrets))
for _, es := range resp.Secrets {
name, err := openAESGCM(cek, es.Key, aad)
if err != nil {
return nil, fmt.Errorf("kms: decrypt secret name: %w", err)
}
names = append(names, string(name))
}
return names, nil
}
// encodeBase64 encodes bytes to URL-safe base64 without padding.
func encodeBase64(data []byte) string {
return base64URLEncode(data)
}