# TLS al origen y Origin CA

> Emite y revoca certificados de origen de la OpenWAF Origin CA, comprueba el TLS al origen y descarga las CA.

Cifra y autentica la conexión entre el borde y tu origen. El borde verifica en modo `full_strict` los certificados de origen emitidos por la OpenWAF Origin CA, y puede presentar su propio certificado de cliente (mTLS) a los orígenes que lo exijan. Consulta [SSL/TLS](https://developers.openwaf.dev/es/ssl/).

| Operación | Ruta |
|---|---|
| [Listar certificados de origen](https://developers.openwaf.dev/es/api/origin-tls/#list-origin-certs) | `GET /v1/orgs/{org}/zones/{zone}/origin-certs` |
| [Emitir un certificado de origen](https://developers.openwaf.dev/es/api/origin-tls/#issue-origin-cert) | `POST /v1/orgs/{org}/zones/{zone}/origin-certs` |
| [Revocar un certificado de origen](https://developers.openwaf.dev/es/api/origin-tls/#revoke-origin-cert) | `DELETE /v1/orgs/{org}/zones/{zone}/origin-certs/{cert}` |
| [Ver el estado del TLS al origen](https://developers.openwaf.dev/es/api/origin-tls/#get-origin-tls-status) | `GET /v1/orgs/{org}/zones/{zone}/origin-tls/status` |
| [Descargar la Origin CA](https://developers.openwaf.dev/es/api/origin-tls/#get-origin-ca) | `GET /v1/origin-tls/origin-ca.pem` |
| [Descargar la CA de cliente del borde](https://developers.openwaf.dev/es/api/origin-tls/#get-edge-client-ca) | `GET /v1/origin-tls/edge-client-ca.pem` |

## Listar certificados de origen

```http
GET /v1/orgs/{org}/zones/{zone}/origin-certs
```

Lista los certificados de origen emitidos para la zona, del más reciente al más antiguo, con sus nombres, tipo de clave, serie, caducidad y estado (`active`, `revoked`, `expired`). Nunca devuelve claves privadas.

**Rol necesario:** `readonly` o superior.

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |

#### Respuesta

- `200`: Devuelve `items`, un array de [OriginCert](https://developers.openwaf.dev/es/api/schemas/#origin-cert).

| Campo | Tipo | Descripción |
|---|---|---|
| `items[].id` | string (uuid) | Id. |
| `items[].zone_id` | string (uuid) | Id de la zona. |
| `items[].hostnames` | array de string |  |
| `items[].key_type` | string | `csr`: se certificó la clave pública de una petición. Uno de `ecdsa_p256`, `rsa2048`, `csr`. |
| `items[].serial` | string | Hexadecimal en minúsculas sin ceros a la izquierda. |
| `items[].fingerprint_sha256` | string | Huella SHA-256 del certificado. |
| `items[].not_before` | string (date-time) | Inicio de la validez. |
| `items[].not_after` | string (date-time) | Fin de la validez. |
| `items[].validity_days` | integer |  |
| `items[].status` | string | revoked gana a expired. Uno de `active`, `revoked`, `expired`. |
| `items[].created_by` | string | Quién lo creó (actor). |
| `items[].created_at` | string (date-time) | Fecha de creación. |
| `items[].revoked_at` | string (date-time) o null | Fecha de revocación (null si está activo). |
| `items[].revoked_by` | string o null | Quién lo revocó. |

#### Errores

| Estado | Descripción |
|---|---|
| `404` | No existe o el token no lo ve (`not_found`). |
| `503` | `unavailable`: la Origin CA no está disponible ahora. |

#### Ejemplo

**curl**


```sh
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-certs" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.listOriginCerts({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.list_origin_certs(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d")
```



```json
{
  "items": [
    {
      "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
      "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
      "hostnames": [
        "example.com",
        "*.example.com"
      ],
      "key_type": "ecdsa_p256",
      "serial": "5f3a9c0e7b21d4a68c13e9f02b7d4a61",
      "fingerprint_sha256": "example",
      "not_before": "2026-10-27T09:30:00Z",
      "not_after": "2026-10-27T09:30:00Z",
      "validity_days": 0,
      "status": "active",
      "created_by": "token:11111111-2222-4333-8444-555555555555",
      "created_at": "2026-10-27T09:30:00Z",
      "revoked_at": "2026-10-27T09:30:00Z",
      "revoked_by": null
    }
  ]
}
```

## Emitir un certificado de origen

```http
POST /v1/orgs/{org}/zones/{zone}/origin-certs
```

Emite un certificado firmado por la OpenWAF Origin CA para nombres de la zona, para instalarlo en el servidor de origen y que el borde lo pueda verificar en modo `full_strict`. El certificado solo lleva nombres DNS (SAN), uso de autenticación de servidor, un número de serie aleatorio de 128 bits y el primer nombre como CN.

OpenWAF genera la clave (`key_type`) salvo que envíes un `csr`. La clave privada solo se devuelve en esta respuesta (`Cache-Control: no-store`) y nunca se guarda. Con `dry_run=1` solo se valida la petición.

**Rol necesario:** `tech` o superior.

**Controles de cambio:** [`dry_run`](https://developers.openwaf.dev/es/api/#configuration-changes) · [`X-Change-Reason`](https://developers.openwaf.dev/es/api/#configuration-changes) (opcional)

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |
| `X-Change-Reason` | cabecera | string | No | Motivo del cambio (1-500 caracteres UTF-8, sin caracteres de control); por defecto, el alta de la zona. De 1 a 500 caracteres. |
| `dry_run` | consulta | string | No | `1`/`true` simula el cambio: responde 200 con el plan (`Applied`) y no guarda nada. Admite `0`/`false`; otro valor: 400. Uno de `1`, `true`, `0`, `false`. |

#### Cuerpo de la petición

Tipo de contenido `application/json`. Esquema: [OriginCertRequest](https://developers.openwaf.dev/es/api/schemas/#origin-cert-request).

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `hostnames` | array de string | Sí | La zona, nombres por debajo o comodines `*.<nombre de la zona>`; se pasan a minúsculas y sin repetir. De 1 a 100 elementos. |
| `key_type` | string | No | Clave que genera OpenWAF (vacío con `csr`). Uno de `ecdsa_p256`, `rsa2048`. Por defecto `ecdsa_p256`. |
| `validity_days` | integer | No | Uno de `7`, `30`, `90`, `365`, `730`, `1095`, `5475`. Por defecto `5475`. |
| `csr` | string | No | Petición PEM (ECDSA P-256/P-384 o RSA 2048-8192): se certifica su clave pública; su sujeto y extensiones se ignoran y no se genera clave. |

#### Respuesta

- `200`: Simulación. Devuelve [OriginCertIssued](https://developers.openwaf.dev/es/api/schemas/#origin-cert-issued).
- `201`: Emitido. Devuelve [OriginCertIssued](https://developers.openwaf.dev/es/api/schemas/#origin-cert-issued).

| Campo | Tipo | Descripción |
|---|---|---|
| `dry_run` | boolean | Solo con `?dry_run=1`: lo que se emitiría; sin PEM. |
| `certificate` | [OriginCert](https://developers.openwaf.dev/es/api/schemas/#origin-cert) |  |
| `certificate_pem` | string |  |
| `private_key_pem` | string | PKCS#8. Solo en esta respuesta (no se guarda); se omite con `csr`. |
| `ca_pem` | string | La OpenWAF Origin CA que lo firma. |

#### Errores

| Estado | Descripción |
|---|---|
| `400` | Petición mal formada (`bad_request`). |
| `403` | El rol o los límites del token no lo permiten (`forbidden`). |
| `404` | No existe o el token no lo ve (`not_found`). |
| `409` | `zone_merged`: la zona está fusionada en otra (solo lectura hasta que se separe o se purgue). |
| `422` | Error de validación (`invalid`) o motivo ausente/no válido (`reason_required`). |
| `503` | `unavailable`: la Origin CA no está disponible ahora. |

#### Ejemplo

**curl**


```sh
curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-certs" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Origin certificate for the web servers" \
  -H "Content-Type: application/json" \
  --data '{
    "hostnames": [
      "example.com",
      "*.example.com"
    ],
    "key_type": "ecdsa_p256",
    "validity_days": 5475
  }'
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.issueOriginCert({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  body: {
    hostnames: [
      "example.com",
      "*.example.com"
    ],
    key_type: "ecdsa_p256",
    validity_days: 5475
  }
}, {
  reason: "Origin certificate for the web servers"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.issue_origin_cert(
    zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    body={
        "hostnames": [
            "example.com",
            "*.example.com"
        ],
        "key_type": "ecdsa_p256",
        "validity_days": 5475
    },
    reason="Origin certificate for the web servers",
)
```



```json
{
  "dry_run": false,
  "certificate": {
    "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
    "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    "hostnames": [
      "example.com",
      "*.example.com"
    ],
    "key_type": "ecdsa_p256",
    "serial": "5f3a9c0e7b21d4a68c13e9f02b7d4a61",
    "fingerprint_sha256": "example",
    "not_before": "2026-10-27T09:30:00Z",
    "not_after": "2026-10-27T09:30:00Z",
    "validity_days": 0,
    "status": "active",
    "created_by": "token:11111111-2222-4333-8444-555555555555",
    "created_at": "2026-10-27T09:30:00Z",
    "revoked_at": "2026-10-27T09:30:00Z",
    "revoked_by": null
  },
  "certificate_pem": "example",
  "private_key_pem": "example",
  "ca_pem": "example"
}
```

## Revocar un certificado de origen

```http
DELETE /v1/orgs/{org}/zones/{zone}/origin-certs/{cert}
```

Revoca un certificado de origen. Los nodos del borde lo rechazan en modo `full_strict` desde su siguiente configuración, así que un origen que aún lo sirva recibe errores `526`. Revocarlo otra vez no cambia nada. Con `dry_run=1` solo se devuelve el certificado.

**Rol necesario:** `tech` o superior.

**Controles de cambio:** [`dry_run`](https://developers.openwaf.dev/es/api/#configuration-changes) · [`X-Change-Reason`](https://developers.openwaf.dev/es/api/#configuration-changes) (opcional)

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |
| `cert` | ruta | string (uuid) | Sí | Id del certificado. |
| `X-Change-Reason` | cabecera | string | No | Motivo del cambio (1-500 caracteres UTF-8, sin caracteres de control); por defecto, el alta de la zona. De 1 a 500 caracteres. |
| `dry_run` | consulta | string | No | `1`/`true` simula el cambio: responde 200 con el plan (`Applied`) y no guarda nada. Admite `0`/`false`; otro valor: 400. Uno de `1`, `true`, `0`, `false`. |

#### Respuesta

- `200`: El certificado (revocado). Devuelve [OriginCert](https://developers.openwaf.dev/es/api/schemas/#origin-cert).

| Campo | Tipo | Descripción |
|---|---|---|
| `id` | string (uuid) | Id. |
| `zone_id` | string (uuid) | Id de la zona. |
| `hostnames` | array de string |  |
| `key_type` | string | `csr`: se certificó la clave pública de una petición. Uno de `ecdsa_p256`, `rsa2048`, `csr`. |
| `serial` | string | Hexadecimal en minúsculas sin ceros a la izquierda. |
| `fingerprint_sha256` | string | Huella SHA-256 del certificado. |
| `not_before` | string (date-time) | Inicio de la validez. |
| `not_after` | string (date-time) | Fin de la validez. |
| `validity_days` | integer |  |
| `status` | string | revoked gana a expired. Uno de `active`, `revoked`, `expired`. |
| `created_by` | string | Quién lo creó (actor). |
| `created_at` | string (date-time) | Fecha de creación. |
| `revoked_at` | string (date-time) o null | Fecha de revocación (null si está activo). |
| `revoked_by` | string o null | Quién lo revocó. |

#### Errores

| Estado | Descripción |
|---|---|
| `403` | El rol o los límites del token no lo permiten (`forbidden`). |
| `404` | No existe o el token no lo ve (`not_found`). |

#### Ejemplo

**curl**


```sh
curl -X DELETE "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-certs/9e7c5a3b-1d2f-4e8a-b6c4-3a5d7f9b1c2e" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Server decommissioned"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.revokeOriginCert({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  cert: "9e7c5a3b-1d2f-4e8a-b6c4-3a5d7f9b1c2e"
}, {
  reason: "Server decommissioned"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.revoke_origin_cert(
    zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    cert="9e7c5a3b-1d2f-4e8a-b6c4-3a5d7f9b1c2e",
    reason="Server decommissioned",
)
```



```json
{
  "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
  "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  "hostnames": [
    "example.com",
    "*.example.com"
  ],
  "key_type": "ecdsa_p256",
  "serial": "5f3a9c0e7b21d4a68c13e9f02b7d4a61",
  "fingerprint_sha256": "example",
  "not_before": "2026-10-27T09:30:00Z",
  "not_after": "2026-10-27T09:30:00Z",
  "validity_days": 0,
  "status": "active",
  "created_by": "token:11111111-2222-4333-8444-555555555555",
  "created_at": "2026-10-27T09:30:00Z",
  "revoked_at": "2026-10-27T09:30:00Z",
  "revoked_by": null
}
```

## Ver el estado del TLS al origen

```http
GET /v1/orgs/{org}/zones/{zone}/origin-tls/status
```

Para cada nombre con proxy, devuelve sus ajustes efectivos de TLS al origen, lo que muestra un handshake TLS con el origen (sin petición HTTP; las direcciones privadas, de loopback y de enlace local quedan `skipped`) y los errores `525`/`526` que ha notificado el borde en las últimas 24 horas. El resultado se guarda 60 segundos por zona.

**Rol necesario:** `readonly` o superior.

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |

#### Respuesta

- `200`: Devuelve [OriginTLSStatus](https://developers.openwaf.dev/es/api/schemas/#origin-tls-status).

| Campo | Tipo | Descripción |
|---|---|---|
| `checked_at` | string (date-time) | Última comprobación. |
| `hosts` | array de object |  |
| `hosts[].host` | string |  |
| `hosts[].upstream_url` | string | Sin credenciales. |
| `hosts[].tls_verify` | string | Uno de `full`, `full_strict`. |
| `hosts[].server_name` | string | Configurado (vacío = el de siempre). |
| `hosts[].client_cert` | boolean |  |
| `hosts[].effective_server_name` | string | SNI y nombre verificado: server_name, o el host del origen si es un nombre, o el del sitio si es una IP. |
| `hosts[].probe` | object |  |
| `hosts[].edge_errors_24h` | integer | Eventos 525/526 del borde en las últimas 24 h. |
| `hosts[].last_edge_error` | object o null |  |

#### Errores

| Estado | Descripción |
|---|---|
| `404` | No existe o el token no lo ve (`not_found`). |

#### Ejemplo

**curl**


```sh
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-tls/status" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.getOriginTlsStatus({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.get_origin_tls_status(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d")
```



```json
{
  "checked_at": "2026-10-27T09:30:00Z",
  "hosts": [
    {
      "host": "www.example.com",
      "upstream_url": "https://203.0.113.10",
      "tls_verify": "full",
      "server_name": "",
      "client_cert": false,
      "effective_server_name": "example",
      "probe": {
        "state": "ok",
        "error": "",
        "issuer": "example",
        "subject": "example",
        "not_after": "2026-10-27T09:30:00Z",
        "days_left": 0,
        "name_match": false,
        "trusted_by": "public",
        "requests_client_cert": false
      },
      "edge_errors_24h": 0,
      "last_edge_error": {
        "ts": "2026-10-27T09:30:00Z",
        "status": 525,
        "msg": ""
      }
    }
  ]
}
```

## Descargar la Origin CA

```http
GET /v1/origin-tls/origin-ca.pem
```

Devuelve, en PEM, todos los certificados no caducados de la OpenWAF Origin CA (varios durante una rotación). En modo `full_strict` el borde confía en estos certificados además de en las raíces públicas.

**Rol necesario:** Ninguno: público, sin token.

#### Respuesta

- `200`: Cuerpo de tipo `text/plain`.

#### Errores

| Estado | Descripción |
|---|---|
| `503` | `unavailable`: la Origin CA no está disponible ahora. |

#### Ejemplo

**curl**


```sh
curl "https://dash.openwaf.dev/v1/origin-tls/origin-ca.pem"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.getOriginCa();
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.get_origin_ca()
```



```text
-----BEGIN CERTIFICATE-----
MIIB…
-----END CERTIFICATE-----
```

## Descargar la CA de cliente del borde

```http
GET /v1/origin-tls/edge-client-ca.pem
```

Devuelve, en PEM, la CA de los certificados de cliente que el borde presenta a los orígenes (mTLS). Configúrala en un origen que exija el certificado del borde.

**Rol necesario:** Ninguno: público, sin token.

#### Respuesta

- `200`: Cuerpo de tipo `text/plain`.

#### Errores

| Estado | Descripción |
|---|---|
| `503` | `unavailable`: la Origin CA no está disponible ahora. |

#### Ejemplo

**curl**


```sh
curl "https://dash.openwaf.dev/v1/origin-tls/edge-client-ca.pem"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.getEdgeClientCa();
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.get_edge_client_ca()
```



```text
-----BEGIN CERTIFICATE-----
MIIB…
-----END CERTIFICATE-----
```
