# Wildcard SSL de sysfarma.pe - 2026-06-20

## Objetivo

Dejar `*.sysfarma.pe` con HTTPS valido inmediato para cualquier subdominio nuevo, sin reemitir certificados por tenant y sin tocar Apache por cada alta.

## Resultado final

Desde el `2026-06-20`, `sysfarma.pe` usa un certificado wildcard real:

- Certificado activo en Apache: `sysfarma-wildcard`
- SAN del certificado: `sysfarma.pe`, `*.sysfarma.pe`
- Comportamiento esperado:
  - cualquier `nuevo.sysfarma.pe` entra con SSL valido de inmediato,
  - si el subdominio existe en `hostnames`, muestra login del tenant,
  - si el subdominio no existe en `hostnames`, cae al login del sistema central.

## Problema original

Antes de este cambio, `sysfarma.pe` dependia de un certificado SAN con lista manual o expandible de dominios:

- cada tenant nuevo debia entrar al SAN del cert,
- si no entraba a tiempo, el navegador mostraba `ERR_CERT_COMMON_NAME_INVALID`,
- esto obligaba a reemitir el certificado por cada tenant nuevo.

Ese modelo era fragil y no escala.

## Cambio de arquitectura

Se reemplazo el modelo `SAN por tenant` por `wildcard para todo el dominio`.

### Infraestructura validada

- Dominio publico: `sysfarma.pe`
- DNS administrado en Cloudflare
- App productiva: `/var/www/html/farmacia`
- Proxy SSL Apache: `/etc/apache2/sites-available/sysfarma.pe-ssl.conf`
- App backend detras del proxy: `http://127.0.0.1:8081/`

### Certificado emitido

Se emitio un nuevo certificado:

- Nombre certbot: `sysfarma-wildcard`
- Ruta:
  - `/etc/letsencrypt/live/sysfarma-wildcard/fullchain.pem`
  - `/etc/letsencrypt/live/sysfarma-wildcard/privkey.pem`

### Vhost Apache productivo

El vhost SSL de `sysfarma.pe` debe quedar con estas propiedades:

- `ServerName sysfarma.pe`
- `ServerAlias *.sysfarma.pe`
- `ProxyPass / http://127.0.0.1:8081/`
- `SSLCertificateFile /etc/letsencrypt/live/sysfarma-wildcard/fullchain.pem`
- `SSLCertificateKeyFile /etc/letsencrypt/live/sysfarma-wildcard/privkey.pem`

## Reglas que no deben cambiar

Estas reglas son obligatorias. Romper cualquiera puede reintroducir el problema.

1. No volver a `SSLCertificateFile /etc/letsencrypt/live/sysfarma.pe/...`.
   - Ese certificado viejo era SAN por lista de tenants.
   - Volver a usarlo reactiva el problema por tenant nuevo.

2. No reemplazar `ServerAlias *.sysfarma.pe` por una lista manual.
   - El wildcard SSL requiere que Apache acepte cualquier subdominio del dominio.

3. No usar `admin.komanda.pe` ni otro cert default para `sysfarma.pe`.
   - Si Apache cae al vhost por defecto, vuelve `ERR_CERT_COMMON_NAME_INVALID`.

4. No diagnosticar como problema SSL cuando el hostname no existe en tenancy.
   - Con wildcard activo, un subdominio inexistente puede abrir sin error SSL y aun asi mostrar login del superadmin.
   - Eso ya no es falla de certificado: es fallback del routing.

5. No volver a depender del script SAN como solucion principal.
   - El script `/usr/local/sbin/sync-sysfarma-san.sh` fue util como transicion.
   - Con wildcard activo, ya no es la solucion de arquitectura.

## Diferencia entre SSL y tenancy

Hay dos capas distintas:

### Capa 1: SSL

La resuelve Apache + Let's Encrypt.

Con el wildcard activo:

- `https://cualquiercosa.sysfarma.pe` debe presentar un cert valido,
- aunque ese subdominio no exista como tenant.

### Capa 2: Tenancy

La resuelve Laravel leyendo `hostnames`.

- si `TenancyManager::hostname()` encuentra el subdominio en `hostnames`, carga rutas tenant,
- si no lo encuentra, usa las rutas del sistema y `/login` muestra el login central.

Ejemplo real:

- `wild620a.sysfarma.pe`: tenant real, login tenant correcto
- `instantwild.sysfarma.pe`: no era tenant real, por eso mostro login del sistema

Eso es esperado y correcto.

## Validaciones realizadas

### Certificado servido

Se valido en servidor:

```bash
echo | openssl s_client -connect 127.0.0.1:443 -servername wild620a.sysfarma.pe 2>/dev/null \
  | openssl x509 -noout -subject -ext subjectAltName
```

Resultado esperado:

- `subject=CN = sysfarma.pe`
- `DNS:*.sysfarma.pe, DNS:sysfarma.pe`

### Prueba de wildcard puro

Se valido un subdominio no registrado:

- `https://instantwild.sysfarma.pe/login`

Resultado:

- SSL valido inmediato
- responde `HTTP/1.1 200 OK`
- muestra la app central porque no existia en `hostnames`

### Prueba end-to-end real

Se creo un tenant real de prueba:

- `wild620a.sysfarma.pe`
- empresa: `Wildcard Tenant 20260620`

Resultado:

- se creo como cliente real,
- abrio `https://wild620a.sysfarma.pe/login`,
- mostro `Bienvenido a Wildcard Tenant 20260620`,
- sin reemitir SAN por tenant.

### Prueba post-automatizacion

Despues de dejar la renovacion automatica activa, se creo otro tenant real de prueba:

- `auto620c.sysfarma.pe`
- empresa: `Wildcard Auto 20260620`

Resultado:

- el alta termino en el dashboard central,
- `https://auto620c.sysfarma.pe/login` abrio directo con HTTPS valido,
- mostro `Bienvenido a Wildcard Auto 20260620`,
- no se agrego el subdominio al certificado ni al vhost.

## Estado del codigo de la app

En este repo quedaron cambios de observabilidad y compatibilidad:

- `config/tenant.php`
  - `tenant_ssl_sync_command`
  - `tenant_ssl_sync_timeout`
- `app/Http/Controllers/System/ClientController.php`
  - intento de sync post-creacion,
  - chequeo HTTPS post-creacion,
  - diagnostico de errores SSL/DNS/timeout

Estos cambios no son la base del wildcard. El wildcard ya resuelve el problema principal desde infraestructura.

## Renovacion

La renovacion ya quedo automatizada con Cloudflare DNS.

### Estado actual

- Archivo de credenciales: `/etc/letsencrypt/cloudflare.ini`
- Certbot operativo: `/snap/bin/certbot`
- Renewal config activo: `/etc/letsencrypt/renewal/sysfarma-wildcard.conf`
- Authenticator configurado: `dns-cloudflare`
- Deploy hook: `/etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh`

### Reglas operativas

1. No volver a renovar `sysfarma-wildcard` con `manual` ni con `webroot`.
   - La configuracion valida ya quedo escrita en `sysfarma-wildcard.conf`.

2. No borrar ni mover `/etc/letsencrypt/cloudflare.ini` sin reemplazarlo por otro token valido.
   - Sin ese archivo, la renovacion automatica falla.

3. No ampliar el alcance del token de Cloudflare.
   - El token creado para esta automatizacion debe quedar restringido a la zona `sysfarma.pe` con permiso `DNS Write`.

4. Mantener el reload de Apache en el hook de deploy.
   - El cert puede renovarse correctamente pero no quedar servido hasta recargar Apache.

### Validacion de automatizacion

Se reemitio el cert productivo con `dns-cloudflare` y se valido:

```bash
/snap/bin/certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 30 \
  --cert-name sysfarma-wildcard \
  --force-renewal \
  -d sysfarma.pe -d '*.sysfarma.pe'
```

Despues se comprobo:

```bash
/snap/bin/certbot certonly \
  --dry-run \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 30 \
  --cert-name sysfarma-wildcard \
  --force-renewal \
  -d sysfarma.pe -d '*.sysfarma.pe'
```

Resultado esperado y validado:

- `The dry run was successful.`

## Checklist de no regresion

Antes de cerrar cualquier cambio futuro en SSL o proxy de `sysfarma.pe`, verificar:

1. `apachectl configtest`
2. `echo | openssl s_client -connect 127.0.0.1:443 -servername prueba.sysfarma.pe 2>/dev/null | openssl x509 -noout -ext subjectAltName`
3. Confirmar que aparece `DNS:*.sysfarma.pe`
4. `curl -skI https://subdominio-real.sysfarma.pe/login | head -1`
5. Confirmar si el subdominio existe o no en `hostnames` antes de diagnosticar fallback al superadmin

## Decision operativa

La arquitectura correcta para `sysfarma.pe` es:

- wildcard SSL en Apache,
- wildcard DNS en Cloudflare,
- tenancy Laravel controlado por `hostnames`.

No volver al modelo SAN por tenant.
