Examples

TxnBox is a large, complex plugin and it can be challenging to get started. This section provides a number of example uses, all of which are based on actual production use of TxnBox.

Default Accept-Encoding

Goal

Force all proxy requests to have a value for the "Accept-Encoding" field. If not already set, it should be set to "identity".

    - when: proxy-req
      do:
      - proxy-req-field<Accept-Encoding>: [ proxy-req-field<Accept-Encoding> , { else: "identity" } ]

This acts on the proxy request hook. The "Accept-Encoding" field is extracted and then modified using the else which modifies the feature only if the feature is null or the empty string.

Traffic Ramping

For the purposes of this example, there is presumed to exist a remap rule for a specific externally visible host, "base.ex". A new version is being staged on the host "stage.video.ex". The goal is to redirect a fixed percentage of traffic from the existing host to the staging host, in way that is easy to change. In addition it should be easy to have multiple ramping values for different URL paths. The paths for the two hosts are identical, only the host for the request needs to be changed.

The simplest way to do this would be

- with: pre-remap-path
  select:
  - any-of:
    - prefix: "v1/video/search/"
    - prefix: "v1/video/alias/"
    do:
    - with: random
      select:
      - lt: 30
        do:
        - ua-req-host: "stage.video.ex"
  - prefix: "v1/video/channels/"
    do:
    - with: random
      select:
      - lt: 10
        do:
        - ua-req-host: "stage.video.ex"

This has two buckets, the first at 30% and the second at 10%. random is used to generate random numbers in the range 0..99 which means the extracted value is lt: 30 roughly 30 times out of every hundred, or 30% of the time. The buckets are selected by first checking the pre-remap path (so that it is not affected by other plugins which may run earlier in remapping). Two paths are in the 30% bucket and one in the 10% bucket. Adding additional paths is easy, as is changing the percent diversion. Other buckets can be added with little effort.

This can be done in another way by generating the random value once and checking it multiple times. Given the no backtrack rule, this is challenging to do by checking the percentage first. Instead the use of tuples makes it possible to check both the value and the path together.

- with: [ random, pre-remap-path ]
  select:
  - as-tuple:
    - lt: 30
    - any-of:
      - prefix: "v1/video/search/"
      - prefix: "v1/video/alias/"
    do:
    - ua-req-host: "stage.video.ex"
  - as-tuple:
    - lt: 10
    - prefix: "v1/video/channels/"
    do:
    - ua-req-host: "stage.video.ex"

The with is provided with a tuple of size 2, the random value and the pre-remap path. Each comparison uses as-tuple to perform parallel comparisons on the tuple. The first comparison is applied to the first tuple element, the value, and the second comparison to the second value, the path. Because there is no nested with there is no need to backtrack.

It might be reasonable to split every path in to a different bucket to make adjusting the percentage easier. In that case the previous example could be changed to look like

- with: [ random, pre-remap-path ]
  select:
  - any-of:
    - as-tuple:
      - lt: 30
      - prefix: "v1/video/search/"
    - as-tuple:
      - lt: 30
      - prefix: "v1/video/alias/"
    - as-tuple:
      - lt: 10
      - prefix: "v1/video/channels/"
    do:
    - ua-req-host: "stage.video.ex"

This style presumes the bucket action is identical for all buckets. If not, the previous style would be better. Note the do is attached to the any-of so that if any of the nested comparisons succeed the action is performed.

Static File Serving

TxnBox enables serving defined content. This can be done with the upstream-rsp-body directive to replace content from the upstream with configuration specified content. This is enhanced with "text blocks" which allow obtaining content from external files.

Goal

Provide a default security.txt file if an upstream doesn't provide one.

Example configuration

    - when: upstream-rsp
      do:
      - with: [ upstream-rsp-status , proxy-req-path ]
        select:
        - as-tuple:
          - eq: 404
          - match: "security.txt"
          do:
          - debug: "Resetting upstream response"
          - upstream-rsp-status: [ 200 , "OK" ]
          - upstream-rsp-body: *secure-text
          - upstream-rsp-field<Cache-Control>: "max-age=3600"

This checks on the upstream response. If the status is 404 (not found) and the path is exactly "security.txt" then change the response to a 200 and provide a hard wired default for the content. The text is retrieved via a YAML reference to an anchor.

  secure_text: &secure-text "# Yoyodyne uses SmackerTwo for responsible disclosure.\n\
                             # To report abusive behavior please visit http://yoyodyne.ex\n\
                             Contact: mailto:security@yoyodyne.ex\n\
                            "

Unfortunately this is insufficient because in some situations there will be no proxy request and therefore no upstream response. Because of limitations in the plugin API this can't be handled directly and requires an additional bit of configuration for that case. This is the reason for using the anchor and reference in the previous configuration, to make sure the exact same text is used in both cases. Note this is done during YAML parsing, not at runtime, and is identical to using literal strings in both cases.

    - when: proxy-rsp
      do:
      # If this is checking it could be because there was an early failure. In that case the proxy
      # request may never have been created and proxy-req-path will be NULL. This syntax tries
      # proxy-req-path and if it is NULL, tries ua-req-path instead which will always be something.
      - debug: "id {ua-req-field<UUID>} proxy-rsp-status {proxy-rsp-status} proxy-req-path {proxy-req-path}"
      - with: [ proxy-rsp-status , [ proxy-req-path , { else: ua-req-path } ] ]
        select:
        - as-tuple:
          - eq: 404
          - match: "security.txt"
          do:
          - debug: "Resetting proxy response"
          - proxy-rsp-status: [ 200 , "OK" ]
          - proxy-rsp-body: [ *secure-text , "text/plain" ]

Note only one of these will trigger on a specific transaction, because if the upstream check activates, the status will not be 404 when it is checked here. This is also a bit more complex because in some situations for which this is checking the proxy request will not have been created, e.g. if there is a failure to remap the request. As a result the path uses a modifier so that if proxy-req-path isn't available due to the proxy request not having been created, it falls back to ua-req-path to get the path the user agent sent. It would also be reasonable to only use ua-req-path in both cases so that only if the user agent specifically requested "security.txt" would the default be used.

Goal

Provide a default JSON web token.

The utility here is to bootstrap into an established JWT infrastructure. On a first request into a CDN the default token is provided to enable access to the resources needed to get a personalized token. For security reasons the tokens expire on a regular basis which includes the default token. It would be too expensive to restart or reload Traffic Server on every expiration. Presuming an infrastructure that pushes default tokens to the file "/var/www/jwt/default-token.jwt", a text block can be defined to load that file and check it for changes every 12 hours. If the file is missing, a special marker "N/A" that signals this problem to the upstream.

      - text-block-define:
          name: "default-jwt"
          path: "/var/www/jwt/default-token.jwt"
          text: "N/A"
          duration: "12h"

To use this, the proxy request is checked for the "Author-i-tay" field. If set it is passed through on the presumption it is a valid token. If not, then the default token is added.

    - when: proxy-rsp
      do:
      - with: proxy-req-field<Author-i-tay>
        select:
        - is-empty:
          do:
          - proxy-rsp-field<Author-i-tay>: text-block<default-jwt>

Once this is set up, pushes of new tokens to the file system on the production system takes no more than 12 hours to show up in the default tokens used.

Path Tweaking

This example also serves to illustrate the use of continue in with. The goal is to adjust a file name in a path depending on the presence of specific query parameters, which are then discarded. If the parameter "ma=0" is present, then the base file name must have the string "_noma" attached. If the parameter "mc=1" is present, then the base filename must have the string "_mac" attached. If both are present then both strings are added.

    - var<path>: "boot" # base file name stem.
    - with: ua-req-query # Check for ma=0
      select:
      - contains: "ma=0"
        do:
        - var<path>: "{var<path>}_noma"
      continue:
    - with: ua-req-query # Check for mc=1
      select:
      - contains: "mc=1"
        do:
        - var<path>: "{var<path>}_mac"
      continue:
    # Correct file name is assembled, update the path.
    - ua-req-path: "edge/file/{var<path>}.ipxe.img"
    - ua-req-query: NULL # Do not pass the query parameters upstream.

The configuration uses with to check for the query parameters and adjust a variable containing the file name as needed. The continue causes the invocation to proceed past the with even if it matches. At the end, the path is assembled and set and the query parameters cleared.

Client Certificate Authorization

A common use of Traffic Server is to place it as a "side-car" in front of a service to perform various network tasks. The task in this example is to use client certificate information to authorize access to the proxied service. We presume client certificates are issued to provide authentication and authorization (such as via a tool like Athenz) but it would be too much work to change the service to verify this. Instead the service can use HTTP basic authentication and have Traffic Server provide that authentication based on information in the client certificate while scrubbing the inbound request to prevent spoofing.

This requires additional support from Traffic Server to requires a client certificate and verify it is signed by a trusted root certificate. To do this globally, add the configuration value to records.config

CONFIG proxy.config.ssl.client.certification_level INT 2

The trusted root certificates are either the base operating system certificates or those specified by the configuration variables proxy.config.ssl.CA.cert.path and proxy.config.ssl.CA.cert.filename

The basic setup is to check the values on the client certificate and set the Authorization field if valid and remove it if not.

txn_box:
- when: ua-req
  do:
  - with: [ inbound-cert-remote-issuer-field<CN> , inbound-cert-remote-subject-field<CN> ]
    select:
    - as-tuple:
      - match: "TxnBox CA alpha" # Authenticating issuer
      - prefix: "base.ex:role." # e.g. "base.ex:role.user"
      do:
      - ua-req-field<Authorization>: "{*}"
    - otherwise: # not Alpha mTLS - allow only GET and HEAD and no authorization.
      do:
      - with: ua-req-method
        select:
        - none-of:
          - match<nc>: "get"
          - match<nc>: "head"
          do:
          - proxy-reply: 418 # be distinct for testing purposes.
      # valid method at this point.
      - ua-req-field<Authorization>: NULL # get rid of the field entirely.

This checks two values - the issuer (authentication) and the subject field (authorization).

  • The issuer must be exactly the expected issuer.

  • Authorization is passed in the subject field, which is expected to contain an authorization domain ("base.ex"), a key word ("role") and then the actual authorization role.

If both are successful then the role for the request is extracted and passed to the service in the Authorization field. Otherwise only GET and HEAD methods are allowed - if neither of those the request is immediately rejected with a 418 status. If allowed the Authorization field is stripped so the service can detect the lack of authorization for the request.

Pulling the authorization value from the certificate disconnects the Traffic Server configuration from the specific roles supported by the service. Whatever those roles are, the administrator can put them in the certificate where they can be passed through.

While it is best to adjust remap.config to not forward non-TLS requests, this configuration will still work correctly because the client certificate values will be NIL for a plain text connection and therefore not match. Traffic Server support is needed for the TLS case to verify the client certificate so TxnBox can trust the values pulled from that certificate.

If this is needed in a multi-tenant / CDN proxy, it will be necessary to use sni.yaml to adjust the client certificate requirements based on the SNI.