Template Technical Architecture

This document gives an overview of how base and use case templates work in the NGINX Management Suite App Delivery Manager module. It gives instructions and practical guidance on how to customize these templates and use them effectively.


Overview

The App Delivery Manager module uses templates for each of the main NGINX blocks to control the exact format of the generated NGINX configuration. App Delivery Manager uses a set of default built-in templates whenever a new environment is created. These templates provide basic features needed by all app developers when deploying their apps.

These templates can be copied and customized to a degree to support use cases not provided by the built-in templates. All NGINX directives, including directives defined by the rich set of community-provided dynamic modules, are available for use with these custom templates. The additional use cases and enhancements can either be hardcoded into the templates themselves or, by using variables, be made available to app developers as part of the App Delivery Manager API (a feature called customExtensions).

This diagram shows how App Delivery Manager configurations are generated.

App Delivery Manager Objects

  1. The REST API request is processed.
    • The request is validated.
    • Business logic is applied.
    • Template inputs are generated.
  2. The generated template input is validated against the schema.
  3. The base and use case templates are run using the input generated in step 1.
  4. The generated configuration is merged with the base configuration and the configuration from other modules to form the final configuration. This configuration is pushed to the instance groups.

Note:
  • App Delivery Manager templates only control the configuration snippet generated by the App Delivery Manager module.
  • The configuration generated by the App Delivery Manager module is merged with the base configuration and the configuration from other modules to form the final configuration.
  • In the final configuration, the order of directives within a server, location, map, match, upstream, and other blocks will match what is in the template.
  • In the final configuration, the order of main, HTTP, stream, server, upstream, match, map, and other directives will match what is in the template. But the line number of where the App Delivery Manager configuration starts will be different and cannot be controlled via App Delivery Manager templates.

Template Types

The built-in templates are located in the /etc/nms/adm/templates directory. Any custom templates must also be placed in this directory. The content of the template file uses GO Templates syntax. There are two types of templates:

  • Base templates
  • Use case templates

Base Templates

Base templates contain the logic for generating the core parts of the NGINX configuration, like server blocks, location blocks, and upstream blocks. These templates are located in the /etc/nms/modules/adm/templates/base directory. Each API resource (Gateways, Web components, and TCP/UDP components) that generates an NGINX configuration has a template.

API resource Template
Gateways <name>-gateway.tmpl
Web components <name>-web-component.tmpl, <name>-web-component-locations.tmpl
TCP UDP components <name>-tcp-udp-component.tmpl
Warning:
The built-in templates should not be updated, as they may change from release to release. MD5 checks will be built into the code of App Delivery Manager to enforce this.

Gateway

A Gateway configuration is generated using the Gateway base template. The filename suffix -gateway.tmpl is used to identify this template.

Web Component

Web components use two base templates to generate the configuration:

  • Web component locations template: Generates the locations that are injected into Gateway server blocks based on the Gateway references.
  • Web component template: All other configurations, except locations.

The filename suffixes -web-component-locations.tmpl and -web-component.tmpl are used to identify these templates.

TCP/UDP Component

A TCP/UDP component configuration is generated using the TCP/UDP component base template. The filename suffix -tcp-udp-component.tmpl is used to identify this template.

Instance Group

An instance group template renders the final configuration snippet. The filename built-in-v1-instance-group.tmpl is hardcoded and cannot be changed. This template cannot be configured per environment.

Note:
The suffix and the extension of the template file are hidden in the web interface and API. For example, the base template file my-new-base-gateway.tmpl will be referred to using the name my-new-base. The suffix -gateway.tmpl is not visible in the web interface or API.

Use Case Templates

Use case templates contain:

  • The logic for implementing additional use cases that add directives.
  • Block directives (if, match, split_clients…).
  • Arguments to directives generated by the base templates.

Using use case templates, you can layer any NGINX configuration over the base template. The changes are additive, and use cases cannot remove the configuration added by the base templates. Unlike a base template, which is a single file, use case templates are grouped together in a directory under /etc/nms/adm/templates/usecases. This directory contains all the artifacts needed to implement the use case:

  • Template files.
  • Schema files for validation and user interface definition.
  • Documentation.
Warning:
  • We do not recommend implementing use cases in base templates.
  • To create a custom base template, copy one of the built-in templates and replace the prefix built-in-v1 with a unique name.
  • Using custom base templates may break compatibility with built-in use cases.

Folder Name Convention

The folder name matches the name of the use case. After a use case folder is added, the folder’s name can be used to enable the use case on an environment.

Filename Conventions

The suffix of a template name follows the same convention as that of base templates. For example, a use case template with a -gateway suffix will be invoked from a gateway base template.

The template name prefix determines the injection point of the configuration. The injection point is where the directives from this template will be placed inside the configuration generated by the base template. For example, a use case template named main-gateway.tmpl will be invoked when the line {{.ExecUseCaseTemplates "main"}} is invoked in the gateway base template. The use case template is run only if the use case is enabled on the environment.


Template Input

The template input is the data structure passed into the template when it is rendered. The template uses the input data to generate the configuration.

Base Template Data Structure

All base template inputs have a shared wrapper shown below. The structure of the data inside the Data.<versioned_input> field is determined by the type of base template being run. For example, a gateway template input provides data to create server blocks (generated from a Gateway API request), and a web component template input will contain data needed to create location blocks (generated from a Web component API request).

{
	"Data": {
		"v1": {
            ....
		}
	}
}

The input is versioned. We will temporarily maintain compatibility with older versions if breaking changes are made.

Use Case Template Data Structure

All use case template inputs have a shared wrapper shown below. The structure of the data inside the Data.<versioned_input> field is determined by the base template invoking this use case template. For example, if invoked by a gateway template, the Data.<versioned_input> will contain the gateway template input (the same as the one passed into the base template). The field Args contains the arguments passed in from the base template. For example,.ExecUseCaseTemplates "main" "arg1" "arg2" will result in .Args with ["arg1", "arg2"]

{
    "Data": {
        "v1": {
            .....
        }
    },
    "Args": [
        .....
    ]
}

Methods

The template input exposes the methods that can be used in templates. The following methods are available:

  • ExecUseCaseTemplates - Invoke use case template with a specified prefix.
  • JSON - Print the JSON representation of the template input.

Global Functions

Global helper functions are available to make writing templates easier. The following functions are available:

  • MD5 - Computes the MD5 for a string.
  • IsNGINXIpPort - Returns true if a string format is a valid NGINX conf IP, port combination. For example, `10.1.1.1:53, [2001::1]:6464``.
  • Sprig library template functions.

Expanding the App Delivery Manager API Using Use Case Templates

REST APIs for gateways, web components, and TCP/UDP components can be extended to expose use cases. These APIs contain an empty object called customExtensions at several places. Data in customExtensions is passed to the templates verbatim via the template input. Below is an example of the extension points in the Gateway API and the corresponding template input generated from the API request. In the template, the data in customExtensions can be used to augment the NGINX configuration with additional directives required for the use case.


API and corresponding template input


Template Schema

Adding the logic for the use case to the templates and enabling it in the environment is not sufficient. You need to expose the use case settings to the API and web interface users and protect against malformed and malicious input. You can do this by using the JSON schema associated with the base and use case templates. The JSON schema has two purposes:

  • Validate the template input generated from an API request.
  • The user interface uses the schema definitions under customExtensions to augment the web interface with the use case fields.
Warning:
The web interface has limited support for the JSON schema. To learn more, refer to the topic Web Interface JSON Schema Support .

Base Template Schema

Each base template can be associated with a JSON schema with the same filename, but with .json extension.

Warning:
Modifying the base template schemas is not recommended.

Use Case Template Schema

A use case can be used to expand the REST API for gateway, web component, and TCP/UDP component resources. Depending on which API the use case is exposed on, a use case directory can have four schemas:

  1. gateway.json - Validates the gateway template input and augments the gateway user interface.
  2. web-component.json - Validates the web component template input and augments web component user, except the user interface inside the URI.
  3. web-component-locations.json - Validates the web component template input and augments ONLY the fields inside the URI.
  4. tcp-udp-component.json - Validates the TCP/UDP component template input and augments the TCP/UDP component user interface.

The JSON schema for any of the templates follows the following basic format. A detailed example can be found in the built-in use cases.

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
        "v1": {
            "type": "object",
            "properties": {
                "customExtensions": {
                    "type": ["object", "null"],
                    "properties": {
                    },
                    "required": []
                }
            }
        }
    }
}

Template Writing Best Practices

Templates use REST API data to implement features. A malicious API request can inject NGINX configurations via the REST API. In order to avoid this, it is essential to:

  • Validate all API customExtensions data using the JSON schema.
  • Use best practices while writing the template to prevent configuration injection.

Configuration Injection Example

// Unsafe
proxy_set_header {{$headerName}} {{$headerValue}};

// Safe
proxy_set_header '{{$headerName}}' '{{$headerValue}}';

In the first example a $headerValue of value-1; location = /injected {proxy_pass http://foobackend;} can be used to inject a malicious location block into the configuration.


Upgrading and Supportability

App Delivery Manager ships with built-in templates and use cases. Future App Delivery Manager versions may change the base and use case templates. Modifying them is not supported and may cause upgrades to fail. App Delivery Manager will also prevent these modifications through MD5 checks. The following methods are recommended for enhancing templates:

  • Do not modify the built-in base or use case templates.
  • Do not implement use cases in base templates.
  • If a base template needs to be modified (for example, to add additional injection points), create a copy of the base template and make the modifications.
  • Implement use cases in the /usecases directory.