Este artículo ha sido actualizado con respecto al original en inglés (más antiguo). Los ejemplos que se presentan aquí han sido validados con la última versión de IAM, actualmente la 3.2.1.0-4, y la versión más reciente de InterSystems IRIS, la 2023.2, que es la primera en que ya no se instala por defecto un servidor web.
Este artículo contiene los materiales, ejemplos y ejercicios necesarios para aprender los conceptos básicos de IAM.
Todos los recursos están disponibles en este git: https://github.com/grongierisc/iam-training.
Las soluciones están en esta rama.
En el artículo se tratarán los siguientes temas:
- 1. Introducción
- 2. Instalación
- 2.1. ¿Qué se necesita instalar?
- 2.2. Cómo funciona IAM con IRIS
- 2.3. Configuración
- 2.4. Instalación de IAM
- 2.4.1. Imagen de IRIS
- 2.4.2. Imagen de IAM
- 2.4.3. Actualización del archivo docker
- 2.4.4. Actualización del archivo docker-compose
- 2.4.5. Opción: Añadir IRIS_PASSWORD como un archivo .env
- 2.4.6. Prueba
- 3. Primer servicio/ruta
- 4. Avanza más con plugins
- 5. Añadir nuestra propia autenticación
- 6. Ejercicio, Rate-Limiting
- 7. Portal para desarrolladores
- 8. Portal para desarrolladores, parte dos, Autenticación
- 8.1. Habilitar la autenticación básica
- 8.2. Limitar el acceso
- 8.2.1. Crear un rol
- 8.2.2. Añadir un rol a la especificación
- 8.2.3. Prueba
- 8.3. Añadir Oauth2 para el desarrollador
- 8.3.1. Primero: quitar la autenticación básica
- 8.3.2. Segundo: añadir el plugin Oauth2.0
- 8.3.3. Enlazar el servicio y la documentación
- 9. Portal de Administración seguro
- 10. Plugins Nota de Traducción: apartado obsoleto no funcional con última versión de Kong
- 11. CI/CD
1. Introducción
1.1. ¿Qué es IAM?
IAM son las siglas de InterSystems API Manager, basado en la Kong Enterpise Edition.
Esto significa que concede acceso, además de a la edición open source de Kong, a:
- Portal del administrador
- Portal para desarrolladores
- Plugins avanzados
- Oauth2
- Caching
- ...
1.2. ¿Qué es una Gestión de APIs?
La gestión de APIs es el proceso de creación y publicación de Interfaces de Programación de Aplicaciones (API) web, y de hacer cumplir sus políticas de uso, controlar el acceso, apoyar a la comunidad de suscriptores, recopilar y analizar estadísticas de uso e informar sobre el rendimiento. Los componentes de la gestión de APIs proporcionan mecanismos y herramientas para apoyar a la comunidad de desarrolladores y suscriptores.
1.3. Portal del IAM
Kong e IAM están diseñados como API first, lo que significa que todo lo que se hace en Kong o IAM puede hacerse por medio de llamadas a REST o al Portal de administración.
En este artículo todos los ejemplos y ejercicios se presentarán de dos formas:
Portal IAM | API REST |
---|---|
![]() |
![]() |
1.4.Flujo de este artículo
El objetivo de este artículo es utilizar IAM como proxy de una API REST de IRIS.
La definición de esta API REST se puede encontrar aquí :
http://localhost:52773/swagger-ui/index.html#/
o aquí:
https://github.com/grongierisc/iam-training/blob/training/misc/spec.yml
Empezar este artículo desde la rama principal.
Al final del artículo, se debería tener el mismo resultado que con la rama de formación.
2. Instalación
2.1. ¿Qué se necesita instalar?
- Git
- Docker (si se utiliza Windows, hay que asegurarse de configurar la instalación de Docker para que utilice los "contenedores de Linux").
- Docker Compose
- Visual Studio Code + la extensión de InterSystems ObjectScript para VSCode
- El archivo de licencia habilitado para InterSystems IRIS IAM.
- La imagen Docker del IAM
2.2. Cómo funciona IAM con IRIS
Al iniciar Kong/IAM, el contenedor verifica la licencia de Kong/IAM con una llamada CURL.
El endpoint de esta llamada es una API REST en el contenedor de IRIS.
Para vuestra información: la licencia de Kong está incorporada en la de IRIS.
2.3. Configuración
Clonar este repositorio con git clone.
git clone https://github.com/grongierisc/iam-training
Ejecutar la API REST inicial:
docker-compose up
Probarla:
http://localhost:52773/swagger-ui/index.html#/
Inicio de sesión/Contraseña: SuperUser/SYS
2.4. Instalación de IAM
2.4.1. Imagen de IRIS
Primero hay que cambiar de la Edición Community a una versión con licencia.
Para ello, hay que configurar el acceso al Registro de Contenedores de InterSystems para descargar las imágenes con acceso limitado de IRIS.
Echad un vistazo a Lanzamiento del Registro de Contenedores de InterSystems en la Comunidad de Desarrolladores.
- Iniciar sesión en https://containers.intersystems.com/ usando las credenciales del WRC para obtener un token.
- Configurar un inicio de sesión de docker en el equipo:
docker login -u="user" -p="token" containers.intersystems.com
- Obtener la imagen de InterSystems IRIS:
docker pull containers.intersystems.com/intersystems/irishealth:latest-cd
2.4.2. Imagen de IAM
En la página de distribución de software del Centro de Soporte Internacional (WRC), ir a:
- Components
Download archivo IAM-3.2.1.0-4.tar.gz (versión más reciente a día de hoy), descomprimir y extraer del tar y después cargar la imagen:
docker load -i iam_image.tar
2.4.3. Actualización del archivo Docker
Cambiar la Edición Commmunity de IRIS por una versión con licencia.
- containers.intersystems.com/intersystems/irishealth:latest-cd
- añadir iris.key en el directorio key
Editar el dockerfile para añadir esta parte encima de él
ARG IMAGE=containers.intersystems.com/intersystems/irishealth:latest-cd
# Frist stage
FROM $IMAGE as iris-iam
COPY key/iris.key /usr/irissys/mgr/iris.key
COPY iris-iam.script /tmp/iris-iam.script
RUN iris start IRIS \
&& iris session IRIS < /tmp/iris-iam.script \
&& iris stop IRIS quietly
# Second stage
FROM iris-iam
Esta parte creará un dockerfile multi-stage.
- la primera etapa consiste en habilitar a IRIS para que proporcione la licencia de IAM.
- la segunda etapa consiste en generar la API REST
Crear un nuevo archivo iris-iam.script para generar una nueva imagen de IRIS que habilite el endpoint y el usuario de IAM.
zn "%SYS"
write "Create web application ...",!
set webName = "/api/iam"
set webProperties("Enabled") = 1
set status = ##class(Security.Applications).Modify(webName, .webProperties)
write:'status $system.Status.DisplayError(status)
write "Web application "_webName_" was updated!",!
set userProperties("Enabled") = 1
set userName = "IAM"
Do ##class(Security.Users).Modify(userName,.userProperties)
write "User "_userName_" was updated!",!
halt
2.4.4. Actualización del archivo docker-compose
Actualizar el archivo docker-compose para incluir:
* iris
* argumento para obtener la password durante la reconstrucción de la imagen
* db
* base de datos de Postgres para IAM
* iam-migration
* inicia la base de datos
* iam
* instancia actual de IAM
* un volumen para datos persistentes
Añadir esta parte a la sección de build: del servicio iris: de nuestro docker-compose.yml:
args:
- IRIS_PASSWORD=${IRIS_PASSWORD}
Hemos de incorporar también el servicio de webgateway ya que, al contrario que las Community Edition, el resto de versiones de IRIS no instalan un servidor web. Así, añadiremos tras el bloque de iris:, lo siguiente:
# web gateway container
webgateway:
image: ${ISC_WG_IMAGE}
init: true
container_name: webgateway
hostname: webgateway
ports:
- "8882:80"
- "8883:443"
environment:
- ISC_DATA_DIRECTORY=/webgateway-shared/durable
- ISC_CSP_CONF_FILE=/webgateway-shared/CSP.conf
- ISC_CSP_INI_FILE=/webgateway-shared/CSP.ini
volumes:
- type: bind
source: ./webgateway
target: /webgateway-shared
IMPORTANTE: Fijémonos en que esto significa que todas las peticiones a las APIs REst de IRIS deberán hacerse ahora a través del host: webgateway, y puerto 80 (si son dentro de la red virtual del docker-compose) o del puerto 8882 si vienen del exterior.
Y por último añadir esta parte al final del archivo** *docker-compose.yml** :
iam-migrations:
image: ${ISC_IAM_IMAGE}
command: kong migrations bootstrap up
depends_on:
- db
environment:
KONG_DATABASE: postgres
KONG_PG_DATABASE: ${KONG_PG_DATABASE:-iam}
KONG_PG_HOST: db
KONG_PG_PASSWORD: ${KONG_PG_PASSWORD:-iam}
KONG_PG_USER: ${KONG_PG_USER:-iam}
KONG_CASSANDRA_CONTACT_POINTS: db
KONG_PLUGINS: bundled,jwt-crafter
ISC_IRIS_URL: IAM:${IRIS_PASSWORD}@webgateway:80/api/iam/license
restart: on-failure
links:
- db:db
iam:
image: ${ISC_IAM_IMAGE}
depends_on:
- db
environment:
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_ADMIN_LISTEN: '0.0.0.0:8001'
KONG_ANONYMOUS_REPORTS: 'off'
KONG_CASSANDRA_CONTACT_POINTS: db
KONG_DATABASE: postgres
KONG_PG_DATABASE: ${KONG_PG_DATABASE:-iam}
KONG_PG_HOST: db
KONG_PG_PASSWORD: ${KONG_PG_PASSWORD:-iam}
KONG_PG_USER: ${KONG_PG_USER:-iam}
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_PORTAL: 'on'
KONG_PORTAL_GUI_PROTOCOL: http
KONG_PORTAL_GUI_HOST: '127.0.0.1:8003'
KONG_ADMIN_GUI_URL: http://localhost:8002
KONG_PLUGINS: bundled
ISC_IRIS_URL: IAM:${IRIS_PASSWORD}@webgateway:80/api/iam/license
volumes:
- ./iam:/iam
links:
- db:db
ports:
- target: 8000
published: 8000
protocol: tcp
- target: 8001
published: 8001
protocol: tcp
- target: 8002
published: 8002
protocol: tcp
- target: 8003
published: 8003
protocol: tcp
- target: 8004
published: 8004
protocol: tcp
- target: 8443
published: 8443
protocol: tcp
- target: 8444
published: 8444
protocol: tcp
- target: 8445
published: 8445
protocol: tcp
restart: on-failure
db:
image: postgres:14.5
environment:
POSTGRES_DB: ${KONG_PG_DATABASE:-iam}
POSTGRES_PASSWORD: ${KONG_PG_PASSWORD:-iam}
POSTGRES_USER: ${KONG_PG_USER:-iam}
volumes:
- 'pgdata14:/var/lib/postgresql/data'
healthcheck:
test: ["CMD", "pg_isready", "-U", "${KONG_PG_USER:-iam}"]
interval: 30s
timeout: 30s
retries: 3
restart: on-failure
stdin_open: true
tty: true
volumes:
pgdata14:
Por cierto, esta es la definición de los puertos de Kong:
Puerto | Protocolo | Descripción |
---|---|---|
:8000 | HTTP | Toma el tráfico HTTP recibido de los Consumidores y lo reenvía a los Servicios upstream. |
:8443 | HTTPS | Toma el tráfico HTTPS recibido de los Consumidores y lo reenvía a los Servicios upstream. |
:8001 | HTTP | Admin API. Escucha las llamadas desde la línea de comendo sobre HTTP. |
:8444 | HTTPS | Admin API. Escucha las llamadas desde la línea de comendo sobre HTTPS. |
:8002 | HTTP | Kong Manager (GUI). Escucha tráfico HTTP. |
:8445 | HTTPS | Kong Manager (GUI). Escucha tráfico HTTPS. |
:8003 | HTTP | Portal para desarrolladores. Escucha tráfico HTTP, suponiendo que el Portal para desarrolladores está habilitado. |
:8446 | HTTPS | Portal para desarrolladores. Escucha tráfico HTTPS, suponiendo que el Portal para desarrolladores está habilitado. |
:8004 | HTTP | Portal para desarrolladores/tráfico de archivos por medio de HTTP, suponiendo que el Portal para desarrolladores esté habilitado. |
:8447 | HTTPS | Portal para desarrolladores/tráfico de archivos por medio de HTTPS, suponiendo que el Portal para desarrolladores esté habilitado. |
2.4.5. Añadir variables de entorno en un archivo .env
Como vemos, por facilidad de uso y por seguridad, no incluimos el nombre/tag:version de la imagen de IAM que queremos utilizar, ni la del webgateway ni la password por defecto que queremos para nuestro usuario administrador en IRIS. Esta información la pasaremos como variables de entorno en el momento de la reconstrucción de la imagen de IRIS y del arranque de los contenedores.
Para ello simplemente hemos de crear un fichero llamado: .env, en el directorio donde se encuentre nuestro docker-compose.yml, que deberá contener lo siguiente:
ISC_IAM_IMAGE=intersystems/iam:3.2.1.0-4
ISC_WG_IMAGE=containers.intersystems.com/intersystems/webgateway:latest-cd
IRIS_PASSWORD=SYS
2.4.6. Prueba
docker-compose -f "docker-compose.yml" up -d --build
3. Primer Servicio/Ruta
¿Recordáis cómo funciona Kong/IAM?
En este caso, vamos a crear:
- un servicio
- para nuestra API CRUD
- una ruta
- para acceder a este servicio
3.1. Cómo crear un servicio
Recordad que para acceder al portal del IAM debéis acceder por el puerto 8002, con:
http://localhost:8002/overview
ATENCIÓN: Hay un error en la imagen, el host debería ser el webgateway, por donde han de entrar todas las peticiones web a IRIS, y el puerto el 80, al estar el IAM y el webgateway en la misma red interna de docker
Portal IAM | API REST |
---|---|
|
|
Lo que vemos aquí es que para crear un servicio simplemente necesitamos su URL y que podemos hacerlo tanto utilizando el portal como vía llamadas rest directamete a la API REst del IAM.
3.2. Cómo crear una ruta
Portal IAM | API REST |
---|---|
|
|
Lo que vemos aquí es que para crear un servicio necesitamos basicamente el nombre del servicio.
3.3. Prueba
API original | API vía IAM |
---|---|
|
|
Qué vemos aquí:
- Nada nuevo en las opciones sin IAM.
- En el lado de Kong:
- Cambiamos el puerto
- El path viene a ser la ruta
- Aún necesitamos realizar la autenticación
4. Avanza más con plugins
Para continuar, intentaremos que el IAM se auto-autentique en el endpoint de IRIS.
Para ello, utilizaremos el plugin request-transformer.
4.1. Añadir un plugin al servicio
Portal IAM | API REST |
---|---|
|
|
4.2. Prueba
API original | API via IAM |
---|---|
|
|
Qué vemos aquí:
- El error 401 en la API original
- Obtuvimos los datos sin realizar la autenticación
5.Añadir nuestra propia autenticación
Lo que queremos lograr aquí es añadir nuestra propia autenticación sin ninguna interrupción de la API original.
5.1. Añadir consumidores
Portal IAM | API REST |
---|---|
|
|
|
|
5.2. Añadir el plugin de autenticación básica
Al mismo tiempo que añadimos el plugin de autenticación a la ruta crud-route, indicamos que se asocie cualquier petición no autenticada con el usuario anonymous que acabamos de crear (utilizamos el uuid que nos ha generado al crearlo en el paso anterior).
Portal IAM | API REST |
---|---|
|
|
Donde:
- config.anonymous = Identificador único universal (UUID) del consumidor anonymous
(Atención: sustitúyelo por el uuid generado por tu instalación para ese usuario)
5.3. Añadir el plugin ACL
ACL es un plugin para control de tráfico, que nos va a permitir controlar qué usuarios pueden acceder a los servicios.
Portal IAM | API REST |
---|---|
|
|
Como véis podemos decidir habilitar el plugin globalmente (para todos los servicios, rutas y consumidores) o con un alcance más limitado (en el ejemplo para la ruta: crud-route; con la que estamos trabajando).
5.4. Configurar el USER con ACL y credenciales
Cada plugin requiere su configuración. En el caso de ACL necesitamos que los usuarios estén asociados a grupos sobre los que luego podamos aplicar políticas de acceso. Así que vamos a asociar al usuario: user; a un grupo, también llamado: user, que está configurado en el plugin como que tiene permiso de acceso. Para el plugin de autenticación básica lo que hacemos es asignar una password al usuario: user .
Si utilizamos el portal, debemos ir a Consumers y pinchar en la opción Credentials. Allí nos aparecerán los plugins habilitados para el consumidor y que necesiten alguna configuración relacionada con el acceso.
Portal IAM | API REST |
---|---|
|
|
5.5. Prueba
API original:
# Legacy
curl –i --location --request GET 'http://localhost:8882/crud/persons/all' \
--header 'Authorization:Basic dXNlcjp1c2Vy'
API de IAM:
# IAM
curl –i --location --request GET 'http://localhost:8000/persons/all' \
--header 'Authorization:Basic dXNlcjp1c2Vy'
donde la cadena dXNlcjp1c2Vy es user:user codificada en Base64.
6. Ejercicio. Limitación del número de peticiones por unidad de tiempo (rate-limiting)
- Habilitar un usuario no autenticado
- Limitar la tasa a 2 llamadas por minuto para los usuarios no autenticados
6.1. Solución
- Habilitar a un usuario no autenticado. Añadimos al usuario anonymous como parte del grupo: user del plugin ACL. Recordemos que este grupo era el que tiene permitido el acceso a la ruta crud-route (config.alow=user)
Portal IAM | API REST |
---|---|
|
|
- Limitar la tasa a 2 llamadas por minuto para el usuario anonymous (recordemos que el plugin de autenticación básica hará que todo usuario no autenticado se identifique como anonymous).
Portal IAM | API REST |
---|---|
|
|
7. Portal para desarrolladores
7.1. Descripción breve
El Portal para desarrolladores de Kong proporciona:
- una sola fuente confiable para todos los desarrolladores
- gestion intuitiva de contenidos para la documentación
- incorporación simplificada de desarrolladores
- control de acceso basado en roles (RBAC)
7.2. Activación
Portal IAM | API REST |
---|---|
|
|
7.3. Añadir la primera especificación
Portal IAM | API REST |
---|---|
|
|
7.4. Prueba
http://localhost:8003/default/documentation/iam-training
¿Qué ocurre?
¿Cómo solucionarlo?
La especificación sólo tiene configurado un servidor para acceso seguro (https, sobre puerto 8443), que nosotros no hemos configurado. La forma de rodear este problema es simplemente añadiendo otro servidor más a nuestra especificación. Busquemos en el fichero la sección servers: y añadamos la línea:
- url: http://localhost:8000/
7.5. Ejercicio
- Añadir el plugin CORS en la ruta
7.5.1. Solución
Portal IAM | API REST |
---|---|
|
|
8. Portal para desarrolladores, parte dos, Autenticación
8.1. Habilitar la autenticación básica
Portal IAM | Configuración de la sesión (JSON) |
---|---|
|
|
Ahora la autenticación está habilitada en el Portal para desarrolladores.
8.2. Limitar el acceso
Por defecto, el usuario no autenticado tiene acceso a todo.
Podemos crear un rol para limitar algunos accesos.
Por ejemplo, vamos a restringir el acceso a nuestra documentación de la API CRUD.
8.2.1. Crear un rol
Desde el Portal IAM:
o vía API REst:
# Enable role
curl -i -X POST http://localhost:8001/default/developers/roles \
--data "name=dev"
8.2.2. Añadir un rol a la especificación
Desde el Portal IAM:
o vía API Rest:
# Enable role
curl 'http://localhost:8001/default/files/specs/iam-training.yml' -X PATCH -H 'Accept: application/json, text/plain, */*' --compressed -H 'Content-Type: application/json;charset=utf-8' -H 'Origin: http://localhost:8002' -H 'Referer: http://localhost:8002/default/portal/permissions/' --data-raw $'{"contents":"x-headmatter:\\n readable_by:\\n - dev\\nswagger: \'2.0\'\\ninfo:\\n title: InterSystems IRIS REST CRUD demo\\n description: Demo of a simple rest API on IRIS\\n version: \'0.1\'\\n contact:\\n email: apiteam@swagger.io\\n license:\\n name: Apache 2.0\\n url: \'http://www.apache.org/licenses/LICENSE-2.0.html\'\\nhost: \'localhost:8000\'\\nbasePath: /\\nschemes:\\n - http\\nsecurityDefinitions:\\n basicAuth:\\n type: basic\\nsecurity:\\n - basicAuth: []\\npaths:\\n /:\\n get:\\n description: \' PersonsREST general information \'\\n summary: \' Server Info \'\\n operationId: GetInfo\\n x-ISC_CORS: true\\n x-ISC_ServiceMethod: GetInfo\\n responses:\\n \'200\':\\n description: (Expected Result)\\n schema:\\n type: object\\n properties:\\n version:\\n type: string\\n default:\\n description: (Unexpected Error)\\n /persons/all:\\n get:\\n description: \' Retreive all the records of Sample.Person \'\\n summary: \' Get all records of Person class \'\\n operationId: GetAllPersons\\n x-ISC_ServiceMethod: GetAllPersons\\n responses:\\n \'200\':\\n description: (Expected Result)\\n schema:\\n type: array\\n items:\\n $ref: \'#/definitions/Person\'\\n default:\\n description: (Unexpected Error)\\n \'/persons/{id}\':\\n get:\\n description: \' Return one record fo Sample.Person \'\\n summary: \' GET method to return JSON for a given person id\'\\n operationId: GetPerson\\n x-ISC_ServiceMethod: GetPerson\\n parameters:\\n - name: id\\n in: path\\n required: true\\n type: string\\n responses:\\n \'200\':\\n description: (Expected Result)\\n schema:\\n $ref: \'#/definitions/Person\'\\n default:\\n description: (Unexpected Error)\\n put:\\n description: \' Update a record in Sample.Person with id \'\\n summary: \' Update a person with id\'\\n operationId: UpdatePerson\\n x-ISC_ServiceMethod: UpdatePerson\\n parameters:\\n - name: id\\n in: path\\n required: true\\n type: string\\n - name: payloadBody\\n in: body\\n description: Request body contents\\n required: false\\n schema:\\n type: string\\n responses:\\n \'200\':\\n description: (Expected Result)\\n default:\\n description: (Unexpected Error)\\n delete:\\n description: \' Delete a record with id in Sample.Person \'\\n summary: \' Delete a person with id\'\\n operationId: DeletePerson\\n x-ISC_ServiceMethod: DeletePerson\\n parameters:\\n - name: id\\n in: path\\n required: true\\n type: string\\n responses:\\n \'200\':\\n description: (Expected Result)\\n default:\\n description: (Unexpected Error)\\n /persons/:\\n post:\\n description: \' Creates a new Sample.Person record \'\\n summary: \' Create a person\'\\n operationId: CreatePerson\\n x-ISC_ServiceMethod: CreatePerson\\n parameters:\\n - name: payloadBody\\n in: body\\n description: Request body contents\\n required: false\\n schema:\\n type: string\\n responses:\\n \'200\':\\n description: (Expected Result)\\n default:\\n description: (Unexpected Error)\\ndefinitions:\\n Person:\\n type: object\\n properties:\\n Name:\\n type: string\\n Title:\\n type: string\\n Company:\\n type: string\\n Phone:\\n type: string\\n DOB:\\n type: string\\n format: date-time\\n"}'
Lo importante aquí es esta parte:
x-headmatter:
readable_by:
- dev
Consultad esta documentación: readable_by attribute
8.2.3. Prueba
8.2.3.1. Registrar a un nuevo desarrollador
8.2.3.2. Aprobar a este desarrollador
8.2.3.3. Añadir un rol para este desarrollador
curl 'http://localhost:8001/default/developers/dev@dev.com' -X PATCH \
--compressed -H 'Content-Type: application/json;charset=utf-8' -H 'Cache-Control: no-cache' \
-H 'Origin: http://localhost:8002' -H 'DNT: 1' -H 'Connection: keep-alive' \
-H 'Referer: http://localhost:8002/default/portal/permissions/dev/update' \
-H 'Pragma: no-cache' --data-raw '{"roles":["dev"]}'
8.3. Añadir Oauth2 para el desarrollador
En esta parte añadiremos una autenticación Oauth2 para que los desarrolladores puedan utilizar de forma segura nuestra API CRUD.
Este flujo proporcionará un auto-registro a los desarrolladores y les dará acceso a la API CRUD.
8.3.1. Primero: eliminar la autenticación básica
Para ello, reemplazaremos nuestra autenticación básica por un bearToken.
Primero hay que desactivar nuestra autenticación/acl básica.
Desde el Portal IAM:
o vía API REst:
# Disable ACL Plugin
curl 'http://localhost:8001/default/routes/afefe836-b9be-49a8-927a-1324a8597a9c/plugins/3f2e605e-9cb6-454a-83ec-d1b1929b1d30' \
-X PATCH --compressed -H 'Content-Type: application/json;charset=utf-8' -H 'Cache-Control: no-cache' \
-H 'Origin: http://localhost:8002' -H 'DNT: 1' -H 'Connection: keep-alive' \
-H 'Referer: http://localhost:8002/default/plugins/acl/31fe4e9e-d965-4afc-97ee-9448a4db9125/update' \
-H 'Pragma: no-cache' \
--data-raw '{"enabled":false,"name":"acl","route":{"id":"afefe836-b9be-49a8-927a-1324a8597a9c"},"config":{"hide_groups_header":false,"allow":["user","dev","crud"]}}'
donde afefe836-b9be-49a8-927a-1324a8597a9c es el ID de la ruta y 3f2e605e-9cb6-454a-83ec-d1b1929b1d30 es el ID del plugin ACL.
8.3.2. Segundo: añadir el plugin Oauth2
Portal IAM | API REST |
---|---|
|
|
8.3.3. Enlazar el servicio y la documentación
Desde el Portal IAM:
a través del API REst:
curl 'http://localhost:8001/default/document_objects' --compressed -H 'Content-Type: application/json;charset=utf-8' -H 'Cache-Control: no-cache' -H 'Origin: http://localhost:8002' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Referer: http://localhost:8002/default/services/create-documentation' -H 'Pragma: no-cache' --data-raw '{"service":{"id":"7bcef2e6-117c-487a-aab2-c7e57a0bf61a"},"path":"specs/iam-training.yml"}'
8.3.3.1. Prueba
Desde el portal para desarrolladores, iniciar sesión como dev@dev.com y crear una nueva aplicación.
Esto dará un client_id y un client_secret.
Estos se pueden utilizar en el Portal para desarrolladores de Swagger.
Registrar esta aplicación en el servicio CRUD:
Obtener el token:
curl --insecure -X POST https://localhost:8443/persons/oauth2/token \
--data "grant_type=client_credentials" \
--data "client_id=2TXNvDqjeVMHydJbjv9t96lWTXOKAtU8" \
--data "client_secret=V6Vma6AtIvl04UYssz6gAxPc92eCF4KR"
Usar el token:
curl --insecure -X GET https://localhost:8443/persons/all \
--header "authorization: Bearer u5guWaYR3BjZ1KdwuBSC6C7udCYxj5vK"
9. Portal de administración seguro
ATENCIÓN: El contenido de este apartado se ha modificado con respecto al contenido del mismo apartado en el artículo original ya que áquel no funcionaba en la nueva versión de Kong.
9.1. Crear un administrador
La forma más sencilla de asegurar nuestro API manager es iniciar el IAM (Kong) con un usuario con el role super-admin. Para ello, en la primera instanciación, antes de que se haya generado la BBDD asociada al IAM, deberemos haber incluido en nuestro docker-compose:
Bajo el servicio iam-migrations:
KONG_PASSWORD: kong
y, bajo el servicio iam:
KONG_PASSWORD: kong
KONG_ENFORCE_RBAC: 'on'
KONG_ADMIN_GUI_AUTH: 'basic-auth'
KONG_ADMIN_GUI_SESSION_CONF: '{"secret":"${IRIS_PASSWORD}","storage":"kong","cookie_secure":false}'
Esto hará que el IAM se inicie con una configuración de autenticación básica. Por defecto tendremos disponible un usuario: kong_admin; con role super-admin, que nos permitirá crear cualquier otro tipo de usuario, incluido usuarios administradores.
Una vez en el portal, podremos "Invitar" a usuarios administradores. Para hacerlo:
- Ir a Teams
- Hacer clic en Invite admin
- Especificar el email
- Especificar el Username
- Marcar el rol que deseemos, super-admin, admin, read-only,...
- Invitar
- Ir a Invited Admin
- Hacer clic en View
- Hacer clic en Generate link
Con ese link podremos activar el nuevo usuario administrador. Simplemente tenemos que pegarlo en el browser y darle a enter. Nos debería aparecer un mensaje indicando que el usuario se ha activado correctamente.
9.2. Usar la API del Administrador de Kong con el RBAC
Con un usuario de tipo RBAC ya no podemos utilizar la API de administración del IAM (Kong):
curl -s -X GET \
--url http://localhost:8001/routes
Se recibe este error:
{"message":"Invalid credentials. Token or User credentials required"}
Necesitamos pasar las credenciales (token) en la llamada. Si no tenemos el token de nuestro usuario administrador, podemos regenerarlo. Entramos en el portal con su usuario y password, vamos al Profile, opción Editar y, una vez dentro, hacemos click en Regenerar Token. Lo podemos copiar y utilizar en nuestra llamada con curl.
curl -s -X GET \
--url http://localhost:8001/routes \
--header "Kong-Admin-Token: eBI8jb4V4kHlYWFhdQDVqNydVKHgIjxz"
10. Plugins
Apartado obsoleto. Actualmente varias de los contenidos de este apartado no funcionan con las últimas versiones de Kong y por cómo está descrito no permite ajuste sin cambiarlo radicalmente. Aparece en la traducción actual por mantener la integridad del artículo original.
Kong incluye plugins de alta calidad.
Pero, ¿qué ocurre si necesitamos un plugin que no está incluído? ¿O si queremos plugins creados por la comunidad?
En este apartado hablaremos sobre los plugins creado por la comunidad y cómo importarlos.
A continuación, veremos cómo crear nuestro propio plugin.
10.1. Importar un plugin no incluido en Kong
En esta parte, utilizaremos el plugin jwt-crafter.
Este plugin añade la posibilidad de generar un token JWT dentro del propio IAM (Kong), eliminando la necesidad de que un servicio upstream sea el que genere el token.
Este es el plugin:
https://github.com/grongierisc/kong-plugin-jwt-crafter
Para instalar este plugin, como estamos utilizando la versión docker, necesitamos crear una nueva imagen que integre el plugin.
10.1.1. Generar una nueva imagen Docker para Kong/IAM con el plugin de la comunidad
- Crear una carpeta llamada IAM en la raíz de este git.
- Crear un dockerfile en esta nueva carpeta
- Crear una carpeta llamada plugins
- Aquí es donde añadiremos todos nuestros plugins externos
- Actualizar el archivo docker-compose para habilitar el nuevo plugin
En la carpeta plugins, clonar nuestro plugin externo con git clone.
git clone https://github.com/grongierisc/kong-plugin-jwt-crafter
El dockerfile debería verse así:
FROM intersystems/iam:3.2.1.0-4
USER root
COPY ./plugins /custom/plugins
RUN yum -y install unzip gzip gcc
RUN luarocks install luaossl OPENSSL_DIR=/usr/local/kong/ CRYPTO_INCDIR=/usr/local/kong/include
RUN cd /custom/plugins/kong-plugin-jwt-crafter && luarocks make
USER kong
¿Qué vemos en este dockerfile?
Básicamente, una vez tenemos los componentes de SO necesarios, para instalar un plugin externo, debemos ir a su carpeta raíz (donde está el rockspec) y llamar a luarocks make. Y eso es todo. Ya está instalado el plugin.
Para la parte de docker-compose:
-
Editar la etiqueta imagen de IAM
(puedes incluirlo directamente, y el build posterior la creará con este nombre, o cambiar el fichero .env e indicar el nombre de la imagen custom en la variable ISC_IAM_IMAGE)
intersystems/iam:3.2.1.0-4 -> intersystems/iam-custom:3.2.1.0-4 -
Añadir un build context en el servicio iam
build:
context: iam
dockerfile: dockerfile
- Habilitar el plugin en las variables de entorno
KONG_PLUGINS: 'bundled,jwt-crafter'
Ahora generamos nuestra nueva imagen IAM :
docker-compose build iam
este comando por defecto le da a la nueva imagen el nombre que aparezca en el campo* image:* del servicio. Ese nombre será el que hayamos indicado en la variable ISC_IAM_IMAGE (definida en nuestro archivo .env)
10.1.2. Prueba
docker-compose up -d
En plugin -> new, al final de la lista se ve el plugin jwt-crafter.
10.1.2.1. Uso
- Crear un nuevo servicio:
Portal IAM | API REST |
---|---|
|
|
- Crear una ruta
Portal IAM | API REST |
---|---|
|
|
- Reutilizar nuestra autenticación automática
Portal IAM | API REST |
---|---|
|
|
Ya estamos listos, este es el uso real de jwt-crafter.
# Add acl to route
curl -i -X POST http://localhost:8001/routes/crud-route-jwt/plugins \
--data "name=acl" \
--data "config.whitelist=test" \
--data "config.hide_groups_header=false"
# Create service
curl -i -X POST \
--url http://localhost:8001/services/ \
--data 'name=jwt-login' \
--data 'url=http://neverinvoked/'
# Create route
curl -i -X POST \
--url http://localhost:8001/services/jwt-login/routes \
--data 'name=jwt-login-route' \
--data 'paths=/jwt/log-in'
# Enable basic auth for service
curl -i -X POST http://localhost:8001/routes/jwt-login-route/plugins \
--data "name=basic-auth" \
--data "config.hide_credentials=false"
# Enable basic auth for service
curl -i -X POST http://localhost:8001/routes/jwt-login-route/plugins \
--data "name=jwt-crafter" \
--data "config.expires_in=86400"
# Add consumer
curl -i -X POST \
--url http://localhost:8001/consumers/ \
--data "username=test"
# Add consumer group
curl -i -X POST \
--url http://localhost:8001/consumers/test/acls \
--data "group=test"
# Add consumer credentials
curl -i -X POST http://localhost:8001/consumers/test/basic-auth \
--data "username=test" \
--data "password=test"
curl -i -X POST http://localhost:8001/consumers/test/jwt \
--data "key=test" \
--data "algorithm=HS256"
# JWT plugins
curl -i -X POST http://localhost:8001/routes/crud-route-jwt/plugins \
--data "name=jwt"
Prueba
# test:test is base64 encoded
curl -H 'Authorization: basic dGVzdDp0ZXN0' localhost:8000/jwt/log-in
curl --location --request GET 'http://localhost:8000/crud/persons/all' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW0iOiJ0ZXN0Iiwic3ViIjoiODJiNjcwZDgtNmY2OC00NDE5LWJiMmMtMmYxZjMxNTViN2E2Iiwicm9sIjpbInRlc3QiXSwiZXhwIjoxNjE2MjUyMTIwLCJpc3MiOiJ0ZXN0In0.g2jFqe0hDPumy8_gG7J3nYsuZ8KUz9SgZOecdBDhfns'
10.2. Crear un nuevo plugin
Este no es el lugar para aprender Lua.
Pero os daré algunos consejos sobre cómo reiniciar rápidamente IAM para probar nuestro nuevo desarrollo.
10.2.1. Estructura de los archivos
kong-plugin-helloworld
├── kong
│ └── plugins
│ └── helloworld
│ ├── handler.lua
│ └── schema.lua
└── kong-plugin-helloworld-0.1.0-1.rockspec
Por costumbre, los plugins de kong deben tener el prefijo kong-plugin.
En nuestro ejemplo, el nombre del plugin es helloworld.
Hay tres archivos obligatorios:
- handler.lua: es el núcleo de vuestro plugin. Es una interfaz para implementar, en la que cada función se ejecutará en el momento deseado del ciclo de vida de una solicitud/conexión.
- schema.lua: vuestro plugin probablemente debe retener algún tipo de configuración introducida por el usuario. Este módulo contiene el esquema de esa configuración y define las reglas que se aplican sobre él, para que el usuario solo pueda introducir valores de configuración válidos.
- *.rockspec: Rockspec: es un archivo de especificación de paquetes. Un script declarativo de Lua, con reglas sobre cómo generar y empaquetar rocks *.rockspec, un archivo Lua que contiene algunas tablas.
10.2.1.1. handler.lua
La interfaz de plugins permite anular cualquiera de los siguientes métodos en vuestro archivo handler.lua, para implementar una lógica personalizada en varios puntos de acceso durante la ejecución del ciclo de vida de Kong:
Nombre de la función | Fase | Descripción |
---|---|---|
:init_worker() | init_worker | Se ejecuta cuando inicia cada proceso en el worker de Nginx. |
:certificate() | ssl_certificate | Se ejecuta durante la fase de entrega del certificado SSL durante el establecimiento de la comunicación (handshake) con SSL. |
:rewrite() | rewrite | Se ejecuta para cada solicitud que es recibida de un cliente como controlador de la fase de reescritura. NOTA: en esta fase no se identificaron ni el Servicio ni el Consumidor, por lo que este controlador solamente se ejecutará si el complemento se configuró como un complemento global. |
:access() | access | Se ejecuta para cada solicitud proveniente de un cliente y antes de que esta sea reenviada al servicio de las fases preliminares. |
:response() | access | Reemplaza tanto a header_filter() como a body_filter(). Se ejecuta después de que todas las respuestas hayan sido recibidas del servicio de las fases preliminares, pero antes de enviar cualquier parte de las mismas al cliente. |
:header_filter() | header_filter | Se ejecuta cuando todas las cabeceras de bytes fueron recibidas en el servicio de las fases preliminares. |
:body_filter() | body_filter | Se ejecuta para cada fragmento recibido que proviene del contenido de la respuesta desde el servicio de las fases preliminares. Dado que la respuesta se transmite de vuelta al cliente, puede exceder el tamaño del búfer y sería transmitido por fragmentos, por lo tanto, se puede llamar a este método varias veces si la respuesta es grande. Consulte la documentación del módulo lua-nginx para obtener más información. |
:log() | log | Se ejecuta cuando el último byte de la respuesta es enviado al cliente. |
10.2.1.1.1. Ejemplo
local BasePlugin = require "kong.plugins.base_plugin"
local HelloWorldHandler = BasePlugin:extend()
function HelloWorldHandler:new()
HelloWorldHandler.super.new(self, "helloworld")
end
function HelloWorldHandler:access(conf)
HelloWorldHandler.super.access(self)
if conf.say_hello then
ngx.log(ngx.ERR, "============ Hello World! ============")
ngx.header["Hello-World"] = "Hello World!!!"
else
ngx.log(ngx.ERR, "============ Bye World! ============")
ngx.header["Hello-World"] = "Bye World!!!"
end
end
return HelloWorldHandler
10.2.1.2. schema.lua
El archivo de configuración se ve en el portal.
return {
no_consumer = true,
fields = {
say_hello = { type = "boolean", default = true },
say_hello_body = { type = "boolean", default = true }
}
}
10.2.1.3. *.rockspec
package = "kong-plugin-helloworld" -- hint: rename, must match the info in the filename of this rockspec!
-- as a convention; stick to the prefix: `kong-plugin-`
version = "0.1.0-1" -- hint: renumber, must match the info in the filename of this rockspec!
-- The version '0.1.0' is the source code version, the trailing '1' is the version of this rockspec.
-- whenever the source version changes, the rockspec should be reset to 1. The rockspec version is only
-- updated (incremented) when this file changes, but the source remains the same.
-- TODO: This is the name to set in the Kong configuration `plugins` setting.
-- Here we extract it from the package name.
local pluginName = package:match("^kong%-plugin%-(.+)$") -- "myPlugin"
supported_platforms = {"linux", "macosx"}
source = {
url = "https://github.com/grongierisc/iam-training",
branch = "master",
-- tag = "0.1.0"
-- hint: "tag" could be used to match tag in the repository
}
description = {
summary = "This a demo helloworld for Kong plugin",
homepage = "https://github.com/grongierisc/iam-training",
license = "Apache 2.0"
}
dependencies = {
"lua >= 5.1"
-- other dependencies should appear here
}
build = {
type = "builtin",
modules = {
["kong.plugins."..pluginName..".handler"] = "kong/plugins/"..pluginName.."/handler.lua",
["kong.plugins."..pluginName..".schema"] = "kong/plugins/"..pluginName.."/schema.lua",
}
}
10.2.2. Creación
Vamos a hacer lo mismo que aquí: 11.1.1. Generar una nueva imagen Docker para Kong/IAM con el plugin externo
Pero adaptado a nuestro plugin:
Dockerfile:
FROM intersystems/iam:1.5.0.9-4
USER root
COPY ./plugins /custom/plugins
RUN cd /custom/plugins/kong-plugin-jwt-crafter && luarocks make
RUN cd /custom/plugins/kong-plugin-helloworld && luarocks make
#USER kong #Stay with root use, we will see why later
Habilitar el plugin en las variables de entorno
KONG_PLUGINS: 'bundled,jwt-crafter,helloworld'
Ahora generamos nuestra nueva imagen IAM:
docker-compose build iam
Y después docker-compose upy probadlo.
10.2.3. Consejos
Para ejecutar el contenedor de IAM en el "modo depuración", para pararlo/reiniciarlo fácilmente, modificar el dockerfile para añadir/eliminar el plugin y así sucesivamente.
Se puede detener el servicio de IAM:
docker-compose stop iam
E iniciarlo en modo ejecución con un intérprete:
docker-compose run -p 8000:8000 -p 8001:8001 -p 8002:8002 iam sh
En el contenedor:
./docker-entrypoint.sh kong
Feliz programación :)
11. CI/CD
Estamos cerca del final de este artículo.
Para terminar, hablaremos sobre DevOps/CI/CD. El objetivo de este capítulo es dar algunas ideas sobre cómo implementar CI/CD para IAM/Kong.
Como IAM es API first, la idea es definir todas las llamadas a REST y después reproducirlas en cada entorno.
La forma más sencilla de definir las llamadas a REST es con Postman y su mejor amigo Newman (la versión de línea de comandos de Postman).
11.1. Crear una colección en Postman
Una cosa útil de Postman es su capacidad para ejecutar un script antes y después de una llamada a REST.
En la mayoría de los casos utilizaremos esta funcionalidad.
11.1.1. ¿Está funcionando IAM?
Nuestro primer script verificará si IAM está activo y funcionando.
var iam_url = pm.environment.get("iam_url");
var iam_config_port = pm.environment.get("iam_config_port");
var url = "http://" + iam_url + ":" + iam_config_port + "/";
SenReq(20);
async function SenReq(maxRequest) {
var next_request = "end request";
const result = await SendRequest(maxRequest);
console.log("result:",result);
if(result == -1)
{
console.error("IAM starting .... failed !!!!");
}
}
function SendRequest(maxRequest) {
return new Promise(resolve => {
pm.sendRequest(url,
function (err) {
if (err) {
if (maxRequest > 1) {
setTimeout(function () {}, 5000);
console.warn("IAM not started...retry..next retry in 5 sec");
SendRequest(maxRequest - 1);
} else {
console.error("IAM starting .... failed");
resolve(-1);
}
} else {
console.log("IAM starting .... ok");
resolve(1);
}
}
);
});
}
11.1.2. Eliminar los datos antiguos
var iam_url=pm.environment.get("iam_url");
var iam_config_port=pm.environment.get("iam_config_port");
pm.sendRequest("http://"+iam_url+":"+iam_config_port+"/plugins", function (err, res) {
if (err) {
console.log("ERROR : ",err);
}
else {
var body_json=res.json();
if(body_json.data)
{
for( i=0; i < body_json.data.length; i++)
{
// Example with a full fledged SDK Request
route_id = body_json.data[i].id;
const delete_route = {
url: "http://"+iam_url+":"+iam_config_port+"/plugins/" + route_id,
method: 'DELETE',
};
pm.sendRequest(delete_route, function(err, res){
console.log(err ? err : res);
});
}
}
}
});
Hacer lo mismo para las rutas, los servicios y los consumidores.
Seguir este orden es importante porque no es posible eliminar servicios sobre los que hay rutas definidas.
11.1.3. Crear un servicio/ruta
Las rutas dependen de los servicios. En este tipo de casos podemos utilizar la función Test de Postman para recuperar los datos:
Pantalla | Script |
---|---|
|
|
Aquí guardamos el ID y el nombre de los nuevos servicios de la respuesta.
Entonces podremos utilizarlos en la creación de la siguiente ruta:
Pantalla | Script |
---|---|
|
|
Aquí recuperamos la variable global "service_crud_name".
Después, podemos utilizarla en la llamada real.
Pantalla | Script |
---|---|
|
|
11.1.3.1. Consejos
- paylaod puede ser json o form-data
- form-data:
- json:
Una forma sencilla de obtener el formato json es ir al portal del administrador/View/Copy json:
11.2. Ejecutarlo con Newman
docker run --rm -v "`pwd`/ci/":"/etc/newman" \
--network="iam-training_default" \
-t postman/newman run "DevOps_IAM.postman_collection.json" \
--environment="DevOps_IAM.postman_environment.json"