API HTTP endpoints

O Tatuzim Server expoe 3 superficies HTTP, separadas por porta e nivel de autenticacao.

Portas e niveis de auth

Porta TLS Auth Rotas
8080 nenhum nenhuma GET /health
8444 sem TLS* enrollment token POST /v1/enroll
8443 HTTPS strict mTLS (cert step-ca) POST /v1/certs/issue, GET /v1/manifest, POST /v1/events

* Em prod, expor 8444 atras de reverse proxy (Traefik) com TLS Let's Encrypt termination.

Endpoints


`GET /health` (porta 8080)

Liveness probe. Sem auth.

Request: nenhum body.

Response 200:

{
  "status": "ok",
  "version": "0.1.0"
}

Uso: load balancer health check, Kubernetes liveness probe, monitoring.


`POST /v1/enroll` (porta 8444)

Bootstrap de identidade pra um novo agent. Valido apenas com token one-time.

Request body (JSON):

{
  "token": "EXEMPLO_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "hostname": "vps-mautic-01",
  "role": "mautic",
  "csr_pem": "-----BEGIN CERTIFICATE REQUEST-----\nMIIB...=\n-----END CERTIFICATE REQUEST-----\n"
}

Response 200:

{
  "agent_id": "00000000-0000-4000-8000-000000000001",
  "cert_pem": "-----BEGIN CERTIFICATE-----\nMIIC...=\n-----END CERTIFICATE-----\n",
  "ca_pem": "-----BEGIN CERTIFICATE-----\nMIIC...=\n-----END CERTIFICATE-----\n"
}

Response 401 — token_not_found:

{ "error": "token_not_found" }

Causas:

  • Token nunca existiu
  • Token consumido em enrollment anterior
  • Token expirou (TTL)

Response 401 — token_consumed:

{ "error": "token_consumed" }

Token foi usado antes (single-use).

Response 401 — token_expired:

{ "error": "token_expired" }

Response 403 — hostname_mismatch:

{ "error": "hostname_mismatch" }

Hostname no request nao bate com o vinculado ao token.

Response 403 — role_mismatch:

{ "error": "role_mismatch" }

Response 502 — stepca_failed:

{ "error": "stepca_failed" }

Server nao conseguiu chamar step-ca (problema na CA, JWK errado, network).

Validacoes:

  1. SHA256(req.token) existe em enrollment_tokens
  2. consumed_at ainda NULL
  3. expires_at > now()
  4. expected_hostname == req.hostname
  5. expected_role == req.role
  6. CSR e parseavel (apenas verificacao basica — CN nao e validado aqui)

Apos sucesso:

  • INSERT agentes (id, hostname, role, cert, ...)
  • UPDATE enrollment_tokens SET consumed_at = now()

`POST /v1/certs/issue` (porta 8443, mTLS)

Agente autenticado pede novo cert (renovacao ou issue de cert pra outro hostname — futuro).

mTLS requerido: client cert assinado pela CA do step-ca, com CN = agent hostname.

Request body:

{
  "csr_pem": "-----BEGIN CERTIFICATE REQUEST-----\n..."
}

Response 200:

{
  "cert_pem": "-----BEGIN CERTIFICATE-----\n...",
  "ca_pem": "-----BEGIN CERTIFICATE-----\n..."
}

Response 400 — csr_invalid:

{ "error": "csr_invalid" }

CSR nao parseavel.

Response 403 — csr_cn_mismatch:

{ "error": "csr_cn_mismatch" }

CN do CSR != hostname do agent autenticado. (No MVP, agent so pode pedir cert pra si mesmo.)

Response 502 — stepca_failed:

{ "error": "stepca_failed" }

Uso tipico:

  • tatuzim-agent rotate (renovacao manual)
  • Loop interno do run (renovacao automatica perto da expiracao)
  • Processamento de carga csr_cert (gera cert pra escrever em out dir)

`GET /v1/manifest` (porta 8443, mTLS)

Lista entregas pendentes pro agent autenticado.

Request: sem body.

Response 200:

{
  "entregas": [
    {
      "id": "00000000-0000-4000-8000-000000000010",
      "carga_id": "00000000-0000-4000-8000-000000000020",
      "tipo": "csr_cert",
      "nome": "traefik-cert",
      "versao": 1,
      "status": "pendente"
    }
  ]
}

Response 200 sem cargas:

{ "entregas": [] }

Filtro: server filtra automaticamente — agent so ve cargas onde entregas.agente_id == este agent e status pendente ou em_andamento.

Tipos suportados no MVP:

  • csr_cert — agent processa via process_csr_cert
  • selado (futuro v0.2) — agent loga warn e skip
  • push (futuro v0.3) — agent loga warn e skip

`POST /v1/events` (porta 8443, mTLS)

Agent reporta evento de lifecycle. Insere em audit log com hash chain.

Request body:

{
  "tipo_evento": "entrega_instalada",
  "payload": {
    "cert_path": "/var/lib/tatuzim-agent/out/traefik-cert.crt",
    "key_path": "/var/lib/tatuzim-agent/out/traefik-cert.key",
    "carga_nome": "traefik-cert"
  },
  "entrega_id": "00000000-0000-...",
  "request_id": null
}

Response 200:

{
  "evento_id": "00000000-0000-4000-8000-000000000003",
  "hash_atual": "0000000000000000000000000000000000000000000000000000000000000000"
}

Tipos de evento conhecidos (tipo_evento):

Tipo Emitido por Quando Payload tipico
agent_started agent boot do run {"hostname": "..."}
cert_renewed agent apos rotate (auto ou manual) `{"old_serial", "new_serial", "trigger": "auto_threshold"
entrega_instalada agent apos process_csr_cert ok {"cert_path", "key_path", "carga_nome"}
entrega_falhou agent (futuro) apos falha de processamento {"erro": "..."}
manifest_pulled agent (futuro) a cada poll {"count": N}

Side-effects no server:

  • INSERT eventos (com hash_atual = SHA256(hash_anterior || payload || timestamp))
  • UPDATE agentes.last_seen_at = now()
  • Se tipo_evento{entrega_instalada, entrega_entregue, entrega_falhou} E entrega_id presente: UPDATE entregas.status

Middleware mTLS

Endpoints na porta 8443 (/v1/certs/issue, /v1/manifest, /v1/events) passam pelo middleware require_authenticated_agent:

  1. rustls valida cert do cliente (chain ate root CA do step-ca)
  2. Middleware extrai CN do client cert (extract_cn)
  3. find_agente_by_hostname(cn) → busca no DB
  4. Se ausente → 401 agent_not_registered
  5. Se revoked_at IS NOT NULL → 401 agent_revoked
  6. Injeta Arc<Agente> como axum::Extension
  7. Handler recebe via Extension<Arc<Agente>>

Headers HTTP esperados

Todos os endpoints aceitam:

  • Content-Type: application/json (mandatorio em POST)
  • User-Agent: tatuzim-agent/0.1.0 (informativo)

Nenhum header de auth — autenticacao e via mTLS (cert do cliente).

Exemplo de chamada manual (com curl)

# Read identity
CERT=/var/lib/tatuzim-agent/identity/agent.crt
KEY=/var/lib/tatuzim-agent/identity/agent.key
CHAIN=/tmp/agent-chain.pem

# Bundle cert + intermediate (server precisa da chain)
sudo cat "$CERT" /var/lib/tatuzim-agent/identity/ca.pem > "$CHAIN"
sudo chmod 0644 "$CHAIN"

# Manifest
curl --cert "$CHAIN" --key "$KEY" \
     --cacert /etc/tatuzim-agent/stepca-root.pem \
     https://tatuzim.dev.borlot.com.br:8443/v1/manifest

# Renew (CSR pode ser gerado via openssl req)
openssl req -new -newkey ec:<(openssl ecparam -name prime256v1) -nodes \
    -keyout /tmp/new.key -out /tmp/new.csr -subj "/CN=vps-01" -batch 2>/dev/null
CSR=$(cat /tmp/new.csr)

curl --cert "$CHAIN" --key "$KEY" \
     --cacert /etc/tatuzim-agent/stepca-root.pem \
     -X POST -H "Content-Type: application/json" \
     -d "$(jq -nc --arg c "$CSR" '{csr_pem:$c}')" \
     https://tatuzim.dev.borlot.com.br:8443/v1/certs/issue

# Event
curl --cert "$CHAIN" --key "$KEY" \
     --cacert /etc/tatuzim-agent/stepca-root.pem \
     -X POST -H "Content-Type: application/json" \
     -d '{"tipo_evento":"manifest_pulled","payload":{"count":0}}' \
     https://tatuzim.dev.borlot.com.br:8443/v1/events

Versionamento da API

  • v1 atual: endpoints /v1/...
  • Mudancas breaking → nova versao (/v2/...)
  • Mudancas backward-compat → mesma versao

Roadmap

Endpoint Versao
GET /v1/blobs/<id> (download de blob selado) v0.2
POST /v1/push/<agent_id> (server-initiated push) v0.3
Admin API (revoke agent, list cargas, etc.) v0.4
OpenAPI spec v0.4
By Borlot.com.br on 23/05/2026