From 327de21cfb3883a3b345fb8b3622333756b8931c Mon Sep 17 00:00:00 2001 From: 65397 Date: Thu, 7 Mar 2024 00:23:14 +0000 Subject: [PATCH] API v4.2.2 (#43) * njs bugfix * 20240226-01 Commit * 20240226-02 Commit * 20240226-03 Commit * 20240226-04 Commit * 20240226-05 Commit * 20240229-01 Commit Added client JWT-based authorization * 20240229-02 Commit * 20240206-01 Commit * 20240206-02 Commit * 20240206-03 Commit * 20240206-03 Commit --- FEATURES.md | 32 +++- README.md | 1 + USAGE-v4.2.md | 39 ++++- ...NX Declarative API.postman_collection.json | 156 +++++++++++++++++- etc/config.toml | 12 +- src/V4_2_CreateConfig.py | 71 +++++++- src/V4_2_NginxConfigDeclaration.py | 49 ++++++ templates/v4.2/apigateway.tmpl | 30 +++- .../v4.2/{auth => authn}/client/jwks.tmpl | 0 .../v4.2/{auth => authn}/client/jwt.tmpl | 0 .../v4.2/{auth => authn}/client/mtls.tmpl | 0 .../v4.2/{auth => authn}/server/token.tmpl | 0 .../v4.2/authz/client/jwt-authz-map.tmpl | 14 ++ templates/v4.2/authz/client/jwt.tmpl | 3 + templates/v4.2/http.tmpl | 26 ++- 15 files changed, 411 insertions(+), 22 deletions(-) rename templates/v4.2/{auth => authn}/client/jwks.tmpl (100%) rename templates/v4.2/{auth => authn}/client/jwt.tmpl (100%) rename templates/v4.2/{auth => authn}/client/mtls.tmpl (100%) rename templates/v4.2/{auth => authn}/server/token.tmpl (100%) create mode 100644 templates/v4.2/authz/client/jwt-authz-map.tmpl create mode 100644 templates/v4.2/authz/client/jwt.tmpl diff --git a/FEATURES.md b/FEATURES.md index 5ffd77a..f47e171 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -53,7 +53,7 @@ Client-side authentication profiles to be defined under `.declaration.http.authe "jwt": { "realm": "", "key": "|", - "cachetime": , + "cachetime": , "token_location": "" } } @@ -72,6 +72,36 @@ Client-side authentication profiles to be defined under `.declaration.http.authe } ``` +### Client authorization + +| Type | Description | API v4.0 | API v4.1 | API v4.2 | Notes | +|------|----------------------|----------|----------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| jwt | Java Web Token (JWT) | | | X | Based on JWT claims. Supported under
  • .declaration.http.server[]
  • .declaration.http.server[].location[]
  • .declaration.http.server[].location[].apigateway
  • | + +#### Examples + +Client-side authorization profiles to be defined under `.declaration.http.authorization` + +- jwt client authorization profile + + ```json +{ + "name": "", + "type": "jwt", + "jwt": { + "claims": [ + { + "name": "", + "value": [ + "" + ], + "errorcode": + } + ] + } +} +``` + ### Upstream and Source of truth authentication | Type | Description | API v4.0 | API v4.1 | API v4.2 | Notes | diff --git a/README.md b/README.md index a18ffd0..ace3c05 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Use cases include: - `http` snippets, upstreams, servers, locations - `stream` snippets, upstreams, servers - Swagger / OpenAPI schemas + - NGINX Javascript files ## Requirements diff --git a/USAGE-v4.2.md b/USAGE-v4.2.md index 0e25740..6dde62d 100644 --- a/USAGE-v4.2.md +++ b/USAGE-v4.2.md @@ -115,9 +115,14 @@ Declaration path `.declaration.http.servers[].locations[].apigateway` defines th - `api_gateway.server_url` - the base URL of the upstream server - `developer_portal.enabled` - enable/disable Developer portal provisioning - `developer_portal.uri` - the trailing part of the Developer portal URI, this is appended to `.declaration.http.servers[].locations[].uri`. If omitted it defaults to `devportal.html` -- `authentication` - optional, used to enforce JWT authentication at the API Gateway level -- `authentication.client` - JWT authentication profile name -- `authentication.enforceOnPaths` - if set to `true` JWT authentication is enforced on all API endpoints listed under `authentication.paths`. if set to `false` JWT authentication is enforced on all API endpoints but those listed under `authentication.paths` +- `authentication` - optional, used to enforce authentication at the API Gateway level +- `authentication.client[]` - authentication profile names +- `authentication.enforceOnPaths` - if set to `true` authentication is enforced on all API endpoints listed under `authentication.paths`. if set to `false` authentication is enforced on all API endpoints but those listed under `authentication.paths` +- `authentication.paths` - paths to enforce authentication +- `authorization[]` - optional, used to enforce authorization +- `authorization[].profile` - authorization profile name +- `authorization[].enforceOnPaths` - if set to `true` authorizaion is enforced on all API endpoints listed under `authorization.paths`. if set to `false` authorization is enforced on all API endpoints but those listed under `authorization[].paths` +- `authorization[].paths` - paths to enforce authorization - `rate_limit` - optional, used to enforce rate limiting at the API Gateway level - `rate_limit.enforceOnPaths` - if set to `true` rate limiting is enforced on all API endpoints listed under `rate_limit.paths`. if set to `false` rate limiting is enforced on all API endpoints but those listed under `rate_limit.paths` @@ -192,6 +197,16 @@ is: "/user/logout" ] }, + "authorization": [ + { + "profile": "JWT role based authorization", + "enforceOnPaths": true, + "paths": [ + "/user/login", + "/user/logout" + ] + } + ], "rate_limit": [ { "profile": "petstore_ratelimit", @@ -245,7 +260,23 @@ is: } } ] - } + }, + "authorization": [ + { + "name": "JWT role based authorization", + "type": "jwt", + "jwt": { + "claims": [ + { + "name": "roles", + "value": [ + "~(devops)" + ] + } + ] + } + } + ] } } } diff --git a/contrib/postman/NGINX Declarative API.postman_collection.json b/contrib/postman/NGINX Declarative API.postman_collection.json index 9f9efab..365e760 100644 --- a/contrib/postman/NGINX Declarative API.postman_collection.json +++ b/contrib/postman/NGINX Declarative API.postman_collection.json @@ -1,11 +1,10 @@ { "info": { - "_postman_id": "af3a9409-efb9-416e-ace0-b94f6fcfa9ed", + "_postman_id": "7a9da224-2aa4-47a4-8864-183ddbd12e55", "name": "NGINX Declarative API", "description": "Declarative REST API and GitOps automation layer for NGINX Instance Manager", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "1667416", - "_collection_link": "https://orange-rocket-1353.postman.co/workspace/NGINX~3b744358-53c9-4664-be10-f7d30ab89f84/collection/1667416-af3a9409-efb9-416e-ace0-b94f6fcfa9ed?action=share&source=collection_link&creator=1667416" + "_exporter_id": "30973250" }, "item": [ { @@ -4566,7 +4565,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0,\n \"modules\": [\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Petstore API\",\n \"names\": [\n \"apigw.nginx.lab\"\n ],\n \"resolver\": \"8.8.8.8\",\n \"listen\": {\n \"address\": \"80\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/apigw.nginx.lab-access_log\",\n \"error\": \"/var/log/nginx/apigw.nginx.lab-error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/petstore\",\n \"urimatch\": \"prefix\",\n \"apigateway\": {\n \"openapi_schema\": {\n \"content\": \"http://petstore.swagger.io/v2/swagger.json\",\n \"authentication\": [\n {\n \"profile\": \"Source of truth authentication profile using HTTP header token authentication\"\n }\n ]\n },\n \"api_gateway\": {\n \"enabled\": true,\n \"strip_uri\": true,\n \"server_url\": \"https://petstore.swagger.io/v2\"\n },\n \"developer_portal\": {\n \"enabled\": true,\n \"uri\": \"/petstore-devportal.html\"\n },\n \"authentication\": {\n \"client\": [\n {\n \"profile\": \"Petstore JWT Authentication\"\n }\n ],\n \"enforceOnPaths\": true,\n \"paths\": [\n \"/user/login\",\n \"/user/logout\"\n ]\n },\n \"rate_limit\": [\n {\n \"profile\": \"petstore_ratelimit\",\n \"httpcode\": 429,\n \"burst\": 0,\n \"delay\": 0,\n \"enforceOnPaths\": true,\n \"paths\": [\n \"/user/login\",\n \"/user/logout\"\n ]\n }\n ]\n },\n \"log\": {\n \"access\": \"/var/log/nginx/petstore-access_log\",\n \"error\": \"/var/log/nginx/petstore-error_log\"\n }\n }\n ]\n }\n ],\n \"rate_limit\": [\n {\n \"name\": \"petstore_ratelimit\",\n \"key\": \"$binary_remote_addr\",\n \"size\": \"10m\",\n \"rate\": \"2r/s\"\n }\n ],\n \"authentication\": {\n \"client\": [\n {\n \"name\": \"Petstore JWT Authentication\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"realm\": \"Petstore Authentication\",\n \"key\": \"{\\\"keys\\\": [{\\\"k\\\":\\\"ZmFudGFzdGljand0\\\",\\\"kty\\\":\\\"oct\\\",\\\"kid\\\":\\\"0001\\\"}]}\",\n \"cachetime\": 5\n }\n }\n ],\n \"server\": [\n {\n \"name\": \"Source of truth authentication profile using bearer token authentication\",\n \"type\": \"token\",\n \"token\": {\n \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDI0ODEzNjcsImV4cCI6MTcwMjQ4MTM2OH0.eyJuYW1lIjoiQm9iIERldk9wcyIsInN1YiI6IkpXVCBzdWIgY2xhaW0iLCJpc3MiOiJKV1QgaXNzIGNsYWltIiwicm9sZXMiOlsiZGV2b3BzIl19.SKA_7MszAypMEtX5NDQ0TcUbVYx_Wt0hrtmuyTmrVKU\",\n \"type\": \"bearer\"\n }\n },\n {\n \"name\": \"Source of truth authentication profile using HTTP header token authentication\",\n \"type\": \"token\",\n \"token\": {\n \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDI0ODEzNjcsImV4cCI6MTcwMjQ4MTM2OH0.eyJuYW1lIjoiQm9iIERldk9wcyIsInN1YiI6IkpXVCBzdWIgY2xhaW0iLCJpc3MiOiJKV1QgaXNzIGNsYWltIiwicm9sZXMiOlsiZGV2b3BzIl19.SKA_7MszAypMEtX5NDQ0TcUbVYx_Wt0hrtmuyTmrVKU\",\n \"type\": \"header\",\n \"location\": \"X-AUTH-TOKEN\"\n }\n }\n ]\n }\n }\n }\n}", + "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0,\n \"modules\": [\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Petstore API\",\n \"names\": [\n \"apigw.nginx.lab\"\n ],\n \"resolver\": \"8.8.8.8\",\n \"listen\": {\n \"address\": \"80\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/apigw.nginx.lab-access_log\",\n \"error\": \"/var/log/nginx/apigw.nginx.lab-error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/petstore\",\n \"urimatch\": \"prefix\",\n \"apigateway\": {\n \"openapi_schema\": {\n \"content\": \"http://petstore.swagger.io/v2/swagger.json\",\n \"authentication\": [\n {\n \"profile\": \"Source of truth authentication profile using HTTP header token authentication\"\n }\n ]\n },\n \"api_gateway\": {\n \"enabled\": true,\n \"strip_uri\": true,\n \"server_url\": \"https://petstore.swagger.io/v2\"\n },\n \"developer_portal\": {\n \"enabled\": true,\n \"uri\": \"/petstore-devportal.html\"\n },\n \"authentication\": {\n \"client\": [\n {\n \"profile\": \"Petstore JWT Authentication\"\n }\n ],\n \"enforceOnPaths\": true,\n \"paths\": [\n \"/user/login\",\n \"/user/logout\"\n ]\n },\n \"authorization\": [\n {\n \"profile\": \"JWT role based authorization\",\n \"enforceOnPaths\": true,\n \"paths\": [\n \"/user/login\",\n \"/user/logout\"\n ]\n }\n ],\n \"rate_limit\": [\n {\n \"profile\": \"petstore_ratelimit\",\n \"httpcode\": 429,\n \"burst\": 0,\n \"delay\": 0,\n \"enforceOnPaths\": true,\n \"paths\": [\n \"/user/login\",\n \"/user/logout\"\n ]\n }\n ]\n },\n \"log\": {\n \"access\": \"/var/log/nginx/petstore-access_log\",\n \"error\": \"/var/log/nginx/petstore-error_log\"\n }\n }\n ]\n }\n ],\n \"rate_limit\": [\n {\n \"name\": \"petstore_ratelimit\",\n \"key\": \"$binary_remote_addr\",\n \"size\": \"10m\",\n \"rate\": \"2r/s\"\n }\n ],\n \"authentication\": {\n \"client\": [\n {\n \"name\": \"Petstore JWT Authentication\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"realm\": \"Petstore Authentication\",\n \"key\": \"{\\\"keys\\\": [{\\\"k\\\":\\\"ZmFudGFzdGljand0\\\",\\\"kty\\\":\\\"oct\\\",\\\"kid\\\":\\\"0001\\\"}]}\",\n \"cachetime\": 5\n }\n }\n ],\n \"server\": [\n {\n \"name\": \"Source of truth authentication profile using bearer token authentication\",\n \"type\": \"token\",\n \"token\": {\n \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDI0ODEzNjcsImV4cCI6MTcwMjQ4MTM2OH0.eyJuYW1lIjoiQm9iIERldk9wcyIsInN1YiI6IkpXVCBzdWIgY2xhaW0iLCJpc3MiOiJKV1QgaXNzIGNsYWltIiwicm9sZXMiOlsiZGV2b3BzIl19.SKA_7MszAypMEtX5NDQ0TcUbVYx_Wt0hrtmuyTmrVKU\",\n \"type\": \"bearer\"\n }\n },\n {\n \"name\": \"Source of truth authentication profile using HTTP header token authentication\",\n \"type\": \"token\",\n \"token\": {\n \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDI0ODEzNjcsImV4cCI6MTcwMjQ4MTM2OH0.eyJuYW1lIjoiQm9iIERldk9wcyIsInN1YiI6IkpXVCBzdWIgY2xhaW0iLCJpc3MiOiJKV1QgaXNzIGNsYWltIiwicm9sZXMiOlsiZGV2b3BzIl19.SKA_7MszAypMEtX5NDQ0TcUbVYx_Wt0hrtmuyTmrVKU\",\n \"type\": \"header\",\n \"location\": \"X-AUTH-TOKEN\"\n }\n }\n ]\n },\n \"authorization\": [\n {\n \"name\": \"JWT role based authorization\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"claims\": [\n {\n \"name\": \"roles\",\n \"value\": [\n \"~(devops)\"\n ]\n }\n ]\n }\n }\n ]\n }\n }\n}", "options": { "raw": { "language": "json" @@ -5163,7 +5162,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0,\n \"modules\": [\n \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n }\n}", + "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\"\n }\n },\n \"declaration\": {\n }\n}", "options": { "raw": { "language": "json" @@ -5321,7 +5320,7 @@ "name": "JWT Client Authentication", "item": [ { - "name": "JWT Client Authentication - local JWT key", + "name": "JWT Client Authentication - local JWT key and Bearer token", "event": [ { "listen": "test", @@ -5342,7 +5341,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0,\n \"modules\": []\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Online boutique\",\n \"names\": [\n \"www.online-boutique.lan\"\n ],\n \"listen\": {\n \"address\": \"80\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/online-boutique.lan-access_log\",\n \"error\": \"/var/log/nginx/online-boutique.lan-error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://onlineboutique_upstream\",\n \"authentication\": {\n \"client\": [\n {\n \"profile\": \"online_boutique_jwt_authentication_local\"\n }\n ]\n }\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"onlineboutique_upstream\",\n \"origin\": [\n {\n \"server\": \"192.168.2.200:80\"\n }\n ]\n }\n ],\n \"authentication\": {\n \"client\": [\n {\n \"name\": \"online_boutique_jwt_authentication_local\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"realm\": \"Online Boutique Authentication\",\n \"key\": \"{\\\"keys\\\": [{\\\"k\\\":\\\"ZmFudGFzdGljand0\\\",\\\"kty\\\":\\\"oct\\\",\\\"kid\\\":\\\"0001\\\"}]}\",\n \"cachetime\": 5\n }\n },\n {\n \"name\": \"online_boutique_jwt_authentication_key_from_url\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"realm\": \"Online Boutique Authentication GitOps\",\n \"key\": \"http://192.168.2.19/jwks.json\",\n \"cachetime\": 5\n }\n }\n ]\n }\n }\n }\n}", + "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0,\n \"modules\": []\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Test service\",\n \"resolver\": \"8.8.8.8\",\n \"names\": [\n \"test.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"80\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/test.nginx.lab-access_log\",\n \"error\": \"/var/log/nginx/test.nginx.lab-error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\",\n \"authentication\": {\n \"client\": [\n {\n \"profile\": \"JWT Auth with hardwired key and Bearer token\"\n }\n ]\n },\n \"headers\": {\n \"to_server\": {\n \"set\": [\n {\n \"name\": \"Host\",\n \"value\": \"echo.free.beeceptor.com\"\n }\n ]\n }\n }\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"echo.free.beeceptor.com\"\n }\n ]\n }\n ],\n \"authentication\": {\n \"client\": [\n {\n \"name\": \"JWT Auth with hardwired key and Bearer token\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"realm\": \"JWT Authentication\",\n \"key\": \"{\\\"keys\\\": [{\\\"k\\\":\\\"ZmFudGFzdGljand0\\\",\\\"kty\\\":\\\"oct\\\",\\\"kid\\\":\\\"0001\\\"}]}\"\n }\n },\n {\n \"name\": \"JWT Auth with external key and Bearer token\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"realm\": \"JWT Authentication GitOps\",\n \"key\": \"http://192.168.2.5:20080/jwks.json\",\n \"cachetime\": 5\n }\n },\n {\n \"name\": \"JWT Auth with hardwired key and token in auth_token query string parameter\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"realm\": \"JWT Authentication\",\n \"key\": \"{\\\"keys\\\": [{\\\"k\\\":\\\"ZmFudGFzdGljand0\\\",\\\"kty\\\":\\\"oct\\\",\\\"kid\\\":\\\"0001\\\"}]}\",\n \"token_location\": \"$arg_auth_token\"\n }\n },\n {\n \"name\": \"JWT Auth with hardwired key and token in X-Auth-Token HTTP header\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"realm\": \"JWT Authentication\",\n \"key\": \"{\\\"keys\\\": [{\\\"k\\\":\\\"ZmFudGFzdGljand0\\\",\\\"kty\\\":\\\"oct\\\",\\\"kid\\\":\\\"0001\\\"}]}\",\n \"token_location\": \"$http_x_auth_token\"\n }\n }\n ]\n }\n }\n }\n}", "options": { "raw": { "language": "json" @@ -5365,7 +5364,7 @@ "response": [] }, { - "name": "Change to use JWT key stored on external URL", + "name": "JWT secret fetched from URL", "event": [ { "listen": "test", @@ -5386,7 +5385,97 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0,\n \"modules\": []\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Online boutique\",\n \"names\": [\n \"www.online-boutique.lan\"\n ],\n \"listen\": {\n \"address\": \"80\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/online-boutique.lan-access_log\",\n \"error\": \"/var/log/nginx/online-boutique.lan-error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://onlineboutique_upstream\",\n \"authentication\": {\n \"client\": [\n {\n \"profile\": \"online_boutique_jwt_authentication_key_from_url\"\n }\n ]\n }\n }\n ]\n }\n ]\n }\n }\n}", + "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0,\n \"modules\": []\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Test service\",\n \"resolver\": \"8.8.8.8\",\n \"names\": [\n \"test.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"80\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/test.nginx.lab-access_log\",\n \"error\": \"/var/log/nginx/test.nginx.lab-error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\",\n \"authentication\": {\n \"client\": [\n {\n \"profile\": \"JWT Auth with external key and Bearer token\"\n }\n ]\n },\n \"headers\": {\n \"to_server\": {\n \"set\": [\n {\n \"name\": \"Host\",\n \"value\": \"echo.free.beeceptor.com\"\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{ncg_host}}:{{ncg_port}}/{{ngc_api_version}}/config/{{configUid}}", + "protocol": "http", + "host": [ + "{{ncg_host}}" + ], + "port": "{{ncg_port}}", + "path": [ + "{{ngc_api_version}}", + "config", + "{{configUid}}" + ] + } + }, + "response": [] + }, + { + "name": "JWT token in auth_token query string parameter", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var respData = JSON.parse(responseBody);", + "", + "tests[\"configUid is: \" +respData.configUid] = respData.configUid;", + "", + "pm.collectionVariables.set('configUid',respData.configUid);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0,\n \"modules\": []\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Test service\",\n \"resolver\": \"8.8.8.8\",\n \"names\": [\n \"test.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"80\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/test.nginx.lab-access_log\",\n \"error\": \"/var/log/nginx/test.nginx.lab-error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\",\n \"authentication\": {\n \"client\": [\n {\n \"profile\": \"JWT Auth with hardwired key and token in auth_token query string parameter\"\n }\n ]\n },\n \"headers\": {\n \"to_server\": {\n \"set\": [\n {\n \"name\": \"Host\",\n \"value\": \"echo.free.beeceptor.com\"\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{ncg_host}}:{{ncg_port}}/{{ngc_api_version}}/config/{{configUid}}", + "protocol": "http", + "host": [ + "{{ncg_host}}" + ], + "port": "{{ncg_port}}", + "path": [ + "{{ngc_api_version}}", + "config", + "{{configUid}}" + ] + } + }, + "response": [] + }, + { + "name": "JWT token in HTTP X-Auth-Token header", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var respData = JSON.parse(responseBody);", + "", + "tests[\"configUid is: \" +respData.configUid] = respData.configUid;", + "", + "pm.collectionVariables.set('configUid',respData.configUid);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0,\n \"modules\": []\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Test service\",\n \"resolver\": \"8.8.8.8\",\n \"names\": [\n \"test.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"80\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/test.nginx.lab-access_log\",\n \"error\": \"/var/log/nginx/test.nginx.lab-error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\",\n \"authentication\": {\n \"client\": [\n {\n \"profile\": \"JWT Auth with hardwired key and token in X-Auth-Token HTTP header\"\n }\n ]\n },\n \"headers\": {\n \"to_server\": {\n \"set\": [\n {\n \"name\": \"Host\",\n \"value\": \"echo.free.beeceptor.com\"\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n}", "options": { "raw": { "language": "json" @@ -5411,6 +5500,55 @@ } ] }, + { + "name": "JWT Client Authentication and Authorization", + "item": [ + { + "name": "JWT Client Authentication and Authorization", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var respData = JSON.parse(responseBody);", + "", + "tests[\"configUid is: \" +respData.configUid] = respData.configUid;", + "", + "pm.collectionVariables.set('configUid',respData.configUid);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"output\": {\n \"type\": \"nms\",\n \"nms\": {\n \"url\": \"{{nim_host}}\",\n \"username\": \"{{nim_username}}\",\n \"password\": \"{{nim_password}}\",\n \"instancegroup\": \"{{nim_instancegroup}}\",\n \"synctime\": 0\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"JWT AuthN and AuthZ test server\",\n \"resolver\": \"8.8.8.8\",\n \"names\": [\n \"test.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"0.0.0.0:80\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/auth-test.nginx.lab_access_log\",\n \"error\": \"/var/log/nginx/auth-test.nginx.lab_error_log\"\n },\n \"headers\": {\n \"to_client\": {\n \"add\": [\n {\n \"name\": \"X-Injected-JWT-Group\",\n \"value\": \"$jwt_claim_roles\"\n }\n ]\n }\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\",\n \"headers\": {\n \"to_server\": {\n \"set\": [\n {\n \"name\": \"Host\",\n \"value\": \"echo.free.beeceptor.com\"\n }\n ]\n }\n },\n \"authentication\": {\n \"client\": [\n {\n \"profile\": \"jwt_authentication_local\"\n }\n ]\n },\n \"authorization\": {\n \"profile\": \"jwt role based authorization\"\n }\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"echo.free.beeceptor.com\"\n }\n ]\n }\n ],\n \"authentication\": {\n \"client\": [\n {\n \"name\": \"jwt_authentication_local\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"realm\": \"JWT Client Authentication\",\n \"key\": \"{\\\"keys\\\": [{\\\"k\\\":\\\"ZmFudGFzdGljand0\\\",\\\"kty\\\":\\\"oct\\\",\\\"kid\\\":\\\"0001\\\"}]}\",\n \"jwt_type\": \"signed\"\n }\n }\n ]\n },\n \"authorization\": [\n {\n \"name\": \"jwt role based authorization\",\n \"type\": \"jwt\",\n \"jwt\": {\n \"claims\": [\n {\n \"name\": \"roles\",\n \"value\": [\n \"~(devops)\"\n ]\n }\n ]\n }\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{ncg_host}}:{{ncg_port}}/{{ngc_api_version}}/config", + "protocol": "http", + "host": [ + "{{ncg_host}}" + ], + "port": "{{ncg_port}}", + "path": [ + "{{ngc_api_version}}", + "config" + ] + } + }, + "response": [] + } + ] + }, { "name": "HTTP Headers Manipulation", "item": [ diff --git a/etc/config.toml b/etc/config.toml index 25f24d2..2ed131e 100644 --- a/etc/config.toml +++ b/etc/config.toml @@ -17,8 +17,10 @@ apigwconf = "apigateway.tmpl" streamconf = "stream.tmpl" configmap = "configmap.tmpl" -auth_client_root = "auth/client" -auth_server_root = "auth/server" +auth_client_root = "authn/client" +auth_server_root = "authn/server" + +authz_client_root = "authz/client" # NGINX Declarative API Server [apiserver] @@ -41,11 +43,11 @@ uri = "/v1/devportal" config_dir = '/etc/nginx' certs_dir = '/etc/nginx/ssl' devportal_dir = '/etc/nginx/devportal' -auth_client_dir = '/etc/nginx/auth/client' -auth_server_dir = '/etc/nginx/auth/server' +auth_client_dir = '/etc/nginx/authn/client' +auth_server_dir = '/etc/nginx/authn/server' +authz_client_dir = '/etc/nginx/authz/client' njs_dir = '/etc/nginx/njs' - # Time to wait to get status after committing a staged config staged_config_publish_waittime = 2 diff --git a/src/V4_2_CreateConfig.py b/src/V4_2_CreateConfig.py index 9e4c6b4..d26346d 100644 --- a/src/V4_2_CreateConfig.py +++ b/src/V4_2_CreateConfig.py @@ -198,6 +198,49 @@ def createconfig(declaration: ConfigDeclaration, apiversion: str, runfromautosyn all_auth_server_profiles.append(auth_profile['name']) auxFiles['files'].append(authProfileConfigFile) + + # Check authorization profiles validity and creates authorization config files + + # List of all authorization client profile names + all_authz_client_profiles = [] + + d_authz_profiles = v4_2.MiscUtils.getDictKey(d, 'declaration.http.authorization') + if d_authz_profiles is not None: + # Render all client authorization profiles + + for i in range(len(d_authz_profiles)): + authz_profile = d_authz_profiles[i] + + match authz_profile['type']: + case 'jwt': + # Add the rendered authorization configuration snippet as a config file in the staged configuration - jwt authZ maps template + templateName = NcgConfig.config['templates']['authz_client_root']+"/jwt-authz-map.tmpl" + renderedClientAuthZProfile = j2_env.get_template(templateName).render( + authprofile=authz_profile, ncgconfig=NcgConfig.config) + + b64renderedClientAuthProfile = base64.b64encode(bytes(renderedClientAuthZProfile, 'utf-8')).decode('utf-8') + configFileName = NcgConfig.config['nms']['authz_client_dir'] + '/'+authz_profile['name'].replace(' ','_')+".maps.conf" + authProfileConfigFile = {'contents': b64renderedClientAuthProfile, + 'name': configFileName } + + all_authz_client_profiles.append(authz_profile['name']) + auxFiles['files'].append(authProfileConfigFile) + + # Add the rendered authorization configuration snippet as a config file in the staged configuration - jwt template + templateName = NcgConfig.config['templates']['authz_client_root'] + "/jwt.tmpl" + renderedClientAuthZProfile = j2_env.get_template(templateName).render( + authprofile=authz_profile, ncgconfig=NcgConfig.config) + + b64renderedClientAuthProfile = base64.b64encode(bytes(renderedClientAuthZProfile, 'utf-8')).decode( + 'utf-8') + configFileName = NcgConfig.config['nms']['authz_client_dir'] + '/' + authz_profile['name'].replace(' ', + '_') + ".conf" + authProfileConfigFile = {'contents': b64renderedClientAuthProfile, + 'name': configFileName} + + all_authz_client_profiles.append(authz_profile['name']) + auxFiles['files'].append(authProfileConfigFile) + # NGINX Javascript profiles all_njs_profiles = [] d_njs_files = v4_2.MiscUtils.getDictKey(d, 'declaration.http.njs_profiles') @@ -245,6 +288,25 @@ def createconfig(declaration: ConfigDeclaration, apiversion: str, runfromautosyn {"code": status, "content": f"invalid njs profile [{server['njs'][i]['profile']}] in server [{server['name']}], must be one of {all_njs_profiles}"}}} + # Server client authentication name validity check + if 'authentication' in server and server['authentication']: + serverAuthClientProfiles = server['authentication']['client'] + + for authClientProfile in serverAuthClientProfiles: + if authClientProfile['profile'] not in all_auth_client_profiles: + return {"status_code": 422, + "message": {"status_code": status, "message": + {"code": status, + "content": f"invalid client authentication profile [{authClientProfile['profile']}] in server [{server['name']}] must be one of {all_auth_client_profiles}"}}} + + # Location client authorization name validity check + if 'authorization' in server and server['authorization']: + if server['authorization']['profile'] not in all_authz_client_profiles: + return {"status_code": 422, + "message": {"status_code": status, "message": + {"code": status, + "content": f"invalid client authorization profile [{server['authorization']['profile']}] in server [{server['name']}] must be one of {all_authz_client_profiles}"}}} + if server['snippet']: status, serverSnippet = v4_2.GitOps.getObjectFromRepo(object = server['snippet'], authProfiles = d['declaration']['http']['authentication'], base64Encode = False) @@ -286,7 +348,14 @@ def createconfig(declaration: ConfigDeclaration, apiversion: str, runfromautosyn if authClientProfile['profile'] not in all_auth_client_profiles: return {"status_code": 422, "message": {"status_code": status, "message": - {"code": status, "content": f"invalid client authentication profile [{authClientProfile['profile']}] in location [{loc['uri']}]"}}} + {"code": status, "content": f"invalid client authentication profile [{authClientProfile['profile']}] in location [{loc['uri']}] must be one of {all_auth_client_profiles}"}}} + + # Location client authorization name validity check + if 'authorization' in loc and loc['authorization']: + if loc['authorization']['profile'] not in all_authz_client_profiles: + return {"status_code": 422, + "message": {"status_code": status, "message": + {"code": status, "content": f"invalid client authorization profile [{loc['authorization']['profile']}] in location [{loc['uri']}] must be one of {all_authz_client_profiles}"}}} # Location server authentication name validity check if 'authentication' in loc and loc['authentication']: diff --git a/src/V4_2_NginxConfigDeclaration.py b/src/V4_2_NginxConfigDeclaration.py index 9dccd8f..e09b5f0 100644 --- a/src/V4_2_NginxConfigDeclaration.py +++ b/src/V4_2_NginxConfigDeclaration.py @@ -253,6 +253,9 @@ class LocationAuth(BaseModel, extra="forbid"): server: Optional[List[LocationAuthServer]] = [] +class AuthorizationProfileReference(BaseModel, extra="forbid"): + profile: str + class LocationHeaders(BaseModel, extra="forbid"): to_server: Optional[LocationHeaderToServer] = {} to_client: Optional[LocationHeaderToClient] = {} @@ -271,6 +274,11 @@ class APIGatewayAuthentication(BaseModel, extra="forbid"): paths: Optional[List[str]] = [] +class APIGatewayAuthorization(BaseModel, extra="forbid"): + profile: str + enforceOnPaths: Optional[bool] = True + paths: Optional[List[str]] = [] + class AuthClientJWT(BaseModel, extra="forbid"): realm: str = "JWT Authentication" key: str = "" @@ -315,6 +323,26 @@ def check_type(self) -> 'AuthServerToken': return self +class JwtAuthZNameValue(BaseModel, extra="forbid"): + name: str + value: List[str] + errorcode: Optional[int] = 401 + + @model_validator(mode='after') + def check_type(self) -> 'JwtAuthZNameValue': + errorcode = self.errorcode + + valid = [401, 403] + if errorcode not in valid: + raise ValueError(f"Invalid errorcode [{errorcode}] must be one of {str(valid)}") + + return self + + +class AuthorizationJWT(BaseModel, extra="forbid"): + claims: List[JwtAuthZNameValue] + + class HealthCheck(BaseModel, extra="forbid"): enabled: Optional[bool] = False uri: Optional[str] = "/" @@ -347,6 +375,7 @@ class Location(BaseModel, extra="forbid"): app_protect: Optional[AppProtect] = {} snippet: Optional[ObjectFromSourceOfTruth] = {} authentication: Optional[LocationAuth] = {} + authorization: Optional[AuthorizationProfileReference] = {} headers: Optional[LocationHeaders]= {} njs: Optional[List[NjsHookLocation]] = [] @@ -446,6 +475,8 @@ class Server(BaseModel, extra="forbid"): snippet: Optional[ObjectFromSourceOfTruth] = {} headers: Optional[LocationHeaders] = {} njs: Optional[List[NjsHookHttpServer]] = [] + authentication: Optional[LocationAuth] = {} + authorization: Optional[AuthorizationProfileReference] = {} class L4Server(BaseModel, extra="forbid"): @@ -587,6 +618,22 @@ class Authentication(BaseModel, extra="forbid"): server: Optional[List[Authentication_Server]] = [] +class Authorization(BaseModel, extra="forbid"): + name: str + type: str + + jwt: Optional[AuthorizationJWT] = {} + + @model_validator(mode='after') + def check_type(self) -> 'Authorization': + _type, name = self.type, self.name + + valid = ['jwt'] + if _type not in valid: + raise ValueError(f"Invalid authorization type [{_type}] for profile [{name}] must be one of {str(valid)}") + + return self + class NjsFile(BaseModel, extra="forbid"): name: str file: ObjectFromSourceOfTruth @@ -601,6 +648,7 @@ class Http(BaseModel, extra="forbid"): maps: Optional[List[Map]] = [] snippet: Optional[ObjectFromSourceOfTruth] = {} authentication: Optional[Authentication] = {} + authorization: Optional[List[Authorization]] = [] njs: Optional[List[NjsHookHttpServer]] = [] njs_profiles: Optional[List[NjsFile]] = [] @@ -625,6 +673,7 @@ class APIGateway(BaseModel, extra="forbid"): developer_portal: Optional[DeveloperPortal] = {} rate_limit: Optional[List[RateLimitApiGw]] = [] authentication: Optional[APIGatewayAuthentication] = {} + authorization: Optional[List[APIGatewayAuthorization]] = [] log: Optional[Log] = {} diff --git a/templates/v4.2/apigateway.tmpl b/templates/v4.2/apigateway.tmpl index 9a0bd2a..292c814 100644 --- a/templates/v4.2/apigateway.tmpl +++ b/templates/v4.2/apigateway.tmpl @@ -70,7 +70,7 @@ location {% if '{' not in path.path %}={% else %}~{% endif %} {{ declaration.loc {%- if declaration.location.apigateway.authentication.enforceOnPaths == True -%} {%- set enforceAuth.toBeEnforced = True -%} {%- else -%} - {%- set enforceRL.toBeEnforced = False -%} + {%- set enforceAuth.toBeEnforced = False -%} {%- endif -%} {%- endif -%} {%- endfor -%} @@ -88,6 +88,34 @@ location {% if '{' not in path.path %}={% else %}~{% endif %} {{ declaration.loc {# --- Authentication end --- #} + + {# --- Authorization start --- #} + {%- if declaration.location.apigateway.authorization -%} + {%- for authZentry in declaration.location.apigateway.authorization %} + {%- set enforceAuthZ = namespace(toBeEnforced = False) -%} + {%- if authZentry.enforceOnPaths == False -%} + {%- set enforceAuthZ.toBeEnforced = True -%} + {%- endif -%} + {%- for authPath in authZentry.paths -%} + {%- if path.path == authPath -%} + {%- if authZentry.enforceOnPaths == True -%} + {%- set enforceAuthZ.toBeEnforced = True -%} + {%- else -%} + {%- set enforceAuthZ.toBeEnforced = False -%} + {%- endif -%} + {%- endif -%} + {%- endfor -%} + + {# --- Client authorization --- #} + {%- if enforceAuthZ.toBeEnforced == True -%} + include "{{ ncgconfig.nms.authz_client_dir }}/{{ authZentry.profile | replace(" ", "_") }}.conf"; + {%- endif -%} + + {%- endfor -%} + {%- endif %} + + {# --- Authorization end --- #} + {% if declaration.location.apigateway.api_gateway.strip_uri -%} rewrite ^{{ declaration.location.uri }}/(.*)$ /$1 break; {% endif %} diff --git a/templates/v4.2/auth/client/jwks.tmpl b/templates/v4.2/authn/client/jwks.tmpl similarity index 100% rename from templates/v4.2/auth/client/jwks.tmpl rename to templates/v4.2/authn/client/jwks.tmpl diff --git a/templates/v4.2/auth/client/jwt.tmpl b/templates/v4.2/authn/client/jwt.tmpl similarity index 100% rename from templates/v4.2/auth/client/jwt.tmpl rename to templates/v4.2/authn/client/jwt.tmpl diff --git a/templates/v4.2/auth/client/mtls.tmpl b/templates/v4.2/authn/client/mtls.tmpl similarity index 100% rename from templates/v4.2/auth/client/mtls.tmpl rename to templates/v4.2/authn/client/mtls.tmpl diff --git a/templates/v4.2/auth/server/token.tmpl b/templates/v4.2/authn/server/token.tmpl similarity index 100% rename from templates/v4.2/auth/server/token.tmpl rename to templates/v4.2/authn/server/token.tmpl diff --git a/templates/v4.2/authz/client/jwt-authz-map.tmpl b/templates/v4.2/authz/client/jwt-authz-map.tmpl new file mode 100644 index 0000000..047da21 --- /dev/null +++ b/templates/v4.2/authz/client/jwt-authz-map.tmpl @@ -0,0 +1,14 @@ +{% for claim in authprofile.jwt.claims %} +auth_jwt_claim_set $authz_match_jwt_claim_{{ claim.name }}_{{ authprofile.name | replace(" ", "_") }} {{ claim.name }}; +{% endfor %} + +{% for claim in authprofile.jwt.claims %} +# JWT claim {{ claim.name }} validation for profile "{{ authprofile.name }}" +map $authz_match_jwt_claim_{{ claim.name }}_{{ authprofile.name | replace(" ", "_") }} $jwt_authz_claim_{{ claim.name }}_{{ authprofile.name | replace(" ", "_") }} { +{% for value in claim.value %} + "{{ value }}" 1; +{% endfor %} + default 0; +} + +{% endfor %} \ No newline at end of file diff --git a/templates/v4.2/authz/client/jwt.tmpl b/templates/v4.2/authz/client/jwt.tmpl new file mode 100644 index 0000000..f6b4d91 --- /dev/null +++ b/templates/v4.2/authz/client/jwt.tmpl @@ -0,0 +1,3 @@ +{% for claim in authprofile.jwt.claims %} +auth_jwt_require $jwt_authz_claim_{{ claim.name }}_{{ authprofile.name | replace(" ", "_") }} error={{ claim.errorcode }}; +{% endfor %} \ No newline at end of file diff --git a/templates/v4.2/http.tmpl b/templates/v4.2/http.tmpl index a3c7a9c..d5122a0 100644 --- a/templates/v4.2/http.tmpl +++ b/templates/v4.2/http.tmpl @@ -36,6 +36,14 @@ map {{ m.match }} {{ m.variable }} { {% endfor %} {% endif %} +{# --- Maps section for authorization --- #} +{%- if declaration.authorization -%} +variables_hash_bucket_size 512; +{% for authzprofile in declaration.authorization -%} +include "{{ ncgconfig.nms.authz_client_dir }}/{{ authzprofile.name | replace(" ", "_") }}.maps.conf"; +{% endfor -%} +{%- endif -%} + {# --- Snippets section --- #} {% if declaration.snippet and declaration.snippet.content %}{{ declaration.snippet.content | b64decode }}{% endif %} @@ -260,6 +268,17 @@ server { {% if s.log.error %}error_log {{ s.log.error }};{% endif %} + {# --- Client authentication at server {} level --- #} + {%- if s.authentication and s.authentication.client -%} + {%- for clientAuthProfile in s.authentication.client -%} + include "{{ ncgconfig.nms.auth_client_dir }}/{{ clientAuthProfile.profile | replace(" ", "_") }}.conf"; + {% endfor -%} + {%- endif -%} + + {# --- Client authorization at server {} level --- #} + {%- if s.authorization and s.authorization.profile -%} + include "{{ ncgconfig.nms.authz_client_dir }}/{{ s.authorization.profile | replace(" ", "_") }}.conf"; + {%- endif -%} {% filter indent(width=4) %} {% if s.snippet and s.snippet.content %}{{ s.snippet.content | b64decode }}{% endif %} @@ -368,13 +387,18 @@ server { {% if loc.rate_limit.httpcode %}limit_req_status {{ loc.rate_limit.httpcode }};{% endif %}{% endif %} {% endif %} - {# --- Client authentication --- #} + {# --- Client authentication at location level --- #} {%- if loc.authentication and loc.authentication.client -%} {%- for clientAuthProfile in loc.authentication.client -%} include "{{ ncgconfig.nms.auth_client_dir }}/{{ clientAuthProfile.profile | replace(" ", "_") }}.conf"; {% endfor -%} {%- endif -%} + {# --- Client authorization at location level --- #} + {%- if loc.authorization and loc.authorization.profile -%} + include "{{ ncgconfig.nms.authz_client_dir }}/{{ loc.authorization.profile | replace(" ", "_") }}.conf"; + {%- endif -%} + {# --- Location NGINX App Protect WAF --- #} {% if loc.app_protect -%}