From b4fc44d605cb7833afb13a5f6cdde229a09a4006 Mon Sep 17 00:00:00 2001 From: 65397 Date: Thu, 2 May 2024 17:55:06 +0100 Subject: [PATCH] 4.2.6 (#55) * 20240502-01 commit v4.0 removed * 20240502-02 commit FEATURES updated * 20240502-02 commit FEATURES updated * 20240502-02 commit FEATURES updated * 20240502-03 commit FEATURES updated * 20240502-04 commit FEATURES updated USAGE updated Postman collection updated * 20240502-04 commit FEATURES updated USAGE updated Postman collection updated --- FEATURES.md | 97 +- README.md | 6 +- USAGE-v4.0.md | 238 -- USAGE-v4.2.md | 137 +- ...NX Declarative API.postman_collection.json | 1936 +---------------- src/V4_0_CreateConfig.py | 781 ------- src/V4_0_NginxConfigDeclaration.py | 509 ----- src/V4_2_CreateConfig.py | 2 +- src/main.py | 52 - src/v4_0/APIGateway.py | 0 src/v4_0/DeclarationPatcher.py | 0 src/v4_0/DevPortal.py | 0 src/v4_0/GitOps.py | 38 - src/v4_0/MiscUtils.py | 0 src/v4_0/NAPUtils.py | 269 --- src/v4_0/NIMUtils.py | 30 - src/v4_0/OpenAPIParser.py | 71 - templates/v4.0/apigateway.tmpl | 0 templates/v4.0/auth/client/jwks.tmpl | 0 templates/v4.0/auth/client/jwt.tmpl | 0 templates/v4.0/auth/server/jwt.tmpl | 0 templates/v4.0/configmap.tmpl | 0 templates/v4.0/http.tmpl | 0 templates/v4.0/logformat.tmpl | 0 templates/v4.0/nginx-conf/mime.types | 0 templates/v4.0/nginx-conf/nginx.conf | 0 templates/v4.0/stream.tmpl | 0 27 files changed, 162 insertions(+), 4004 deletions(-) delete mode 100644 USAGE-v4.0.md delete mode 100644 src/V4_0_CreateConfig.py delete mode 100644 src/V4_0_NginxConfigDeclaration.py delete mode 100644 src/v4_0/APIGateway.py delete mode 100644 src/v4_0/DeclarationPatcher.py delete mode 100644 src/v4_0/DevPortal.py delete mode 100644 src/v4_0/GitOps.py delete mode 100644 src/v4_0/MiscUtils.py delete mode 100644 src/v4_0/NAPUtils.py delete mode 100644 src/v4_0/NIMUtils.py delete mode 100644 src/v4_0/OpenAPIParser.py delete mode 100644 templates/v4.0/apigateway.tmpl delete mode 100644 templates/v4.0/auth/client/jwks.tmpl delete mode 100644 templates/v4.0/auth/client/jwt.tmpl delete mode 100644 templates/v4.0/auth/server/jwt.tmpl delete mode 100644 templates/v4.0/configmap.tmpl delete mode 100644 templates/v4.0/http.tmpl delete mode 100644 templates/v4.0/logformat.tmpl delete mode 100644 templates/v4.0/nginx-conf/mime.types delete mode 100644 templates/v4.0/nginx-conf/nginx.conf delete mode 100644 templates/v4.0/stream.tmpl diff --git a/FEATURES.md b/FEATURES.md index 1a87a6c..444b48a 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -2,43 +2,44 @@ ### NGINX `http` and `stream` servers -| Feature | API v4.0 | API v4.1 | API v4.2 | Notes | -|----------------------------|----------|----------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Upstreams | CRUD | CRUD | CRUD |
  • Snippets supported: static and from source of truth
  • | -| HTTP servers | CRUD | CRUD | CRUD |
  • Snippets supported (`http`, `servers`, `locations`): static and from source of truth
  • | -| TCP/UDP servers | CRUD | CRUD | CRUD |
  • Snippets supported (`streams`, `servers`): static and from source of truth
  • | -| TLS | CRUD | CRUD | CRUD |
  • Certificates and keys can be dynamically fetched from source of truth
  • | -| Client authentication | X | X | X | See [client authentication profiles](#Client-authentication-profiles) | -| Server authentication | X | X | X | See [server authentication profiles](#Upstream-and-Source-of-truth-authentication-profiles) | -| Rate limiting | X | X | X | | -| Active healthchecks | X | X | X | | -| Cookie-based stickiness | X | X | X | | -| HTTP headers manipulation | | | X |
  • To server: set, delete
  • To client: add, delete, replace
  • | -| Maps | X | X | X | | -| NGINX Plus REST API access | X | X | X | | -| NGINX App Protect WAF | X | X | X |
  • Per-policy CRUD at `server` and `location` level
  • Support for dataplane-based bundle compilation
  • Security policies can be fetched from source of truth
  • | +| Feature | API v4.1 | API v4.2 | Notes | +|-----------------------------|-----------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Upstreams | CRUD | CRUD |
  • Snippets supported: static and from source of truth
  • | +| HTTP servers | CRUD | CRUD |
  • Snippets supported (`http`, `servers`, `locations`): static and from source of truth
  • | +| TCP/UDP servers | CRUD | CRUD |
  • Snippets supported (`streams`, `servers`): static and from source of truth
  • | +| TLS | CRUD | CRUD |
  • Certificates and keys can be dynamically fetched from source of truth
  • | +| Client authentication | X | X | See [client authentication profiles](#Client-authentication-profiles) | +| Server authentication | X | X | See [server authentication profiles](#Upstream-and-Source-of-truth-authentication-profiles) | +| Rate limiting | X | X | | +| Active healthchecks | X | X | | +| Cookie-based stickiness | X | X | | +| HTTP headers manipulation | | X |
  • To server: set, delete
  • To client: add, delete, replace
  • | +| Maps | X | X | | +| NGINX Plus REST API access | X | X | | +| NGINX App Protect WAF | X | X |
  • Per-policy CRUD at `server` and `location` level
  • Support for dataplane-based bundle compilation
  • Security policies can be fetched from source of truth
  • | ### API Gateway -| Feature | API v4.0 | API v4.1 | API v4.2 | Notes | -|----------------------------------------------|----------|----------|----------|-------------------------------------------------------------------------------------------| -| Configuration generation from OpenAPI schema | X | X | X | | -| HTTP methods enforcement | X | X | X | | -| per-URI rate limiting | X | X | X | | -| per-URI client authentication | X | X | X |
  • Static JWT key
  • JWT key fetched from URL
  • Bearer token
  • | +| Feature | API v4.1 | API v4.2 | Notes | +|----------------------------------------------|----------|----------|-------------------------------------------------------------------------------| +| Configuration generation from OpenAPI schema | X | X | | +| HTTP methods enforcement | X | X | | +| per-URI rate limiting | X | X | | +| per-URI client authentication | X | X |
  • Static JWT key
  • JWT key fetched from URL
  • Bearer token
  • | +| per-URI client authorization | X | X |
  • JWT claims
  • | ### API Gateway - Developer Portal -| Feature | API v4.0 | API v4.1 | API v4.2 | Notes | -|-------------------------------------------------|----------|----------|----------|---------------------------| -| Developer Portal generation from OpenAPI schema | X | X | X |
  • Based on Redocly
  • | +| Feature | API v4.1 | API v4.2 | Notes | +|-------------------------------------------------|----------|----------|---------------------------| +| Developer Portal generation from OpenAPI schema | X | X |
  • Based on Redocly
  • | ### Client authentication -| Type | Description | API v4.0 | API v4.1 | API v4.2 | Notes | -|------|----------------------|----------|---------|----------|-------------------------------------| -| jwt | Java Web Token (JWT) | | X | X | | -| mtls | Mutual TLS | X | X | X |
  • Supported for HTTP servers
  • | +| Type | Description | API v4.1 | API v4.2 | Notes | +|------|----------------------|----------|----------|-------------------------------------| +| jwt | Java Web Token (JWT) | X | X | | +| mtls | Mutual TLS | X | X |
  • Supported for HTTP servers
  • | #### Examples @@ -84,9 +85,9 @@ 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
  • | +| Type | Description | 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 @@ -114,11 +115,11 @@ Client-side authorization profiles to be defined under `.declaration.http.author ### Upstream and Source of truth authentication -| Type | Description | API v4.0 | API v4.1 | API v4.2 | Notes | -|--------------|----------------------------------------------|----------|----------|----------|----------------------------------------------------------------------------------------| -| Bearer token | Authentication token as Authorization Bearer | | X | X | `Bearer` Authorization header is injected in requests to upstreams and source of truth | -| Basic Auth | Authentication token as Authorization Basic | | | X | `Basic` Authorization header is injected in requests to upstreams and source of truth | -| HTTP header | Authentication token in custom HTTP header | | X | X | HTTP header is injected in requests to upstreams and source of truth | +| Type | Description | API v4.1 | API v4.2 | Notes | +|--------------|----------------------------------------------|----------|----------|----------------------------------------------------------------------------------------| +| Bearer token | Authentication token as Authorization Bearer | X | X | `Bearer` Authorization header is injected in requests to upstreams and source of truth | +| Basic Auth | Authentication token as Authorization Basic | | X | `Basic` Authorization header is injected in requests to upstreams and source of truth | +| HTTP header | Authentication token in custom HTTP header | X | X | HTTP header is injected in requests to upstreams and source of truth | #### Examples @@ -167,10 +168,10 @@ Server-side authentication profiles to be defined under `.declaration.http.authe ### HTTP Headers manipulation -| Type | API v4.0 | API v4.1 | API v4.2 | Notes | -|-----------------------------|----------|----------|----------|------------------------------------------------------------------------------------------------------------------------------| -| Request (client to server) | | | X |
  • `set` - new header injection
  • `delete` - client header removal
  • | -| Response (server to client) | | | X |
  • `add` - new header injection
  • `delete` - server header removal
  • `replace` - server header replacement
  • | +| Type | API v4.1 | API v4.2 | Notes | +|-----------------------------|----------|----------|------------------------------------------------------------------------------------------------------------------------------| +| Request (client to server) | | X |
  • `set` - new header injection
  • `delete` - client header removal
  • | +| Response (server to client) | | X |
  • `add` - new header injection
  • `delete` - server header removal
  • `replace` - server header replacement
  • | #### Examples @@ -216,14 +217,14 @@ To be defined under `.declaration.http.servers[].headers` and/or `.declaration.h ### NGINX Javascript -| Hook type | API v4.0 | API v4.1 | API v4.2 | Notes | -|-------------------|----------|----------|----------|------------------------------------------------------------------------------------------------------------------------------| -| js_body_filter | | | X | Available in
  • `declaration.http.server[].location[]`
  • | -| js_content | | | X | Available in
  • `declaration.http.server[].location[]`
  • | -| js_header_filter | | | X | Available in
  • `declaration.http.server[].location[]`
  • | -| js_periodic | | | X | Available in
  • `declaration.http.server[].location[]`
  • | -| js_preload_object | | | X | Available in
  • `.declaration.http`
  • `declaration.http.server[]`
  • `declaration.http.server[].location[]`
  • | -| js_set | | | X | Available in
  • `.declaration.http`
  • `declaration.http.server[]`
  • `declaration.http.server[].location[]`
  • | +| Hook type | API v4.1 | API v4.2 | Notes | +|-------------------|----------|----------|------------------------------------------------------------------------------------------------------------------------------| +| js_body_filter | | X | Available in
  • `declaration.http.server[].location[]`
  • | +| js_content | | X | Available in
  • `declaration.http.server[].location[]`
  • | +| js_header_filter | | X | Available in
  • `declaration.http.server[].location[]`
  • | +| js_periodic | | X | Available in
  • `declaration.http.server[].location[]`
  • | +| js_preload_object | | X | Available in
  • `.declaration.http`
  • `declaration.http.server[]`
  • `declaration.http.server[].location[]`
  • | +| js_set | | X | Available in
  • `.declaration.http`
  • `declaration.http.server[]`
  • `declaration.http.server[].location[]`
  • | Note: `njs` profiles can be included in base64-encoded format under `.declaration.http.njs[]` of fetched from an external source of truth For detailed examples see the [Postman collection](/contrib/postman) diff --git a/README.md b/README.md index 19ba313..a4e7d49 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,11 @@ Use cases include: - Swagger / OpenAPI schemas - NGINX Javascript files -## Requirements +## Supported releases - NGINX Instance Manager 2.14+ -- NGINX Plus R30 or newer +- NGINX Plus R30+ +- NGINX App Protect WAF 4.8+ ## Architecture @@ -145,7 +146,6 @@ Usage details and JSON schema are available here: - [API v4.2](/USAGE-v4.2.md) - latest - [API v4.1](/USAGE-v4.1.md) -- [API v4.0](/USAGE-v4.0.md) A sample Postman collection and usage instructions can be found [here](/contrib/postman) diff --git a/USAGE-v4.0.md b/USAGE-v4.0.md deleted file mode 100644 index d700134..0000000 --- a/USAGE-v4.0.md +++ /dev/null @@ -1,238 +0,0 @@ -# Usage for API v4.0 - -Version 4.0 API requires: - -- NGINX Instance Manager 2.14+ -- NGINX Plus R30+ - -If NGINX App Protect declarations are used: -- NGINX App Protect Policy Compiler 4.2.0+ -- NGINX Plus instances running App Protect WAF 4.2.0+ using `precompiled_publication: true` in `/etc/nginx-agent/nginx-agent.conf` - -The JSON schema is self explanatory. See also the [sample Postman collection](/contrib/postman) - -- `.output.type` defines how NGINX configuration will be returned: - - *plaintext* - plaintext format - - *json* - JSON-wrapped, base64-encoded - - *configmap* - Kubernetes Configmap in YAML format. - - `.output.configmap.name` must be set to the ConfigMap name - - `.output.configmap.filename` must be set to the NGINX configuration filename - - `.output.configmap.namespace` the optional namespace for the ConfigMap - - *http* - NGINX configuration is POSTed to custom url - - `.output.http.url` the URL to POST the configuration to - - *nms* - NGINX configuration is published as a Staged Config to NGINX Instance Manager - - `.output.nms.url` the NGINX Instance Manager URL - - `.output.nms.username` the NGINX Instance Manager authentication username - - `.output.nms.password` the NGINX Instance Manager authentication password - - `.output.nms.instancegroup` the NGINX Instance Manager instance group to publish the configuration to - - `.output.nms.synctime` **optional**, used for GitOps autosync. When specified and the declaration includes HTTP(S) references to NGINX App Protect policies, TLS certificates/keys/chains, the HTTP(S) endpoints will be checked every `synctime` seconds and if external contents have changed, the updated configuration will automatically be published to NGINX Instance Manager - - `.output.nms.modules` an optional array of NGINX module names (ie. 'ngx_http_app_protect_module', 'ngx_http_js_module','ngx_stream_js_module') - - `.output.nms.certificates` an optional array of TLS certificates/keys/chains to be published - - `.output.nms.certificates[].type` the item type ('certificate', 'key', 'chain') - - `.output.nms.certificates[].name` the certificate/key/chain name with no path/extension (ie. 'test-application') - - `.output.nms.certificates[].contents` the content: this can be either base64-encoded or be a HTTP(S) URL that will be fetched dynamically from a source of truth - - `.output.nms.policies[]` an optional array of NGINX App Protect security policies - - `.output.nms.policies[].type` the policy type ('app_protect') - - `.output.nms.policies[].name` the policy name (ie. 'prod-policy') - - `.output.nms.policies[].active_tag` the policy tag to enable among all available versions (ie. 'v1') - - `.output.nms.policies[].versions[]` array with all available policy versions - - `.output.nms.policies[].versions[].tag` the policy version's tag name - - `.output.nms.policies[].versions[].displayName` the policy version's display name - - `.output.nms.policies[].versions[].description` the policy version's description - - `.output.nms.policies[].versions[].contents` this can be either base64-encoded or be a HTTP(S) URL that will be fetched dynamically from a source of truth -- `.declaration` describes the NGINX configuration to be created. - -### Locations ### - -Locations `.declaration.http.servers[].locations[].uri` match modifiers in `.declaration.http.servers[].locations[].urimatch` can be: - -- *prefix* - prefix URI matching -- *exact* - exact URI matching -- *regex* - case sensitive regex matching -- *iregex* - case insensitive regex matching -- *best* - case sensitive regex matching that halts any other location matching once a match is made - -### API Gateway ### - -Swagger files and OpenAPI schemas can be used to automatically configure NGINX as an API Gateway. Developer portal creation is supported through [Redocly](https://redocly.com/) - -Declaration path `.declaration.http.servers[].locations[].apigateway` defines the API Gateway configuration: - -- `openapi_schema` - the base64-encoded schema, or the schema URL. YAML and JSON are supported -- `api_gateway.enabled` - enable/disable API Gateway provisioning -- `api_gateway.strip_uri` - removes the `.declaration.http.servers[].locations[].uri` part of the URI before forwarding requests to the upstream -- `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` -- `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` - -A sample API Gateway declaration to publish the `https://petstore.swagger.io` REST API and enforce: - -- REST API endpoint URIs -- HTTP Methods -- Rate limiting on `/user/login` and `/user/logout` endpoints - -is: - -```commandline -{ - "output": { - "type": "nms", - "nms": { - "url": "{{nim_host}}", - "username": "{{nim_username}}", - "password": "{{nim_password}}", - "instancegroup": "{{nim_instancegroup}}", - "synctime": 0, - "modules": [ - "ngx_http_js_module", - "ngx_stream_js_module" - ] - } - }, - "declaration": { - "http": { - "servers": [ - { - "name": "Petstore API", - "names": [ - "apigw.nginx.lab" - ], - "resolver": "8.8.8.8", - "listen": { - "address": "80" - }, - "log": { - "access": "/var/log/nginx/apigw.nginx.lab-access_log", - "error": "/var/log/nginx/apigw.nginx.lab-error_log" - }, - "locations": [ - { - "uri": "/petstore", - "urimatch": "prefix", - "apigateway": { - "openapi_schema": "https://petstore.swagger.io/v2/swagger.json", - "api_gateway": { - "enabled": true, - "strip_uri": true, - "server_url": "https://petstore.swagger.io/v2" - }, - "developer_portal": { - "enabled": false, - "uri": "/petstore-devportal.html" - }, - "authentication": { - "client": [ - { - "profile": "Petstore JWT Authentication" - } - ], - "enforceOnPaths": true, - "paths": [ - "/user/login", - "/user/logout" - ] - }, - "rate_limit": [ - { - "profile": "petstore_ratelimit", - "httpcode": 429, - "burst": 0, - "delay": 0, - "enforceOnPaths": true, - "paths": [ - "/user/login", - "/user/logout" - ] - } - ] - }, - "log": { - "access": "/var/log/nginx/petstore-access_log", - "error": "/var/log/nginx/petstore-error_log" - } - } - ] - } - ], - "rate_limit": [ - { - "name": "petstore_ratelimit", - "key": "$binary_remote_addr", - "size": "10m", - "rate": "2r/s" - } - ], - "authentication": { - "client": [ - { - "name": "Petstore JWT Authentication", - "type": "jwt", - "jwt": { - "realm": "Petstore Authentication", - "key": "{\"keys\": [{\"k\":\"ZmFudGFzdGljand0\",\"kty\":\"oct\",\"kid\":\"0001\"}]}", - "cachetime": 5 - } - } - ] - } - } - } -} -``` - -It can be tested using: - -``` -curl -iH "Host: apigw.nginx.lab" http:///petstore/store/inventory -``` - -Authentication failed: - -``` -curl -i http://apigw.nginx.lab/petstore/user/login -``` - -Authentication Succeeded: - -``` -curl -i http://apigw.nginx.lab/petstore/user/login -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDI0ODEzNjcsImV4cCI6MTcwMjQ4MTM2OH0.eyJuYW1lIjoiQm9iIERldk9wcyIsInN1YiI6IkpXVCBzdWIgY2xhaW0iLCJpc3MiOiJKV1QgaXNzIGNsYWltIiwicm9sZXMiOlsiZGV2b3BzIl19.SKA_7MszAypMEtX5NDQ0TcUbVYx_Wt0hrtmuyTmrVKU" -``` - -The API Developer portal can be accessed at: - - http:///petstore/petstore-devportal.html - -### Maps ### - -Map entries `.declaration.maps[].entries.keymatch` can be: - -- *exact* - exact variable matching -- *regex* - case sensitive regex matching -- *iregex* - case insensitive regex matching - -### Snippets ### - -Snippets for http, upstream, server and location can be specified as: -- base64-encoded content -- HTTP(S) URL of a source of truth to fetch snippet content from. Content on the source of truth must be plaintext, it will be automatically base64-encoded - -### Methods ### - -- `POST /v4.0/config/` - Publish a new declaration -- `PATCH /v4.0/config/{config_uid}` - Update an existing declaration - - Per-HTTP server CRUD - - Per-HTTP upstream CRUD - - Per-Stream server CRUD - - Per-Stream upstream CRUD - - Per-NGINX App Protect WAF policy CRUD -- `GET /v4.0/config/{config_uid}` - Retrieve an existing declaration -- `DELETE /v4.0/config/{config_uid}` - Delete an existing declaration - -### Usage Examples ### - -A sample Postman collection is available [here](/contrib/postman) \ No newline at end of file diff --git a/USAGE-v4.2.md b/USAGE-v4.2.md index 69de62a..85c5d3b 100644 --- a/USAGE-v4.2.md +++ b/USAGE-v4.2.md @@ -130,8 +130,9 @@ A sample API Gateway declaration to publish the `https://petstore.swagger.io` RE - REST API endpoint URIs - HTTP Methods -- Rate limiting on `/user/login` and `/user/logout` -- JWT authentication on `/user/login` and `/usr/logout` +- Rate limiting on `/user/login`, `/usr/logout` and `/pet/{petId}/uploadImage` +- JWT authentication on `/user/login`, `/usr/logout` and `/pet/{petId}/uploadImage` +- JWT claim-based authorization on `/user/login`, `/usr/logout` and `/pet/{petId}/uploadImage` is: @@ -144,7 +145,51 @@ is: "username": "{{nim_username}}", "password": "{{nim_password}}", "instancegroup": "{{nim_instancegroup}}", - "synctime": 0 + "synctime": 0, + "modules": [ + "ngx_http_app_protect_module" + ], + "certificates": [ + { + "type": "certificate", + "name": "test_cert", + "contents": { + "content": "{{github_gitops_root}}/v4.2/testcert.crt" + } + }, + { + "type": "key", + "name": "test_key", + "contents": { + "content": "{{github_gitops_root}}/v4.2/testcert.key" + } + } + ], + "policies": [ + { + "type": "app_protect", + "name": "production-policy", + "active_tag": "xss-blocked", + "versions": [ + { + "tag": "xss-blocked", + "displayName": "Production Policy - XSS blocked", + "description": "This is a production-ready policy - XSS blocked", + "contents": { + "content": "{{github_gitops_root}}/v4.2/nap-policy-xss-blocked-bot-allowed.json" + } + }, + { + "tag": "xss-allowed", + "displayName": "Production Policy - XSS allowed", + "description": "This is a production-ready policy - XSS allowed", + "contents": { + "content": "{{github_gitops_root}}/v4.2/nap-policy-xss-allowed.json" + } + } + ] + } + ] } }, "declaration": { @@ -157,7 +202,17 @@ is: ], "resolver": "8.8.8.8", "listen": { - "address": "80" + "address": "0.0.0.0:443", + "http2": true, + "tls": { + "certificate": "test_cert", + "key": "test_key", + "ciphers": "DEFAULT", + "protocols": [ + "TLSv1.2", + "TLSv1.3" + ] + } }, "log": { "access": "/var/log/nginx/apigw.nginx.lab-access_log", @@ -166,15 +221,10 @@ is: "locations": [ { "uri": "/petstore", - "urimatch": "prefix", + "urimatch": "prefix", "apigateway": { - "openapi_schema": { - "content": "http://petstore.swagger.io/v2/swagger.json", - "authentication": [ - { - "profile": "Source of truth authentication profile using HTTP header token authentication" - } - ] + "openapi_schema": { + "content": "http://petstore.swagger.io/v2/swagger.json" }, "api_gateway": { "enabled": true, @@ -182,7 +232,7 @@ is: "server_url": "https://petstore.swagger.io/v2" }, "developer_portal": { - "enabled": false, + "enabled": true, "uri": "/petstore-devportal.html" }, "authentication": { @@ -194,7 +244,8 @@ is: "enforceOnPaths": true, "paths": [ "/user/login", - "/user/logout" + "/user/logout", + "/pet/{petId}/uploadImage" ] }, "authorization": [ @@ -203,7 +254,8 @@ is: "enforceOnPaths": true, "paths": [ "/user/login", - "/user/logout" + "/user/logout", + "/pet/{petId}/uploadImage" ] } ], @@ -216,7 +268,8 @@ is: "enforceOnPaths": true, "paths": [ "/user/login", - "/user/logout" + "/user/logout", + "/pet/{petId}/uploadImage" ] } ] @@ -224,6 +277,15 @@ is: "log": { "access": "/var/log/nginx/petstore-access_log", "error": "/var/log/nginx/petstore-error_log" + }, + "app_protect": { + "enabled": true, + "policy": "production-policy", + "log": { + "profile_name": "secops_dashboard", + "enabled": true, + "destination": "127.0.0.1:514" + } } } ] @@ -248,17 +310,6 @@ is: "cachetime": 5 } } - ], - "server": [ - { - "name": "Source of truth authentication profile using HTTP header token authentication", - "type": "token", - "token": { - "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDI0ODEzNjcsImV4cCI6MTcwMjQ4MTM2OH0.eyJuYW1lIjoiQm9iIERldk9wcyIsInN1YiI6IkpXVCBzdWIgY2xhaW0iLCJpc3MiOiJKV1QgaXNzIGNsYWltIiwicm9sZXMiOlsiZGV2b3BzIl19.SKA_7MszAypMEtX5NDQ0TcUbVYx_Wt0hrtmuyTmrVKU", - "type": "header", - "location": "X-AUTH-TOKEN" - } - } ] }, "authorization": [ @@ -271,7 +322,8 @@ is: "name": "roles", "value": [ "~(devops)" - ] + ], + "errorcode": 403 } ] } @@ -294,12 +346,39 @@ Authentication failed: curl -i http://apigw.nginx.lab/petstore/user/login ``` -Authentication Succeeded: +Authentication successful: ``` curl -i http://apigw.nginx.lab/petstore/user/login -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDI0ODEzNjcsImV4cCI6MTcwMjQ4MTM2OH0.eyJuYW1lIjoiQm9iIERldk9wcyIsInN1YiI6IkpXVCBzdWIgY2xhaW0iLCJpc3MiOiJKV1QgaXNzIGNsYWltIiwicm9sZXMiOlsiZGV2b3BzIl19.SKA_7MszAypMEtX5NDQ0TcUbVYx_Wt0hrtmuyTmrVKU" ``` +Authorization failed (based on JWT `role` claim): + +``` +curl -w '\n' -ki https://apigw.nginx.lab/petstore/user/login -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDk3NjQ3NTMsImV4cCI6MTcwOTc2NDc1NH0.eyJuYW1lIjoiQWxpY2UgR3Vlc3QiLCJzdWIiOiJKV1Qgc3ViIGNsYWltIiwiaXNzIjoiSldUIGlzcyBjbGFpbSIsInJvbGVzIjpbImd1ZXN0Il19.jFJDq-33irz7uFxdI8c8fIb5TwTAU5BlemmIFVALUAE" +``` +``` +curl -w '\n' -ki https://apigw.nginx.lab/petstore/pet/1/uploadImage -X POST \ + -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDk3NjQ3NTMsImV4cCI6MTcwOTc2NDc1NH0.eyJuYW1lIjoiQWxpY2UgR3Vlc3QiLCJzdWIiOiJKV1Qgc3ViIGNsYWltIiwiaXNzIjoiSldUIGlzcyBjbGFpbSIsInJvbGVzIjpbImd1ZXN0Il19.jFJDq-33irz7uFxdI8c8fIb5TwTAU5BlemmIFVALUAE" \ + -H 'accept: application/json' \ + -H 'Content-Type: multipart/form-data' \ + -F 'file=iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TS0UqChYRcchQXbSLijjWKhShQqgVWnUwuX5Ck4YkxcVRcC04+LFYdXBx1tXBVRAEP0CcHZwUXaTE/yWFFjEeHPfj3b3H3TtAaFSYanbFAFWzjFQiLmayq2LwFQEI6Mc4BmVm6nOSlITn+LqHj693UZ7lfe7P0ZvLmwzwicQxphsW8QbxzKalc94nDrOSnCM+J54w6ILEj1xXXH7jXHRY4JlhI52aJw4Ti8UOVjqYlQyVeJo4klM1yhcyLuc4b3FWKzXWuid/YSivrSxzneYIEljEEiSIUFBDGRVYiNKqkWIiRftxD/+w45fIpZCrDEaOBVShQnb84H/wu1uzMDXpJoXiQODFtj9GgeAu0Kzb9vexbTdPAP8zcKW1/dUGMPtJer2tRY6Avm3g4rqtKXvA5Q4w9KTLhuxIfppCoQC8n9E3ZYGBW6Bnze2ttY/TByBNXSVvgINDYKxI2ese7+7u7O3fM63+fgB5bXKpzZcBIwAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+gFAhArKAvJglcAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC;type=image/png' +``` + +Authorization successful (based on JWT `role` claim): + +``` +curl -w '\n' -ki https://apigw.nginx.lab/petstore/user/login -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDI0ODEzNjcsImV4cCI6MTcwMjQ4MTM2OH0.eyJuYW1lIjoiQm9iIERldk9wcyIsInN1YiI6IkpXVCBzdWIgY2xhaW0iLCJpc3MiOiJKV1QgaXNzIGNsYWltIiwicm9sZXMiOlsiZGV2b3BzIl19.SKA_7MszAypMEtX5NDQ0TcUbVYx_Wt0hrtmuyTmrVKU" +``` + +``` +curl -w '\n' -ki https://apigw.nginx.lab/petstore/pet/1/uploadImage -X POST \ + -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJpYXQiOjE3MDI0ODEzNjcsImV4cCI6MTcwMjQ4MTM2OH0.eyJuYW1lIjoiQm9iIERldk9wcyIsInN1YiI6IkpXVCBzdWIgY2xhaW0iLCJpc3MiOiJKV1QgaXNzIGNsYWltIiwicm9sZXMiOlsiZGV2b3BzIl19.SKA_7MszAypMEtX5NDQ0TcUbVYx_Wt0hrtmuyTmrVKU" \ + -H 'accept: application/json' \ + -H 'Content-Type: multipart/form-data' \ + -F 'file=iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TS0UqChYRcchQXbSLijjWKhShQqgVWnUwuX5Ck4YkxcVRcC04+LFYdXBx1tXBVRAEP0CcHZwUXaTE/yWFFjEeHPfj3b3H3TtAaFSYanbFAFWzjFQiLmayq2LwFQEI6Mc4BmVm6nOSlITn+LqHj693UZ7lfe7P0ZvLmwzwicQxphsW8QbxzKalc94nDrOSnCM+J54w6ILEj1xXXH7jXHRY4JlhI52aJw4Ti8UOVjqYlQyVeJo4klM1yhcyLuc4b3FWKzXWuid/YSivrSxzneYIEljEEiSIUFBDGRVYiNKqkWIiRftxD/+w45fIpZCrDEaOBVShQnb84H/wu1uzMDXpJoXiQODFtj9GgeAu0Kzb9vexbTdPAP8zcKW1/dUGMPtJer2tRY6Avm3g4rqtKXvA5Q4w9KTLhuxIfppCoQC8n9E3ZYGBW6Bnze2ttY/TByBNXSVvgINDYKxI2ese7+7u7O3fM63+fgB5bXKpzZcBIwAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+gFAhArKAvJglcAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC;type=image/png' +``` + The API Developer portal can be accessed at: http:///petstore/petstore-devportal.html diff --git a/contrib/postman/NGINX Declarative API.postman_collection.json b/contrib/postman/NGINX Declarative API.postman_collection.json index c6ef418..0cca422 100644 --- a/contrib/postman/NGINX Declarative API.postman_collection.json +++ b/contrib/postman/NGINX Declarative API.postman_collection.json @@ -8,1940 +8,6 @@ "_collection_link": "https://orange-rocket-1353.postman.co/workspace/NGINX-Declarative-API~8ba6e9c1-a04b-4484-8193-bbb142560553/collection/1667416-f786ef84-f8bf-4f38-a297-e6f3d3953c84?action=share&source=collection_link&creator=1667416" }, "item": [ - { - "name": "v4.0", - "item": [ - { - "name": "Configuration generation", - "item": [ - { - "name": "Basic - LB", - "item": [ - { - "name": "Basic - LB - plaintext output", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"plaintext\"\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"sample L4 service\",\n \"listen\": {\n \"address\": \"10053\",\n \"protocol\": \"udp\"\n },\n \"upstream\": \"l4_upstream\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"l4_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:53\"\n },\n {\n \"server\": \"10.0.0.2:53\"\n }\n ]\n }\n ]\n },\n \"http\": {\n \"servers\": [\n {\n \"name\": \"A sample HTTP service\",\n \"names\": [\n \"server_8080.nginx.lab\",\n \"server_8081.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_log\",\n \"error\": \"/var/log/nginx/error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\",\n \"health_check\": {\n \"enabled\": true\n }\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\"\n },\n {\n \"server\": \"10.0.0.2:80\"\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": "Basic - LB - json b64 encoded output", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"json\"\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"sample L4 service\",\n \"listen\": {\n \"address\": \"10053\",\n \"protocol\": \"udp\"\n },\n \"upstream\": \"l4_upstream\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"l4_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:53\"\n },\n {\n \"server\": \"10.0.0.2:53\"\n }\n ]\n }\n ]\n },\n \"http\": {\n \"servers\": [\n {\n \"name\": \"A sample HTTP service\",\n \"names\": [\n \"server_8080.nginx.lab\",\n \"server_8081.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_log\",\n \"error\": \"/var/log/nginx/error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\",\n \"health_check\": {\n \"enabled\": true\n }\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\"\n },\n {\n \"server\": \"10.0.0.2:80\"\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": "Basic - LB - ConfigMap output", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"configmap\",\n \"configmap\": {\n \"name\": \"nginx.test\",\n \"filename\": \"testservice.conf\",\n \"namespace\": \"test-namespace\"\n }\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"sample L4 service\",\n \"listen\": {\n \"address\": \"10053\",\n \"protocol\": \"udp\"\n },\n \"upstream\": \"l4_upstream\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"l4_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:53\"\n },\n {\n \"server\": \"10.0.0.2:53\"\n }\n ]\n }\n ]\n },\n \"http\": {\n \"servers\": [\n {\n \"name\": \"A sample HTTP service\",\n \"names\": [\n \"server_8080.nginx.lab\",\n \"server_8081.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_log\",\n \"error\": \"/var/log/nginx/error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\",\n \"health_check\": {\n \"enabled\": true\n }\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\"\n },\n {\n \"server\": \"10.0.0.2:80\"\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": "Basic - LB - HTTP output", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"http\",\n \"http\": {\n \"url\": \"http://192.168.2.19:8080/path/service\"\n }\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"sample L4 service\",\n \"listen\": {\n \"address\": \"10053\",\n \"protocol\": \"udp\"\n },\n \"upstream\": \"l4_upstream\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"l4_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:53\"\n },\n {\n \"server\": \"10.0.0.2:53\"\n }\n ]\n }\n ]\n },\n \"http\": {\n \"servers\": [\n {\n \"name\": \"A sample HTTP service\",\n \"names\": [\n \"server_8080.nginx.lab\",\n \"server_8081.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_log\",\n \"error\": \"/var/log/nginx/error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\",\n \"health_check\": {\n \"enabled\": true\n }\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\"\n },\n {\n \"server\": \"10.0.0.2:80\"\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": "Advanced", - "item": [ - { - "name": "Advanced LB - plaintext output", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"plaintext\"\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"sample_layer4_service\",\n \"listen\": {\n \"address\": \"10053\",\n \"protocol\": \"tcp\",\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"upstream\": \"l4_upstream\",\n \"snippet\": \"IyBUaGlzIGlzIGEgbDQgdXBzdHJlYW0gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"l4_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:53\"\n },\n {\n \"server\": \"10.0.0.2:53\"\n }\n ]\n }\n ]\n },\n \"http\": {\n \"servers\": [\n {\n \"name\": \"HTTP test application\",\n \"names\": [\n \"server_8080.nginx.lab\",\n \"server_8081.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_log\",\n \"error\": \"/var/log/nginx/error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/test\",\n \"urimatch\": \"exact\",\n \"upstream\": \"http://test_upstream\",\n \"health_check\": {\n \"enabled\": true,\n \"uri\": \"/healthcheck\",\n \"interval\": 5,\n \"fails\": 3,\n \"passes\": 2\n },\n \"rate_limit\": {\n \"profile\": \"test_ratelimit\",\n \"httpcode\": 429,\n \"burst\": 10,\n \"delay\": 3\n },\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"test_policy\",\n \"log\": {\n \"profile_name\": \"log_illegal\",\n \"enabled\": true,\n \"destination\": \"192.168.1.5:514\"\n }\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgbG9jYXRpb24gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"test_policy\",\n \"log\": {\n \"profile_name\": \"log_blocked\",\n \"enabled\": true,\n \"destination\": \"192.168.1.5:514\"\n }\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgc2VydmVyIHNuaXBwZXQgY29tbWVudAo=\"\n },\n {\n \"name\": \"another HTTP test application\",\n \"names\": [\n \"server_443\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"upstream\": \"http://test_upstream\"\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\",\n \"weight\": 5,\n \"max_fails\": 2,\n \"fail_timeout\": \"30s\",\n \"max_conns\": 3,\n \"slow_start\": \"30s\"\n },\n {\n \"server\": \"10.0.0.2:80\",\n \"backup\": true\n }\n ],\n \"sticky\": {\n \"cookie\": \"cookie_name\",\n \"expires\": \"1h\",\n \"domain\": \".testserver\",\n \"path\": \"/\"\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgdXBzdHJlYW0gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"rate_limit\": [\n {\n \"name\": \"test_ratelimit\",\n \"key\": \"$binary_remote_addr\",\n \"size\": \"10m\",\n \"rate\": \"1r/s\"\n }\n ],\n \"maps\": [\n {\n \"match\": \"$host$request_uri\",\n \"variable\": \"$backend\",\n \"entries\": [\n {\n \"key\": \"www.test.lab/app1/\",\n \"keymatch\": \"iregex\",\n \"value\": \"upstream_1\"\n },\n {\n \"key\": \"(.*).test.lab/app2/\",\n \"keymatch\": \"regex\",\n \"value\": \"upstream_2\"\n }\n ]\n }\n ],\n \"nginx_plus_api\": {\n \"write\": true,\n \"listen\": \"127.0.0.1:8080\",\n \"allow_acl\": \"0.0.0.0/0\"\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgSFRUUCBzbmlwcGV0IGNvbW1lbnQK\"\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": "Advanced LB - json b64 encoded output", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"json\"\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"sample_layer4_service\",\n \"listen\": {\n \"address\": \"10053\",\n \"protocol\": \"tcp\",\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"upstream\": \"l4_upstream\",\n \"snippet\": \"IyBUaGlzIGlzIGEgbDQgdXBzdHJlYW0gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"l4_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:53\"\n },\n {\n \"server\": \"10.0.0.2:53\"\n }\n ]\n }\n ]\n },\n \"http\": {\n \"servers\": [\n {\n \"name\": \"HTTP test application\",\n \"names\": [\n \"server_8080.nginx.lab\",\n \"server_8081.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_log\",\n \"error\": \"/var/log/nginx/error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/test\",\n \"urimatch\": \"exact\",\n \"upstream\": \"http://test_upstream\",\n \"health_check\": {\n \"enabled\": true,\n \"uri\": \"/healthcheck\",\n \"interval\": 5,\n \"fails\": 3,\n \"passes\": 2\n },\n \"rate_limit\": {\n \"profile\": \"test_ratelimit\",\n \"httpcode\": 429,\n \"burst\": 10,\n \"delay\": 3\n },\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"test_policy\",\n \"log\": {\n \"profile_name\": \"log_illegal\",\n \"enabled\": true,\n \"destination\": \"192.168.1.5:514\"\n }\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgbG9jYXRpb24gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"test_policy\",\n \"log\": {\n \"profile_name\": \"log_blocked\",\n \"enabled\": true,\n \"destination\": \"192.168.1.5:514\"\n }\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgc2VydmVyIHNuaXBwZXQgY29tbWVudAo=\"\n },\n {\n \"name\": \"another HTTP test application\",\n \"names\": [\n \"server_443\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"upstream\": \"http://test_upstream\"\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\",\n \"weight\": 5,\n \"max_fails\": 2,\n \"fail_timeout\": \"30s\",\n \"max_conns\": 3,\n \"slow_start\": \"30s\"\n },\n {\n \"server\": \"10.0.0.2:80\",\n \"backup\": true\n }\n ],\n \"sticky\": {\n \"cookie\": \"cookie_name\",\n \"expires\": \"1h\",\n \"domain\": \".testserver\",\n \"path\": \"/\"\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgdXBzdHJlYW0gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"rate_limit\": [\n {\n \"name\": \"test_ratelimit\",\n \"key\": \"$binary_remote_addr\",\n \"size\": \"10m\",\n \"rate\": \"1r/s\"\n }\n ],\n \"maps\": [\n {\n \"match\": \"$host$request_uri\",\n \"variable\": \"$backend\",\n \"entries\": [\n {\n \"key\": \"www.test.lab/app1/\",\n \"keymatch\": \"iregex\",\n \"value\": \"upstream_1\"\n },\n {\n \"key\": \"(.*).test.lab/app2/\",\n \"keymatch\": \"regex\",\n \"value\": \"upstream_2\"\n }\n ]\n }\n ],\n \"nginx_plus_api\": {\n \"write\": true,\n \"listen\": \"127.0.0.1:8080\",\n \"allow_acl\": \"0.0.0.0/0\"\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgSFRUUCBzbmlwcGV0IGNvbW1lbnQK\"\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": "Advanced LB - ConfigMap output", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"configmap\",\n \"configmap\": {\n \"name\": \"nginx.test\",\n \"filename\": \"testservice.conf\",\n \"namespace\": \"test-namespace\"\n }\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"sample_layer4_service\",\n \"listen\": {\n \"address\": \"10053\",\n \"protocol\": \"tcp\",\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"upstream\": \"l4_upstream\",\n \"snippet\": \"IyBUaGlzIGlzIGEgbDQgdXBzdHJlYW0gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"l4_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:53\"\n },\n {\n \"server\": \"10.0.0.2:53\"\n }\n ]\n }\n ]\n },\n \"http\": {\n \"servers\": [\n {\n \"name\": \"HTTP test application\",\n \"names\": [\n \"server_8080.nginx.lab\",\n \"server_8081.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_log\",\n \"error\": \"/var/log/nginx/error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/test\",\n \"urimatch\": \"exact\",\n \"upstream\": \"http://test_upstream\",\n \"health_check\": {\n \"enabled\": true,\n \"uri\": \"/healthcheck\",\n \"interval\": 5,\n \"fails\": 3,\n \"passes\": 2\n },\n \"rate_limit\": {\n \"profile\": \"test_ratelimit\",\n \"httpcode\": 429,\n \"burst\": 10,\n \"delay\": 3\n },\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"test_policy\",\n \"log\": {\n \"profile_name\": \"log_illegal\",\n \"enabled\": true,\n \"destination\": \"192.168.1.5:514\"\n }\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgbG9jYXRpb24gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"test_policy\",\n \"log\": {\n \"profile_name\": \"log_blocked\",\n \"enabled\": true,\n \"destination\": \"192.168.1.5:514\"\n }\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgc2VydmVyIHNuaXBwZXQgY29tbWVudAo=\"\n },\n {\n \"name\": \"another HTTP test application\",\n \"names\": [\n \"server_443\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"upstream\": \"http://test_upstream\"\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\",\n \"weight\": 5,\n \"max_fails\": 2,\n \"fail_timeout\": \"30s\",\n \"max_conns\": 3,\n \"slow_start\": \"30s\"\n },\n {\n \"server\": \"10.0.0.2:80\",\n \"backup\": true\n }\n ],\n \"sticky\": {\n \"cookie\": \"cookie_name\",\n \"expires\": \"1h\",\n \"domain\": \".testserver\",\n \"path\": \"/\"\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgdXBzdHJlYW0gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"rate_limit\": [\n {\n \"name\": \"test_ratelimit\",\n \"key\": \"$binary_remote_addr\",\n \"size\": \"10m\",\n \"rate\": \"1r/s\"\n }\n ],\n \"maps\": [\n {\n \"match\": \"$host$request_uri\",\n \"variable\": \"$backend\",\n \"entries\": [\n {\n \"key\": \"www.test.lab/app1/\",\n \"keymatch\": \"iregex\",\n \"value\": \"upstream_1\"\n },\n {\n \"key\": \"(.*).test.lab/app2/\",\n \"keymatch\": \"regex\",\n \"value\": \"upstream_2\"\n }\n ]\n }\n ],\n \"nginx_plus_api\": {\n \"write\": true,\n \"listen\": \"127.0.0.1:8080\",\n \"allow_acl\": \"0.0.0.0/0\"\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgSFRUUCBzbmlwcGV0IGNvbW1lbnQK\"\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": "Advanced LB - HTTP output", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"output\": {\n \"type\": \"http\",\n \"http\": {\n \"url\": \"http://192.168.1.19:8080/path/service\"\n }\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"sample_layer4_service\",\n \"listen\": {\n \"address\": \"10053\",\n \"protocol\": \"tcp\",\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"upstream\": \"l4_upstream\",\n \"snippet\": \"IyBUaGlzIGlzIGEgbDQgdXBzdHJlYW0gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"l4_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:53\"\n },\n {\n \"server\": \"10.0.0.2:53\"\n }\n ]\n }\n ]\n },\n \"http\": {\n \"servers\": [\n {\n \"name\": \"HTTP test application\",\n \"names\": [\n \"server_8080.nginx.lab\",\n \"server_8081.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_log\",\n \"error\": \"/var/log/nginx/error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/test\",\n \"urimatch\": \"exact\",\n \"upstream\": \"http://test_upstream\",\n \"health_check\": {\n \"enabled\": true,\n \"uri\": \"/healthcheck\",\n \"interval\": 5,\n \"fails\": 3,\n \"passes\": 2\n },\n \"rate_limit\": {\n \"profile\": \"test_ratelimit\",\n \"httpcode\": 429,\n \"burst\": 10,\n \"delay\": 3\n },\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"test_policy\",\n \"log\": {\n \"profile_name\": \"log_illegal\",\n \"enabled\": true,\n \"destination\": \"192.168.1.5:514\"\n }\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgbG9jYXRpb24gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"test_policy\",\n \"log\": {\n \"profile_name\": \"log_blocked\",\n \"enabled\": true,\n \"destination\": \"192.168.1.5:514\"\n }\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgc2VydmVyIHNuaXBwZXQgY29tbWVudAo=\"\n },\n {\n \"name\": \"another HTTP test application\",\n \"names\": [\n \"server_443\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"upstream\": \"http://test_upstream\"\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\",\n \"weight\": 5,\n \"max_fails\": 2,\n \"fail_timeout\": \"30s\",\n \"max_conns\": 3,\n \"slow_start\": \"30s\"\n },\n {\n \"server\": \"10.0.0.2:80\",\n \"backup\": true\n }\n ],\n \"sticky\": {\n \"cookie\": \"cookie_name\",\n \"expires\": \"1h\",\n \"domain\": \".testserver\",\n \"path\": \"/\"\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgdXBzdHJlYW0gc25pcHBldCBjb21tZW50Cg==\"\n }\n ],\n \"rate_limit\": [\n {\n \"name\": \"test_ratelimit\",\n \"key\": \"$binary_remote_addr\",\n \"size\": \"10m\",\n \"rate\": \"1r/s\"\n }\n ],\n \"maps\": [\n {\n \"match\": \"$host$request_uri\",\n \"variable\": \"$backend\",\n \"entries\": [\n {\n \"key\": \"www.test.lab/app1/\",\n \"keymatch\": \"iregex\",\n \"value\": \"upstream_1\"\n },\n {\n \"key\": \"(.*).test.lab/app2/\",\n \"keymatch\": \"regex\",\n \"value\": \"upstream_2\"\n }\n ]\n }\n ],\n \"nginx_plus_api\": {\n \"write\": true,\n \"listen\": \"127.0.0.1:8080\",\n \"allow_acl\": \"0.0.0.0/0\"\n },\n \"snippet\": \"IyBUaGlzIGlzIGEgSFRUUCBzbmlwcGV0IGNvbW1lbnQK\"\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": "Declarative automation examples", - "item": [ - { - "name": "API Gateway", - "item": [ - { - "name": "Ergast API", - "item": [ - { - "name": "Ergast API Gateway and DevPortal", - "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 \"modules\": [\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Ergast 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\": \"/ergast\",\n \"urimatch\": \"prefix\",\n \"snippet\": \"IyBUZXN0IFNOSVBQRVQK\",\n \"apigateway\": {\n \"openapi_schema\": \"https://raw.githubusercontent.com/adampax/ergast-f1-openapi-doc/e558eea18e176e4f78a8765ac7eccc804b5157ff/ergast-openapi-doc.yaml\",\n \"api_gateway\": {\n \"enabled\": true,\n \"strip_uri\": true\n },\n \"developer_portal\": {\n \"enabled\": true,\n \"uri\": \"/ergast-devportal.html\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/ergast-access_log\",\n \"error\": \"/var/log/nginx/ergast-error_log\"\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": "Petstore API", - "item": [ - { - "name": "Petstore API Gateway RateLimit", - "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 \"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\": \"https://petstore.swagger.io/v2/swagger.json\",\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 \"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 \"log\": {\n \"access\": \"/var/log/nginx/petstore-access_log\",\n \"error\": \"/var/log/nginx/petstore-error_log\"\n }\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 }\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": "Petstore API Gateway RateLimit + JWT Authentication", - "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 \"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\": \"https://petstore.swagger.io/v2/swagger.json\",\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 }\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": "Petstore & Ergast API Gateway all in one", - "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 \"modules\": [\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Petstore and Ergast API\",\n \"names\": [\n \"apigw.nginx.lab\"\n ],\n \"resolver\": \"192.168.2.13\",\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\": \"https://petstore.swagger.io/v2/swagger.json\",\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 \"log\": {\n \"access\": \"/var/log/nginx/petstore-access_log\",\n \"error\": \"/var/log/nginx/petstore-error_log\"\n }\n }\n },\n {\n \"uri\": \"/ergast\",\n \"urimatch\": \"prefix\",\n \"snippet\": \"IyBUZXN0IFNOSVBQRVQK\",\n \"apigateway\": {\n \"openapi_schema\": \"https://raw.githubusercontent.com/adampax/ergast-f1-openapi-doc/e558eea18e176e4f78a8765ac7eccc804b5157ff/ergast-openapi-doc.yaml\",\n \"api_gateway\": {\n \"enabled\": true,\n \"strip_uri\": true\n },\n \"developer_portal\": {\n \"enabled\": true,\n \"uri\": \"/ergast-devportal.html\"\n },\n \"rate_limit\": [\n {\n \"profile\": \"ergast_ratelimit\",\n \"httpcode\": 429,\n \"burst\": 0,\n \"delay\": 0\n }\n ],\n \"log\": {\n \"access\": \"/var/log/nginx/ergast-access_log\",\n \"error\": \"/var/log/nginx/ergast-error_log\"\n }\n }\n }\n ]\n }\n ],\n \"rate_limit\": [\n {\n \"name\": \"ergast_ratelimit\",\n \"key\": \"$binary_remote_addr\",\n \"size\": \"10m\",\n \"rate\": \"1r/s\"\n },\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 }\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": "CRUD automation", - "item": [ - { - "name": "Create initial NGINX configuration", - "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 \"modules\": [\n \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"HTTP test application\",\n \"names\": [\n \"patched_server.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_log\",\n \"error\": \"/var/log/nginx/error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream\"\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\"\n },\n {\n \"server\": \"10.0.0.2:80\"\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": "Update HTTP upstream", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\"\n },\n {\n \"server\": \"10.0.0.2:80\"\n },\n {\n \"server\": \"10.0.0.3:80\"\n },\n {\n \"server\": \"10.0.0.4:80\"\n },\n {\n \"server\": \"10.0.0.5:80\"\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": "Modify and add HTTP upstream", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"upstreams\": [\n {\n \"name\": \"test_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:80\"\n },\n {\n \"server\": \"10.0.0.2:80\"\n }\n ]\n },\n {\n \"name\": \"test_upstream_added\",\n \"origin\": [\n {\n \"server\": \"192.168.1.1:80\"\n },\n {\n \"server\": \"192.168.1.2:80\"\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": "Modify and add HTTP server and upstream", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"HTTP test application\",\n \"names\": [\n \"patched_server_v2.nginx.lab\"\n ],\n \"listen\": {\n \"address\": \"127.0.0.1:8080\"\n },\n \"log\": {\n \"access\": \"/var/log/nginx/access_v2_log\",\n \"error\": \"/var/log/nginx/error_v2_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://test_upstream_added\"\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream_added\",\n \"origin\": [\n {\n \"server\": \"192.168.1.100:80\",\n \"weight\": 5,\n \"max_fails\": 2,\n \"fail_timeout\": \"30s\",\n \"max_conns\": 3,\n \"slow_start\": \"30s\"\n },\n {\n \"server\": \"192.168.1.101:80\"\n },\n {\n \"server\": \"192.168.1.102:80\"\n },\n {\n \"server\": \"192.168.1.103:80\"\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": "Remove HTTP server and upstream", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"HTTP test application\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"test_upstream_added\"\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": "Modify Stream server and upstream", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"TCP_10053_service\",\n \"listen\": {\n \"address\": \"10053\",\n \"protocol\": \"tcp\"\n },\n \"upstream\": \"TCP_10053_upstream\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"TCP_10053_upstream\",\n \"origin\": [\n {\n \"server\": \"10.0.0.1:53\"\n },\n {\n \"server\": \"10.0.0.2:53\"\n },\n {\n \"server\": \"10.0.0.3:53\"\n },\n {\n \"server\": \"10.0.0.4:53\"\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": "Add stream server and upstream", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"TCP_15432_service\",\n \"listen\": {\n \"address\": \"15432\",\n \"protocol\": \"tcp\"\n },\n \"upstream\": \"TCP_15432_upstream\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"TCP_15432_upstream\",\n \"origin\": [\n {\n \"server\": \"172.16.10.1:5432\"\n },\n {\n \"server\": \"172.16.10.1:5432\"\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": "Remove stream server and upstream #1", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"TCP_10053_service\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"TCP_10053_upstream\"\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": "Remove stream server and upstream #2", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"layer4\": {\n \"servers\": [\n {\n \"name\": \"TCP_15432_service\"\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"TCP_15432_upstream\"\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": "Get declaration status", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{ncg_host}}:{{ncg_port}}/{{ngc_api_version}}/config/{{configUid}}/status", - "protocol": "http", - "host": [ - "{{ncg_host}}" - ], - "port": "{{ncg_port}}", - "path": [ - "{{ngc_api_version}}", - "config", - "{{configUid}}", - "status" - ] - } - }, - "response": [] - }, - { - "name": "Get declaration", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "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": "Delete declaration", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "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": "GitOps autosync", - "item": [ - { - "name": "NGINX Plus and GitOps", - "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\": 5,\n \"modules\": [\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ],\n \"certificates\": [\n {\n \"type\": \"certificate\",\n \"name\": \"test_cert\",\n \"contents\": \"{{github_gitops_root}}/v4.0/www.online-boutique.local.crt\"\n },\n {\n \"type\": \"key\",\n \"name\": \"test_key\",\n \"contents\": \"{{github_gitops_root}}/v4.0/www.online-boutique.local.key\"\n }\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Online boutique HTTPS\",\n \"names\": [\n \"www.online-boutique.lan\"\n ],\n \"listen\": {\n \"address\": \"0.0.0.0:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"log\": {\n \"access\": \"/var/log/nginx/online_boutique_https_access_log\",\n \"error\": \"/var/log/nginx/online_boutique_https_error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://upstream_boutique\"\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"upstream_boutique\",\n \"origin\": [\n {\n \"server\": \"192.168.2.200:80\"\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": "NGINX App Protect WAF and GitOps", - "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\": 5,\n \"modules\": [\n \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ],\n \"certificates\": [\n {\n \"type\": \"certificate\",\n \"name\": \"test_cert\",\n \"contents\": \"{{github_gitops_root}}/v4.0/www.online-boutique.local.crt\"\n },\n {\n \"type\": \"key\",\n \"name\": \"test_key\",\n \"contents\": \"{{github_gitops_root}}/v4.0/www.online-boutique.local.key\"\n }\n ],\n \"policies\": [\n {\n \"type\": \"app_protect\",\n \"name\": \"production-policy\",\n \"active_tag\": \"gitops\",\n \"versions\": [\n {\n \"tag\": \"gitops\",\n \"displayName\": \"Production Policy - GitOps\",\n \"description\": \"This is a production-ready policy - Managed by GitOps\",\n \"contents\": \"{{github_gitops_root}}/v4.0/nap-policy-gitops.json\"\n }\n ]\n }\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Online boutique HTTPS\",\n \"names\": [\n \"www.online-boutique.lan\"\n ],\n \"listen\": {\n \"address\": \"0.0.0.0:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"log\": {\n \"access\": \"/var/log/nginx/online_boutique_https_access_log\",\n \"error\": \"/var/log/nginx/online_boutique_https_error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://upstream_boutique\"\n }\n ],\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"production-policy\",\n \"log\": {\n \"profile_name\": \"secops_dashboard\",\n \"enabled\": true,\n \"destination\": \"127.0.0.1:514\"\n }\n }\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"upstream_boutique\",\n \"origin\": [\n {\n \"server\": \"192.168.1.200:80\"\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": "Get declaration status", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{ncg_host}}:{{ncg_port}}/{{ngc_api_version}}/config/{{configUid}}/status", - "protocol": "http", - "host": [ - "{{ncg_host}}" - ], - "port": "{{ncg_port}}", - "path": [ - "{{ngc_api_version}}", - "config", - "{{configUid}}", - "status" - ] - } - }, - "response": [] - }, - { - "name": "Get declaration", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "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": "Delete declaration", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "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": "Housekeeping - common endpoints", - "item": [ - { - "name": "Clean NGINX configuration", - "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 \"modules\": [\n \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\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": "Get declaration status", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{ncg_host}}:{{ncg_port}}/{{ngc_api_version}}/config/{{configUid}}/status", - "protocol": "http", - "host": [ - "{{ncg_host}}" - ], - "port": "{{ncg_port}}", - "path": [ - "{{ngc_api_version}}", - "config", - "{{configUid}}", - "status" - ] - } - }, - "response": [] - }, - { - "name": "Get declaration", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "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": "Delete declaration", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "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 Client Authentication", - "item": [ - { - "name": "JWT Client Authentication - local JWT key", - "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 \"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}", - "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": "Change to use JWT key stored on external URL", - "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\": \"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}", - "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": "mTLS Client Authentication", - "item": [ - { - "name": "HTTPS server with mTLS, OCSP, SSL Stapling", - "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 \"modules\": [\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ],\n \"certificates\": [\n {\n \"type\": \"certificate\",\n \"name\": \"server_cert\",\n \"contents\": \"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdSRENDQkN5Z0F3SUJBZ0lVTTNJQVZIRmxhSTVsY1d0TjZxOUVhcnlka0w4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1NURUxNQWtHQTFVRUJoTUNTVlF4RFRBTEJnTlZCQWdNQkVGemRHa3hFVEFQQmdOVkJBb01DRlJsYzNRZwpUR0ZpTVJnd0ZnWURWUVFEREE5MmJTMWliR0Z1YXk1bVppNXNZVzR3SGhjTk1qTXdOakE0TVRBd01qTTVXaGNOCk1qUXdOakEzTVRBd01qTTVXakJKTVFzd0NRWURWUVFHRXdKSlZERU5NQXNHQTFVRUNBd0VRWE4wYVRFUk1BOEcKQTFVRUNnd0lWR1Z6ZENCTVlXSXhHREFXQmdOVkJBTU1EM1p0TFdKc1lXNXJMbVptTG14aGJqQ0NBaUl3RFFZSgpLb1pJaHZjTkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFMRFQ2bnZleVZlNi9VZlk2aUtHVC9oV1A0cktDSGR0ClloZWU3RGVZR29QWGhGVjB6a3grVWExanBEZ21WUE1kVEJBdnoxODg5NzlEcHBqdmNYeFhsRmpnaUhjWDhpWVgKSXovSUVMc3dKRUNITWNsNkxmelA5eDVUY1gxTEdFblFOTWhHRzA2MjlxU2NCQmQyUUNiWlY0UWE1TkxlQnQ4cQpHQ2lXY3JiQnR3YlpiSGo1dk9aenJrdHBtRFBGS1V4bXR5b2dBQnNaTllnL0F3Y1l2RXdBOEQ0QTN0VEgxcGhvCkdYY3ZvZWpJelhRMUdmYys5azR3OFhHYWFQOGd2bTdOMXN2MnU2Yld4SHRGZHpWQk9udzJyaHUvWGYyY0N0dW4KUnIxSENKQXRRSDlkbDhzZks1czBSRlVuTlVYbFBiNTFBTjBjVFVGbEYrZlVUVmVON3dNMTdmeVZVY3IydTltSwo0UGdoWjkvMml0ZUpZV3hjK3k4V2NEQzBUV3hwZ2paVEw5Tk1GK2t6SXV2TjJOWFFybjcvSU5UQTMvNFlmWGRPCloxelpTdTlkclRMcG5DZHRpOWxuRHBKODd3bW41cVZSTlZiTlZRbldEeW5yZnoyTU1DY21jLzcvdkJFN2dDemQKNFJLWHJLdHloenlQSitycmh3NmpxYVA4QytaZGRvKzkvak9QVDFTSnUxZ21VbzFuZ2hBMWh2N0M5RUYrM2xQVApYSk5WV3dtYkdWK0p4cUdKSjJSa2toMlIrZTVIREdRY2hGWjJIcXBGTGVQN0trTHJBR2RkZFZQWEZhQ0RiU0R6ClJQd0I5WFlhakg5Zm5QWEtFT3ZpVEJhQVNjWUZwTXB5cm02UkxHUGRSVnE2RUNYVlB4MDdHdGFCaEVvVWIwK2YKVkZnNExtQkx4MldQQWdNQkFBR2pnZ0VpTUlJQkhqQUpCZ05WSFJNRUFqQUFNQkVHQ1dDR1NBR0crRUlCQVFRRQpBd0lHUURBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjBaV1FnVTJWeWRtVnlJRU5sCmNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCVHZFZWJGK1JDV0JhcGVPWUdpQ0YyVHZxbExYekNCaEFZRFZSMGoKQkgwd2U0QVVFdW9Db3kvcmhMQmxzcm5KdXE2QzFJczQxbFNoVGFSTE1Fa3hDekFKQmdOVkJBWVRBa2xVTVEwdwpDd1lEVlFRSURBUkJjM1JwTVJFd0R3WURWUVFLREFoVVpYTjBJRXhoWWpFWU1CWUdBMVVFQXd3UGRtMHRZbXhoCmJtc3VabVl1YkdGdWdoUld4QjhCa3lmK1RkQXc2Q3dPZE1aT0k0NlZ2REFPQmdOVkhROEJBZjhFQkFNQ0JhQXcKRXdZRFZSMGxCQXd3Q2dZSUt3WUJCUVVIQXdFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFHUDR6ZkdseTI1RwpneTBSeC9SSTNpNzJDVlIrSXY3SW5WTUVGWDZqRHRNV3hSblFtRGZsMWtTOVF1Y3hNb0tnOE9URStMcnlzdGJsClF4WGZiakZQekNoNHB1UGtGTmNBeG1mVmR4b20xR1lodWpoYTBQOUswUURZSDZycGlUaFdSQ2greUovQm1qZ2wKTlJabks4WGRqME85Ui9XKzJrTFRac2VFbS9hZHFVQ3dkYzNBWWlNWGh4QXkvQlh3bFRQeDMyMHZCcXYxZGFyVgp5ZlVoRlM1Rkg3enV2bGtGQ1p6M3lpOGYvYXMwbkRTUkFrY3dPRFQvN1diQlN4QTk3ZzJmRk1EMEI3WlUvbndGCmU4VnRzNDl3YmZ6QWJRMk40RUc2OEVhODE1VlFRM2N6YWthdjBCdkxHL2UwT0habGxYcUVhV1ZlWFJtSWFFOHcKWko5OEhUaDJMbUlFV2Jpdm94Kyt2UXd3bVhKTm1DRFVXNnVmcHdBOVdKQ0VhYmhxeXdGVzh1dFVENzRTVXE3SApEUDhNamtJZ0o3ekl2Tkd1RkFsSzd6c2xpV2pzeUN1OGVNamhvN2pVRFhGR1R0R0ZMUGtVa08vSysrSGVVRFg0Cm1OWDJ2aHI3NGRqRkNBTTEvOTYxWnB5NUFYUzZkd2g3MFlJL2dMdldSL0J1ejBnNEp6YUI2UFo4M1ErYm9QVHYKM1ZIS2xOWjlKQlhRTmtSc3N6U0dYWG5MYmtOTmNwVFg2cnAyZ1pUSS9NNDhGTnBxanAxOXRpQVg3bWN0cTl2SgpNejhvemhEcHZmSTlnMjFsNFZlRGdpbWEwTDVBc1pQbFdIQlZjcy9yL3dMU2YzWFVYZEs0UHpCQUdIRFBidXYrCnpKOVNqS0NFVll2bHRhMHlUUVBCSFJPa2Y2MG1sVmh6Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K\"\n },\n {\n \"type\": \"key\",\n \"name\": \"server_key\",\n \"contents\": \"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS0FJQkFBS0NBZ0VBc05QcWU5N0pWN3I5UjlqcUlvWlArRlkvaXNvSWQyMWlGNTdzTjVnYWc5ZUVWWFRPClRINVJyV09rT0NaVTh4MU1FQy9QWHp6M3YwT21tTzl4ZkZlVVdPQ0lkeGZ5SmhjalA4Z1F1ekFrUUljeHlYb3QKL00vM0hsTnhmVXNZU2RBMHlFWWJUcmIycEp3RUYzWkFKdGxYaEJyazB0NEczeW9ZS0paeXRzRzNCdGxzZVBtOAo1bk91UzJtWU04VXBUR2EzS2lBQUd4azFpRDhEQnhpOFRBRHdQZ0RlMU1mV21HZ1pkeStoNk1qTmREVVo5ejcyClRqRHhjWnBvL3lDK2JzM1d5L2E3cHRiRWUwVjNOVUU2ZkRhdUc3OWQvWndLMjZkR3ZVY0lrQzFBZjEyWHl4OHIKbXpSRVZTYzFSZVU5dm5VQTNSeE5RV1VYNTlSTlY0M3ZBelh0L0pWUnl2YTcyWXJnK0NGbjMvYUsxNGxoYkZ6NwpMeFp3TUxSTmJHbUNObE12MDB3WDZUTWk2ODNZMWRDdWZ2OGcxTURmL2hoOWQwNW5YTmxLNzEydE11bWNKMjJMCjJXY09rbnp2Q2FmbXBWRTFWczFWQ2RZUEtldC9QWXd3Snlaei92KzhFVHVBTE4zaEVwZXNxM0tIUEk4bjZ1dUgKRHFPcG8vd0w1bDEyajczK000OVBWSW03V0NaU2pXZUNFRFdHL3NMMFFYN2VVOU5jazFWYkNac1pYNG5Hb1lrbgpaR1NTSFpINTdrY01aQnlFVm5ZZXFrVXQ0L3NxUXVzQVoxMTFVOWNWb0lOdElQTkUvQUgxZGhxTWYxK2M5Y29RCjYrSk1Gb0JKeGdXa3luS3VicEVzWTkxRldyb1FKZFUvSFRzYTFvR0VTaFJ2VDU5VVdEZ3VZRXZIWlk4Q0F3RUEKQVFLQ0FnQVBUR1pQRFRsU004VlIvL3hSdkZrUzNUTm1LSkNPOUpHMkJYUGVZM1IzejUrTlhTdTBCb0craEk1aQpwVDVZUWtLZ2ErSi9GT0ZDVlBJRzdVQmVSNTE0Q3dVRGVMamtmci8zOXJFcjRNQmlMTkFyNUR3eVVUUEtGZUlOCnV2K0E4MWg5czBNTmpsck1ad3NibElsOFV2VjFZblpGb0J2c0Z0SThRTGZ3QTlaMzZ6dXRRNzRLR2h3TVBqaUMKMGgzK2xDeG9vcGdmd0JDWGx3d0dBeWZYVTRWMWQ5SFBpdktRQVFHakJDWDM0OWVTcEQxNDNLT21wQ2xmY01LQQp3QzU1bTZsbndCTUFIamlsaVo4RXBuNE8zUlEzSmxsVlpiaXl4RWdrZkE3TG1uNm9Ca3Jwc2VxdDVObThuRVhKCnBFbXhQcUl5Znc1WUNBMEhhNkM5WUhRN1RPRW9BbHBmWld4azAxSnpoVi9aK3FmVHM1YlMwQWNaTzFOVDRaeDgKWlF2eHQ0TDJINVcrK2R6RjhReTlidzQ2M3lKb1dydWxtNy9uQ3YvL1FpNGl0eHRnYyt0N2lwVXZzaUdTVktVWQpPelhCSXNWTUlnd0F6eUtTSEhPL21rMkEwVkgxaHB3emY2L0RzR2wxSjM4TU9pVGo4dEx1RWt3cFY4WGh5MnZwCkd0cXpsT21DS1hodlVDam9iZWlYSWJwSlIzeEM1NmliRjVadk0vQUdONzI5K0xKRFNwbHJtWVJRVHh1UTJWSE8KQWFXQ01SQWFBdUtCVnBxYTRjd25WRy9POEpkN2ZPSi9tMFlIN3FpRlJHREdvdVNOdHZJUUVtaXVkK3dRWjJ6dwpUcmFNVWk0SENtNEFPa0ZNVXBsRmt1ajA2ZHRqM2RIWUtPQkdMK25vaUp4WmJxb3kwUUtDQVFFQTFiZUl6WHh6CnRFRlp2OGRlOXljOWdCUUtNNUNIbHp6NUNMZXVkTitvemxxeDNCMW1PRStxbFkyaEd3RklIWVBJajFLYS83RlkKbExmNFpiUEJRMFhiNUo5VzQzSGIyTnEydXdRQ3ZiSXhVMW9zaGJVWlhZc2FUaE15azc2VzQ5YjU3UC9HdFE3NwpTbkVZTXNrTzRUQndyS3lBdVhDVHRtTk1Qa2J1NFBxT05PeVFQY3o3Yi92VEU1eERjMENMVS9oUXM3NWFHeCs1Citld2VjeEZNa0JKTVo2c2N5TzcySEdSNHZwTHduRXUvcU5uN2JmUElSaUx1T3BwTTdHNlUwQlBPL2todHJ5ZmQKV3U3MHJYZGJSdGRJUHlsQWxSOG9zczJqWWsrRHNPUnNESm9pbkk5WU1Va3dmdHdCNTRQbytGRGtGOHBzV202RQpSaklpenFBK0piWDlTd0tDQVFFQTA4Ly9oM0NabDg2M2xUZHNrU1JKRUZKc0RtdkZkUStzMWtlNUFwMjdnWTBXCmZJbEFGZFlRR3RORUVlTk9xS3EwdTFtS0lqWHFacWNTdU9DNzZIYTE5Tk9waHVoK1dwV0t2Ni9BTWtQSjE5SUIKQ3RqS0lkc2s0U2M3WG02MnNOV1pnQm5XT1Z3QVdzU0VzTHRac1NvWUJUVTJJS1pBOVJOWHhkSEQreGZ2SWJkNApZYngzTzk4WklNQzNlVFFiOW9jVHZab0RNWGdLaHRtTy9iMnlSeEVDSGpGRmxzYlhhc1RPeG5XOWZSVXJtdGVqCk9pdVlXaEZOM2R6dmpuVEdLY0xieWY0MWpHaUVUeFViUHVpei9ZMmk5NldCNVN6MW9zaGorRU1OaFhtRzZSYXUKQUIvelhwNldtSUJ2bDNpU0lzOGJRNkh3Qm1DTjc1R2VVVG1GUUlyaVRRS0NBUUVBbTkzWVN5MXA0VndNRGI5bApObElMRzM4Q0ZhdGlDRjR5cmpYd2FWSzVkWTVWeTFneHRmMzhSa2hkNkNrZUpGQjVsSFhGajVnVEo1dW84TnVSCnB2T3JOT2swNEhxb3dWWjZFSmtUT3JCY0l4TlFCMUFXS05BTHBrZUFDcHJreDFTQlFHVW0wZVFVUjYyRjNYd2YKZXdMdUdqRlJURzJiZlZpY1FZdFFLd3J4YmczZUFRU2ZtSU9MNVBDQmpPdlU4YS9YZzgvZlBZcjlBeFkrK3VMeAorTjB2bGlnSXZVN3lkYkNkRXpodGZVQU5qeU16cVhRemExdU1iWGNkaFEzOVFHaEIvZGhyRG1TL250Tko1YjEzCjk0bUpLbTkycDR0ckRrVEYxU3h5dWk5TjBqOFQ0U1QyU0RPOXg3ZkROOHRQdk5LYUYvUE01SU5YdXk1VGptajIKQ21EWlV3S0NBUUFOUVJYSFh1ZHRsWFR0ZEhOcHZiQ0l3ZStiRTJsZXd1VlkzMUlYZE5GWDhRRTROOHAzMDFaYwpwMTI2Rk5SR1A3QmhqTi9VOWpTOXliU2xOd0xyTUFxQTBJSHFQRUF6NE9tMnh3T3E0WTBPNFVoSmFubHpsdWYrCjR0cVhOU3hmY201UmtzeFIrSXpaSVRVQWJpalZxa0dvaWNUaVZDVDZjUVJzRDQxSStCMXhxYTV4eHo1YTA4SVoKeDVWemt5d3d5QkVYS3owSjZtNFdOQ1Q3Z2RSWEdCeGUwVXgrZStEZEFJWEQ2M2c1RElzVy9HbHRhVzcySytFSQpnaHZIZVUweExjMWRIWGd5V2hQMWN1ZXFqeHM4UVpHeUYzeENZQWJhOGRrM250S0l5S3NGaVBMSWRUZGdjMklQCkZ2SmtzeG5KN2RYUjdKODlkdXRLMDN6cHJrVEZYaXQ5QW9JQkFDcjhkb2ZCcFlFL1JuTlFwbVNET29DRm1sdTkKQlozN3h5K0puZ2FrQ2RSdHFyR1lDdkZMSnI2QnpGdXE0SHpsM0piTkRCM1BkYSs4Z2VNd2cxU1htTEhrRVFrTQpXV2ptNHpmU3hiTUtKamx3REdoeUlwSU9nQ2FQL1hyT2hxTGl4bnJ6UHFHZmM4R0FZTDE2Rm1PeGVqbVk5aERtCmNibkFqZlNwUjF1WEt2S2d6d1NLQ0VWdzc0VjJSRmRqQXBLVDl3bkpOQTZiWHQ5SXFkaS96d3BYbDQ0OVczdVMKNjRjVVpaK3luYnQ5QUlxbFNjMDdNRHl1TUtueExMbDFLeEJYenNxZlVsYWtlRGVoVmdGS05OOTNXQWJJc09ieAp1d1hTd0hXa1B6RGFHeE9wdzlSMHo2S2t2N25YZnBIYW1RWENBZEdsRjkyc1QwYW80Y3FuejFJSmJ2bz0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K\"\n },\n {\n \"type\": \"certificate\",\n \"name\": \"cacert\",\n \"contents\": \"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZjekNDQTF1Z0F3SUJBZ0lVVnNRZkFaTW4vazNRTU9nc0RuVEdUaU9PbGJ3d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1NURUxNQWtHQTFVRUJoTUNTVlF4RFRBTEJnTlZCQWdNQkVGemRHa3hFVEFQQmdOVkJBb01DRlJsYzNRZwpUR0ZpTVJnd0ZnWURWUVFEREE5MmJTMWliR0Z1YXk1bVppNXNZVzR3SGhjTk1qTXdOakE0TURrMU5EQTRXaGNOCk1qUXdOakEzTURrMU5EQTRXakJKTVFzd0NRWURWUVFHRXdKSlZERU5NQXNHQTFVRUNBd0VRWE4wYVRFUk1BOEcKQTFVRUNnd0lWR1Z6ZENCTVlXSXhHREFXQmdOVkJBTU1EM1p0TFdKc1lXNXJMbVptTG14aGJqQ0NBaUl3RFFZSgpLb1pJaHZjTkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFLMlQwWXpkcjB0dWQzaVJRNGNzaGNhRVJTRzVjTDE2CkhRblhoYWw4emlUL1VRQUNIUGdzZDYwcWlEaldvQTJXb0lGWFFpUHkzOG1vZGtWRlR4Qmt5U2VldndOOFJiLzEKOFhaMS8yS1RnVmRDcHkvNm11WE15bXZYODJad05CVkV3QnoxUk5kbklUSk44cVh3a0d4bHozbDBib1loRkFyUQpNdmkxcW1RaHpDa2Zpb041MVkrYlBXOXpTQlFQdXNrcXJYYzRqTTJ0VENNQ2pTcFlvd1hXM1ppRmc5WEJ1Z09aCjFmdWd1Zmw4K1FJYzNZSEFoL1Z1NloraXFEOGxQeGRKODlBeDZaazVtOGdkVG9JdUhBbUNWaHFpUXBGRjkzSTgKbkYrSnRuYnBaNTRJUTZBbWYrYiswakMxdmY4Kzg0WUppaEVzWExyaGMxZTRTZ2dwdzEvcWpDb21QblhGVjEzUwpsUG5kVlhVR0taa1ZKdXdZTjJyZElmd3YrdCs5MGhwUVBmNmFBTjRCamRxOXdkdkQzSXVnS2JYZG5CQ0FUTEY4ClYyRTFTSE9VZGdRY3duK1d1WDVVOGdPa3B2b2VFN0g1REJ6Rks1WTZ2SHZlaTRlNkp3RTRDK3FJL1BmbTgreTEKNEpsOFBSOW5JQmdGQ3hrZWpwa2tRQ0I5U0dvMVZidzZhWmdZd0VQNHh6YXFYYXV3L3F4c0oxNUkrRTBndEs1OApuWUtkM0hqelk5Slh6V0NVNTdXbmc2SzNvTTIzNXpyRzJnNm1FaHQ4SStDckVMUFNuZURjZU8zVlJkc2dlblBCCis4U1JxVU8vWG9LWHNEU3I5amoxdWluVzYwTG5MZ0Zmc3JQeGlQVlZlMFh1TFZESlhCSlNoRDZDeGRyMnBSOGQKS25SRDZrTFpZZEtMQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJRUzZnS2pMK3VFc0dXeXVjbTZyb0xVaXpqVwpWREFmQmdOVkhTTUVHREFXZ0JRUzZnS2pMK3VFc0dXeXVjbTZyb0xVaXpqV1ZEQVBCZ05WSFJNQkFmOEVCVEFECkFRSC9NQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUJad3B4Z2Z4N2thZFhvRHNyT1hUVXJ6dEFPMkFQRVJNaTAKaTkyNk9DTGFPbVVYZW1uKytXSUU1K2tUSE0wcS8vbUZCTURzSmdZSFVLUlNvRGNsNmh4TnVFNUNzS2trRVFTSgpMTHZrWlB0S2J5NGlxMitLZ1JtdVZxbXJNVTBYQzZMZDl3WmttL2huUjNtT3V6bko4MGZmV1JDQ0xGWDEwY2EzCnc5TGM1d1JLTFBZZXQvcEs5SitOYWN3TFJRYTczVFovMUpQNW9BU3czVjNoYkxlLy9UeWpnOURqUlZGY3FYWnEKWWs2Mm5qSkhZVzh3WmlhZzc0QXU4dHE5OG5KandBV1ROMFV5L2w1Q2VpWnV5bzZlU0RHVDNJNm1BdGU1VXBvWAppNXBkYlZ6VDdOZC9IOEwwZHZNdVZ2N0FmakZlcU91cUZNNkkzTnlvbStLWENxNmJQdGxBWEkzeVFZc0t4ZlRkCkw3SnRaTmx6MGJ6eHJhcHI4RmpYcjhML1ZkeHQza00xMnJwb2kzL3hsckR6Q2Q2b2YrQ1MxelBocUdpOUhvcUoKZEU5VGhYMklTdkd2akVSYzVVNFRsNjJBNHNyeGJQbUt0eWx3dGNGVEJacUJiRGY3ZjBBc2cveWhndXdTcktsQQpBNkRWVXVCRFErdGpwZ0N0b0ZlOEhLVDJ6UFVlaEQ2ZjVNQkhmU2ZUZ1crTlhFSXNvVDNsampjY1hsYXhPcFJWCkNQNWxCczNmekxyYnBxbUlLaWZhdWlTNWM4TzlSUjhjQTVzeWlBOTBmbmJIdDlmdGxpRG9jcFRzNUtrbjk2NkIKZUxMM1dXVldCYUtvanJzY1RkVXJoalNnVVBmam5FTXpnVzR2eEc3d3BVNHR2ME4yaEtHUWc0bVhhcDV0SU5Pcwp4WktnZXRHUldnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=\"\n }\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Online boutique HTTPS\",\n \"names\": [\n \"vm-blank.ff.lan\"\n ],\n \"resolver\": \"192.168.1.13\",\n \"listen\": {\n \"address\": \"0.0.0.0:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"server_cert\",\n \"key\": \"server_key\",\n \"trusted_ca_certificates\": \"cacert\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ],\n \"mtls\": {\n \"enabled\": \"on\",\n \"client_certificates\": \"cacert\"\n },\n \"ocsp\": {\n \"enabled\": \"on\",\n \"responder\": \"http://ocsp.k8s.ie.ff.lan\"\n },\n \"stapling\": {\n \"enabled\": true,\n \"verify\": true,\n \"responder\": \"http://ocsp.k8s.ie.ff.lan\"\n }\n }\n },\n \"log\": {\n \"access\": \"/var/log/nginx/vm-blank.ff.lan_access_log\",\n \"error\": \"/var/log/nginx/vm-blank.ff.lan_error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://origin_server\"\n }\n ]\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"origin_server\",\n \"origin\": [\n {\n \"server\": \"192.168.1.200:80\"\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": "NGINX App Protect WAF", - "item": [ - { - "name": "Create initial NGINX configuration with NGINX App Protect WAF", - "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 \"modules\": [\n \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ],\n \"certificates\": [\n {\n \"type\": \"certificate\",\n \"name\": \"test_cert\",\n \"contents\": \"{{github_gitops_root}}/v4.0/www.online-boutique.local.crt\"\n },\n {\n \"type\": \"key\",\n \"name\": \"test_key\",\n \"contents\": \"{{github_gitops_root}}/v4.0/www.online-boutique.local.key\"\n }\n ],\n \"policies\": [\n {\n \"type\": \"app_protect\",\n \"name\": \"production-policy\",\n \"active_tag\": \"xss-blocked\",\n \"versions\": [\n {\n \"tag\": \"xss-blocked\",\n \"displayName\": \"Production Policy - XSS blocked\",\n \"description\": \"This is a production-ready policy - XSS blocked\",\n \"contents\": \"{{github_gitops_root}}/v4.0/nap-policy-xss-blocked.json\"\n },\n {\n \"tag\": \"xss-allowed\",\n \"displayName\": \"Production Policy - XSS allowed\",\n \"description\": \"This is a production-ready policy - XSS allowed\",\n \"contents\": \"{{github_gitops_root}}/v4.0/nap-policy-xss-allowed.json\"\n }\n ]\n }\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Online boutique HTTPS\",\n \"names\": [\n \"www.online-boutique.lan\"\n ],\n \"listen\": {\n \"address\": \"0.0.0.0:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"log\": {\n \"access\": \"/var/log/nginx/online_boutique_https_access_log\",\n \"error\": \"/var/log/nginx/online_boutique_https_error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://upstream_boutique\"\n }\n ],\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"production-policy\",\n \"log\": {\n \"profile_name\": \"secops_dashboard\",\n \"enabled\": true,\n \"destination\": \"127.0.0.1:514\"\n }\n }\n }\n ],\n \"upstreams\": [\n {\n \"name\": \"upstream_boutique\",\n \"origin\": [\n {\n \"server\": \"192.168.1.200:80\"\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": "Change active NGINX App Protect policy", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ],\n \"policies\": [\n {\n \"type\": \"app_protect\",\n \"name\": \"production-policy\",\n \"active_tag\": \"xss-allowed\",\n \"versions\": [\n {\n \"tag\": \"xss-blocked\",\n \"displayName\": \"Production Policy - XSS blocked\",\n \"description\": \"Production-ready policy - XSS blocked\",\n \"contents\": \"{{github_gitops_root}}/v4.0/nap-policy-xss-blocked.json\"\n },\n {\n \"tag\": \"xss-allowed\",\n \"displayName\": \"Production Policy - XSS allowed\",\n \"description\": \"Production-ready policy - XSS allowed\",\n \"contents\": \"{{github_gitops_root}}/v4.0/nap-policy-xss-allowed.json\"\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": "Update TLS certificates", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ],\n \"certificates\": [\n {\n \"type\": \"certificate\",\n \"name\": \"test_cert\",\n \"contents\": \"{{github_gitops_root}}/v4.0/www2.online-boutique.local.crt\"\n },\n {\n \"type\": \"key\",\n \"name\": \"test_key\",\n \"contents\": \"{{github_gitops_root}}/v4.0/www2.online-boutique.local.key\"\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": "Disable NGINX App Protect WAF", - "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 \"ngx_http_app_protect_module\",\n \"ngx_http_js_module\",\n \"ngx_stream_js_module\"\n ]\n }\n },\n \"declaration\": {\n \"http\": {\n \"servers\": [\n {\n \"name\": \"Online boutique HTTPS\",\n \"names\": [\n \"www.online-boutique.lan\"\n ],\n \"listen\": {\n \"address\": \"0.0.0.0:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\n },\n \"log\": {\n \"access\": \"/var/log/nginx/online_boutique_https_access_log\",\n \"error\": \"/var/log/nginx/online_boutique_https_error_log\"\n },\n \"locations\": [\n {\n \"uri\": \"/\",\n \"urimatch\": \"prefix\",\n \"upstream\": \"http://upstream_boutique\"\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": "Get declaration status", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{ncg_host}}:{{ncg_port}}/{{ngc_api_version}}/config/{{configUid}}/status", - "protocol": "http", - "host": [ - "{{ncg_host}}" - ], - "port": "{{ncg_port}}", - "path": [ - "{{ngc_api_version}}", - "config", - "{{configUid}}", - "status" - ] - } - }, - "response": [] - }, - { - "name": "Get declaration", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "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": "Delete declaration", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "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": "v4.1", "item": [ @@ -4614,7 +2680,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 ],\n \"certificates\": [\n {\n \"type\": \"certificate\",\n \"name\": \"test_cert\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/v4.2/testcert.crt\"\n }\n },\n {\n \"type\": \"key\",\n \"name\": \"test_key\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/v4.2/testcert.key\"\n }\n }\n ],\n \"policies\": [\n {\n \"type\": \"app_protect\",\n \"name\": \"production-policy\",\n \"active_tag\": \"xss-blocked\",\n \"versions\": [\n {\n \"tag\": \"xss-blocked\",\n \"displayName\": \"Production Policy - XSS blocked\",\n \"description\": \"This is a production-ready policy - XSS blocked\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/v4.2/nap-policy-xss-blocked-bot-allowed.json\"\n }\n },\n {\n \"tag\": \"xss-allowed\",\n \"displayName\": \"Production Policy - XSS allowed\",\n \"description\": \"This is a production-ready policy - XSS allowed\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/v4.2/nap-policy-xss-allowed.json\"\n }\n }\n ]\n }\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\": \"0.0.0.0:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\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 },\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 \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"production-policy\",\n \"log\": {\n \"profile_name\": \"secops_dashboard\",\n \"enabled\": true,\n \"destination\": \"127.0.0.1:514\"\n }\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 },\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 \"errorcode\": 403\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 \"ngx_http_app_protect_module\"\n ],\n \"certificates\": [\n {\n \"type\": \"certificate\",\n \"name\": \"test_cert\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/v4.2/testcert.crt\"\n }\n },\n {\n \"type\": \"key\",\n \"name\": \"test_key\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/v4.2/testcert.key\"\n }\n }\n ],\n \"policies\": [\n {\n \"type\": \"app_protect\",\n \"name\": \"production-policy\",\n \"active_tag\": \"xss-blocked\",\n \"versions\": [\n {\n \"tag\": \"xss-blocked\",\n \"displayName\": \"Production Policy - XSS blocked\",\n \"description\": \"This is a production-ready policy - XSS blocked\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/v4.2/nap-policy-xss-blocked-bot-allowed.json\"\n }\n },\n {\n \"tag\": \"xss-allowed\",\n \"displayName\": \"Production Policy - XSS allowed\",\n \"description\": \"This is a production-ready policy - XSS allowed\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/v4.2/nap-policy-xss-allowed.json\"\n }\n }\n ]\n }\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\": \"0.0.0.0:443\",\n \"http2\": true,\n \"tls\": {\n \"certificate\": \"test_cert\",\n \"key\": \"test_key\",\n \"ciphers\": \"DEFAULT\",\n \"protocols\": [\n \"TLSv1.2\",\n \"TLSv1.3\"\n ]\n }\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 },\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 \"/pet/{petId}/uploadImage\"\n ]\n },\n \"authorization\": [\n {\n \"profile\": \"JWT role based authorization\",\n \"enforceOnPaths\": true,\n \"paths\": [\n \"/user/login\",\n \"/user/logout\",\n \"/pet/{petId}/uploadImage\"\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 \"/pet/{petId}/uploadImage\"\n ]\n }\n ]\n },\n \"log\": {\n \"access\": \"/var/log/nginx/petstore-access_log\",\n \"error\": \"/var/log/nginx/petstore-error_log\"\n },\n \"app_protect\": {\n \"enabled\": true,\n \"policy\": \"production-policy\",\n \"log\": {\n \"profile_name\": \"secops_dashboard\",\n \"enabled\": true,\n \"destination\": \"127.0.0.1:514\"\n }\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 },\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 \"errorcode\": 403\n }\n ]\n }\n }\n ]\n }\n }\n}", "options": { "raw": { "language": "json" diff --git a/src/V4_0_CreateConfig.py b/src/V4_0_CreateConfig.py deleted file mode 100644 index b13141b..0000000 --- a/src/V4_0_CreateConfig.py +++ /dev/null @@ -1,781 +0,0 @@ -""" -Configuration creation based on jinja2 templates -""" - -import base64 -import json -import pickle -import time -import uuid -from datetime import datetime -from urllib.parse import urlparse - -import requests -import schedule -from fastapi.responses import Response, JSONResponse -from jinja2 import Environment, FileSystemLoader -from pydantic import ValidationError -from requests.packages.urllib3.exceptions import InsecureRequestWarning - -import v4_0.APIGateway -import v4_0.DevPortal -import v4_0.DeclarationPatcher -import v4_0.GitOps -import v4_0.MiscUtils - -# NGINX App Protect helper functions -import v4_0.NAPUtils -import v4_0.NIMUtils - -# NGINX Declarative API modules -from NcgConfig import NcgConfig -from NcgRedis import NcgRedis - -# pydantic models -from V4_0_NginxConfigDeclaration import * - -# Tolerates self-signed TLS certificates -requests.packages.urllib3.disable_warnings(InsecureRequestWarning) - - -def getuniqueid(): - return uuid.uuid4() - - -def configautosync(configUid): - print("Autosyncing configuid [" + configUid + "]") - - declaration = '' - declFromRedis = NcgRedis.redis.get(f'ncg.declaration.{configUid}') - - if declFromRedis is not None: - declaration = pickle.loads(declFromRedis) - apiversion = NcgRedis.redis.get(f'ncg.apiversion.{configUid}').decode() - - createconfig(declaration=declaration, apiversion=apiversion, runfromautosync=True, configUid=configUid) - - -# Create the given declarative configuration -# Return a JSON string: -# { "status_code": nnn, "headers": {}, "message": {} } -def createconfig(declaration: ConfigDeclaration, apiversion: str, runfromautosync: bool = False, configUid: str = ""): - # Building NGINX configuration for the given declaration - - # NGINX configuration files for staged config - configFiles = {'files': [], 'rootDir': NcgConfig.config['nms']['config_dir']} - - # NGINX auxiliary files for staged config - auxFiles = {'files': [], 'rootDir': NcgConfig.config['nms']['config_dir']} - - try: - # Pydantic JSON validation - ConfigDeclaration(**declaration.model_dump()) - except ValidationError as e: - print(f'Invalid declaration {e}') - - d = declaration.model_dump() - decltype = d['output']['type'] - - j2_env = Environment(loader=FileSystemLoader(NcgConfig.config['templates']['root_dir'] + '/' + apiversion), - trim_blocks=True, extensions=["jinja2_base64_filters.Base64Filters"]) - j2_env.filters['regex_replace'] = v4_0.MiscUtils.regex_replace - - if 'http' in d['declaration']: - if 'snippet' in d['declaration']['http']: - status, snippet = v4_0.GitOps.getObjectFromRepo(d['declaration']['http']['snippet']) - - if status != 200: - return {"status_code": 422, "message": {"status_code": status, "message": snippet}} - - d['declaration']['http']['snippet'] = snippet - - # Check HTTP upstreams validity - all_upstreams = [] - http = d['declaration']['http'] - - if 'upstreams' in http: - for i in range(len(http['upstreams'])): - - upstream = http['upstreams'][i] - - if upstream['snippet']: - status, snippet = v4_0.GitOps.getObjectFromRepo(upstream['snippet']) - - if status != 200: - return {"status_code": 422, "message": {"status_code": status, "message": snippet}} - - d['declaration']['http']['upstreams'][i]['snippet'] = snippet - - all_upstreams.append(http['upstreams'][i]['name']) - - # Check HTTP rate_limit profiles validity - all_ratelimits = [] - http = d['declaration']['http'] - - d_rate_limit = v4_0.MiscUtils.getDictKey(d, 'declaration.http.rate_limit') - if d_rate_limit is not None: - for i in range(len(d_rate_limit)): - all_ratelimits.append(d_rate_limit[i]['name']) - - # Check authentication profiles validity and creates authentication config files - - # List of all auth client & server profile names - all_auth_client_profiles = [] - all_auth_server_profiles = [] - - d_auth_profiles = v4_0.MiscUtils.getDictKey(d, 'declaration.http.authentication') - if d_auth_profiles is not None: - if 'client' in d_auth_profiles: - # Render all client authentication profiles - - auth_client_profiles = d_auth_profiles['client'] - for i in range(len(auth_client_profiles)): - auth_profile = auth_client_profiles[i] - - match auth_profile['type']: - case 'jwt': - # Add the rendered authentication configuration snippet as a config file in the staged configuration - jwt template - templateName = NcgConfig.config['templates']['auth_client_root']+"/jwt.tmpl" - renderedClientAuthProfile = j2_env.get_template(templateName).render( - authprofile=auth_profile, ncgconfig=NcgConfig.config) - - b64renderedClientAuthProfile = base64.b64encode(bytes(renderedClientAuthProfile, 'utf-8')).decode('utf-8') - configFileName = NcgConfig.config['nms']['auth_client_dir'] + '/'+auth_profile['name'].replace(' ','_')+".conf" - authProfileConfigFile = {'contents': b64renderedClientAuthProfile, - 'name': configFileName } - - all_auth_client_profiles.append(auth_profile['name']) - auxFiles['files'].append(authProfileConfigFile) - - # Add the rendered authentication configuration snippet as a config file in the staged configuration - jwks template - templateName = NcgConfig.config['templates']['auth_client_root']+"/jwks.tmpl" - renderedClientAuthProfile = j2_env.get_template(templateName).render( - authprofile=auth_profile, ncgconfig=NcgConfig.config) - - b64renderedClientAuthProfile = base64.b64encode(bytes(renderedClientAuthProfile, 'utf-8')).decode('utf-8') - configFileName = NcgConfig.config['nms']['auth_client_dir'] + '/jwks_'+auth_profile['name'].replace(' ','_')+".conf" - authProfileConfigFile = {'contents': b64renderedClientAuthProfile, - 'name': configFileName } - - all_auth_client_profiles.append(auth_profile['name']) - auxFiles['files'].append(authProfileConfigFile) - - #if 'server' in d_auth_profiles: - # # Render all server authentication profiles - # - # auth_server_profiles = d_auth_profiles['server'] - # for i in range(len(auth_server_profiles)): - # auth_profile = auth_server_profiles[i] - # templateName = NcgConfig.config['templates']['auth_server_root']+"/"+auth_profile['type']+".tmpl" - # renderedServerAuthProfile = j2_env.get_template(templateName).render( - # authprofile=auth_profile, ncgconfig=NcgConfig.config) - # - # # Add the rendered authentication configuration snippet as a config file in the staged configuration - # b64renderedServerAuthProfile = base64.b64encode(bytes(renderedServerAuthProfile, 'utf-8')).decode('utf-8') - # configFileName = NcgConfig.config['nms']['auth_server_dir'] + '/'+auth_profile['name'].replace(' ','_')+".conf" - # authProfileConfigFile = {'contents': b64renderedServerAuthProfile, - # 'name': configFileName } - # - # all_auth_server_profiles.append(auth_profile['name']) - # auxFiles['files'].append(authProfileConfigFile) - - # Parse HTTP servers - d_servers = v4_0.MiscUtils.getDictKey(d, 'declaration.http.servers') - if d_servers is not None: - apiGatewaySnippet = '' - - for server in d_servers: - serverSnippet = '' - - if server['snippet']: - status, serverSnippet = v4_0.GitOps.getObjectFromRepo(server['snippet'],base64Encode=False) - - if status != 200: - return {"status_code": 422, "message": {"status_code": status, "message": serverSnippet}} - - for loc in server['locations']: - if loc['snippet']: - status, snippet = v4_0.GitOps.getObjectFromRepo(loc['snippet']) - - if status != 200: - return {"status_code": 422, "message": {"status_code": status, "message": snippet}} - - loc['snippet'] = snippet - - # Location upstream name validity check - if 'upstream' in loc and loc['upstream'] and urlparse(loc['upstream']).netloc not in all_upstreams: - return {"status_code": 422, - "message": {"status_code": status, "message": - {"code": status, "content": f"invalid HTTP upstream [{loc['upstream']}]"}}} - - # Location client authentication name validity check - if 'authentication' in loc and loc['authentication']: - locAuthClientProfiles = loc['authentication']['client'] - - for authClientProfile in locAuthClientProfiles: - 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']}]"}}} - - # Location server authentication name validity check - if 'authentication' in loc and loc['authentication']: - locAuthServerProfiles = loc['authentication']['server'] - - for authServerProfile in locAuthServerProfiles: - if authServerProfile['profile'] not in all_auth_server_profiles: - return {"status_code": 422, - "message": {"status_code": status, "message": - {"code": status, "content": f"invalid server authentication profile [{authServerProfile['profile']}] in location [{loc['uri']}]"}}} - - # API Gateway provisioning - if loc['apigateway'] and loc['apigateway']['api_gateway'] and loc['apigateway']['api_gateway']['enabled'] and loc['apigateway']['api_gateway']['enabled'] == True: - status, apiGatewayConfigDeclaration = ( - v4_0.APIGateway.createAPIGateway(locationDeclaration=loc)) - else: - apiGatewayConfigDeclaration = '' - - # API Gateway Developer portal provisioning - if loc['apigateway'] and loc['apigateway']['developer_portal'] and 'enabled' in loc['apigateway']['developer_portal'] and loc['apigateway']['developer_portal']['enabled'] == True: - - status, devPortalHTML = ( - v4_0.DevPortal.createDevPortal(locationDeclaration=loc)) - - if status != 200: - return {"status_code": 400, - "message": {"status_code": status, "message": - {"code": status, "content": f"Developer Portal creation failed for {loc['apigateway']['openapi_schema']}"}}} - - ### Add optional API Developer portal HTML files - # devPortalHTML - newAuxFile = {'contents': devPortalHTML, 'name': NcgConfig.config['nms']['devportal_dir'] + - loc['apigateway']['developer_portal']['uri']} - auxFiles['files'].append(newAuxFile) - - ### / Add optional API Developer portal HTML files - - if loc['rate_limit'] is not None: - if 'profile' in loc['rate_limit'] and loc['rate_limit']['profile'] and loc['rate_limit'][ - 'profile'] not in all_ratelimits: - return {"status_code": 422, - "message": { - "status_code": status, - "message": - {"code": status, - "content": - f"invalid rate_limit profile [{loc['rate_limit']['profile']}]"}}} - - # API Gateway configuration template rendering - apiGatewaySnippet += j2_env.get_template(NcgConfig.config['templates']['apigwconf']).render( - declaration=apiGatewayConfigDeclaration, ncgconfig=NcgConfig.config)\ - if apiGatewayConfigDeclaration else '' - - server['snippet'] = base64.b64encode(bytes(serverSnippet + apiGatewaySnippet, 'utf-8')).decode('utf-8') - - if 'layer4' in d['declaration']: - # Check Layer4/stream upstreams validity - all_upstreams = [] - - d_upstreams = v4_0.MiscUtils.getDictKey(d, 'declaration.layer4.upstreams') - if d_upstreams is not None: - for i in range(len(d_upstreams)): - all_upstreams.append(d_upstreams[i]['name']) - - d_servers = v4_0.MiscUtils.getDictKey(d, 'declaration.layer4.servers') - if d_servers is not None: - for server in d_servers: - - if server['snippet']: - status, snippet = v4_0.GitOps.getObjectFromRepo(server['snippet']) - - if status != 200: - return {"status_code": 422, "message": {"status_code": status, "message": snippet}} - - server['snippet'] = snippet - - if 'upstream' in server and server['upstream'] and server['upstream'] not in all_upstreams: - return {"status_code": 422, - "message": { - "status_code": status, - "message": - {"code": status, "content": f"invalid Layer4 upstream {server['upstream']}"}}} - - # HTTP configuration template rendering - httpConf = j2_env.get_template(NcgConfig.config['templates']['httpconf']).render( - declaration=d['declaration']['http'], ncgconfig=NcgConfig.config) if 'http' in d['declaration'] else '' - - # Stream configuration template rendering - streamConf = j2_env.get_template(NcgConfig.config['templates']['streamconf']).render( - declaration=d['declaration']['layer4'], ncgconfig=NcgConfig.config) if 'layer4' in d['declaration'] else '' - - b64HttpConf = str(base64.b64encode(httpConf.encode("utf-8")), "utf-8") - b64StreamConf = str(base64.b64encode(streamConf.encode("utf-8")), "utf-8") - - if decltype.lower() == "plaintext": - # Plaintext output - return httpConf + streamConf - - elif decltype.lower() == "json" or decltype.lower() == 'http': - # JSON-wrapped b64-encoded output - payload = {"http_config": f"{b64HttpConf}", "stream_config": f"{b64StreamConf}"} - - if decltype.lower() == "json": - # JSON output - return {"status_code": 200, "message": {"status_code": 200, "message": payload}} - else: - # HTTP POST output - try: - r = requests.post(d['output']['http']['url'], data=json.dumps(payload), - headers={'Content-Type': 'application/json'}) - except: - headers = {'Content-Type': 'application/json'} - content = {'message': d['output']['http']['url'] + ' unreachable'} - - return {"status_code": 502, "message": {"status_code": 502, "message": content}, "headers": headers} - - r.headers.pop("Content-Length") if "Content-Length" in r.headers else '' - r.headers.pop("Server") if "Server" in r.headers else '' - r.headers.pop("Date") if "Date" in r.headers else '' - r.headers.pop("Content-Type") if "Content-Type" in r.headers else '' - - r.headers['Content-Type'] = 'application/json' - - return {"status_code": r.status_code, "message": {"code": r.status_code, "content": r.text}, - "headers": r.headers} - - elif decltype.lower() == 'configmap': - # Kubernetes ConfigMap output - cmHttp = j2_env.get_template(NcgConfig.config['templates']['configmap']).render(nginxconfig=httpConf, - name=d['output']['configmap'][ - 'name'] + '.http', - filename= - d['output']['configmap'][ - 'filename'] + '.http', - namespace= - d['output']['configmap'][ - 'namespace']) - cmStream = j2_env.get_template(NcgConfig.config['templates']['configmap']).render(nginxconfig=streamConf, - name=d['output']['configmap'][ - 'name'] + '.stream', - filename= - d['output']['configmap'][ - 'filename'] + '.stream', - namespace= - d['output']['configmap'][ - 'namespace']) - - return Response(content=cmHttp + '\n---\n' + cmStream, headers={'Content-Type': 'application/x-yaml'}) - - elif decltype.lower() == 'nms': - # NGINX Instance Manager Staged Configuration publish - - nmsUsername = v4_0.MiscUtils.getDictKey(d, 'output.nms.username') - nmsPassword = v4_0.MiscUtils.getDictKey(d, 'output.nms.password') - nmsInstanceGroup = v4_0.MiscUtils.getDictKey(d, 'output.nms.instancegroup') - nmsSynctime = v4_0.MiscUtils.getDictKey(d, 'output.nms.synctime') - - nmsUrlFromJson = v4_0.MiscUtils.getDictKey(d, 'output.nms.url') - urlCheck = urlparse(nmsUrlFromJson) - - if urlCheck.scheme not in ['http', 'https'] or urlCheck.scheme == "" or urlCheck.netloc == "": - return {"status_code": 400, - "message": {"status_code": 400, "message": {"code": 400, - "content": f"invalid NGINX Management Suite URL {nmsUrlFromJson}"}}, - "headers": {'Content-Type': 'application/json'}} - - nmsUrl = f"{urlCheck.scheme}://{urlCheck.netloc}" - - if nmsSynctime < 0: - return {"status_code": 400, - "message": {"status_code": 400, "message": {"code": 400, "content": "synctime must be >= 0"}}, - "headers": {'Content-Type': 'application/json'}} - - # Fetch NGINX App Protect WAF policies from source of truth if needed - d_policies = v4_0.MiscUtils.getDictKey(d, 'output.nms.policies') - if d_policies is not None: - for policy in d_policies: - if 'versions' in policy: - for policyVersion in policy['versions']: - status, content = v4_0.GitOps.getObjectFromRepo(policyVersion['contents']) - - if status != 200: - return {"status_code": 422, "message": {"status_code": status, "message": content}} - - policyVersion['contents'] = content - - # Check TLS items validity - all_tls = {'certificate': {}, 'key': {}} - - d_certs = v4_0.MiscUtils.getDictKey(d, 'output.nms.certificates') - if d_certs is not None: - for i in range(len(d_certs)): - if d_certs[i]['name']: - all_tls[d_certs[i]['type']][d_certs[i]['name']] = True - - d_servers = v4_0.MiscUtils.getDictKey(d, 'declaration.http.servers') - if d_servers is not None: - for server in d_servers: - if server['listen'] is not None: - if 'tls' in server['listen']: - cert_name = v4_0.MiscUtils.getDictKey(server, 'listen.tls.certificate') - if cert_name and cert_name not in all_tls['certificate']: - return {"status_code": 422, - "message": { - "status_code": 422, - "message": {"code": 422, - "content": "invalid TLS certificate " + - cert_name + " for server" + str( - server['names'])} - }} - - cert_key = v4_0.MiscUtils.getDictKey(server, 'listen.tls.key') - if cert_key and cert_key not in all_tls['key']: - return {"status_code": 422, - "message": { - "status_code": 422, - "message": {"code": 422, - "content": "invalid TLS key " + cert_key + " for server" + str( - server['names'])} - }} - - trusted_cert_name = v4_0.MiscUtils.getDictKey(server, 'listen.tls.trusted_ca_certificates') - if trusted_cert_name and trusted_cert_name not in all_tls['certificate']: - return {"status_code": 422, - "message": { - "status_code": 422, - "message": {"code": 422, - "content": "invalid trusted CA certificate " + - trusted_cert_name + " for server" + str(server['names'])} - }} - - if v4_0.MiscUtils.getDictKey(server, 'listen.tls.mtls.enabled') in ['optional_no_ca'] \ - and 'ocsp' in server['listen']['tls']: - return {"status_code": 422, - "message": { - "status_code": 422, - "message": {"code": 422, - "content": "OCSP is incompatible with 'optional_no_ca' client " - "mTLS verification for server" + str( - server['names'])} - }} - - client_cert_name = v4_0.MiscUtils.getDictKey(server, 'listen.tls.mtls.client_certificates') - if client_cert_name and client_cert_name not in all_tls['certificate']: - return {"status_code": 422, - "message": { - "status_code": 422, - "message": {"code": 422, - "content": f"invalid mTLS client certificates [{client_cert_name}] for server {str(server['names'])}"} - }} - - # Add optional certificates specified under output.nms.certificates - extensions_map = {'certificate': '.crt', 'key': '.key'} - - d_certificates = v4_0.MiscUtils.getDictKey(d, 'output.nms.certificates') - if d_certificates is not None: - for c in d_certificates: - status, certContent = v4_0.GitOps.getObjectFromRepo(c['contents']) - - if status != 200: - return {"status_code": 422, - "message": {"status_code": status, "message": {"code": status, "content": certContent}}} - - newAuxFile = {'contents': certContent, 'name': NcgConfig.config['nms']['certs_dir'] + - '/' + c['name'] + extensions_map[c['type']]} - auxFiles['files'].append(newAuxFile) - - ### / Add optional certificates specified under output.nms.certificates - - # NGINX main configuration file through template - j2_env = Environment(loader=FileSystemLoader(NcgConfig.config['templates']['root_dir'] + '/' + apiversion), - trim_blocks=True, extensions=["jinja2_base64_filters.Base64Filters"]) - - nginxMainConf = j2_env.get_template(NcgConfig.config['templates']['nginxmain']).render( - nginxconf={'modules': v4_0.MiscUtils.getDictKey(d, 'output.nms.modules')}) - - # Base64-encoded NGINX main configuration (/etc/nginx/nginx.conf) - b64NginxMain = str(base64.urlsafe_b64encode(nginxMainConf.encode("utf-8")), "utf-8") - - # Base64-encoded NGINX mime.types (/etc/nginx/mime.types) - f = open(NcgConfig.config['templates']['root_dir'] + '/' + apiversion + '/' + NcgConfig.config['templates'][ - 'mimetypes'], 'r') - nginxMimeTypes = f.read() - f.close() - - b64NginxMimeTypes = str(base64.urlsafe_b64encode(nginxMimeTypes.encode("utf-8")), "utf-8") - filesMimeType = {'contents': b64NginxMimeTypes, 'name': NcgConfig.config['nms']['config_dir'] + '/mime.types'} - auxFiles['files'].append(filesMimeType) - - # Base64-encoded NGINX HTTP service configuration - filesNginxMain = {'contents': b64NginxMain, 'name': NcgConfig.config['nms']['config_dir'] + '/nginx.conf'} - filesHttpConf = {'contents': b64HttpConf, - 'name': NcgConfig.config['nms']['config_dir'] + '/' + NcgConfig.config['nms'][ - 'staged_config_http_filename']} - filesStreamConf = {'contents': b64StreamConf, - 'name': NcgConfig.config['nms']['config_dir'] + '/' + NcgConfig.config['nms'][ - 'staged_config_stream_filename']} - - # Append config files to staged configuration - configFiles['files'].append(filesNginxMain) - configFiles['files'].append(filesHttpConf) - configFiles['files'].append(filesStreamConf) - - # Staged config - baseStagedConfig = {'auxFiles': auxFiles, 'configFiles': configFiles} - stagedConfig = {'auxFiles': auxFiles, 'configFiles': configFiles, - 'updateTime': datetime.utcnow().isoformat()[:-3] + 'Z', - 'ignoreConflict': True, 'validateConfig': False} - - currentBaseStagedConfig = NcgRedis.redis.get(f'ncg.basestagedconfig.{configUid}').decode('utf-8') if NcgRedis.redis.get(f'ncg.basestagedconfig.{configUid}') else None - newBaseStagedConfig = json.dumps(baseStagedConfig) - - if currentBaseStagedConfig is not None and newBaseStagedConfig == currentBaseStagedConfig: - print(f'Declaration [{configUid}] not changed') - return {"status_code": 200, - "message": {"status_code": 200, "message": {"code": 200, "content": "no changes"}}} - else: - # Configuration objects have changed, publish to NIM needed - print(f'Declaration [{configUid}] changed, publishing to NMS') - - # Retrieve instance group uid - try: - ig = requests.get(url=f'{nmsUrl}/api/platform/v1/instance-groups', auth=(nmsUsername, nmsPassword), - verify=False) - except Exception as e: - return {"status_code": 400, - "message": {"status_code": 400, - "message": {"code": 400, "content": f"Can't connect to {nmsUrl}"}}} - - if ig.status_code != 200: - try: - return {"status_code": ig.status_code, - "message": {"status_code": ig.status_code, - "message": {"code": ig.status_code, "content": json.loads(ig.text)}}} - except: - return {"status_code": ig.status_code, - "message": {"status_code": ig.status_code, - "message": {"code": ig.status_code, "content": ig.text}}} - - # Get the instance group id - igUid = v4_0.NIMUtils.getNIMInstanceGroupUid(nmsUrl=nmsUrl, nmsUsername=nmsUsername, - nmsPassword=nmsPassword, instanceGroupName=nmsInstanceGroup) - - # Invalid instance group - if igUid is None: - return {"status_code": 404, - "message": {"status_code": 404, "message": {"code": 404, - "content": f"instance group {nmsInstanceGroup} not found"}}, - "headers": {'Content-Type': 'application/json'}} - - ### NGINX App Protect policies support - commits policies to control plane - - # Check NGINX App Protect WAF policies configuration sanity - status, description = v4_0.NAPUtils.checkDeclarationPolicies(d) - - if status != 200: - return {"status_code": 422, "message": {"status_code": status, "message": description}} - - # Provision NGINX App Protect WAF policies to NGINX Management Suite - provisionedNapPolicies, activePolicyUids = v4_0.NAPUtils.provisionPolicies( - nmsUrl=nmsUrl, nmsUsername=nmsUsername, nmsPassword=nmsPassword, declaration=d) - - ### / NGINX App Protect policies support - - ### Publish staged config to instance group - r = requests.post(url=nmsUrl + f"/api/platform/v1/instance-groups/{igUid}/config", - data=json.dumps(stagedConfig), - headers={'Content-Type': 'application/json'}, - auth=(nmsUsername, nmsPassword), - verify=False) - - if r.status_code != 202: - # Configuration push failed - return {"status_code": r.status_code, - "message": {"status_code": r.status_code, "message": r.text}, - "headers": {'Content-Type': 'application/json'}} - - # Fetch the deployment status - publishResponse = json.loads(r.text) - - # Wait for either NIM success or failure after pushing a staged config - isPending = True - while isPending: - time.sleep(NcgConfig.config['nms']['staged_config_publish_waittime']) - deploymentCheck = requests.get(url=nmsUrl + publishResponse['links']['rel'], - auth=(nmsUsername, nmsPassword), - verify=False) - - checkJson = json.loads(deploymentCheck.text) - - if not checkJson['details']['pending']: - isPending = False - - if len(checkJson['details']['failure']) > 0: - # Staged config publish to NIM failed - jsonResponse = checkJson['details']['failure'][0] - deploymentCheck.status_code = 422 - else: - # Staged config publish to NIM succeeded - jsonResponse = json.loads(deploymentCheck.text) - - # if nmsSynctime > 0 and runfromautosync == False: - if runfromautosync == False: - # No configuration is found, generate one - configUid = str(getuniqueid()) - - # Stores the staged config to redis - # Redis keys: - # ncg.declaration.[configUid] = original config declaration - # ncg.declarationrendered.[configUid] = original config declaration - rendered - # ncg.basestagedconfig.[configUid] = base staged configuration - # ncg.apiversion.[configUid] = ncg API version - # ncg.status.[configUid] = latest status - - NcgRedis.redis.set(f'ncg.declaration.{configUid}', pickle.dumps(declaration)) - NcgRedis.redis.set(f'ncg.declarationrendered.{configUid}', json.dumps(d)) - NcgRedis.redis.set(f'ncg.basestagedconfig.{configUid}', json.dumps(baseStagedConfig)) - NcgRedis.redis.set(f'ncg.apiversion.{configUid}', apiversion) - - # Makes NGINX App Protect policies active - doWeHavePolicies = v4_0.NAPUtils.makePolicyActive(nmsUrl=nmsUrl, nmsUsername=nmsUsername, - nmsPassword=nmsPassword, - activePolicyUids=activePolicyUids, - instanceGroupUid=igUid) - - if doWeHavePolicies: - # Clean up NGINX App Protect WAF policies not used anymore - # and not defined in the declaration just pushed - time.sleep(NcgConfig.config['nms']['staged_config_publish_waittime']) - v4_0.NAPUtils.cleanPolicyLeftovers(nmsUrl=nmsUrl, nmsUsername=nmsUsername, - nmsPassword=nmsPassword, - currentPolicies=provisionedNapPolicies) - - # If deploying a new configuration in GitOps mode start autosync - if nmsSynctime == 0: - NcgRedis.declarationsList[configUid] = "static" - elif not runfromautosync: - # GitOps autosync - print(f'Starting autosync for configUid {configUid} every {nmsSynctime} seconds') - - job = schedule.every(nmsSynctime).seconds.do(lambda: configautosync(configUid)) - # Keep track of GitOps configs, key is the threaded job - NcgRedis.declarationsList[configUid] = job - - NcgRedis.redis.set(f'ncg.apiversion.{configUid}', apiversion) - - responseContent = {'code': deploymentCheck.status_code, 'content': jsonResponse, 'configUid': configUid} - - # Configuration push completed, update redis keys - if configUid != "": - NcgRedis.redis.set('ncg.status.' + configUid, json.dumps(responseContent)) - - # if nmsSynctime > 0: - # Updates status, declaration and basestagedconfig in redis - NcgRedis.redis.set('ncg.declaration.' + configUid, pickle.dumps(declaration)) - NcgRedis.redis.set('ncg.declarationrendered.' + configUid, json.dumps(d)) - NcgRedis.redis.set('ncg.basestagedconfig.' + configUid, json.dumps(baseStagedConfig)) - - return {"status_code": deploymentCheck.status_code, - "message": {"status_code": deploymentCheck.status_code, - "message": responseContent}, - "headers": {'Content-Type': 'application/json'} - } - - else: - return {"status_code": 422, "message": {"status_code": 422, "message": f"output type {decltype} unknown"}} - - -def patch_config(declaration: ConfigDeclaration, configUid: str, apiversion: str): - # Patch a declaration - if configUid not in NcgRedis.declarationsList: - return JSONResponse( - status_code=404, - content={'code': 404, 'details': {'message': f'declaration {configUid} not found'}}, - headers={'Content-Type': 'application/json'} - ) - - # The declaration sections to be patched - declarationToPatch = declaration.model_dump() - - # The currently applied declaration - status_code, currentDeclaration = get_declaration(configUid=configUid) - - # Handle policy updates - d_policies = v4_0.MiscUtils.getDictKey(declarationToPatch, 'output.nms.policies') - if d_policies is not None: - # NGINX App Protect WAF policy updates - for p in d_policies: - currentDeclaration = v4_0.DeclarationPatcher.patchNAPPolicies( - sourceDeclaration=currentDeclaration, patchedNAPPolicies=p) - - # Handle certificate updates - d_certificates = v4_0.MiscUtils.getDictKey(declarationToPatch, 'output.nms.certificates') - if d_certificates is not None: - # TLS certificate/key updates - for p in d_certificates: - currentDeclaration = v4_0.DeclarationPatcher.patchCertificates( - sourceDeclaration=currentDeclaration, patchedCertificates=p) - - # Handle declaration updates - if 'declaration' in declarationToPatch: - # HTTP - d_upstreams = v4_0.MiscUtils.getDictKey(declarationToPatch, 'declaration.http.upstreams') - if d_upstreams: - # HTTP upstream patch - for u in d_upstreams: - currentDeclaration = v4_0.DeclarationPatcher.patchHttpUpstream( - sourceDeclaration=currentDeclaration, patchedHttpUpstream=u) - - d_servers = v4_0.MiscUtils.getDictKey(declarationToPatch, 'declaration.http.servers') - if d_servers: - # HTTP servers patch - for s in d_servers: - currentDeclaration = v4_0.DeclarationPatcher.patchHttpServer( - sourceDeclaration=currentDeclaration, patchedHttpServer=s) - - # Stream / Layer4 - d_upstreams = v4_0.MiscUtils.getDictKey(declarationToPatch, 'declaration.layer4.upstreams') - if d_upstreams: - # Stream upstream patch - for u in d_upstreams: - currentDeclaration = v4_0.DeclarationPatcher.patchStreamUpstream( - sourceDeclaration=currentDeclaration, patchedStreamUpstream=u) - - d_servers = v4_0.MiscUtils.getDictKey(declarationToPatch, 'declaration.layer4.servers') - if d_servers: - # Stream servers patch - for s in d_servers: - currentDeclaration = v4_0.DeclarationPatcher.patchStreamServer( - sourceDeclaration=currentDeclaration, patchedStreamServer=s) - - # Apply the updated declaration - configDeclaration = ConfigDeclaration.model_validate_json(json.dumps(currentDeclaration)) - - r = createconfig(declaration=configDeclaration, apiversion=apiversion, - runfromautosync=True, configUid=configUid) - - # Return the updated declaration - message = r['message'] - - if r['status_code'] != 200: - currentDeclaration = {} - # message = f'declaration {configUid} update failed'; - - responseContent = {'code': r['status_code'], 'details': {'message': message}, - 'declaration': currentDeclaration, 'configUid': configUid} - - return JSONResponse( - status_code=r['status_code'], - content=responseContent, - headers={'Content-Type': 'application/json'} - ) - - -# Gets the given declaration. Returns status_code and body -def get_declaration(configUid: str): - cfg = NcgRedis.redis.get('ncg.declaration.' + configUid) - - if cfg is None: - return 404, "" - - return 200, pickle.loads(cfg).dict() diff --git a/src/V4_0_NginxConfigDeclaration.py b/src/V4_0_NginxConfigDeclaration.py deleted file mode 100644 index 85d1995..0000000 --- a/src/V4_0_NginxConfigDeclaration.py +++ /dev/null @@ -1,509 +0,0 @@ -""" -JSON declaration format -""" - -from __future__ import annotations - -from typing import List, Optional - -from pydantic import BaseModel, Extra, model_validator - - -class OutputConfigMap(BaseModel, extra="forbid"): - name: str = "nginx-config" - namespace: Optional[str] = "" - filename: str = "nginx-config.conf" - - -class OutputHttp(BaseModel, extra="forbid"): - url: str = "" - - -class NmsCertificate(BaseModel, extra="forbid"): - type: str - name: str - contents: str - - @model_validator(mode='after') - def check_type(self) -> 'NmsCertificate': - _type = self.type - - valid = ['certificate', 'key'] - if _type not in valid: - raise ValueError(f"Invalid certificate type [{_type}] must be one of {str(valid)}") - - return self - - -class NmsPolicyVersion(BaseModel, extra="forbid"): - tag: str = "" - displayName: Optional[str] = "" - description: Optional[str] = "" - contents: str = "" - - -class NmsPolicy(BaseModel, extra="forbid"): - type: str = "" - name: str = "" - active_tag: str = "" - versions: Optional[List[NmsPolicyVersion]] = [] - - @model_validator(mode='after') - def check_type(self) -> 'NmsPolicy': - _type = self.type - - valid = ['app_protect'] - if _type not in valid: - raise ValueError(f"Invalid policy type [{_type}] must be one of {str(valid)}") - - return self - - -class AppProtectLogProfile(BaseModel, extra="forbid"): - name: str - format: Optional[str] = "default" - format_string: Optional[str] = "" - type: Optional[str] = "blocked" - max_request_size: Optional[str] = "any" - max_message_size: Optional[str] = "5k" - - @model_validator(mode='after') - def check_type(self) -> 'AppProtectLogProfile': - _type, _format, _format_string = self.type, self.format, self.format_string - - valid = ['all', 'illegal', 'blocked'] - if _type not in valid: - raise ValueError(f"Invalid NGINX App Protect log type [{_type}] must be one of {str(valid)}") - - valid = ['default', 'grpc', 'arcsight', 'splunk', 'user-defined'] - if _format not in valid: - raise ValueError(f"Invalid NGINX App Protect log format [{_format}] must be one of {str(valid)}") - - if _format == 'user-defined' and _format_string == "": - raise ValueError(f"NGINX App Protect log format {_format} requires format_string") - - return self - - -class LogProfile(BaseModel, extra="forbid"): - type: str - app_protect: Optional[AppProtectLogProfile] = {} - - @model_validator(mode='after') - def check_type(self) -> 'LogProfile': - _type, app_protect = self.type, self.app_protect - - valid = ['app_protect'] - if _type not in valid: - raise ValueError(f"Invalid log profile type [{_type}] must be one of {str(valid)}") - - isError = False - if _type == 'app_protect': - if app_protect is None: - isError = True - - if isError: - raise ValueError(f"Invalid log profile data for type [{_type}]") - - return self - - -class OutputNMS(BaseModel, extra="forbid"): - url: str = "" - username: str = "" - password: str = "" - instancegroup: str = "" - synctime: Optional[int] = 0 - modules: Optional[List[str]] = [] - certificates: Optional[List[NmsCertificate]] = [] - policies: Optional[List[NmsPolicy]] = [] - log_profiles: Optional[List[LogProfile]] = [] - - -class Output(BaseModel, extra="forbid"): - type: str - configmap: Optional[OutputConfigMap] = {} - http: Optional[OutputHttp] = {} - nms: Optional[OutputNMS] = {} - - @model_validator(mode='after') - def check_type(self) -> 'Output': - _type, configmap, http, nms = self.type, self.configmap, self.http, self.nms - - valid = ['plaintext', 'json', 'configmap', 'http', 'nms'] - if _type not in valid: - raise ValueError(f"Invalid output type [{_type}] must be one of {str(valid)}") - - isError = False - - if _type == 'configmap' and not configmap: - isError = True - elif _type == 'http' and not http: - isError = True - elif _type == 'nms' and not nms: - isError = True - - if isError: - raise ValueError(f"Invalid output data for type [{_type}]") - - return self - - -class OcspStapling(BaseModel, extra="forbid"): - enabled: Optional[bool] = False - verify: Optional[bool] = False - responder: Optional[str] = "" - - -class Ocsp(BaseModel, extra="forbid"): - enabled: Optional[str] = "off" - responder: Optional[str] = "" - - -class Mtls(BaseModel, extra="forbid"): - enabled: Optional[str] = "off" - client_certificates: str = "" - - @model_validator(mode='after') - def check_type(self) -> 'Mtls': - _enabled = self.enabled - - valid = ['on', 'off', 'optional', 'optional_no_ca'] - if _enabled not in valid: - raise ValueError(f"Invalid mTLS type [{_enabled}] must be one of {str(valid)}") - - return self - - -class Tls(BaseModel, extra="forbid"): - certificate: str = "" - key: str = "" - trusted_ca_certificates: str = "" - ciphers: Optional[str] = "" - protocols: Optional[List[str]] = [] - mtls: Optional[Mtls] = {} - ocsp: Optional[Ocsp] = {} - stapling: Optional[OcspStapling] = {} - - -class Listen(BaseModel, extra="forbid"): - address: Optional[str] = "" - http2: Optional[bool] = False - tls: Optional[Tls] = {} - - -class ListenL4(BaseModel, extra="forbid"): - address: Optional[str] = "" - protocol: Optional[str] = "tcp" - tls: Optional[Tls] = {} - - @model_validator(mode='after') - def check_type(self) -> 'ListenL4': - protocol, tls = self.protocol, self.tls - - valid = ['tcp', 'udp'] - if protocol not in valid: - raise ValueError(f"Invalid protocol [{protocol}] must be one of {str(valid)}") - - if protocol != 'tcp' and tls and tls.certificate: - raise ValueError("TLS termination over UDP is not supported") - - return self - - -class Log(BaseModel, extra="forbid"): - access: Optional[str] = "" - error: Optional[str] = "" - - -class RateLimit(BaseModel, extra="forbid"): - profile: Optional[str] = "" - httpcode: Optional[int] = 429 - burst: Optional[int] = 0 - delay: Optional[int] = 0 - - -class LocationAuthClient(BaseModel, extra="forbid"): - profile: Optional[str] = "" - - -class LocationAuthServer(BaseModel, extra="forbid"): - profile: Optional[str] = "" - - -class LocationAuth(BaseModel, extra="forbid"): - client: Optional[List[LocationAuthClient]] = [] - server: Optional[List[LocationAuthServer]] = [] - - -class RateLimitApiGw(BaseModel, extra="forbid"): - profile: Optional[str] = "" - httpcode: Optional[int] = 429 - burst: Optional[int] = 0 - delay: Optional[int] = 0 - enforceOnPaths: Optional[bool] = True - paths: Optional[List[str]] = [] - -class APIGatewayAuthentication(BaseModel, extra="forbid"): - client: Optional[List[LocationAuthClient]] = [] - enforceOnPaths: Optional[bool] = True - paths: Optional[List[str]] = [] - - -class AuthClientJWT(BaseModel, extra="forbid"): - realm: str = "JWT Authentication" - key: str = "" - cachetime: Optional[int] = 0 - jwt_type: str = "signed" - - @model_validator(mode='after') - def check_type(self) -> 'AuthClientJWT': - jwt_type, key = self.jwt_type, self.key - - if not key.strip() : - raise ValueError(f"Invalid: JWT key must not be empty") - - valid = ['signed', 'encrypted', 'nested'] - if jwt_type not in valid: - raise ValueError(f"Invalid JWT type [{jwt_type}] must be one of {str(valid)}") - - return self - -class AuthServerJWT(BaseModel, extra="forbid"): - token: str = "" - - #@model_validator(mode='after') - #def check_type(self) -> 'AuthServerJWT': - # token = self.token - - # if not token.strip(): - # raise ValueError("Invalid JWT token '" + token + "' must not be empty") - - # return self - - -class HealthCheck(BaseModel, extra="forbid"): - enabled: Optional[bool] = False - uri: Optional[str] = "/" - interval: Optional[int] = 5 - fails: Optional[int] = 1 - passes: Optional[int] = 1 - - -class AppProtectLog(BaseModel, extra="forbid"): - enabled: Optional[bool] = False - profile_name: Optional[str] = "" - destination: Optional[str] = "" - - -class AppProtect(BaseModel, extra="forbid"): - enabled: Optional[bool] = False - policy: str = "" - log: AppProtectLog = {} - - -class Location(BaseModel, extra="forbid"): - uri: str - urimatch: Optional[str] = "prefix" - upstream: Optional[str] = "" - log: Optional[Log] = {} - apigateway: Optional[APIGateway] = {} - caching: Optional[str] = "" - rate_limit: Optional[RateLimit] = {} - health_check: Optional[HealthCheck] = {} - app_protect: Optional[AppProtect] = {} - snippet: Optional[str] = "" - authentication: Optional[LocationAuth] = {} - - @model_validator(mode='after') - def check_type(self) -> 'Location': - urimatch = self.urimatch - upstream = self.upstream - - valid = ['prefix', 'exact', 'regex', 'iregex', 'best'] - if urimatch not in valid: - raise ValueError(f"Invalid URI match type [{urimatch}] must be one of {str(valid)}") - - prefixes = ["http://", "https://"] - if upstream != "" and not upstream.lower().startswith(tuple(prefixes)): - raise ValueError(f"Upstream must start with one of {str(prefixes)}") - - return self - - -class Server(BaseModel, extra="forbid"): - name: str - names: Optional[List[str]] = [] - resolver: Optional[str] = "" - listen: Optional[Listen] = {} - log: Optional[Log] = {} - locations: Optional[List[Location]] = [] - app_protect: Optional[AppProtect] = {} - snippet: Optional[str] = "" - - -class L4Server(BaseModel, extra="forbid"): - name: str - listen: Optional[ListenL4] = {} - upstream: Optional[str] = "" - snippet: Optional[str] = "" - - -class Sticky(BaseModel, extra="forbid"): - cookie: str = "" - expires: Optional[str] = "" - domain: Optional[str] = "" - path: Optional[str] = "" - - -class Origin(BaseModel, extra="forbid"): - server: str - weight: Optional[int] = 1 - max_fails: Optional[int] = 1 - fail_timeout: Optional[str] = "10s" - max_conns: Optional[int] = 0 - slow_start: Optional[str] = "0" - backup: Optional[bool] = False - - -class L4Origin(BaseModel, extra="forbid"): - server: str - weight: Optional[int] = 1 - max_fails: Optional[int] = 1 - fail_timeout: Optional[str] = "" - max_conns: Optional[int] = 0 - slow_start: Optional[str] = "" - backup: Optional[bool] = False - - -class Upstream(BaseModel, extra="forbid"): - name: str - origin: Optional[List[Origin]] = [] - sticky: Optional[Sticky] = {} - snippet: Optional[str] = "" - - -class L4Upstream(BaseModel, extra="forbid"): - name: str - origin: Optional[List[L4Origin]] = [] - snippet: Optional[str] = "" - - -class ValidItem(BaseModel, extra="forbid"): - codes: Optional[List[int]] = [200] - ttl: Optional[str] = 60 - - -class CachingItem(BaseModel, extra="forbid"): - name: str - key: str - size: Optional[str] = "10m" - valid: Optional[List[ValidItem]] = [] - - -class RateLimitItem(BaseModel, extra="forbid"): - name: str - key: str - size: Optional[str] = "" - rate: Optional[str] = "" - - -class NginxPlusApi(BaseModel, extra="forbid"): - write: Optional[bool] = False - listen: Optional[str] = "" - allow_acl: Optional[str] = "" - - -class MapEntry(BaseModel, extra="forbid"): - key: str - keymatch: str - value: str - - @model_validator(mode='after') - def check_type(self) -> 'MapEntry': - keymatch = self.keymatch - - valid = ['exact', 'regex', 'iregex'] - if keymatch not in valid: - raise ValueError(f"Invalid key match type [{keymatch}] must be one of {str(valid)}") - - return self - - -class Map(BaseModel, extra="forbid"): - match: str - variable: str - entries: Optional[List[MapEntry]] = [] - - -class Layer4(BaseModel, extra="forbid"): - servers: Optional[List[L4Server]] = [] - upstreams: Optional[List[L4Upstream]] = [] - - -class Authentication_Client(BaseModel, extra="forbid"): - name: str - type: str - - jwt: Optional[AuthClientJWT] = {} - - @model_validator(mode='after') - def check_type(self) -> 'Authentication_Client': - _type, name = self.type, self.name - - valid = ['jwt'] - if _type not in valid: - raise ValueError(f"Invalid client authentication type [{_type}] for profile [{name}] must be one of {str(valid)}") - - return self - - -class Authentication_Server(BaseModel, extra="forbid"): - name: str - type: str - - jwt: Optional[AuthServerJWT] = {} - - -class Authentication(BaseModel, extra="forbid"): - client: Optional[List[Authentication_Client]] = [] - server: Optional[List[Authentication_Server]] = [] - - -class Http(BaseModel, extra="forbid"): - servers: Optional[List[Server]] = [] - upstreams: Optional[List[Upstream]] = [] - caching: Optional[List[CachingItem]] = [] - rate_limit: Optional[List[RateLimitItem]] = [] - nginx_plus_api: Optional[NginxPlusApi] = {} - maps: Optional[List[Map]] = [] - snippet: Optional[str] = "" - authentication: Optional[Authentication] = {} - - -class Declaration(BaseModel, extra="forbid"): - layer4: Optional[Layer4] = {} - http: Optional[Http] = {} - - -class API_Gateway(BaseModel, extra="forbid"): - enabled: Optional[bool] = False - strip_uri: Optional[bool] = False - server_url: Optional[str] = "" - -class DeveloperPortal(BaseModel, extra="forbid"): - enabled: Optional[bool] = False - uri: Optional[str] = "/devportal.html" - -class APIGateway(BaseModel, extra="forbid"): - openapi_schema: Optional[str] = "" - api_gateway: Optional[API_Gateway] = {} - developer_portal: Optional[DeveloperPortal] = {} - rate_limit: Optional[List[RateLimitApiGw]] = [] - authentication: Optional[APIGatewayAuthentication] = {} - log: Optional[Log] = {} - - -class ConfigDeclaration(BaseModel, extra="forbid"): - output: Output - declaration: Optional[Declaration] = {} diff --git a/src/V4_2_CreateConfig.py b/src/V4_2_CreateConfig.py index 61c49f3..9fac133 100644 --- a/src/V4_2_CreateConfig.py +++ b/src/V4_2_CreateConfig.py @@ -681,7 +681,7 @@ def createconfig(declaration: ConfigDeclaration, apiversion: str, runfromautosyn "message": {"status_code": 200, "message": {"code": 200, "content": "no changes"}}} else: # Configuration objects have changed, publish to NIM needed - print(f'Declaration [{configUid}] changed, publishing to NMS') + print(f'Declaration [{configUid}] changed, publishing to NIM' if configUid else f'New declaration created, publishing to NIM') # Retrieve instance group uid try: diff --git a/src/main.py b/src/main.py index a3d1560..73b9085 100644 --- a/src/main.py +++ b/src/main.py @@ -16,9 +16,6 @@ import NcgConfig import NcgRedis -import V4_0_CreateConfig -import V4_0_NginxConfigDeclaration - import V4_1_CreateConfig import V4_1_NginxConfigDeclaration @@ -42,28 +39,6 @@ def runScheduler(): time.sleep(1) -# Submit declaration using v4.0 API -@app.post("/v4.0/config", status_code=200, response_class=PlainTextResponse) -def post_config_v4_0(d: V4_0_NginxConfigDeclaration.ConfigDeclaration, response: Response): - output = V4_0_CreateConfig.createconfig(declaration=d, apiversion='v4.0') - - if type(output) in [Response, str]: - # ConfigMap or plaintext response - return output - - headers = output['message']['headers'] if 'headers' in output['message'] else {'Content-Type': 'application/json'} - - if 'message' in output: - if 'message' in output['message']: - response = output['message']['message'] - else: - response = output['message'] - else: - response = output - - return JSONResponse(content=response, status_code=output['status_code'], headers=headers) - - # Submit declaration using v4.1 API @app.post("/v4.1/config", status_code=200, response_class=PlainTextResponse) def post_config_v4_1(d: V4_1_NginxConfigDeclaration.ConfigDeclaration, response: Response): @@ -108,12 +83,6 @@ def post_config_v4_2(d: V4_2_NginxConfigDeclaration.ConfigDeclaration, response: return JSONResponse(content=response, status_code=output['status_code'], headers=headers) -# Modify declaration using v4.0 API -@app.patch("/v4.0/config/{configuid}", status_code=200, response_class=PlainTextResponse) -def patch_config_v4_0(d: V4_0_NginxConfigDeclaration.ConfigDeclaration, response: Response, configuid: str): - return V4_0_CreateConfig.patch_config(declaration=d, configUid=configuid, apiversion='v4.0') - - # Modify declaration using v4.1 API @app.patch("/v4.1/config/{configuid}", status_code=200, response_class=PlainTextResponse) def patch_config_v4_1(d: V4_1_NginxConfigDeclaration.ConfigDeclaration, response: Response, configuid: str): @@ -126,25 +95,6 @@ def patch_config_v4_2(d: V4_2_NginxConfigDeclaration.ConfigDeclaration, response return V4_2_CreateConfig.patch_config(declaration=d, configUid=configuid, apiversion='v4.2') -# Get declaration - v4.0 API -@app.get("/v4.0/config/{configuid}", status_code=200, response_class=PlainTextResponse) -def get_config_declaration_v4_0(configuid: str): - status_code, content = V4_0_CreateConfig.get_declaration(configUid=configuid) - - if status_code == 404: - return JSONResponse( - status_code=404, - content={'code': 404, 'details': {'message': f'declaration {configuid} not found'}}, - headers={'Content-Type': 'application/json'} - ) - - return JSONResponse( - status_code=200, - content=content, - headers={'Content-Type': 'application/json'} - ) - - # Get declaration - v4.1 API @app.get("/v4.1/config/{configuid}", status_code=200, response_class=PlainTextResponse) def get_config_declaration_v4_1(configuid: str): @@ -185,7 +135,6 @@ def get_config_declaration_v4_2(configuid: str): # Get declaration status -@app.get("/v4.0/config/{configuid}/status", status_code=200, response_class=PlainTextResponse) @app.get("/v4.1/config/{configuid}/status", status_code=200, response_class=PlainTextResponse) @app.get("/v4.2/config/{configuid}/status", status_code=200, response_class=PlainTextResponse) def get_config_status(configuid: str): @@ -206,7 +155,6 @@ def get_config_status(configuid: str): # Delete declaration -@app.delete("/v4.0/config/{configuid}", status_code=200, response_class=PlainTextResponse) @app.delete("/v4.1/config/{configuid}", status_code=200, response_class=PlainTextResponse) @app.delete("/v4.2/config/{configuid}", status_code=200, response_class=PlainTextResponse) def delete_config(configuid: str = ""): diff --git a/src/v4_0/APIGateway.py b/src/v4_0/APIGateway.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/v4_0/DeclarationPatcher.py b/src/v4_0/DeclarationPatcher.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/v4_0/DevPortal.py b/src/v4_0/DevPortal.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/v4_0/GitOps.py b/src/v4_0/GitOps.py deleted file mode 100644 index 9f9d2be..0000000 --- a/src/v4_0/GitOps.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -GitOps support functions -""" - -import base64 -import requests - -from requests import ReadTimeout, HTTPError, Timeout, ConnectionError, ConnectTimeout - - -# Fetches a URL content -def __fetchfromsourceoftruth__(url): - # Object is fetched from external repository - try: - reply = requests.get(url=url, verify=False) - except (ConnectTimeout, HTTPError, ReadTimeout, Timeout, ConnectionError): - return 408, "URL " + url + " unreachable" - - return reply.status_code, reply.text - - -# If content starts with http(s):// fetches the object and return it b64-encoded by default. -# base64Encode to be set to False to disable b64 encoding -# Returns the status original content otherwise. -# Return is a tuple: status_code, content -def getObjectFromRepo(content: str, base64Encode: bool=True): - status_code = 200 - if content.lower().startswith("http://") or content.lower().startswith("https://"): - # Object is fetched from external repository - status_code, content = __fetchfromsourceoftruth__(content) - - if status_code == 200: - if base64Encode == True: - content = base64.b64encode(bytes(content, 'utf-8')).decode('utf-8') - else: - content = bytes(content, 'utf-8').decode("utf-8") - - return status_code, content \ No newline at end of file diff --git a/src/v4_0/MiscUtils.py b/src/v4_0/MiscUtils.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/v4_0/NAPUtils.py b/src/v4_0/NAPUtils.py deleted file mode 100644 index 25cc3ad..0000000 --- a/src/v4_0/NAPUtils.py +++ /dev/null @@ -1,269 +0,0 @@ -""" -NGINX App Protect support functions -""" - -import requests -import json - -import v4_0.GitOps - -from fastapi.responses import Response, JSONResponse - -available_log_profiles = ['log_all', 'log_blocked', 'log_illegal', 'secops_dashboard'] - - -# Define (create/update) a NGINX App Protect policy on NMS. -# If policyUid is not empty a the policy update is performed -# Returns a tuple {status_code,text}. status_code is 201 if successful -def __definePolicyOnNMS__(nmsUrl: str, nmsUsername: str, nmsPassword: str, policyName: str, policyDisplayName: str, - policyDescription: str, policyJson: str, policyUid: str = ""): - # policyBody holds the base64-encoded policy JSON definition - # Control plane-compiled policy bundles are supported. Create the NGINX App Protect policy on NMS - # POST https://{{nms_host}}/api/platform/v1/security/policies - # { - # "metadata": { - # "name": "prod-policy", - # "displayName": "Production Policy - blocking", - # "description": "Production-ready policy - blocking" - # }, - # "content": "" - # } - - policyCreationPayload = {'metadata': {}} - policyCreationPayload['metadata']['name'] = policyName - policyCreationPayload['metadata']['displayName'] = policyDisplayName - policyCreationPayload['metadata']['description'] = policyDescription - policyCreationPayload['content'] = policyJson - - if policyUid != "": - # Existing policy update - r = requests.put(url=f"{nmsUrl}/api/platform/v1/security/policies/{policyUid}", - data=json.dumps(policyCreationPayload), - headers={'Content-Type': 'application/json'}, - auth=(nmsUsername, nmsPassword), - verify=False) - else: - # New policy creation - first try to create it as a new revision for an existing policy - # The response code is 201 if successful and 404 if there is no policy with the given name - r = requests.post(url=f"{nmsUrl}/api/platform/v1/security/policies?isNewRevision=true", - data=json.dumps(policyCreationPayload), - headers={'Content-Type': 'application/json'}, - auth=(nmsUsername, nmsPassword), - verify=False) - - # Check if this is a new policy with no existing versions. If this is true create its initial version - if r.status_code == 404: - r = requests.post(url=f"{nmsUrl}/api/platform/v1/security/policies", - data=json.dumps(policyCreationPayload), - headers={'Content-Type': 'application/json'}, - auth=(nmsUsername, nmsPassword), - verify=False) - - return r - - -# Retrieve security policies information -def __getAllPolicies__(nmsUrl: str, nmsUsername: str, nmsPassword: str): - return requests.get(url=f'{nmsUrl}/api/platform/v1/security/policies', - auth=(nmsUsername, nmsPassword), verify=False) - - -# Delete security policy from control plane -def __deletePolicy__(nmsUrl: str, nmsUsername: str, nmsPassword: str, policyUid: str): - return requests.delete(url=f'{nmsUrl}/api/platform/v1/security/policies/{policyUid}', - auth=(nmsUsername, nmsPassword), verify=False) - - -# Check NAP policies names validity for the given declaration -# Return a tuple: status, description. If status = 200 checks were successful -def checkDeclarationPolicies(declaration: dict): - # NGINX App Protect policies check - duplicated policy names - - # all policy names as defined in the declaration - # { 'policyName': 'activeTag', ... } - allPolicyNames = {} - - if 'policies' not in declaration['output']['nms']: - return 200, "" - - for policy in declaration['output']['nms']['policies']: - # print(f"Found NAP Policy [{policy['name']}] active tag [{policy['active_tag']}]") - - if policy['name'] and policy['name'] in allPolicyNames: - return 422, f"Duplicated NGINX App Protect WAF policy [{policy['name']}]" - - allPolicyNames[policy['name']] = policy['active_tag'] - - # Check policy releases for non-univoque tags - allPolicyVersionTags = {} - for policyVersion in policy['versions']: - # print(f"--> Policy [{policy['name']}] tag [{policyVersion['tag']}]") - if policyVersion['tag'] and policyVersion['tag'] in allPolicyVersionTags: - return 422, f"Duplicated NGINX App Protect WAF policy tag [{policyVersion['tag']}] " \ - f"for policy [{policy['name']}]" - - allPolicyVersionTags[policyVersion['tag']] = "found" - - if policy['active_tag'] and policy['active_tag'] not in allPolicyVersionTags: - return 422, f"Invalid active tag [{policy['active_tag']}] for policy [{policy['name']}]" - - # Check policy names referenced by the declaration inside HTTP servers[]: they must be valid - if 'http' in declaration['declaration'] and 'servers' in declaration['declaration']['http']: - for httpServer in declaration['declaration']['http']['servers']: - if 'app_protect' in httpServer: - if 'policy' in httpServer['app_protect'] and httpServer['app_protect']['policy'] \ - and httpServer['app_protect']['policy'] not in allPolicyNames: - return 422, f"Unknown NGINX App Protect WAF policy [{httpServer['app_protect']['policy']}] " \ - f"referenced by HTTP server [{httpServer['name']}]" - - if 'log' in httpServer['app_protect'] \ - and 'profile_name' in httpServer['app_protect']['log'] \ - and httpServer['app_protect']['log']['profile_name'] \ - and httpServer['app_protect']['log']['profile_name'] \ - not in available_log_profiles: - return 422, f"Invalid NGINX App Protect WAF log profile " \ - f"[{httpServer['app_protect']['log']['profile_name']}] referenced by HTTP server " \ - f"[{httpServer['name']}]" - - # Check policy names referenced in HTTP servers[].locations[] - for location in httpServer['locations']: - if 'app_protect' in location: - if 'policy' in location['app_protect'] and location['app_protect']['policy'] \ - and location['app_protect']['policy'] not in allPolicyNames: - return 422, f"Unknown NGINX App Protect WAF policy [{location['app_protect']['policy']}] " \ - f"referenced by HTTP server [{httpServer['name']}] location [{location['uri']}]" - - if 'log' in httpServer['app_protect'] and httpServer['app_protect']['log'] \ - and httpServer['app_protect']['log']['profile_name'] \ - and httpServer['app_protect']['log']['profile_name'] \ - not in available_log_profiles: - return 422, f"Invalid NGINX App Protect WAF log profile " \ - f"[{httpServer['app_protect']['log']['profile_name']}] referenced by HTTP server " \ - f"[{httpServer['name']}] location [{location['uri']}]" - - return 200, "" - - -# For the given declaration creates/updates NGINX App Protect WAF policies on NGINX Management Suite -# making sure that they are in sync with what is defined in the JSON declaration -# Returns a tuple with two dictionaries: all_policy_names_and_versions, all_policy_active_names_and_uids -def provisionPolicies(nmsUrl: str, nmsUsername: str, nmsPassword: str, declaration: dict): - # NGINX App Protect policies - each policy supports multiple tagged versions - - # Policy names and all tag/uid pairs - # {'prod-policy': [{'tag': 'v1', 'uid': 'ebcf9c7e-0930-450d-8108-7cad30e59661'}, - # {'tag': 'v2', 'uid': 'd18c2eb7-814e-4e4d-90fc-54014eef199e'}], - # 'staging-policy': [{'tag': 'block', 'uid': '9794faa7-5b6c-4ce5-9e68-946f04766bb4'}, - # {'tag': 'xss-ok', 'uid': '7b4b850a-ff9e-42a0-85d0-850171474224'}]} - all_policy_names_and_versions = {} - - # Policy names and active tag uids - # { 'prod-policy': 'ebcf9c7e-0930-450d-8108-7cad30e59661', - # 'staging-policy': '7b4b850a-ff9e-42a0-85d0-850171474224' } - all_policy_active_names_and_uids = {} - - for p in declaration['output']['nms']['policies']: - policy_name = p['name'] - if policy_name: - policy_active_tag = p['active_tag'] - - # Iterates over all NGINX App Protect policies - if p['type'] == 'app_protect': - # Iterates over all policy versions - for policyVersion in p['versions']: - status, policyBody = v3_1.GitOps.getObjectFromRepo(policyVersion['contents']) - - if status != 200: - return JSONResponse( - status_code=422, - content={"code": status, - "details": policyBody} - ) - - # Create the NGINX App Protect policy on NMS - r = __definePolicyOnNMS__( - nmsUrl=nmsUrl, nmsUsername=nmsUsername, nmsPassword=nmsPassword, - policyName=policy_name, - policyDisplayName=policyVersion['displayName'], - policyDescription=policyVersion['description'], - policyJson=policyBody - ) - - # Check for errors creating NGINX App Protect policy - if r.status_code != 201: - return JSONResponse( - status_code=r.status_code, - content={"code": r.status_code, "details": json.loads(r.text)} - ) - else: - # Policy was created, retrieve metadata.uid for each policy version - if policy_name not in all_policy_names_and_versions: - all_policy_names_and_versions[policy_name] = [] - - # Stores the policy version - uid = json.loads(r.text)['metadata']['uid'] - tag = policyVersion['tag'] - - if policy_active_tag == tag: - all_policy_active_names_and_uids[policy_name] = uid - - all_policy_names_and_versions[policy_name].append({'tag': tag, 'uid': uid}) - - return all_policy_names_and_versions, all_policy_active_names_and_uids - - -# Publish a NGINX App Protect WAF policy making it active -# activePolicyUids is a dict { "policy_name": "active_uid", [...] } -# Return True if at least one policy was enabled, False otherwise -def makePolicyActive(nmsUrl: str, nmsUsername: str, nmsPassword: str, activePolicyUids: dict, instanceGroupUid: str): - doWeHavePolicies = False - - for policyName in activePolicyUids: - body = { - "publications": [ - { - "policyContent": { - "name": f'{policyName}', - "uid": f'{activePolicyUids[policyName]}' - }, - "instanceGroups": [ - f'{instanceGroupUid}' - ] - } - ] - } - - doWeHavePolicies = True - r = requests.post(url=f'{nmsUrl}/api/platform/v1/security/publish', auth=(nmsUsername, nmsPassword), - data=json.dumps(body), headers={'Content-Type': 'application/json'}, verify=False) - - return doWeHavePolicies - - -# For the given declaration creates/updates NGINX App Protect WAF policies on NGINX Management Suite -# making sure that they are in sync with what is defined in the JSON declaration -# Returns a tuple: status, response payload -def cleanPolicyLeftovers(nmsUrl: str, nmsUsername: str, nmsPassword: str, currentPolicies: dict): - # Fetch all policies currently defined on the control plane - allNMSPolicies = __getAllPolicies__(nmsUrl=nmsUrl, nmsUsername=nmsUsername, nmsPassword=nmsPassword) - allNMSPoliciesJson = json.loads(allNMSPolicies.text) - - # Build a list of all uids for policies currently available on the control plane whose names match - # currentPolicies (policies that have just been pushed to data plane) - allUidsOnNMS = [] - for p in allNMSPoliciesJson['items']: - if p['metadata']['name'] in currentPolicies: - allUidsOnNMS.append(p['metadata']['uid']) - - allCurrentPoliciesUIDs = [] - for policyName in currentPolicies: - if policyName: - for tag in currentPolicies[policyName]: - allCurrentPoliciesUIDs.append(tag['uid']) - - uidsToRemove = list(set(allUidsOnNMS) - set(allCurrentPoliciesUIDs)) - - for uid in uidsToRemove: - __deletePolicy__(nmsUrl=nmsUrl, nmsUsername=nmsUsername, nmsPassword=nmsPassword, policyUid=uid) - - return diff --git a/src/v4_0/NIMUtils.py b/src/v4_0/NIMUtils.py deleted file mode 100644 index 2fcec4c..0000000 --- a/src/v4_0/NIMUtils.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -NGINX Instance Manager support functions -""" - -import requests -import json - -import v4_0.GitOps - -from fastapi.responses import Response, JSONResponse - - -# Fetch an instance group UID from NGINX Instance Manager -# Return None if not found -def getNIMInstanceGroupUid(nmsUrl: str, nmsUsername: str, nmsPassword: str, instanceGroupName: str): - # Retrieve instance group uid - ig = requests.get(url=f'{nmsUrl}/api/platform/v1/instance-groups', auth=(nmsUsername, nmsPassword), - verify=False) - - if ig.status_code != 200: - return None - - # Get the instance group id - igUid = None - igJson = json.loads(ig.text) - for i in igJson['items']: - if i['name'] == instanceGroupName: - igUid = i['uid'] - - return igUid diff --git a/src/v4_0/OpenAPIParser.py b/src/v4_0/OpenAPIParser.py deleted file mode 100644 index 0e4e355..0000000 --- a/src/v4_0/OpenAPIParser.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -OpenAPI schema parser support functions -""" - -import json - -class OpenAPIParser: - httpMethods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'] - - def __init__(self, openAPISchema): - self.openAPISchema = openAPISchema - - def version(self): - if 'openapi' in self.openAPISchema: - return self.openAPISchema['openapi'] - elif 'swagger' in self.openAPISchema: - return self.openAPISchema['swagger'] - - return None - - def info(self): - return self.openAPISchema['info'] - - def servers(self): - self.allServers = [] - - # Loop over OpenAPI schema servers - if 'servers' in self.openAPISchema: - for server in self.openAPISchema['servers']: - urlName = server['url'] - self.s = {} - self.s['url'] = urlName - - if 'description' in server: - self.s['description'] = server['description'] - - self.allServers.append(self.s) - - return self.allServers - - def paths(self): - self.allPaths = [] - - # Loop over OpenAPI schema paths - if 'paths' in self.openAPISchema: - for path in self.openAPISchema['paths'].keys(): - #print(f"- {path}") - self.p = {} - self.p['path'] = path - self.p['methods'] = [] - - # Loop over path HTTP methods found in schema - for method in self.openAPISchema['paths'][path].keys(): - methodInfo = self.openAPISchema['paths'][path][method] - - if method.upper() in self.httpMethods: - #print(f" - {method} - {methodInfo['description'] if 'description' in methodInfo else ''}") - self.m = {} - self.m['method'] = method - self.m['details'] = {} - - if 'description' in methodInfo and methodInfo['description']: - self.m['details']['description'] = methodInfo['description'] - if 'operationId' in methodInfo and methodInfo['operationId']: - self.m['details']['operationId'] = methodInfo['operationId'] - - self.p['methods'].append(self.m) - - self.allPaths.append(self.p) - - return self.allPaths \ No newline at end of file diff --git a/templates/v4.0/apigateway.tmpl b/templates/v4.0/apigateway.tmpl deleted file mode 100644 index e69de29..0000000 diff --git a/templates/v4.0/auth/client/jwks.tmpl b/templates/v4.0/auth/client/jwks.tmpl deleted file mode 100644 index e69de29..0000000 diff --git a/templates/v4.0/auth/client/jwt.tmpl b/templates/v4.0/auth/client/jwt.tmpl deleted file mode 100644 index e69de29..0000000 diff --git a/templates/v4.0/auth/server/jwt.tmpl b/templates/v4.0/auth/server/jwt.tmpl deleted file mode 100644 index e69de29..0000000 diff --git a/templates/v4.0/configmap.tmpl b/templates/v4.0/configmap.tmpl deleted file mode 100644 index e69de29..0000000 diff --git a/templates/v4.0/http.tmpl b/templates/v4.0/http.tmpl deleted file mode 100644 index e69de29..0000000 diff --git a/templates/v4.0/logformat.tmpl b/templates/v4.0/logformat.tmpl deleted file mode 100644 index e69de29..0000000 diff --git a/templates/v4.0/nginx-conf/mime.types b/templates/v4.0/nginx-conf/mime.types deleted file mode 100644 index e69de29..0000000 diff --git a/templates/v4.0/nginx-conf/nginx.conf b/templates/v4.0/nginx-conf/nginx.conf deleted file mode 100644 index e69de29..0000000 diff --git a/templates/v4.0/stream.tmpl b/templates/v4.0/stream.tmpl deleted file mode 100644 index e69de29..0000000