Las API son parte vital de las aplicaciones modernas conectadas a Internet. Transportan solicitudes de aplicaciones móviles cada milisegundo, tales como pedir comida a domicilio, dar "me gusta" a esa foto, así como indicaciones a dispositivos IoT, p. ej. desbloquear la puerta del coche, iniciar el ciclo de lavado, el usuario acaba de terminar una carrera de 5 kilómetros, entre otras innumerables llamadas.
También son blanco de ataques generalizados concebidos para realizar acciones no autorizadas o exfiltrar datos como muestran, cada vez más, los datos de Gartner: "Para 2021, la superficie de ataque del 90 % de las aplicaciones web será mayor en forma de API expuestas que de la interfaz de usuario en comparación con el 40 % en 2019. "Gartner pronosticó que, para 2022, los abusos derivados de las API pasarán de ser un vector de ataque poco frecuente a ser el más frecuente, lo que provocará filtraciones de datos para las aplicaciones web empresariales"[1][2]. De los 18 millones de solicitudes por segundo que atraviesan la red de Cloudflare, el 50 % se dirige a las API, y la mayoría de estas solicitudes se bloquean por ser malintencionadas.
Para combatir estas amenazas, Cloudflare simplifica la seguridad de las API mediante el uso de una identidad sólida basada en certificados de cliente y una estricta validación basada en esquemas. A partir de hoy, estas funciones están disponibles gratuitamente en todos los planes con nuestra nueva solución "API Shield". Desde hoy, las ventajas de seguridad se amplían también a las API basadas en el servicio gRPC, que utilizan formatos binarios como protocol buffers en lugar de JSON, y que han ido ganando popularidad entre nuestra cartera de clientes.
Sigue leyendo para saber más sobre las nuevas funciones, o ve directamente al apartado "Demostración" para ver ejemplos de cómo empezar a configurar tu primera regla de API Shield.
Modelos de seguridad positiva y certificados de cliente
Un modelo de "seguridad positiva" es aquel que solo permite comportamientos e identidades conocidos, rechazando todo lo demás. Es la antítesis del modelo tradicional de "seguridad negativa" aplicado por un firewall de aplicaciones web (WAF) que permite todo excepto las solicitudes procedentes de direcciones IP, números de AS, países o solicitudes con firmas problemáticas (intentos de inyección de código SQL, etc.).
Implementar un modelo de seguridad positivo para las API es la forma más directa de bloquear los ataques de relleno de credenciales y otras herramientas de análisis automatizadas. Además, el primer paso hacia un modelo positivo es implementar una autenticación segura, como la autenticación mutua TLS, que no es vulnerable a la reutilización o el uso compartido de contraseñas.
Al igual que simplificamos la emisión de certificados de servidor en 2014 con Universal SSL, API Shield reduce el proceso de emisión de certificados de cliente con solo unos clics en el panel de control de Cloudflare. Al proporcionar una infraestructura de clave pública (PKI) privada totalmente alojada, puedes centrarte en tus aplicaciones y funciones, en lugar de tener que operar y asegurar tu propia autoridad de certificación (CA).
Aplicación de solicitudes válidas con la validación de esquemas
Una vez que los desarrolladores están seguros de que solo los clientes legítimos (con certificados SSL en mano) se conectan a sus API, el siguiente paso para implementar un modelo de seguridad positivo es asegurarse de que esos clientes realizan solicitudes válidas. Extraer un certificado de cliente de un dispositivo y reutilizarlo en otro lugar es difícil, pero no imposible, por lo que también es importante asegurarse de que se está llamando a la API según lo previsto.
Es posible que las solicitudes que contengan entradas extrañas no hayan sido previstas por el desarrollador de la API, y pueden causar problemas si la aplicación las procesa directamente, por lo que se deben descartar en el perímetro si es posible. La validación del esquema de la API funciona cotejando el contenido de las solicitudes de la API (los parámetros de consulta que vienen después de la URL y el contenido del cuerpo de la solicitud POST) con un contrato o "esquema" que contiene las reglas de lo que se espera. Si la validación falla, la llamada a la API se bloquea, protegiendo al origen de una solicitud no válida o de una carga maliciosa.
La validación de esquemas está actualmente en fase beta cerrada para cargas JSON, y la hoja de ruta incluye la compatibilidad para gRPC/protocol buffer. Si quieres unirte a la versión beta, crea una incidencia de soporte con el asunto "Versión beta de la validación de esquema de API". Una vez finalice la versión beta, tenemos previsto que la validación de esquemas forme parte de la interfaz de usuario de API Shield.
Demostración
Para demostrar cómo se pueden proteger las API que activan los dispositivos IoT y las aplicaciones móviles, hemos creado una demostración de API Shield utilizando certificados de cliente y validación de esquemas.
Las temperaturas son captadas por un dispositivo IoT, representado en la demostración por una Raspberry Pi 3 Modelo B+ con un sensor de temperatura infrarrojo externo, y luego se transmiten mediante una solicitud POST a una API que protege Cloudflare. A continuación, las temperaturas se recuperan mediante solicitudes GET y se muestran en una aplicación móvil creada en Swift para iOS.
En ambos casos, la API se creó utilizando Cloudflare Workers® y Workers KV, pero se puede sustituir por cualquier punto final accesible desde Internet.
1. Configuración de la API
Antes de configurar el dispositivo IoT y la aplicación móvil para que se comuniquen de forma segura con la API, tenemos que arrancar los puntos finales de la API. Para facilitar el ejemplo, y al mismo tiempo permitir una personalización adicional, hemos implementado la API como un Cloudflare Worker (tomando prestado código del tutorial de la lista de tareas pendientes).
En este ejemplo concreto, las temperaturas se almacenan en Workers KV utilizando la dirección IP de origen como clave, pero se podría sustituir fácilmente por un valor del certificado del cliente, por ejemplo, la huella dactilar. El código siguiente guarda una temperatura y una marca de tiempo en KV cuando se realiza una solicitud POST, y devuelve las cinco últimas temperaturas cuando se realiza una solicitud GET.
Antes de añadir la autenticación mutua TLS, probaremos a enviar una solicitud POST de lectura de una temperatura aleatoria:
const defaultData = { temperatures: [] }
const getCache = key => TEMPERATURES.get(key)
const setCache = (key, data) => TEMPERATURES.put(key, data)
async function addTemperature(request) {
// pull previously recorded temperatures for this client
const ip = request.headers.get('CF-Connecting-IP')
const cacheKey = `data-${ip}`
let data
const cache = await getCache(cacheKey)
if (!cache) {
await setCache(cacheKey, JSON.stringify(defaultData))
data = defaultData
} else {
data = JSON.parse(cache)
}
// append the recorded temperatures with the submitted reading (assuming it has both temperature and a timestamp)
try {
const body = await request.text()
const val = JSON.parse(body)
if (val.temperature && val.time) {
data.temperatures.push(val)
await setCache(cacheKey, JSON.stringify(data))
return new Response("", { status: 201 })
} else {
return new Response("Unable to parse temperature and/or timestamp from JSON POST body", { status: 400 })
}
} catch (err) {
return new Response(err, { status: 500 })
}
}
function compareTimestamps(a,b) {
return -1 * (Date.parse(a.time) - Date.parse(b.time))
}
// return the 5 most recent temperature measurements
async function getTemperatures(request) {
const ip = request.headers.get('CF-Connecting-IP')
const cacheKey = `data-${ip}`
const cache = await getCache(cacheKey)
if (!cache) {
return new Response(JSON.stringify(defaultData), { status: 200, headers: { 'content-type': 'application/json' } })
} else {
data = JSON.parse(cache)
const retval = JSON.stringify(data.temperatures.sort(compareTimestamps).splice(0,5))
return new Response(retval, { status: 200, headers: { 'content-type': 'application/json' } })
}
}
async function handleRequest(request) {
if (request.method === 'POST') {
return addTemperature(request)
} else {
return getTemperatures(request)
}
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
Vemos ahora una lectura posterior de esa temperatura, junto con las 4 anteriores que se enviaron:
$ TEMPERATURE=$(echo $((361 + RANDOM %11)) | awk '{printf("%.2f",$1/10.0)}')
$ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
$ echo -e "$TEMPERATURE\n$TIMESTAMP"
36.30
2020-09-28T02:57:49Z
$ curl -v -H "Content-Type: application/json" -d '{"temperature":'''$TEMPERATURE''', "time": "'''$TIMESTAMP'''"}' https://shield.upinatoms.com/temps 2>&1 | grep "< HTTP/2"
< HTTP/2 201
2. Emisión de certificados de cliente
$ curl -s https://shield.upinatoms.com/temps | jq .
[
{
"temperature": 36.3,
"time": "2020-09-28T02:57:49Z"
},
{
"temperature": 36.7,
"time": "2020-09-28T02:54:56Z"
},
{
"temperature": 36.2,
"time": "2020-09-28T02:33:08Z"
},
{
"temperature": 36.5,
"time": "2020-09-28T02:29:22Z"
},
{
"temperature": 36.9,
"time": "2020-09-28T02:27:19Z"
}
]
Ya con nuestra API en la mano, es hora de bloquearla para exigir un certificado de cliente válido. Antes de hacerlo, queremos generar esos certificados. Para ello, puedes ir a la pestaña SSL/TLS → Certificados de cliente del panel de control de Cloudflare y hacer clic en "Crear certificado" o puedes automatizar el proceso mediante llamadas API.
Dado que la mayoría de los desarrolladores a escala generarán sus propias claves privadas y las CSR, y solicitarán la firma a través de la API, mostraremos ese proceso a continuación. Con CFSSL, el kit de herramientas de PKI de Cloudflare, crearemos primero un certificado bootstrap para la aplicación iOS y, a continuación, crearemos un certificado para el dispositivo IoT:
Genera una clave privada y una CSR para el dispositivo IoT y la aplicación iOS
$ cat <<'EOF' | tee -a csr.json
{
"hosts": [
"ios-bootstrap.devices.upinatoms.com"
],
"CN": "ios-bootstrap.devices.upinatoms.com",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [{
"C": "US",
"L": "Austin",
"O": "Temperature Testers, Inc.",
"OU": "Tech Operations",
"ST": "Texas"
}]
}
EOF
$ cfssl genkey csr.json | cfssljson -bare certificate
2020/09/27 21:28:46 [INFO] generate received request
2020/09/27 21:28:46 [INFO] received CSR
2020/09/27 21:28:46 [INFO] generating key: rsa-2048
2020/09/27 21:28:47 [INFO] encoded CSR
$ mv certificate-key.pem ios-key.pem
$ mv certificate.csr ios.csr
// and do the same for the IoT sensor
$ sed -i.bak 's/ios-bootstrap/sensor-001/g' csr.json
$ cfssl genkey csr.json | cfssljson -bare certificate
...
$ mv certificate-key.pem sensor-key.pem
$ mv certificate.csr sensor.csr
Solicita a Cloudflare que firme las CSR con la CA privada emitida para tu zona
// we need to replace actual newlines in the CSR with ‘\n’ before POST’ing
$ CSR=$(cat ios.csr | perl -pe 's/\n/\\n/g')
$ request_body=$(< <(cat <<EOF
{
"validity_days": 3650,
"csr":"$CSR"
}
EOF
))
// save the response so we can view it and then extract the certificate
$ curl -H 'X-Auth-Email: YOUR_EMAIL' -H 'X-Auth-Key: YOUR_API_KEY' -H 'Content-Type: application/json' -d “$request_body” https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/client_certificates > response.json
$ cat response.json | jq .
{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "7bf7f70c-7600-42e1-81c4-e4c0da9aa515",
"certificate_authority": {
"id": "8f5606d9-5133-4e53-b062-a2e5da51be5e",
"name": "Cloudflare Managed CA for account 11cbe197c050c9e422aaa103cfe30ed8"
},
"certificate": "-----BEGIN CERTIFICATE-----\nMIIEkzCCA...\n-----END CERTIFICATE-----\n",
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDITCCA...\n-----END CERTIFICATE REQUEST-----\n",
"ski": "eb2a48a19802a705c0e8a39489a71bd586638fdf",
"serial_number": "133270673305904147240315902291726509220894288063",
"signature": "SHA256WithRSA",
"common_name": "ios-bootstrap.devices.upinatoms.com",
"organization": "Temperature Testers, Inc.",
"organizational_unit": "Tech Operations",
"country": "US",
"state": "Texas",
"location": "Austin",
"expires_on": "2030-09-26T02:41:00Z",
"issued_on": "2020-09-28T02:41:00Z",
"fingerprint_sha256": "84b045d498f53a59bef53358441a3957de81261211fc9b6d46b0bf5880bdaf25",
"validity_days": 3650
}
}
$ cat response.json | jq .result.certificate | perl -npe 's/\\n/\n/g; s/"//g' > ios.pem
// now ask that the second client certificate signing request be signed
$ CSR=$(cat sensor.csr | perl -pe 's/\n/\\n/g')
$ request_body=$(< <(cat <<EOF
{
"validity_days": 3650,
"csr":"$CSR"
}
EOF
))
$ curl -H 'X-Auth-Email: YOUR_EMAIL' -H 'X-Auth-Key: YOUR_API_KEY' -H 'Content-Type: application/json' -d "$request_body" https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/client_certificates | perl -npe 's/\\n/\n/g; s/"//g' > sensor.pem
3. Creación de la regla API Shield
Con los certificados en la mano, ahora podemos configurar el punto final de la API para exigir su uso. A continuación, puedes ver una demostración de cómo crear una regla de este tipo.
Los pasos incluyen especificar qué nombres de host deben solicitar certificados, por ejemplo, shield.upinatoms.com, y luego crear la regla API Shield.
4. Comunicación con el dispositivo IoT
Para preparar una comunicación segura entre el dispositivo IoT y nuestro punto final de la API, tenemos que insertar el certificado en el dispositivo, y luego apuntar nuestra aplicación a él para que pueda ser utilizado al hacer la solicitud POST al punto final de la API.
Copiamos de forma segura la clave privada y el certificado en /etc/ssl/private/sensor-key.pem y /etc/ssl/certs/sensor.pem, y luego modificamos nuestro script de ejemplo para que apuntara a estos archivos:
Cuando el script intenta conectarse a https://shield.upinatoms.com/temps, Cloudflare solicita que se envíe un ClientCertificate, y nuestro script envía el contenido de sensor.pem antes de demostrar que está en posesión de sensor-key.pem, como se requiere para completar el protocolo de enlace SSL/TLS.
import requests
import json
from datetime import datetime
def readSensor():
# Takes a reading from a temperature sensor and store it to temp_measurement
dateTimeObj = datetime.now()
timestampStr = dateTimeObj.strftime(‘%Y-%m-%dT%H:%M:%SZ’)
measurement = {'temperature':str(36.5),'time':timestampStr}
return measurement
def main():
print("Cloudflare API Shield [IoT device demonstration]")
temperature = readSensor()
payload = json.dumps(temperature)
url = 'https://shield.upinatoms.com/temps'
json_headers = {'Content-Type': 'application/json'}
cert_file = ('/etc/ssl/certs/sensor.pem', '/etc/ssl/private/sensor-key.pem')
r = requests.post(url, headers = json_headers, data = payload, cert = cert_file)
print("Request body: ", r.request.body)
print("Response status code: %d" % r.status_code)
Si no enviamos el certificado del cliente o intentamos incluir campos extraños en la solicitud de la API, fallará la validación del esquema (configuración no mostrada) y se rechazará la solicitud:
Si por el contrario se presenta un certificado válido y la carga sigue el esquema cargado previamente, nuestro script enviará solicitudes POST de la última lectura de temperatura a la API.
Cloudflare API Shield [IoT device demonstration]
Request body: {"temperature": "36.5", "time": "2020-09-28T15:52:19Z"}
Response status code: 403
5. Comunicación con la aplicación móvil (iOS)
Cloudflare API Shield [IoT device demonstration]
Request body: {"temperature": "36.5", "time": "2020-09-28T15:56:45Z"}
Response status code: 201
Ahora que las solicitudes de temperatura se han enviado a nuestro punto final de la API, es hora de leerlas de forma segura desde nuestra aplicación móvil utilizando uno de los certificados de cliente.
En aras de la brevedad, vamos a insertar una clave y un certificado "bootstrap" como archivo PKCS#12 dentro del paquete de la aplicación. En una implementación real, este certificado "bootstrap" solo se debe utilizar junto con las credenciales de los usuarios para autenticarse en un punto final de la API que pueda devolver un certificado de usuario único. Los usuarios corporativos querrán utilizar una solución MDM para distribuir certificados y disponer de opciones adicionales de control y persistencia.
Empaqueta el certificado y la clave privada
Antes de añadir el certificado bootstrap y la clave privada, tenemos que combinarlos en un archivo binario PKCS#12. Este archivo binario se añadirá después al paquete de nuestra aplicación iOS.
Añade el paquete de certificados a tu aplicación iOS
$ openssl pkcs12 -export -out bootstrap-cert.pfx -inkey ios-key.pem -in ios.pem
Enter Export Password:
Verifying - Enter Export Password:
En XCode, haz clic en Archivo → Añadir archivos a "[Nombre del proyecto]" y selecciona tu archivo .pfx. Asegúrate de marcar "Añadir al objetivo" antes de confirmar.
Modifica tu código URLSession para utilizar el certificado de cliente
Este artículo ofrece un tutorial útil sobre el uso de una clase PKCS#11 y URLSessionDelegate para modificar tu aplicación y completar la autenticación mutua TLS cuando te conectes a una API que lo requiera.
Perspectivas
En los próximos meses, tenemos previsto incorporar a API Shield una serie de funciones adicionales diseñadas para proteger el tráfico de la API. Ofreceremos a los clientes que quieran utilizar su propia PKI la posibilidad de importar sus propias CA, una función que ya está disponible en Cloudflare Access.
Conforme recibamos comentarios sobre nuestra versión beta de validación de esquemas, intentaremos que esta función esté disponible para todos los clientes. Si estás probando la versión beta y quieres compartir tu opinión, nos encantaría saber de ti.
Además de los certificados y la validación de esquemas, nos complace incorporar funciones adicionales de seguridad de las API, así como análisis detallados para ayudarte a comprender mejor tus API. Si hay alguna función que te gustaría ver, ¡dínoslo en los comentarios!
1: "Para 2021, la superficie de ataque del 90 % de las aplicaciones web será mayor en forma de API expuestas que de la interfaz de usuario en comparación con el 40 % en 2019". Fuente: Gartner, "Gartner's API Strategy Maturity Model", Saniye Alaybeyi, Mark O'Neill, 21 de octubre de 2019. (Se requiere suscripción a Gartner)
2: "Gartner pronosticó que, para 2022, los abusos derivados de las API pasarán de ser un vector de ataque poco frecuente a ser el más frecuente, lo que provocará filtraciones de datos para las aplicaciones web empresariales". Fuente: Gartner, "Cool Vendors in API Strategy", Shameen Pillai, Paolo Malinverno, Mark O'Neill, Jeremy D'Hoinne, 18 de mayo de 2020. (Se requiere suscripción a Gartner)