El Problema
Estoy haciendo una miserable aplicacioncita con Polymer del lado del cliente y Node Express como API, siendo servidas de distintas URLs, ambas con TLS.
Al intentar agregar un Custom HTTP Header en lugar de ver un:
api_1 | { connection: 'upgrade',
api_1 | host: 'api.techu.example',
api_1 | 'user-agent': 'Mozilla/5.0 ...Firefox/57.0',
api_1 | accept: 'text/html,application....9,*/*;q=0.8',
api_1 | 'accept-language': 'en-US,en;q=0.5',
api_1 | 'accept-encoding': 'gzip, deflate, br',
api_1 | 'access-control-request-method': 'GET',
api_1 | 'access-control-request-headers': 'content-type',
api_1 | 'X-Practitioner-Auth': '2',
api_1 | origin: 'https://www.techu.example' }
veo
api_1 | { connection: 'upgrade',
api_1 | host: 'api.techu.example',
api_1 | 'user-agent': 'Mozilla/5.0 ...Firefox/57.0',
api_1 | accept: 'text/html,application....*;q=0.8',
api_1 | 'accept-language': 'en-US,en;q=0.5',
api_1 | 'accept-encoding': 'gzip, deflate, br',
api_1 | 'access-control-request-method': 'GET',
api_1 | 'access-control-request-headers': 'content-type,x-practitioner-auth'
api_1 | origin: 'https://www.techu.example' }
y un
NetworkError: A network error occurred
en la consola del browser.
Como no entiendo en absoluto por qué esta ocurriendo esto, quiero descartar capas intermedias, quiero ver el tráfico de red, pero está con TLS.
No me interesa tanto mostrar la solución al problema que es CORS sino el camino seguido, centrándome en la dificultad de que haya TLS de por medio.
Alcance y consideraciones
Tengo la clave privada del servidor pues estoy en un ambiente de test y yo generé las CAs.
Tuve suerte, la ciphersuite que acordaron el servidor y el cliente es descifrable por wireshark. Según leí por ahí, hay ciertas combinaciones que lo son y otras que no. Hay maneras de forzarla.
Linux Mint 19.x.
Para variar, no sé casi nada de Polymer, Node, CORS, pero se parece a cosas que he aprendido y olvidado varias veces.
Instalar wireshark
sudo apt install wireshark-qt
Cuando pregunta si permitir a usuarios comunes capturar en modo promiscuo, decile que si.
Luego hay que agregar el usuario al grupo y reiniciar la sesión.
sudo addgroup practitioner wireshark
Configurar el browser
Pedile al browser que deje disponibles las claves de sesión en un archivo:
SSLKEYLOGFILE=/home/practitioner/sslkeylog.log firefox
Para un launcher es parecido:
|
Si lo estás editando |
|
Si lo estás creando |
Una vez arrancado el browser, hay que identificar en que interfaz capturar. Podés usar ANY, pero estando con Docker mejor ser más especìfico. Sospecho que localhost es el lugar, pero comprobémoslo generando tráfico:
|
Tráfico en localhost y algunas interfaces de Docker |
Localhost ok.
Luego hay que pedirle a wireshark que mire el archivo del browser:
|
(Pre)-Master-Secret log filename |
y las private del sitio o sitios, yo tengo API y www, claro, si este problema se trata de algo de cross domain...
|
Private Keys |
Mejor vaciar la cache, cerrar y volver a abrir el browser, para que no esté lleno de 304 y que agarre toda la sesión TLS.
Si tuviste éxito, vas a ver una solapita abajo que dice "Decrypted SSL"
No están de más algunos filtros como:
tcp.port == 443 && ssl.record.length > 66
La primera parte es para sacarse de encima lo que no es TLS, pero sólo si tenés la certeza que tu tráfico es sólo TLS.
Para tener esa certeza tendrías que activar en el servidor
HSTS
La segunda es para eliminar los keep alives y otra información administrativa del canal. No hallé o al menos no busqué mucho como filtrar dentro del tráfico TLS, por ejemplo si quisieras sólo los GET,
frame matches xxx no anda. No digo que no haya.
Con "Follow HTTP Stream" se pone mucho más amigable
Me apoyé en varias fuentes, fundamentalmente
https://packetpushers.net/using-wireshark-to-decode-ssltls-packets/, donde hay más detalles relativos a las ciphersuites soportadas y como ajustarlas.
A diagnosticar
El encabezado sale "mal" desde el browser, no es problema de la API.
¿Qué estoy haciendo en el browser? Hago lo natural:
request.setRequestHeader('X-Practitioner-Auth', 'tok');
¿Qué hay de malo con eso?
Leyendo
https://www.html5rocks.com/en/tutorials/cors/ inferí que el problema probablemente viene por el preflight fallido de CORS, que es ese OPTIONS sin GET posterior.
O sea que lo que puse en amarillo en realidad está ok, no me había dado cuenta que era en OPTIONS.... mejor no voy a decir nada por que no sé bien, aunque lo haya arreglado.
Supuse que la falla era tener en el middleware:
app.use(function(req,res,next) {
res.setHeader('Access-Control-Allow-Origin','https://www.techu.example');
res.setHeader('Access-Control-Allow-Headers','Content-Type');
next();
});
en lugar de
app.use(function(req,res,next) {
res.setHeader('Access-Control-Allow-Origin','https://www.techu.example');
res.setHeader('Access-Control-Allow-Headers','Content-Type','X-Practitioner-Auth');
next();
});
Pero no, no anda ni a palos. Despues de muchas vueltas llegué a que hay que usar CORS y listo:
npm install cors
y en la API
const cors = require('cors');
var corsOptions = {
origin: ['https://www.techu.example'],
allowedHeaders: ['Content-Type', 'Autorization']
}
app.use(cors(corsOptions));
Un poco de CORS:
En resumen, en el comienzo todo era caos y promiscuidad, luego hubo SOP (Same Origin Policy) por motivos de seguridad pero eso complicaba la operativa así que hoy hay CORS (Cross Origin Resource Sharing), que es un mecanismo para que un servidor le diga a un navegador que puede ser llamado desde código provisto por otro sitio.
Que quede claro que CORS no afecta a una herramienta como Postman, en los navegadores incluso se puede deshabilitar. Dicen por ahí que con
--disable-web-security en chrome, no way firefox.
Me parece mejor implementarlo bien.
Mucho más largo, algún día...
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS