CLI

Tres subcomandos. La config por defecto es condor.yaml en el cwd; se cambia con -c/--config.

$ condor check  -c condor.yaml   # valida y sale (exit 0 si es válida)
$ condor start  -c condor.yaml   # inicia listeners + endpoints de gestión
$ condor reload -c condor.yaml   # POST /reload al admin_addr de la config

El reload también se dispara con SIGHUP al proceso (systemctl reload condor).

global

Ajustes de proceso: nivel y formato de log, y direcciones de los endpoints de gestión.

global:
  log_level: info            # debug | info | warn | error
  log_format: json           # json | pretty
  metrics_addr: "0.0.0.0:9090"  # /health, /metrics (scrape público)
  admin_addr:   "127.0.0.1:9091" # + /config, /reload (solo local)
RUST_LOG sobreescribe log_level si está presente.

listeners & TLS

Cada listener enlaza una dirección. La presencia de la clave tls activa HTTPS. Sin ella, es HTTP en claro. El listener detecta HTTP/1.1 y HTTP/2 automáticamente.

listeners:
  - addr: "0.0.0.0:80"          # HTTP en claro

  - addr: "0.0.0.0:443"
    tls:
      mode: file              # file | acme | self_signed
      cert: /etc/condor/tls/fullchain.pem
      key:  /etc/condor/tls/privkey.pem
  • mode: file — carga cert + key en PEM. Soporta SNI multi-dominio.
  • mode: self_signed — genera un cert autofirmado en memoria (dev / interno).
  • mode: acme — emite con Let's Encrypt (ver abajo).

ACME / Let's Encrypt

Con mode: acme, Condor obtiene y renueva certificados vía desafío HTTP-01. Requiere un listener en el puerto 80 accesible públicamente (sirve /.well-known/acme-challenge/) y DNS apuntando al host.

tls:
  mode: acme
  acme_email: ops@falp.cl
  acme_domains: ["api.falp.cl", "www.falp.cl"]
  cache_dir: /var/lib/condor/certs   # account.json + cert.pem + key.pem

El certificado se cachea en cache_dir y se renueva en segundo plano antes de expirar. El resolver SNI se actualiza en caliente cuando llega el cert nuevo.

routes

Las rutas se resuelven por especificidad: host exacto → prefijo de path (el más largo gana) → host wildcard (*.dominio) → catch-all.

routes:
  - name: api
    match_host: api.falp.cl     # opcional; admite *.falp.cl
    match_path: /hercules/*     # exacto (/health) o prefijo (/x/*)
    upstream: hercules-pool    # debe existir en upstreams
    middleware: [ ... ]

Al reenviar, Condor agrega X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host y X-Request-Id (ULID), y elimina los headers hop-by-hop.

middleware

Lista ordenada por ruta. Un middleware que rechaza corta el flujo con el status correspondiente.

middleware:
  - type: auth
    mode: jwt                  # jwt | api_key
    jwks_url: "https://auth.falp.cl/.well-known/jwks.json"
    # mode: api_key → api_keys: ["k1", "k2"]  (header X-Api-Key)

  - type: ratelimit
    requests_per_second: 500
    burst: 100                  # token bucket por IP → 429 + Retry-After

  - type: audit                 # registra la petición (ver audit)
  • auth jwt — valida el Bearer contra el JWKS (refrescado en background); el sub queda como user_id en el audit. Falla → 401/403.
  • auth api_key — compara el header X-Api-Key contra la lista.
  • ratelimit — token bucket por IP de origen; excedido → 429.

upstreams

Pool de targets con estrategia de balanceo, health checks y circuit breaker.

upstreams:
  - name: hercules-pool
    strategy: round_robin   # round_robin (ponderado) | least_conn | random | ip_hash
    http2: false             # true = h2c prior-knowledge hacia el upstream
    targets:
      - { addr: "10.0.1.10:8080", weight: 1 }
      - { addr: "10.0.1.11:8080", weight: 2 }
    health_check:
      path: /health
      interval_secs: 5
      timeout_ms: 2000
      healthy_threshold: 2
      unhealthy_threshold: 3
    circuit_breaker:
      failure_threshold: 50     # % de error en la ventana para abrir
      recovery_secs: 30         # tiempo en Open antes de HalfOpen

audit

Log append-only en JSONL, una línea por petición. Nunca modifica líneas existentes (compatible con sinks WORM).

audit:
  enabled: true
  path: /var/log/condor/audit.jsonl
  rotate: daily             # daily | never → audit-YYYY-MM-DD.jsonl
  fields: [timestamp, request_id, client_ip, method, host,
           path, status, upstream, duration_ms, user_id]

Cada línea, con claves compactas:

{"ts":"2026-05-24T03:14:15.926Z","rid":"01J...","cip":"1.2.3.4",
 "m":"GET","host":"api.falp.cl","path":"/hercules/patients",
 "st":200,"up":"hercules-pool","dur_ms":12,"uid":"rvielma"}

endpoints de gestión

  • GET /health — estado y salud de upstreams (JSON). En metrics_addr y admin_addr.
  • GET /metrics — formato de texto Prometheus.
  • GET /config — config actual sin secretos. Solo en admin_addr.
  • POST /reload — dispara hot reload. Solo en admin_addr.

métricas

condor_requests_total{route,upstream,status,method}
condor_request_duration_seconds{route,upstream}
condor_upstream_health{upstream,target}
condor_circuit_state{upstream}            # 0=closed 1=open 2=half
condor_tls_handshakes_total{listener,result}
condor_ratelimit_rejected_total{route}
condor_upstream_pool_size{upstream}

deploy

Compilá un binario estático y desplegalo con systemd:

$ cargo zigbuild --release --target x86_64-unknown-linux-musl --bin condor
$ scp target/x86_64-unknown-linux-musl/release/condor  servidor:/tmp/
$ sudo install -m755 /tmp/condor /usr/local/bin/condor
$ sudo install -m644 deploy/condor.service /etc/systemd/system/
$ sudo systemctl enable --now condor
El hot reload aplica cambios de rutas, upstreams y middleware en caliente. Cambiar direcciones de listener o la config TLS requiere reiniciar el servicio.