Consideraciones de seguridad
El módulo JSON:API está diseñado para tomar el modelo de datos definido en Drupal mediante las APIs de Entity, Field y Typed Data de Drupal, y exponerlo a través de una API que cumple con la especificación JSON:API, con el fin de facilitar la interacción con los datos (entidades) gestionados por Drupal.
Al hacerlo, respeta todas las medidas de seguridad de Drupal para esos datos:
- Se respeta el acceso a entidades.
- Se respeta el acceso a campos.
- Al modificar datos, se respetan las restricciones de validación.
- Se respeta la bandera
internal
(consulta la documentación sobre cómo puede configurarse en la definición de un tipo de entidad, campo o propiedad).
En otras palabras: JSON:API no omite ninguna de las medidas de seguridad existentes y no añade ninguna capa propia; reutiliza la base de Drupal.
Los errores en tipos de entidad, campos y tipos de datos pueden provocar vulnerabilidades de seguridad
Sin embargo, existen errores en el código que implementa los tipos de entidad, tipos de campo y tipos de datos, así como en sus controladores de acceso y restricciones de validación. Esto se debe en gran parte al legado de Drupal: originalmente, Drupal no tenía restricciones de validación, sino callbacks de validación de formularios; el cambio a una mentalidad "API-first" puede considerarse completo en el núcleo de Drupal, pero no está garantizado para los módulos contribuidos o personalizados.
Estos errores pueden dar lugar a vulnerabilidades de seguridad, y de hecho así ha ocurrido en el pasado. Tales vulnerabilidades no se limitan al módulo JSON:API; también afectan, por ejemplo, al módulo RESTful Web Services y a cualquier código PHP que interactúe con la API de entidades.
Sin embargo, dado que un usuario malicioso puede acceder más fácilmente a una API HTTP como JSON:API o RESTful Web Services que a una API PHP, se requiere especial precaución en este caso. A diferencia de otros módulos de API HTTP, JSON:API tiene una mayor superficie de API por defecto: todos los tipos de entidad no internal
están disponibles por defecto (aunque, por supuesto, respetando el acceso a entidades) para hacer que la experiencia del desarrollador sea lo más fluida posible.
Seis consideraciones de seguridad
1. La importancia de usar módulos contribuidos estables
Las vulnerabilidades de seguridad causadas por tipos de entidad, tipos de campo y tipos de datos se resuelven lo más rápido posible solo para los módulos estables publicados en Drupal.org y cubiertos por la política de avisos de seguridad. Los módulos personalizados y los módulos contribuidos no estables no están cubiertos. Si utilizas alguno de ellos, extrema las precauciones.
2. Auditoría de acceso a entidades y campos
Independientemente de si utilizas JSON:API u otro módulo de tipo API, siempre se recomienda auditar el acceso a entidades y campos en los sitios Drupal. Esto es especialmente importante si las capacidades de escritura de JSON:API están habilitadas.
3. Exponer solo lo necesario
Cuando ciertos tipos de recursos (tipos de entidad + bundles) no necesitan exponerse, tras asegurar que el acceso a ellos está denegado, puedes ir aún más lejos y deshabilitarlos. Para deshabilitar un tipo de recurso o campo, existe una API PHP que puedes implementar en un módulo personalizado, o puedes utilizar el módulo contribuido JSON:API Extras, que proporciona una interfaz para deshabilitar tipos de recursos y campos. Esto no siempre es posible, pero en casos donde el propietario del sitio también controla todos los clientes de API, puedes hacerlo para reducir al máximo la superficie expuesta de la API.
4. Modo solo lectura
Si solo necesitas poder leer datos, puedes optar por activar el modo solo lectura de JSON:API en /admin/config/services/jsonapi
. Esto mitiga riesgos derivados de errores hipotéticos y aún desconocidos en las restricciones de validación o lógica de escritura. Dado que la mayoría de los entornos modernos desacoplados de Drupal solo necesitan leer datos, el modo solo lectura está activado por defecto. (En el núcleo de Drupal y en la versión 2.4 y posteriores del módulo contribuido).
5. Seguridad por oscuridad: base path secreta
La ruta base para JSON:API es /jsonapi
por defecto. Esto se puede cambiar a algo como /hidden/b69dhj027ooae/jsonapi
, lo que ayuda a reducir la efectividad de ataques automatizados. Crea sites/example.com/services.yml
si no existe y añade lo siguiente:
parameters:
jsonapi.base_path: /hidden/b69dhj027ooae/jsonapi
6. Limitar qué bundles de entidad pueden crearse o editarse eliminando algunas rutas
Si solo necesitas crear o actualizar ciertos bundles de entidad mediante JSON:API, puedes implementar un event subscriber para eliminar todas las rutas POST y PATCH excepto las de una lista blanca en un módulo personalizado. Esto tendrá efecto después de desactivar el modo solo lectura y puede requerir reconstruir las rutas.
Agrega un servicio a tu archivo services.yml del módulo:
services:
mymodule.route_subscriber:
class: Drupal\mymodule\Routing\JsonapiLimitingRouteSubscriber
tags:
- { name: event_subscriber }
Crea el event subscriber. Este ejemplo también impide eliminar contenido vía JSON:API:
<?php
namespace Drupal\mymodule\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Clase JsonapiLimitingRouteSubscriber.
*
* Elimina todas las rutas DELETE de recursos jsonapi para proteger el contenido.
*
* Elimina rutas POST y PATCH de recursos jsonapi excepto aquellas
* que se permiten explícitamente mediante la API desacoplada.
*/
class JsonapiLimitingRouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
$mutable_types = $this->mutableResourceTypes();
foreach ($collection as $name => $route) {
$defaults = $route->getDefaults();
if (!empty($defaults['_is_jsonapi']) && !empty($defaults['resource_type'])) {
$methods = $route->getMethods();
if (in_array('DELETE', $methods)) {
// Nunca permitir eliminar datos, solo despublicar.
$collection->remove($name);
}
else {
$resource_type = $defaults['resource_type'];
if (empty($mutable_types[$resource_type])) {
if (in_array('POST', $methods) || in_array('PATCH', $methods)) {
$collection->remove($name);
}
}
}
}
}
}
/**
* Tipos de recurso editables expuestos a cambios del usuario vía API.
*
* @return array
* Lista de tipos de recurso jsonapi editables como claves.
*/
public function mutableResourceTypes(): array {
return [
'node--article' => TRUE,
'node--document' => TRUE,
'custom_entity--custom_entity' => TRUE,
];
}
}
Limitar acceso a todas las rutas JSON:API con un permiso adicional
Cuando se utiliza JSON:API para integraciones backend, clientes API limitados u otros casos no públicos, puede ser conveniente restringir el acceso a usuarios con un permiso específico. En vez de o además de lo anterior, añade el siguiente fragmento al subscriber mencionado:
// Limitar acceso a todas las rutas jsonapi con un permiso adicional.
foreach ($collection as $route) {
$defaults = $route->getDefaults();
if (!empty($defaults['_is_jsonapi'])) {
$route->setRequirement('_permission', 'FOO custom access jsonapi');
}
}
Luego define ese permiso en FOO.permissions.yml y asígnalo a los roles de usuario deseados.
Artículo extraído de Drupal Documentation.