The Enterprise Control Plane
How iddio scales from a single proxy to a fleet. Multi-tenant PostgreSQL architecture, OIDC authentication for operators, RBAC with four roles, and ETag-based proxy config sync.
From Single Proxy to Fleet
The open-source proxy runs as a single binary on one machine. It’s designed for individual developers or small teams running one or two clusters. But enterprises run hundreds of clusters, with dozens of operators and hundreds of AI agents. They need centralized management.
The enterprise control plane is a server that manages a fleet of iddio proxies. It stores policies, agent identities, and audit events in PostgreSQL, authenticates operators via OIDC, and syncs configuration to edge proxies using ETag-based polling.
Architecture
Operator → Server (PostgreSQL, OIDC, REST API)
↓ (config sync)
Proxy 1 Proxy 2 Proxy 3
↓ ↓ ↓
Cluster A Cluster B Cluster C
The server is the source of truth. Proxies pull their configuration from the server and push audit events back. If the server goes down, proxies continue operating with their last-known-good configuration.
Multi-Tenant PostgreSQL
Each organization gets its own schema within a shared PostgreSQL database. Isolation is enforced at the query level — every query includes WHERE org_id = $1:
func (s *Store) GetAgentPolicy(ctx context.Context, orgID, agentName string) (*Policy, error) {
row := s.db.QueryRow(ctx,
`SELECT policy_json FROM agent_policies
WHERE org_id = $1 AND agent_name = $2`,
orgID, agentName)
// ...
}
Tables:
organizations— org metadata, billing statusoperators— OIDC-linked operator accountsagents— agent identities per orgpolicies— policy configurations per orgaudit_events— audit log entries per orgproxy_registrations— registered edge proxies per org
OIDC Authentication
Operators authenticate via OIDC (OpenID Connect). The server supports any OIDC provider: Okta, Azure AD, Google Workspace, Keycloak.
# server config
auth:
oidc:
issuer: https://accounts.google.com
client_id: ${OIDC_CLIENT_ID}
client_secret: ${OIDC_CLIENT_SECRET}
redirect_url: https://console.iddio.dev/callback
scopes: [openid, email, profile]
The OIDC flow produces a JWT that the server validates on every API request. The JWT’s email claim is mapped to an operator account, and the operator’s role determines what they can do.
RBAC with Four Roles
| Role | Permissions |
|---|---|
| Viewer | Read policies, agents, and audit events |
| Operator | Viewer + approve/deny escalations |
| Admin | Operator + create/edit agents and policies |
| Owner | Admin + manage operators and billing |
Roles are per-organization. An operator can be Admin in one org and Viewer in another.
type Role string
const (
RoleViewer Role = "viewer"
RoleOperator Role = "operator"
RoleAdmin Role = "admin"
RoleOwner Role = "owner"
)
func (r Role) CanApprove() bool { return r >= RoleOperator }
func (r Role) CanEditPolicy() bool { return r >= RoleAdmin }
func (r Role) CanManageOrg() bool { return r == RoleOwner }
ETag-Based Config Sync
Edge proxies poll the server for configuration updates. The server returns the current config with an ETag header. On subsequent polls, the proxy sends If-None-Match with the previous ETag. If nothing changed, the server returns 304 Not Modified — zero bandwidth, zero parsing.
func (p *EdgeProxy) syncConfig(ctx context.Context) error {
req, _ := http.NewRequestWithContext(ctx, "GET",
p.serverURL+"/api/v1/proxy/config", nil)
req.Header.Set("If-None-Match", p.lastETag)
resp, err := p.client.Do(req)
if err != nil {
return err // Keep last-known-good
}
if resp.StatusCode == http.StatusNotModified {
return nil // No changes
}
// Parse and swap config
var config ProxyConfig
json.NewDecoder(resp.Body).Decode(&config)
p.lastETag = resp.Header.Get("ETag")
p.SwapPolicy(config.Policy)
return nil
}
The default poll interval is 30 seconds. For latency-sensitive deployments, the server also supports WebSocket push notifications.
Audit Event Streaming
Audit events flow from proxies to the server in batches:
func (p *EdgeProxy) flushAuditBuffer(ctx context.Context) error {
events := p.auditBuffer.Drain()
if len(events) == 0 {
return nil
}
body, _ := json.Marshal(events)
req, _ := http.NewRequestWithContext(ctx, "POST",
p.serverURL+"/api/v1/proxy/audit", bytes.NewReader(body))
resp, err := p.client.Do(req)
if err != nil {
p.auditBuffer.Requeue(events) // Retry later
return err
}
return nil
}
Events are buffered locally and flushed every 10 seconds (configurable). If the server is unreachable, events are requeued and flushed on the next successful connection. The local JSONL audit log is always written regardless of server connectivity — no audit events are ever lost.
REST API
The server exposes a REST API for management:
GET /api/v1/agents— list agentsPOST /api/v1/agents— create agentGET /api/v1/policies— get active policyPUT /api/v1/policies— update policyGET /api/v1/audit— query audit eventsPOST /api/v1/approvals/:id— approve/deny escalationGET /api/v1/proxies— list registered proxies
All endpoints require OIDC authentication and RBAC authorization.
Try It Yourself
Iddio is open source. Deploy a zero-trust command proxy for your AI agents in minutes.