Id RFC 007 admin service and cli

RFC-007 — Admin Service & CLI

  • *tatus:*Draft (revised 20260518 to reconcile with shipped CLI in #073)
  • *ate:*20260408
  • *uthor:*Koder Team
  • *epends on:*RFC001, RFC002

Summary

The Admin service provides tenant management, system configuration, OAuth2 client registration, and API key management. The CLI (koder-id-cli) is a command-line client that communicates with the Admin service via gRPC.

Admin Service Responsibilities

  1. *enant management*— create, configure, suspend tenants (organizations)
  2. *Auth2 client registration*— register apps that use Koder ID for authentication
  3. *PI key management*— create/revoke machinetomachine keys
  4. *ystem configuration*— global and per-tenant settings
  5. *udit log access*— query audit trail
  6. *ootstrap*— initial system setup (first tenant, first admin user)

API Endpoints (REST) — actual shipped surface

Reality as of 20260518 (see services/admin/internal/handler/http.go::RegisterRoutes). Tables below are normative; plannedbutnot-shipped endpoints are called out in a separate *uture*sub-section.

Tenants

Method Endpoint Description
GET /v1/admin/tenants List tenants
POST /v1/admin/tenants Create tenant (name, slug, domain)
GET /v1/admin/tenants/{id_or_slug} Get tenant
PUT /v1/admin/tenants/{id_or_slug} Update tenant
DELETE /v1/admin/tenants/{id_or_slug} Suspend tenant

API Keys

Method Endpoint Description
GET /v1/admin/api-keys List API keys
POST /v1/admin/api-keys Create API key
DELETE /v1/admin/api-keys/{id} Revoke API key

Audit, System, Bootstrap

Method Endpoint Description
GET /v1/admin/audit Query audit log
GET /v1/admin/system/health Health check
GET /v1/admin/system/metrics Prometheus metrics
POST /v1/admin/bootstrap Idempotent bootstrap of required tenants

OAuth2 Clients — served by the OAuth service, not Admin

Client management is *ot*under /v1/admin/.... The OAuth service exposes both gRPC (OAuthService.{CreateClient, GetClient, UpdateClient, DeleteClient, ListClients}) and a narrow HTTP surface:

Method Endpoint Service Description
POST /oauth/v2/register oauth RFC 7591 Dynamic Client Registration — gated by KODER_ID_DCR_BOOTSTRAP_TOKEN
POST /v1/admin/clients/{client_id}/reset-secret oauth Rotate client secret — gated by KODER_ID_MIGRATION_MODE
POST /v1/admin/migrate/oauth-client oauth Migration helper for legacy → v2 clients

The CLI (koder-id-cli client {register,list,get,delete,rotate-secret}) talks to OAuth gRPC for CRUD and to /v1/admin/clients/{id}/reset-secret for rotation.

Future REST surface (not yet shipped)

Method Endpoint Description Status
POST /v1/admin/oauth/clients Mirror CreateClient under admin namespace not impl
GET /v1/admin/oauth/clients Mirror ListClients under admin namespace not impl
DELETE /v1/admin/oauth/clients/{id} Mirror DeleteClient under admin namespace not impl
GET/POST /v1/admin/tenants/{id}/users/... Admin-side user management not impl (Identity service has its own surface)
GET/PATCH /v1/admin/system/config Runtime config CRUD not impl

gRPC Service Definitions

The CLI consumes two gRPC services. Both live under pkg/proto/koder/id/{admin,oauth}/v1/.

koder.id.admin.v1.AdminService (port 4005)

Actual shipped RPCs (see pkg/proto/koder/id/admin/v1/admin_grpc.pb.go):

service AdminService {
    rpc CreateTenant(CreateTenantRequest) returns (CreateTenantResponse);
    rpc GetTenant(GetTenantRequest)       returns (GetTenantResponse);
    rpc UpdateTenant(UpdateTenantRequest) returns (UpdateTenantResponse);
    rpc DeleteTenant(DeleteTenantRequest) returns (DeleteTenantResponse);
    rpc ListTenants(ListTenantsRequest)   returns (ListTenantsResponse);

    rpc CreateAPIKey(CreateAPIKeyRequest) returns (CreateAPIKeyResponse);
    rpc ListAPIKeys(ListAPIKeysRequest)   returns (ListAPIKeysResponse);
    rpc RevokeAPIKey(RevokeAPIKeyRequest) returns (RevokeAPIKeyResponse);

    rpc QueryAuditLog(QueryAuditLogRequest) returns (QueryAuditLogResponse);

    rpc Bootstrap(BootstrapRequest) returns (BootstrapResponse);
    rpc Health(HealthRequest)       returns (HealthResponse);
    rpc Metrics(MetricsRequest)     returns (MetricsResponse);
}

Note: RegisterClient, DisableUser, EnableUser, ForceLogout, GetConfig, UpdateConfig were planned in earlier RFC revisions but were *ot*implemented. The first three live in OAuth and Identity services respectively (see below); the config RPCs were dropped in favor of env-var configuration.

koder.id.oauth.v1.OAuthService (port 4003)

CRUD for OAuth clients (consumed by the CLI's client subcommands):

service OAuthService {
    rpc CreateClient(CreateClientRequest) returns (CreateClientResponse);
    rpc GetClient(GetClientRequest)       returns (GetClientResponse);
    rpc UpdateClient(UpdateClientRequest) returns (UpdateClientResponse);
    rpc DeleteClient(DeleteClientRequest) returns (DeleteClientResponse);
    rpc ListClients(ListClientsRequest)   returns (ListClientsResponse);
    // ... plus token/authorize/introspect RPCs out of CLI scope
}

Bootstrap Flow

The bootstrap endpoint (POST /v1/admin/bootstrap, AdminService.Bootstrap) is *dempotent* it ensures a named set of tenants exists and returns status per tenant. Safe to re-run.

$ koder-id-cli bootstrap
Bootstrap complete:
  ✓ Tenant "koder" (slug: koder)
  ✓ Tenant "crescer" (slug: crescer)  [skipped — already exists]

# Verify mode prints status without making changes
$ koder-id-cli bootstrap --check --tenants koder,crescer,vivver
TENANT   STATUS   ID
koder    ok       01KR87BDSNMEQT55R39J1HFERZ
crescer  ok       01KS12...
vivver   MISSING  -

The legacy --tenant-name/--tenant-slug/--admin-email/--admin-password flags from earlier drafts were *ever implemented*and are not supported. Admin user creation happens outofband today (drop-in SQL/kdbnext seed); productionizing the adminuser path is tracked separately.

OAuth Client Bootstrap (without CLI subcommand pre-#073)

For historical context: before #073 added client register, the only way to create the first OAuth client (Flow, Kall, etc.) was via the RFC 7591 DCR endpoint, gated by a single-use bootstrap token. This flow remains the supported way to create the very first client (when no CLI is yet deployed to the host):

  1. Drop a systemd override on koder-id-oauth.service with

    KODER_ID_DCR_BOOTSTRAP_TOKEN=<one-time-secret>.

  2. POST /oauth/v2/register with Authorization: Bearer <secret>

    and the RFC 7591 client metadata body.

  3. Capture client_id + client_secret from the response.
  4. Remove the drop-in and restart the service.

After step 4, all subsequent client management goes through koder-id-cli client register (which talks to OAuth gRPC directly and does not depend on the DCR token).

CLI — koder-id-cli

Shipped binary: /usr/local/bin/koder-id-v2-cli, with symlink /usr/local/bin/koder-id-cli. Source: engine/cli/cmd/.

Configuration

The CLI is stateless and configured exclusively via environment variables. There is no config file.

Env var Default Purpose
KODER_ID_ADMIN_ADDR localhost:4005 Admin service gRPC address
KODER_ID_OAUTH_ADDR localhost:4003 OAuth service gRPC address
KODER_ID_OAUTH_HTTP_ADDR localhost:4013 OAuth service HTTP address (used by client rotate-secret)
KODER_ID_FORMAT table Output format — table or json

Commands

Flat verb-noun dispatch (no cobra, no global flag namespace). Help mirrors the shipped binary:

Usage: koder-id-cli <command> [flags]

Commands:
  bootstrap                                       Initial system setup
  bootstrap --check [--tenants koder,crescer]     Verify required tenants exist
  tenant create|list|get <slug>                   Manage tenants
  client list|get|register|delete|rotate-secret   Manage OAuth clients
  api-key create|list|revoke                      Manage API keys
  keys init|add|get|list                          Koder Keys vault operations
  health                                          Check system health
  test-oidc [--issuer URL] [--tenant SLUG]        Validate OIDC discovery + JWKS
  smoke [--tenant SLUG] [--product NAME] [--issuer URL]
                                                  Liveness smoke test
  metrics [--window DURATION]                     Print service health as JSON
  version                                         Show version
  help                                            Show this help

client subcommands (added in #073):

koder-id-cli client list          --tenant <slug>
koder-id-cli client get           --tenant <slug> --client-id <id>
koder-id-cli client register      --tenant <slug> --name <n> [--type confidential] \
                                  [--redirect <url>]... [--grant <gt>]... [--scope <s>]...
koder-id-cli client delete        --tenant <slug> --client-id <id>
koder-id-cli client rotate-secret --tenant <slug> --client-id <id>

Tenant identifier resolution: any subcommand that takes --tenant <slug> accepts either a slug ("koder") or a 26-char ULID; the CLI auto-resolves slug → ULID via admin GetTenant.

Tenant slug vs storage tenant

The string passed via --tenant is the *ublic slug*(e.g. koder), which is what RFC-006 §Q1 and the tenant claim in id_tokens (#069) reference. This is distinct from DEFAULT_TENANT / KDBNEXT_TENANT env vars that the engine binaries read — those name the *torage tenant*in kdb-next where the tables live, and may differ (e.g. prod runs with DEFAULT_TENANT=koder-id while the user-facing tenant slug is koder).

Example Usage

# Bootstrap — idempotent; safe to re-run
$ koder-id-cli bootstrap

# Verify required tenants are provisioned
$ koder-id-cli bootstrap --check
TENANT   STATUS   ID
koder    ok       01KR87BDSNMEQT55R39J1HFERZ
crescer  MISSING  -
vivver   MISSING  -

# Create tenants
$ koder-id-cli tenant create --name Crescer --slug crescer --domain crescer.koder.dev
$ koder-id-cli tenant create --name Vivver  --slug vivver  --domain vivver.koder.dev

# Register an OAuth client (Flow as OIDC RP)
$ koder-id-cli client register \
    --tenant koder \
    --name "koder-flow" \
    --type confidential \
    --redirect "https://flow.koder.dev/user/oauth2/koder-id/callback" \
    --grant authorization_code --grant refresh_token \
    --scope openid --scope profile --scope email

# List clients (table; KODER_ID_FORMAT=json for JSON)
$ koder-id-cli client list --tenant koder

# Rotate a client secret (requires KODER_ID_MIGRATION_MODE=true on the server)
$ koder-id-cli client rotate-secret --tenant koder --client-id 01KR77G98C2WRKT4

# Spot-check OIDC discovery + JWKS for a tenant
$ koder-id-cli test-oidc --issuer https://id.koder.dev --tenant koder

# Liveness smoke test
$ koder-id-cli smoke --tenant koder --issuer https://id.koder.dev

# Health check across all sub-services
$ koder-id-cli health

Authorization Model

The Admin service uses a simple role-based model:

Role Scope
system_admin Full access to all tenants and system config
tenant_admin Full access within their tenant only
tenant_viewer Read-only access within their tenant

Roles are stored as claims in the user's access token:

{
    "roles": ["system_admin"],
    "tenant_id": "koder"
}

Interaction with Other Services

┌────────┐
│ Admin  │─── gRPC ──▶ Identity Service  (user disable/enable)
│Service │─── gRPC ──▶ OAuth Service     (client registration)
│        │─── gRPC ──▶ Session Service   (force logout)
└────────┘
     ▲
     │ gRPC
┌────────┐
│  CLI   │
└────────┘

Security Considerations

  • Admin API today is unauthenticated on the loopback interface (see

    services/admin/cmd/main.go); the LXC firewall keeps it off the public network. Adding bearer/mTLS auth is a separate, tracked ticket (backlog/pending admin-auth gate; #098 documents the gap).

  • The OAuth /v1/admin/clients/{id}/reset-secret HTTP endpoint and

    /v1/admin/migrate/oauth-client are gated by KODER_ID_MIGRATION_MODE=true env var (a temporary opt-in until proper admin auth lands).

  • DCR (POST /oauth/v2/register) is gated by

    KODER_ID_DCR_BOOTSTRAP_TOKEN per #070/#072 fix.

  • Bootstrap is idempotent (not oneshot), so rerunning it is safe.
  • Client secrets are shown only once on creation; stored as base64

    SHA-256.

  • API keys are shown only once on creation; stored as base64 SHA-256.
  • All admin operations are audit-logged via services/audit.

Changelog

  • 20260518 — Reconcile with shipped CLI (#073): swap plannedvsreal

    REST tables, replace the legacy bootstrap flags, add OAuth bootstrap flow, replace the multi-RPC AdminService draft with the actual shipped RPCs, document --tenant slugvsstorage distinction surfaced in #073, fix the security section to reflect that admin auth is still tracked.

  • 20260408 — Initial draft.

Source: ../home/koder/dev/koder/meta/docs/stack/rfcs/id-RFC-007-admin-service-and-cli.md