Plugin SDK Reference
Protocol Specification
Arcan plugins communicate with the core over JSON-over-stdin/stdout. The core spawns the plugin as a child process, sends one JSON object per line on stdin, and reads one JSON object per line from stdout.
Request Format
{"method": "describe", "params": null}
{"method": "ping", "params": null}
{"method": "generate", "params": {"role": "readonly", "ttl": "1h"}}
{"method": "validate", "params": {"username": "arcan_a1b2c3d4"}}
| Field | Type | Description |
|---|---|---|
method | string | One of: describe, ping, generate, validate |
params | json.RawMessage | Method-specific parameters (null for describe/ping) |
Response Format
Success:
{"data": {"name": "postgres", "version": "0.1.0"}}
Error:
{"error": "unknown role \"superadmin\" — use readonly, readwrite, or admin"}
Only one of data or error is present in a response.
Methods
| Method | Purpose | Requires SecretEngine |
|---|---|---|
describe | Return plugin identity and capabilities | No |
ping | Verify the plugin is healthy | No |
generate | Create new credentials | Yes |
validate | Validate or revoke existing credentials | Yes |
The Engine Interface
Every plugin implements either Engine (basic) or SecretEngine (credentials).
// Engine is the interface every plugin must implement.
type Engine interface {
Describe() Descriptor
Ping() error
}
// SecretEngine extends Engine with secret generation and validation.
type SecretEngine interface {
Engine
Generate(params json.RawMessage) (*SecretResult, error)
Validate(params json.RawMessage) (*ValidationResult, error)
}
Descriptor
type Descriptor struct {
Name string `json:"name"`
Version string `json:"version"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Capabilities []string `json:"capabilities"`
}
SecretResult / ValidationResult
type SecretResult struct {
Data map[string]any `json:"data"`
}
type ValidationResult struct {
Valid bool `json:"valid"`
Message string `json:"message,omitempty"`
}
Plugin Skeleton
Minimal compilable plugin (30 lines):
package main
import (
"encoding/json"
"getarcan.dev/arcan/sdk"
)
type MyEngine struct{}
func (e *MyEngine) Describe() sdk.Descriptor {
return sdk.Descriptor{
Name: "my-engine",
Version: "0.1.0",
DisplayName: "My Engine",
Description: "Description of what this engine does",
Capabilities: []string{"dynamic_credentials"},
}
}
func (e *MyEngine) Ping() error {
return nil
}
func (e *MyEngine) Generate(params json.RawMessage) (*sdk.SecretResult, error) {
return &sdk.SecretResult{
Data: map[string]any{"secret": "value"},
}, nil
}
func (e *MyEngine) Validate(params json.RawMessage) (*sdk.ValidationResult, error) {
return &sdk.ValidationResult{Valid: true, Message: "ok"}, nil
}
func main() {
sdk.Serve(&MyEngine{})
}
sdk.Serve starts a bufio.Scanner loop on stdin. Each line is dispatched to the appropriate method. The loop runs until stdin closes.
Building Your Plugin
Directory Structure
my-engine/
go.mod
main.go
README.md
Makefile
go.mod
module my-engine
go 1.22
require getarcan.dev/arcan/sdk v0.1.0
Build Commands
Native binary:
go build -o arcan-engine-my-engine .
WebAssembly (WASM):
GOOS=wasip1 GOARCH=wasm go build -o arcan-engine-my-engine.wasm .
Scaffolder
arcan plugin init my-engine
Generates a working plugin directory with go.mod, main.go, Makefile, and README.md.
Signing and Installation
Generate a Signing Key Pair
arcan plugin keygen
Sign the Plugin Binary
arcan plugin sign arcan-engine-my-engine
Creates arcan-engine-my-engine.sig alongside the binary.
Install
Copy the binary and signature to the plugins directory:
cp arcan-engine-my-engine ~/.arcan/data/plugins/
cp arcan-engine-my-engine.sig ~/.arcan/data/plugins/
What Happens on Server Startup
- Discovery: The server scans
~/.arcan/data/plugins/for binaries. - Signature verification: Each binary's
.sigfile is verified against the registered Ed25519 public key. Unsigned plugins are rejected. - Describe: The server spawns each verified plugin, sends
{"method":"describe"}, and registers its capabilities.
Testing Your Plugin
Manual Testing (stdin/stdout)
# Describe
echo '{"method":"describe"}' | ./arcan-engine-my-engine
# Ping
echo '{"method":"ping"}' | ./arcan-engine-my-engine
# Generate
echo '{"method":"generate","params":{"role":"readonly","ttl":"1h"}}' | ./arcan-engine-my-engine
# Validate (revoke)
echo '{"method":"validate","params":{"username":"arcan_a1b2c3d4"}}' | ./arcan-engine-my-engine
Official Plugins Reference
| Plugin | Type | Capabilities | Roles |
|---|---|---|---|
| PostgreSQL | Database | dynamic_credentials, root_rotation | readonly, readwrite, admin |
| MySQL | Database | dynamic_credentials, root_rotation | readonly, readwrite, admin |
| MongoDB | Database | dynamic_credentials | read, readWrite, dbAdmin |
| Redis | Database | dynamic_credentials | readonly, readwrite, admin |
| AWS IAM | Cloud | engine:dynamic_credentials | readonly, poweruser, admin, custom |
| Azure | Cloud | engine:dynamic_credentials | reader, contributor, owner |
| GCP | Cloud | engine:dynamic_credentials | viewer, editor, owner, custom |
| SSH | Crypto | secret_generation, secret_validation | N/A (principals-based) |
| Kubernetes | Infrastructure | secret_generation, secret_validation | viewer, editor, admin |
| RabbitMQ | Infrastructure | secret_generation, secret_validation | consumer, producer, admin |
| PKI | Crypto | secret_generation, secret_validation | server, client, both |
Configuration Patterns
Database plugins (PostgreSQL, MySQL, MongoDB, Redis): No configuration at startup. The core manages root credentials. The plugin returns SQL/commands for the core to execute.
Cloud plugins (AWS IAM, Azure, GCP): Load credentials from the ARCAN_ENGINE_CONFIG environment variable (JSON object).
Infrastructure plugins (Kubernetes, RabbitMQ): Use individual environment variables (ARCAN_KUBE_*, ARCAN_RABBITMQ_*).
Crypto plugins (SSH, PKI): Load CA keys from environment variables with optional fallback to ephemeral keys for development.
Environment Variables
| Variable | Used by | Format |
|---|---|---|
ARCAN_ENGINE_CONFIG | aws-iam, azure, gcp | JSON object |
ARCAN_SSH_CA_PRIVATE_KEY | ssh | PEM-encoded Ed25519/RSA private key |
ARCAN_PKI_CA_CERT | pki | PEM-encoded X.509 certificate |
ARCAN_PKI_CA_KEY | pki | PEM-encoded ECDSA private key |
ARCAN_PKI_ORGANIZATION | pki | String (default: "Arcan PKI") |
ARCAN_KUBE_API_SERVER | kubernetes | URL |
ARCAN_KUBE_TOKEN | kubernetes | Bearer token string |
ARCAN_KUBE_NAMESPACE | kubernetes | String (default: "default") |
ARCAN_KUBE_SKIP_TLS | kubernetes | "true" to skip TLS verification |
ARCAN_RABBITMQ_URL | rabbitmq | Management API URL |
ARCAN_RABBITMQ_USER | rabbitmq | Admin username |
ARCAN_RABBITMQ_PASS | rabbitmq | Admin password |
ARCAN_RABBITMQ_VHOST | rabbitmq | Default vhost (default: "/") |
Generate Input/Output Summary
| Plugin | Input | Output |
|---|---|---|
| PostgreSQL | {role, ttl} | {username, password, expires_at, sql_statements} |
| MySQL | {role, ttl, database} | {username, password, expires_at, database, sql_statements} |
| MongoDB | {role, ttl, database, auth_database} | {username, password, expires_at, database, auth_database, mongo_command} |
| Redis | {role, ttl} | {username, password, expires_at, redis_command} |
| AWS IAM | {role, ttl, policy_arn} | {access_key_id, secret_access_key, username, account_id, region, policy_arn} |
| Azure | {role, ttl} | {client_id, client_secret, tenant_id, subscription_id, app_object_id, sp_object_id, role, display_name} |
| GCP | {role, ttl, iam_role} | {email, key_json, project_id, iam_role, unique_id} |
| SSH | {public_key, principals, ttl, key_id} | {signed_certificate, serial, key_id, principals, valid_after, valid_until, ca_public_key} |
| Kubernetes | {namespace, role, ttl} | {token, namespace, service_account, role_binding, cluster_role, expires_at} |
| RabbitMQ | {role, vhost} | {username, password, vhost, management_url, amqp_url, permissions} |
| PKI | {common_name, san, role, ttl} | {certificate, private_key, ca_certificate, common_name, san, serial, not_before, expires_at} |