mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-24 20:09:02 +00:00
docs(services): correct S3/IAM/KMS/MPC/Base/PaaS to real /v1 surfaces
Audited each service's current source and rewrote the docs to match: - S3: SeaweedFS-based (was documented as MinIO) - S3 SigV4, real clients (mc/aws/boto3), correct ports; dropped fabricated SDKs + cross-brand buckets - IAM: Casdoor fork - canonical /v1/iam/* OIDC, <org>-<app> app convention, JWKS token validation, per-host white-label - KMS: Go /v1/kms surface - machine-identity login, org/path/name?env reads with CAS, KMSSecret keys[]; removed fabricated /api/v1 + SDK examples - MPC: /v1/mpc custody API with session-based signing; CGGMP21/FROST/LSS/BLS - Base: PocketBase fork on /v1 - IAM-native auth (local passwords removed), real endpoints incl /v1/sql, /v1/tasks, /v1/private - PaaS: Dokploy fork (SQLite/Base) - /v1 container redeploy + /api application.* build pipeline; replaced fabricated Mongo/Redis architecture House style: /v1 not /api, api.<svc>.hanzo.ai hosts, KMS paths for secrets, Hanzo-only (stripped Lux/Zoo leaks, prod IPs, nginx, internal slugs).
This commit is contained in:
@@ -1,71 +1,217 @@
|
||||
---
|
||||
title: Hanzo Base
|
||||
description: Open-source backend-as-a-service with REST API, auth, realtime, file storage, cloud functions, and plugin system
|
||||
description: Backend-as-a-service with an auto-generated REST API, IAM-native auth, realtime, file storage, durable tasks, and per-tenant encrypted storage
|
||||
---
|
||||
|
||||
# Hanzo Base
|
||||
|
||||
Hanzo Base is an open-source backend-as-a-service (BaaS) that ships as a single Go binary with zero dependencies. It provides a full REST API auto-generated from your schema, authentication, realtime subscriptions, file storage, cloud functions, and an extensible plugin system.
|
||||
Hanzo Base is a backend-as-a-service that ships as a single Go binary. Define a collection schema and get a full REST API automatically — records CRUD, realtime subscriptions, file storage, a durable task queue, and a per-user key-value store — with authentication delegated natively to [Hanzo IAM](/docs/services/iam).
|
||||
|
||||
**Production:** `https://base.hanzo.ai`
|
||||
**Admin UI:** `https://base.hanzo.ai/_/`
|
||||
**Source:** [github.com/hanzoai/base](https://github.com/hanzoai/base)
|
||||
| | |
|
||||
|---|---|
|
||||
| **Production** | `https://base.hanzo.ai` |
|
||||
| **API prefix** | `/v1` |
|
||||
| **Admin UI** | `https://base.hanzo.ai/_/` |
|
||||
| **Auth** | Hanzo IAM (OIDC / JWKS) — no local passwords |
|
||||
| **Source** | [github.com/hanzoai/base](https://github.com/hanzoai/base) |
|
||||
|
||||
<Callout>
|
||||
Hanzo Base serves its API under `/v1`, not `/api`. Authentication is **IAM-native**: the built-in local password/OAuth flows are removed, and Hanzo IAM is the only token authority. Clients run the OIDC PKCE flow against IAM and present the resulting JWT as a bearer token.
|
||||
</Callout>
|
||||
|
||||
## Features
|
||||
|
||||
- **Database**: SQLite for local dev, PostgreSQL for production. Full REST API auto-generated from schema.
|
||||
- **Authentication**: Email/password, OAuth2, and Hanzo IAM integration with fine-grained access control.
|
||||
- **Realtime**: Subscribe to database changes over SSE with optimistic update support.
|
||||
- **File Storage**: Upload files directly to collections. Local filesystem or S3-compatible storage (Hanzo S3).
|
||||
- **Cloud Functions**: Server-side JavaScript hooks, custom routes, and scheduled tasks.
|
||||
- **Admin UI**: Built-in dashboard at `/_/` for managing collections, users, settings, and logs.
|
||||
- **Plugin System**: Extend Base with custom Go plugins for routes, hooks, middleware, and data providers.
|
||||
- **Auto-generated REST API**: Full CRUD for every collection under `/v1/collections/{name}/records`
|
||||
- **IAM-native auth**: Tokens issued by [Hanzo IAM](/docs/services/iam) and validated against its JWKS — users are not stored in Base
|
||||
- **Realtime**: Subscribe to record changes over SSE (`/v1/realtime`) or WebSocket (`/v1/realtime/ws`)
|
||||
- **File storage**: Upload files to records; stored on local disk or [Hanzo S3](/docs/services/s3)
|
||||
- **Durable tasks & workflows**: A built-in task queue at `/v1/tasks`
|
||||
- **Per-user private KV**: A scoped key-value store at `/v1/private`
|
||||
- **Raw SQL over HTTP**: `/v1/sql` for superusers
|
||||
- **Per-tenant encrypted storage**: One encrypted SQLite database per organization, with WAL replication to object storage
|
||||
- **Admin dashboard**: A React UI at `/_/` for collections, settings, logs, and backups
|
||||
|
||||
## Architecture
|
||||
## Storage model
|
||||
|
||||
Base uses **SQLite by default** — pure-Go, embedded, zero external dependencies — which makes local development instant. Each organization gets its own database file, encrypted at rest with a key derived from [Hanzo KMS](/docs/services/kms); the write-ahead log is streamed (age-encrypted) to S3/GCS for durability.
|
||||
|
||||
For production multi-instance deployments that need a shared writer (e.g. leader-elected cron), Base runs on **PostgreSQL** via `pgx`. SQLite remains the default everywhere else.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ HANZO BASE │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌───────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
|
||||
│ │ REST API │ │ Auth │ │ Realtime │ │ Admin UI │ │
|
||||
│ │ (CRUD) │ │ (OIDC) │ │ (SSE) │ │ (/_/) │ │
|
||||
│ └─────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬─────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌─────▼──────────────▼──────────────▼───────────────▼─────┐ │
|
||||
│ │ Core Engine │ │
|
||||
│ │ Collections · Hooks · Middleware · Plugin Registry │ │
|
||||
│ └─────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────▼─────────────────────────────────┐ │
|
||||
│ │ Storage Layer │ │
|
||||
│ │ SQLite (dev) │ PostgreSQL (prod) │ S3 (files) │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ Plugin System │ │
|
||||
│ │ Routes · Hooks · Middleware · Migrations · Cron Jobs │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
Local dev / single node → SQLite (encrypted, per-org)
|
||||
Production multi-instance → PostgreSQL (pgx)
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
## Authentication
|
||||
|
||||
### Local Development
|
||||
Base does not authenticate users itself — it trusts [Hanzo IAM](/docs/services/iam). The flow:
|
||||
|
||||
<Steps>
|
||||
|
||||
### Discover the auth provider
|
||||
|
||||
```bash
|
||||
# Download the binary
|
||||
curl -fsSL https://base.hanzo.ai/install.sh | sh
|
||||
|
||||
# Start in development mode (SQLite)
|
||||
base serve --dev
|
||||
curl https://base.hanzo.ai/v1/collections/users/auth-methods
|
||||
```
|
||||
|
||||
The admin UI is available at `http://localhost:8090/_/`.
|
||||
Returns the single configured `iam` OAuth2/PKCE provider with its authorize URL and PKCE challenge.
|
||||
|
||||
### Docker
|
||||
### Run the OIDC PKCE flow against IAM
|
||||
|
||||
Redirect the user through `https://hanzo.id/v1/iam/oauth/authorize`, then exchange the code at IAM's token endpoint. See [Hanzo IAM](/docs/services/iam) for the full flow.
|
||||
|
||||
### Call Base with the IAM-issued JWT
|
||||
|
||||
```bash
|
||||
curl https://base.hanzo.ai/v1/collections/posts/records \
|
||||
-H "Authorization: Bearer $IAM_JWT"
|
||||
```
|
||||
|
||||
Base validates the bearer against IAM's JWKS and evaluates your collection access rules using the token's claims (`sub`, `email`, `owner`, `isAdmin`).
|
||||
|
||||
</Steps>
|
||||
|
||||
A token can be rotated without re-running the full flow:
|
||||
|
||||
```bash
|
||||
curl -X POST https://base.hanzo.ai/v1/collections/users/auth-refresh \
|
||||
-H "Authorization: Bearer $IAM_JWT"
|
||||
```
|
||||
|
||||
## Collections & the REST API
|
||||
|
||||
Collections are Base's core data model (`base`, `auth`, or `view` type). Define a schema once and every record operation is exposed automatically.
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | `/v1/collections/{name}/records` | List/search (filter, sort, page, expand, fields) |
|
||||
| `GET` | `/v1/collections/{name}/records/{id}` | Get one record |
|
||||
| `POST` | `/v1/collections/{name}/records` | Create a record |
|
||||
| `PATCH` | `/v1/collections/{name}/records/{id}` | Update a record |
|
||||
| `DELETE` | `/v1/collections/{name}/records/{id}` | Delete a record |
|
||||
| `POST` | `/v1/batch` | Batched transactional writes |
|
||||
|
||||
### Create a record
|
||||
|
||||
```bash
|
||||
curl -X POST https://base.hanzo.ai/v1/collections/posts/records \
|
||||
-H "Authorization: Bearer $IAM_JWT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "title": "Hello", "body": "World", "published": true }'
|
||||
```
|
||||
|
||||
### Filter, sort, paginate
|
||||
|
||||
```bash
|
||||
curl "https://base.hanzo.ai/v1/collections/posts/records?filter=(published=true%26%26author.name~'jin')&sort=-created&page=1&perPage=20" \
|
||||
-H "Authorization: Bearer $IAM_JWT"
|
||||
```
|
||||
|
||||
### Manage collections (superuser)
|
||||
|
||||
```bash
|
||||
# Create a collection
|
||||
curl -X POST https://base.hanzo.ai/v1/collections \
|
||||
-H "Authorization: Bearer $IAM_ADMIN_JWT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "posts",
|
||||
"type": "base",
|
||||
"fields": [
|
||||
{ "name": "title", "type": "text", "required": true },
|
||||
{ "name": "body", "type": "editor" },
|
||||
{ "name": "published", "type": "bool" }
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
## Realtime
|
||||
|
||||
Subscribe to record changes over Server-Sent Events. Base also offers a WebSocket transport at `/v1/realtime/ws`.
|
||||
|
||||
```typescript
|
||||
import PocketBase from '@hanzoai/js-sdk' // wire-compatible client
|
||||
|
||||
const base = new PocketBase('https://base.hanzo.ai') // targets /v1 automatically
|
||||
base.authStore.save(iamJwt, null) // attach the IAM-issued JWT
|
||||
|
||||
base.collection('posts').subscribe('*', (e) => {
|
||||
console.log(e.action, e.record) // 'create' | 'update' | 'delete'
|
||||
})
|
||||
```
|
||||
|
||||
Under the hood: open `GET /v1/realtime` (the server returns a `clientId`), then `POST /v1/realtime` with `{ clientId, subscriptions: ["posts", "posts/RECORD_ID"] }`. Subscriptions are gated by each collection's list/view rules.
|
||||
|
||||
## File Storage
|
||||
|
||||
Upload files directly to record fields. Files are served from local disk in development and from [Hanzo S3](/docs/services/s3) in production.
|
||||
|
||||
```bash
|
||||
# Upload (multipart)
|
||||
curl -X POST https://base.hanzo.ai/v1/collections/posts/records \
|
||||
-H "Authorization: Bearer $IAM_JWT" \
|
||||
-F "title=My Post" \
|
||||
-F "[email protected]"
|
||||
|
||||
# Download
|
||||
curl "https://base.hanzo.ai/v1/files/posts/RECORD_ID/image.jpg?thumb=100x100"
|
||||
```
|
||||
|
||||
Protected files use a short-lived token from `POST /v1/files/token`.
|
||||
|
||||
## Beyond CRUD
|
||||
|
||||
Hanzo Base adds first-class endpoints on top of the record API:
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `POST /v1/sql` | Run a parameterized SQL query (superuser) |
|
||||
| `/v1/tasks` | Durable task & workflow queue (claim, start, complete, fail, signal) |
|
||||
| `/v1/private` | Per-user private key-value store |
|
||||
| `/v1/backups` | Create, download, and restore database backups (superuser) |
|
||||
| `/v1/crons` | List and trigger scheduled jobs (superuser) |
|
||||
| `/v1/logs` | Request logs (superuser) |
|
||||
|
||||
## Access Control
|
||||
|
||||
Collection access is governed by filter-expression rules evaluated against the IAM token claims:
|
||||
|
||||
```json
|
||||
{
|
||||
"listRule": "@request.auth.id != ''",
|
||||
"viewRule": "@request.auth.id != '' || published = true",
|
||||
"createRule": "@request.auth.id != ''",
|
||||
"updateRule": "@request.auth.id = author",
|
||||
"deleteRule": "@request.auth.id = author || @request.auth.isAdmin = true"
|
||||
}
|
||||
```
|
||||
|
||||
A request is treated as **superuser** when its IAM token carries an admin claim (`isAdmin`/`isGlobalAdmin`, or an `owner` of `built-in`/`superuser`). The platform superuser (`[email protected]`) is seeded in [Hanzo IAM](/docs/services/iam), not in Base — Base simply honors the admin claim on the token.
|
||||
|
||||
## Extending Base
|
||||
|
||||
Base embeds multiple in-process runtimes for custom logic — JavaScript (goja), WASM (wazero), Python, and Starlark — plus Go extension via `core.App`:
|
||||
|
||||
```go
|
||||
// Go: register a custom route
|
||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
||||
se.Router.GET("/v1/custom/stats", func(e *core.RequestEvent) error {
|
||||
return e.JSON(200, map[string]string{"status": "ok"})
|
||||
})
|
||||
return se.Next()
|
||||
})
|
||||
```
|
||||
|
||||
## SDKs
|
||||
|
||||
| Language | Package |
|
||||
|----------|---------|
|
||||
| JavaScript / TypeScript | `@hanzoai/js-sdk` |
|
||||
| Dart / Flutter | `@hanzoai/dart-sdk` |
|
||||
|
||||
The wire protocol is PocketBase-compatible for record CRUD and realtime, so existing PocketBase clients work for data operations — only the auth step differs (IAM PKCE instead of local password).
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
```yaml
|
||||
# compose.yml
|
||||
@@ -74,6 +220,8 @@ services:
|
||||
image: ghcr.io/hanzoai/base:latest
|
||||
ports:
|
||||
- "8090:8090"
|
||||
environment:
|
||||
IAM_ENDPOINT: https://hanzo.id
|
||||
volumes:
|
||||
- base-data:/app/data
|
||||
restart: unless-stopped
|
||||
@@ -82,269 +230,21 @@ volumes:
|
||||
base-data:
|
||||
```
|
||||
|
||||
### Production (Kubernetes)
|
||||
|
||||
```bash
|
||||
kubectl apply -k infra/k8s/base/
|
||||
```
|
||||
|
||||
Production deployments use PostgreSQL via Hanzo SQL and S3 storage via Hanzo S3.
|
||||
|
||||
## Collections
|
||||
|
||||
Collections are Base's core data model. Define a schema and get a full REST API automatically.
|
||||
|
||||
### Create a Collection
|
||||
|
||||
```bash
|
||||
curl -X POST https://base.hanzo.ai/api/collections \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "posts",
|
||||
"type": "base",
|
||||
"schema": [
|
||||
{"name": "title", "type": "text", "required": true},
|
||||
{"name": "body", "type": "editor"},
|
||||
{"name": "published", "type": "bool", "default": false},
|
||||
{"name": "author", "type": "relation", "options": {"collectionId": "users"}}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### Auto-Generated REST API
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | `/api/collections/{name}/records` | List records with filtering, sorting, pagination |
|
||||
| `GET` | `/api/collections/{name}/records/{id}` | Get single record |
|
||||
| `POST` | `/api/collections/{name}/records` | Create record |
|
||||
| `PATCH` | `/api/collections/{name}/records/{id}` | Update record |
|
||||
| `DELETE` | `/api/collections/{name}/records/{id}` | Delete record |
|
||||
|
||||
### Filtering
|
||||
|
||||
```bash
|
||||
# Filter with expressions
|
||||
curl "https://base.hanzo.ai/api/collections/posts/records?filter=(published=true&&author.name~'john')"
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Base supports multiple auth methods:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| **Email/Password** | Built-in with verification emails |
|
||||
| **OAuth2** | Google, GitHub, Apple, Discord, and more |
|
||||
| **Hanzo IAM** | Enterprise SSO via `hanzo.id` OIDC integration |
|
||||
| **API Keys** | For service-to-service communication |
|
||||
|
||||
### Hanzo IAM Integration
|
||||
|
||||
```bash
|
||||
# Configure IAM as OAuth2 provider
|
||||
curl -X PATCH https://base.hanzo.ai/api/settings \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-d '{
|
||||
"meta": {
|
||||
"appName": "My App"
|
||||
},
|
||||
"iam": {
|
||||
"enabled": true,
|
||||
"clientId": "app-myapp",
|
||||
"clientSecret": "...",
|
||||
"authUrl": "https://hanzo.id/oauth/authorize",
|
||||
"tokenUrl": "https://hanzo.id/oauth/token",
|
||||
"userInfoUrl": "https://hanzo.id/oauth/userinfo"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Realtime
|
||||
|
||||
Subscribe to collection changes via Server-Sent Events:
|
||||
|
||||
```typescript
|
||||
import Base from '@hanzo/base-sdk'
|
||||
|
||||
const base = new Base('https://base.hanzo.ai')
|
||||
|
||||
// Subscribe to changes
|
||||
base.collection('posts').subscribe('*', (e) => {
|
||||
console.log(e.action) // 'create' | 'update' | 'delete'
|
||||
console.log(e.record)
|
||||
})
|
||||
|
||||
// Subscribe to specific record
|
||||
base.collection('posts').subscribe('RECORD_ID', (e) => {
|
||||
console.log('Record updated:', e.record)
|
||||
})
|
||||
```
|
||||
|
||||
## File Storage
|
||||
|
||||
Upload files directly to collection records:
|
||||
|
||||
```bash
|
||||
curl -X POST https://base.hanzo.ai/api/collections/posts/records \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "title=My Post" \
|
||||
-F "[email protected]"
|
||||
```
|
||||
|
||||
Files are stored in the configured storage backend (local filesystem for dev, Hanzo S3 for production).
|
||||
|
||||
## Cloud Functions
|
||||
|
||||
Server-side JavaScript for custom logic:
|
||||
|
||||
### Hooks
|
||||
|
||||
```javascript
|
||||
// pb_hooks/posts.pb.js
|
||||
|
||||
// Before create hook
|
||||
onRecordBeforeCreateRequest((e) => {
|
||||
const record = e.record
|
||||
record.set('slug', slugify(record.get('title')))
|
||||
}, 'posts')
|
||||
|
||||
// After create hook
|
||||
onRecordAfterCreateRequest((e) => {
|
||||
// Send notification, update search index, etc.
|
||||
$app.logger().info('Post created', 'id', e.record.id)
|
||||
}, 'posts')
|
||||
```
|
||||
|
||||
### Custom Routes
|
||||
|
||||
```javascript
|
||||
// pb_hooks/api.pb.js
|
||||
|
||||
routerAdd('GET', '/api/custom/stats', (e) => {
|
||||
const posts = $app.dao().findRecordsByFilter('posts', 'published = true')
|
||||
return e.json(200, { total: posts.length })
|
||||
})
|
||||
```
|
||||
|
||||
### Scheduled Tasks (Cron)
|
||||
|
||||
```javascript
|
||||
// pb_hooks/cron.pb.js
|
||||
|
||||
cronAdd('cleanup', '0 0 * * *', () => {
|
||||
// Daily cleanup
|
||||
$app.dao().deleteRecordsByFilter('logs', 'created < @now - 30d')
|
||||
})
|
||||
```
|
||||
|
||||
## Plugin System
|
||||
|
||||
Base's plugin system allows extending the core with custom Go code.
|
||||
|
||||
### Plugin Interface
|
||||
|
||||
```go
|
||||
package myplugin
|
||||
|
||||
import "github.com/hanzoai/base/core"
|
||||
|
||||
func Register(app core.App) error {
|
||||
// Add custom routes
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.GET("/api/custom", func(c echo.Context) error {
|
||||
return c.JSON(200, map[string]string{"status": "ok"})
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
// Add hooks
|
||||
app.OnRecordBeforeCreateRequest("posts").Add(func(e *core.RecordCreateEvent) error {
|
||||
// Custom validation, transformation, etc.
|
||||
return nil
|
||||
})
|
||||
|
||||
// Add migrations
|
||||
app.OnBeforeBootstrap().Add(func(e *core.BootstrapEvent) error {
|
||||
// Run custom migrations
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Built-in Plugins
|
||||
|
||||
| Plugin | Description |
|
||||
|--------|-------------|
|
||||
| **IAM Auth** | Hanzo IAM OIDC authentication provider |
|
||||
| **S3 Storage** | Hanzo S3 file storage backend |
|
||||
| **Search** | Hanzo Search integration for full-text search |
|
||||
| **ORM** | Hanzo ORM for advanced database operations |
|
||||
| **Analytics** | Event tracking to Hanzo Insights |
|
||||
|
||||
### Creating a Plugin
|
||||
|
||||
1. Create a Go module that imports `github.com/hanzoai/base/core`
|
||||
2. Implement the `Register(app core.App) error` function
|
||||
3. Add routes, hooks, middleware, or migrations
|
||||
4. Build with `go build -buildmode=plugin`
|
||||
5. Place in `pb_plugins/` directory
|
||||
|
||||
```go
|
||||
// main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/hanzoai/base/core"
|
||||
"github.com/hanzoai/base/plugins/myplugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := core.NewBaseApp()
|
||||
myplugin.Register(app)
|
||||
app.Start()
|
||||
}
|
||||
```
|
||||
|
||||
## Access Control
|
||||
|
||||
Base provides collection-level access rules using filter expressions:
|
||||
|
||||
```json
|
||||
{
|
||||
"listRule": "@request.auth.id != ''",
|
||||
"viewRule": "@request.auth.id != '' || published = true",
|
||||
"createRule": "@request.auth.id != '' && @request.auth.role = 'editor'",
|
||||
"updateRule": "@request.auth.id = author",
|
||||
"deleteRule": "@request.auth.id = author || @request.auth.role = 'admin'"
|
||||
}
|
||||
```
|
||||
|
||||
## SDK Libraries
|
||||
|
||||
| Language | Package |
|
||||
|----------|---------|
|
||||
| **JavaScript** | `@hanzo/base-sdk` |
|
||||
| **Go** | `github.com/hanzoai/base/sdk` |
|
||||
| **Python** | `hanzoai-base` |
|
||||
| **Dart/Flutter** | `hanzo_base` |
|
||||
The admin UI is available at `http://localhost:8090/_/`. The API base path defaults to `/v1` and can be moved with `BASE_API_PREFIX` for multi-app gateways.
|
||||
|
||||
## Related Services
|
||||
|
||||
<Cards>
|
||||
<Card title="Hanzo IAM" href="/docs/services/iam">
|
||||
Identity and access management for SSO integration
|
||||
The identity provider Base delegates all authentication to
|
||||
</Card>
|
||||
<Card title="Hanzo ORM" href="/docs/services/orm">
|
||||
Shared ORM for advanced database operations
|
||||
<Card title="Hanzo KMS" href="/docs/services/kms">
|
||||
Per-tenant database encryption keys
|
||||
</Card>
|
||||
<Card title="Hanzo S3" href="/docs/services/s3">
|
||||
S3-compatible object storage for file uploads
|
||||
Object storage backend for file uploads
|
||||
</Card>
|
||||
<Card title="Hanzo Search" href="/docs/services/search">
|
||||
Full-text search integration for collections
|
||||
<Card title="Hanzo PaaS" href="/docs/services/platform">
|
||||
Deploy and operate Base on your clusters
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
@@ -23,15 +23,15 @@ They are populated from KMS at deploy time.
|
||||
|----------|----------|-------------|---------|
|
||||
| `IAM_ENDPOINT` | yes | IAM server URL. In-cluster: `http://iam:8000`. Public: `https://hanzo.id`. | `https://hanzo.id` |
|
||||
| `IAM_ISSUER` | optional | OIDC issuer URL used for token validation. Defaults to `IAM_ENDPOINT`. | `https://hanzo.id` |
|
||||
| `IAM_CLIENT_ID` | yes | OAuth client ID issued for this application. | `app-myapp` |
|
||||
| `IAM_CLIENT_ID` | yes | OAuth client ID issued for this application. | `hanzo-myapp` |
|
||||
| `IAM_CLIENT_SECRET` | yes | OAuth client secret. Must come from KMS, never plaintext. | `${IAM_APP_MYAPP_CLIENT_SECRET}` |
|
||||
| `IAM_ORG` | optional | Organization slug this app is scoped to. | `hanzo` |
|
||||
| `IAM_APP` | optional | Application name as registered in IAM. | `app-myapp` |
|
||||
| `IAM_APP` | optional | Application name as registered in IAM. | `hanzo-myapp` |
|
||||
| `IAM_CERT` | optional | PEM-encoded signing certificate (for offline JWT validation). | (PEM blob) |
|
||||
| `IAM_USERINFO_URL` | optional | Override for the OIDC userinfo URL. Defaults to `${IAM_ENDPOINT}/oauth/userinfo`. | `https://hanzo.id/oauth/userinfo` |
|
||||
| `IAM_USERINFO_URL` | optional | Override for the OIDC userinfo URL. Defaults to `${IAM_ENDPOINT}/v1/iam/oauth/userinfo`. | `https://hanzo.id/v1/iam/oauth/userinfo` |
|
||||
|
||||
The `<org>-<app>` naming scheme is canonical for `IAM_APP` values: `hanzo-team`,
|
||||
`hanzo-tasks`, `lux-cloud`, `zoo-research`, etc.
|
||||
`hanzo-tasks`, `hanzo-cloud`, `hanzo-brain`, etc.
|
||||
|
||||
## RFC-compliant OAuth/OIDC endpoints
|
||||
|
||||
@@ -46,15 +46,15 @@ available for a non-OIDC endpoint.
|
||||
|
||||
| Purpose | Path |
|
||||
|---------|------|
|
||||
| Authorization | `/oauth/authorize` |
|
||||
| Token | `/oauth/token` |
|
||||
| Introspection | `/oauth/introspect` |
|
||||
| Revocation | `/oauth/revoke` |
|
||||
| UserInfo | `/oauth/userinfo` |
|
||||
| Device authorization | `/oauth/device` |
|
||||
| End-session / logout | `/oauth/logout` |
|
||||
| Discovery | `/.well-known/openid-configuration` |
|
||||
| JSON Web Key Set | `/.well-known/jwks` |
|
||||
| Authorization | `/v1/iam/oauth/authorize` |
|
||||
| Token | `/v1/iam/oauth/token` |
|
||||
| Introspection | `/v1/iam/oauth/introspect` |
|
||||
| Revocation | `/v1/iam/oauth/revoke` |
|
||||
| UserInfo | `/v1/iam/oauth/userinfo` |
|
||||
| Device authorization | `/v1/iam/oauth/device` |
|
||||
| End-session / logout | `/v1/iam/oauth/logout` |
|
||||
| Discovery | `/.well-known/openid-configuration` (alias of `/v1/iam/.well-known/openid-configuration`) |
|
||||
| JSON Web Key Set | `/v1/iam/.well-known/jwks` |
|
||||
|
||||
A consumer that reads `IAM_ENDPOINT` constructs every endpoint by
|
||||
concatenation — there are no other env vars for individual OAuth URLs.
|
||||
@@ -67,9 +67,9 @@ key for an application's secret follows the convention
|
||||
|
||||
| KMS key | Consumes |
|
||||
|---------|----------|
|
||||
| `IAM_APP_HANZO_CLIENT_SECRET` | app-hanzo |
|
||||
| `IAM_APP_CLOUD_CLIENT_SECRET` | app-cloud |
|
||||
| `IAM_APP_COMMERCE_CLIENT_SECRET` | app-commerce |
|
||||
| `IAM_APP_HANZO_CLIENT_SECRET` | hanzo-app |
|
||||
| `IAM_APP_CLOUD_CLIENT_SECRET` | hanzo-cloud |
|
||||
| `IAM_APP_COMMERCE_CLIENT_SECRET` | hanzo-commerce |
|
||||
|
||||
The KMS operator syncs these into a service-scoped K8s `Secret`, and the
|
||||
consumer pod reads them as `IAM_CLIENT_SECRET` from that Secret. See
|
||||
@@ -84,10 +84,10 @@ services:
|
||||
image: ghcr.io/hanzoai/myapp:v1.0.0
|
||||
environment:
|
||||
IAM_ENDPOINT: https://hanzo.id
|
||||
IAM_CLIENT_ID: app-myapp
|
||||
IAM_CLIENT_ID: hanzo-myapp
|
||||
IAM_CLIENT_SECRET: ${IAM_APP_MYAPP_CLIENT_SECRET}
|
||||
IAM_ORG: hanzo
|
||||
IAM_APP: app-myapp
|
||||
IAM_APP: hanzo-myapp
|
||||
```
|
||||
|
||||
## Example: Kubernetes
|
||||
@@ -105,7 +105,7 @@ spec:
|
||||
- name: IAM_ENDPOINT
|
||||
value: http://iam.hanzo.svc.cluster.local:8000
|
||||
- name: IAM_CLIENT_ID
|
||||
value: app-myapp
|
||||
value: hanzo-myapp
|
||||
- name: IAM_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@@ -125,9 +125,9 @@ const iam = {
|
||||
issuer: process.env.IAM_ISSUER ?? process.env.IAM_ENDPOINT!,
|
||||
};
|
||||
|
||||
const tokenUrl = `${iam.endpoint}/oauth/token`;
|
||||
const tokenUrl = `${iam.endpoint}/v1/iam/oauth/token`;
|
||||
const userInfoUrl =
|
||||
process.env.IAM_USERINFO_URL ?? `${iam.endpoint}/oauth/userinfo`;
|
||||
process.env.IAM_USERINFO_URL ?? `${iam.endpoint}/v1/iam/oauth/userinfo`;
|
||||
```
|
||||
|
||||
```python
|
||||
@@ -138,9 +138,9 @@ IAM_CLIENT_ID = os.environ["IAM_CLIENT_ID"]
|
||||
IAM_CLIENT_SECRET = os.environ["IAM_CLIENT_SECRET"]
|
||||
IAM_ISSUER = os.environ.get("IAM_ISSUER", IAM_ENDPOINT)
|
||||
|
||||
TOKEN_URL = f"{IAM_ENDPOINT}/oauth/token"
|
||||
TOKEN_URL = f"{IAM_ENDPOINT}/v1/iam/oauth/token"
|
||||
USERINFO_URL = os.environ.get(
|
||||
"IAM_USERINFO_URL", f"{IAM_ENDPOINT}/oauth/userinfo"
|
||||
"IAM_USERINFO_URL", f"{IAM_ENDPOINT}/v1/iam/oauth/userinfo"
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -5,229 +5,217 @@ description: Identity and Access Management for the Hanzo AI Platform
|
||||
|
||||
# Hanzo IAM
|
||||
|
||||
Hanzo IAM (Identity and Access Management) provides unified authentication and authorization across all Hanzo services. It supports OAuth2, OIDC, SAML, Kerberos/SPNEGO, LDAP, Web3, and MPC authentication.
|
||||
Hanzo IAM is the identity provider for the Hanzo platform, served at **`hanzo.id`**. It is a standards-compliant OpenID Connect (OIDC) and OAuth 2.0 authorization server that issues the JWTs every other Hanzo service trusts. It supports password, social OAuth2, Web3 wallet, WebAuthn/passkey, LDAP, SAML, device-code, and MFA authentication.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Issuer** | `https://hanzo.id` |
|
||||
| **API prefix** | `/v1/iam` |
|
||||
| **Discovery** | `https://hanzo.id/.well-known/openid-configuration` |
|
||||
| **JWKS** | `https://hanzo.id/v1/iam/.well-known/jwks` |
|
||||
| **Source** | [github.com/hanzoai/iam](https://github.com/hanzoai/iam) |
|
||||
|
||||
<Callout>
|
||||
Every Hanzo IAM endpoint lives under the single canonical prefix `/v1/iam`. The standard discovery path `/.well-known/openid-configuration` is served as a back-compatible alias so off-the-shelf OIDC clients work out of the box.
|
||||
</Callout>
|
||||
|
||||
## Features
|
||||
|
||||
- **OAuth2/OIDC**: Standard OpenID Connect flows with regex/wildcard scope support
|
||||
- **Kerberos/SPNEGO**: Enterprise SSO via Kerberos authentication with per-organization realm configuration
|
||||
- **LDAP**: Directory integration with configurable attribute filtering per organization
|
||||
- **SAML**: SAML 2.0 service provider and identity provider
|
||||
- **OAuth2 / OIDC**: Authorization Code (+ PKCE), Client Credentials, Refresh Token, Device Code, and Token Exchange grants
|
||||
- **Per-host white-label**: one deployment serves many domains, each with its own issuer, branding, and JWKS
|
||||
- **Web3 Authentication**: MetaMask, WalletConnect, Coinbase, Rainbow
|
||||
- **MPC Authentication**: Multi-party computation for secure key recovery
|
||||
- **Multi-tenancy**: Organizations, applications, and user isolation
|
||||
- **SSO**: Single sign-on across all Hanzo services
|
||||
- **RBAC**: Role-based access control
|
||||
- **MFA**: TOTP, WebAuthn, SMS, Email verification
|
||||
- **WebAuthn / Passkeys**: FIDO2 passwordless authentication with hardware keys or biometrics
|
||||
- **LDAP & SAML**: Directory integration and SAML 2.0 SP/IdP
|
||||
- **MFA**: TOTP, WebAuthn, SMS, and email verification
|
||||
- **Post-quantum tokens**: JWT signing with `MLDSA65` (ML-DSA-65, FIPS 204) in addition to `RS256`/`ES256`
|
||||
- **Multi-tenancy**: Organizations, applications, and user isolation with RBAC
|
||||
|
||||
## Endpoints
|
||||
## OIDC Endpoints
|
||||
|
||||
| Environment | URL |
|
||||
|-------------|-----|
|
||||
| Production | `https://iam.hanzo.ai` |
|
||||
| Staging | `https://stg.iam.hanzo.ai` |
|
||||
All endpoints are under `https://hanzo.id/v1/iam`.
|
||||
|
||||
## Quick Start
|
||||
| Endpoint | Path |
|
||||
|----------|------|
|
||||
| Discovery | `GET /.well-known/openid-configuration` (alias) · `GET /v1/iam/.well-known/openid-configuration` |
|
||||
| JWKS | `GET /v1/iam/.well-known/jwks` |
|
||||
| Authorize | `GET /v1/iam/oauth/authorize` |
|
||||
| Token | `POST /v1/iam/oauth/token` |
|
||||
| Userinfo | `GET /v1/iam/oauth/userinfo` |
|
||||
| Logout / end-session | `GET,POST /v1/iam/oauth/logout` |
|
||||
| Introspect (RFC 7662) | `POST /v1/iam/oauth/introspect` |
|
||||
| Revoke (RFC 7009) | `POST /v1/iam/oauth/revoke` |
|
||||
| Device code | `POST /v1/iam/oauth/device` |
|
||||
| Dynamic client registration | `POST /v1/iam/oauth/register` |
|
||||
|
||||
### 1. Register Your Application
|
||||
## Applications: the `<org>-<app>` convention
|
||||
|
||||
Create an application in Hanzo IAM to get client credentials:
|
||||
Every consumer of Hanzo IAM registers an **application**, and the application's name follows one rule:
|
||||
|
||||
```
|
||||
app slug = <org>-<app>
|
||||
```
|
||||
|
||||
For example: `hanzo-brain`, `hanzo-cloud`, `hanzo-chat`, `acme-verify`. The organization that owns the data goes in the `organization` field; the app name is namespaced with the org so it stays globally unique. Your application's `client_id` and the `aud` (audience) claim of issued tokens both default to this `<org>-<app>` slug.
|
||||
|
||||
### Register an application
|
||||
|
||||
```bash
|
||||
curl -X POST https://iam.hanzo.ai/api/add-application \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
curl -X POST https://hanzo.id/v1/iam/add-application \
|
||||
-H "Authorization: Bearer $IAM_ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"owner": "admin",
|
||||
"name": "my-app",
|
||||
"displayName": "My Application",
|
||||
"name": "hanzo-brain",
|
||||
"displayName": "Hanzo Brain",
|
||||
"organization": "hanzo",
|
||||
"redirectUris": ["https://myapp.com/callback"],
|
||||
"enablePassword": true,
|
||||
"enableSignUp": true,
|
||||
"enableWebAuthn": true
|
||||
"cert": "cert-built-in",
|
||||
"tokenFormat": "JWT",
|
||||
"grantTypes": ["authorization_code", "refresh_token"],
|
||||
"redirectUris": ["https://brain.hanzo.ai/callback"]
|
||||
}'
|
||||
```
|
||||
|
||||
### 2. Implement OAuth2 Flow
|
||||
`clientId` and `clientSecret` are generated automatically if omitted. Store the returned `clientSecret` in [Hanzo KMS](/docs/services/kms) — never in source or plaintext config.
|
||||
|
||||
```typescript
|
||||
// Redirect to authorization
|
||||
const authUrl = new URL('https://iam.hanzo.ai/oauth/authorize')
|
||||
authUrl.searchParams.set('client_id', CLIENT_ID)
|
||||
authUrl.searchParams.set('redirect_uri', REDIRECT_URI)
|
||||
authUrl.searchParams.set('response_type', 'code')
|
||||
authUrl.searchParams.set('scope', 'openid profile email')
|
||||
authUrl.searchParams.set('state', generateState())
|
||||
<Callout type="info">
|
||||
For idempotent, declarative provisioning (CI/CD, operators), use `POST /v1/iam/admin/applications/upsert` with an `Authorization: Bootstrap <token>` header instead of a session/admin JWT.
|
||||
</Callout>
|
||||
|
||||
window.location.href = authUrl.toString()
|
||||
## Authorization Code + PKCE Flow
|
||||
|
||||
This is the recommended flow for web apps, SPAs, and mobile clients.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Discover
|
||||
|
||||
```bash
|
||||
curl https://hanzo.id/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
### 3. Exchange Code for Token
|
||||
The `issuer` and every endpoint come back scoped to the requesting host (`hanzo.id`).
|
||||
|
||||
### Redirect to authorize
|
||||
|
||||
```
|
||||
https://hanzo.id/v1/iam/oauth/authorize
|
||||
?response_type=code
|
||||
&client_id=hanzo-brain
|
||||
&redirect_uri=https://brain.hanzo.ai/callback
|
||||
&scope=openid%20profile%20email
|
||||
&state=RANDOM_STATE
|
||||
&code_challenge=<S256(verifier)>
|
||||
&code_challenge_method=S256
|
||||
```
|
||||
|
||||
The user authenticates on the branded login page and is redirected back with `?code=...&state=...`.
|
||||
|
||||
### Exchange the code for tokens
|
||||
|
||||
```bash
|
||||
curl -X POST https://hanzo.id/v1/iam/oauth/token \
|
||||
-d grant_type=authorization_code \
|
||||
-d code=$AUTH_CODE \
|
||||
-d redirect_uri=https://brain.hanzo.ai/callback \
|
||||
-d client_id=hanzo-brain \
|
||||
-d client_secret=$CLIENT_SECRET \
|
||||
-d code_verifier=$PKCE_VERIFIER
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIs...",
|
||||
"id_token": "eyJhbGciOiJSUzI1NiIs...",
|
||||
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600
|
||||
}
|
||||
```
|
||||
|
||||
### Get user info
|
||||
|
||||
```bash
|
||||
curl https://hanzo.id/v1/iam/oauth/userinfo \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Validating tokens in a resource server
|
||||
|
||||
Any Hanzo service validates a `hanzo.id` token **offline**: fetch the JWKS once, then verify each request locally.
|
||||
|
||||
1. Fetch the signing keys from `https://hanzo.id/v1/iam/.well-known/jwks` (cache by `kid`).
|
||||
2. Verify the JWT signature (`RS256` by default).
|
||||
3. Require `iss == https://hanzo.id`.
|
||||
4. Require `aud == <org>-<app>` (your own `client_id`).
|
||||
5. Read `sub` (the user ID) and `owner` (the user's organization) to scope data.
|
||||
|
||||
```typescript
|
||||
const response = await fetch('https://iam.hanzo.ai/oauth/token', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
grant_type: 'authorization_code',
|
||||
client_id: CLIENT_ID,
|
||||
client_secret: CLIENT_SECRET,
|
||||
code: authorizationCode,
|
||||
redirect_uri: REDIRECT_URI
|
||||
import { createRemoteJWKSet, jwtVerify } from 'jose'
|
||||
|
||||
const JWKS = createRemoteJWKSet(new URL('https://hanzo.id/v1/iam/.well-known/jwks'))
|
||||
|
||||
export async function verify(token: string) {
|
||||
const { payload } = await jwtVerify(token, JWKS, {
|
||||
issuer: 'https://hanzo.id',
|
||||
audience: 'hanzo-brain', // your <org>-<app> client_id
|
||||
})
|
||||
})
|
||||
|
||||
const { access_token, refresh_token, id_token } = await response.json()
|
||||
return { userId: payload.sub, org: payload.owner as string }
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Get User Info
|
||||
The Hanzo Gateway performs this validation at the edge and injects trusted `X-Org-Id`, `X-User-Id`, and `X-User-Email` headers (stripping any client-supplied identity headers) for upstream services.
|
||||
|
||||
```typescript
|
||||
const userInfo = await fetch('https://iam.hanzo.ai/oauth/userinfo', {
|
||||
headers: { 'Authorization': `Bearer ${access_token}` }
|
||||
}).then(r => r.json())
|
||||
## White-labeling by domain
|
||||
|
||||
// userInfo contains: sub, name, email, picture, etc.
|
||||
```
|
||||
Hanzo IAM is multi-tenant white-label by design. A single deployment serves many domains:
|
||||
|
||||
## Organizations
|
||||
- **Per-host issuer** — the issuer, authorization endpoint, and JWKS are derived per request from `X-Forwarded-Host` (set by [Hanzo Ingress](/docs/services/ingress)). Each domain returns its own discovery document.
|
||||
- **Per-app branding** — logo, title, favicon, theme color, and custom CSS are configured on each organization and application, and resolved on the login page by `client_id` (or by org in the path, `/login/{org}`).
|
||||
|
||||
Hanzo IAM supports multi-tenant organizations:
|
||||
|
||||
| Organization | Domain | Theme Color |
|
||||
|--------------|--------|-------------|
|
||||
| hanzo | hanzo.ai | #fd4444 (Red) |
|
||||
| zoo | zoo.ngo | #10b981 (Green) |
|
||||
| lux | lux.network | #8b5cf6 (Purple) |
|
||||
| pars | pars.ai | #3b82f6 (Blue) |
|
||||
|
||||
## Applications
|
||||
|
||||
Pre-configured applications:
|
||||
|
||||
| Application | Organization | Description |
|
||||
|-------------|--------------|-------------|
|
||||
| app-hanzo | hanzo | Main Hanzo AI Platform |
|
||||
| app-cloud | hanzo | Hanzo Cloud Console |
|
||||
| app-commerce | hanzo | Hanzo Commerce Platform |
|
||||
| app-console | hanzo | Hanzo Observability Console |
|
||||
| app-platform | hanzo | Hanzo PaaS Platform |
|
||||
| app-zoo | zoo | Zoo Labs Foundation |
|
||||
| app-lux | lux | Lux Network |
|
||||
| app-pars | pars | Pars AI Platform |
|
||||
| app-sign | hanzo | Document Signing |
|
||||
| app-dataroom | hanzo | Secure Document Sharing |
|
||||
| app-captable | hanzo | Equity Management |
|
||||
This is how `hanzo.id` presents Hanzo branding while the same engine can serve entirely separate, independently-branded identity surfaces for other tenants.
|
||||
|
||||
## Authentication Methods
|
||||
|
||||
### Password Authentication
|
||||
| Method | Notes |
|
||||
|--------|-------|
|
||||
| **Password** | `argon2id` hashing |
|
||||
| **OAuth2 social** | GitHub, Google, Microsoft, Apple, Discord, X |
|
||||
| **Web3 wallets** | MetaMask, WalletConnect, Coinbase, Rainbow |
|
||||
| **WebAuthn / Passkeys** | FIDO2 hardware keys and biometrics |
|
||||
| **LDAP** | Per-organization directory integration |
|
||||
| **SAML 2.0** | Service provider and identity provider |
|
||||
| **Device code** | CLIs and input-constrained devices |
|
||||
| **MFA** | TOTP, WebAuthn, SMS, email |
|
||||
|
||||
Standard username/password with argon2id hashing.
|
||||
|
||||
### OAuth2 Providers
|
||||
|
||||
- GitHub
|
||||
- Google
|
||||
- Microsoft
|
||||
- Apple
|
||||
- Discord
|
||||
- Twitter/X
|
||||
|
||||
### Web3 Wallets
|
||||
|
||||
- MetaMask
|
||||
- WalletConnect
|
||||
- Coinbase Wallet
|
||||
- Rainbow
|
||||
|
||||
### WebAuthn/Passkeys
|
||||
|
||||
FIDO2 passwordless authentication with hardware keys or biometrics.
|
||||
|
||||
## API Reference
|
||||
|
||||
See the [IAM API documentation](/docs/services/iam/api) for the complete API reference.
|
||||
|
||||
## SDK Integration
|
||||
|
||||
### JavaScript/TypeScript
|
||||
## SDK
|
||||
|
||||
```typescript
|
||||
import { HanzoAuth } from '@hanzo/auth'
|
||||
import { signIn } from '@hanzo/iam/browser'
|
||||
|
||||
const auth = new HanzoAuth({
|
||||
clientId: 'your-client-id',
|
||||
redirectUri: 'https://yourapp.com/callback',
|
||||
organization: 'hanzo'
|
||||
await signIn({
|
||||
iamUrl: 'https://hanzo.id',
|
||||
clientId: 'hanzo-brain', // your <org>-<app> slug
|
||||
redirectUri: 'https://brain.hanzo.ai/callback',
|
||||
})
|
||||
|
||||
// Login
|
||||
await auth.login()
|
||||
|
||||
// Get current user
|
||||
const user = await auth.getUser()
|
||||
|
||||
// Logout
|
||||
await auth.logout()
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
from hanzoai.auth import HanzoAuth
|
||||
|
||||
auth = HanzoAuth(
|
||||
client_id="your-client-id",
|
||||
client_secret="your-client-secret",
|
||||
organization="hanzo"
|
||||
)
|
||||
|
||||
# Get access token (client credentials)
|
||||
token = auth.get_access_token()
|
||||
|
||||
# Verify token
|
||||
claims = auth.verify_token(token)
|
||||
```
|
||||
|
||||
## OIDC Flows
|
||||
|
||||
Hanzo IAM supports the full OpenID Connect specification. Choose the right flow for your application:
|
||||
|
||||
| Application Type | Flow | Guide |
|
||||
|-----------------|------|-------|
|
||||
| Web app (server-side) | Authorization Code | [OIDC Flows](/docs/services/iam/how-to-connect/oidc-flows) |
|
||||
| SPA / Mobile | Authorization Code + PKCE | [OIDC Flows](/docs/services/iam/how-to-connect/oidc-flows#authorization-code--pkce) |
|
||||
| Service / API | Client Credentials | [OIDC Flows](/docs/services/iam/how-to-connect/oidc-flows#client-credentials-flow) |
|
||||
| CLI / IoT | Device Code | [OIDC Flows](/docs/services/iam/how-to-connect/oidc-flows#device-code-flow) |
|
||||
|
||||
**Discovery URL:** `https://hanzo.id/.well-known/openid-configuration`
|
||||
|
||||
All Hanzo services authenticate via IAM using the `owner` claim in the JWT to scope data to the correct organization.
|
||||
|
||||
## Next Steps
|
||||
|
||||
<Cards>
|
||||
<Card title="Configuration" href="/docs/services/iam/configuration">
|
||||
Canonical `IAM_*` environment variable contract for consumer services
|
||||
</Card>
|
||||
<Card title="OIDC Flows" href="/docs/services/iam/how-to-connect/oidc-flows">
|
||||
Step-by-step guide to authorization code, PKCE, client credentials, and device code flows
|
||||
</Card>
|
||||
<Card title="OIDC Discovery" href="/docs/services/iam/how-to-connect/oidc-client">
|
||||
Discovery endpoints and OIDC client configuration
|
||||
</Card>
|
||||
<Card title="WebAuthn / FIDO2" href="/docs/services/iam/how-to-connect/webauthn">
|
||||
Passwordless authentication with hardware keys and biometrics
|
||||
</Card>
|
||||
<Card title="SAML" href="/docs/services/iam/how-to-connect/saml/overview">
|
||||
SAML 2.0 service provider and identity provider
|
||||
</Card>
|
||||
<Card title="MCP Auth" href="/docs/services/iam/mcp-auth/overview">
|
||||
Model Context Protocol authentication for AI agents
|
||||
The canonical `IAM_*` environment variable contract for consumer services
|
||||
</Card>
|
||||
<Card title="KMS Integration" href="/docs/services/iam/kms-integration">
|
||||
How IAM uses KMS for secret management
|
||||
How IAM stores and reads its own secrets from Hanzo KMS
|
||||
</Card>
|
||||
<Card title="MPC Auth" href="/docs/services/iam/mpc">
|
||||
Multi-party computation authentication for secure key recovery
|
||||
</Card>
|
||||
<Card title="Hanzo KMS" href="/docs/services/kms">
|
||||
Secret management for client secrets and signing keys
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
@@ -1,57 +1,45 @@
|
||||
---
|
||||
title: KMS Integration
|
||||
description: How Hanzo IAM uses KMS for secure secret management
|
||||
description: How Hanzo IAM stores and reads its secrets from Hanzo KMS
|
||||
---
|
||||
|
||||
# KMS Integration
|
||||
|
||||
Hanzo IAM stores all sensitive configuration — OAuth client secrets, social provider credentials, admin passwords — in [Hanzo KMS](/docs/services/kms). No secrets are hardcoded in deployment manifests or environment variables.
|
||||
Hanzo IAM keeps all sensitive configuration — OAuth client secrets, social-provider credentials, and admin passwords — in [Hanzo KMS](/docs/services/kms). Nothing sensitive is hard-coded in manifests or environment variables.
|
||||
|
||||
## Architecture
|
||||
## How it works
|
||||
|
||||
IAM authenticates to KMS with a **machine identity** and reads its secrets through two complementary paths:
|
||||
|
||||
1. **Direct fetch at startup** — IAM logs in to KMS (`POST /v1/kms/auth/login`) with its machine-identity `clientId`/`clientSecret` and reads the secrets it needs.
|
||||
2. **KMSSecret CRD** — the kms-operator keeps an `iam-secrets` Kubernetes Secret continuously in sync as a fallback for workloads that consume secrets as env vars.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Hanzo KMS (kms.hanzo.ai) │
|
||||
│ │
|
||||
│ Project: hanzo-iam │
|
||||
│ Environment: prod │
|
||||
│ Secrets: 23 entries (client secrets, OAuth, admin creds) │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Machine Identity: iam-service │ │
|
||||
│ │ Auth: Universal Auth (clientId + clientSecret) │ │
|
||||
│ │ Role: Viewer on hanzo-iam project │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└──────────────┬───────────────────────────┬───────────────────┘
|
||||
│ │
|
||||
Direct SDK fetch KMSSecret CRD (backup)
|
||||
(startup time) (kms-operator sync)
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌──────────────────────────┐
|
||||
│ IAM Pod │ │ iam-secrets K8s Secret │
|
||||
│ (reads from KMS │ │ (auto-synced every 60s) │
|
||||
│ via kms-go SDK) │ │ │
|
||||
└──────────────────────┘ └──────────────────────────┘
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Hanzo KMS (kms.hanzo.ai) │
|
||||
│ org: hanzo-iam env: production │
|
||||
│ machine identity: iam-service (read-only) │
|
||||
└───────────────┬───────────────────┬───────────┘
|
||||
direct login KMSSecret CRD
|
||||
(startup) (operator sync)
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌──────────────────┐
|
||||
│ IAM pod │ │ iam-secrets (K8s) │
|
||||
└──────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
IAM uses **two complementary mechanisms**:
|
||||
## Machine identity
|
||||
|
||||
1. **Direct SDK fetch** — At startup, IAM uses the `kms-go` SDK to pull secrets directly from KMS. This is the primary path.
|
||||
2. **KMSSecret CRD** — The kms-operator syncs secrets to a `iam-secrets` K8s Secret every 60 seconds as a backup/fallback.
|
||||
|
||||
## Machine Identity Setup
|
||||
|
||||
IAM authenticates to KMS using a **Machine Identity** with Universal Auth credentials:
|
||||
IAM uses a dedicated, least-privilege machine identity so its secrets are isolated from other services:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Identity name | `iam-service` |
|
||||
| Auth method | Universal Auth |
|
||||
| Project access | `hanzo-iam` (Viewer) |
|
||||
| Credential mount | `/etc/kms/auth/clientId`, `/etc/kms/auth/clientSecret` |
|
||||
| Identity | `iam-service` |
|
||||
| Access | read-only on the `hanzo-iam` org |
|
||||
| Credential | `clientId` / `clientSecret`, mounted from a K8s Secret |
|
||||
|
||||
The credentials are stored in a K8s Secret and mounted as files:
|
||||
The credentials live in a Kubernetes Secret — only the references, never the values, are committed:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
@@ -61,58 +49,23 @@ metadata:
|
||||
namespace: hanzo
|
||||
type: Opaque
|
||||
stringData:
|
||||
clientId: "" # KMS Machine Identity Client ID
|
||||
clientSecret: "" # KMS Machine Identity Client Secret
|
||||
clientId: "" # populated out-of-band from KMS/IAM, never committed
|
||||
clientSecret: ""
|
||||
```
|
||||
|
||||
## Secrets Inventory
|
||||
## What IAM stores in KMS
|
||||
|
||||
The `hanzo-iam` KMS project contains 23 secrets:
|
||||
IAM's secrets are organized under its `hanzo-iam` org in KMS:
|
||||
|
||||
### Application Client Secrets
|
||||
| Category | Examples |
|
||||
|----------|----------|
|
||||
| **Application client secrets** | One per registered `<org>-<app>` application (e.g. `hanzo-cloud`, `hanzo-chat`, `hanzo-platform`) |
|
||||
| **Social provider credentials** | GitHub, Google, Microsoft, Apple OAuth client IDs and secrets |
|
||||
| **Admin credentials** | The seeded organization administrator password |
|
||||
|
||||
Every IAM-registered application has its OAuth client secret stored in KMS:
|
||||
Each application's OAuth `clientSecret` is stored under a key named for its `<org>-<app>` slug — never inlined into `init_data.json` or a manifest.
|
||||
|
||||
| KMS Key | Application |
|
||||
|---------|-------------|
|
||||
| `IAM_APP_HANZO_CLIENT_SECRET` | app-hanzo (hanzo.ai) |
|
||||
| `IAM_APP_CLOUD_CLIENT_SECRET` | app-cloud (cloud.hanzo.ai) |
|
||||
| `IAM_APP_COMMERCE_CLIENT_SECRET` | app-commerce (api.hanzo.ai) |
|
||||
| `IAM_APP_CONSOLE_CLIENT_SECRET` | app-console (console.hanzo.ai) |
|
||||
| `IAM_APP_CHAT_CLIENT_SECRET` | app-chat (hanzo.chat) |
|
||||
| `IAM_APP_PLATFORM_CLIENT_SECRET` | app-platform (platform.hanzo.ai) |
|
||||
| `IAM_APP_ZOO_CLIENT_SECRET` | app-zoo (zoo.ngo) |
|
||||
| `IAM_APP_LUX_CLIENT_SECRET` | app-lux (lux.network) |
|
||||
| `IAM_APP_PARS_CLIENT_SECRET` | app-pars (pars.ai) |
|
||||
| `IAM_APP_HANZOBOT_CLIENT_SECRET` | app-hanzobot (app.hanzo.bot) |
|
||||
| `IAM_APP_BOOTNODE_CLIENT_SECRET` | app-bootnode |
|
||||
| `IAM_APP_HANZO_WEB3_CLIENT_SECRET` | app-hanzo-web3 |
|
||||
| `IAM_APP_LUX_WEB3_CLIENT_SECRET` | app-lux-web3 |
|
||||
| `IAM_APP_ZOO_WEB3_CLIENT_SECRET` | app-zoo-web3 |
|
||||
| `IAM_APP_TEAM_CLIENT_SECRET` | app-team |
|
||||
| `IAM_APP_ADNEXUS_CLIENT_SECRET` | app-adnexus |
|
||||
| `IAM_APP_ZT_CONSOLE_CLIENT_SECRET` | app-zt-console |
|
||||
|
||||
### Social Provider Credentials
|
||||
|
||||
| KMS Key | Provider |
|
||||
|---------|----------|
|
||||
| `IAM_GITHUB_CLIENT_ID` | GitHub OAuth |
|
||||
| `IAM_GITHUB_CLIENT_SECRET` | GitHub OAuth |
|
||||
| `IAM_GOOGLE_CLIENT_ID` | Google OAuth |
|
||||
| `IAM_GOOGLE_CLIENT_SECRET` | Google OAuth |
|
||||
|
||||
### Admin Credentials
|
||||
|
||||
| KMS Key | Purpose |
|
||||
|---------|---------|
|
||||
| `IAM_ADMIN_PASSWORD` | Built-in admin account |
|
||||
| `IAM_Z_HANZO_PASSWORD` | Hanzo org admin |
|
||||
| `IAM_Z_LUX_PASSWORD` | Lux org admin |
|
||||
|
||||
## KMSSecret CRD Configuration
|
||||
|
||||
The backup sync uses the KMSSecret custom resource:
|
||||
## Syncing IAM's secrets to Kubernetes
|
||||
|
||||
```yaml
|
||||
apiVersion: secrets.lux.network/v1alpha1
|
||||
@@ -121,7 +74,7 @@ metadata:
|
||||
name: iam-kms-sync
|
||||
namespace: hanzo
|
||||
spec:
|
||||
hostAPI: http://kms.hanzo.svc.cluster.local/api
|
||||
hostAPI: https://kms.hanzo.ai
|
||||
resyncInterval: 60
|
||||
authentication:
|
||||
universalAuth:
|
||||
@@ -129,101 +82,52 @@ spec:
|
||||
secretName: iam-kms-auth
|
||||
secretNamespace: hanzo
|
||||
secretsScope:
|
||||
projectSlug: hanzo-iam
|
||||
envSlug: prod
|
||||
projectSlug: hanzo-iam # IAM's own org
|
||||
envSlug: production
|
||||
secretsPath: /
|
||||
keys:
|
||||
- IAM_ADMIN_PASSWORD
|
||||
- HANZO_CLOUD_CLIENT_SECRET
|
||||
- GITHUB_CLIENT_SECRET
|
||||
managedSecretReference:
|
||||
secretName: iam-secrets
|
||||
secretNamespace: hanzo
|
||||
secretType: Opaque
|
||||
creationPolicy: Orphan
|
||||
```
|
||||
|
||||
Key fields:
|
||||
## How other services consume an IAM secret
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `hostAPI` | KMS internal endpoint (in-cluster, no TLS needed) |
|
||||
| `resyncInterval` | Sync frequency in seconds |
|
||||
| `credentialsRef` | K8s secret containing Universal Auth credentials |
|
||||
| `secretsScope` | KMS project, environment, and path to sync |
|
||||
| `managedSecretReference` | Target K8s Secret to write synced values into |
|
||||
|
||||
## Adding a New Application Secret
|
||||
|
||||
When registering a new application in IAM:
|
||||
|
||||
1. **Create the secret in KMS** at `kms.hanzo.ai` under the `hanzo-iam` project:
|
||||
```
|
||||
Key: IAM_APP_MYAPP_CLIENT_SECRET
|
||||
Value: <generated-secret>
|
||||
Environment: prod
|
||||
```
|
||||
|
||||
2. **Reference it in IAM's init_data.json** (or update via API):
|
||||
```json
|
||||
{
|
||||
"name": "app-myapp",
|
||||
"clientSecret": "${IAM_APP_MYAPP_CLIENT_SECRET}"
|
||||
}
|
||||
```
|
||||
|
||||
3. **The KMSSecret CRD** auto-syncs the new secret to the `iam-secrets` K8s Secret within 60 seconds.
|
||||
|
||||
## How Other Services Consume IAM Secrets
|
||||
|
||||
Each service that authenticates via IAM OIDC stores its own copy of the client secret in KMS. For example, Hanzo Chat stores `OPENID_CLIENT_SECRET` under the `/chat` path in the shared `secrets-639-c` project:
|
||||
|
||||
```yaml
|
||||
# chat/kms-secrets.yaml
|
||||
apiVersion: secrets.lux.network/v1alpha1
|
||||
kind: KMSSecret
|
||||
metadata:
|
||||
name: chat-kms-sync
|
||||
spec:
|
||||
authentication:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: universal-auth-credentials
|
||||
secretsScope:
|
||||
projectSlug: secrets-639-c
|
||||
envSlug: prod
|
||||
secretsPath: /chat
|
||||
managedSecretReference:
|
||||
secretName: chat-secrets
|
||||
```
|
||||
|
||||
The deployment then references the synced K8s Secret:
|
||||
A service that authenticates via IAM OIDC stores its own `client_secret` in KMS and syncs it the same way. For example, an app reads its OIDC client secret from a synced Secret:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: OPENID_CLIENT_SECRET
|
||||
- name: OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: chat-secrets
|
||||
key: OPENID_CLIENT_SECRET
|
||||
name: app-secrets
|
||||
key: OIDC_CLIENT_SECRET
|
||||
```
|
||||
|
||||
This pattern is consistent across all services — see [KMS Service Integration](/docs/services/kms/service-integration) for the full list.
|
||||
See [KMS Service Integration](/docs/services/kms/service-integration) for the full pattern.
|
||||
|
||||
## Security Model
|
||||
## Security model
|
||||
|
||||
- **No plaintext secrets in manifests** — All K8s Secrets are populated by KMS, never checked into git
|
||||
- **Least-privilege access** — IAM's Machine Identity has Viewer (read-only) access to its own project only
|
||||
- **Separate projects** — IAM secrets (`hanzo-iam`) are isolated from service secrets (`secrets-639-c`)
|
||||
- **In-cluster communication** — KMS API is accessed via `kms.hanzo.svc.cluster.local`, never exposed externally for secret sync
|
||||
- **60-second resync** — Rotated secrets propagate to K8s within one minute
|
||||
- **Audit trail** — All secret reads/writes are logged in KMS audit log
|
||||
- **No plaintext secrets in git** — Kubernetes Secrets are populated from KMS, never committed.
|
||||
- **Least privilege** — IAM's machine identity is read-only and scoped to its own org.
|
||||
- **Isolation** — IAM's secrets live in a separate KMS org from service secrets.
|
||||
- **Audit trail** — every read and write is logged in KMS.
|
||||
|
||||
## Next Steps
|
||||
|
||||
<Cards>
|
||||
<Card title="KMS Overview" href="/docs/services/kms">
|
||||
Hanzo KMS architecture and features
|
||||
Hanzo KMS architecture and the secret API
|
||||
</Card>
|
||||
<Card title="KMS Service Integration" href="/docs/services/kms/service-integration">
|
||||
How all Hanzo services consume secrets from KMS
|
||||
How every Hanzo service consumes secrets from KMS
|
||||
</Card>
|
||||
<Card title="IAM Applications" href="/docs/services/iam/application">
|
||||
Register and configure IAM applications
|
||||
<Card title="Machine Identities" href="/docs/services/kms/universal-auth">
|
||||
Service-to-KMS authentication
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
@@ -1,373 +1,171 @@
|
||||
---
|
||||
title: Hanzo KMS
|
||||
description: Key Management Service for secrets, API keys, and encryption
|
||||
description: Key Management Service for secrets, machine-identity access, and threshold signing keys
|
||||
---
|
||||
|
||||
# Hanzo KMS
|
||||
|
||||
Hanzo KMS is an open-source Key Management Service for managing secrets, API keys, certificates, and encryption keys across infrastructure. It provides enterprise-grade secret management with MPC signer integration.
|
||||
Hanzo KMS is the key management service for the Hanzo platform. It stores secrets and API keys, hands them to workloads through short-lived machine-identity tokens, syncs them into Kubernetes, and manages threshold signing keys backed by [Hanzo MPC](/docs/services/mpc).
|
||||
|
||||
## Features
|
||||
| | |
|
||||
|---|---|
|
||||
| **API** | `https://kms.hanzo.ai` |
|
||||
| **API prefix** | `/v1/kms` |
|
||||
| **Auth** | Machine-identity login → bearer JWT (verified against [Hanzo IAM](/docs/services/iam)) |
|
||||
| **K8s sync** | `KMSSecret` CRD via the kms-operator |
|
||||
| **Source** | [github.com/hanzoai/kms](https://github.com/hanzoai/kms) |
|
||||
|
||||
- **Secrets Management**: Store and manage secrets, API keys, certificates
|
||||
- **Environment Sync**: Sync secrets across development, staging, production
|
||||
- **MPC Integration**: Threshold signatures via Hanzo MPC Signer
|
||||
- **Dynamic Secrets**: Auto-rotating database credentials (PostgreSQL, MySQL, MongoDB, Redis, ClickHouse)
|
||||
- **PAM Discovery**: Automated discovery of privileged accounts and resources with Active Directory support
|
||||
- **Certificate Management**: PKI certificate lifecycle with metadata, Sectigo/DigiCert CA integration
|
||||
- **Secret Versioning**: Full history and rollback
|
||||
- **Audit Logging**: Complete access audit trail
|
||||
- **RBAC**: Role-based access control with granular project permissions
|
||||
- **Sub-Organizations**: Hierarchical organization management with membership delegation
|
||||
- **Kubernetes Operator**: Native K8s secret injection
|
||||
<Callout>
|
||||
Hanzo KMS exposes a single surface under `/v1/kms`. Secrets are addressed by **org / path / name** with the environment as a query parameter. There is no plaintext-secret-in-config anywhere — workloads authenticate with a machine identity and read values at runtime.
|
||||
</Callout>
|
||||
|
||||
## Architecture
|
||||
## Authentication
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ HANZO KMS │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ KMS Control Plane │ │
|
||||
│ │ ┌──────────┬─────────────┬──────────────┬─────────────────┐ │ │
|
||||
│ │ │ Policy │ Approvals │ Audit Log │ Key Registry │ │ │
|
||||
│ │ └────┬─────┴──────┬──────┴───────┬──────┴───────┬─────────┘ │ │
|
||||
│ └───────┼────────────┼──────────────┼──────────────┼─────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌───────▼────────────▼──────────────▼──────────────▼─────────────┐ │
|
||||
│ │ Unified Signing API │ │
|
||||
│ └───────┬────────────┬──────────────┬──────────────┬─────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌───────▼───┐ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ │
|
||||
│ │ HSM │ │ MPC │ │ Software │ │ AWS/ │ │
|
||||
│ │ Signer │ │ Signer │ │ Signer │ │ GCP │ │
|
||||
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Hanzo MPC Cluster (Private BFT Chain) │ │
|
||||
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
|
||||
│ │ │ Node 0 │ │ Node 1 │ │ Node 2 │ │ Node 3 │ │ Node 4 │ │ │
|
||||
│ │ │ share₀ │ │ share₁ │ │ share₂ │ │ share₃ │ │ share₄ │ │ │
|
||||
│ │ │Ed25519 │ │Ed25519 │ │Ed25519 │ │Ed25519 │ │Ed25519 │ │ │
|
||||
│ │ │ML-DSA │ │ML-DSA │ │ML-DSA │ │ML-DSA │ │ML-DSA │ │ │
|
||||
│ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ │
|
||||
│ │ Dual-cert finality: Ed25519 + ML-DSA-65 (3-of-5) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Environment | URL |
|
||||
|-------------|-----|
|
||||
| Production | `https://kms.hanzo.ai` |
|
||||
| Staging | `https://stg.kms.hanzo.ai` |
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Development Setup
|
||||
KMS uses **machine identities**: a non-human `clientId` + `clientSecret` minted in [Hanzo IAM](/docs/services/iam). Exchange them for a short-lived access token, then send that token as a bearer on every request.
|
||||
|
||||
```bash
|
||||
cd ~/work/hanzo/kms
|
||||
|
||||
# Copy environment
|
||||
cp .env.dev.example .env
|
||||
|
||||
# Start with Docker Compose
|
||||
docker compose -f docker-compose.dev.yml up
|
||||
|
||||
# Or run separately
|
||||
cd backend && npm run dev
|
||||
cd frontend && npm run dev
|
||||
# Log in (clientId/clientSecret come from a K8s Secret or your secret store — never inline them)
|
||||
ACCESS_TOKEN=$(curl -sS -X POST https://kms.hanzo.ai/v1/kms/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}" \
|
||||
| jq -r .accessToken)
|
||||
```
|
||||
|
||||
### 2. Create a Secret
|
||||
Response:
|
||||
|
||||
```typescript
|
||||
|
||||
const kms = new HanzoKMS({
|
||||
apiKey: process.env.HANZO_KMS_TOKEN
|
||||
})
|
||||
|
||||
// Create a secret
|
||||
await kms.secrets.create({
|
||||
environment: 'production',
|
||||
path: '/api-keys',
|
||||
key: 'STRIPE_SECRET',
|
||||
value: 'sk_live_xxx'
|
||||
})
|
||||
```json
|
||||
{ "accessToken": "<RS256 JWT>", "expiresIn": 86400, "tokenType": "Bearer" }
|
||||
```
|
||||
|
||||
### 3. Retrieve Secrets
|
||||
The token is a standard JWT whose signature, issuer, and audience are validated against IAM's JWKS. Its `owner` claim is the organization it can act on — a token can only read secrets under its own `{org}`.
|
||||
|
||||
```typescript
|
||||
// Get single secret
|
||||
const secret = await kms.secrets.get({
|
||||
environment: 'production',
|
||||
path: '/api-keys',
|
||||
key: 'STRIPE_SECRET'
|
||||
})
|
||||
## Secret hierarchy
|
||||
|
||||
// Get all secrets in path
|
||||
const secrets = await kms.secrets.list({
|
||||
environment: 'production',
|
||||
path: '/api-keys'
|
||||
})
|
||||
A secret is addressed by four coordinates:
|
||||
|
||||
```
|
||||
org / path / name ? env
|
||||
hanzo / providers / OPENAI_API_KEY ? env=production
|
||||
```
|
||||
|
||||
## Secret Types
|
||||
- **org** — the organization slug; must match the token's `owner` claim.
|
||||
- **path** — a folder within the org (e.g. `/`, `/providers`, `/db`).
|
||||
- **name** — the exact secret key.
|
||||
- **env** — a query parameter; defaults to `default` when omitted.
|
||||
|
||||
### Static Secrets
|
||||
<Callout type="info">
|
||||
KMS reads are **exact-name** — there is no "list all secrets in a folder" endpoint. Enumerate the keys your workload needs (the `KMSSecret` CRD does exactly this).
|
||||
</Callout>
|
||||
|
||||
Standard key-value secrets:
|
||||
## Reading & writing secrets
|
||||
|
||||
```typescript
|
||||
await kms.secrets.create({
|
||||
key: 'DATABASE_URL',
|
||||
value: 'postgres://user:pass@host:5432/db',
|
||||
type: 'static'
|
||||
})
|
||||
```
|
||||
All routes are under `https://kms.hanzo.ai/v1/kms/orgs/{org}/secrets`.
|
||||
|
||||
### Dynamic Secrets
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `GET` | `/v1/kms/orgs/{org}/secrets/{path}/{name}?env=` | Read a secret value + version |
|
||||
| `POST` | `/v1/kms/orgs/{org}/secrets` | Create/upsert (`{path,name,env,value}`) → `{ok,version}` |
|
||||
| `PATCH` | `/v1/kms/orgs/{org}/secrets/{path}/{name}` | Update (optimistic concurrency — see below) |
|
||||
| `DELETE` | `/v1/kms/orgs/{org}/secrets/{path}/{name}?env=` | Delete |
|
||||
|
||||
Auto-rotating credentials:
|
||||
|
||||
```typescript
|
||||
// PostgreSQL dynamic secret
|
||||
await kms.dynamicSecrets.create({
|
||||
name: 'postgres-readonly',
|
||||
type: 'postgresql',
|
||||
config: {
|
||||
host: 'db.example.com',
|
||||
port: 5432,
|
||||
database: 'myapp',
|
||||
adminUsername: 'admin',
|
||||
adminPassword: 'xxx'
|
||||
},
|
||||
ttl: '1h',
|
||||
maxTtl: '24h'
|
||||
})
|
||||
|
||||
// Lease a credential
|
||||
const lease = await kms.dynamicSecrets.lease('postgres-readonly')
|
||||
console.log(lease.username, lease.password) // Auto-generated, auto-rotated
|
||||
```
|
||||
|
||||
### Signing Keys (MPC)
|
||||
|
||||
Create keys backed by MPC threshold signatures:
|
||||
|
||||
```typescript
|
||||
// Create MPC-backed signing key
|
||||
const key = await kms.keys.create({
|
||||
name: 'ethereum-hot-wallet',
|
||||
keyType: 'secp256k1',
|
||||
signer: 'mpc',
|
||||
scheme: '2-of-3',
|
||||
policy: {
|
||||
maxDailyTransactions: 100,
|
||||
maxTransactionValue: '10000000000000000000' // 10 ETH
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Address:', key.ethAddress)
|
||||
|
||||
// Sign transaction
|
||||
const signature = await kms.keys.sign(key.id, {
|
||||
chain: 'ethereum',
|
||||
transaction: {
|
||||
to: '0x...',
|
||||
value: '1000000000000000000'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Environment Management
|
||||
|
||||
### Sync Environments
|
||||
### Read
|
||||
|
||||
```bash
|
||||
# CLI: Pull secrets to .env
|
||||
hanzo-kms pull --env production --output .env
|
||||
|
||||
# CLI: Push secrets from .env
|
||||
hanzo-kms push --env staging --input .env
|
||||
curl -sS "https://kms.hanzo.ai/v1/kms/orgs/hanzo/secrets/providers/OPENAI_API_KEY?env=production" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
### Environment Inheritance
|
||||
|
||||
```
|
||||
production
|
||||
└── staging (inherits, can override)
|
||||
└── development (inherits, can override)
|
||||
```json
|
||||
{ "secret": { "value": "<secret value>" }, "version": 3 }
|
||||
```
|
||||
|
||||
## Kubernetes Integration
|
||||
A root-path secret omits the folder segment: `…/secrets/STRIPE_SECRET_KEY?env=production`.
|
||||
|
||||
### Install Operator
|
||||
### Create
|
||||
|
||||
```bash
|
||||
helm repo add hanzo https://charts.hanzo.ai
|
||||
helm install hanzo-kms-operator hanzo/kms-operator
|
||||
curl -sS -X POST "https://kms.hanzo.ai/v1/kms/orgs/hanzo/secrets" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "path": "/providers", "name": "OPENAI_API_KEY", "env": "production", "value": "<new value>" }'
|
||||
```
|
||||
|
||||
### Inject Secrets
|
||||
```json
|
||||
{ "ok": true, "version": 1 }
|
||||
```
|
||||
|
||||
### Update (optimistic concurrency)
|
||||
|
||||
Updates require the current version, supplied either as an `If-Match` header or `version` in the body. A missing precondition returns `428 Precondition Required`; a stale version returns `409 Conflict` with the current version.
|
||||
|
||||
```bash
|
||||
curl -sS -X PATCH "https://kms.hanzo.ai/v1/kms/orgs/hanzo/secrets/providers/OPENAI_API_KEY?env=production" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "If-Match: 3" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "value": "<rotated value>" }'
|
||||
```
|
||||
|
||||
## Threshold signing keys
|
||||
|
||||
When KMS is wired to an [MPC](/docs/services/mpc) vault, it manages distributed signing keys whose private material is never assembled in one place:
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `POST` | `/v1/kms/keys/generate` | Distributed key generation (DKG) for a validator/signing key set |
|
||||
| `GET` | `/v1/kms/keys` · `/v1/kms/keys/{id}` | List / inspect key sets |
|
||||
| `POST` | `/v1/kms/keys/{id}/sign` | Threshold sign (e.g. `bls`) |
|
||||
| `POST` | `/v1/kms/keys/{id}/rotate` | Reshare / rotate shares |
|
||||
|
||||
These operations require an admin or MPC-scoped token. See [Hanzo MPC](/docs/services/mpc) for the underlying threshold scheme.
|
||||
|
||||
## Kubernetes integration
|
||||
|
||||
In a cluster, you never call the API directly — you declare a `KMSSecret` and the **kms-operator** logs in with a machine identity, reads the named keys, and writes a Kubernetes `Secret` you reference as normal:
|
||||
|
||||
```yaml
|
||||
apiVersion: secrets.hanzo.ai/v1
|
||||
kind: HanzoSecret
|
||||
apiVersion: secrets.lux.network/v1alpha1
|
||||
kind: KMSSecret
|
||||
metadata:
|
||||
name: api-secrets
|
||||
name: app-kms-sync
|
||||
namespace: hanzo
|
||||
spec:
|
||||
environment: production
|
||||
path: /api-keys
|
||||
target:
|
||||
name: api-secrets
|
||||
type: kubernetes.io/Opaque
|
||||
hostAPI: https://kms.hanzo.ai
|
||||
resyncInterval: 60
|
||||
authentication:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: app-kms-auth # K8s Secret holding clientId/clientSecret
|
||||
secretNamespace: hanzo
|
||||
secretsScope:
|
||||
projectSlug: hanzo # -> {org}
|
||||
envSlug: production # -> ?env=production
|
||||
secretsPath: /providers # -> {path}
|
||||
keys: # enumerated; no list endpoint
|
||||
- OPENAI_API_KEY
|
||||
- ANTHROPIC_API_KEY
|
||||
managedSecretReference:
|
||||
secretName: app-secrets
|
||||
secretNamespace: hanzo
|
||||
secretType: Opaque
|
||||
creationPolicy: Orphan
|
||||
```
|
||||
|
||||
## Access Control
|
||||
|
||||
### Roles
|
||||
|
||||
| Role | Permissions |
|
||||
|------|-------------|
|
||||
| Admin | Full access |
|
||||
| Developer | Read/write specific paths |
|
||||
| Viewer | Read-only access |
|
||||
| Machine | Programmatic access only |
|
||||
|
||||
### Policies
|
||||
|
||||
```typescript
|
||||
await kms.policies.create({
|
||||
name: 'frontend-team',
|
||||
rules: [
|
||||
{
|
||||
path: '/frontend/*',
|
||||
permissions: ['read', 'write']
|
||||
},
|
||||
{
|
||||
path: '/backend/*',
|
||||
permissions: ['read']
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## Audit Logging
|
||||
|
||||
All secret access is logged:
|
||||
|
||||
```typescript
|
||||
const logs = await kms.audit.list({
|
||||
startTime: '2024-01-01',
|
||||
endTime: '2024-01-31',
|
||||
actions: ['read', 'write', 'delete']
|
||||
})
|
||||
|
||||
for (const log of logs) {
|
||||
console.log({
|
||||
timestamp: log.timestamp,
|
||||
user: log.userId,
|
||||
action: log.action,
|
||||
path: log.secretPath,
|
||||
ip: log.ipAddress
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## MPC Signer Integration
|
||||
|
||||
Hanzo KMS integrates with Hanzo MPC for threshold signatures. MPC nodes run their own private BFT blockchain (ConsensusKV) with dual-certificate finality -- Ed25519 (classical) and ML-DSA-65 (post-quantum, FIPS 204).
|
||||
|
||||
```typescript
|
||||
// Configure MPC backend
|
||||
await kms.config.update({
|
||||
mpc: {
|
||||
endpoint: 'https://mpc.hanzo.ai',
|
||||
threshold: '3-of-5',
|
||||
kvBackend: 'consensus', // Private BFT blockchain (default)
|
||||
consensusChainId: 'm-chain',
|
||||
nodeEndpoints: [
|
||||
'https://mpc-node0.hanzo.ai',
|
||||
'https://mpc-node1.hanzo.ai',
|
||||
'https://mpc-node2.hanzo.ai',
|
||||
'https://mpc-node3.hanzo.ai',
|
||||
'https://mpc-node4.hanzo.ai'
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### TFHE-KMS Bridge
|
||||
|
||||
KMS includes a TFHE bridge for fully homomorphic encryption operations backed by MPC threshold decryption:
|
||||
|
||||
```typescript
|
||||
// Create TFHE key via MPC DKG
|
||||
const tfheKey = await kms.keys.create({
|
||||
name: 'encrypted-policy-key',
|
||||
keyType: 'tfhe-uint64',
|
||||
signer: 'mpc-tfhe',
|
||||
scheme: '3-of-5'
|
||||
})
|
||||
|
||||
// Encrypt data with public key (KMS-side, no MPC needed)
|
||||
const encrypted = await kms.tfhe.encrypt(tfheKey.id, { value: 1000000 })
|
||||
|
||||
// Threshold decrypt (requires t-of-n MPC shares)
|
||||
const decrypted = await kms.tfhe.decrypt(tfheKey.id, encrypted.ciphertext)
|
||||
```
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Endpoint | Backend | Description |
|
||||
|----------|---------|-------------|
|
||||
| `mpc.hanzo.ai` | Consensus | Managed MPC (3-of-5, dual-cert PQ finality) |
|
||||
| `mpc.lux.network` | Consensus | Decentralized MPC (same cluster, Lux subnet integration) |
|
||||
|
||||
See [MPC Consensus](/docs/services/mpc/consensus) for details on the private blockchain and [MPC Authentication](/docs/services/iam/mpc) for auth integration.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Secrets
|
||||
|
||||
- `POST /api/v1/secrets` - Create secret
|
||||
- `GET /api/v1/secrets` - List secrets
|
||||
- `GET /api/v1/secrets/:id` - Get secret
|
||||
- `PATCH /api/v1/secrets/:id` - Update secret
|
||||
- `DELETE /api/v1/secrets/:id` - Delete secret
|
||||
|
||||
### Keys
|
||||
|
||||
- `POST /api/v1/keys` - Create signing key
|
||||
- `GET /api/v1/keys/:id` - Get key info
|
||||
- `POST /api/v1/keys/:id/sign` - Sign with key
|
||||
|
||||
### Dynamic Secrets
|
||||
|
||||
- `POST /api/v1/dynamic-secrets` - Create dynamic secret
|
||||
- `POST /api/v1/dynamic-secrets/:id/lease` - Get credential lease
|
||||
- `DELETE /api/v1/dynamic-secrets/:id/lease/:leaseId` - Revoke lease
|
||||
See the [KMSSecret CRD reference](/docs/services/kms/kmssecret-crd) for the full spec.
|
||||
|
||||
## Next Steps
|
||||
|
||||
<Cards>
|
||||
<Card title="Service Integration" href="/docs/services/kms/service-integration">
|
||||
How all Hanzo services consume secrets from KMS
|
||||
</Card>
|
||||
<Card title="Universal Auth" href="/docs/services/kms/universal-auth">
|
||||
Machine Identity authentication for services
|
||||
<Card title="Machine Identities" href="/docs/services/kms/universal-auth">
|
||||
How services authenticate to KMS and refresh tokens
|
||||
</Card>
|
||||
<Card title="KMSSecret CRD" href="/docs/services/kms/kmssecret-crd">
|
||||
Kubernetes operator secret sync reference
|
||||
Complete Kubernetes sync reference
|
||||
</Card>
|
||||
<Card title="IAM Integration" href="/docs/services/iam/kms-integration">
|
||||
How IAM uses KMS for secret management
|
||||
<Card title="Service Integration" href="/docs/services/kms/service-integration">
|
||||
The pattern every Hanzo service uses to consume secrets
|
||||
</Card>
|
||||
<Card title="Hanzo MPC" href="/docs/services/mpc">
|
||||
Threshold signing backend for KMS-managed keys
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
@@ -27,8 +27,8 @@ metadata:
|
||||
app.kubernetes.io/component: secrets
|
||||
app.kubernetes.io/part-of: hanzo-platform
|
||||
spec:
|
||||
# KMS API endpoint (use in-cluster address)
|
||||
hostAPI: http://kms.hanzo.svc.cluster.local/api
|
||||
# KMS API endpoint
|
||||
hostAPI: https://kms.hanzo.ai
|
||||
|
||||
# How often to re-sync secrets (seconds)
|
||||
resyncInterval: 60
|
||||
@@ -38,15 +38,19 @@ spec:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
# K8s Secret containing clientId and clientSecret
|
||||
secretName: universal-auth-credentials
|
||||
secretName: app-kms-auth
|
||||
secretNamespace: hanzo
|
||||
secretsScope:
|
||||
# KMS project to read from
|
||||
projectSlug: secrets-639-c
|
||||
# KMS org to read from (matches the machine identity's owner)
|
||||
projectSlug: hanzo
|
||||
# KMS environment
|
||||
envSlug: prod
|
||||
# Path within the project
|
||||
envSlug: production
|
||||
# Path within the org
|
||||
secretsPath: /example
|
||||
# Exact secret names to sync — KMS has no list endpoint, enumerate them
|
||||
keys:
|
||||
- EXAMPLE_API_KEY
|
||||
- EXAMPLE_DB_URL
|
||||
|
||||
# Target K8s Secret
|
||||
managedSecretReference:
|
||||
@@ -68,8 +72,8 @@ spec:
|
||||
|---|---|
|
||||
| Type | `string` |
|
||||
| Required | Yes |
|
||||
| Description | URL of the KMS API. Always use the in-cluster address for security. |
|
||||
| Example | `http://kms.hanzo.svc.cluster.local/api` |
|
||||
| Description | Base URL of the KMS API. The operator appends `/v1/kms`. In-cluster you may use `http://kms.hanzo.svc.cluster.local`. |
|
||||
| Example | `https://kms.hanzo.ai` |
|
||||
|
||||
### `spec.resyncInterval`
|
||||
|
||||
@@ -98,9 +102,10 @@ The referenced Secret must contain two keys:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `projectSlug` | `string` | Yes | KMS project slug |
|
||||
| `envSlug` | `string` | Yes | Environment slug (`prod`, `staging`, `dev`) |
|
||||
| `secretsPath` | `string` | Yes | Path within the project (e.g., `/chat`, `/`) |
|
||||
| `projectSlug` | `string` | Yes | KMS org slug — maps to `{org}` and must match the identity's `owner` |
|
||||
| `envSlug` | `string` | Yes | Environment — maps to `?env=` (`production`, `staging`, `dev`) |
|
||||
| `secretsPath` | `string` | Yes | Path within the org (e.g., `/providers`, `/`) |
|
||||
| `keys` | `string[]` | Yes | Exact secret names to sync (1–128). KMS has no list endpoint — enumerate every key |
|
||||
|
||||
### `spec.managedSecretReference`
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ Every Hanzo service retrieves its secrets from KMS rather than storing them in e
|
||||
│ /bootnode /studio │
|
||||
│ /app /bot-hub │
|
||||
│ /crawl /auto │
|
||||
│ /lux-chat │
|
||||
└──────────┬──────────────────────────────────┬────────────────────┘
|
||||
│ │
|
||||
Universal Auth Machine Identity
|
||||
@@ -48,7 +47,7 @@ Every Hanzo service retrieves its secrets from KMS rather than storing them in e
|
||||
│ pricing-secrets search-secrets vector-secrets │
|
||||
│ bootnode-secrets studio-secrets bot-hub-secrets │
|
||||
│ hanzo-app-secrets crawl-secrets auto-secrets │
|
||||
│ lux-chat-secrets iam-secrets │
|
||||
│ iam-secrets │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -91,7 +90,7 @@ metadata:
|
||||
app.kubernetes.io/component: secrets
|
||||
app.kubernetes.io/part-of: hanzo-platform
|
||||
spec:
|
||||
hostAPI: http://kms.hanzo.svc.cluster.local/api
|
||||
hostAPI: http://kms.hanzo.svc.cluster.local
|
||||
resyncInterval: 60
|
||||
authentication:
|
||||
universalAuth:
|
||||
@@ -113,7 +112,7 @@ spec:
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `hostAPI` | Yes | KMS API URL. Use in-cluster address `http://kms.hanzo.svc.cluster.local/api` |
|
||||
| `hostAPI` | Yes | KMS API URL. Use in-cluster address `http://kms.hanzo.svc.cluster.local` |
|
||||
| `resyncInterval` | Yes | How often (seconds) to re-fetch secrets. Default: `60` |
|
||||
| `authentication.universalAuth.credentialsRef` | Yes | K8s Secret containing `clientId` and `clientSecret` |
|
||||
| `authentication.universalAuth.secretsScope.projectSlug` | Yes | KMS project slug |
|
||||
@@ -146,7 +145,6 @@ Every service in the Hanzo platform uses this pattern. Here is the full list:
|
||||
| Hanzo App | `/app` | `hanzo-app-secrets` | Universal Auth |
|
||||
| Crawl | `/crawl` | `crawl-secrets` | Universal Auth |
|
||||
| Auto | `/auto` | `auto-secrets` | Universal Auth |
|
||||
| Lux Chat | `/lux-chat` | `lux-chat-secrets` | Universal Auth |
|
||||
|
||||
## Example: Adding Secrets for a New Service
|
||||
|
||||
@@ -167,7 +165,7 @@ metadata:
|
||||
app.kubernetes.io/component: secrets
|
||||
app.kubernetes.io/part-of: hanzo-platform
|
||||
spec:
|
||||
hostAPI: http://kms.hanzo.svc.cluster.local/api
|
||||
hostAPI: http://kms.hanzo.svc.cluster.local
|
||||
resyncInterval: 60
|
||||
authentication:
|
||||
universalAuth:
|
||||
|
||||
@@ -49,7 +49,7 @@ stringData:
|
||||
clientSecret: "<machine-identity-client-secret>"
|
||||
```
|
||||
|
||||
**Access scope**: `secrets-639-c` project, `prod` environment, all paths.
|
||||
**Access scope**: the shared services org, `production` environment, all paths.
|
||||
|
||||
### Dedicated Identity (IAM)
|
||||
|
||||
@@ -80,24 +80,26 @@ stringData:
|
||||
5. Copy the generated `clientId` and `clientSecret`
|
||||
6. Under **Projects**, grant access to the appropriate project with the required role (typically **Viewer** for read-only)
|
||||
|
||||
### Via API
|
||||
### Via Hanzo IAM
|
||||
|
||||
A machine identity is a client-credentials application in [Hanzo IAM](/docs/services/iam). Register it (which mints a `clientId`/`clientSecret`), then grant it read access to the appropriate KMS org. The credentials are what your workload exchanges for a token.
|
||||
|
||||
### Exchange credentials for a token
|
||||
|
||||
Once you have a `clientId`/`clientSecret`, log in to KMS:
|
||||
|
||||
```bash
|
||||
# Create identity
|
||||
curl -X POST https://kms.hanzo.ai/api/v1/identities \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-d '{"name": "my-service", "role": "member"}'
|
||||
|
||||
# Enable Universal Auth
|
||||
curl -X POST https://kms.hanzo.ai/api/v1/auth/universal-auth/identities/$IDENTITY_ID \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN"
|
||||
|
||||
# Grant project access
|
||||
curl -X POST https://kms.hanzo.ai/api/v1/projects/$PROJECT_ID/identity-memberships \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-d '{"identityId": "$IDENTITY_ID", "role": "viewer"}'
|
||||
curl -X POST https://kms.hanzo.ai/v1/kms/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}"
|
||||
```
|
||||
|
||||
```json
|
||||
{ "accessToken": "<JWT>", "expiresIn": 86400, "tokenType": "Bearer" }
|
||||
```
|
||||
|
||||
Use the returned `accessToken` as `Authorization: Bearer <accessToken>` on every secret read.
|
||||
|
||||
## Storing Credentials in K8s
|
||||
|
||||
After creating the Machine Identity, store the credentials as a K8s Secret:
|
||||
@@ -125,7 +127,7 @@ spec:
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Token type | JWT |
|
||||
| Default TTL | 7200 seconds (2 hours) |
|
||||
| Default TTL | 86400 seconds (24 hours) |
|
||||
| Max TTL | Configurable per identity |
|
||||
| Refresh | Automatic by kms-operator |
|
||||
| Revocation | Delete the Machine Identity or its Universal Auth config |
|
||||
|
||||
@@ -1,486 +1,214 @@
|
||||
---
|
||||
title: API Reference
|
||||
description: Complete REST API reference for Hanzo MPC
|
||||
description: REST API reference for the Hanzo MPC custody service
|
||||
---
|
||||
|
||||
# API Reference
|
||||
|
||||
All endpoints are served at `https://mpc.hanzo.ai` and require a Bearer token from Hanzo IAM.
|
||||
The Hanzo MPC custody API is served at `https://mpc.hanzo.ai` under the `/v1` prefix. Signing is session-based: create a wallet, open a session, then sign.
|
||||
|
||||
## Authentication
|
||||
|
||||
Every request must include an `Authorization` header:
|
||||
Every request (except health) carries a bearer token issued by [Hanzo IAM](/docs/services/iam):
|
||||
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
Authorization: Bearer <iam-access-token>
|
||||
```
|
||||
|
||||
Obtain tokens from Hanzo IAM at `https://hanzo.id`. See [IAM documentation](/docs/services/iam) for details.
|
||||
A service holding an external OIDC token can exchange it for an MPC token (the issuer must be allow-listed):
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/v1/auth/oidc \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "token": "<external-oidc-access-token>" }'
|
||||
```
|
||||
|
||||
Machine callers may instead present a long-lived API key via `X-API-Key`. Roles (`owner`, `admin`, `signer`, `viewer`) and per-route permissions (e.g. `mpc:sign`) gate access.
|
||||
|
||||
## Base URL
|
||||
|
||||
| Environment | URL |
|
||||
|-------------|-----|
|
||||
| Production | `https://mpc.hanzo.ai` |
|
||||
| Staging | `https://stg.mpc.hanzo.ai` |
|
||||
|
||||
---
|
||||
|
||||
## POST /api/wallets
|
||||
## POST /v1/mpc/wallets
|
||||
|
||||
Create a new MPC wallet. This triggers a distributed key generation (DKG) ceremony across the configured number of parties.
|
||||
Create a wallet via distributed key generation (DKG) across the configured parties.
|
||||
|
||||
### Request
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/wallets \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"protocol": "cggmp21",
|
||||
"curve": "secp256k1",
|
||||
"threshold": 2,
|
||||
"parties": 3,
|
||||
"label": "eth-hot-wallet",
|
||||
"metadata": {
|
||||
"department": "treasury",
|
||||
"owner": "[email protected]"
|
||||
}
|
||||
}'
|
||||
-d '{ "protocol": "cggmp21" }'
|
||||
```
|
||||
|
||||
### Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `protocol` | string | Yes | Cryptographic protocol. One of: `cggmp21`, `frost`, `lsss`, `tfhe`, `corona`, `quasar`, `bls`, `doerner` |
|
||||
| `curve` | string | Yes | Elliptic curve. One of: `secp256k1`, `ed25519`, `ristretto`, `bls12-381`, `p256`, `sr25519` |
|
||||
| `threshold` | integer | Yes | Minimum signers required (t) |
|
||||
| `parties` | integer | Yes | Total number of key share holders (n) |
|
||||
| `label` | string | No | Human-readable wallet label |
|
||||
| `metadata` | object | No | Arbitrary key-value metadata |
|
||||
| `protocol` | string | Yes | `cggmp21` (ECDSA) or `frost` (Schnorr) |
|
||||
|
||||
### Protocol-Curve Compatibility
|
||||
|
||||
| Protocol | Compatible Curves |
|
||||
|----------|------------------|
|
||||
| `cggmp21` | `secp256k1`, `p256` |
|
||||
| `frost` | `ed25519`, `ristretto`, `secp256k1` |
|
||||
| `lsss` | Any curve |
|
||||
| `tfhe` | Lattice (internal) |
|
||||
| `corona` | Module-LWE (internal) |
|
||||
| `quasar` | `bls12-381` |
|
||||
| `bls` | `bls12-381` |
|
||||
| `doerner` | `secp256k1` |
|
||||
For a wallet in a specific vault with an explicit key type, use `POST /v1/vaults/{vaultId}/wallets` with `{ "name", "key_type", "protocol" }`, where `protocol` is `cggmp21`, `frost`, `lss`, or `sr25519`.
|
||||
|
||||
### Response `201 Created`
|
||||
|
||||
```json
|
||||
{
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"walletId": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
|
||||
"protocol": "cggmp21",
|
||||
"curve": "secp256k1",
|
||||
"threshold": 2,
|
||||
"parties": 3,
|
||||
"public_key": "04a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
|
||||
"addresses": {
|
||||
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
|
||||
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
||||
"bitcoin_legacy": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
|
||||
},
|
||||
"label": "eth-hot-wallet",
|
||||
"metadata": {
|
||||
"department": "treasury",
|
||||
"owner": "[email protected]"
|
||||
},
|
||||
"keygen_duration_ms": 22,
|
||||
"created_at": "2026-02-23T10:30:00Z"
|
||||
"threshold": "2-of-3",
|
||||
"isDefault": true,
|
||||
"status": "active",
|
||||
"createdAt": "2026-06-26T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Response Fields
|
||||
---
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `wallet_id` | string | Unique wallet identifier (prefixed `w_`) |
|
||||
| `protocol` | string | Protocol used for DKG |
|
||||
| `curve` | string | Elliptic curve |
|
||||
| `threshold` | integer | Signing threshold (t) |
|
||||
| `parties` | integer | Total parties (n) |
|
||||
| `public_key` | string | Hex-encoded public key |
|
||||
| `addresses` | object | Derived chain addresses |
|
||||
| `label` | string | Wallet label |
|
||||
| `metadata` | object | Custom metadata |
|
||||
| `keygen_duration_ms` | integer | DKG ceremony duration |
|
||||
| `created_at` | string | ISO 8601 timestamp |
|
||||
## GET /v1/wallets/{id}/addresses
|
||||
|
||||
### Errors
|
||||
Return the chain addresses derived from a wallet's public key.
|
||||
|
||||
| Status | Code | Description |
|
||||
|--------|------|-------------|
|
||||
| 400 | `invalid_protocol` | Unsupported protocol name |
|
||||
| 400 | `invalid_curve` | Unsupported curve or incompatible with protocol |
|
||||
| 400 | `invalid_threshold` | Threshold must be `>= 1` and `≤ parties` |
|
||||
| 401 | `unauthorized` | Missing or invalid Bearer token |
|
||||
| 403 | `forbidden` | Token lacks `mpc:wallets:create` scope |
|
||||
| 500 | `keygen_failed` | DKG ceremony failed (check node health) |
|
||||
```bash
|
||||
curl https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/addresses \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
|
||||
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
||||
"solana": "7nYBzS3Yz5mY8Qm4iXqz1bWmHnf8QpHq3xWv6Yc9aBcD",
|
||||
"xrp": "rPdvC6ccq8hCdPKSPJkPmyZ4Mi1oG2FFkT",
|
||||
"ton": "EQByY8B0iZ0a..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /api/sign
|
||||
## POST /v1/mpc/wallets/{id}/sessions
|
||||
|
||||
Sign a message or transaction hash using the specified wallet. The coordinator selects `t` available nodes and runs the threshold signing protocol.
|
||||
Open a signing session. A session scopes and authorizes the signatures that follow, and is consumed atomically — which makes every signature an explicit, auditable action.
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets/$WALLET_ID/sessions \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
```json
|
||||
{ "sessionId": "s_a1b2c3d4", "walletId": "w_3fa85f64-...", "expiresAt": "2026-06-26T10:45:00Z" }
|
||||
```
|
||||
|
||||
List or revoke sessions with `GET /v1/mpc/wallets/{id}/sessions` and `DELETE /v1/mpc/wallets/{id}/sessions/{sessionId}`.
|
||||
|
||||
---
|
||||
|
||||
## POST /v1/mpc/sign
|
||||
|
||||
Threshold-sign a message hash. The coordinator selects `t` available nodes and runs the signing protocol. Requires an open `sessionId`.
|
||||
|
||||
### Request
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/sign \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/sign \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"walletId": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"sessionId": "s_a1b2c3d4",
|
||||
"message": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"encoding": "hex"
|
||||
}'
|
||||
```
|
||||
|
||||
### Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `wallet_id` | string | Yes | Target wallet ID |
|
||||
| `walletId` | string | Yes | Target wallet |
|
||||
| `sessionId` | string | Yes | An open signing session |
|
||||
| `message` | string | Yes | Message or transaction hash to sign |
|
||||
| `encoding` | string | No | Message encoding: `hex` (default), `base64`, `utf8` |
|
||||
| `signers` | integer[] | No | Specific signer indices to use. If omitted, coordinator selects automatically |
|
||||
| `metadata` | object | No | Audit metadata (e.g., transaction context) |
|
||||
| `encoding` | string | No | `hex` (default) or `base64` |
|
||||
|
||||
### Response `200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"signature": {
|
||||
"r": "0x1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b",
|
||||
"s": "0x4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e",
|
||||
"v": 27
|
||||
},
|
||||
"signers": [1, 3],
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"protocol": "cggmp21",
|
||||
"duration_ms": 45,
|
||||
"timestamp": "2026-02-23T10:31:00Z"
|
||||
"signature": "0x1a2b3c...4d5e6f...1b",
|
||||
"r": "0x1a2b3c4d5e6f...",
|
||||
"s": "0x4d5e6f1a2b3c..."
|
||||
}
|
||||
```
|
||||
|
||||
### Signature Format by Protocol
|
||||
### Signature format by protocol
|
||||
|
||||
| Protocol | Signature Fields |
|
||||
|----------|-----------------|
|
||||
| CGGMP21 | `r`, `s`, `v` (ECDSA) |
|
||||
| FROST | `R`, `s` (Schnorr) |
|
||||
| BLS | `signature` (compressed G1 point) |
|
||||
| Quasar | `bls_signature`, `pq_signature` (dual) |
|
||||
| Doerner | `r`, `s`, `v` (ECDSA) |
|
||||
| Protocol | Fields |
|
||||
|----------|--------|
|
||||
| CGGMP21 | `r`, `s`, and a recovery byte (ECDSA) |
|
||||
| FROST | 64-byte Schnorr `signature` |
|
||||
| BLS | compressed G1 point `signature` |
|
||||
|
||||
### Errors
|
||||
|
||||
| Status | Code | Description |
|
||||
|--------|------|-------------|
|
||||
| 400 | `invalid_message` | Message is empty or malformed |
|
||||
| 400 | `invalid_encoding` | Unsupported encoding format |
|
||||
| 404 | `wallet_not_found` | Wallet ID does not exist |
|
||||
| 401 | `unauthorized` | Missing or invalid Bearer token |
|
||||
| 403 | `forbidden` | Token lacks `mpc:sign` scope |
|
||||
| 503 | `insufficient_signers` | Fewer than `t` nodes are available |
|
||||
| 500 | `signing_failed` | Protocol execution failed |
|
||||
| 500 | `identifiable_abort` | Malicious signer detected (includes blame proof) |
|
||||
|
||||
### Identifiable Abort Response
|
||||
|
||||
When a signer deviates from the protocol:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "identifiable_abort",
|
||||
"blame": {
|
||||
"party_index": 2,
|
||||
"proof": "base64-encoded-blame-proof",
|
||||
"reason": "invalid_commitment"
|
||||
},
|
||||
"timestamp": "2026-02-23T10:31:00Z"
|
||||
}
|
||||
```
|
||||
For settlement workflows with an explicit reason and policy check, use `POST /v1/mpc/settlement/sign` with `{ "walletId", "sessionId", "payload", "reason" }`.
|
||||
|
||||
---
|
||||
|
||||
## POST /api/reshare
|
||||
## POST /v1/wallets/{id}/reshare
|
||||
|
||||
Perform a proactive share refresh. This generates new shares for the same underlying key, optionally changing the threshold or party count. The public key and all derived addresses remain unchanged.
|
||||
|
||||
### Request
|
||||
Proactive share refresh. Generates new shares for the same key, optionally changing the participant set or threshold. The public key and every derived address are unchanged.
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/reshare \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/reshare \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"new_threshold": 3,
|
||||
"new_parties": 5
|
||||
}'
|
||||
-d '{ "new_threshold": 3, "new_participants": ["node-0","node-1","node-2","node-3","node-4"] }'
|
||||
```
|
||||
|
||||
### Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `wallet_id` | string | Yes | Target wallet ID |
|
||||
| `new_threshold` | integer | No | New signing threshold. Defaults to current |
|
||||
| `new_parties` | integer | No | New total parties. Defaults to current |
|
||||
| `reason` | string | No | Audit reason (e.g., `scheduled_rotation`, `suspected_compromise`) |
|
||||
|
||||
### Response `200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"previous": {
|
||||
"threshold": 2,
|
||||
"parties": 3,
|
||||
"epoch": 1
|
||||
},
|
||||
"current": {
|
||||
"threshold": 3,
|
||||
"parties": 5,
|
||||
"epoch": 2
|
||||
},
|
||||
"public_key": "04a1b2c3d4e5f6...",
|
||||
"addresses_unchanged": true,
|
||||
"reshare_duration_ms": 180,
|
||||
"timestamp": "2026-02-23T10:32:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
|
||||
| Status | Code | Description |
|
||||
|--------|------|-------------|
|
||||
| 400 | `invalid_threshold` | New threshold exceeds new party count |
|
||||
| 404 | `wallet_not_found` | Wallet ID does not exist |
|
||||
| 401 | `unauthorized` | Missing or invalid Bearer token |
|
||||
| 403 | `forbidden` | Token lacks `mpc:reshare` scope |
|
||||
| 409 | `reshare_in_progress` | Another reshare is already running for this wallet |
|
||||
| 500 | `reshare_failed` | Protocol execution failed |
|
||||
|
||||
---
|
||||
|
||||
## GET /api/wallets
|
||||
|
||||
List all wallets accessible to the authenticated user.
|
||||
|
||||
### Request
|
||||
|
||||
```bash
|
||||
curl https://mpc.hanzo.ai/api/wallets \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN"
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `limit` | integer | 20 | Results per page (max 100) |
|
||||
| `offset` | integer | 0 | Pagination offset |
|
||||
| `protocol` | string | - | Filter by protocol |
|
||||
| `label` | string | - | Filter by label (substring match) |
|
||||
|
||||
### Response `200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"wallets": [
|
||||
{
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"protocol": "cggmp21",
|
||||
"curve": "secp256k1",
|
||||
"threshold": 2,
|
||||
"parties": 3,
|
||||
"public_key": "04a1b2c3d4e5f6...",
|
||||
"addresses": {
|
||||
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
|
||||
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
|
||||
},
|
||||
"label": "eth-hot-wallet",
|
||||
"epoch": 2,
|
||||
"created_at": "2026-02-23T10:30:00Z",
|
||||
"last_reshare": "2026-02-23T10:32:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"limit": 20,
|
||||
"offset": 0
|
||||
}
|
||||
{ "walletId": "w_3fa85f64-...", "newThreshold": 3, "keyType": "secp256k1", "status": "completed" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /api/wallets/:id
|
||||
## GET /v1/status · GET /v1/info
|
||||
|
||||
Get detailed information about a specific wallet.
|
||||
|
||||
### Request
|
||||
Cluster status and capabilities.
|
||||
|
||||
```bash
|
||||
curl https://mpc.hanzo.ai/api/wallets/w_3fa85f64-5717-4562-b3fc-2c963f66afa6 \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN"
|
||||
curl https://mpc.hanzo.ai/v1/status -H "Authorization: Bearer $IAM_ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
### Response `200 OK`
|
||||
```json
|
||||
{ "status": "healthy", "nodes": { "total": 5, "healthy": 5 }, "threshold": "3-of-5", "transport": "nats" }
|
||||
```
|
||||
|
||||
## GET /healthz
|
||||
|
||||
Liveness probe, no authentication.
|
||||
|
||||
```bash
|
||||
curl https://mpc.hanzo.ai/healthz
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"protocol": "cggmp21",
|
||||
"curve": "secp256k1",
|
||||
"threshold": 2,
|
||||
"parties": 3,
|
||||
"public_key": "04a1b2c3d4e5f6...",
|
||||
"addresses": {
|
||||
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
|
||||
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
||||
"bitcoin_legacy": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
|
||||
"bitcoin_taproot": "bc1p...",
|
||||
"solana": "7nYB...",
|
||||
"cosmos": "cosmos1...",
|
||||
"lux_c": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
|
||||
"lux_x": "X-lux1...",
|
||||
"lux_p": "P-lux1..."
|
||||
},
|
||||
"label": "eth-hot-wallet",
|
||||
"metadata": {
|
||||
"department": "treasury",
|
||||
"owner": "[email protected]"
|
||||
},
|
||||
"epoch": 2,
|
||||
"node_status": [
|
||||
{"index": 0, "healthy": true, "last_seen": "2026-02-23T10:35:00Z"},
|
||||
{"index": 1, "healthy": true, "last_seen": "2026-02-23T10:35:00Z"},
|
||||
{"index": 2, "healthy": true, "last_seen": "2026-02-23T10:35:00Z"}
|
||||
],
|
||||
"signing_count": 142,
|
||||
"last_signed": "2026-02-23T10:34:00Z",
|
||||
"created_at": "2026-02-23T10:30:00Z",
|
||||
"last_reshare": "2026-02-23T10:32:00Z"
|
||||
}
|
||||
{ "status": "ok", "node_id": "node-0", "connected_peers": 4, "threshold": 3 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /health
|
||||
|
||||
Health check endpoint. No authentication required.
|
||||
|
||||
### Request
|
||||
|
||||
```bash
|
||||
curl https://mpc.hanzo.ai/health
|
||||
```
|
||||
|
||||
### Response `200 OK`
|
||||
## Error format
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"version": "1.4.0",
|
||||
"nodes": {
|
||||
"total": 3,
|
||||
"healthy": 3,
|
||||
"degraded": 0,
|
||||
"offline": 0
|
||||
},
|
||||
"nats": "connected",
|
||||
"consul": "connected",
|
||||
"uptime_seconds": 864000,
|
||||
"timestamp": "2026-02-23T10:35:00Z"
|
||||
}
|
||||
{ "error": "insufficient_signers", "message": "Fewer than t nodes are available", "request_id": "req_abc123" }
|
||||
```
|
||||
|
||||
### Status Values
|
||||
| Status | Code | Meaning |
|
||||
|--------|------|---------|
|
||||
| 400 | `invalid_protocol` / `invalid_encoding` | Bad request input |
|
||||
| 401 | `unauthorized` | Missing or invalid bearer token |
|
||||
| 403 | `forbidden` | Token lacks the required role/permission |
|
||||
| 404 | `wallet_not_found` | Unknown wallet |
|
||||
| 409 | `reshare_in_progress` | A reshare is already running for this wallet |
|
||||
| 503 | `insufficient_signers` | Fewer than `t` nodes available |
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| `healthy` | All nodes operational, messaging and discovery connected |
|
||||
| `degraded` | Some nodes offline but threshold still achievable |
|
||||
| `unhealthy` | Fewer healthy nodes than minimum threshold |
|
||||
|
||||
---
|
||||
|
||||
## Error Response Format
|
||||
|
||||
All errors follow a consistent structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "error_code",
|
||||
"message": "Human-readable description of what went wrong",
|
||||
"details": {},
|
||||
"request_id": "req_abc123",
|
||||
"timestamp": "2026-02-23T10:35:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Rate Limits
|
||||
|
||||
| Endpoint | Limit | Window |
|
||||
|----------|-------|--------|
|
||||
| `POST /api/wallets` | 10 | per minute |
|
||||
| `POST /api/sign` | 100 | per minute |
|
||||
| `POST /api/reshare` | 5 | per minute |
|
||||
| `GET /api/wallets` | 60 | per minute |
|
||||
| `GET /health` | 300 | per minute |
|
||||
|
||||
Rate limit headers are included in every response:
|
||||
|
||||
```
|
||||
X-RateLimit-Limit: 100
|
||||
X-RateLimit-Remaining: 97
|
||||
X-RateLimit-Reset: 1708689360
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
List endpoints support cursor-based pagination:
|
||||
|
||||
```bash
|
||||
# First page
|
||||
curl "https://mpc.hanzo.ai/api/wallets?limit=10"
|
||||
|
||||
# Next page
|
||||
curl "https://mpc.hanzo.ai/api/wallets?limit=10&offset=10"
|
||||
```
|
||||
|
||||
## Webhook Notifications
|
||||
|
||||
Configure webhooks to receive real-time events:
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/webhooks \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"url": "https://example.com/webhooks/mpc",
|
||||
"events": ["wallet.created", "sign.completed", "sign.failed", "reshare.completed"],
|
||||
"secret": "whsec_your_webhook_secret"
|
||||
}'
|
||||
```
|
||||
|
||||
### Event Types
|
||||
|
||||
| Event | Trigger |
|
||||
|-------|---------|
|
||||
| `wallet.created` | DKG ceremony completed |
|
||||
| `sign.completed` | Signature produced |
|
||||
| `sign.failed` | Signing failed or aborted |
|
||||
| `reshare.completed` | Share refresh completed |
|
||||
| `reshare.failed` | Share refresh failed |
|
||||
| `node.offline` | An MPC node became unreachable |
|
||||
| `node.recovered` | An offline node came back |
|
||||
When a signer deviates, signing aborts with an identifiable-abort error that names the offending party and includes a signed blame proof.
|
||||
|
||||
@@ -34,7 +34,7 @@ A block is finalized **only** when both signature types reach the t-of-n thresho
|
||||
| Security level | NIST Level 3 (192-bit classical equivalent) |
|
||||
| Package | `github.com/luxfi/crypto/mldsa` |
|
||||
|
||||
The same ML-DSA implementation is used by the [Lux Network](/docs/services/lux) mainnet precompiles for on-chain post-quantum signature verification.
|
||||
ML-DSA is the NIST-standardized (FIPS 204) post-quantum digital signature scheme used for on-chain and consensus signature verification.
|
||||
|
||||
## Consensus Protocol
|
||||
|
||||
@@ -214,18 +214,12 @@ identity:
|
||||
| **External dependency** | None (self-sovereign) | Consul servers | NATS servers |
|
||||
| **Post-quantum** | ML-DSA-65 dual-cert | No | No |
|
||||
| **Finality model** | Dual-cert threshold | Raft leader commit | JetStream ack |
|
||||
| **Latency** | <100ms (typical) | ~50ms | ~10ms |
|
||||
| **Latency** | <100ms (typical) | ~50ms | ~10ms |
|
||||
| **Trust model** | Same as MPC threshold | Trust Consul cluster | Trust NATS cluster |
|
||||
| **State integrity** | Blockchain (hash chain) | Raft log | Stream sequence |
|
||||
|
||||
The key advantage: ConsensusKV uses the **same trust model** as MPC signing. If you trust 3-of-5 nodes to sign transactions, you can trust 3-of-5 nodes to finalize state -- no additional infrastructure needed.
|
||||
|
||||
## Lux Network Integration
|
||||
## Post-quantum cryptography
|
||||
|
||||
ConsensusKV uses the same post-quantum crypto library as the Lux Network:
|
||||
|
||||
- **ML-DSA precompile**: `lux/precompile/mldsa/` -- on-chain PQ signature verification
|
||||
- **Quasar consensus**: `lux/consensus/protocol/quasar/` -- dual BLS+Corona threshold finality
|
||||
- **Shared crypto**: `github.com/luxfi/crypto/mldsa` -- ML-DSA-44/65/87 implementation
|
||||
|
||||
The MPC private chain can optionally register as a **Lux subnet** (M-Chain or T-Chain) for network-level integration and cross-chain state anchoring.
|
||||
ConsensusKV's dual-certificate finality combines a classical Ed25519 certificate with a post-quantum ML-DSA-65 certificate, so cluster state stays verifiable even against a quantum adversary. The ML-DSA implementation supports the FIPS 204 parameter sets (ML-DSA-44/65/87); the cluster uses ML-DSA-65 (NIST Level 3) by default.
|
||||
|
||||
@@ -265,14 +265,11 @@ tls:
|
||||
# Ingress (dual endpoints)
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
className: hanzo
|
||||
hosts:
|
||||
- host: mpc.hanzo.ai
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
- host: mpc.lux.network
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
|
||||
# Monitoring
|
||||
metrics:
|
||||
@@ -627,7 +624,7 @@ If a node is permanently lost but the cluster still has `t` healthy nodes:
|
||||
4. Old shares from the lost node are automatically invalidated
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/reshare \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/reshare \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
||||
@@ -1,317 +1,204 @@
|
||||
---
|
||||
title: Hanzo MPC
|
||||
description: Enterprise-grade multi-party computation for digital asset custody with blockchain-native consensus, dual-certificate post-quantum finality, 8 cryptographic protocols, and configurable t-of-n thresholds.
|
||||
description: Threshold multi-party signing for digital asset custody — distributed key generation, t-of-n signing, and proactive share refresh with post-quantum consensus.
|
||||
---
|
||||
|
||||
# Hanzo MPC
|
||||
|
||||
Hanzo MPC is a self-sovereign wallet infrastructure for enterprise digital asset custody. It eliminates single points of failure by splitting private keys into shares distributed across independent nodes, requiring a configurable threshold of cooperating parties to produce valid signatures.
|
||||
Hanzo MPC is wallet infrastructure for enterprise digital asset custody. Private keys are never assembled in one place — they are split into shares held by independent nodes, and a configurable threshold of nodes must cooperate to produce a valid signature. Think of it as a self-hosted alternative to Fireblocks: you own the infrastructure, control the key shares, and operate under your own policies.
|
||||
|
||||
MPC nodes form their own **private BFT blockchain** for cluster state management, with **dual-certificate finality** combining Ed25519 (classical) and ML-DSA-65 (post-quantum, FIPS 204) signatures. Think of it as a self-hosted alternative to Fireblocks -- you own the infrastructure, control the key shares, and operate under your own security policies.
|
||||
| | |
|
||||
|---|---|
|
||||
| **Custody API** | `https://mpc.hanzo.ai` |
|
||||
| **API prefix** | `/v1` |
|
||||
| **Auth** | Hanzo IAM (OIDC) bearer tokens |
|
||||
| **Default threshold** | t-of-n, default t = ⌊n/2⌋ + 1 |
|
||||
| **Source** | [github.com/hanzoai/mpc](https://github.com/hanzoai/mpc) |
|
||||
|
||||
## Key Properties
|
||||
|
||||
- **No single point of failure**: No individual node ever holds a complete private key
|
||||
- **Blockchain-native consensus**: MPC nodes are validators on their own private BFT chain
|
||||
- **Dual-certificate finality**: Ed25519 + ML-DSA-65 (NIST Level 3) -- quantum-safe state management
|
||||
- **Configurable thresholds**: Any t-of-n scheme (2-of-3, 3-of-5, 5-of-9, etc.)
|
||||
- **Identifiable aborts**: Malicious participants are detected and attributed
|
||||
- **Proactive share refresh**: Rotate key shares without changing the public key or address
|
||||
- **Multi-chain**: 16+ blockchain networks from a single key management plane
|
||||
- **8 cryptographic protocols**: From production ECDSA to post-quantum lattice signatures
|
||||
- **17 zero-knowledge proof systems**: Comprehensive verification at every protocol stage
|
||||
- **Encrypted at rest**: AES-256-GCM for all stored key shares
|
||||
- **KMS-first key management**: Identity and consensus keys stored in KMS/HSM when configured
|
||||
- **No single point of failure** — no node ever holds a complete private key
|
||||
- **Distributed key generation (DKG)** — keys are generated collaboratively; no dealer ever sees the whole key
|
||||
- **Configurable thresholds** — any t-of-n scheme (2-of-3, 3-of-5, 5-of-9, …)
|
||||
- **Proactive share refresh** — rotate shares without changing the public key or any derived address
|
||||
- **Identifiable aborts** — a deviating participant is detected and attributed
|
||||
- **Multi-chain** — one key plane derives addresses for Bitcoin, Ethereum/EVM, Solana, and more
|
||||
- **Encrypted at rest** — key shares are stored AES-256-GCM encrypted; the encryption key is supplied by [Hanzo KMS](/docs/services/kms)/HSM, never plaintext config
|
||||
- **Post-quantum consensus** — cluster state is managed by a private BFT chain with dual-certificate finality (Ed25519 + ML-DSA-65, FIPS 204)
|
||||
|
||||
## Supported Protocols
|
||||
|
||||
Hanzo MPC builds on the `luxfi/threshold` engine. Four threshold schemes are in production:
|
||||
|
||||
| Protocol | Type | Curve | Best for |
|
||||
|----------|------|-------|----------|
|
||||
| **CGGMP21** | Threshold ECDSA | secp256k1 | Bitcoin, Ethereum, EVM chains |
|
||||
| **FROST** | Threshold Schnorr | secp256k1 (BIP-340), Ristretto255 | Bitcoin Taproot, Ed25519/sr25519 chains |
|
||||
| **LSS** | Linear secret sharing | secp256k1 | Dynamic resharing, blinded signing |
|
||||
| **BLS** | Threshold aggregate | BLS12-381 | Validator keys, aggregate signatures |
|
||||
|
||||
Key types accepted by the API: `secp256k1`, `ed25519`, `sr25519`, `bls`.
|
||||
|
||||
## Authentication
|
||||
|
||||
The custody API trusts tokens issued by [Hanzo IAM](/docs/services/iam). Present an IAM-issued JWT as a bearer token:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
|
||||
https://mpc.hanzo.ai/v1/info
|
||||
```
|
||||
|
||||
Services that already hold an external OIDC token can exchange it for an MPC session token (issuer must be on the allowlist):
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/v1/auth/oidc \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "token": "<external-oidc-access-token>" }'
|
||||
```
|
||||
|
||||
Roles (`owner`, `admin`, `signer`, `viewer`) gate each route; signing additionally requires `mpc:sign`. Long-lived API keys are also supported for machine callers — store them in [Hanzo KMS](/docs/services/kms), never inline.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Create a Wallet
|
||||
Signing is a three-step flow: create a wallet (DKG), open a signing session, then sign.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Create a wallet (DKG)
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/wallets \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"protocol": "cggmp21",
|
||||
"curve": "secp256k1",
|
||||
"threshold": 2,
|
||||
"parties": 3,
|
||||
"label": "eth-hot-wallet"
|
||||
}'
|
||||
-d '{ "protocol": "cggmp21" }'
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"walletId": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
|
||||
"protocol": "cggmp21",
|
||||
"curve": "secp256k1",
|
||||
"threshold": 2,
|
||||
"parties": 3,
|
||||
"public_key": "04a1b2c3d4e5f6...",
|
||||
"addresses": {
|
||||
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
|
||||
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
|
||||
},
|
||||
"created_at": "2026-02-23T10:30:00Z"
|
||||
"threshold": "2-of-3",
|
||||
"status": "active",
|
||||
"createdAt": "2026-06-26T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Sign a Transaction
|
||||
### Open a signing session
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/sign \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets/$WALLET_ID/sessions \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
Returns a `sessionId`. Signing consumes a session, which makes each signature an explicit, auditable authorization.
|
||||
|
||||
### Sign
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/sign \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"message": "0xdeadbeef...",
|
||||
"walletId": "'$WALLET_ID'",
|
||||
"sessionId": "'$SESSION_ID'",
|
||||
"message": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"encoding": "hex"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
Response (ECDSA):
|
||||
|
||||
```json
|
||||
{ "signature": "0x...", "r": "0x1a2b3c...", "s": "0x4d5e6f..." }
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Core Endpoints
|
||||
|
||||
All under `https://mpc.hanzo.ai/v1`.
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `POST` | `/v1/mpc/wallets` | Create a wallet (DKG); `{ "protocol": "cggmp21" \| "frost" }` |
|
||||
| `GET` | `/v1/wallets/{id}/addresses` | Derived chain addresses for a wallet |
|
||||
| `POST` | `/v1/mpc/wallets/{id}/sessions` | Open a signing session |
|
||||
| `GET` | `/v1/mpc/wallets/{id}/sessions` | List sessions |
|
||||
| `POST` | `/v1/mpc/sign` | Threshold-sign a message (requires `sessionId`) |
|
||||
| `POST` | `/v1/wallets/{id}/reshare` | Proactive share refresh / change t-of-n |
|
||||
| `GET` | `/v1/status` · `/v1/info` | Cluster status and capabilities |
|
||||
| `GET` | `/healthz` | Liveness (no auth) |
|
||||
|
||||
A single wallet derives addresses for many chains:
|
||||
|
||||
```bash
|
||||
curl https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/addresses \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"signature": {
|
||||
"r": "0x1a2b3c...",
|
||||
"s": "0x4d5e6f...",
|
||||
"v": 27
|
||||
},
|
||||
"signers": [1, 3],
|
||||
"protocol": "cggmp21",
|
||||
"duration_ms": 45
|
||||
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
|
||||
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
||||
"solana": "7nYBzS3Yz5mY8Qm4iXqz1bWmHnf8QpHq3xWv6Yc9aBcD"
|
||||
}
|
||||
```
|
||||
|
||||
### Reshare Key Shares
|
||||
## Proactive Share Refresh
|
||||
|
||||
Rotate shares without changing the public key or any on-chain addresses:
|
||||
Rotate the key shares without changing the public key or any on-chain address. Old shares become useless after a refresh, so a share compromised in a past epoch cannot be combined with current shares.
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/reshare \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/reshare \
|
||||
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"new_threshold": 3,
|
||||
"new_parties": 5
|
||||
}'
|
||||
-d '{ "new_threshold": 3, "new_participants": ["node-0","node-1","node-2","node-3","node-4"] }'
|
||||
```
|
||||
|
||||
## Supported Protocols
|
||||
## In-cluster Go client (NATS)
|
||||
|
||||
Hanzo MPC ships 8 cryptographic protocols, each optimized for different use cases:
|
||||
Inside the cluster, the canonical interface is an asynchronous Go client over NATS — every keygen/sign/reshare request is Ed25519-signed by an initiator key and answered with a result event:
|
||||
|
||||
| Protocol | Type | Curve | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| **CGGMP21** | Threshold ECDSA | secp256k1, P-256 | Bitcoin, Ethereum, EVM chains |
|
||||
| **FROST** | Threshold Schnorr/EdDSA | Ed25519, Ristretto | Solana, Polkadot, Cosmos |
|
||||
| **LSSS** | Linear secret sharing | Any | Dynamic resharing, key rotation |
|
||||
| **TFHE** | Threshold FHE | Lattice | Encrypted computation on shares |
|
||||
| **Corona** | Post-quantum lattice | Module-LWE | Future-proof custody |
|
||||
| **Quasar** | Hybrid BLS + PQ | BLS12-381 + Dilithium | Ethereum 2.0, PQ hedge |
|
||||
| **BLS** | Aggregate signatures | BLS12-381 | Validator keys, aggregate sigs |
|
||||
| **Doerner** | Optimized 2-of-2 | secp256k1 | High-frequency 2-party signing |
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/mpc/pkg/client"
|
||||
"github.com/luxfi/mpc/pkg/event"
|
||||
)
|
||||
|
||||
See the [Protocols deep dive](/docs/services/mpc/protocols) for detailed specifications.
|
||||
mpc := client.NewMPCClient(client.Options{NatsConn: nc, KeyPath: "./event_initiator.key"})
|
||||
|
||||
## Multi-Chain Support
|
||||
|
||||
A single MPC wallet can derive addresses for any supported chain:
|
||||
|
||||
| Chain | Curve | Protocol | Notes |
|
||||
|-------|-------|----------|-------|
|
||||
| Bitcoin | secp256k1 | CGGMP21 | Legacy, SegWit, Native SegWit |
|
||||
| Bitcoin Taproot | secp256k1 (BIP-340) | FROST | Schnorr signatures |
|
||||
| Ethereum | secp256k1 | CGGMP21 | EIP-155 replay protection |
|
||||
| All EVM chains | secp256k1 | CGGMP21 | Polygon, Arbitrum, BNB, etc. |
|
||||
| Solana | Ed25519 | FROST | SPL token support |
|
||||
| TON | Ed25519 | FROST | |
|
||||
| Cardano | Ed25519 | FROST | Byron and Shelley |
|
||||
| NEAR | Ed25519 | FROST | |
|
||||
| Polkadot | Sr25519 | FROST | Substrate chains |
|
||||
| Cosmos | secp256k1 | CGGMP21 | IBC-compatible |
|
||||
| Lux Network | secp256k1 | CGGMP21 | X/P/C chains |
|
||||
| XRPL | secp256k1 | CGGMP21 | |
|
||||
| BNB Chain | secp256k1 | CGGMP21 | BSC and BNB Beacon |
|
||||
| Polygon | secp256k1 | CGGMP21 | PoS and zkEVM |
|
||||
| Arbitrum | secp256k1 | CGGMP21 | L2 rollup |
|
||||
| Ethereum 2.0 | BLS12-381 | BLS / Quasar | Validator signing |
|
||||
|
||||
## Performance
|
||||
|
||||
Real benchmarks from production clusters:
|
||||
|
||||
### FROST Key Generation
|
||||
|
||||
| Parties | Time | Notes |
|
||||
|---------|------|-------|
|
||||
| 3 | 22 ms | Typical hot wallet |
|
||||
| 10 | 38 ms | Team custody |
|
||||
| 20 | 332 ms | Institutional |
|
||||
| 30 | 535 ms | Large consortium |
|
||||
| 50 | 1.85 s | DAO governance |
|
||||
|
||||
### FROST Signing
|
||||
|
||||
| Signers | Time | Notes |
|
||||
|---------|------|-------|
|
||||
| 3 | 25 ms | Standard threshold |
|
||||
| 5 | 21 ms | Batch-optimized |
|
||||
|
||||
Signing is **O(t)** -- it depends only on the threshold, not the total number of parties. A 3-of-100 wallet signs just as fast as a 3-of-3.
|
||||
|
||||
### Cryptographic Primitives
|
||||
|
||||
| Operation | Throughput |
|
||||
|-----------|-----------|
|
||||
| Scalar multiplication | 23 us/op |
|
||||
| Lagrange interpolation | O(n^2) |
|
||||
| Polynomial evaluation | O(n * t) |
|
||||
| Keygen communication | O(n^2) rounds |
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────────────────┐
|
||||
│ Client / KMS │
|
||||
│ (REST API + Bearer Auth) │
|
||||
└──────────────┬───────────────────┘
|
||||
│
|
||||
┌──────────────▼───────────────────┐
|
||||
│ MPC Coordinator │
|
||||
│ - Protocol orchestration │
|
||||
│ - IAM token validation │
|
||||
│ - Audit logging │
|
||||
└──────────────┬───────────────────┘
|
||||
│ NATS JetStream
|
||||
┌──────────────▼─────────────────────────┐
|
||||
│ MPC Nodes (StatefulSet / Validators) │
|
||||
│ │
|
||||
│ ┌────────┐ ┌────────┐ ... ┌────────┐ │
|
||||
│ │ Node 0 │ │ Node 1 │ │ Node N │ │
|
||||
│ │ share₀ │ │ share₁ │ │ shareₙ │ │
|
||||
│ │ Ed25519│ │ Ed25519│ │ Ed25519│ │
|
||||
│ │ ML-DSA │ │ ML-DSA │ │ ML-DSA │ │
|
||||
│ └────┬───┘ └────┬───┘ └────┬───┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌────▼──────────▼────────────────▼───┐ │
|
||||
│ │ Private BFT Blockchain (ConsensusKV)│ │
|
||||
│ │ Dual-cert: Ed25519 + ML-DSA-65 │ │
|
||||
│ │ t-of-n threshold finality │ │
|
||||
│ └────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────▼──────────────────────────────┐ │
|
||||
│ │ BadgerDB (encrypted) │ │
|
||||
│ │ AES-256-GCM per node │ │
|
||||
│ └────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────┘
|
||||
mpc.OnWalletCreationResult(func(e event.KeygenResultEvent) {
|
||||
// e.ECDSAPubKey (CGGMP21), e.EDDSAPubKey (FROST), …
|
||||
})
|
||||
mpc.CreateWallet(walletID)
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
| Component | Technology | Purpose |
|
||||
|-----------|-----------|---------|
|
||||
| Coordinator | Go | Protocol orchestration, API gateway |
|
||||
| MPC Nodes | Go + Rust (crypto) | Key share storage, protocol execution, consensus validators |
|
||||
| ConsensusKV | Private BFT chain | Cluster state (peers, keys, wallets) with dual-cert finality |
|
||||
| Message Bus | NATS JetStream | Inter-node messaging + consensus transport |
|
||||
| Storage | BadgerDB | Encrypted key share persistence |
|
||||
| Auth | Hanzo IAM (hanzo.id) | Bearer token validation, RBAC |
|
||||
| Identity | Ed25519 + ML-DSA-65 | Dual post-quantum node identity, stored in KMS/HSM |
|
||||
|
||||
### State Management Backends
|
||||
|
||||
All cluster state flows through an abstract KV interface. Three backends are available:
|
||||
|
||||
| Backend | Config | Transport | Finality | Use Case |
|
||||
|---------|--------|-----------|----------|----------|
|
||||
| `consensus` | `kv_backend: consensus` | NATS + Private BFT | Dual-cert threshold (<100ms) | **Production** (default) |
|
||||
| `nats` | `kv_backend: nats` | NATS JetStream KV | JetStream replication | Development/staging |
|
||||
| `consul` | `kv_backend: consul` | Consul HTTP | Raft | Legacy/parity testing |
|
||||
|
||||
See the [Consensus deep dive](/docs/services/mpc/consensus) for how the private blockchain works.
|
||||
|
||||
## Authentication
|
||||
|
||||
All API requests require a Bearer token issued by Hanzo IAM:
|
||||
|
||||
```bash
|
||||
# Obtain token from Hanzo IAM
|
||||
TOKEN=$(curl -s https://hanzo.id/v1/iam/login \
|
||||
-d '{"username":"[email protected]","password":"..."}' | jq -r .token)
|
||||
|
||||
# Use with MPC API
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
https://mpc.hanzo.ai/api/wallets
|
||||
```
|
||||
|
||||
See [Hanzo IAM](/docs/services/iam) for full authentication documentation.
|
||||
The REST custody API above is a facade over this same backend.
|
||||
|
||||
## Security Model
|
||||
|
||||
### Key Share Isolation
|
||||
|
||||
- Each node runs in its own Kubernetes pod with dedicated persistent volume
|
||||
- Shares are encrypted at rest with AES-256-GCM using node-specific keys
|
||||
- Node-to-node communication uses mTLS via Consul Connect
|
||||
- No single node ever holds enough information to reconstruct a private key
|
||||
|
||||
### Zero-Knowledge Proofs
|
||||
|
||||
17 ZK proof systems verify correctness at every protocol stage:
|
||||
|
||||
- Schnorr proofs of discrete log knowledge
|
||||
- Paillier encryption correctness proofs
|
||||
- Range proofs for secret share values
|
||||
- Commitment consistency proofs
|
||||
- Feldman VSS verification
|
||||
- Pedersen commitment proofs
|
||||
|
||||
### Identifiable Aborts
|
||||
|
||||
If any participant deviates from the protocol, the system:
|
||||
|
||||
1. Detects the deviation through ZK verification
|
||||
2. Identifies the specific malicious party
|
||||
3. Aborts the operation with a signed blame proof
|
||||
4. Logs the incident to the audit trail
|
||||
|
||||
### Proactive Share Refresh
|
||||
|
||||
Key shares can be refreshed on a schedule or on-demand without changing the underlying key:
|
||||
|
||||
- Old shares become useless after refresh
|
||||
- Compromised shares from a past epoch cannot be combined with current shares
|
||||
- The public key and all derived addresses remain unchanged
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Environment | URL | Backend | Threshold |
|
||||
|-------------|-----|---------|-----------|
|
||||
| Production (managed) | `https://mpc.hanzo.ai` | consensus | 3-of-5 |
|
||||
| Production (decentralized) | `https://mpc.lux.network` | consensus | 3-of-5 |
|
||||
| Staging | `https://stg.mpc.hanzo.ai` | nats | 2-of-3 |
|
||||
- **Key share isolation** — each node runs in its own pod with a dedicated encrypted volume; no node holds enough to reconstruct a key.
|
||||
- **Encrypted shares** — shares are AES-256-GCM encrypted in an embedded key-value store; the encryption key comes from an HSM or [Hanzo KMS](/docs/services/kms) (`kms.hanzo.ai`). In production, plaintext-password backends are rejected outright.
|
||||
- **Signed messaging** — every inter-node and initiator message is Ed25519-signed and verified before processing.
|
||||
- **Zero-knowledge proofs** verify correctness at each protocol stage; a deviating party is identified and an operation aborts with a signed blame proof.
|
||||
- **Post-quantum cluster state** — peers, keys, and wallets live in a private BFT chain with dual-certificate (Ed25519 + ML-DSA-65) threshold finality.
|
||||
|
||||
## Next Steps
|
||||
|
||||
<Cards>
|
||||
<Card title="Consensus" href="/docs/services/mpc/consensus">
|
||||
How the private BFT blockchain and dual-certificate finality work
|
||||
<Card title="API Reference" href="/docs/services/mpc/api">
|
||||
Complete REST API: wallets, sessions, signing, resharing
|
||||
</Card>
|
||||
<Card title="Protocols" href="/docs/services/mpc/protocols">
|
||||
Deep dive into all 8 cryptographic protocols
|
||||
CGGMP21, FROST, LSS, and BLS threshold schemes
|
||||
</Card>
|
||||
<Card title="API Reference" href="/docs/services/mpc/api">
|
||||
Complete REST API documentation
|
||||
</Card>
|
||||
<Card title="Deployment" href="/docs/services/mpc/deployment">
|
||||
Helm charts, Docker, and Kubernetes setup
|
||||
<Card title="Consensus" href="/docs/services/mpc/consensus">
|
||||
The private BFT chain and dual-certificate finality
|
||||
</Card>
|
||||
<Card title="KMS Integration" href="/docs/services/kms">
|
||||
Use MPC as a signing backend for Hanzo KMS
|
||||
Use MPC as a threshold signing backend for Hanzo KMS
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
@@ -86,14 +86,13 @@ The workhorse protocol for Bitcoin, Ethereum, and all EVM-compatible chains. Bas
|
||||
- Bitcoin (legacy, SegWit, Native SegWit)
|
||||
- Ethereum and all EVM chains
|
||||
- Cosmos (secp256k1)
|
||||
- Lux Network (C/X/P chains)
|
||||
- XRPL
|
||||
- Any chain using ECDSA over secp256k1 or P-256
|
||||
|
||||
### Example
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/wallets \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -163,7 +162,7 @@ Signing time depends only on `t` (threshold), not `n` (total parties). A 3-of-10
|
||||
### Example
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/wallets \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -221,7 +220,7 @@ LSSS generalizes Shamir's Secret Sharing with the addition of proactive refresh
|
||||
|
||||
```bash
|
||||
# Reshare: change from 2-of-3 to 3-of-5
|
||||
curl -X POST https://mpc.hanzo.ai/api/reshare \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/reshare \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -307,7 +306,7 @@ Corona provides threshold signatures using Module-LWE, offering resistance again
|
||||
### Example
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/wallets \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -352,7 +351,7 @@ Quasar produces dual signatures -- one BLS12-381 for immediate blockchain compat
|
||||
### Example
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/wallets \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -395,12 +394,12 @@ Signing is non-interactive -- once nonce commitments are exchanged during a setu
|
||||
|
||||
- Ethereum 2.0 validator signing
|
||||
- Aggregate multi-party attestations
|
||||
- Consensus protocol signing (Lux Network validators)
|
||||
- Consensus protocol signing (PoS validator keys)
|
||||
|
||||
### Example
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/wallets \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -449,7 +448,7 @@ Doerner is a specialized protocol for the 2-of-2 case that achieves the lowest p
|
||||
### Example
|
||||
|
||||
```bash
|
||||
curl -X POST https://mpc.hanzo.ai/api/wallets \
|
||||
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
|
||||
-H "Authorization: Bearer $HANZO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
||||
@@ -1,601 +1,50 @@
|
||||
---
|
||||
title: Hanzo PaaS
|
||||
description: Kubernetes-native container orchestration and deployment layer for Hanzo infrastructure. Manages DOKS clusters, namespace isolation, ingress, secrets, CI/CD, and auto-scaling.
|
||||
description: The platform.hanzo.ai control plane for deploying and operating services on Hanzo's Kubernetes clusters.
|
||||
---
|
||||
|
||||
# Hanzo PaaS
|
||||
|
||||
Hanzo PaaS is the container orchestration and deployment layer that runs all Hanzo and Lux production infrastructure. It provides Kubernetes-native deployments on DigitalOcean Kubernetes (DOKS) clusters with namespace isolation, automated TLS, secrets management via KMS, CI/CD pipelines, and horizontal auto-scaling.
|
||||
Hanzo PaaS is the deployment and operations control plane for the Hanzo platform, served at **`platform.hanzo.ai`**. You register an application, point it at a container image or Git repository, attach a domain, and the platform builds, deploys, and operates it on Hanzo's Kubernetes clusters — with deployment history, rolling updates, and drift detection.
|
||||
|
||||
**Endpoint:** `paas.hanzo.ai`
|
||||
**Gateway:** `api.hanzo.ai/v1/paas/*`
|
||||
| | |
|
||||
|---|---|
|
||||
| **Control plane** | `https://platform.hanzo.ai` |
|
||||
| **Canonical API** | `https://platform.hanzo.ai/v1` |
|
||||
| **Auth** | [Hanzo IAM](/docs/services/iam) (interactive) · API key / service token (machine) |
|
||||
| **Secrets** | [Hanzo KMS](/docs/services/kms) via `KMSSecret` CRDs — zero plaintext |
|
||||
| **Ingress** | Hanzo Ingress + cert-manager (automatic Let's Encrypt TLS) |
|
||||
|
||||
## Features
|
||||
|
||||
- **Kubernetes-Native Deployments**: All services run as K8s Deployments, StatefulSets, or DaemonSets with declarative manifests
|
||||
- **Multi-Cluster Management**: Two DOKS clusters purpose-separated for Hanzo services and Lux blockchain
|
||||
- **Namespace Isolation**: Service groups are isolated by namespace with RBAC boundaries
|
||||
- **Ingress + TLS**: nginx-ingress with cert-manager for automatic Let's Encrypt certificate provisioning
|
||||
- **Secrets Management**: KMS integration via `KMSSecret` custom resources for zero-plaintext secrets
|
||||
- **CI/CD Pipelines**: GitHub Actions workflows build, push, and deploy on every merge to main
|
||||
- **Container Registry**: All images published to `ghcr.io/hanzoai/*` with multi-arch support
|
||||
- **Helm Charts**: Complex multi-component services deployed via Helm for repeatability
|
||||
- **Horizontal Auto-Scaling**: HPA policies based on CPU, memory, and custom metrics
|
||||
- **Persistent Volumes**: Block storage PVCs for stateful services (databases, validators, storage)
|
||||
- **Internal DNS**: Service discovery via `*.hanzo.svc` cluster DNS
|
||||
|
||||
## Architecture
|
||||
## How it fits together
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ Cloudflare DNS │
|
||||
│ *.hanzo.ai *.lux.network *.zoo.id│
|
||||
└──────────┬──────────┬────────────────┘
|
||||
│ │
|
||||
┌─────────────────▼──┐ ┌────▼─────────────────┐
|
||||
│ hanzo-k8s │ │ lux-k8s │
|
||||
│ 24.199.76.156 │ │ 24.144.69.101 │
|
||||
│ │ │ │
|
||||
│ ┌───────────────┐ │ │ ┌───────────────┐ │
|
||||
│ │ nginx-ingress │ │ │ │ nginx-ingress │ │
|
||||
│ │ + cert-manager│ │ │ │ + cert-manager│ │
|
||||
│ └──────┬────────┘ │ │ └──────┬────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌──────▼────────┐ │ │ ┌──────▼────────┐ │
|
||||
│ │ Namespaces │ │ │ │ Namespaces │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ hanzo │ │ │ │ hanzo │ │
|
||||
│ │ hanzo-zt │ │ │ │ lux │ │
|
||||
│ │ monitoring │ │ │ │ monitoring │ │
|
||||
│ │ cert-manager │ │ │ │ cert-manager │ │
|
||||
│ └──────┬────────┘ │ │ └──────┬────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌──────▼────────┐ │ │ ┌──────▼────────┐ │
|
||||
│ │ Data Layer │ │ │ │ Data Layer │ │
|
||||
│ │ PostgreSQL │ │ │ │ PostgreSQL │ │
|
||||
│ │ Redis/Valkey │ │ │ │ Redis/Valkey │ │
|
||||
│ │ MongoDB │ │ │ │ Datastore │ │
|
||||
│ │ Hanzo S3 │ │ │ └───────────────┘ │
|
||||
│ │ ClickHouse │ │ │ │
|
||||
│ └───────────────┘ │ │ ┌───────────────┐ │
|
||||
│ │ │ │ Lux Network │ │
|
||||
│ ┌───────────────┐ │ │ │ 15 validators│ │
|
||||
│ │ KMS Operator │ │ │ │ gateway │ │
|
||||
│ │ KMS Operator │ │ │ │ markets │ │
|
||||
│ └───────────────┘ │ │ └───────────────┘ │
|
||||
└─────────────────────┘ └──────────────────────┘
|
||||
│ │
|
||||
┌─────────▼────────────────────────▼──────────┐
|
||||
│ ghcr.io/hanzoai/* │
|
||||
│ Container Image Registry │
|
||||
└─────────────────────────────────────────────┘
|
||||
push image / git ──▶ platform.hanzo.ai ──▶ operator ──▶ Kubernetes
|
||||
(declare + drive) (reconcile) (run + serve)
|
||||
▲ │
|
||||
└────────── observe (drift) ────────┘
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
- **Declare & drive** — register applications and trigger deploys through the control plane.
|
||||
- **Reconcile** — the operator applies the declared state to the cluster.
|
||||
- **Observe** — the control plane tracks declared vs running vs latest image tags and surfaces drift.
|
||||
|
||||
### Prerequisites
|
||||
Secrets are never embedded in manifests — workloads read them from [Hanzo KMS](/docs/services/kms), synced into Kubernetes with [KMSSecret CRDs](/docs/services/kms/kmssecret-crd).
|
||||
|
||||
```bash
|
||||
# Install kubectl and configure cluster access
|
||||
brew install kubectl helm
|
||||
## Get started
|
||||
|
||||
# Configure contexts for both clusters
|
||||
doctl kubernetes cluster kubeconfig save hanzo-k8s
|
||||
doctl kubernetes cluster kubeconfig save lux-k8s
|
||||
|
||||
# Verify access
|
||||
kubectl --context do-sfo3-hanzo-k8s get nodes
|
||||
kubectl --context do-sfo3-lux-k8s get nodes
|
||||
```
|
||||
|
||||
### Deploy a Service
|
||||
|
||||
```yaml
|
||||
# deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: my-service
|
||||
namespace: hanzo
|
||||
labels:
|
||||
app: my-service
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: my-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: my-service
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-secret
|
||||
containers:
|
||||
- name: my-service
|
||||
image: ghcr.io/hanzoai/my-service:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: my-service-secrets
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 15
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
```
|
||||
|
||||
```bash
|
||||
# Apply to hanzo-k8s cluster
|
||||
kubectl --context do-sfo3-hanzo-k8s apply -f deployment.yaml
|
||||
```
|
||||
|
||||
### Expose via Ingress
|
||||
|
||||
```yaml
|
||||
# ingress.yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: my-service
|
||||
namespace: hanzo
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- my-service.hanzo.ai
|
||||
secretName: my-service-tls
|
||||
rules:
|
||||
- host: my-service.hanzo.ai
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: my-service
|
||||
port:
|
||||
number: 8080
|
||||
```
|
||||
|
||||
### Deploy with Helm
|
||||
|
||||
```bash
|
||||
# Add Hanzo chart repository
|
||||
helm repo add hanzo https://charts.hanzo.ai
|
||||
helm repo update
|
||||
|
||||
# Install a chart
|
||||
helm install my-release hanzo/my-chart \
|
||||
--namespace hanzo \
|
||||
--set image.tag=v1.2.3 \
|
||||
--set replicas=3
|
||||
```
|
||||
|
||||
## Cluster Layout
|
||||
|
||||
### hanzo-k8s (24.199.76.156)
|
||||
|
||||
Primary cluster for all Hanzo platform services. Region: SFO3.
|
||||
|
||||
| Service | Namespace | Domain | Image |
|
||||
|---------|-----------|--------|-------|
|
||||
| IAM | hanzo | hanzo.id, lux.id, zoo.id, pars.id | `ghcr.io/hanzoai/iam` |
|
||||
| KMS | hanzo | kms.hanzo.ai | `ghcr.io/hanzoai/kms` |
|
||||
| Console | hanzo | console.hanzo.ai | `hanzoai/console` |
|
||||
| Console Worker | hanzo | (internal) | `ghcr.io/hanzoai/console-worker` |
|
||||
| Cloud Backend | hanzo | cloud.hanzo.ai | `ghcr.io/hanzoai/cloud` |
|
||||
| Gateway | hanzo | api.hanzo.ai | `ghcr.io/hanzoai/gateway` |
|
||||
| LLM Proxy | hanzo | llm.hanzo.ai | `ghcr.io/hanzoai/llm` |
|
||||
| Commerce | hanzo | commerce.hanzo.ai | `ghcr.io/hanzoai/commerce` |
|
||||
| Platform | hanzo | platform.hanzo.ai | `ghcr.io/hanzoai/platform` |
|
||||
| Storage (Hanzo S3) | hanzo | s3.hanzo.ai, s3-api.hanzo.ai | `ghcr.io/hanzoai/storage` |
|
||||
| PostgreSQL | hanzo | postgres.hanzo.svc | `postgres:16` |
|
||||
| Redis/Valkey | hanzo | redis.hanzo.svc | `valkey/valkey:8` |
|
||||
| MongoDB | hanzo | mongodb.hanzo.svc | `mongo:7` |
|
||||
| ClickHouse | hanzo | clickhouse.hanzo.svc | `clickhouse/clickhouse-server` |
|
||||
| ZT Controller | hanzo-zt | zt.hanzo.ai | `ghcr.io/hanzoai/ziti` |
|
||||
| ZT Router | hanzo-zt | (mesh) | `ghcr.io/hanzoai/ziti-router` |
|
||||
|
||||
### lux-k8s (24.144.69.101)
|
||||
|
||||
Dedicated cluster for Lux blockchain infrastructure. Region: SFO3.
|
||||
|
||||
| Service | Namespace | Domain | Image |
|
||||
|---------|-----------|--------|-------|
|
||||
| Lux Validators (x15) | lux | (P2P) | `ghcr.io/luxfi/node` |
|
||||
| Hanzo Gateway | hanzo | api.lux.network | `ghcr.io/hanzoai/gateway` |
|
||||
| Lux Cloud | hanzo | cloud.lux.network | `ghcr.io/luxfi/cloud-web` |
|
||||
| Markets | hanzo | markets.lux.network | `ghcr.io/luxfi/markets` |
|
||||
| PostgreSQL | hanzo | postgres.hanzo.svc | `postgres:16` |
|
||||
| Redis/Valkey | hanzo | redis.hanzo.svc | `valkey/valkey:8` |
|
||||
|
||||
## Namespace Organization
|
||||
|
||||
Namespaces provide security boundaries and resource isolation.
|
||||
|
||||
```
|
||||
hanzo-k8s:
|
||||
hanzo/ # All core Hanzo services
|
||||
hanzo-zt/ # Zero Trust mesh networking (Ziti)
|
||||
monitoring/ # Prometheus, Grafana, alerting
|
||||
cert-manager/ # TLS certificate automation
|
||||
kube-system/ # K8s system components
|
||||
|
||||
lux-k8s:
|
||||
hanzo/ # Platform services (gateway, cloud)
|
||||
lux/ # Blockchain validators and infra
|
||||
monitoring/ # Cluster monitoring
|
||||
cert-manager/ # TLS certificate automation
|
||||
kube-system/ # K8s system components
|
||||
```
|
||||
|
||||
Each namespace has:
|
||||
- **ResourceQuotas**: CPU/memory limits per namespace
|
||||
- **NetworkPolicies**: Restrict inter-namespace traffic (only explicit allowlists)
|
||||
- **RBAC**: Service accounts scoped to namespace resources
|
||||
- **LimitRanges**: Default container resource constraints
|
||||
|
||||
## Secrets Management
|
||||
|
||||
All secrets are managed through Hanzo KMS and synced into Kubernetes via the KMS operator. No plaintext secrets in manifests or Git.
|
||||
|
||||
### KMSSecret Custom Resource
|
||||
|
||||
```yaml
|
||||
# secrets.yaml
|
||||
apiVersion: secrets.hanzo.ai/v1
|
||||
kind: KMSSecret
|
||||
metadata:
|
||||
name: my-service-secrets
|
||||
namespace: hanzo
|
||||
spec:
|
||||
# KMS project and environment
|
||||
projectId: "proj-xxxxxxxx"
|
||||
environment: "production"
|
||||
secretPath: "/my-service"
|
||||
|
||||
# Authentication via Universal Auth
|
||||
authentication:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: kms-machine-identity
|
||||
secretNamespace: hanzo
|
||||
|
||||
# Target K8s secret
|
||||
managedSecretReference:
|
||||
secretName: my-service-secrets
|
||||
secretNamespace: hanzo
|
||||
secretType: Opaque
|
||||
|
||||
# Sync interval
|
||||
resyncInterval: 60
|
||||
```
|
||||
|
||||
### Secret Workflow
|
||||
|
||||
```
|
||||
Developer Hanzo KMS K8s Cluster
|
||||
│ │ │
|
||||
├── Create secret ───────►│ │
|
||||
│ via UI or CLI │ │
|
||||
│ ├── KMS Operator sync ───►│
|
||||
│ │ (every 60s) │
|
||||
│ │ ├── Pod mounts
|
||||
│ │ │ secret as env
|
||||
│ │ │
|
||||
```
|
||||
|
||||
### Migrating from Static Secrets
|
||||
|
||||
Existing manifests with inline secrets should be replaced with `KMSSecret` resources:
|
||||
|
||||
```bash
|
||||
# 1. Store secret in KMS
|
||||
hanzo-kms secrets set DATABASE_URL "postgres://..." \
|
||||
--env production --path /my-service
|
||||
|
||||
# 2. Replace K8s secret manifest with KMSSecret
|
||||
# 3. Reference the synced secret in your Deployment envFrom
|
||||
```
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
All services use GitHub Actions for continuous integration and deployment.
|
||||
|
||||
### Standard Workflow
|
||||
|
||||
```yaml
|
||||
# .github/workflows/deploy.yml
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# 1. Checkout
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# 2. Authenticate with KMS for deploy secrets
|
||||
- name: Fetch deploy secrets from KMS
|
||||
uses: hanzoai/kms-action@v1
|
||||
with:
|
||||
client-id: ${{ secrets.KMS_CLIENT_ID }}
|
||||
client-secret: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
project-id: ${{ secrets.KMS_PROJECT_ID }}
|
||||
environment: production
|
||||
path: /ci-cd
|
||||
env:
|
||||
KMS_URL: https://kms.hanzo.ai
|
||||
|
||||
# 3. Build and push to GHCR
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: docker/build-push-action@v5
|
||||
with:
|
||||
push: true
|
||||
tags: ghcr.io/hanzoai/${{ github.event.repository.name }}:latest
|
||||
|
||||
# 4. Deploy to K8s
|
||||
- uses: digitalocean/action-doctl@v2
|
||||
with:
|
||||
token: ${{ env.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
|
||||
- run: |
|
||||
doctl kubernetes cluster kubeconfig save hanzo-k8s
|
||||
kubectl set image deployment/my-service \
|
||||
my-service=ghcr.io/hanzoai/my-service:${{ github.sha }} \
|
||||
--namespace hanzo
|
||||
kubectl rollout status deployment/my-service \
|
||||
--namespace hanzo --timeout=300s
|
||||
```
|
||||
|
||||
### Image Registry
|
||||
|
||||
All container images are stored in the GitHub Container Registry under the `hanzoai` organization:
|
||||
|
||||
```
|
||||
ghcr.io/hanzoai/
|
||||
iam:latest # IAM
|
||||
kms:latest # KMS
|
||||
console:latest # Console web
|
||||
console-worker:latest # Console MQ worker
|
||||
cloud:latest # Cloud backend
|
||||
cloud-site:latest # Cloud landing page
|
||||
gateway:latest # API gateway
|
||||
llm:latest # LLM proxy
|
||||
commerce:latest # Commerce engine
|
||||
platform:latest # Platform (PaaS)
|
||||
storage:latest # Hanzo S3 storage
|
||||
bot-site:latest # hanzo.bot site
|
||||
ziti:latest # ZT controller
|
||||
ziti-router:latest # ZT router
|
||||
```
|
||||
|
||||
Authentication for pulling images in-cluster:
|
||||
|
||||
```bash
|
||||
# Create GHCR pull secret (one-time per cluster)
|
||||
kubectl create secret docker-registry ghcr-secret \
|
||||
--docker-server=ghcr.io \
|
||||
--docker-username=hanzoai \
|
||||
--docker-password=$GHCR_TOKEN \
|
||||
--namespace hanzo
|
||||
```
|
||||
|
||||
## Auto-Scaling
|
||||
|
||||
Horizontal Pod Autoscaler (HPA) policies ensure services scale with demand.
|
||||
|
||||
```yaml
|
||||
# hpa.yaml
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: my-service
|
||||
namespace: hanzo
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: my-service
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
behavior:
|
||||
scaleUp:
|
||||
stabilizationWindowSeconds: 60
|
||||
policies:
|
||||
- type: Pods
|
||||
value: 2
|
||||
periodSeconds: 60
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
policies:
|
||||
- type: Pods
|
||||
value: 1
|
||||
periodSeconds: 120
|
||||
```
|
||||
|
||||
## Persistent Volumes
|
||||
|
||||
Stateful services use DigitalOcean Block Storage via the `do-block-storage` StorageClass.
|
||||
|
||||
```yaml
|
||||
# pvc.yaml
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: postgres-data
|
||||
namespace: hanzo
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: do-block-storage
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
||||
```
|
||||
|
||||
Current persistent volumes:
|
||||
|
||||
| Service | Size | Mount Path |
|
||||
|---------|------|------------|
|
||||
| PostgreSQL (hanzo-k8s) | 50Gi | /var/lib/postgresql/data |
|
||||
| PostgreSQL (lux-k8s) | 50Gi | /var/lib/postgresql/data |
|
||||
| MongoDB | 20Gi | /data/db |
|
||||
| ClickHouse | 100Gi | /var/lib/clickhouse |
|
||||
| Hanzo S3 | 200Gi | /data |
|
||||
| ZT Controller | 2Gi | /persistent |
|
||||
| ZT Router | 100Mi | /persistent |
|
||||
| Lux Validators (x15) | 50Gi each | /data |
|
||||
|
||||
## Monitoring
|
||||
|
||||
Cluster health and service metrics are collected via the monitoring stack.
|
||||
|
||||
### Stack
|
||||
|
||||
- **Prometheus**: Metrics collection from all pods (ServiceMonitor CRDs)
|
||||
- **Grafana**: Dashboards and alerting (accessible via console.hanzo.ai)
|
||||
- **AlertManager**: PagerDuty and Slack alert routing
|
||||
- **Node Exporter**: Host-level metrics from all cluster nodes
|
||||
|
||||
### Health Checks
|
||||
|
||||
Every service exposes standard health endpoints:
|
||||
|
||||
```
|
||||
GET /health # Liveness - is the process alive?
|
||||
GET /health/ready # Readiness - is the service ready for traffic?
|
||||
```
|
||||
|
||||
### Key Alerts
|
||||
|
||||
| Alert | Condition | Severity |
|
||||
|-------|-----------|----------|
|
||||
| PodCrashLooping | Restart count > 3 in 5m | critical |
|
||||
| HighCPU | CPU > 90% for 10m | warning |
|
||||
| HighMemory | Memory > 85% for 10m | warning |
|
||||
| CertExpiringSoon | TLS cert expires < 14d | warning |
|
||||
| PVCNearFull | Volume usage > 80% | warning |
|
||||
| NodeNotReady | Node unschedulable > 5m | critical |
|
||||
|
||||
## Internal DNS
|
||||
|
||||
All services within a cluster are discoverable via Kubernetes DNS:
|
||||
|
||||
```
|
||||
# Format: <service>.<namespace>.svc.cluster.local
|
||||
# Short form (same namespace): <service>.hanzo.svc
|
||||
|
||||
postgres.hanzo.svc:5432 # PostgreSQL
|
||||
redis.hanzo.svc:6379 # Redis/Valkey
|
||||
mongodb.hanzo.svc:27017 # MongoDB
|
||||
clickhouse.hanzo.svc:9000 # ClickHouse
|
||||
s3.hanzo.svc:9000 # Hanzo S3 API
|
||||
s3.hanzo.svc:9001 # Hanzo S3 Console
|
||||
```
|
||||
|
||||
Services reference each other by internal DNS, keeping traffic in-cluster and avoiding external round-trips.
|
||||
|
||||
## Infrastructure as Code
|
||||
|
||||
All cluster manifests live in the `universe/infra/k8s/` directory, organized by service:
|
||||
|
||||
```
|
||||
universe/infra/k8s/
|
||||
iam/ # IAM deployment, service, ingress, configmap
|
||||
kms/ # KMS Helm values, secrets
|
||||
console/ # Console web + worker deployments
|
||||
cloud/ # Cloud backend
|
||||
gateway/ # API gateway
|
||||
commerce/ # Commerce engine
|
||||
storage/ # Hanzo S3 deployment, ingress, buckets
|
||||
paas/ # PaaS platform
|
||||
zt/ # Zero Trust controller + router
|
||||
monitoring/ # Prometheus, Grafana, AlertManager
|
||||
cert-manager/ # ClusterIssuer, cert resources
|
||||
```
|
||||
|
||||
Changes to manifests follow GitOps: push to main, CI applies to cluster.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Deployments
|
||||
|
||||
- `GET /v1/paas/deployments` - List all deployments
|
||||
- `POST /v1/paas/deployments` - Create deployment
|
||||
- `GET /v1/paas/deployments/:id` - Get deployment status
|
||||
- `PATCH /v1/paas/deployments/:id` - Update deployment
|
||||
- `DELETE /v1/paas/deployments/:id` - Delete deployment
|
||||
- `POST /v1/paas/deployments/:id/rollback` - Rollback to previous revision
|
||||
|
||||
### Clusters
|
||||
|
||||
- `GET /v1/paas/clusters` - List managed clusters
|
||||
- `GET /v1/paas/clusters/:id/nodes` - List cluster nodes
|
||||
- `GET /v1/paas/clusters/:id/namespaces` - List namespaces
|
||||
|
||||
### Scaling
|
||||
|
||||
- `GET /v1/paas/deployments/:id/hpa` - Get HPA configuration
|
||||
- `PUT /v1/paas/deployments/:id/hpa` - Update HPA policy
|
||||
- `POST /v1/paas/deployments/:id/scale` - Manual scale
|
||||
|
||||
See the [full API reference](/docs/openapi) for all endpoints.
|
||||
|
||||
## Related Services
|
||||
The full control-plane reference — the project/environment/application model, the `/v1` container redeploy, the build pipeline, clusters, domains, and the observe/drift board — lives in the Platform docs:
|
||||
|
||||
<Cards>
|
||||
<Card title="Platform" href="/docs/services/platform">
|
||||
PaaS UI for managing deployments, domains, and databases via web interface
|
||||
<Card title="Platform Overview" href="/docs/services/platform">
|
||||
The container/app model and deployment workflow
|
||||
</Card>
|
||||
<Card title="Registry" href="/docs/services/registry">
|
||||
Container image registry and artifact management at ghcr.io/hanzoai
|
||||
<Card title="API Reference" href="/docs/services/platform/api">
|
||||
The `/v1` control surface and the `/api` build pipeline
|
||||
</Card>
|
||||
<Card title="KMS" href="/docs/services/kms">
|
||||
Secrets management with Kubernetes operator for zero-plaintext secret sync
|
||||
<Card title="Authentication" href="/docs/services/platform/api/authentication">
|
||||
API keys, service tokens, and IAM tokens
|
||||
</Card>
|
||||
<Card title="Observability" href="/docs/services/o11y">
|
||||
Prometheus, Grafana, and AlertManager for cluster and service monitoring
|
||||
<Card title="Containers API" href="/docs/services/platform/api/containers">
|
||||
Create, deploy, redeploy, and operate applications
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
@@ -1,66 +1,51 @@
|
||||
---
|
||||
title: Authentication
|
||||
description: OAuth2 via Hanzo ID, API tokens, and bearer authentication
|
||||
description: IAM tokens, API keys, and service tokens for the Hanzo PaaS API
|
||||
---
|
||||
|
||||
All API requests to Hanzo Platform require authentication. The platform supports OAuth2 via Hanzo ID and long-lived API tokens.
|
||||
Hanzo PaaS has three credential types, matched to its two API surfaces. Machine credentials are stored in [Hanzo KMS](/docs/services/kms) and read at runtime — never hard-coded.
|
||||
|
||||
## Authentication Methods
|
||||
|
||||
| Method | Lifetime | Use Case |
|
||||
|--------|----------|----------|
|
||||
| **OAuth2 Access Token** | 1 hour | Dashboard sessions, user-facing apps |
|
||||
| **API Token** | Until revoked | CI/CD, scripts, server-to-server |
|
||||
|
||||
Both methods use the same `Authorization: Bearer <token>` header.
|
||||
| Credential | Header | Surface | Use |
|
||||
|------------|--------|---------|-----|
|
||||
| **IAM token** | `Authorization: Bearer <jwt>` | `/api` | Interactive / user access via SSO |
|
||||
| **API key** | `x-api-key: <key>` | `/api` | Machine access to the build pipeline; org-scoped |
|
||||
| **Service token** | `Authorization: Bearer <token>` | `/v1` | Container redeploy and observe (M2M) |
|
||||
|
||||
## OAuth2 via Hanzo ID
|
||||
|
||||
Hanzo Platform uses [Hanzo ID](https://hanzo.id) as its identity provider. The OAuth2 Authorization Code flow is used for user authentication.
|
||||
|
||||
### Authorization Code Flow
|
||||
Interactive access uses the [Hanzo ID](https://hanzo.id) OAuth2 Authorization Code flow.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Redirect to Hanzo ID
|
||||
|
||||
```
|
||||
https://hanzo.id/oauth/authorize?
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
https://hanzo.id/v1/iam/oauth/authorize?
|
||||
client_id=hanzo-platform&
|
||||
redirect_uri=https://yourapp.com/callback&
|
||||
response_type=code&
|
||||
scope=openid profile email&
|
||||
state=RANDOM_STATE
|
||||
```
|
||||
|
||||
### User authenticates
|
||||
|
||||
The user logs in at Hanzo ID and approves access.
|
||||
|
||||
### Handle the callback
|
||||
|
||||
Hanzo ID redirects to your `redirect_uri` with an authorization code:
|
||||
Hanzo ID redirects back with `?code=...&state=...`.
|
||||
|
||||
```
|
||||
https://yourapp.com/callback?code=AUTH_CODE&state=RANDOM_STATE
|
||||
```
|
||||
|
||||
### Exchange code for tokens
|
||||
### Exchange the code for tokens
|
||||
|
||||
```bash
|
||||
curl -X POST https://hanzo.id/oauth/token \
|
||||
curl -X POST https://hanzo.id/v1/iam/oauth/token \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"client_id": "YOUR_CLIENT_ID",
|
||||
"client_secret": "YOUR_CLIENT_SECRET",
|
||||
"code": "AUTH_CODE",
|
||||
"client_id": "hanzo-platform",
|
||||
"client_secret": "'$CLIENT_SECRET'",
|
||||
"code": "'$AUTH_CODE'",
|
||||
"redirect_uri": "https://yourapp.com/callback",
|
||||
"grant_type": "authorization_code"
|
||||
}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIs...",
|
||||
@@ -72,89 +57,43 @@ curl -X POST https://hanzo.id/oauth/token \
|
||||
|
||||
</Steps>
|
||||
|
||||
### Refreshing Tokens
|
||||
Refresh with `grant_type=refresh_token`. See [Hanzo IAM](/docs/services/iam) for the full flow.
|
||||
|
||||
Access tokens expire after 1 hour. Use the refresh token to obtain a new access token:
|
||||
## API keys (build pipeline)
|
||||
|
||||
```bash
|
||||
curl -X POST https://hanzo.id/oauth/token \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"client_id": "YOUR_CLIENT_ID",
|
||||
"client_secret": "YOUR_CLIENT_SECRET",
|
||||
"refresh_token": "YOUR_REFRESH_TOKEN",
|
||||
"grant_type": "refresh_token"
|
||||
}'
|
||||
```
|
||||
|
||||
## API Tokens
|
||||
|
||||
API tokens are long-lived credentials for programmatic access. They do not expire unless revoked.
|
||||
|
||||
### Creating a Token
|
||||
|
||||
<Tabs items={["Dashboard", "CLI"]}>
|
||||
<Tab value="Dashboard">
|
||||
Go to **Settings > API Tokens > Create Token**. Name the token and select its scope. The token is shown once -- copy it immediately.
|
||||
</Tab>
|
||||
<Tab value="CLI">
|
||||
```bash
|
||||
hanzo token create --name "CI Pipeline" --scope "deploy:*"
|
||||
# Output: tk_abc123def456...
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
API keys authenticate machine callers to the `/api` build-pipeline surface. Create one in the Studio under **Settings → API Keys** — the key is scoped to the organization you create it under and shown once.
|
||||
|
||||
<Callout type="warn">
|
||||
API tokens are shown only once at creation time. Store them securely. If lost, revoke the old token and create a new one.
|
||||
Store API keys in [Hanzo KMS](/docs/services/kms) immediately. They are shown only at creation. If lost, revoke and create a new one.
|
||||
</Callout>
|
||||
|
||||
### Using a Token
|
||||
|
||||
```bash
|
||||
curl https://app.platform.hanzo.ai/api/cluster.all \
|
||||
-H "Authorization: Bearer tk_abc123def456"
|
||||
curl https://platform.hanzo.ai/api/application.all \
|
||||
-H "x-api-key: $PLATFORM_API_KEY"
|
||||
```
|
||||
|
||||
### Revoking a Token
|
||||
## Service tokens (`/v1` surface)
|
||||
|
||||
The canonical `/v1` control surface (container redeploy, `/v1/apps` observe) uses a service token presented as a bearer:
|
||||
|
||||
```bash
|
||||
# Via CLI
|
||||
hanzo token revoke tk_abc123def456
|
||||
curl -fsS -X POST \
|
||||
"https://platform.hanzo.ai/v1/org/$ORG_ID/project/$PROJECT_ID/env/$ENV_ID/container/$CONTAINER_ID/redeploy" \
|
||||
-H "Authorization: Bearer $PAAS_SERVICE_TOKEN"
|
||||
```
|
||||
|
||||
# Via API
|
||||
curl -X POST https://app.platform.hanzo.ai/api/user.revokeToken \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
The service token is issued out-of-band and stored in KMS (e.g. `kms.hanzo.ai/v1/kms/orgs/hanzo/secrets/paas/SERVICE_TOKEN`). It is a pure machine-to-machine credential with no user session.
|
||||
|
||||
## Request examples
|
||||
|
||||
```bash
|
||||
# List organizations (build pipeline, API key)
|
||||
curl https://platform.hanzo.ai/api/organization.all \
|
||||
-H "x-api-key: $PLATFORM_API_KEY"
|
||||
|
||||
# Register an application
|
||||
curl -X POST https://platform.hanzo.ai/api/application.create \
|
||||
-H "x-api-key: $PLATFORM_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "tokenId": "tk_abc123def456" }'
|
||||
```
|
||||
|
||||
## Token Scopes
|
||||
|
||||
Tokens can be scoped to limit access:
|
||||
|
||||
| Scope | Access |
|
||||
|-------|--------|
|
||||
| `*` | Full access (default) |
|
||||
| `read:*` | Read-only across all resources |
|
||||
| `deploy:*` | Deploy to any cluster |
|
||||
| `deploy:cluster_abc` | Deploy to a specific cluster |
|
||||
| `admin:*` | Administrative operations |
|
||||
|
||||
## Request Examples
|
||||
|
||||
```bash
|
||||
# List organizations
|
||||
curl https://app.platform.hanzo.ai/api/organization.all \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Create a container
|
||||
curl -X POST https://app.platform.hanzo.ai/api/container.create \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "web",
|
||||
"image": "ghcr.io/myorg/myapp:latest",
|
||||
"clusterId": "cluster_abc123"
|
||||
}'
|
||||
-d '{ "name": "web", "environmentId": "'$ENV_ID'" }'
|
||||
```
|
||||
|
||||
@@ -26,7 +26,7 @@ The Clusters API handles the full lifecycle of compute clusters: creation, confi
|
||||
`GET /api/cluster.all?organizationId=org_abc123`
|
||||
|
||||
```bash
|
||||
curl "https://app.platform.hanzo.ai/api/cluster.all?organizationId=org_abc123" \
|
||||
curl "https://platform.hanzo.ai/api/cluster.all?organizationId=org_abc123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
@@ -66,7 +66,7 @@ curl "https://app.platform.hanzo.ai/api/cluster.all?organizationId=org_abc123" \
|
||||
`GET /api/cluster.one?clusterId=cluster_abc123`
|
||||
|
||||
```bash
|
||||
curl "https://app.platform.hanzo.ai/api/cluster.one?clusterId=cluster_abc123" \
|
||||
curl "https://platform.hanzo.ai/api/cluster.one?clusterId=cluster_abc123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
@@ -110,7 +110,7 @@ curl "https://app.platform.hanzo.ai/api/cluster.one?clusterId=cluster_abc123" \
|
||||
`POST /api/cluster.create`
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/cluster.create \
|
||||
curl -X POST https://platform.hanzo.ai/api/cluster.create \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -154,7 +154,7 @@ Cluster provisioning is asynchronous. The cluster enters `provisioning` status a
|
||||
`PATCH /api/cluster.update`
|
||||
|
||||
```bash
|
||||
curl -X PATCH https://app.platform.hanzo.ai/api/cluster.update \
|
||||
curl -X PATCH https://platform.hanzo.ai/api/cluster.update \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -168,7 +168,7 @@ curl -X PATCH https://app.platform.hanzo.ai/api/cluster.update \
|
||||
`POST /api/cluster.addNode`
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/cluster.addNode \
|
||||
curl -X POST https://platform.hanzo.ai/api/cluster.addNode \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -185,7 +185,7 @@ The node joins the cluster automatically. For Kubernetes clusters, it is registe
|
||||
`DELETE /api/cluster.removeNode`
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://app.platform.hanzo.ai/api/cluster.removeNode \
|
||||
curl -X DELETE https://platform.hanzo.ai/api/cluster.removeNode \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -203,7 +203,7 @@ Workloads on the removed node are automatically rescheduled to remaining nodes.
|
||||
`DELETE /api/cluster.remove`
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://app.platform.hanzo.ai/api/cluster.remove \
|
||||
curl -X DELETE https://platform.hanzo.ai/api/cluster.remove \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
||||
@@ -1,281 +1,106 @@
|
||||
---
|
||||
title: Containers API
|
||||
description: Deploy, manage, and monitor container workloads
|
||||
description: Deploy, redeploy, and operate container workloads
|
||||
---
|
||||
|
||||
The Containers API manages container deployments across your clusters. It supports Docker images from any registry and handles the full deployment lifecycle.
|
||||
A **container** is a deployable workload on Hanzo PaaS. There are two ways to act on one:
|
||||
|
||||
## Endpoints
|
||||
- the canonical **`/v1` container surface** — a machine-driven, zero-downtime **redeploy** (rolling restart) of a running workload, authenticated with a service token;
|
||||
- the **`/api` build pipeline** — the Dokploy-derived tRPC procedures (`application.*`) that register sources and run builds, authenticated with an API key.
|
||||
|
||||
| Procedure | Method | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `container.all` | GET | List all containers in a project |
|
||||
| `container.one` | GET | Get container details |
|
||||
| `container.create` | POST | Create a new container deployment |
|
||||
| `container.update` | PATCH | Update container configuration |
|
||||
| `container.remove` | DELETE | Remove a container |
|
||||
| `container.deploy` | POST | Trigger a new deployment |
|
||||
| `container.restart` | POST | Restart running instances |
|
||||
| `container.stop` | POST | Stop a container |
|
||||
| `container.start` | POST | Start a stopped container |
|
||||
| `container.logs` | GET | Stream container logs |
|
||||
Under the hood a container maps to an `application` row whose `appName` is the live workload's name.
|
||||
|
||||
## List Containers
|
||||
|
||||
`GET /api/container.all?projectId=proj_abc123`
|
||||
## Redeploy a running container (`/v1`)
|
||||
|
||||
```bash
|
||||
curl "https://app.platform.hanzo.ai/api/container.all?projectId=proj_abc123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
curl -fsS -X POST \
|
||||
"https://platform.hanzo.ai/v1/org/$ORG_ID/project/$PROJECT_ID/env/$ENV_ID/container/$CONTAINER_ID/redeploy" \
|
||||
-H "Authorization: Bearer $PAAS_SERVICE_TOKEN"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"data": [
|
||||
{
|
||||
"id": "ctr_abc123",
|
||||
"name": "web",
|
||||
"image": "ghcr.io/myorg/web:v1.2.3",
|
||||
"status": "running",
|
||||
"replicas": 3,
|
||||
"port": 3000,
|
||||
"clusterId": "cluster_abc123",
|
||||
"createdAt": "2025-09-01T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "ctr_def456",
|
||||
"name": "worker",
|
||||
"image": "ghcr.io/myorg/worker:v1.2.3",
|
||||
"status": "running",
|
||||
"replicas": 1,
|
||||
"port": null,
|
||||
"clusterId": "cluster_abc123",
|
||||
"createdAt": "2025-09-01T10:05:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{ "ok": true }
|
||||
```
|
||||
|
||||
## Get Container
|
||||
This re-pulls the image and recreates pods with a rolling update — no new build. The service token comes from [Hanzo KMS](/docs/services/kms).
|
||||
|
||||
`GET /api/container.one?containerId=ctr_abc123`
|
||||
## Build pipeline procedures (`/api`)
|
||||
|
||||
Each procedure is an HTTP `POST` to `https://platform.hanzo.ai/api/<procedure>` with `x-api-key`.
|
||||
|
||||
| Procedure | Purpose |
|
||||
|-----------|---------|
|
||||
| `application.create` | Register a new application (does not deploy) |
|
||||
| `application.one` | Get application details |
|
||||
| `application.all` | List applications in a project |
|
||||
| `application.update` | Update configuration |
|
||||
| `application.deploy` | Build & deploy (manual deployment) |
|
||||
| `application.redeploy` | Rebuild an existing application |
|
||||
| `application.start` · `application.stop` · `application.reload` | Lifecycle operations |
|
||||
| `application.remove` | Delete the application |
|
||||
|
||||
### Create an application
|
||||
|
||||
`name` and `environmentId` are required; `appName` is generated if omitted.
|
||||
|
||||
```bash
|
||||
curl "https://app.platform.hanzo.ai/api/container.one?containerId=ctr_abc123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"data": {
|
||||
"id": "ctr_abc123",
|
||||
"name": "web",
|
||||
"image": "ghcr.io/myorg/web:v1.2.3",
|
||||
"status": "running",
|
||||
"replicas": 3,
|
||||
"port": 3000,
|
||||
"clusterId": "cluster_abc123",
|
||||
"env": [
|
||||
{ "key": "NODE_ENV", "value": "production" },
|
||||
{ "key": "DATABASE_URL", "value": "***" }
|
||||
],
|
||||
"resources": {
|
||||
"cpuLimit": "500m",
|
||||
"memoryLimit": "512Mi"
|
||||
},
|
||||
"domains": ["app.example.com"],
|
||||
"healthCheck": {
|
||||
"path": "/health",
|
||||
"port": 3000,
|
||||
"interval": 30
|
||||
},
|
||||
"createdAt": "2025-09-01T10:00:00Z",
|
||||
"lastDeployedAt": "2026-02-20T15:30:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Create Container
|
||||
|
||||
`POST /api/container.create`
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/container.create \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/application.create \
|
||||
-H "x-api-key: $PLATFORM_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"projectId": "proj_abc123",
|
||||
"clusterId": "cluster_abc123",
|
||||
"name": "web",
|
||||
"image": "ghcr.io/myorg/web:v1.2.3",
|
||||
"port": 3000,
|
||||
"replicas": 3,
|
||||
"env": [
|
||||
{ "key": "NODE_ENV", "value": "production" }
|
||||
],
|
||||
"resources": {
|
||||
"cpuLimit": "500m",
|
||||
"memoryLimit": "512Mi"
|
||||
},
|
||||
"healthCheck": {
|
||||
"path": "/health",
|
||||
"port": 3000,
|
||||
"interval": 30
|
||||
}
|
||||
"environmentId": "'$ENV_ID'",
|
||||
"description": "Public web app"
|
||||
}'
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
Attach a Docker image source with `application.saveDockerProvider` (or a Git source with `application.saveGithubProvider`), then deploy.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `projectId` | string | Yes | Project ID |
|
||||
| `clusterId` | string | Yes | Target cluster |
|
||||
| `name` | string | Yes | Container name |
|
||||
| `image` | string | Yes | Docker image reference |
|
||||
| `port` | number | No | Exposed port |
|
||||
| `replicas` | number | No | Number of instances (default: 1) |
|
||||
| `env` | array | No | Environment variables |
|
||||
| `resources` | object | No | CPU and memory limits |
|
||||
| `healthCheck` | object | No | Health check configuration |
|
||||
|
||||
**Response:** `201 Created`
|
||||
|
||||
## Update Container
|
||||
|
||||
`PATCH /api/container.update`
|
||||
|
||||
Update configuration without triggering a new deployment. Use `container.deploy` to roll out changes.
|
||||
### Deploy & redeploy
|
||||
|
||||
```bash
|
||||
curl -X PATCH https://app.platform.hanzo.ai/api/container.update \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
# Build and deploy
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/application.deploy \
|
||||
-H "x-api-key: $PLATFORM_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"containerId": "ctr_abc123",
|
||||
"replicas": 5,
|
||||
"env": [
|
||||
{ "key": "NODE_ENV", "value": "production" },
|
||||
{ "key": "LOG_LEVEL", "value": "info" }
|
||||
]
|
||||
}'
|
||||
```
|
||||
-d '{ "applicationId": "'$APP_ID'" }'
|
||||
|
||||
## Deploy
|
||||
|
||||
`POST /api/container.deploy`
|
||||
|
||||
Trigger a new deployment with an updated image tag or configuration.
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/container.deploy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
# Rebuild an existing application
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/application.redeploy \
|
||||
-H "x-api-key: $PLATFORM_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"containerId": "ctr_abc123",
|
||||
"image": "ghcr.io/myorg/web:v1.3.0"
|
||||
}'
|
||||
-d '{ "applicationId": "'$APP_ID'", "title": "ship v1.3.0" }'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
`application.deploy` and `application.redeploy` enqueue a build job and return after it is scheduled. Deployments roll out with a zero-downtime strategy by default.
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"data": {
|
||||
"deploymentId": "dep_xyz789",
|
||||
"status": "in_progress",
|
||||
"previousImage": "ghcr.io/myorg/web:v1.2.3",
|
||||
"newImage": "ghcr.io/myorg/web:v1.3.0",
|
||||
"triggeredAt": "2026-02-26T10:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Deployments use a rolling update strategy by default. If the health check fails, the deployment is automatically rolled back.
|
||||
|
||||
## Lifecycle Operations
|
||||
|
||||
### Restart
|
||||
### Lifecycle
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/container.restart \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "containerId": "ctr_abc123" }'
|
||||
# Stop / start / restart-in-place
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/application.stop -H "x-api-key: $PLATFORM_API_KEY" -d '{"applicationId":"'$APP_ID'"}'
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/application.start -H "x-api-key: $PLATFORM_API_KEY" -d '{"applicationId":"'$APP_ID'"}'
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/application.reload -H "x-api-key: $PLATFORM_API_KEY" -d '{"applicationId":"'$APP_ID'","appName":"'$APP_NAME'"}'
|
||||
```
|
||||
|
||||
### Stop
|
||||
## Inspect & observe
|
||||
|
||||
Use the [observe surface](/docs/services/platform) to see declared vs running vs latest image tags and drift:
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/container.stop \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "containerId": "ctr_abc123" }'
|
||||
curl -fsS "https://platform.hanzo.ai/v1/apps?org=hanzo" \
|
||||
-H "Authorization: Bearer $PLATFORM_SERVICE_TOKEN"
|
||||
```
|
||||
|
||||
### Start
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/container.start \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "containerId": "ctr_abc123" }'
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
`GET /api/container.logs?containerId=ctr_abc123`
|
||||
|
||||
Stream logs from all replicas of a container.
|
||||
|
||||
```bash
|
||||
curl "https://app.platform.hanzo.ai/api/container.logs?containerId=ctr_abc123&tail=100" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
**Query parameters:**
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `containerId` | string | -- | Container ID |
|
||||
| `tail` | number | 100 | Number of recent lines |
|
||||
| `since` | string | -- | ISO 8601 timestamp |
|
||||
| `follow` | boolean | false | Stream logs in real time |
|
||||
|
||||
## Delete Container
|
||||
|
||||
`DELETE /api/container.remove`
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://app.platform.hanzo.ai/api/container.remove \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "containerId": "ctr_abc123" }'
|
||||
```
|
||||
|
||||
**Response:** `204 No Content`
|
||||
|
||||
<Callout type="warn">
|
||||
Deleting a container stops all running instances and removes associated domains and volumes. This cannot be undone.
|
||||
</Callout>
|
||||
|
||||
## Container Status Values
|
||||
## Application status values
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| `creating` | Container is being provisioned |
|
||||
| `running` | All replicas are healthy |
|
||||
| `deploying` | A new deployment is rolling out |
|
||||
| `stopped` | Container is stopped |
|
||||
| `error` | One or more replicas have failed |
|
||||
| `deleting` | Container is being removed |
|
||||
| `idle` | Registered, not running |
|
||||
| `running` | Deployed and serving |
|
||||
| `done` | Last deployment completed |
|
||||
| `error` | A deployment or replica failed |
|
||||
|
||||
<Callout type="warn">
|
||||
`application.remove` stops all instances and removes associated domains and volumes. This cannot be undone.
|
||||
</Callout>
|
||||
|
||||
@@ -5,44 +5,37 @@ description: Platform REST and tRPC API for managing organizations, clusters, co
|
||||
|
||||
Hanzo Platform exposes two API surfaces: **tRPC** for the dashboard and **REST** for external integrations. Both share the same authentication and authorization layer.
|
||||
|
||||
## Base URL
|
||||
## Base URLs
|
||||
|
||||
```
|
||||
https://app.platform.hanzo.ai/api
|
||||
```
|
||||
| Surface | Base | Auth | Use |
|
||||
|---------|------|------|-----|
|
||||
| **Canonical control** | `https://platform.hanzo.ai/v1` | `Authorization: Bearer <service-token>` | Container redeploy, observe/drift (machine) |
|
||||
| **Build pipeline** | `https://platform.hanzo.ai/api` | `x-api-key: <key>` | Build/deploy applications via tRPC-over-OpenAPI |
|
||||
|
||||
All endpoints are relative to this base URL.
|
||||
|
||||
## API Surfaces
|
||||
|
||||
| Surface | Protocol | Use Case |
|
||||
|---------|----------|----------|
|
||||
| **tRPC** | HTTP POST with JSON | Dashboard, SDK, programmatic access |
|
||||
| **REST** | Standard HTTP methods | External integrations, webhooks, CI/CD |
|
||||
|
||||
The tRPC API uses procedure calls (e.g., `organization.all`, `cluster.create`). The REST API uses standard resource paths (e.g., `GET /clusters`, `POST /containers`).
|
||||
The `/v1` surface is the canonical Hanzo control plane. The `/api` surface is the Dokploy-derived build pipeline, where each endpoint is a tRPC procedure exposed over HTTP as `<router>.<procedure>` (e.g. `application.create`, `domain.create`).
|
||||
|
||||
## Authentication
|
||||
|
||||
All requests require a Bearer token in the `Authorization` header:
|
||||
The build-pipeline surface accepts an API key (machine) or an IAM bearer token (interactive):
|
||||
|
||||
```bash
|
||||
curl https://app.platform.hanzo.ai/api/organization.all \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
curl https://platform.hanzo.ai/api/organization.all \
|
||||
-H "x-api-key: $PLATFORM_API_KEY"
|
||||
```
|
||||
|
||||
See [Authentication](/docs/services/platform/api/authentication) for details on obtaining tokens.
|
||||
The canonical `/v1` surface uses a service token: `Authorization: Bearer $PAAS_SERVICE_TOKEN`. Both credential types come from [Hanzo KMS](/docs/services/kms). See [Authentication](/docs/services/platform/api/authentication) for details.
|
||||
|
||||
## tRPC Procedures
|
||||
|
||||
The platform exposes 16 tRPC routers:
|
||||
The build pipeline exposes these tRPC routers:
|
||||
|
||||
| Router | Description |
|
||||
|--------|-------------|
|
||||
| `organization` | Organization CRUD |
|
||||
| `orgTeam` | Team member management |
|
||||
| `cluster` | Cluster lifecycle |
|
||||
| `container` | Container deployments |
|
||||
| `application` | Application deployments (the unit a container maps to) |
|
||||
| `compose` | Multi-service compose/stack units |
|
||||
| `project` | Project management |
|
||||
| `environment` | Environment configuration |
|
||||
| `build` | Build pipelines |
|
||||
@@ -61,7 +54,7 @@ The platform exposes 16 tRPC routers:
|
||||
tRPC requests use HTTP POST with a JSON body:
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/cluster.create \
|
||||
curl -X POST https://platform.hanzo.ai/api/cluster.create \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
||||
@@ -22,7 +22,7 @@ The Organizations API manages the top-level entity in Hanzo Platform. Every clus
|
||||
Returns all organizations the authenticated user is a member of.
|
||||
|
||||
```bash
|
||||
curl https://app.platform.hanzo.ai/api/organization.all \
|
||||
curl https://platform.hanzo.ai/api/organization.all \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
@@ -58,7 +58,7 @@ curl https://app.platform.hanzo.ai/api/organization.all \
|
||||
`GET /api/organization.one?organizationId=org_abc123`
|
||||
|
||||
```bash
|
||||
curl "https://app.platform.hanzo.ai/api/organization.one?organizationId=org_abc123" \
|
||||
curl "https://platform.hanzo.ai/api/organization.one?organizationId=org_abc123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
@@ -92,7 +92,7 @@ curl "https://app.platform.hanzo.ai/api/organization.one?organizationId=org_abc1
|
||||
`POST /api/organization.create`
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/organization.create \
|
||||
curl -X POST https://platform.hanzo.ai/api/organization.create \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -130,7 +130,7 @@ The authenticated user is automatically assigned the **Owner** role.
|
||||
`PATCH /api/organization.update`
|
||||
|
||||
```bash
|
||||
curl -X PATCH https://app.platform.hanzo.ai/api/organization.update \
|
||||
curl -X PATCH https://platform.hanzo.ai/api/organization.update \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -155,7 +155,7 @@ Requires **Owner** or **Admin** role.
|
||||
`DELETE /api/organization.remove`
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://app.platform.hanzo.ai/api/organization.remove \
|
||||
curl -X DELETE https://platform.hanzo.ai/api/organization.remove \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -181,11 +181,11 @@ Team members are managed via the `orgTeam` router:
|
||||
|
||||
```bash
|
||||
# List members
|
||||
curl "https://app.platform.hanzo.ai/api/orgTeam.all?organizationId=org_abc123" \
|
||||
curl "https://platform.hanzo.ai/api/orgTeam.all?organizationId=org_abc123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Change role
|
||||
curl -X PATCH https://app.platform.hanzo.ai/api/orgTeam.update \
|
||||
curl -X PATCH https://platform.hanzo.ai/api/orgTeam.update \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -195,7 +195,7 @@ curl -X PATCH https://app.platform.hanzo.ai/api/orgTeam.update \
|
||||
}'
|
||||
|
||||
# Remove member
|
||||
curl -X DELETE https://app.platform.hanzo.ai/api/orgTeam.remove \
|
||||
curl -X DELETE https://platform.hanzo.ai/api/orgTeam.remove \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
||||
@@ -26,7 +26,7 @@ The Virtual Machines API manages Visor VMs -- full Linux virtual machines with r
|
||||
`GET /api/vm.all?organizationId=org_abc123`
|
||||
|
||||
```bash
|
||||
curl "https://app.platform.hanzo.ai/api/vm.all?organizationId=org_abc123" \
|
||||
curl "https://platform.hanzo.ai/api/vm.all?organizationId=org_abc123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
@@ -56,7 +56,7 @@ curl "https://app.platform.hanzo.ai/api/vm.all?organizationId=org_abc123" \
|
||||
`GET /api/vm.one?vmId=vm_abc123`
|
||||
|
||||
```bash
|
||||
curl "https://app.platform.hanzo.ai/api/vm.one?vmId=vm_abc123" \
|
||||
curl "https://platform.hanzo.ai/api/vm.one?vmId=vm_abc123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
@@ -100,7 +100,7 @@ curl "https://app.platform.hanzo.ai/api/vm.one?vmId=vm_abc123" \
|
||||
`POST /api/vm.launch`
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/vm.launch \
|
||||
curl -X POST https://platform.hanzo.ai/api/vm.launch \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -161,7 +161,7 @@ The VM enters `provisioning` status. It transitions to `running` with an assigne
|
||||
Gracefully shuts down the VM. The VM retains its IP address and storage. You are not billed for compute while stopped, but storage charges continue.
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/vm.stop \
|
||||
curl -X POST https://platform.hanzo.ai/api/vm.stop \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "vmId": "vm_abc123" }'
|
||||
@@ -174,7 +174,7 @@ curl -X POST https://app.platform.hanzo.ai/api/vm.stop \
|
||||
Boots a stopped VM. The VM retains its previous IP and all attached volumes.
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/vm.start \
|
||||
curl -X POST https://platform.hanzo.ai/api/vm.start \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "vmId": "vm_abc123" }'
|
||||
@@ -187,7 +187,7 @@ curl -X POST https://app.platform.hanzo.ai/api/vm.start \
|
||||
Performs a graceful reboot.
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/vm.restart \
|
||||
curl -X POST https://platform.hanzo.ai/api/vm.restart \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "vmId": "vm_abc123" }'
|
||||
@@ -200,7 +200,7 @@ curl -X POST https://app.platform.hanzo.ai/api/vm.restart \
|
||||
Permanently destroys the VM and its boot disk. Attached volumes are detached but not deleted.
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://app.platform.hanzo.ai/api/vm.destroy \
|
||||
curl -X DELETE https://platform.hanzo.ai/api/vm.destroy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "vmId": "vm_abc123" }'
|
||||
@@ -217,14 +217,14 @@ Destroying a VM deletes its boot disk permanently. Attached volumes are preserve
|
||||
### List Volumes
|
||||
|
||||
```bash
|
||||
curl "https://app.platform.hanzo.ai/api/vm.volumes?vmId=vm_abc123" \
|
||||
curl "https://platform.hanzo.ai/api/vm.volumes?vmId=vm_abc123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### Attach Volume
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/vm.attachVolume \
|
||||
curl -X POST https://platform.hanzo.ai/api/vm.attachVolume \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -239,7 +239,7 @@ The volume is attached as a block device. If `mountPath` is provided, the platfo
|
||||
### Detach Volume
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.platform.hanzo.ai/api/vm.detachVolume \
|
||||
curl -X POST https://platform.hanzo.ai/api/vm.detachVolume \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
||||
@@ -1,435 +1,193 @@
|
||||
---
|
||||
title: Hanzo Platform
|
||||
description: GitOps deployment platform for applications, databases, containers, and custom domains with IAM-integrated multi-org support.
|
||||
title: Hanzo PaaS
|
||||
description: The platform.hanzo.ai control plane — deploy and operate applications, containers, and custom domains across your clusters, with IAM-native multi-org access.
|
||||
---
|
||||
|
||||
# Hanzo Platform
|
||||
# Hanzo PaaS
|
||||
|
||||
Hanzo Platform is a self-hosted Platform as a Service (PaaS) for deploying and
|
||||
managing applications, databases, and containers. It provides Git-based
|
||||
deployments, Docker orchestration, auto-scaling, SSL/TLS certificates, custom
|
||||
domains, environment variable management, and real-time monitoring -- all
|
||||
integrated with Hanzo IAM for multi-organization access control.
|
||||
Hanzo PaaS (`platform.hanzo.ai`) is the control plane for shipping software on Hanzo infrastructure. You register an application, point it at a Docker image or Git repo, attach a domain, and the platform builds, deploys, and operates it on your clusters — with deployment history, rolling updates, and drift detection. Authentication is delegated to [Hanzo IAM](/docs/services/iam) with per-organization isolation.
|
||||
|
||||
**Studio:** `platform.hanzo.ai`
|
||||
**API:** `api.platform.hanzo.ai`
|
||||
**Sync:** `sync.platform.hanzo.ai`
|
||||
**Webhook:** `webhook.platform.hanzo.ai`
|
||||
**OAuth:** `oauth.platform.hanzo.ai`
|
||||
**Source:** [github.com/hanzoai/platform](https://github.com/hanzoai/platform)
|
||||
| | |
|
||||
|---|---|
|
||||
| **Studio** | `https://platform.hanzo.ai` |
|
||||
| **Canonical API** | `https://platform.hanzo.ai/v1` |
|
||||
| **Build pipeline API** | `https://platform.hanzo.ai/api` (tRPC + OpenAPI) |
|
||||
| **Auth** | Hanzo IAM (interactive) · API key / service token (machine) |
|
||||
| **Source** | [github.com/hanzoai/platform](https://github.com/hanzoai/platform) |
|
||||
|
||||
## Features
|
||||
## The model
|
||||
|
||||
- **Git-Based Deployments** -- Push to deploy from GitHub, GitLab, Gitea, or
|
||||
Bitbucket. Automatic builds on push with configurable branch tracking.
|
||||
- **Docker Container Orchestration** -- Native Docker Compose support, Swarm
|
||||
mode for multi-node clusters, and direct container management.
|
||||
- **Auto-Scaling** -- Horizontal scaling with configurable replicas, rolling
|
||||
updates with zero downtime, and pod anti-affinity for high availability.
|
||||
- **SSL/TLS Certificates** -- Automatic certificate provisioning and renewal
|
||||
via cert-manager with Let's Encrypt integration.
|
||||
- **Custom Domains** -- Map any domain to your application with automatic DNS
|
||||
verification and TLS termination.
|
||||
- **Environment Variables** -- Secure environment variable management per
|
||||
application with KMS integration for secrets.
|
||||
- **Managed Databases** -- One-click provisioning for PostgreSQL, MySQL,
|
||||
MariaDB, MongoDB, and Redis with automated backups.
|
||||
- **Real-Time Monitoring** -- Live CPU, memory, storage, and network metrics
|
||||
for every deployed resource.
|
||||
- **Multi-Org Support** -- Full IAM integration via Hanzo IAM (hanzo.id) with
|
||||
per-organization project isolation and RBAC.
|
||||
- **Preview Deployments** -- Automatic per-PR preview environments with
|
||||
unique URLs for review workflows.
|
||||
- **Rollbacks** -- One-click rollback to any previous deployment version with full context preservation and registry authentication.
|
||||
- **Volume Backups** -- Scheduled volume backups with S3 destination, retention policies, and success/error notifications.
|
||||
- **Notifications** -- Deploy status alerts via Slack, Discord, Telegram,
|
||||
and email.
|
||||
- **AI Assistant** -- Built-in AI provider selection for deployment assistance.
|
||||
|
||||
## Architecture
|
||||
|
||||
Hanzo Platform runs as six coordinated services on Kubernetes:
|
||||
Hanzo PaaS organizes work into a simple hierarchy:
|
||||
|
||||
```
|
||||
platform.hanzo.ai
|
||||
|
|
||||
+-----------+
|
||||
| Ingress |
|
||||
| (nginx) |
|
||||
+-----------+
|
||||
/ | | \
|
||||
/ | | \
|
||||
+------+ +----+ +----+ +-------+
|
||||
|Studio| |API | |Sync| |Webhook|
|
||||
| (UI) | | | | | | (Go) |
|
||||
+------+ +----+ +----+ +-------+
|
||||
:4000 :4000 :4000 :8080
|
||||
| |
|
||||
+---------+------+---------+
|
||||
| | |
|
||||
+-------+ +-------+ +---------+
|
||||
|MongoDB| | Redis | | Storage |
|
||||
| | | | |(Hanzo S3)|
|
||||
+-------+ +-------+ +---------+
|
||||
organization
|
||||
└── project
|
||||
└── environment (e.g. production, staging)
|
||||
├── application (a Docker image or Git source → one workload)
|
||||
└── compose (a multi-service docker-compose / stack unit)
|
||||
```
|
||||
|
||||
### Service Components
|
||||
| Object | What it is |
|
||||
|--------|------------|
|
||||
| **project** | A grouping of related services, owned by an organization |
|
||||
| **environment** | An isolated slice of a project (production, staging, preview) |
|
||||
| **application** | A single deployable unit. Its `appName` is the name of the live workload it drives. `sourceType` is one of `docker`, `git`, `github`, `gitlab`, `bitbucket`, `gitea`, or `drop`; `deployTarget` is `local`, `cloud`, or `k8s` |
|
||||
| **deployment** | One build/deploy run, with status and start/finish timestamps |
|
||||
| **domain** | A hostname mapped to an application or compose service, with automatic TLS |
|
||||
|
||||
| Service | Image | Replicas | Port | Purpose |
|
||||
|---------|-------|----------|------|---------|
|
||||
| **Platform** (API) | `ghcr.io/hanzoai/paas-api` | 2 | 4000 | Core API server, deployment orchestration, RBAC |
|
||||
| **Studio** (UI) | `ghcr.io/hanzoai/paas-ui` | 2 | 4000 | Web dashboard (React, Hanzo-branded) |
|
||||
| **Monitor** | `ghcr.io/hanzoai/paas-monitor` | 1 | 4000 | Resource metrics collection and alerting |
|
||||
| **Sync** | `ghcr.io/hanzoai/paas-sync` | 2 | 4000 | Real-time WebSocket state synchronization |
|
||||
| **Webhook** | `ghcr.io/hanzoai/paas-webhook` | 1 | 8080 | Git provider webhook receiver (Go) |
|
||||
| **OAuth Proxy** | `ghcr.io/hanzoai/platform-oauth-proxy` | 1 | - | IAM OAuth2 proxy at oauth.platform.hanzo.ai |
|
||||
State is stored in [Hanzo Base](/docs/services/base) (embedded SQLite, replicated to object storage) — there is no external database to run.
|
||||
|
||||
### Studio Branding
|
||||
## Authentication
|
||||
|
||||
The Studio UI uses Hanzo's visual identity:
|
||||
- Pure black background (`#000000`)
|
||||
- Monochrome white Hanzo "H" logo
|
||||
- Hanzo red (`#fd4444`) accent color for interactive elements
|
||||
Two credential types, both retrieved from [Hanzo KMS](/docs/services/kms) — never hard-coded:
|
||||
|
||||
### Data Stores
|
||||
| Credential | Header | Use |
|
||||
|------------|--------|-----|
|
||||
| **API key** | `x-api-key: <key>` | Machine access to the build-pipeline API (`/api/*`). Org-scoped; issued under **Settings → API Keys** |
|
||||
| **Service token** | `Authorization: Bearer <token>` | The canonical `/v1` container surface (redeploy, observe) |
|
||||
| **IAM token** | `Authorization: Bearer <iam-jwt>` | Interactive / user access, via [Hanzo IAM](/docs/services/iam) SSO |
|
||||
|
||||
| Store | Purpose |
|
||||
|-------|---------|
|
||||
| **MongoDB** | Application state, project configs, deployment history |
|
||||
| **Redis** | Cache, real-time sync pub/sub, session state |
|
||||
| **Hanzo S3** | Build artifacts, backup storage, asset uploads |
|
||||
## Deploy
|
||||
|
||||
## Quick Start
|
||||
### From the Studio
|
||||
|
||||
### Deploy via Studio UI
|
||||
1. Sign in at [platform.hanzo.ai](https://platform.hanzo.ai) with your Hanzo IAM account.
|
||||
2. Create a project and an environment.
|
||||
3. Add an application — choose a Docker image or connect a Git repository.
|
||||
4. Attach a domain and deploy.
|
||||
|
||||
1. Navigate to [platform.hanzo.ai](https://platform.hanzo.ai)
|
||||
2. Sign in with your Hanzo IAM account
|
||||
3. Create a new project
|
||||
4. Connect your Git repository (GitHub, GitLab, Gitea, or Bitbucket)
|
||||
5. Configure build settings and environment variables
|
||||
6. Deploy
|
||||
### Redeploy a running container (canonical `/v1`)
|
||||
|
||||
### Deploy via API
|
||||
|
||||
Create a project and deploy an application using the Platform API:
|
||||
The canonical, machine-driven redeploy performs a zero-downtime rolling restart of the live workload — it re-pulls the image and recreates pods. It takes a service token and no body:
|
||||
|
||||
```bash
|
||||
# Authenticate (get token from hanzo.id)
|
||||
export TOKEN="your-iam-access-token"
|
||||
# Service token comes from KMS — never inline a real value
|
||||
TOKEN=$(curl -sS -X POST https://kms.hanzo.ai/v1/kms/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}" | jq -r .accessToken)
|
||||
PAAS_TOKEN=$(curl -sS "https://kms.hanzo.ai/v1/kms/orgs/hanzo/secrets/paas/SERVICE_TOKEN?env=production" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq -r .secret.value)
|
||||
|
||||
# Create a project
|
||||
curl -X POST https://api.platform.hanzo.ai/api/project.create \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "my-app",
|
||||
"description": "My application"
|
||||
}'
|
||||
|
||||
# Create an application in the project
|
||||
curl -X POST https://api.platform.hanzo.ai/api/application.create \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "web",
|
||||
"projectId": "PROJECT_ID",
|
||||
"sourceType": "github",
|
||||
"repository": "https://github.com/org/repo",
|
||||
"branch": "main",
|
||||
"buildPath": "/",
|
||||
"publishDirectory": "dist"
|
||||
}'
|
||||
|
||||
# Trigger a deployment
|
||||
curl -X POST https://api.platform.hanzo.ai/api/application.deploy \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"applicationId": "APP_ID"
|
||||
}'
|
||||
curl -fsS -X POST \
|
||||
"https://platform.hanzo.ai/v1/org/$ORG_ID/project/$PROJECT_ID/env/$ENV_ID/container/$APP_ID/redeploy" \
|
||||
-H "Authorization: Bearer $PAAS_TOKEN"
|
||||
# → {"ok":true}
|
||||
```
|
||||
|
||||
### Via Unified Gateway
|
||||
### Build and deploy (pipeline `/api`)
|
||||
|
||||
All Platform API endpoints are also accessible through the Hanzo API gateway:
|
||||
To register a new app and run a build through the deployment pipeline, use the OpenAPI surface with an API key. Procedures are `<router>.<procedure>`:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer hk-your-api-key" \
|
||||
https://api.hanzo.ai/v1/platform/projects
|
||||
# 1. Register the application (does not deploy)
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/application.create \
|
||||
-H "x-api-key: $PLATFORM_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "name": "web", "environmentId": "'$ENV_ID'" }'
|
||||
|
||||
# 2. Attach a Docker image source, then deploy
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/application.deploy \
|
||||
-H "x-api-key: $PLATFORM_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "applicationId": "'$APP_ID'" }'
|
||||
|
||||
# Rebuild an existing application
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/application.redeploy \
|
||||
-H "x-api-key: $PLATFORM_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "applicationId": "'$APP_ID'", "title": "ship v1.3.0" }'
|
||||
```
|
||||
|
||||
## Deployment Workflow
|
||||
`application.deploy` and `application.redeploy` enqueue a build job; `application.start`, `application.stop`, and `application.reload` manage the running workload. See the [Containers API](/docs/services/platform/api/containers) for the full procedure set.
|
||||
|
||||
### Git Push Workflow
|
||||
## Observe & drift
|
||||
|
||||
```
|
||||
Developer pushes code
|
||||
|
|
||||
v
|
||||
+----------+ +---------+ +----------+
|
||||
| Git Host | ----> |Webhook | ----> | Platform |
|
||||
| (GitHub) | POST | Service | | API |
|
||||
+----------+ +---------+ +----------+
|
||||
|
|
||||
+--------+--------+
|
||||
| |
|
||||
Build Image Run Migrations
|
||||
| |
|
||||
+--------+--------+
|
||||
|
|
||||
Rolling Deploy
|
||||
|
|
||||
Health Check Pass
|
||||
|
|
||||
Traffic Switched
|
||||
```
|
||||
The platform continuously reconciles three views of every app and computes drift:
|
||||
|
||||
1. **Push**: Developer pushes to the configured branch
|
||||
2. **Webhook**: Git provider sends a webhook to `webhook.platform.hanzo.ai`
|
||||
3. **Build**: Platform builds a Docker image from source (Nixpacks, Dockerfile, or Buildpacks)
|
||||
4. **Deploy**: Rolling update with zero downtime -- new pods start before old ones terminate
|
||||
5. **Health Check**: Readiness probes confirm the new deployment is serving traffic
|
||||
6. **Notify**: Deployment status sent to configured notification channels
|
||||
|
||||
### Build Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| **Nixpacks** | Auto-detected build packs for Node.js, Python, Go, Ruby, PHP, Rust, Java |
|
||||
| **Dockerfile** | Custom Dockerfile in repository root or specified path |
|
||||
| **Docker Compose** | Full `compose.yml` support for multi-container applications |
|
||||
| **Buildpacks** | Cloud Native Buildpacks (CNB) for standardized builds |
|
||||
|
||||
### Deployment Strategies
|
||||
|
||||
| Strategy | Description |
|
||||
|----------|-------------|
|
||||
| **Rolling Update** | Default. `maxSurge: 1, maxUnavailable: 0` for zero-downtime deploys |
|
||||
| **Recreate** | Stop existing pods before starting new ones (for stateful workloads) |
|
||||
| **Preview** | Per-PR ephemeral environments with unique URLs |
|
||||
|
||||
## Custom Domains
|
||||
|
||||
Add custom domains to any deployed application:
|
||||
| Tag | Meaning |
|
||||
|-----|---------|
|
||||
| `declared` | The version your operator/manifest declares |
|
||||
| `running` | The image actually live in the cluster |
|
||||
| `latest` | The newest released version available |
|
||||
|
||||
```bash
|
||||
# Add a domain
|
||||
curl -X POST https://api.platform.hanzo.ai/api/domain.create \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
# List apps with drift, scoped to an org
|
||||
curl -fsS "https://platform.hanzo.ai/v1/apps?org=hanzo&drift=true" \
|
||||
-H "Authorization: Bearer $PLATFORM_SERVICE_TOKEN"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"apps": [
|
||||
{ "org": "hanzo", "app": "docs", "env": "production",
|
||||
"declared_tag": "v1.4.0", "running_tag": "v1.3.0", "latest_tag": "v1.4.0",
|
||||
"health": "yellow", "drift": ["un-rolled"] }
|
||||
],
|
||||
"summary": { "total": 1, "byDrift": { "un-rolled": 1 } }
|
||||
}
|
||||
```
|
||||
|
||||
Drift is never stored — it is recomputed from declared/running/latest each time. `POST /v1/apps/sync` refreshes the observed columns from the cluster and release feeds.
|
||||
|
||||
## Custom domains & TLS
|
||||
|
||||
Attach a hostname to an application; TLS is provisioned automatically (Let's Encrypt) and a DNS record is created:
|
||||
|
||||
```bash
|
||||
curl -fsS -X POST https://platform.hanzo.ai/api/domain.create \
|
||||
-H "x-api-key: $PLATFORM_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"applicationId": "APP_ID",
|
||||
"applicationId": "'$APP_ID'",
|
||||
"host": "app.example.com",
|
||||
"https": true,
|
||||
"certificateType": "letsencrypt"
|
||||
}'
|
||||
```
|
||||
|
||||
### DNS Configuration
|
||||
Point your DNS at the platform ingress IP shown on the project's **Domains** tab.
|
||||
|
||||
Point your domain to the Platform ingress:
|
||||
|
||||
| Record Type | Name | Value |
|
||||
|-------------|------|-------|
|
||||
| A | `app.example.com` | `24.199.76.156` |
|
||||
| CNAME | `app.example.com` | `platform.hanzo.ai` |
|
||||
|
||||
SSL/TLS certificates are automatically provisioned and renewed via
|
||||
cert-manager with Let's Encrypt. Wildcard certificates are supported using
|
||||
DNS-01 challenges.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Manage environment variables per application with support for secrets:
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
curl -X POST https://api.platform.hanzo.ai/api/environment.create \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"applicationId": "APP_ID",
|
||||
"variables": {
|
||||
"DATABASE_URL": "postgresql://user:pass@db:5432/app",
|
||||
"REDIS_URL": "redis://redis:6379",
|
||||
"API_KEY": "secret-value"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### KMS Integration
|
||||
|
||||
For sensitive values, Platform integrates with [Hanzo KMS](/docs/services/kms)
|
||||
to sync secrets directly from KMS vaults:
|
||||
|
||||
- Secrets are referenced by KMS path, never stored in Platform's database
|
||||
- Automatic rotation when KMS secrets are updated
|
||||
- Audit trail for all secret access via KMS logs
|
||||
|
||||
### Variable Scoping
|
||||
|
||||
| Scope | Description |
|
||||
|-------|-------------|
|
||||
| **Application** | Available only to a specific application |
|
||||
| **Project** | Shared across all applications in a project |
|
||||
| **Global** | Available to all projects in an organization |
|
||||
| **Build-time** | Injected during the build phase only |
|
||||
| **Runtime** | Injected at container start only |
|
||||
|
||||
## Managed Databases
|
||||
|
||||
Platform can provision and manage database instances:
|
||||
|
||||
| Database | Versions | Backups | Replicas |
|
||||
|----------|----------|---------|----------|
|
||||
| PostgreSQL | 14, 15, 16 | Automated daily | Read replicas |
|
||||
| MySQL | 8.0, 8.4 | Automated daily | Read replicas |
|
||||
| MariaDB | 10.11, 11.x | Automated daily | Read replicas |
|
||||
| MongoDB | 6.0, 7.0 | Automated daily | Replica sets |
|
||||
| Redis | 7.x | Snapshot | Sentinel HA |
|
||||
|
||||
### Automated Backups
|
||||
|
||||
Backups are stored in Hanzo S3 and can be configured with:
|
||||
- Custom retention policies
|
||||
- Point-in-time recovery (PostgreSQL, MySQL)
|
||||
- Cross-region backup destinations
|
||||
- Backup encryption at rest
|
||||
|
||||
## Monitoring
|
||||
|
||||
Platform provides real-time resource monitoring for all deployed services.
|
||||
|
||||
### Metrics Collected
|
||||
|
||||
| Metric | Granularity | Retention |
|
||||
|--------|-------------|-----------|
|
||||
| CPU usage | Per container | 30 days |
|
||||
| Memory usage | Per container | 30 days |
|
||||
| Network I/O | Per container | 30 days |
|
||||
| Disk usage | Per volume | 30 days |
|
||||
| HTTP requests | Per application | 30 days |
|
||||
| Response times | Per application | 30 days |
|
||||
|
||||
### Accessing Metrics
|
||||
|
||||
View metrics in the Studio dashboard under **Monitoring**, or query via API:
|
||||
|
||||
```bash
|
||||
curl https://api.platform.hanzo.ai/api/monitoring.stats \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-G -d "applicationId=APP_ID" \
|
||||
-d "period=24h"
|
||||
```
|
||||
|
||||
### Alerts
|
||||
|
||||
Configure alerts for resource thresholds:
|
||||
- CPU usage exceeds percentage
|
||||
- Memory usage exceeds limit
|
||||
- Container restart count
|
||||
- Health check failures
|
||||
- Deployment failures
|
||||
|
||||
Notifications can be sent to Slack, Discord, Telegram, or email.
|
||||
|
||||
## Authentication
|
||||
|
||||
Platform uses Hanzo IAM (hanzo.id) for all authentication:
|
||||
|
||||
| Provider | Method |
|
||||
|----------|--------|
|
||||
| **Hanzo IAM** | OAuth2/OIDC SSO via hanzo.id (primary) |
|
||||
| **GitHub** | Git provider OAuth for repository access |
|
||||
| **GitLab** | Git provider OAuth for repository access |
|
||||
| **Bitbucket** | Git provider OAuth for repository access |
|
||||
|
||||
The OAuth proxy at `oauth.platform.hanzo.ai` handles the IAM authorization
|
||||
code exchange. The Studio login page uses `provider=hanzo` to initiate the
|
||||
Better Auth generic OAuth flow against hanzo.id.
|
||||
|
||||
### IAM Application
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| App Name | `app-platform` |
|
||||
| Organization | `hanzo` |
|
||||
| IAM Endpoint | `https://hanzo.id` |
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
### Docker Compose (Local Development)
|
||||
## Self-hosting
|
||||
|
||||
```bash
|
||||
git clone https://github.com/hanzoai/platform
|
||||
cd platform
|
||||
cp .env.example .env
|
||||
# Edit .env with your configuration
|
||||
cp .env.example .env # set IAM endpoint, service token, etc.
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Kubernetes (Production)
|
||||
|
||||
The recommended production deployment uses the K8s manifests in
|
||||
`universe/infra/k8s/paas/`:
|
||||
|
||||
```bash
|
||||
kubectl apply -k infra/k8s/paas/
|
||||
```
|
||||
|
||||
This creates:
|
||||
- Platform API (2 replicas, rolling update)
|
||||
- Studio UI (2 replicas, rolling update)
|
||||
- Monitor (1 replica)
|
||||
- Sync (2 replicas, rolling update)
|
||||
- Webhook (1 replica)
|
||||
- Ingress rules for all subdomains
|
||||
- RBAC service account (`hanzo-paas-sa`)
|
||||
- Pod disruption budgets
|
||||
|
||||
### Required Secrets
|
||||
|
||||
| Secret | Keys |
|
||||
|--------|------|
|
||||
| `mongodb` | `databaseURI`, `username`, `password` |
|
||||
| `redis` | `hostname`, `password` |
|
||||
| `storage` | `endpoint`, `port`, `accessKey`, `secretKey` |
|
||||
| `hanzo-paas` | `passPhrase`, `clusterAccessToken`, `masterToken`, `clusterSlug` |
|
||||
The platform runs as a Next.js application with an embedded SQLite database; for production it deploys to a cluster via the operator. It authenticates against [Hanzo IAM](/docs/services/iam) using the `hanzo-platform` application.
|
||||
|
||||
## API Reference
|
||||
|
||||
The Platform API uses tRPC-style endpoints. Key resource groups:
|
||||
|
||||
| Resource | Endpoints |
|
||||
|----------|-----------|
|
||||
| **Projects** | `project.create`, `project.list`, `project.delete` |
|
||||
| **Applications** | `application.create`, `application.deploy`, `application.stop` |
|
||||
| **Compose** | `compose.create`, `compose.deploy`, `compose.redeploy` |
|
||||
| **Domains** | `domain.create`, `domain.delete`, `domain.verify` |
|
||||
| **Certificates** | `certificate.create`, `certificate.renew` |
|
||||
| **Environment** | `environment.create`, `environment.update` |
|
||||
| **Databases** | `postgres.create`, `mysql.create`, `mongo.create`, `redis.create` |
|
||||
| **Monitoring** | `monitoring.stats`, `monitoring.logs` |
|
||||
| **Backups** | `backup.create`, `backup.restore`, `backup.list` |
|
||||
| **Deployments** | `deployment.list`, `deployment.cancel`, `deployment.rollback` |
|
||||
| **Servers** | `server.create`, `server.list`, `server.validate` |
|
||||
|
||||
Full Swagger documentation is available at `platform.hanzo.ai/swagger`.
|
||||
<Cards>
|
||||
<Card title="API Overview" href="/docs/services/platform/api">
|
||||
The `/v1` control surface and the `/api` build pipeline
|
||||
</Card>
|
||||
<Card title="Authentication" href="/docs/services/platform/api/authentication">
|
||||
API keys, service tokens, and IAM tokens
|
||||
</Card>
|
||||
<Card title="Containers API" href="/docs/services/platform/api/containers">
|
||||
Create, deploy, redeploy, and operate applications
|
||||
</Card>
|
||||
<Card title="Clusters" href="/docs/services/platform/clusters">
|
||||
Register and manage clusters and node pools
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Related Services
|
||||
|
||||
<Cards>
|
||||
<Card title="Hanzo PaaS" href="/docs/services/paas">
|
||||
K8s-native fleet operations and per-org cluster management
|
||||
</Card>
|
||||
<Card title="Hanzo Console" href="/docs/services/console">
|
||||
Observability, tracing, and monitoring for deployed applications
|
||||
</Card>
|
||||
<Card title="Hanzo IAM" href="/docs/services/iam">
|
||||
Identity and access management with multi-org SSO
|
||||
</Card>
|
||||
<Card title="Container Registry" href="/docs/services/registry">
|
||||
Private Docker registry with IAM-based token authentication
|
||||
<Card title="Hanzo KMS" href="/docs/services/kms">
|
||||
Secrets and service tokens for deployments
|
||||
</Card>
|
||||
<Card title="Hanzo Base" href="/docs/services/base">
|
||||
The embedded data store behind the control plane
|
||||
</Card>
|
||||
<Card title="Hanzo S3" href="/docs/services/s3">
|
||||
Object storage for build artifacts and backups
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
|
||||
@@ -7,268 +7,147 @@ description: S3-compatible object storage for AI infrastructure
|
||||
|
||||
Hanzo S3 is a high-performance, S3-compatible object storage service built for AI workloads. It provides durable, scalable storage for model artifacts, training datasets, media assets, event streams, and application data across the Hanzo platform.
|
||||
|
||||
**Console:** `s3.hanzo.ai`
|
||||
**S3 API:** `s3.hanzo.ai` / `api.s3.hanzo.ai`
|
||||
**Server:** [github.com/hanzoai/s3](https://github.com/hanzoai/s3)
|
||||
**Console:** [github.com/hanzos3/console](https://github.com/hanzos3/console)
|
||||
**CLI:** [github.com/hanzos3/cli](https://github.com/hanzos3/cli)
|
||||
Hanzo S3 is built on [SeaweedFS](https://github.com/seaweedfs/seaweedfs) (Apache-2.0) and exposes the **AWS S3 API** with Signature V4 authentication. Any S3 client, SDK, or tool works unmodified — point it at the endpoint and use your access key and secret key.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **S3 API** | `https://s3.hanzo.ai` |
|
||||
| **Console** | `https://s3.hanzo.ai` (web admin) |
|
||||
| **Protocol** | AWS S3 (SigV4), path-style and virtual-host style |
|
||||
| **Region** | `us-east-1` (default; cosmetic — set it on your client) |
|
||||
| **Source** | [github.com/hanzoai/s3](https://github.com/hanzoai/s3) |
|
||||
|
||||
## Features
|
||||
|
||||
- **S3 API Compatible**: Drop-in replacement for Amazon S3 -- works with any S3 client, SDK, or tool
|
||||
- **Object Versioning**: Track and restore previous versions of any object
|
||||
- **Lifecycle Management**: Automated expiration, transition, and tiering rules
|
||||
- **Erasure Coding**: Configurable data redundancy across drives and nodes
|
||||
- **Server-Side Encryption**: SSE-S3 and SSE-KMS for data at rest and in transit
|
||||
- **OIDC SSO**: Single sign-on via `hanzo.id` for console access
|
||||
- **Bucket Policies**: Fine-grained S3-compatible IAM policy documents
|
||||
- **Multi-Tenancy**: Isolated namespaces and access boundaries for teams and services
|
||||
- **Event Notifications**: Webhook and queue notifications on object mutations
|
||||
- **High Throughput**: Designed to saturate NVMe and modern network hardware
|
||||
- **S3 API Compatible**: Drop-in replacement for Amazon S3 — works with `aws` CLI, `mc`, `boto3`, `@aws-sdk/client-s3`, and any other S3 SDK
|
||||
- **Buckets & Objects**: Full create/list/delete, put/get/head/copy/delete, and `ListObjects` v1 + v2 + versions
|
||||
- **Multipart Uploads**: Create, upload-part, copy-part, complete, abort, and list — for large objects
|
||||
- **Presigned URLs**: Time-limited GET/PUT URLs for browser and client-side transfers
|
||||
- **POST-Policy Uploads**: Direct browser form uploads with policy constraints
|
||||
- **Versioning**: Track and restore previous versions of any object
|
||||
- **Object Lock**: Retention and legal-hold for compliance (WORM)
|
||||
- **Lifecycle Rules**: Automated expiration and tiering
|
||||
- **Server-Side Encryption**: SSE-S3 and SSE-KMS (keys managed by [Hanzo KMS](/docs/services/kms))
|
||||
- **Bucket Policies, ACLs, CORS, Tagging**: Standard S3 access and metadata controls
|
||||
- **Advanced IAM (optional)**: OIDC/JWT identity providers, STS role assumption, and AWS-style policy documents
|
||||
|
||||
## Architecture
|
||||
|
||||
Hanzo S3 runs the SeaweedFS storage stack behind the S3 gateway:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Clients │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ s3 CLI │ │ AWS SDKs │ │ Hanzo │ │ Console │ │
|
||||
│ │ │ │ boto3 │ │ S3 SDKs │ │ s3.hanzo.ai │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │ │
|
||||
└───────┼──────────────┼──────────────┼───────────────┼───────────┘
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ s3.hanzo.ai / api.s3.hanzo.ai │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Hanzo S3 Server │ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||
│ │ │ S3 API │ │ IAM / │ │ Erasure │ │ Lifecycle│ │ │
|
||||
│ │ │ Handler │ │ Policies │ │ Coding │ │ Manager │ │ │
|
||||
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||
│ │ │ SSE-KMS │ │ Version │ │ Event │ │ Console │ │ │
|
||||
│ │ │ Encrypt │ │ Control │ │ Notify │ │ UI :9001 │ │ │
|
||||
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────┴─────────┐ │
|
||||
│ │ Persistent Data │ │
|
||||
│ │ (NVMe / Block) │ │
|
||||
│ └───────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ Clients │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ aws CLI │ │ mc │ │ boto3 / │ │ Console │ │
|
||||
│ │ │ │ │ │ aws-sdk │ │ s3.hanzo.ai │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
|
||||
└───────┼─────────────┼─────────────┼───────────────┼──────────┘
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ s3.hanzo.ai — S3 Gateway (SigV4 auth, S3 → Filer translation) │
|
||||
└───────────────┬───────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────┼─────────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌────────┐ ┌──────────────────┐
|
||||
│ Master │ │ Filer │ │ Volume servers │
|
||||
│ (topo) │ │ (meta) │ │ (object data) │
|
||||
└────────┘ └────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
| Component | Role |
|
||||
|-----------|------|
|
||||
| **S3 Gateway** | Translates S3 API calls to Filer operations, enforces SigV4 auth |
|
||||
| **Master** | Cluster topology and volume assignment |
|
||||
| **Filer** | Object metadata, directory tree, bucket configuration |
|
||||
| **Volume servers** | Durable object data storage (Haystack-style needle storage) |
|
||||
|
||||
## Credentials
|
||||
|
||||
Hanzo S3 authenticates with an **access key + secret key** using AWS Signature V4 — exactly like Amazon S3. Each identity is granted a set of actions (`Admin`, `Read`, `Write`, `List`, `Tagging`), optionally scoped to specific buckets.
|
||||
|
||||
Store your S3 credentials in [Hanzo KMS](/docs/services/kms) and inject them at runtime — never hard-code them:
|
||||
|
||||
```bash
|
||||
# Pull credentials from KMS (machine-identity auth), then use any S3 client
|
||||
export AWS_ACCESS_KEY_ID=$(hanzo kms secret get --env prod --path /s3 ACCESS_KEY)
|
||||
export AWS_SECRET_ACCESS_KEY=$(hanzo kms secret get --env prod --path /s3 SECRET_KEY)
|
||||
```
|
||||
|
||||
In Kubernetes, sync the keys into a Secret with a [KMSSecret CRD](/docs/services/kms/kmssecret-crd) instead of embedding them in manifests.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Install the CLI
|
||||
Any S3 client works. The examples below use the host `s3.hanzo.ai` with credentials from the environment.
|
||||
|
||||
### MinIO Client (`mc`)
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install hanzos3/tap/s3
|
||||
mc alias set hanzo https://s3.hanzo.ai "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY"
|
||||
|
||||
# Linux (amd64)
|
||||
curl -fsSL https://dl.hanzo.ai/s3/cli/linux-amd64/s3 -o /usr/local/bin/s3
|
||||
chmod +x /usr/local/bin/s3
|
||||
|
||||
# From source
|
||||
go install github.com/hanzos3/cli/cmd/s3@latest
|
||||
mc mb hanzo/my-models # create a bucket
|
||||
mc cp ./model.safetensors hanzo/my-models/ # upload
|
||||
mc ls hanzo/my-models/ # list
|
||||
mc cp hanzo/my-models/model.safetensors ./ # download
|
||||
mc mirror ./training-data/ hanzo/datasets/ # recursive sync
|
||||
```
|
||||
|
||||
### Configure
|
||||
### AWS CLI
|
||||
|
||||
The AWS CLI works against Hanzo S3 with `--endpoint-url`. Set any region (it is not used for routing).
|
||||
|
||||
```bash
|
||||
# Add Hanzo S3 as an alias
|
||||
s3 alias set hanzo https://s3.hanzo.ai $HANZO_S3_ACCESS_KEY $HANZO_S3_SECRET_KEY
|
||||
|
||||
# Verify connectivity
|
||||
s3 admin info hanzo
|
||||
aws --endpoint-url https://s3.hanzo.ai --region us-east-1 s3 mb s3://my-models
|
||||
aws --endpoint-url https://s3.hanzo.ai --region us-east-1 s3 cp ./model.safetensors s3://my-models/
|
||||
aws --endpoint-url https://s3.hanzo.ai --region us-east-1 s3 ls s3://my-models/
|
||||
aws --endpoint-url https://s3.hanzo.ai --region us-east-1 s3 sync ./run-042/ s3://datasets/run-042/
|
||||
```
|
||||
|
||||
### Basic Operations
|
||||
|
||||
```bash
|
||||
# Create a bucket
|
||||
s3 mb hanzo/my-models
|
||||
|
||||
# Upload a model artifact
|
||||
s3 cp ./model.safetensors hanzo/my-models/v1/
|
||||
|
||||
# List objects
|
||||
s3 ls hanzo/my-models/
|
||||
|
||||
# Download
|
||||
s3 cp hanzo/my-models/v1/model.safetensors ./local-copy.safetensors
|
||||
|
||||
# Recursive sync
|
||||
s3 mirror ./training-data/ hanzo/datasets/run-042/
|
||||
|
||||
# Remove an object
|
||||
s3 rm hanzo/my-models/v1/model.safetensors
|
||||
|
||||
# Set bucket policy (public read)
|
||||
s3 anonymous set download hanzo/public-assets
|
||||
```
|
||||
|
||||
### Python SDK
|
||||
### Python (boto3)
|
||||
|
||||
```python
|
||||
from hanzos3 import S3Client
|
||||
|
||||
client = S3Client(
|
||||
endpoint="s3.hanzo.ai",
|
||||
access_key="your-access-key",
|
||||
secret_key="your-secret-key",
|
||||
secure=True,
|
||||
)
|
||||
|
||||
# Create a bucket
|
||||
client.make_bucket("my-models")
|
||||
|
||||
# Upload a file
|
||||
client.fput_object("my-models", "v1/model.safetensors", "./model.safetensors")
|
||||
|
||||
# Download a file
|
||||
client.fget_object("my-models", "v1/model.safetensors", "./downloaded.safetensors")
|
||||
|
||||
# List objects
|
||||
for obj in client.list_objects("my-models", prefix="v1/", recursive=True):
|
||||
print(f"{obj.object_name} {obj.size} bytes")
|
||||
|
||||
# Presigned URL (7 days)
|
||||
from datetime import timedelta
|
||||
url = client.presigned_get_object("my-models", "v1/model.safetensors", expires=timedelta(days=7))
|
||||
print(url)
|
||||
```
|
||||
|
||||
Install:
|
||||
|
||||
```bash
|
||||
pip install hanzos3
|
||||
```
|
||||
|
||||
### Go SDK
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hanzos3/go-sdk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hanzos3.New("s3.hanzo.ai", &hanzos3.Options{
|
||||
Creds: hanzos3.NewStaticV4("access-key", "secret-key", ""),
|
||||
Secure: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create bucket
|
||||
err = client.MakeBucket(ctx, "my-models", hanzos3.MakeBucketOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Upload
|
||||
_, err = client.FPutObject(ctx, "my-models", "v1/model.safetensors",
|
||||
"./model.safetensors", hanzos3.PutObjectOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// List
|
||||
for obj := range client.ListObjects(ctx, "my-models", hanzos3.ListObjectsOptions{
|
||||
Prefix: "v1/",
|
||||
Recursive: true,
|
||||
}) {
|
||||
if obj.Err != nil {
|
||||
log.Fatal(obj.Err)
|
||||
}
|
||||
fmt.Printf("%s %d bytes\n", obj.Key, obj.Size)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Install:
|
||||
|
||||
```bash
|
||||
go get github.com/hanzos3/go-sdk
|
||||
```
|
||||
|
||||
### JavaScript / TypeScript SDK
|
||||
|
||||
```typescript
|
||||
import * as HanzoS3 from '@hanzos3/js-sdk'
|
||||
|
||||
const client = new HanzoS3.Client({
|
||||
endPoint: 's3.hanzo.ai',
|
||||
port: 443,
|
||||
useSSL: true,
|
||||
accessKey: 'your-access-key',
|
||||
secretKey: 'your-secret-key',
|
||||
})
|
||||
|
||||
// Create bucket
|
||||
await client.makeBucket('my-models')
|
||||
|
||||
// Upload
|
||||
await client.fPutObject('my-models', 'v1/model.safetensors', './model.safetensors')
|
||||
|
||||
// List
|
||||
const stream = client.listObjects('my-models', 'v1/', true)
|
||||
stream.on('data', (obj) => {
|
||||
console.log(`${obj.name} ${obj.size} bytes`)
|
||||
})
|
||||
stream.on('error', (err) => console.error(err))
|
||||
|
||||
// Presigned download URL
|
||||
const url = await client.presignedGetObject('my-models', 'v1/model.safetensors', 7 * 24 * 60 * 60)
|
||||
console.log(url)
|
||||
```
|
||||
|
||||
Install:
|
||||
|
||||
```bash
|
||||
npm install @hanzos3/js-sdk
|
||||
```
|
||||
|
||||
### AWS SDK Compatibility
|
||||
|
||||
Any standard AWS S3 SDK works with Hanzo S3. Override the endpoint:
|
||||
|
||||
```python
|
||||
# Python (boto3)
|
||||
import boto3
|
||||
from datetime import timedelta
|
||||
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
endpoint_url="https://s3.hanzo.ai",
|
||||
aws_access_key_id="your-access-key",
|
||||
aws_secret_access_key="your-secret-key",
|
||||
aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"],
|
||||
aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"],
|
||||
region_name="us-east-1",
|
||||
)
|
||||
|
||||
s3.put_object(Bucket="my-models", Key="v1/model.safetensors", Body=open("model.safetensors", "rb"))
|
||||
# Create a bucket and upload
|
||||
s3.create_bucket(Bucket="my-models")
|
||||
s3.upload_file("model.safetensors", "my-models", "v1/model.safetensors")
|
||||
|
||||
# List objects
|
||||
for obj in s3.list_objects_v2(Bucket="my-models", Prefix="v1/").get("Contents", []):
|
||||
print(obj["Key"], obj["Size"])
|
||||
|
||||
# Presigned download URL (1 hour)
|
||||
url = s3.generate_presigned_url(
|
||||
"get_object",
|
||||
Params={"Bucket": "my-models", "Key": "v1/model.safetensors"},
|
||||
ExpiresIn=3600,
|
||||
)
|
||||
print(url)
|
||||
```
|
||||
|
||||
### JavaScript / TypeScript (`@aws-sdk/client-s3`)
|
||||
|
||||
```typescript
|
||||
// TypeScript (@aws-sdk/client-s3)
|
||||
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
|
||||
import { S3Client, PutObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const client = new S3Client({
|
||||
endpoint: 'https://s3.hanzo.ai',
|
||||
region: 'us-east-1',
|
||||
credentials: {
|
||||
accessKeyId: 'your-access-key',
|
||||
secretAccessKey: 'your-secret-key',
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
forcePathStyle: true,
|
||||
})
|
||||
@@ -278,115 +157,34 @@ await client.send(new PutObjectCommand({
|
||||
Key: 'v1/model.safetensors',
|
||||
Body: readFileSync('model.safetensors'),
|
||||
}))
|
||||
|
||||
const out = await client.send(new ListObjectsV2Command({ Bucket: 'my-models', Prefix: 'v1/' }))
|
||||
for (const obj of out.Contents ?? []) console.log(obj.Key, obj.Size)
|
||||
```
|
||||
|
||||
## Platform Buckets
|
||||
## Multipart Uploads
|
||||
|
||||
The Hanzo platform uses the following preconfigured buckets for internal services:
|
||||
|
||||
| Bucket | Purpose |
|
||||
|--------|---------|
|
||||
| `chat` | Chat message history and attachments |
|
||||
| `commerce` | Product media, invoices, and transaction artifacts |
|
||||
| `deploy` | Deployment artifacts and build outputs |
|
||||
| `hanzo` | Core platform assets and configuration |
|
||||
| `hanzo-events` | Event stream uploads (analytics, audit logs) |
|
||||
| `hanzo-media` | User-uploaded media (images, video, documents) |
|
||||
| `lux` | Lux blockchain snapshots and state data |
|
||||
| `pars` | Pars network artifacts |
|
||||
| `zen` | Zen model weights and checkpoints |
|
||||
| `zoo` | Zoo research datasets and experiment outputs |
|
||||
|
||||
You can create additional buckets for your own projects at any time.
|
||||
|
||||
## Console
|
||||
|
||||
The Hanzo S3 web console is available at [s3.hanzo.ai](https://s3.hanzo.ai):
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Hanzo S3 [email protected] ▾ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Buckets (10) + Create Bucket │
|
||||
│ ───────────────────────────────────────────────────────── │
|
||||
│ Name Objects Size Created │
|
||||
│ ───────────────────────────────────────────────────────── │
|
||||
│ chat 12,847 3.2 GB 2025-11-01 │
|
||||
│ commerce 4,231 1.8 GB 2025-11-01 │
|
||||
│ deploy 8,912 24.1 GB 2025-11-01 │
|
||||
│ hanzo 15,003 8.7 GB 2025-11-01 │
|
||||
│ hanzo-events 142,500 12.4 GB 2025-11-01 │
|
||||
│ hanzo-media 28,744 45.2 GB 2025-11-01 │
|
||||
│ lux 2,100 67.3 GB 2025-12-15 │
|
||||
│ pars 450 2.1 GB 2025-12-15 │
|
||||
│ zen 1,205 312.8 GB 2026-01-10 │
|
||||
│ zoo 3,891 89.4 GB 2026-01-10 │
|
||||
│ │
|
||||
│ Total: 219,883 objects, 567.0 GB │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Sign in with your Hanzo account via OIDC SSO (powered by `hanzo.id`).
|
||||
|
||||
## OIDC Integration
|
||||
|
||||
Hanzo S3 console authentication is integrated with `hanzo.id` via OpenID Connect:
|
||||
|
||||
- **Provider:** `hanzo.id` (Hanzo IAM)
|
||||
- **IAM App:** `app-storage`
|
||||
- **Client ID:** Configured in K8s secret
|
||||
- **Scopes:** `openid`, `profile`, `email`
|
||||
- **Flow:** Authorization Code with PKCE
|
||||
|
||||
Users with a Hanzo account can sign in to the console at `s3.hanzo.ai` without creating separate S3 credentials. Access policies are managed through bucket policies and IAM service accounts.
|
||||
|
||||
### Service Accounts
|
||||
|
||||
For programmatic access (CI/CD, applications, agents), create service accounts:
|
||||
Large objects upload in parts. High-level SDK helpers (`aws s3 cp`, `boto3 upload_file`, `mc cp`) use multipart automatically. To drive it explicitly:
|
||||
|
||||
```bash
|
||||
# Create a service account with read/write to specific buckets
|
||||
s3 admin user svcacct add hanzo my-user \
|
||||
--access-key "svc-myapp-key" \
|
||||
--secret-key "svc-myapp-secret" \
|
||||
--policy /path/to/policy.json
|
||||
# The AWS CLI automatically switches to multipart above the threshold
|
||||
aws --endpoint-url https://s3.hanzo.ai s3 cp ./checkpoint-70b.safetensors s3://my-models/ \
|
||||
--expected-size 140000000000
|
||||
```
|
||||
|
||||
Example policy (`policy.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::my-models",
|
||||
"arn:aws:s3:::my-models/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
The underlying operations — `CreateMultipartUpload`, `UploadPart`, `CompleteMultipartUpload`, `AbortMultipartUpload`, `ListParts`, `ListMultipartUploads` — are all available via the S3 API for clients that manage parts directly.
|
||||
|
||||
## Bucket Policies
|
||||
|
||||
Apply fine-grained access control with S3-compatible policy documents:
|
||||
Hanzo S3 supports standard S3 policy documents via `PutBucketPolicy`:
|
||||
|
||||
```bash
|
||||
# Set a bucket policy
|
||||
s3 anonymous set-json /path/to/policy.json hanzo/my-models
|
||||
|
||||
# View current policy
|
||||
s3 anonymous get-json hanzo/my-models
|
||||
aws --endpoint-url https://s3.hanzo.ai s3api put-bucket-policy \
|
||||
--bucket public-assets \
|
||||
--policy file://policy.json
|
||||
```
|
||||
|
||||
### Common Policies
|
||||
|
||||
**Public read-only:**
|
||||
**Public read-only** (`policy.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -402,124 +200,105 @@ s3 anonymous get-json hanzo/my-models
|
||||
}
|
||||
```
|
||||
|
||||
**Restrict to specific IP range:**
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": "*",
|
||||
"Action": ["s3:GetObject", "s3:PutObject"],
|
||||
"Resource": ["arn:aws:s3:::internal-data/*"],
|
||||
"Condition": {
|
||||
"IpAddress": {"aws:SourceIp": "10.0.0.0/8"}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Object Lifecycle
|
||||
|
||||
Automate object management with lifecycle rules:
|
||||
|
||||
```bash
|
||||
# Set lifecycle rule: expire objects after 90 days
|
||||
s3 ilm rule add hanzo/logs --expiry-days 90
|
||||
|
||||
# Transition to lower tier after 30 days
|
||||
s3 ilm rule add hanzo/archives --transition-days 30 --storage-class GLACIER
|
||||
|
||||
# List lifecycle rules
|
||||
s3 ilm rule ls hanzo/logs
|
||||
```
|
||||
|
||||
## Versioning
|
||||
|
||||
Enable versioning to preserve every revision of an object:
|
||||
|
||||
```bash
|
||||
# Enable versioning
|
||||
s3 version enable hanzo/my-models
|
||||
aws --endpoint-url https://s3.hanzo.ai s3api put-bucket-versioning \
|
||||
--bucket my-models --versioning-configuration Status=Enabled
|
||||
|
||||
# List object versions
|
||||
s3 ls --versions hanzo/my-models/v1/model.safetensors
|
||||
# List versions
|
||||
aws --endpoint-url https://s3.hanzo.ai s3api list-object-versions --bucket my-models
|
||||
|
||||
# Restore a previous version
|
||||
s3 cp --version-id "abc123" hanzo/my-models/v1/model.safetensors ./restored.safetensors
|
||||
# Get a specific version
|
||||
aws --endpoint-url https://s3.hanzo.ai s3api get-object \
|
||||
--bucket my-models --key v1/model.safetensors --version-id "<id>" restored.safetensors
|
||||
```
|
||||
|
||||
## Encryption
|
||||
|
||||
All data at rest is encrypted by default. Choose the encryption method:
|
||||
All data is encrypted at rest. Choose the key management model per bucket:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| **SSE-S3** | Server-managed keys (default) |
|
||||
| **SSE-KMS** | Keys managed by Hanzo KMS (`kms.hanzo.ai`) |
|
||||
| **SSE-KMS** | Keys managed by [Hanzo KMS](/docs/services/kms) (`kms.hanzo.ai`) |
|
||||
|
||||
```bash
|
||||
# Enable SSE-KMS on a bucket
|
||||
s3 encrypt set sse-kms hanzo-kms-key hanzo/sensitive-data
|
||||
aws --endpoint-url https://s3.hanzo.ai s3api put-bucket-encryption \
|
||||
--bucket sensitive-data \
|
||||
--server-side-encryption-configuration '{
|
||||
"Rules": [{ "ApplyServerSideEncryptionByDefault": { "SSEAlgorithm": "aws:kms" } }]
|
||||
}'
|
||||
```
|
||||
|
||||
## Event Notifications
|
||||
|
||||
Receive notifications when objects are created, accessed, or deleted:
|
||||
## Object Lock & Lifecycle
|
||||
|
||||
```bash
|
||||
# Add webhook notification for object creation
|
||||
s3 event add hanzo/uploads arn:hanzo:sqs::my-queue \
|
||||
--event put --suffix .safetensors
|
||||
# Lifecycle: expire objects after 90 days
|
||||
aws --endpoint-url https://s3.hanzo.ai s3api put-bucket-lifecycle-configuration \
|
||||
--bucket logs --lifecycle-configuration file://lifecycle.json
|
||||
|
||||
# List configured events
|
||||
s3 event ls hanzo/uploads
|
||||
# Object Lock retention (WORM) is set at bucket creation and per-object
|
||||
aws --endpoint-url https://s3.hanzo.ai s3api put-object-retention \
|
||||
--bucket archives --key 2026/report.pdf \
|
||||
--retention '{ "Mode": "COMPLIANCE", "RetainUntilDate": "2027-01-01T00:00:00Z" }'
|
||||
```
|
||||
|
||||
## SDKs
|
||||
## Console
|
||||
|
||||
| Language | Package | Repository |
|
||||
|----------|---------|------------|
|
||||
| Python | `hanzos3` | [github.com/hanzos3/py-sdk](https://github.com/hanzos3/py-sdk) |
|
||||
| Go | `github.com/hanzos3/go-sdk` | [github.com/hanzos3/go-sdk](https://github.com/hanzos3/go-sdk) |
|
||||
| JavaScript | `@hanzos3/js-sdk` | [github.com/hanzos3/js-sdk](https://github.com/hanzos3/js-sdk) |
|
||||
| Java | `io.hanzo.s3` | [github.com/hanzos3/java-sdk](https://github.com/hanzos3/java-sdk) |
|
||||
| Rust | `hanzos3` | [github.com/hanzos3/rust-sdk](https://github.com/hanzos3/rust-sdk) |
|
||||
| .NET | `HanzoS3` | [github.com/hanzos3/dotnet-sdk](https://github.com/hanzos3/dotnet-sdk) |
|
||||
| C++ | `hanzos3-cpp` | [github.com/hanzos3/cpp-sdk](https://github.com/hanzos3/cpp-sdk) |
|
||||
The Hanzo S3 web admin console is available at [s3.hanzo.ai](https://s3.hanzo.ai). It provides a file browser, bucket management, and cluster status. The console is protected by an administrator login configured at deployment.
|
||||
|
||||
All standard AWS S3 SDKs (`boto3`, `@aws-sdk/client-s3`, `aws-sdk-go`) also work by setting the endpoint to `https://s3.hanzo.ai`.
|
||||
For day-to-day object operations, use the S3 API (CLI/SDK) rather than the console.
|
||||
|
||||
## Docker
|
||||
## Self-Hosting
|
||||
|
||||
Run a standalone Hanzo S3 server:
|
||||
Run a standalone Hanzo S3 server (SeaweedFS in S3 mode):
|
||||
|
||||
```bash
|
||||
docker run -p 9000:9000 -p 9001:9001 \
|
||||
-e "HANZO_S3_ROOT_USER=admin" \
|
||||
-e "HANZO_S3_ROOT_PASSWORD=changeme-strong-password" \
|
||||
# Single-node server with the S3 gateway enabled
|
||||
docker run -p 8333:8333 -p 8888:8888 -p 9333:9333 \
|
||||
-v /data:/data \
|
||||
ghcr.io/hanzoai/storage:latest server /data --console-address :9001
|
||||
ghcr.io/hanzoai/s3:latest \
|
||||
server -dir=/data -s3 -filer
|
||||
```
|
||||
|
||||
- **Port 9000**: S3 API
|
||||
- **Port 9001**: Web console
|
||||
| Port | Service |
|
||||
|------|---------|
|
||||
| `8333` | S3 API gateway |
|
||||
| `8888` | Filer (metadata + web UI) |
|
||||
| `9333` | Master (topology) |
|
||||
| `8080` | Volume server |
|
||||
| `23646` | Web admin console |
|
||||
|
||||
Provide credentials with an identity config file (`-config`) or the `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` environment variables. The public hostnames `s3.hanzo.ai` are wired at the ingress layer (`-domainName` / `S3_EXTERNAL_URL`), not baked into the binary.
|
||||
|
||||
## SDK Compatibility
|
||||
|
||||
Hanzo S3 has no bespoke SDK — and needs none. Every standard AWS S3 SDK works by setting the endpoint to `https://s3.hanzo.ai`:
|
||||
|
||||
| Language | Package |
|
||||
|----------|---------|
|
||||
| Python | `boto3`, `aioboto3` |
|
||||
| JavaScript / TypeScript | `@aws-sdk/client-s3` |
|
||||
| Go | `github.com/aws/aws-sdk-go-v2` |
|
||||
| Java | `software.amazon.awssdk:s3` |
|
||||
| Rust | `aws-sdk-s3` |
|
||||
| CLI | `aws`, `mc` (MinIO Client), `rclone` |
|
||||
|
||||
## Related Services
|
||||
|
||||
<Cards>
|
||||
<Card title="KV" href="/docs/services/kv">
|
||||
Key-value store for caching and session data
|
||||
</Card>
|
||||
<Card title="DB" href="/docs/services/db">
|
||||
Serverless PostgreSQL for structured data
|
||||
</Card>
|
||||
<Card title="Gateway" href="/docs/services/gateway">
|
||||
API gateway routing and rate limiting
|
||||
</Card>
|
||||
<Card title="KMS" href="/docs/services/kms">
|
||||
Key management for SSE-KMS encryption
|
||||
Key management for SSE-KMS encryption and S3 credential storage
|
||||
</Card>
|
||||
<Card title="Base" href="/docs/services/base">
|
||||
Backend-as-a-service that uses Hanzo S3 for file storage
|
||||
</Card>
|
||||
<Card title="IAM" href="/docs/services/iam">
|
||||
Identity provider for advanced OIDC-based S3 access
|
||||
</Card>
|
||||
<Card title="PaaS" href="/docs/services/platform">
|
||||
Deploy and operate Hanzo S3 on your clusters
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
Reference in New Issue
Block a user