RBAC con API Gateway y Open Policy Agent (OPA)
May 15, 2023
Con diversos modelos de control de acceso y métodos de implementación disponibles, construir un sistema de autorización para APIs de servicios backend puede seguir siendo un desafío. Sin embargo, el objetivo final es garantizar que la persona correcta tenga el acceso adecuado al recurso correspondiente. En este artículo, discutiremos cómo habilitar el modelo de autorización de control de acceso basado en roles (RBAC) para tu API utilizando la pasarela de API de código abierto Apache APISIX y Open Policy Agent (OPA).
Objetivos de aprendizaje
A lo largo del artículo, aprenderás lo siguiente:
- ¿Qué es RBAC y cómo funciona?
- ¿Qué es OPA y cómo funciona?
- ¿Cómo implementar RBAC con OPA y Apache APISIX?
- Cómo definir y registrar la política en OPA.
- Cómo crear un upstream, una ruta y habilitar el plugin de OPA.
- Cómo se analizan el rol y los permisos del usuario desde el payload del token JWT o los datos del consumidor.
¿Qué es RBAC?
Control de Acceso Basado en Roles (RBAC) y Control de Acceso Basado en Atributos (ABAC) son dos modelos de control de acceso comúnmente utilizados para gestionar permisos y controlar el acceso a recursos en sistemas informáticos. RBAC asigna permisos a los usuarios en función de su rol dentro de una organización. En RBAC, los roles se definen en función de las funciones o responsabilidades de los usuarios, y los permisos se asignan a esos roles. Luego, los usuarios se asignan a uno o más roles y heredan los permisos asociados con esos roles. En el contexto de las API, por ejemplo, un rol de desarrollador podría tener permiso para crear y actualizar recursos de API, mientras que un rol de usuario final solo podría tener permiso para leer o ejecutar recursos de API.
Básicamente, RBAC asigna permisos basados en roles de usuario, mientras que ABAC asigna permisos basados en atributos asociados con los usuarios y los recursos.
En RBAC, una política se define por la combinación del rol asignado a un usuario, las acciones que está autorizado a realizar y los recursos en los que puede realizar esas acciones.
¿Qué es OPA?
OPA (Open Policy Agent) es un motor de políticas y un conjunto de herramientas que proporcionan un enfoque unificado para la aplicación de políticas en todo un sistema distribuido. Permite definir, gestionar y aplicar políticas de manera centralizada desde un solo punto. Al definir políticas como código, OPA facilita la revisión, edición y reversión de políticas, lo que permite una gestión eficiente de las mismas.
OPA proporciona un lenguaje declarativo llamado Rego, que te permite crear y aplicar políticas en toda tu infraestructura. Cuando solicitas una decisión de política a OPA, utiliza las reglas y los datos que has proporcionado en un archivo .rego
para evaluar la consulta y producir una respuesta. El resultado de la consulta se devuelve como la decisión de la política. OPA almacena todas las políticas y los datos que necesita en su caché en memoria. Como resultado, OPA devuelve resultados rápidamente. Aquí tienes un ejemplo de un archivo Rego simple de OPA:
package example
default allow = false
allow {
input.method == "GET"
input.path =="/api/resource"
input.user.role == "admin"
}
En este ejemplo, tenemos un paquete llamado "example" que define una regla llamada "allow". La regla "allow" especifica que la solicitud está permitida si el método de entrada es "GET", la ruta solicitada es /api/resource
y el rol del usuario es "admin". Si se cumplen estas condiciones, la regla "allow" se evaluará como "true", permitiendo que la solicitud continúe.
¿Por qué usar OPA y una pasarela de API para RBAC?
La pasarela de API proporciona una ubicación centralizada para configurar y gestionar la API y los consumidores de la API. Puede utilizarse como una pasarela de autenticación centralizada al evitar que cada servicio individual implemente la lógica de autenticación dentro del propio servicio. Por otro lado, OPA añade una capa de autorización y desacopla la política del código, creando un beneficio distinto para la autorización. Con esta combinación, puedes agregar permisos para un recurso de API a un rol. Los usuarios pueden estar asociados con uno o más roles de usuario. Cada rol de usuario define un conjunto de permisos (GET, PUT, DELETE) en recursos RBAC (definidos por rutas URI). En la siguiente sección, aprendamos cómo lograr RBAC utilizando estos dos.
Cómo implementar RBAC con OPA y Apache APISIX
En Apache APISIX, puedes configurar rutas y plugins para definir el comportamiento de tu API. Puedes usar el plugin opa de APISIX para aplicar políticas RBAC al reenviar solicitudes a OPA para la toma de decisiones. Luego, OPA toma una decisión de autorización en tiempo real basada en los roles y permisos de los usuarios.
Supongamos que tenemos una API de Conferencias donde puedes recuperar/editar sesiones de eventos, temas e información de los oradores. Un orador solo puede leer sus propias sesiones y temas, mientras que el administrador puede agregar/editar más sesiones y temas. O los asistentes pueden dejar sus comentarios sobre la sesión del orador mediante una solicitud POST a /speaker/speakerId/session/feedback
, y el orador solo puede verlos solicitando el método GET de la misma URI. El siguiente diagrama ilustra todo el escenario:
- El consumidor de la API solicita una ruta en la pasarela de API con sus credenciales, como un token JWT en la cabecera de autorización.
- La pasarela de API envía los datos del consumidor con una cabecera JWT al motor OPA.
- OPA evalúa si el consumidor tiene derecho a acceder al recurso utilizando las políticas (roles y permisos) que especificamos en el archivo .rego.
- Si la decisión de OPA es permitir, la solicitud se reenvía al servicio de Conferencias upstream.
A continuación, instalamos, configuramos APISIX y definimos políticas en OPA.
Requisitos previos
- Docker se utiliza para instalar etcd y APISIX en contenedores.
- curl se utiliza para enviar solicitudes a la API de administración de APISIX. También puedes usar herramientas como Postman para interactuar con la API.
Paso 1: Instalar Apache APISIX
APISIX se puede instalar y ejecutar fácilmente con el siguiente script de inicio rápido:
curl -sL https://run.api7.ai/apisix/quickstart | sh
Paso 2: Configurar el servicio backend (upstream)
Para enrutar las solicitudes al servicio backend de la API de Conferencias, debes configurarlo agregando un servidor upstream en Apache APISIX a través de la API de administración.
curl http://127.0.0.1:9180/apisix/admin/upstreams/1 -X PUT -d '
{
"name":"Conferences API upstream",
"desc":"Registrar la API de Conferencias como el upstream",
"type":"roundrobin",
"scheme":"https",
"nodes":{
"conferenceapi.azurewebsites.net:443":1
}
}'
Paso 3: Crear un consumidor de API
A continuación, creamos un consumidor (un nuevo orador) con el nombre de usuario jack
en Apache APISIX. Configuramos el plugin jwt-auth para el consumidor con la clave y el secreto especificados. Esto permitirá que el consumidor se autentique utilizando un JSON Web Token (JWT).
curl http://127.0.0.1:9180/apisix/admin/consumers -X PUT -d '
{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "user-key",
"secret": "my-secret-key"
}
}
}'
Paso 4: Crear un endpoint público para generar un token JWT
También necesitas configurar una nueva Ruta que genere y firme el token utilizando el plugin public-api. En este escenario, la pasarela de API actúa como un servidor de proveedor de identidad para crear y verificar el token con la clave de nuestro consumidor jack. El proveedor de identidad también puede ser cualquier otro servicio de terceros como Google, Okta, Keycloak, y Ory Hydra.
curl http://127.0.0.1:9180/apisix/admin/routes/jas -X PUT -d '
{
"uri": "/apisix/plugin/jwt/sign",
"plugins": {
"public-api": {}
}
}'
Paso 5: Reclamar un nuevo token JWT para el consumidor de API
Ahora podemos obtener un nuevo token para nuestro orador Jack desde la pasarela de API utilizando el endpoint público que creamos. El siguiente comando curl genera un nuevo token con las credenciales de Jack y asigna el rol y el permiso en el payload.
curl -G --data-urlencode 'payload={"role":"speaker","permission":"read"}' http://127.0.0.1:9080/apisix/plugin/jwt/sign?key=user-key -i
Después de ejecutar el comando anterior, recibirás un token como respuesta. Guarda este token en algún lugar, ya que lo usaremos más adelante para acceder a nuestro nuevo endpoint de la pasarela de API.
Paso 6: Crear una nueva configuración de plugin
Este paso implica configurar 3 plugins de APISIX: proxy-rewrite, jwt-auth y opa.
curl "http://127.0.0.1:9180/apisix/admin/plugin_configs/1" -X PUT -d '
{
"plugins":{
"jwt-auth":{
},
"proxy-rewrite":{
"host":"conferenceapi.azurewebsites.net"
}
}
}'
- El plugin
proxy-rewrite
está configurado para redirigir las solicitudes al hostconferenceapi.azurewebsites.net
. - El plugin de autenticación OPA está configurado para usar el motor de políticas OPA que se ejecuta en http://localhost:8181/v1/data/rbacExample. Además, APISIX envía toda la información relacionada con el consumidor a OPA. Agregaremos este archivo de política
.rego
en la sección de configuración de OPA.
Paso 7: Crear una Ruta para las sesiones de Conferencias
El paso final es crear una nueva ruta para las sesiones de oradores de la API de Conferencias:
curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT -d '
{
"name":"Ruta de sesiones de oradores de la API de Conferencias",
"desc":"Crear una nueva ruta en APISIX para las sesiones de oradores de la API de Conferencias",
"methods": ["GET", "POST"],
"uris": ["/speaker/*/topics","/speaker/*/sessions"],
"upstream_id":"1",
"plugin_config_id":1
}'
El payload contiene información sobre la ruta, como su nombre, descripción, métodos, URIs, ID de upstream y ID de configuración de plugin. En este caso, la ruta está configurada para manejar solicitudes GET y POST para dos URIs diferentes, /speaker/topics
y /speaker/sessions
. El campo "upstream_id" especifica el ID del servicio upstream que manejará las solicitudes entrantes para esta ruta, mientras que el campo "plugin_config_id" especifica el ID de la configuración de plugin que se utilizará para esta ruta.
Paso 8: Probar la configuración sin OPA
Hasta ahora, hemos configurado todo lo necesario para que APISIX dirija las solicitudes entrantes a los endpoints de la API de Conferencias, permitiendo solo a los consumidores de API autorizados. Ahora, cada vez que un consumidor de API quiera acceder a un endpoint, debe proporcionar un token JWT para recuperar datos del servicio backend de Conferencias. Puedes verificar esto accediendo al endpoint y la dirección de dominio que estamos solicitando ahora es nuestra pasarela de API personalizada, no el servicio real de Conferencias:
curl -i http://127.0.0.1:9080/speaker/1/topics -H 'Authorization: {API_CONSUMER_TOKEN}'
Paso 9: Ejecutar el servicio OPA
Los otros dos pasos son ejecutar el servicio OPA usando Docker y cargar nuestra definición de política usando su API, que se puede utilizar para evaluar políticas de autorización para las solicitudes entrantes.
docker run -d --network=apisix-quickstart-net --name opa -p 8181:8181 openpolicyagent/opa:latest run -s
Este comando de Docker ejecuta un contenedor de la imagen OPA con la última versión. Crea un nuevo contenedor en la red existente de APISIX apisix-quickstart-net
con el nombre opa
y expone el puerto 8181
. Por lo tanto, APISIX puede enviar solicitudes de verificación de políticas a OPA directamente utilizando la dirección [http://opa:8181](http://opa:8181)
. Ten en cuenta que OPA y APISIX deben ejecutarse en la misma red de Docker.
Paso 10: Definir y registrar la política
El segundo paso en el lado de OPA es que necesitas definir las políticas que se utilizarán para controlar el acceso a los recursos de la API. Estas políticas deben definir los atributos requeridos para el acceso (qué usuarios tienen qué roles) y los permisos (qué roles tienen qué permisos) que están permitidos o denegados en función de esos atributos. Por ejemplo, en la siguiente configuración, le estamos diciendo a OPA que verifique en la tabla user_roles
cuál es el rol de jack
. Esta información es enviada por APISIX dentro de input.consumer.username
. Además, estamos verificando el permiso del consumidor leyendo el payload del JWT y extrayendo token.payload.permission
de allí. Los comentarios describen los pasos claramente.
curl -X PUT '127.0.0.1:8181/v1/policies/rbacExample' \
-H 'Content-Type: text/plain' \
-d 'package rbacExample
# Asignación de roles de usuario
user_roles := {
"jack": ["speaker"],
"bobur":["admin"]
}
# Asignación de permisos de roles
role_permissions := {
"speaker": [{"permission": "read"}],
"admin": [{"permission": "read"}, {"permission": "write"}]
}
# Funciones auxiliares de JWT
bearer_token := t {
t := input.request.headers.authorization
}
# Decodificar el token de autorización para obtener un rol y un permiso
token = {"payload": payload} {
[_, payload, _] := io.jwt.decode(bearer_token)
}
# Lógica que implementa RBAC
default allow = false
allow {
# Buscar la lista de roles para el usuario
roles := user_roles[input.consumer.username]
# Para cada rol en esa lista
r := roles[_]
# Buscar la lista de permisos para el rol r
permissions := role_permissions[r]
# Para cada permiso
p := permissions[_]
# Verificar si el permiso otorgado a r coincide con la solicitud del usuario
p == {"permission": token.payload.permission}
}'
Paso 11: Actualizar la configuración de plugin existente con el plugin OPA
Una vez que definimos las políticas en el servicio OPA, necesitamos actualizar la configuración de plugin existente para la ruta para usar el plugin OPA. Especificamos en el atributo policy
del plugin OPA.
curl "http://127.0.0.1:9180/apisix/admin/plugin_configs/1" -X PATCH -d '
{
"plugins":{
"opa":{
"host":"http://opa:8181",
"policy":"rbacExample",
"with_consumer":true
}
}
}'
Paso 12: Probar la configuración con OPA
Ahora podemos probar toda la configuración que hicimos con las políticas de OPA. Si intentas ejecutar el mismo comando curl para acceder al endpoint de la pasarela de API, primero verificará el token JWT como parte del proceso de autenticación y enviará los datos del consumidor y el token JWT a OPA para verificar el rol y el permiso como parte del proceso de autorización. Cualquier solicitud sin un token JWT o con roles no permitidos será denegada.
curl -i http://127.0.0.1:9080/speaker/1/topics -H 'Authorization: {API_CONSUMER_TOKEN}'
Conclusión
En este artículo, aprendimos cómo implementar RBAC con OPA y Apache APISIX. Definimos una lógica de política personalizada simple para permitir/denegar el acceso a los recursos de la API en función del rol y los permisos de nuestro consumidor de API. Además, este tutorial demostró cómo podemos extraer información relacionada con el consumidor de API en el archivo de política desde el payload del token JWT o el objeto de consumidor enviado por APISIX.
Recursos relacionados
- Política de Autorización de Apache APISIX: Protege tus APIs
- Autenticación Centralizada con Plugins de Apache APISIX
- Gestionar Consumidores de API con Apache APISIX