diff --git a/FEATURES.md b/FEATURES.md
index 69f65b6..3657543 100644
--- a/FEATURES.md
+++ b/FEATURES.md
@@ -60,6 +60,10 @@ Declaration path `.declaration.http.servers[].locations[].apigateway` defines th
- `developer_portal.type` - developer portal type. `redocly` and `backstage` are currently supported
- `developer_portal.redocly.*` - Redocly-based developer portal parameters. See the [Postman collection](/contrib/postman)
- `developer_portal.backstage.*` - Backstage-based developer portal parameters. See the [Postman collection](/contrib/postman)
+- `visibility[]` - API Gateway visibility
+ - `visibility[].enabled` - enable/disable API gateway visibility
+ - `visibility[].type` - visibility integration type. `moesif` is currently supported
+ - `visibility[].moesif.*` - Moesif visibility parameters. See the [Postman collection](/contrib/postman)
- `authentication` - optional, used to enforce authentication at the API Gateway level
- `authentication.client[]` - authentication profile names
- `authentication.enforceOnPaths` - if set to `true` authentication is enforced on all API endpoints listed under `authentication.paths`. if set to `false` authentication is enforced on all API endpoints but those listed under `authentication.paths`
@@ -80,6 +84,13 @@ See the [Postman collection](/contrib/) for usage examples
| Redocly | X | X | X | Developer portal published by NGINX Plus |
| Backstage.io | | X | X | Backstage YAML manifest generated |
+### NGINX API Gateway use case - Visibility
+
+| Type | API v4.2 | API v5.0 | API v5.1 | Notes |
+|--------------|----------|----------|----------|-----------------------------------------------------------------------------------------------|
+| Moesif | | | X | Integration with Moesif - see https://www.moesif.com/docs/server-integration/nginx-openresty/ |
+
+
### Client authentication
| Type | Description | API v4.2 | API v5.0 | API v5.1 | Notes |
diff --git a/README.md b/README.md
index 087170c..f1d209c 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,7 @@ stateDiagram-v2
DEVP: Developer Portal Service
OUTPUT: Output
REDIS: Redis
+ 3RDPARTY: 3rd Party integrations
DevOps --> Pipeline
Pipeline --> INPUT
@@ -62,6 +63,8 @@ stateDiagram-v2
NDAPI --> OUTPUT
NDAPI --> SOT
SOT --> NDAPI
+ NDAPI --> 3RDPARTY
+ 3RDPARTY --> NDAPI
NDAPI --> REDIS
REDIS --> NDAPI
OUTPUT --> NIM
diff --git a/contrib/postman/NGINX Declarative API.postman_collection.json b/contrib/postman/NGINX Declarative API.postman_collection.json
index c2f6c7e..e1d68ed 100644
--- a/contrib/postman/NGINX Declarative API.postman_collection.json
+++ b/contrib/postman/NGINX Declarative API.postman_collection.json
@@ -1,11 +1,10 @@
{
"info": {
- "_postman_id": "cebe565d-04bd-4e7c-bc1b-f92c751bfea1",
+ "_postman_id": "4ba43073-b4a2-4e07-9da7-bc252c90a1cd",
"name": "NGINX Declarative API",
"description": "Declarative REST API and GitOps automation layer for NGINX Instance Manager and NGINX One\n\n[https://github.com/f5devcentral/NGINX-Declarative-API/](https://github.com/f5devcentral/NGINX-Declarative-API/)",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
- "_exporter_id": "1667416",
- "_collection_link": "https://orange-rocket-1353.postman.co/workspace/NGINX-Declarative-API~8ba6e9c1-a04b-4484-8193-bbb142560553/collection/1667416-cebe565d-04bd-4e7c-bc1b-f92c751bfea1?action=share&source=collection_link&creator=1667416"
+ "_exporter_id": "30973250"
},
"item": [
{
@@ -5681,21 +5680,7 @@
"name": "Get Inventory (legitimate request)",
"request": {
"method": "GET",
- "header": [],
- "url": {
- "raw": "https://apigw.nginx.lab/petstore/store/inventory",
- "protocol": "https",
- "host": [
- "apigw",
- "nginx",
- "lab"
- ],
- "path": [
- "petstore",
- "store",
- "inventory"
- ]
- }
+ "header": []
},
"response": []
},
@@ -5731,27 +5716,7 @@
"name": "Get Inventory (XSS attack)",
"request": {
"method": "GET",
- "header": [],
- "url": {
- "raw": "https://apigw.nginx.lab/petstore/store/inventory?",
- "protocol": "https",
- "host": [
- "apigw",
- "nginx",
- "lab"
- ],
- "path": [
- "petstore",
- "store",
- "inventory"
- ],
- "query": [
- {
- "key": "",
- "value": null
- }
- ]
- }
+ "header": []
},
"response": []
},
@@ -5759,21 +5724,7 @@
"name": "Login without token",
"request": {
"method": "GET",
- "header": [],
- "url": {
- "raw": "https://apigw.nginx.lab/petstore/user/login",
- "protocol": "https",
- "host": [
- "apigw",
- "nginx",
- "lab"
- ],
- "path": [
- "petstore",
- "user",
- "login"
- ]
- }
+ "header": []
},
"response": []
},
@@ -5791,21 +5742,7 @@
]
},
"method": "GET",
- "header": [],
- "url": {
- "raw": "https://apigw.nginx.lab/petstore/user/login",
- "protocol": "https",
- "host": [
- "apigw",
- "nginx",
- "lab"
- ],
- "path": [
- "petstore",
- "user",
- "login"
- ]
- }
+ "header": []
},
"response": []
},
@@ -6190,6 +6127,51 @@
},
"response": []
},
+ {
+ "name": "Petstore API Gateway RateLimit + JWT AuthN/AuthZ + redocly + moesif",
+ "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",
+ "packages": {}
+ }
+ }
+ ],
+ "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 \"ndk_http_module\",\n \"ngx_http_lua_module\"\n ],\n \"certificates\": [\n {\n \"type\": \"certificate\",\n \"name\": \"test_cert\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/{{ngc_api_version}}/testcert.crt\"\n }\n },\n {\n \"type\": \"key\",\n \"name\": \"test_key\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/{{ngc_api_version}}/testcert.key\"\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\": \"Google\",\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 \"type\": \"redocly\",\n \"redocly\": {\n \"uri\": \"/petstore-devportal.html\"\n }\n },\n \"visibility\": [\n {\n \"enabled\": true,\n \"type\": \"moesif\",\n \"moesif\": {\n \"application_id\": \"{{moesif_application_id}}\"\n }\n }\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 }\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 \"nginx_plus_api\": {\n \"write\": false,\n \"listen\": \"127.0.0.1:8080\",\n \"allow_acl\": \"0.0.0.0/0\"\n }\n },\n \"resolvers\": [\n {\n \"name\": \"Google\",\n \"address\": \"8.8.8.8\",\n \"ipv4\": true,\n \"ipv6\": false,\n \"timeout\": \"30s\"\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 AuthN/AuthZ + backstage",
"event": [
@@ -8083,10 +8065,7 @@
"name": "Get Inventory (legitimate request)",
"request": {
"method": "GET",
- "header": [],
- "url": {
- "raw": ""
- }
+ "header": []
},
"response": []
},
@@ -8122,10 +8101,7 @@
"name": "Get Inventory (XSS attack)",
"request": {
"method": "GET",
- "header": [],
- "url": {
- "raw": ""
- }
+ "header": []
},
"response": []
},
@@ -8133,10 +8109,7 @@
"name": "Login without token",
"request": {
"method": "GET",
- "header": [],
- "url": {
- "raw": ""
- }
+ "header": []
},
"response": []
},
@@ -8154,10 +8127,7 @@
]
},
"method": "GET",
- "header": [],
- "url": {
- "raw": ""
- }
+ "header": []
},
"response": []
},
@@ -8497,6 +8467,51 @@
},
"response": []
},
+ {
+ "name": "Petstore API Gateway RateLimit + JWT AuthN/AuthZ + redocly + moesif",
+ "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",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"output\": {\n \"type\": \"nginxone\",\n \"nginxone\": {\n \"url\": \"{{nginxone_url}}\",\n \"namespace\": \"{{nginxone_namespace}}\",\n \"token\": \"{{nginxone_token}}\",\n \"configsyncgroup\": \"{{nginxone_configsyncgroup}}\",\n \"synctime\": 0,\n \"modules\": [\n \"ndk_http_module\",\n \"ngx_http_lua_module\"\n ],\n \"certificates\": [\n {\n \"type\": \"certificate\",\n \"name\": \"test_cert\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/{{ngc_api_version}}/testcert.crt\"\n }\n },\n {\n \"type\": \"key\",\n \"name\": \"test_key\",\n \"contents\": {\n \"content\": \"{{github_gitops_root}}/{{ngc_api_version}}/testcert.key\"\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\": \"Google\",\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 \"type\": \"redocly\",\n \"redocly\": {\n \"uri\": \"/petstore-devportal.html\"\n }\n },\n \"visibility\": [\n {\n \"enabled\": true,\n \"type\": \"moesif\",\n \"moesif\": {\n \"application_id\": \"{{moesif_application_id}}\"\n }\n }\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 }\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 \"nginx_plus_api\": {\n \"write\": false,\n \"listen\": \"127.0.0.1:8080\",\n \"allow_acl\": \"0.0.0.0/0\"\n }\n },\n \"resolvers\": [\n {\n \"name\": \"Google\",\n \"address\": \"8.8.8.8\",\n \"ipv4\": true,\n \"ipv6\": false,\n \"timeout\": \"30s\"\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 AuthN/AuthZ + backstage",
"event": [
@@ -9491,6 +9506,11 @@
"key": "nginxone_configsyncgroup",
"value": "declarativeAPITest",
"type": "string"
+ },
+ {
+ "key": "moesif_application_id",
+ "value": "",
+ "type": "string"
}
]
}
\ No newline at end of file
diff --git a/etc/config.toml b/etc/config.toml
index 3684fe3..4091cd0 100644
--- a/etc/config.toml
+++ b/etc/config.toml
@@ -14,6 +14,7 @@ mimetypes = "nginx-conf/mime.types"
httpconf = "http.tmpl"
apigwconf = "apigateway.tmpl"
+visibility_root = "visibility"
streamconf = "stream.tmpl"
configmap = "configmap.tmpl"
@@ -54,6 +55,7 @@ nginx_conf = '/etc/nginx/nginx.conf'
config_dir = '/etc/nginx'
certs_dir = '/etc/nginx/ssl'
apigw_dir = '/etc/nginx/apigateway'
+visibility_dir = '/etc/nginx/visibility'
devportal_dir = '/etc/nginx/devportal'
auth_client_dir = '/etc/nginx/authn/client'
auth_server_dir = '/etc/nginx/authn/server'
diff --git a/src/V5_1_CreateConfig.py b/src/V5_1_CreateConfig.py
index f171b3f..ca24ee8 100644
--- a/src/V5_1_CreateConfig.py
+++ b/src/V5_1_CreateConfig.py
@@ -444,6 +444,52 @@ def createconfig(declaration: ConfigDeclaration, apiversion: str, runfromautosyn
"message": {"status_code": status, "message":
{"code": status, "content": f"invalid server authentication profile [{authServerProfile['profile']}] in location [{loc['uri']}]"}}}
+ # API Gateway visualization integrations
+ apiGwVisibilityIntegrations = {}
+
+ if loc['apigateway'] and loc['apigateway']['visibility']:
+ visibility_integrations = loc['apigateway']['visibility']
+ for i in range(len(visibility_integrations)):
+ vis = visibility_integrations[i]
+
+ if vis['enabled'] == True:
+ apiGwVisibilityIntegrations[ vis['type'] ] = True
+
+ if vis['type'].lower() == 'moesif':
+ # Moesif integration
+
+ # Add the rendered Moesif visibility configuration snippet as a config file in the staged configuration - HTTP context
+ templateName = NcgConfig.config['templates'][
+ 'visibility_root'] + "/moesif/http.tmpl"
+ renderedMoesifHTTP = j2_env.get_template(templateName).render(
+ vis=vis, loc=loc, ncgconfig=NcgConfig.config)
+
+ b64renderedMoesifHTTP = base64.b64encode(
+ bytes(renderedMoesifHTTP, 'utf-8')).decode(
+ 'utf-8')
+ moesifHTTPConfigFile = {'contents': b64renderedMoesifHTTP,
+ 'name': NcgConfig.config['nms'][
+ 'visibility_dir'] +
+ loc['uri'] + "-moesif-http.conf"}
+
+ auxFiles['files'].append(moesifHTTPConfigFile)
+
+ # Add the rendered Moesif visibility configuration snippet as a config file in the staged configuration - server context
+ templateName = NcgConfig.config['templates'][
+ 'visibility_root'] + "/moesif/server.tmpl"
+ renderedMoesifServer = j2_env.get_template(templateName).render(
+ vis=vis, loc=loc, ncgconfig=NcgConfig.config)
+
+ b64renderedMoesifServer = base64.b64encode(
+ bytes(renderedMoesifServer, 'utf-8')).decode(
+ 'utf-8')
+ moesifServerConfigFile = {'contents': b64renderedMoesifServer,
+ 'name': NcgConfig.config['nms'][
+ 'visibility_dir'] +
+ loc['uri'] + "-moesif-server.conf"}
+
+ auxFiles['files'].append(moesifServerConfigFile)
+
# API Gateway provisioning
if loc['apigateway'] and loc['apigateway']['api_gateway'] and loc['apigateway']['api_gateway']['enabled'] and loc['apigateway']['api_gateway']['enabled'] == True:
openApiAuthProfile = loc['apigateway']['openapi_schema']['authentication']
@@ -458,7 +504,7 @@ def createconfig(declaration: ConfigDeclaration, apiversion: str, runfromautosyn
# API Gateway configuration template rendering
if apiGatewayConfigDeclaration:
apiGatewaySnippet = j2_env.get_template(NcgConfig.config['templates']['apigwconf']).render(
- declaration=apiGatewayConfigDeclaration, ncgconfig=NcgConfig.config)
+ declaration=apiGatewayConfigDeclaration, enabledVisibility=apiGwVisibilityIntegrations, ncgconfig=NcgConfig.config)
apiGatewaySnippetb64 = base64.b64encode(bytes(apiGatewaySnippet, 'utf-8')).decode('utf-8')
newAuxFile = {'contents': apiGatewaySnippetb64, 'name': NcgConfig.config['nms']['apigw_dir'] +
@@ -485,14 +531,14 @@ def createconfig(declaration: ConfigDeclaration, apiversion: str, runfromautosyn
loc['apigateway']['developer_portal']['redocly']['uri']}
auxFiles['files'].append(newAuxFile)
+ ### / Redocly developer portal - Add optional API Developer portal HTML files
elif loc['apigateway']['developer_portal']['type'].lower() == 'backstage':
### Backstage developer portal - Create Kubernetes Backstage manifest
backstageManifest = j2_env.get_template(f"{NcgConfig.config['templates']['devportal_root']}/backstage.tmpl").render(
declaration=loc['apigateway']['developer_portal']['backstage'], openAPISchema = v5_1.MiscUtils.json_to_yaml(openAPISchemaJSON), ncgconfig=NcgConfig.config)
extraOutputManifests.append(backstageManifest)
-
- ### / Backstage developer portal - Create Kubernetes Backstage manifest
+ ### / Backstage developer portal - Create Kubernetes Backstage manifest
# Check rate limit profile name validity
if loc['rate_limit'] is not None:
diff --git a/src/V5_1_NginxConfigDeclaration.py b/src/V5_1_NginxConfigDeclaration.py
index cd255c6..58d669b 100644
--- a/src/V5_1_NginxConfigDeclaration.py
+++ b/src/V5_1_NginxConfigDeclaration.py
@@ -831,10 +831,40 @@ def check_type(self) -> 'DeveloperPortal':
return self
+class Visibility_Moesif(BaseModel, extra="forbid"):
+ application_id: str = ""
+ plugin_path: Optional[str] = "/usr/local/share/lua/5.1/resty/moesif"
+
+
+class Visibility(BaseModel, extra="forbid"):
+ enabled: Optional[bool] = False
+ type: str = ""
+ moesif: Optional[Visibility_Moesif] = {}
+
+ @model_validator(mode='after')
+ def check_type(self) -> 'Visibility':
+ _enabled, _type, _moesif = self.enabled, self.type, self.moesif
+
+ valid = ['moesif']
+
+ if _enabled == True and _type not in valid:
+ raise ValueError(f"Invalid visibility type [{_type}] must be one of {str(valid)}")
+
+ isError = False
+
+ if _type == 'moesif' and not _moesif:
+ isError = True
+
+ if isError:
+ raise ValueError(f"Missing visibility data for type [{_type}]")
+
+ return self
+
class APIGateway(BaseModel, extra="forbid"):
openapi_schema: Optional[ObjectFromSourceOfTruth] = {}
api_gateway: Optional[API_Gateway] = {}
developer_portal: Optional[DeveloperPortal] = {}
+ visibility: Optional[List[Visibility]] = []
rate_limit: Optional[List[RateLimitApiGw]] = []
authentication: Optional[APIGatewayAuthentication] = {}
authorization: Optional[List[APIGatewayAuthorization]] = []
diff --git a/src/v5_1/NGINXOneOutput.py b/src/v5_1/NGINXOneOutput.py
index dbfbd89..fe9290c 100644
--- a/src/v5_1/NGINXOneOutput.py
+++ b/src/v5_1/NGINXOneOutput.py
@@ -38,7 +38,7 @@ def NGINXOneOutput(d, declaration: ConfigDeclaration, apiversion: str, b64HttpCo
b64StreamConf: str,configFiles = {}, auxFiles = {},
runfromautosync: bool = False,
configUid: str = ""):
- # NGINX Instance Manager Staged Configuration publish
+ # NGINX One Cloud Console Staged Configuration publish
nOneToken = v5_1.MiscUtils.getDictKey(d, 'output.nginxone.token')
nOneConfigSyncGroup = v5_1.MiscUtils.getDictKey(d, 'output.nginxone.configsyncgroup')
@@ -213,7 +213,7 @@ def NGINXOneOutput(d, declaration: ConfigDeclaration, apiversion: str, b64HttpCo
#
#### / NGINX App Protect policies support
- ### Publish staged config to instance group
+ ### Publish staged config to config sync group
r = requests.put(url=f'{nOneUrl}/api/nginx/one/namespaces/{nOneNamespace}/config-sync-groups/{igUid}/config',
data=json.dumps(stagedConfig),
headers={'Content-Type': 'application/json', "Authorization": f"Bearer APIToken {nOneToken}"},
diff --git a/templates/v5.1/apigateway.tmpl b/templates/v5.1/apigateway.tmpl
index c222762..512a0bf 100644
--- a/templates/v5.1/apigateway.tmpl
+++ b/templates/v5.1/apigateway.tmpl
@@ -18,6 +18,10 @@
# Strip base URI: {{ declaration.location.apigateway.api_gateway.strip_uri }}
# Destination server: {{ destination_server }}
+{% for v in enabledVisibility %}
+include "{{ ncgconfig.nms.visibility_dir }}{{ declaration.location.uri }}-{{ v }}-server.conf";
+{% endfor %}
+
{% if declaration.paths -%}
{% for path in declaration.paths %}
location {% if '{' not in path.path %}={% else %}~{% endif %} {{ declaration.location.uri }}{{ path.path | regex_replace('{(.*?)}','(.*)') }} {
diff --git a/templates/v5.1/http.tmpl b/templates/v5.1/http.tmpl
index 98bcaf9..5bcc643 100644
--- a/templates/v5.1/http.tmpl
+++ b/templates/v5.1/http.tmpl
@@ -49,6 +49,7 @@ include "{{ ncgconfig.nms.authz_client_dir }}/{{ authzprofile.name | replace(" "
{# --- Upstreams section --- #}
+# Upstreams
{% if declaration.upstreams %}
{% for u in declaration.upstreams %}
{% if u.name %}
@@ -58,13 +59,17 @@ include "{{ ncgconfig.nms.upstream_http_dir }}/{{ u.name | replace(' ', '_') }}.
{% endif %}
{# --- Rate limit section --- #}
-
+# Rate limiting zones
{% if declaration.rate_limit %}
{% for rl in declaration.rate_limit %}
limit_req_zone {{ rl.key }} zone={{ rl.name }}:{{ rl.size }} rate={{ rl.rate }};
{% endfor %}
{% endif %}
+{# --- Visibility integration section --- #}
+# Visibility integrations
+include "{{ ncgconfig.nms.visibility_dir }}/*-http.conf";
+
{# --- Server section for NGINX Plus API --- #}
{% if declaration.nginx_plus_api %}
diff --git a/templates/v5.1/visibility/moesif/http.tmpl b/templates/v5.1/visibility/moesif/http.tmpl
new file mode 100644
index 0000000..ccf38e2
--- /dev/null
+++ b/templates/v5.1/visibility/moesif/http.tmpl
@@ -0,0 +1,14 @@
+# Moesif integration - https://www.moesif.com/docs/server-integration/nginx-openresty/
+# URI: {{ loc.uri }}
+# application ID: {{ vis.moesif.application_id }}
+
+lua_shared_dict moesif_conf 5m;
+
+init_by_lua_block {
+ local config = ngx.shared.moesif_conf;
+ config:set("application_id", "{{ vis.moesif.application_id }}")
+}
+
+lua_package_cpath ";;${prefix}?.so;${prefix}src/?.so;/usr/share/lua/5.1/lua/resty/moesif/?.so;/usr/share/lua/5.1/?.so;/usr/lib64/lua/5.1/?.so;/usr/lib/lua/5.1/?.so;/usr/local/openresty/luajit/share/lua/5.1/lua/resty?.so;/usr/local/share/lua/5.1/resty/moesif/?.so;{{ vis.moesif.plugin_path }}/?.so";
+lua_package_path ";;${prefix}?.lua;${prefix}src/?.lua;/usr/share/lua/5.1/lua/resty/moesif/?.lua;/usr/share/lua/5.1/?.lua;/usr/lib64/lua/5.1/?.lua;/usr/lib/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/lua/resty?.lua;/usr/local/share/lua/5.1/resty/moesif/?.lua;{{ vis.moesif.plugin_path }}/?.lua";
+
diff --git a/templates/v5.1/visibility/moesif/server.tmpl b/templates/v5.1/visibility/moesif/server.tmpl
new file mode 100644
index 0000000..51c58da
--- /dev/null
+++ b/templates/v5.1/visibility/moesif/server.tmpl
@@ -0,0 +1,21 @@
+# Moesif integration - https://www.moesif.com/docs/server-integration/nginx-openresty/
+# URI: {{ loc.uri }}
+# application ID: {{ vis.moesif.application_id }}
+
+# Define the variables Moesif requires
+set $moesif_user_id nil;
+set $moesif_company_id nil;
+set $moesif_req_body nil;
+set $moesif_res_body nil;
+
+# Optionally, set moesif_user_id and moesif_company_id such from a request header or NGINX var to identify customer
+header_filter_by_lua_block {
+ ngx.var.moesif_user_id = ngx.req.get_headers()["X-User-Id"]
+ ngx.var.moesif_company_id = ngx.req.get_headers()["X-Company-Id"]
+}
+
+# Add Moesif plugin
+access_by_lua_file {{ vis.moesif.plugin_path }}/read_req_body.lua;
+body_filter_by_lua_file {{ vis.moesif.plugin_path }}/read_res_body.lua;
+log_by_lua_file {{ vis.moesif.plugin_path }}/send_event.lua;
+