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, deleteTo 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` levelSupport for dataplane-based bundle compilationSecurity 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, deleteTo 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` levelSupport for dataplane-based bundle compilationSecurity 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 keyJWT key fetched from URLBearer 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 keyJWT key fetched from URLBearer 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