From e92c3cece3c92cfbbf92909faf8fab0985a392bf Mon Sep 17 00:00:00 2001 From: 65397 Date: Wed, 18 Sep 2024 12:56:00 +0100 Subject: [PATCH] 5.1.3 (#70) * 20240917-01 dev Moesif integration * 20240917-01 Added API visibility integration with Moesif Postman collection updated --- FEATURES.md | 11 + README.md | 3 + ...NX Declarative API.postman_collection.json | 190 ++++++++++-------- etc/config.toml | 2 + src/V5_1_CreateConfig.py | 52 ++++- src/V5_1_NginxConfigDeclaration.py | 30 +++ src/v5_1/NGINXOneOutput.py | 4 +- templates/v5.1/apigateway.tmpl | 4 + templates/v5.1/http.tmpl | 7 +- templates/v5.1/visibility/moesif/http.tmpl | 14 ++ templates/v5.1/visibility/moesif/server.tmpl | 21 ++ 11 files changed, 247 insertions(+), 91 deletions(-) create mode 100644 templates/v5.1/visibility/moesif/http.tmpl create mode 100644 templates/v5.1/visibility/moesif/server.tmpl 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; +