mirror of
https://github.com/hanzoai/kms.git
synced 2026-10-17 11:18:16 +00:00
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).
112 lines
2.6 KiB
Go
112 lines
2.6 KiB
Go
// Copyright (C) 2020-2026, Hanzo AI Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package kms
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
// SecretEvent represents a change event from the CRDT sync stream.
|
|
type SecretEvent struct {
|
|
// Type is the event type: "set", "delete", or "rotate".
|
|
Type string `json:"type"`
|
|
|
|
// SecretID is the (encrypted) identifier of the affected secret.
|
|
SecretID string `json:"secret_id"`
|
|
|
|
// Timestamp is the server-observed event time (epoch milliseconds).
|
|
Timestamp int64 `json:"timestamp"`
|
|
}
|
|
|
|
// Sync triggers CRDT sync across all MPC nodes for this org.
|
|
// This is normally automatic; use this to force immediate convergence.
|
|
func (c *Client) Sync() error {
|
|
path := fmt.Sprintf("/v1/orgs/%s/zk/sync", url.PathEscape(c.orgSlug))
|
|
return c.broadcastPost(path, []byte(`{}`))
|
|
}
|
|
|
|
// Watch subscribes to secret change events via server-sent events (SSE).
|
|
// Events are encrypted CRDT operations — the client observes which secrets
|
|
// changed without seeing plaintext values.
|
|
//
|
|
// The returned channel is closed when the context is cancelled or the
|
|
// connection drops. Errors during streaming are silently dropped; reconnect
|
|
// by calling Watch again.
|
|
func (c *Client) Watch(ctx context.Context) (<-chan SecretEvent, error) {
|
|
if len(c.nodes) == 0 {
|
|
return nil, fmt.Errorf("kms: no nodes configured")
|
|
}
|
|
|
|
// Connect to the first available node for SSE.
|
|
var (
|
|
resp *http.Response
|
|
err error
|
|
)
|
|
|
|
path := fmt.Sprintf("/v1/orgs/%s/zk/watch", url.PathEscape(c.orgSlug))
|
|
|
|
for _, node := range c.nodes {
|
|
req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, node+path, nil)
|
|
if reqErr != nil {
|
|
err = reqErr
|
|
continue
|
|
}
|
|
req.Header.Set("Accept", "text/event-stream")
|
|
|
|
resp, err = c.http.Do(req)
|
|
if err == nil && resp.StatusCode == http.StatusOK {
|
|
break
|
|
}
|
|
if resp != nil {
|
|
resp.Body.Close()
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("kms: watch: connect: %w", err)
|
|
}
|
|
if resp == nil || resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("kms: watch: no node available")
|
|
}
|
|
|
|
ch := make(chan SecretEvent, 64)
|
|
|
|
go func() {
|
|
defer resp.Body.Close()
|
|
defer close(ch)
|
|
|
|
scanner := bufio.NewScanner(resp.Body)
|
|
for scanner.Scan() {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
}
|
|
|
|
line := scanner.Text()
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
|
|
var event SecretEvent
|
|
if err := json.Unmarshal([]byte(line), &event); err != nil {
|
|
continue
|
|
}
|
|
|
|
select {
|
|
case ch <- event:
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return ch, nil
|
|
}
|