Skip to content

Public API surface: tokens, OpenAPI, SDKs, Terraform provider, aperio CLI #52

@dcoln25-writer

Description

@dcoln25-writer

Problem

Aperio has a ConnectRPC server and a /api/v1/* REST compatibility layer, but none of the externalizing tooling that security engineering teams expect from a posture product:

  • No published API surface — no OpenAPI spec, no SDK, no auth scheme for headless callers (sessions only).
  • No Terraform provider — SecOps can't declare connectors / rules / SIEM destinations / risk exceptions as code.
  • No CLI — operators can't script ad-hoc queries (aperio finding list, aperio rule test).

This blocks adoption in two important segments:

  1. Regulated buyers who require all infrastructure to be Terraform-managed.
  2. Detection engineers who want to keep rule changes in a Git workflow with CI tests.

Goals

  1. Headless API authentication — long-lived, scoped API tokens (not cookie sessions).
  2. Published OpenAPI spec — generated from the existing ConnectRPC contracts; versioned and CI-validated.
  3. Official SDKs — Go and TypeScript first; Python in P2.
  4. Terraform provider — manage IntegrationConnection, DetectionRule (depends on Detection-as-code: declarative YAML rules + community rule packs #47), SiemDestination, WorkflowDestination (depends on Persist ingestion jobs in the database #6), RiskException, Organization settings.
  5. aperio CLI — operator + ops use cases (list, get, mute, test, sync, export).

Non-goals

  • Not building a GraphQL endpoint in v1 (most consumers want REST/RPC).
  • Not maintaining SDKs for every language (Go + TS + Python is the bar; others community-driven).
  • Not replacing the existing cookie-session web auth — token auth is additive.

Proposed design

Headless auth (scoped API tokens)

New schema:

enum ApiTokenScope {
  READ_ONLY
  WRITE
  ADMIN
}

model ApiToken {
  id                String        @id @default(cuid())
  organizationId    String        @map("organization_id")
  createdByUserId   String        @map("created_by_user_id")
  name              String        @db.VarChar(160)
  tokenPrefix       String        @map("token_prefix") @db.VarChar(12)   // "apk_live_..."
  tokenHash         String        @unique @map("token_hash") @db.VarChar(128)
  scopes            ApiTokenScope[] @default([READ_ONLY])
  allowedIpCidrs    String[]      @default([]) @map("allowed_ip_cidrs")
  lastUsedAt        DateTime?     @map("last_used_at")
  lastUsedIp        String?       @map("last_used_ip") @db.VarChar(64)
  expiresAt         DateTime?     @map("expires_at")
  revokedAt         DateTime?     @map("revoked_at")
  createdAt         DateTime      @default(now()) @map("created_at")
  organization      Organization  @relation(...)
  createdBy         User          @relation(...)
  @@index([organizationId, revokedAt])
  @@map("api_tokens")
}

Token format: apk_live_<base62-26-chars>. Hashed with HMAC-SHA256 + APERIO_API_TOKEN_SALT. Validated in the same middleware as cookies; sets the same OrgContext for downstream handlers. Per-token rate limits via the existing RateLimitBucket.

OpenAPI generation

Use Buf's gRPC-Gateway transcoder + buf generate to emit OpenAPI v3 from the existing proto/aperio/v1/api.proto. Published at:

  • /openapi.json (live)
  • /openapi.yaml (live)
  • Bundled into the repo as gen/openapi/aperio-v1.yaml (CI-verified to match source)

Swagger UI served at /api/docs (gated behind a config flag).

SDKs

Language Generation Location
Go connectrpc.com/connect (existing) gen/go-sdk/
TypeScript @connectrpc/connect-web + OpenAPI types packages/sdk-ts/ (npm publishable)
Python openapi-python-client from the OpenAPI spec new python-sdk/ repo

Each SDK ships with a thin auth helper for API tokens and per-token rate limit handling.

Terraform provider

New repo terraform-provider-aperio (Go), built against the Go SDK. Resources:

resource "aperio_integration_connection" "github_main" {
  organization_id    = data.aperio_organization.default.id
  provider           = "GITHUB"
  display_name       = "main-org"
  external_account_id = "acme-eng"
  encrypted_token    = vault_kv_secret_v2.github_pat.data["token"]
  disabled_checks    = []
}

resource "aperio_detection_rule" "github_public_repo" {
  organization_id = data.aperio_organization.default.id
  rule_key        = "github.public_repo"
  spec_yaml       = file("${path.module}/rules/github-public-repo.yaml")
}

resource "aperio_siem_destination" "splunk" {
  organization_id = data.aperio_organization.default.id
  kind            = "SPLUNK_HEC"
  name            = "prod-splunk"
  endpoint_url    = "https://splunk.acme.example/services/collector"
  encrypted_token = vault_kv_secret_v2.splunk_hec.data["token"]
  streams         = ["FINDINGS"]
}

resource "aperio_risk_exception" "approved_public_repo" {
  organization_id        = data.aperio_organization.default.id
  finding_id             = "..."
  title                  = "OSS sample repo allowed-public"
  rationale              = "Approved by SecOps 2026-Q2 review"
  compensating_controls  = ["secret-scanning-on", "no-sensitive-data"]
  expires_at             = "2026-12-31T00:00:00Z"
}

Provider published to the Terraform Registry under writer/aperio. Examples and docs autogenerated via tfplugindocs.

CLI (aperio)

Cobra-based Go binary. Distribution: GitHub releases (signed via Sigstore), Homebrew tap, Docker image.

# Auth (interactive OAuth-like device flow, or via env APERIO_API_TOKEN)
aperio auth login --org acme

# Findings
aperio finding list --severity CRITICAL,HIGH --status OPEN --json
aperio finding view <id>
aperio finding mute <id> --rationale "...handled in JIRA-1234" --expires 7d

# Connectors
aperio connector list
aperio connector sync github --force

# Rules (depends on #47)
aperio rule list
aperio rule test ./rules/github/public-repo.yaml --window 30d
aperio rule push ./rules/github/

# Exports
aperio finding export --format jsonl --since 30d > findings.jsonl
aperio compliance export --framework soc2 --period 2026-Q1 --out evidence.zip   # depends on #5

# Admin
aperio token create --name "ci-deploy" --scopes WRITE
aperio token list
aperio token revoke <id>

CLI config at ~/.aperio/config.yaml with multi-org context switching (aperio context use acme).

Audit & observability

Every API token use writes a TenantAuditLog entry (action="api.invoke", metadata.tokenId, .method, .ip). Per-token usage dashboard tile (calls/day, error rate, last used).

Phasing

Phase Scope
P1 API token auth + middleware; /admin/tokens UI; OpenAPI generation + CI check; basic Go SDK
P2 TypeScript SDK; CLI (auth, finding, connector commands); CLI distribution (brew + GH releases)
P3 Terraform provider (connector + SIEM + risk-exception resources first); Registry publication
P4 Terraform aperio_detection_rule resource (post-#47); Python SDK; CLI rule commands (post-#47)

Open questions

  • Do API tokens scope to the org only, or also to a specific connector / destination subset?
  • Terraform provider: drift-detection strategy — Aperio is the source of truth, but operators may also mutate via the web UI. Detect-and-warn vs. force-overwrite?
  • Should the CLI support a "watch" mode (aperio finding list --watch) for SOC dashboards on terminals?
  • Versioning strategy for the OpenAPI spec — semver per proto/aperio/vN/?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions