Back to Blog
Phase 5 2026-02-17

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 status
  • operators — OIDC-linked operator accounts
  • agents — agent identities per org
  • policies — policy configurations per org
  • audit_events — audit log entries per org
  • proxy_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

RolePermissions
ViewerRead policies, agents, and audit events
OperatorViewer + approve/deny escalations
AdminOperator + create/edit agents and policies
OwnerAdmin + 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 agents
  • POST /api/v1/agents — create agent
  • GET /api/v1/policies — get active policy
  • PUT /api/v1/policies — update policy
  • GET /api/v1/audit — query audit events
  • POST /api/v1/approvals/:id — approve/deny escalation
  • GET /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.