Files
zeekayandGitHub 5b5cf9e9db feat: merge AgentField control plane + SDKs as Hanzo Agents (#1)
Transforms hanzoai/agents from a marketplace config repo into a full
agent orchestration platform by merging the AgentField infrastructure:

- Go control plane (K8s-like agent orchestration, service mesh, DAG workflows)
- Python SDK (FastAPI-based agent framework, LiteLLM, memory, DID/VC)
- TypeScript SDK (@hanzo/agents - Vercel AI SDK, MCP, workflows)
- Go SDK (idiomatic agent builder + client)
- React 19 embedded dashboard (XyFlow DAGs, Recharts, Radix UI)
- Docker/K8s/Helm deployment configs
- Examples: hello world, deep research, RAG, simulation, benchmarks
- Functional test suite across Python/Go/TypeScript

All references rebranded: AgentField → Hanzo Agents
- Module: github.com/hanzoai/agents/control-plane
- Python: hanzo_agents
- TypeScript: @hanzo/agents
- Go: github.com/hanzoai/agents/sdk/go
- Env vars: HANZO_AGENTS_*
- Config: hanzo-agents.yaml

Previous marketplace configs moved to config/ directory.
2026-02-09 18:53:09 -08:00

176 lines
4.8 KiB
Go

package services
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
"os"
"path/filepath"
"github.com/hanzoai/agents/control-plane/internal/config"
)
// KeystoreService handles secure storage and management of cryptographic keys.
type KeystoreService struct {
config *config.KeystoreConfig
gcm cipher.AEAD
}
// NewKeystoreService creates a new keystore service instance.
func NewKeystoreService(cfg *config.KeystoreConfig) (*KeystoreService, error) {
// For now, use a simple AES-GCM encryption with a fixed key
// In production, this should use proper key derivation and HSM integration
key := make([]byte, 32) // 256-bit key
if _, err := rand.Read(key); err != nil {
return nil, fmt.Errorf("failed to generate encryption key: %w", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("failed to create cipher: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("failed to create GCM: %w", err)
}
// Ensure keystore directory exists
if err := os.MkdirAll(cfg.Path, 0700); err != nil {
return nil, fmt.Errorf("failed to create keystore directory: %w", err)
}
return &KeystoreService{
config: cfg,
gcm: gcm,
}, nil
}
// StoreKey stores a key securely in the keystore.
func (ks *KeystoreService) StoreKey(keyID string, keyData []byte) error {
if ks.config.Type != "local" {
return fmt.Errorf("only local keystore is currently supported")
}
// Encrypt the key data
nonce := make([]byte, ks.gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return fmt.Errorf("failed to generate nonce: %w", err)
}
ciphertext := ks.gcm.Seal(nonce, nonce, keyData, nil)
// Store encrypted key to file
keyPath := filepath.Join(ks.config.Path, keyID+".key")
if err := os.WriteFile(keyPath, ciphertext, 0600); err != nil {
return fmt.Errorf("failed to write key file: %w", err)
}
return nil
}
// RetrieveKey retrieves a key from the keystore.
func (ks *KeystoreService) RetrieveKey(keyID string) ([]byte, error) {
if ks.config.Type != "local" {
return nil, fmt.Errorf("only local keystore is currently supported")
}
// Read encrypted key from file
keyPath := filepath.Join(ks.config.Path, keyID+".key")
ciphertext, err := os.ReadFile(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to read key file: %w", err)
}
// Extract nonce and decrypt
nonceSize := ks.gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := ks.gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("failed to decrypt key: %w", err)
}
return plaintext, nil
}
// DeleteKey deletes a key from the keystore.
func (ks *KeystoreService) DeleteKey(keyID string) error {
if ks.config.Type != "local" {
return fmt.Errorf("only local keystore is currently supported")
}
keyPath := filepath.Join(ks.config.Path, keyID+".key")
if err := os.Remove(keyPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to delete key file: %w", err)
}
return nil
}
// ListKeys lists all keys in the keystore.
func (ks *KeystoreService) ListKeys() ([]string, error) {
if ks.config.Type != "local" {
return nil, fmt.Errorf("only local keystore is currently supported")
}
entries, err := os.ReadDir(ks.config.Path)
if err != nil {
return nil, fmt.Errorf("failed to read keystore directory: %w", err)
}
var keys []string
for _, entry := range entries {
if !entry.IsDir() && filepath.Ext(entry.Name()) == ".key" {
keyID := entry.Name()[:len(entry.Name())-4] // Remove .key extension
keys = append(keys, keyID)
}
}
return keys, nil
}
// BackupKeys creates a backup of all keys in the keystore.
func (ks *KeystoreService) BackupKeys() error {
if !ks.config.BackupEnabled {
return nil
}
// Implementation for key backup
// This would typically involve creating encrypted backups
// For now, just return nil
return nil
}
// EncryptData encrypts arbitrary data using the keystore's encryption.
func (ks *KeystoreService) EncryptData(data []byte) ([]byte, error) {
nonce := make([]byte, ks.gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, fmt.Errorf("failed to generate nonce: %w", err)
}
ciphertext := ks.gcm.Seal(nonce, nonce, data, nil)
return ciphertext, nil
}
// DecryptData decrypts data that was encrypted with EncryptData.
func (ks *KeystoreService) DecryptData(ciphertext []byte) ([]byte, error) {
nonceSize := ks.gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := ks.gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("failed to decrypt data: %w", err)
}
return plaintext, nil
}