API Guide - Services--HostedService-Spec-Syntax

Service Metadata

type Metadata struct {
  Name        string `json:"name"`
  Description string `json:"description"`
  ClusterName string `json:"cluster"`
}

Service Spec

type Spec struct {
  Attributes   `json:"attributes"`
  Backend      `json:"backend"`
  CertSettings `json:"cert_settings"`
  HTTPSettings `json:"http_settings"`
  ClientCIDRs  []ClientCIDRs `json:"client_cidrs"`
}

type Attributes

Attributes define the major user- and admin- visible properties of a Service.

type Attributes struct {
  // TLSSNI (optional) tells Netagent to use Server Name Indication (SNI) for
  // TLS connections.
  // If TLSSNI is NOT set, Netagent transparently forwards on all TCP
  // connections for Policy validation and then onto its original
  // destination.
  // If TLSSNI is set, Netagent will reject all non-TLS connections;
  // it will only forward on TLS connections where the SNI matches
  // for Policy validation
  TLSSNI            []string            `json:"tls_sni"`
  // FrontendAddresses (mandatory) is used to specifcy the IP addresses and
  // Ports the service listens on.
  // Netagent will intercept traffic that matches FrontendAddresses and,
  // if a Policy is attached, enforce policies
  FrontendAddresses []FrontendAddress   `json:"frontend_addresses"`
  // HostTagSelector (optional) tells Netagent to intercept FrontendAddresses
  // on only a specific subset of Hosts
  HostTagSelector   []map[string]string `json:"host_tag_selector"`
}

type FrontendAddress

FrontendAddress is used to specify the IP addresses and Ports the service listens on

type FrontendAddress struct {
  // CIDR (optional) is a list of IP addresses specified in CIDR notation
  // that the Service should match
  CIDR string `json:"cidr"`
  // Port (mandatory) is the port that the Service listens on
  Port string `json:"port"`
}

type CertSettings

CertSettings specifies the X.509 server certificate to use for this Service

type CertSettings struct {
  // Letsencrypt flag will be used 
  // whether to request a letsencrypt certificate for given domains
  Letsencrypt bool `json:"letsencrypt"`
  // DNSNames specifies how to populate the CommonName field in the X.509
  // server certificate for this Service;
  // if DNSNames is not specified the CommonName field will be set to the
  // ServiceName
  DNSNames      []string `json:"dns_names"`
  CustomTLSCert `json:"custom_tls_cert"`
}

type HTTPSettings

HTTPSettings is used by HTTP services for provide specific functionality

type HTTPSettings struct {
  Enabled         bool `json:"enabled"`
  OIDCSettings    `json:"oidc_settings"`
  HTTPHealthCheck `json:"http_health_check"`
  ExemptedPaths   `json:"exempted_paths"`
  // Headers is a list of HTTP headers to add to every request sent to the Backend;
  // the key of the map is the header name, and the value is the header value you want.
  // The header value may be constructed using Go template syntax, such as {{.Email}} 
  // referencing values in Banyan's JWT TrustToken
  Headers         map[string]string `json:"headers" toml:"headers"`
}

type ClientCIDRs

ClientCIDRs is used in environments with Network Address Translation (NAT) to list the IP addresses that are used to access the Service; Netagent will then automatically intercept traffic to these IP addresses.

type ClientCIDRs struct {
  // Addresses is a list of IP addresses specified in CIDR notation
  Addresses       []CIDRAddress       `json:"addresses"`
  // HostTagSelector tells Netagent to set Client CIDRs on only a specific
  // subset of Hosts
  HostTagSelector []map[string]string `json:"host_tag_selector"`
  // Clusters tells Netagent to set Client CIDRs on only a specific subset
  // of Clusters
  Clusters        []string            `json:"clusters"`
}

type CIDRAddress

CIDRAddress uses the Classless Inter-Domain Routing (CIDR) format for flexible allocation of IP addresses

type CIDRAddress struct {
  CIDR  string `json:"cidr"`
  Ports string `json:"ports"`
}

type OIDCSettings

OIDCSettings provides Netagent specific parameters needed to use OpenID Connect to authenticate an Entity for access to a Service

type OIDCSettings struct {
    // Enabled (mandatory) turns on the OIDC capability
    Enabled                         bool              `json:"enabled" toml:"enabled"`
    // ServiceDomainName (mandatory, ex: https://myapp.myorg.com:8123) is
    // the URL used to access the service
    ServiceDomainName               string            `json:"service_domain_name" toml:"service_domain_name"`
    // PostAuthRedirectPath (optional, default: /) is the path to return the
    // user to after OpenID Connect flow
    PostAuthRedirectPath            string            `json:"post_auth_redirect_path" toml:"post_auth_redirect_path"` // has default value "/"
    // APIPath (optional, default: /api) is the path serving AJAX requests;
    // if a request is not authenticated, paths starting with the APIPath
    // will receive a 403 Unauthorized response,
    // instead of a 302 Redirect to the authentication provider
    APIPath                         string            `json:"api_path" toml:"api_path"`                               // has default value "/api"
    TrustCallBacks                  map[string]string `json:"trust_callbacks" toml:"trust_callbacks"`                 // for multiple redirect URLs
    // SuppressDeviceTrustVerification disables Device Trust Verification for a service if set to true
    SuppressDeviceTrustVerification bool              `json:"suppress_device_trust_verification" toml:"suppress_device_trust_verification"`
}

type HTTPHealthCheck

HTTPHealthCheck tells Netagent that specific HTTP paths should be exempted from access control policies

type HTTPHealthCheck struct {
  // Enabled (mandatory) turns on the HTTP health check capability
  Enabled     bool     `json:"enabled"`
  // Path (mandatory) specifies the HTTP health check path
  Path        string   `json:"path"`
  // Method (optional, default: GET) specifies the health check HTTP method
  Method      string   `json:"method"`  
  // UserAgent (optional, default: no check) is a string to check for in
  // the HTTP user-agent header
  UserAgent   string   `json:"user_agent"`
  // FromAddress (optional, default: allow all) is a string array of
  // allowed source addresses of the health checker
  FromAddress []string `json:"from_address"`
  // HTTPS (optional, default: false) specifies that the health check
  // uses HTTPS instead of HTTP
  HTTPS       bool     `json:"https"`
}

type CustomTLSCert

CustomTLSCert enables Netagent to override the default behavior of obtaining a X.509 server certificate for this Service from Shield, and instead use a TLS certificate on the local file system

type CustomTLSCert struct {
  // Enabled (mandatory) turns on the custom TLS certificate capability
  Enabled  bool   `json:"enabled"`
  // CertFile (mandatory) specifies the local path of the Public
  // Certificate (ex: /etc/letsencrypt/live/intks.net-0001/fullchain.pem)
  CertFile string `json:"cert_file"`
  // KeyFile (mandatory) specifies the local path of the Private
  // Key (ex: /etc/letsencrypt/live/www.example.com/privkey.pem)
  KeyFile  string `json:"key_file"`
}

type Backend

Backend specifies how Netagent, when acting as a reverse proxy, forwards incoming “frontend connections” to a backend workload instance that implements a registered service.

type Backend struct {
  // BackendTarget specifies the backend workload instance's address or name,
  // ports, and TLS properties.
  BackendTarget `json:"target"`
  // BackendDNSOverrides is an optional section that specifies name-to-address or name-to-name mappings.
  // Name-to-address mapping could be used instead of DNS lookup. Format is "FQDN: ip_address".
  // Name-to-name mapping could be used to override one FQDN with the other. Format is "FQDN1: FQDN2"
  // Example: name-to-address -> "internal.myservice.com" : "10.23.0.1"
  //          name-to-name    ->    "exposed.service.com" : "internal.myservice.com"
  BackendDNSOverrides map[string]string `json:"dns_overrides"`
  // BackendWhitelist is an optional section that indicates the allowed names for
  // the backend workload instance. If this field is populated, then the backend name must
  // match at least one entry in this field list to establish connection with the backend service.
  // The names in this list are allowed to start with the wildcard character "*" to match more
  // than one backend name. 
  // This field is used generally with HttpConnect=FALSE. For all HttpConnect=TRUE cases, or where 
  // more advanced backend defining patterns are required, use BackendAllowPatterns.
  BackendWhitelist []string `json:"whitelist"`
  // BackendAllowPatterns is an optional section defines the patterns for the backend workload
  // instance. If the BackendAllowPatterns is populated, then the backend must match at least one entry
  // in this list to establish connection with the backend service. 
  // Note that this field is effective only when BackendWhitelist is not populated.
  // If BackendWhitelist and BackendAllowPatterns are both not populated, then all backend
  // address/name/port are allowed.
  // This field could be used with httpConnect set to TRUE or FALSE. With HttpConnect set to FALSE, 
  // only backend hostnames are supported, all other fields are ignored. With HttpConnect set to TRUE, 
  // all fields of BackendAllowPatterns are supported and effective.
  BackendAllowPatterns []BackendAllowPattern `json:"allow_patterns,omitempty"`
  // HttpConnect is an optional setting that indicates to use HTTP Connect request to derive
  // the backend target address.
  HttpConnect bool `json:"http_connect,omitempty"`
}

type BackendTarget

BackendTarget specifies the backend workload instance’s name, ports, and TLS properties.

type BackendTarget struct {
  // NameDelimiter is an optional string used to separate the initial component of
  // the frontend domain name into multiple parts, e.g., if name delimiter is "--" and
  // the frontend domain name is "abc--def.example.com", then the resulting parts are
  // "abc", "def", "example", and "com". These parts can be used in the backend target
  // name, e.g., "{{index .Parts 0}}.{{index .Parts 1}}.ec2.internal" (which would
  // get instantiated as "abc.def.ec2.internal").
  NameDelimiter string `json:"name_delimiter,omitempty" toml:"name_delimiter,omitempty"`

  // Name specifies the name of the backend workload instance.
  // If it is the empty string, then netagent will use the destination
  // IP address of the incoming frontend connection as the workload
  // instance's address.
  // If non-empty, Name can be a plain string, or a Go template string
  // containing "{{ .Name }}" and/or "{{ .Domain }}" for extracting portions
  // of the client SNI. For example, if SNI is "www.example.com",
  // the "{{ .Name }}" corresponds to "www" and the "{{ .Domain }}"
  // corresponds to "example.com".
  // As a concrete example, "{{ .Name }}-internal.{{ .Domain }}"
  // maps to "www-internal.example.com" for client SNI "www.example.com".
  Name string `json:"name"`
  // Port specifies the backend server's TCP port number.
  Port string `json:"port"`
  // TLS indicates whether the connection to the backend server uses TLS.
  TLS bool `json:"tls"`
  // TLSInsecure indicates whether the backend TLS connection does not
  // validate the server's TLS certificate"
  TLSInsecure bool `json:"tls_insecure"`
  // ClientCertificate indicates whether to provide netagent's client TLS
  // certificate to the server if the server asks for it in the TLS handshake.
  ClientCertificate bool `json:"client_certificate"`
}

type BackendAllowPattern struct {
	// Allowed hostnames my include a leading and/or trailing wildcard character "*"
	// to match multiple hostnames
	Hostnames []string `json:"hostnames,omitempty"`
	// Host may be a CIDR such as 10.1.1.0/24
	CIDRs []string `json:"cidrs,omitempty"`
	// List of allowed ports and port ranges
	Ports *BackendAllowPorts `json:"ports,omitempty"`
}

type BackendAllowPorts struct {
	// List of allowed ports
	PortList []int `json:"port_list,omitempty"`
	// List of allowed port ranges
	PortRanges []PortRange `json:"port_ranges,omitempty"`
}

type PortRange struct {
	// Min and Max values of the port range
	Min int `json:"min"`
	Max int `json:"max"`
}

Example-1 Backend JSON

This example fills in two fields just for demonstration. In particular, both ipv4 and name are filled in, but really a valid spec would have only one of the two fields filled in.

{
  "backend": {
    "target": {
      "name": "{{ .Name }}-internal.{{ .Domain }}",
      "port": "8080",
      "tls": true,
      "tls_insecure": false,
      "client_certificate": true
    },
    "dns_overrides": {
      "jira-internal.foo": "10.0.142.5",
      "gitlab-internal.foo": "10.0.142.6",
      "phase1-test.foo": "10.0.152.27",
      "phase2-test.foo": "10.0.152.38"
    },
    "whitelist": [
      "jira-internal.foo",
      "gitlab-internal.foo",
      "*-test.foo"
    ]
  }
}

Example-2 Backend JSON

This example is similar to the previous one, except it shows a usecase with HttpConnect=TRUE. We are using the BackendAllowPatterns field instead of BackendWhitelist field, since it provides the ability to specify the allowed backend services by CIDR/hostnames/ports etc.

{
  "backend": {
    "dns_overrides": {
      "jira-internal.foo": "10.0.142.5",
      "gitlab-internal.foo": "10.0.142.6",
      "phase1-test.foo": "10.0.152.27",
      "phase2-test.foo": "10.0.152.38"
    },
    "whitelist": [],
    "allow_patterns": [
      {
        "hostnames": [
          "jira-internal.foo",
          "gitlab-internal.foo"
        ],
        "cidrs": [
          "10.0.34.2",
          "34.70.156.134"
        ],
        "ports": {
          "port_list": [
            80,
            5901
          ],
          "port_ranges": [
            {
              "Min": 5905,
              "Max": 5911
            }
          ]
        }
      },
      {
        "hostnames": [
          "*test.foo"
        ],
      }
    ],
    "http_connect": true
  }
}

type ExemptedPaths

ExemptedPaths tells Netagent that specific HTTP paths should be whitelisted/exempted from OIDC authentication

type ExemptedPaths struct {
  // Enabled (mandatory)- turns on the HTTP path whitelist capability
  Enabled bool     `json:"enabled"`
  // Paths (optional)- specifies the HTTP whitelist paths. HTTP Requests to these matching paths
  // will be exempted from OIDC. Paths could be defined as wildcards like /*, /banyan/*, /banyan* also.
  // Only HTTP request paths are matched, no other fields of the HTTP request header is checked.
  // In cases where other HTTP request fields like headers/methods need to be matched, plz use Patterns.
  // Http requests are first matched with paths defined here. If no match, only then requests are matched with Patterns.
  Paths   []string `json:"paths,omitempty"`
  // Patterns (optional)- specifies the attributes matched in HTTP request to exempt from OIDC authentication.
  // Check the exemptedPatterns below for details.
  Patterns []Pattern `json:"patterns,omitempty"`
}

type Pattern

Pattern tells Netagent to exempt HTTP requests based on matching HTTP request attributes, such as source IP, host, headers, methods, paths, etc. For example, use this section when exempting CORS requests by source IP address.

type Pattern struct {
    // SourceCIDRs (optional)- specifies the source IP address of the HTTP request. The matching request should match or should be in the range of the CIDR specified.
    // SourceCIDRs is an array and multiple CIDRs with/without prefix could be specified like, 127.0.0.1, 192.168.1.0/29, 10.0.0.0/8 etc.
    // If source-ip matching is not required, please skip this field. Please check the examples below.
    SourceCIDRs []string `json:"source_cidrs,omitempty" toml:"source_cidrs"`
    // Hosts (mandatory) is the host/origin header values in the HTTP request, please check "Host" structure for details
    Hosts []Host   `json:"hosts" toml:"hosts"`
    // Methods (mandatory) matches the HTTP request methods. The matching request will have any one of the listed methods.
    // To list all the methods supported "like GET/POST/OPTIONS etc.
    // ["*"] value will have "DONT CARE" effect and will skip matching methods.
	  Methods []string `json:"methods"`
    // MandatoryHeaders (mandatory) matches the HTTP request headers. The matching request will have all of the headers listed.
    // To list all the headers that a matching HTTP request should have for instance "Content-Type"/"Access-Control-Allow-Origin" etc.
    // ["*"] will have "DONT CARE" effect and will skip matching headers.
    MandatoryHeaders []string `json:"mandatory_headers"`
    // Paths (mandatory) matches the HTTP request URI. The matching request will have any one of the paths/strings listed.
    // It follows the same semantics as paths in above ExemptedPaths definition.
	Paths []string `json:"paths"`
}

type Host struct {
    // OriginHeader (mandatory)-is list of web host address. The web-host address matches to contents of Origin header in the HTTP request.
    // The value should have "scheme:host:port", ex: "https://www.banyansecurity.io:443". This field supports single domain wildcards also, like
    // https://*.banyansecurity.com or https://api.*.banyansecurity.com:443
    OriginHeader []string `json:"origin_header"`
    // Target (mandatory) list of web host address. In this web-host address, the hostname matches to host header in the HTTP request.
    // The value should have "scheme:host:port", ex: https://www.banyansecurity.io:443. This field supports single domain wildcards also, like
    // https://*.banyansecurity.com or https://api.*.banyansecurity.com:443.  This should be filled only while hosting multi-domain services. In single domain
    // service deployments, this field to be filled as [*] to have "DONT CARE" effect.
    Target       []string `json:"target"`
}

Example - Single Domain case

Consider domain-b allowing CORS requests originating from domain-a.

To achieve the above result, the service definition in JSON is:

  • OriginHeader : Will have the content of origin header
  • target : Will be DONT CARE *
{
    "exempted_paths": {
    "enabled": true,
    "paths": [ ],
    "patterns": [
        {
            "hosts": [
              {
                "origin_header": ["https://www.domain-a.com:443"],
                "target": ["*"]
              }
            ],
            "methods": [
                "GET", "POST", "OPTIONS"
            ],
            "mandatory_headers": [
                "origin"
            ],
            "paths": [
                "/index.html"
            ]
        }
      ]
    }
}

Example - Multi-Domain case

Consider multiple subdomain services being protected by the same Netagent. Ex: Considering app.domain.com, api.domain.com, games.domain.com, everybody.domain.com are the hosted services.

If api.domain.com wants to support CORS request and allow only those originating from app.domain.com and games.domain.com.

To achieve the above result, the service definition in JSON is:

  • OriginHeader : Will have the content of origin header which is app.domain.com, games.domain.com
  • Target: As the registered service matches *.domain.com, the exact target api.domain.com that is receiving the CORS request needs to be specified
{
  "exempted_paths": {
    "paths": [],
    "enabled": true,
    "patterns": [
      {
        "hosts": [
          {
            "origin_header": [
              "https://www.app.domain.com:443",
              "https://www.games.domain.com:443"
            ],
            "target": [
              "https://www.api.domain.com:443"
            ]
          }
        ],
        "methods": [
          "GET",
          "POST",
          "OPTIONS"
        ],
        "mandatory_headers": [
          "origin"
        ],
        "paths": [
          "/index.html"
        ]
      }
    ]
  }
}

Example - Single Domain case with source CIDR exemption

Consider domain-b allowing CORS requests originating from domain-a. If this has to be applied to requests with specific source IP address, say 30.0.0.0/24 or 10.0.0.5/32.

To achieve the above result, the service definition in JSON is:

  • SourceCIDRs : list the above source CIDRs to match
  • OriginHeader : Will have the content of origin header
  • target : Will be DONT CARE *
{
    "exempted_paths": {
    "enabled": true,
    "paths": [ ],
    "patterns": [
        {
            "source_cidrs": ["30.0.0.0/24", "10.0.0.5/32"],
            "hosts": [
              {
                "origin_header": ["https://www.domain-a.com:443"],
                "target": ["*"]
              }
            ],
            "methods": [
                "GET", "POST", "OPTIONS"
            ],
            "mandatory_headers": [
                "origin"
            ],
            "paths": [
                "/index.html"
            ]
        }
      ]
    }
}

Example - Just Source CIDR exemption

Consider the scenario to exempt OIDC for HTTP requests with specific source IP address, say 20.0.0.1 or 192.168.1.0/29.

To achieve the above result, the service definition in JSON is:

  • SourceCIDRs : list the above source CIDRs to match
  • Rest of the patterns field to have DONT-CARE value as below
{
    "exempted_paths": {
    "enabled": true,
    "paths": [ ],
    "patterns": [
        {
            "source_cidrs": ["20.0.0.1", "192.168.1.0/29"],
            "hosts": [
              {
                "origin_header": ["*"],
                "target": ["*"]
              }
            ],
            "methods": [
                "*"
            ],
            "mandatory_headers": [
                "*"
            ],
            "paths": [
                "/*"
            ]
        }
      ]
    }
}

Headers

Headers is a list of HTTP headers to add to every authenticated request sent to the Backend; the key of the map is the header name, and the value is the header value you want. The header value may be constructed using Go template syntax, such as {{.Email}}, referencing values in Banyan’s JWT TrustToken.

Example - string constants

A simple example is depicted below. This will add a HTTP header X-Foo: bar to all requests.

{
  "headers": {
    "X-Foo": "bar"
  }
}

Example - templating

A more complex example is depicted below.

{
  "headers": {
    "X-Email": "{{.Email}}",
    "X-Groups": "{{.Groups}}",
    "X-DevicePlatform": "{{.Platform}}",
    "Y-User": "{{.Email.User}}",
    "Y-Domain": "{{.Email.Domain}}",
    "Z-Token": "{{.JWT}}"
  }
}

The template values will be filled out from a Banyan TrustToken, that looks like:

{
  "at_hash": "sJEbAwFXmfUVTDiPuBJEEQ",
  "aud": "Yeqzf9nMdKzCnal-1SeJAg",
  "device_id": "9c93cb4788be663fedf2",
  "email": "adam@medsoft.digital",
  "email_verified": true,
  "exp": 1616091761,
  "groups": [
    "Everyone",
    "Admins"
  ],
  "https://banyansecurity.io": {
    "device_ownership": "Corporate Dedicated",
    "is_staged_install": false,
    "mdm_present": false,
    "orgname": "test-drive",
    "platform": "Windows"
  },
  "iat": 1616005361,
  "iss": "https://test-drive.trust-preview.banyanops.com/v2",
  "name": "Adam Admin",
  "nonce": "D3zDrJaFolAiI5KnwEvpr",
  "preferred_username": "adam@medsoft.digital",
  "serial_number": "9c93cb4788be663fedf2",
  "sub": "ChQwMHU1b3E4OXU5aWxFZDVnVTVkNhIUdGVzdC1kcml2ZS1jb25uZWN0b3I"
}

The HTTP headers sent to the backend would then be:

X-Email: adam@medsoft.digital
X-Groups: Everyone,Admins
X-DevicePlatform: Windows
Y-User: adam
Y-Domain: medsoft.digital
Z-Token: eyJhbGci...

Variables

The following variables in the TrustToken are available for use in the template syntax:

DeviceID            string
SerialNumber        string
Platform            string
DeviceOwnership     string
Name                string
Email               string
Groups              []string

A few additional variables are also made available for your use:

Email.User          part of the user's email address before the `@`
Email.Domain        part of the user's email address after the `@`
JWT                 the entire JSON Web Token payload

Note that the variables in the template syntax are in CamelCase, while the keys in the JWT token are in snake_case. So, you if you needed to reference device_ownership in a header using the template syntax, use .DeviceOwnership.


Last modified: Jul 22, 2021