En el anterior artículo vimos que era mejor autenticarse con usuario y contraseña para acceder al endpoint publicado mediante https, que acceder a un endpoint completamente abierto publicado por http aunque pocos fueran los que conociesen la url.
La configuración que proponemos en esta tercera entrega de la serie, te ahorrará tener que enviar los datos del usuario y la contraseña en la comunicación con el endpoint.
Tabla de Contenidos
Emplear una llave de un solo uso
Una alternativa a emplear siempre el mismo usuario y contraseña, es la posibilidad de emplear una llave encriptada de un solo uso, que nos permita acceder a un recurso.
Para ello debemos emplear un algoritmo que, partiendo de unos datos de entrada, genere esta llave encriptada. Tanto el cliente de la conexión como el receptor deben poder validar que la llave es correcta.
A este mecanismo se le denomina autenticación basada en “Digest” o basada en resumen.
La conversación entre el cliente que hace la petición y tu endpoint comienza con la petición de acceso al recurso, a lo que el endpoint responde con un mensaje de ‘No autorizado’ y unos parámetros (realm, cnonce, qop) generados para esta petición y que el cliente debe capturar.
A continuación se genera otra nueva petición donde se envía un hash generado a partir de varias operaciones con los parámetros recibidos y otros propios, tales como username, nonce, uri, nc, siendo nc y nonce generados por el cliente de forma única para esta petición.
Aunque pueda parece más complejo, la realidad es que son pocas operaciones adicionales a otros métodos.
Con ello conseguimos evitar un grave problema, que consiste en que alguien que ha capturado el tráfico pueda realizar las mismas llamadas que un cliente autorizado pero desde su propia aplicación.
Aun así, sigue flaqueando en ciertos aspectos. Al final debemos almacenar la contraseña en el servidor donde se publique el endpoint y alguien podría obtenerla.
¿Cómo añadimos una autenticación basada en Digest a tu balanceador?
Tenemos que tener en cuenta que para que nginx permita este tipo de autenticación debemos instalar un módulo que no ha sido del todo validado, se trata de “ngx_http_auth_digest”. Las instrucciones están detalladas en este enlace (2)
Para asegurarnos que el módulo está disponible en nuestra instalación podemos ejecutar el comado “nginx -V” como se muestra a continuación.
nginx version: nginx/1.10.1
built by gcc 6.3.0 (Alpine 6.3.0)
built with OpenSSL 1.0.2r 26 Feb 2019
TLS SNI support enabled
configure arguments: –with-http_ssl_module –with-http_gzip_static_module –prefix=/etc/nginx –http-log-path=/var/log/nginx/access.log –error-log-path=/var/log/nginx/error.log –sbin-path=/usr/local/sbin/nginx –add-module=../nginx-http-auth-digest
Ya estamos en disposición de usar el módulo, para lo que modificaremos el fichero load-balancer.conf como se indica a continuación.
upstream applicationserver {
server 192.168.0.10:8080;
}
server {
listen 443 ssl;
server_name api.apiprovider.com;
ssl_certificate /etc/ssl/certs/api.apiprovider.crt;
ssl_certificate_key /etc/ssl/certs/api.apiprovider.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
auth_digest_user_file /opt/httpd/conf/passwd.digest;
auth_digest_shm_size 4m;
location /api {
proxy_pass http://applicationserver/api;
auth_digest realm;
auth_digest_timeout 60s;
auth_digest_expires 10s;
auth_digest_replays 20;
}
}
}
En esta configuración empleamos un fichero de claves que generamos con el comando “htdigest” pasándole el recurso, el usuario y su contraseña.
Adding password for realm in realm username.
New password: <password>
Re-type new password: <password>
A diferencia del fichero .htpasswd hay que agregar una entrada para cada recurso de tu API, si quieres controlar a qué recursos se tendrá acceso.
Después de recargar la configuración, podrás realizar una simple comprobación enviando una petición con el comando curl.
-X GET http://api.apiprovider.com/api/doctor/1 | jq
* Trying ::1…
* TCP_NODELAY set
* Connected to api.apiprovider.com (::1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: C=ES; ST=ANDALUCIA; L=SEVILLA; O=falc0n.es; OU=Operaciones; CN=api.apiprovider.com
* start date: Aug 17 17:59:33 2019 GMT
* expire date: Jun 6 17:59:33 2022 GMT
* issuer: C=ES; ST=ANDALUCIA; L=SEVILLA; O=falc0n.es; OU=Operaciones; CN=api.apiprovider.com
* SSL certificate verify result: self signed certificate (18), continuing anyway.
* Server auth using Digest with user ‘username’
> GET /api/doctor/1 HTTP/1.1
> Host: api.apiprovider.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Server: nginx/1.10.1
< Date: Sat, 17 Aug 2019 18:22:27 GMT
< Content-Type: text/html
< Content-Length: 195
< Connection: keep-alive
< WWW-Authenticate: Digest algorithm=»MD5″, qop=»auth», realm=»medapp», nonce=»1122e7865d5845e3″
<
* Ignoring the response-body
* Connection #0 to host api.apiprovider.com left intact
* Issue another request to this URL: ‘https://api.apiprovider.com/api/doctor/1’
* Found bundle for host api.apiprovider.com: 0x7fb341f12c20 [can pipeline]
* Re-using existing connection! (#0) with host api.apiprovider.com
* Connected to api.apiprovider.com (::1) port 443 (#0)
* Server auth using Digest with user ‘username’
> GET /api/doctor/1 HTTP/1.1
> Host: api.apiprovider.com
> Authorization: Digest username=»username», realm=»medapp», nonce=»1122e7865d5845e3″, uri=»/api/doctor/1″, cnonce=»MDVjZDZkN2E4OGU3MGRmYTNmODJkNDBkNTIzYjljMGE=», nc=00000001, qop=auth, response=»16ee2fb81e46dcb7a0d59e9d24c79fae», algorithm=»MD5″
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.10.1
< Date: Sat, 17 Aug 2019 18:22:28 GMT
< Content-Type: application/json
< Content-Length: 283
< Connection: keep-alive
< Authentication-Info: qop=»auth», rspauth=»3d87fa3f31bc8cd08a378bea672d3fcd», cnonce=»MDVjZDZkN2E4OGU3MGRmYTNmODJkNDBkNTIzYjljMGE=», nc=00000001
< Sl-Request-Valid: true
< Sl-Response-Valid: false
* Connection #0 to host api.apiprovider.com left intact
{
«category»: {
«id»: 84738527,
«name»: «Ut cupidatat et aliqua»
},
«id»: 40824116,
«name»: «doggie»,
«photoUrls»: [
«sunt aliqua voluptate ipsum»,
«eiusmod»
],
«status»: «resting»,
«tags»: [
{
«id»: 47309272,
«name»: «cillum sit»
}
]
}
Y en Nubentos, ¿cómo usamos el endpoint basado en Digest?
Es tan sencillo como seleccionar la opción correspondiente a la hora de agregarlo en la sección “Implement”.
Desplegamos “Show more options” y completamos el campo “Endpoint Auth Type” con la opción “Digest Auth” e introducimos el usuario y contraseña.
En este artículo hemos desarrollado las configuraciones básicas para securizar el acceso a tu endpoint. Existen otros mecanismos más complejos y seguros tales como OAuth2 ,OpenID y JWT, que requieren del empleo de frameworks específicos para el desarrollo de la API, y una mayor complejidad de la infraestructura necesaria.
Nubentos proporciona estos mecanismos por defecto a los consumidores de la API, por lo que no son necesarios para la interconexión con el endpoint de la API del proveedor.
0 comentarios