NPM con OAuth2 Client Credentials

Fecha publicación
Dificultad
Avanzado
#
9
3 more properties
Publicado
tags
IoT, Gateways, logs, métricas, plataforma, Grafana, Alloy, Docker, sistema, Mimir, Loki, MinIO, almacenamiento, AWS, S3, visualización, monitoreo, captura, servidores, integración, DB, BBDD, servers, integration, metrics, systems, logs, keycloak, oauth2, oauth2-client-credentials

Descripción

Se describe al detalle cómo Nginx Proxy Manager (NPM) delega la autenticación del servicio Grafana MIMIR a Keycloak. Concretamente la aplicación cliente usa OAuth2 Client Credentials para poder validar su acceso al servicio. El vídeo describe con todo lujo de detalles el proceso y hace demostraciones además de proporcionar los ficheros de configuración necesarios.

Secuencia del proceso OAuth2 Client Credentials con reverse proxy

OAuth2.drawio.svg
Esquea origina hecho con mermaid:
Mermaid
sequenceDiagram
  autonumber
  participant ClientApp as Client App
  participant Proxy as Reverse Proxy (NPM)
  participant Server as Service
  participant IdP as IdP (Keycloak)
  
  ClientApp->>IdP: Authenticate<br />Client ID + Client Secret
  IdP->>ClientApp: ID + Secret are valid<br />Return Access Token
  ClientApp->>Proxy: GET /api/resource<br />using Access Token in headers
  Proxy->>IdP: Access Token valid?
  IdP->>Proxy: Yes, is valid
  Proxy->>Server: GET /api/resource<br />w/user metadata:::redLine
  Server->>Proxy: response GET /api/resource :::redLine
  Proxy->>ClientApp: response GET /api/resource
  
%%{init:{'theme':'forest'}}%% 
Fichero .png con código vectorial de .drawio incluido

Configuración NGINX Proxy Manager (NPM)

Capturas de pantalla de la configuración de metrics.i40sys.com con las customizaciones para cargar las particularidades de la configuración OAuth2.
image.png
image.png
image.png

image.png
image.png

ficheros de configuración custom

/data/nginx/custom/i40sys_mimir_custom_root.conf

Bash
# no hay configuraciones necesarias para OAuth2 Client Credentials
# este fichero se llenará con OAuth2 Authorization Code

/data/nginx/custom/i40sys_mimir_custom.conf

Bash
client_body_buffer_size 256M;
proxy_read_timeout 600;
proxy_connect_timeout 600;
proxy_send_timeout 600;
client_header_timeout 600s;
client_body_timeout 600s;
proxy_request_buffering off;

location /access-token/validation {
  internal;
  proxy_method GET;
  proxy_pass https://iam_i40sys-keycloak-1:8443/realms/observability/protocol/openid-connect/userinfo;

  proxy_set_header Host iam.i40sys.com;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Scheme $scheme;
  proxy_set_header Content-Length "";
  proxy_pass_request_body off;
}

location ~ ^/(api|prometheus)/ {
  proxy_pass http://observability_i40sys-mimir-1$request_uri;
  auth_request /access-token/validation;

  # proxy_set_header Upgrade $http_upgrade;
  # proxy_set_header Connection "upgrade";
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

notas sobre el docker-compose.yaml

El fichero de configuración es estándard, es decir, cualquier fichero que uses está bien. Lo más relevante a tener en cuenta es que el volumen que contiene los ficheros custom debe montarse en /data para que los anteriores ficheros esten mapeados.
Concretamente la configuración es:
YAML
services:
  nginx-reverse-proxy:
    image: 'lepresidente/nginxproxymanager:latest'
		...
    volumes:
      - "/data/rproxy/data:/data"
    ...
concretamente en nuestro escenario el fichero docker-compose.yaml es algo complejo porqué NPM usa Crowdsec para hacer filtrado. Dejamos una copia a continuación por si puede ayudar a alguien.
docker-compose.yaml
YAML
services:
  nginx-reverse-proxy:
    container_name: nginx-reverse-proxy
    image: 'lepresidente/nginxproxymanager:latest'
    hostname: "nginx-reverse-proxy"
    security_opt:
      - no-new-privileges=true
    entrypoint:
      - "/init"
    environment:
      PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt
      CURL_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt
      SUPPRESS_NO_CONFIG_WARNING: 1
      S6_BEHAVIOUR_IF_STAGE2_FAILS: 1
      S6_CMD_WAIT_FOR_SERVICES_MAXTIME: 0
      S6_FIX_ATTRS_HIDDEN: 1
      S6_KILL_FINISH_MAXTIME: 10000
      S6_VERBOSITY: 1
      NODE_ENV: production
      CROWDSEC_BOUNCER_APIKEY: ${CROWDSEC_BOUNCER_APIKEY}
      CROWDSEC_OPENRESTY_BOUNCER: |
        ENABLED=true
        API_URL=http://crowdsec:8080
        API_KEY=${CROWDSEC_BOUNCER_APIKEY}
        EXCLUDE_DOMAINS="metrics.i40sys.com,iam.i40sys.com,logs.i40sys.com,proxy.i40sys.com"
      # Uncomment this if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'
    ipc: "private"
    restart: "unless-stopped"
    volumes:
      - "/data/rproxy/data:/data"
      - "/data/rproxy/letsencrypt:/etc/letsencrypt"
    working_dir: "/app"
    networks:
      - "services"
      - "crowdsec"
    ports:
      - "10.2.0.134:81:81/tcp"
      - 80:80
      - 443:443

  crowdsec:
    image: docker.io/crowdsecurity/crowdsec:latest
    container_name: crowdsec
    environment:
      - COLLECTIONS=crowdsecurity/nginx-proxy-manager
    volumes:
      - /docker-data/crowdsec/db:/var/lib/crowdsec/data/
      - /docker-data/crowdsec/config:/etc/crowdsec/
      #- ./crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml
      - /data/rproxy/data/logs/:/var/log/npm:ro
    networks:
      crowdsec:
    restart: unless-stopped
    security_opt:
      - no-new-privileges=true
    depends_on:
      - nginx-reverse-proxy

networks:
  crowdsec:
    external: true
    name: "crowdsec"
  services:
    external: true
    name: "services"

References