SDK de Python y CLI
Mantenido por: Aether365 Team Audiencia: Desarrolladores e ingenieros DevOps Alcance: Instalación y uso del SDK de Python oficial aether365 y de su interfaz de línea de comandos
El paquete aether365 es el SDK de Python oficial y la interfaz de línea de comandos de la API de Aether365. Envuelve https://api.aether365.io, gestiona la autenticación, desempaqueta el envelope de respuesta y reintenta automáticamente los fallos transitorios. Todo lo que puedes hacer con curl puedes hacerlo con el SDK o la CLI.
El SDK y la CLI usan una clave de API (ak_live_...) y apuntan al endpoint unificado: las peticiones se enrutan automáticamente a la región de origen de tu tenant. Consulta Autenticación para saber cómo funcionan las claves.
Instalación
El paquete se distribuye internamente (no está en PyPI). Instálalo directamente desde el repositorio o desde un checkout local.
bash
# From a local checkout of the monorepo
pip install ./packages/cli
# Or with Poetry, from a path
poetry add ./packages/cliSe requiere Python 3.12 o superior. Al instalar el paquete, el comando aether365 queda además disponible en tu PATH.
Configuración
Tanto el SDK como la CLI leen las mismas variables de entorno:
| Variable | Propósito | Por defecto |
|---|---|---|
AETHER365_API_KEY | Tu clave de API (ak_live_...). Obligatoria. | - |
AETHER365_API_URL | Sobrescribe la URL base (por ejemplo, el endpoint de dev). | https://api.aether365.io |
AETHER365_OUTPUT | Formato de salida de la CLI: table o json. | table |
bash
export AETHER365_API_KEY="ak_live_..."La región es automática: la API resuelve tu tenant a partir de la clave y reenvía la petición a la región correcta, así que nunca tienes que configurar ni pasar una región.
Inicio rápido: SDK
python
from aether365 import Aether365Client
with Aether365Client() as client: # reads AETHER365_API_KEY
tenant = client.tenants.me()
print(tenant["name"])
# Trigger a scan and wait for it to finish
scan = client.scans.create("compliance")
for snapshot in client.scans.wait(scan["id"]):
print(snapshot["status"])
# Read the findings
findings = client.scans.results(scan["id"], result="Failed")
print(f"{len(findings)} failed checks")Inicio rápido: CLI
bash
# Show the authenticated tenant
aether365 tenant me
# Trigger a scan and follow progress until it finishes
aether365 scan run --type compliance --watch
# List scans as JSON
aether365 --output json scan list
# Fail a CI job when a scan has failed findings (exit code 2)
aether365 scan results <SCAN_ID> --fail-on-findingsQué puede y qué no puede hacer una clave de API
Una clave de API está limitada a una superficie segura, de lectura más operaciones. Puede leer tus datos, lanzar y gestionar scans, generar informes y gestionar los objetivos de superficie de ataque. No puede realizar acciones de control de la cuenta ni de bloqueo del directorio: gestión de claves de API, facturación, miembros del equipo, alta/baja de conexiones, SSO, aplicar un plan de remediación o las escrituras de conditional-access/break-glass de AI Pilot. Esas acciones siguen disponibles solo para una sesión iniciada en el dashboard. Una petición a una ruta no permitida devuelve 403 AUTH_INSUFFICIENT_SCOPE.
Referencia de comandos y métodos
Cada fila muestra la misma operación de tres formas: curl en crudo, el método del SDK y el comando de la CLI.
Tenant y cuenta
| Operación | curl | SDK | CLI |
|---|---|---|---|
| Obtener el perfil del tenant | GET /tenants/me | client.tenants.me() | aether365 tenant me |
| Listar conexiones | GET /tenants/me/connections | client.connections.list() | aether365 tenant connections |
| Preferencias de notificación | GET /tenants/me/notifications | client.notifications.get() | aether365 tenant notifications |
| Scans programados | GET /tenants/me/scheduled-scans | client.scheduled_scans.list() | aether365 tenant scheduled-scans |
Scans
| Operación | curl | SDK | CLI |
|---|---|---|---|
| Listar scans | GET /tenants/me/scans | client.scans.list() | aether365 scan list |
| Lanzar un scan | POST /tenants/me/scans | client.scans.create("compliance") | aether365 scan run --type compliance |
| Obtener un scan | GET /scans/{id} | client.scans.get(id) | aether365 scan get <id> |
| Leer resultados | GET /scans/{id}/results | client.scans.results(id) | aether365 scan results <id> |
| Cancelar un scan | POST /scans/{id}/cancel | client.scans.cancel(id) | aether365 scan cancel <id> |
| Ocultar un scan | PATCH /scans/{id}/hide | client.scans.hide(id) | aether365 scan hide <id> |
| Mostrar un scan oculto | PATCH /scans/{id}/unhide | client.scans.unhide(id) | aether365 scan unhide <id> |
| Eliminar un scan | DELETE /scans/{id} | client.scans.delete(id) | aether365 scan delete <id> |
El curl para lanzar un scan:
bash
curl -X POST https://api.aether365.io/tenants/me/scans \
-H "Authorization: Bearer $AETHER365_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scanType": "compliance"}'Informes
| Operación | curl | SDK | CLI |
|---|---|---|---|
| Estado del informe | GET /scans/{id}/report | client.reports.status(id) | aether365 report status <id> |
| Generar un informe | POST /scans/{id}/report/generate | client.reports.generate(id) | aether365 report generate <id> |
| Listar informes | GET /tenants/me/reports | client.reports.list() | aether365 report list |
| Descargar un informe | GET /scans/{id}/report (presigned) | client.reports.download(id, path) | aether365 report download <id> -o out.pdf |
El estado de un informe es uno de ready, rendering, orgNameRequired o locked. Generar un informe puede consumir un crédito de informe (un cargo único cuando no hay asignación incluida): el propietario de la clave lo acepta al llamar a generate.
El listado de informes usa paginación por offset: devuelve meta["nextCursor"] (un offset de fila entero, o None en la última página) como cursor. client.reports.iter_all() sigue el cursor por ti.
Superficie de ataque
| Operación | curl | SDK | CLI |
|---|---|---|---|
| Resumen | GET /tenants/me/attack-surface | client.attack_surface.summary() | aether365 eas summary |
| Historial | GET /tenants/me/attack-surface/history | client.attack_surface.history() | aether365 eas history |
| Listar objetivos | GET /tenants/me/attack-surface/targets | client.attack_surface.targets() | aether365 eas targets list |
| Añadir un objetivo | POST /tenants/me/attack-surface/targets | client.attack_surface.add_target(host) | aether365 eas targets add <host> |
| Actualizar un objetivo | PATCH /tenants/me/attack-surface/targets/{id} | client.attack_surface.update_target(id, ...) | aether365 eas targets update <id> --label ... |
| Eliminar un objetivo | DELETE /tenants/me/attack-surface/targets/{id} | client.attack_surface.remove_target(id) | aether365 eas targets remove <id> |
| Verificar un objetivo | POST /tenants/me/attack-surface/targets/{id}/verify | client.attack_surface.verify_target(id) | aether365 eas targets verify <id> |
| Ejecutar un scan EAS | POST /tenants/me/attack-surface/scan | client.attack_surface.scan() | aether365 eas scan |
Postura, remediación y AI Pilot
| Operación | curl | SDK | CLI |
|---|---|---|---|
| Vista de amenazas / riesgos | GET /tenants/me/threats | client.threats.list() | aether365 threats |
| Postura de políticas | GET /tenants/me/policies | client.policies.list() | aether365 policies |
| Capacidades de remediación | GET /tenants/me/remediation/capabilities | client.remediation.capabilities() | aether365 remediation capabilities |
| Listar planes de remediación | GET /tenants/me/remediation-plans | client.remediation.plans() | aether365 remediation plans |
| Obtener un plan de remediación | GET /tenants/me/remediation-plans/{id} | client.remediation.plan(id) | aether365 remediation plan <id> |
| Remediables de AI Pilot | GET /tenants/me/ai-pilot/remediable | client.ai_pilot.remediable() | aether365 ai-pilot remediable |
| Break-glass de AI Pilot | GET /tenants/me/ai-pilot/break-glass | client.ai_pilot.break_glass() | aether365 ai-pilot break-glass |
| Políticas CA de AI Pilot | GET /tenants/me/ai-pilot/conditional-access | client.ai_pilot.conditional_access() | aether365 ai-pilot conditional-access |
La remediación es de solo lectura para las claves de API. Aplicar un plan no está expuesto: los elementos del action-registry de un plan pueden incluir parches de Conditional-Access / authorization-policy capaces de dejar a un tenant fuera de su propio directorio (AADSTS50097), así que la aplicación se queda en el dashboard con un operador supervisando. Las vistas de conditional-access y break-glass de AI Pilot se exponen igualmente en solo lectura por el mismo motivo.
Gestión de errores y reintentos
Cada error de la API se corresponde con una excepción tipada, subclase de Aether365Error:
python
from aether365 import Aether365Client
from aether365.exceptions import PermissionError_, PlanLimitError, NotFoundError
with Aether365Client() as client:
try:
client.scans.create("compliance")
except PlanLimitError:
print("Scan quota reached for this plan.")
except PermissionError_:
print("Route not allowed for this key, or plan lacks API access.")
except NotFoundError:
print("Resource not found.")El cliente reintenta automáticamente 429 RATE_LIMITED y 503 SERVICE_STARTING (el arranque en frío de la base de datos de dev) con backoff exponencial, respetando la cabecera Retry-After. Los errores de cuota como SCAN_PLAN_LIMIT_REACHED se lanzan de inmediato: no se reintentan.
Uso de la CLI en CI
aether365 scan results <id> --fail-on-findings termina con código 2 cuando existe algún hallazgo fallido, de modo que un pipeline puede condicionar su resultado a los resultados del scan:
bash
aether365 scan run --type compliance --watch
aether365 scan results "$SCAN_ID" --fail-on-findingsLa CLI escribe los errores en stderr y termina con 1 ante cualquier error de la API y con 2 en la puerta de hallazgos.