HTTP Transformations
Lua is a lightweight, high-level, multi-paradigm programming language designed primarily for embedded use in applications.
HTTP transformation rules that are used to control the processing of a request by IBM Application Gateway (IAG) can be easily written as a Lua script.
Detailed information on Lua scripting can be found in the Lua reference manual: Lua 5.4 Reference Manual. In addition to this, due to the fact that Lua is a widely used scripting language, there are many resources available on the Web to assist you with learning Lua. You can start learning Lua with Programming in Lua. It is available in paperback and as an e-book. The first edition is available for free at Programming in Lua (first edition).
IAG provides custom Lua modules to control the processing of the request. See Custom Lua modules.
Since Lua is a lightweight and performant scripting language, and the custom Lua modules operate on 'live' data, the impact performance of calling simple Lua transformation rules is negligible. This means that it is acceptable to invoke Lua transformation rules on every request to handle things such as the insertion of custom HTTP headers into responses.
Configuring HTTP Transformation Rules
Transformation rules can be applied to HTTP requests or responses by the application gateway. Rules themselves can be attached based on a combination of host, paths and methods. See HTTP Transformations/Request, HTTP Transformations/Postazn and HTTP Transformations/Response for complete details of the matching properties.
Different triggers are used to indicate that the transformation should take place at different stages during process:
Trigger | Description |
---|---|
request | The rule is triggered as soon as the request is received by IAG. |
postazn | The rule is triggered immediately after the authorization decision has been made. This option should be used if you wish to use credential or session information in your rule. |
response | The rule is triggered after the response has been received from the resource server. |
policies:
http_transformations:
request:
- name: insert-custom-request-identifier-header
host: www.test.com
paths:
- "/reports/*"
- "/myapp/*"
method: GET
rule: "@req_insert_header.lua"
postazn:
- name: insert-credential-attribute-header
host: www.test.com
paths:
- "/reports/generate"
- "/reports/export"
method: POST
rule: "@req_insert_header.lua"
response:
- name: apply-content-security-policy
host: www.test.com
paths:
- "*"
method: GET
rule: "@resp_csp.lua"
Legacy XSLT HTTP Transformation Rules
IAG also supports legacy XSLT HTTP Transformation Rules from IBM Security Verify Access. To configure an XSLT rule, just provide the XSL rule in place of the Lua rule. Lua transformation rules execute significantly faster than the legacy XSL engine rules and are much simpler to author. It is strongly recommended to consider rewriting any legacy XSL rules in Lua.
Installed Lua modules
LuaRocks is a package manager for Lua scripts. It allows you to create and install Lua modules as self-contained packages called rocks.
It is not possible to install additional rocks into the container, but several rocks have been preinstalled. The following table lists the preinstalled rocks that are available to the transformation rule scripting:
Rock | Description |
---|---|
basexx | A Lua library which provides base2(bitfield), base16(hex), base32(crockford/rfc), base64(rfc/url), base85(z85) decoding and encoding. |
binaryheap | Binary heaps are an efficient sorting algorithm. This module implements a plain binary heap (without reverse lookup) and a 'unique' binary heap (with unique payloads and reverse lookup). |
cqueues | cqueues is a type of event loop for Lua, except it's not a classic event loop. It does not use callbacks—neither as part of the API nor internally—but instead you communicate with an event controller by the yielding and resumption of Lua coroutines using objects that adhere to a simple interface. |
fifo | A Lua library/'class' that implements a FIFO. Objects in the FIFO can be of any type, including nil. |
http | A HTTP library for Lua. |
lpeg | LPeg is a new pattern-matching library for Lua, based on Parsing Expression Grammars (PEGs). The nice thing about PEGs is that it has a formal basis (instead of being an ad-hoc set of features), allows an efficient and simple implementation, and does most things we expect from a pattern-matching library (and more, as we can define entire grammars). |
lpeg_patterns | A collection of LPEG patterns. |
luaossl | luaossl is a comprehensive binding to OpenSSL for Lua. It includes support for certificate and key management, key generation, signature verification, and deep bindings to the distinguished name, alternative name, and X.509v3 extension interfaces. It also binds OpenSSL's bignum, message digest, HMAC, cipher, and CSPRNG interfaces. |
luasocket | LuaSocket is a Lua extension library that provides support for the TCP and UDP transport layers, commonly needed by applications that deal with the Internet. |
lua-cjson | Lua CJSON provides fast UTF-8 JSON parsing/encoding support for Lua. |
redis-lua | A Lua client library for the redis key value storage system. |
urlencode | A URL encoder/decoder with native extension. |
Custom Lua modules
IAG provides a number of custom modules which can be used to control the processing of the request.
Module | Description |
---|---|
HTTPRequest | This module allows you to manage the HTTP request. |
HTTPResponse | This module allows you to manage the HTTP response. |
Session | This module allows you to access details of the current user session. The user session information will be available for response triggers and will not be available for request triggers. |
Control | This module allows you to control the processing of the request. |
HTTPResponse
This module allows you to retrieve details of the current HTTP response, and modify the current HTTP response.
Header Access
boolean containsHeader(string name)
Used to determine whether the specified HTTP header exists in the response.
Parameters
Name | Description |
---|---|
name | The name of the HTTP header. |
Return value
true if the header exists, otherwise false.
Example
if HTTPResponse.containsHeader("my-header") then
print(HTTPResponse.getHeader("my-header"))
end
table getHeaderNames()
Used to retrieve the header names from the response.
Return value
A table of HTTP header names.
Example
table = HTTPResponse.getHeaderNames()
string getHeader(string name)
Used to retrieve the specified header from the response.
Parameters
Name | Description |
---|---|
name | The name of the HTTP header. |
Return value
The value of the HTTP header, or \a nil if the header was not found.
Example
print(HTTPResponse.getHeader("my-header"))
nil setHeader(string name, string value)
Used to set the specified header in the response.
Parameters
Name | Description |
---|---|
name | The name of the HTTP header. |
value | The value of the HTTP header. |
Return value
nil
Example
HTTPResponse.setHeader("my-header", "my-header-value")
nil removeHeader(string name)
Used to remove the specified header from the response.
Parameters
Name | Description |
---|---|
name | The name of the HTTP header. |
Return value
nil
Example
HTTPResponse.removeHeader("my-header")
nil clearHeaders()
Used to remove all headers from the response.
Return value
nil
Example
HTTPResponse.clearHeaders()
Cookie Access
boolean containsCookie(string name)
Used to determine whether the specified cookie exists in the response.
Parameters
Name | Description |
---|---|
name | The name of the cookie. |
Return value
true if the cookie exists, otherwise false.
Example
if HTTPResponse.containsCookie("my-cookie") then
print(HTTPResponse.getCookie("my-cookie"))
end
table getCookieNames()
Used to retrieve the cookie names from the response.
Return value
A table of HTTP cookie names.
Example
table = HTTPResponse.getCookieNames()
string getCookie(string name)
Used to retrieve the specified cookie from the response.
Parameters
Name | Description |
---|---|
name | The name of the cookie. |
Return value
The value of the cookie, or \a nil if the cookie was not found.
Example
print(HTTPResponse.getCookie("my-cookie"))
nil setCookie(string name, string value)
Used to set the specified cookie in the response.
Parameters
Name | Description |
---|---|
name | The name of the cookie. |
value | The value of the cookie. |
Return value
nil
Example
HTTPResponse.setCookie("my-cookie", "cookie-value; Secure; HttpOnly")
nil removeCookie(string name)
Used to remove the specified cookie from the response.
Parameters
Name | Description |
---|---|
name | The name of the cookie. |
Return value
nil
Example
HTTPResponse.removeCookie("my-cookie")
nil clearCookies()
Used to remove all cookies from the response.
Return value
nil
Example
HTTPResponse.clearCookies()
Body Access
string getBody()
Used to retrieve the body of the response. You will not be able to use this function to retrieve the body if the body is in binary format. The maximum size of the body which is returned is controlled by the '[server] request-body-max-read' WebSEAL configuration entry.
Return value
The body, or \a nil if the body does not exist.
Example
print(HTTPResponse.getBody())
nil setBody(String body)
Used to replace the body of the response.
Parameters
Name | Description |
---|---|
body | The new response body. |
Return value
nil
Example
HTTPResponse.setBody("<html><body><h1>Hello World!</h1></body></html>")
number getContentLength()
Used to retrieve the content length of the body.
Return value
The body content length, or \a nil if the content length is unknown (e.g. for a chunked response).
Example
print(HTTPResponse.getContentLength())
Response Line Access
string getVersion()
Used to retrieve the HTTP version from the response.
Return value
The HTTP version of the response.
Example
print(HTTPResponse.getVersion())
number getStatusCode()
Used to retrieve the status code of the response.
Return value
The status code of the response.
Example
print(HTTPResponse.getStatusCode())
nil setStatusCode(number code)
Used to set the status code of the response.
Parameters
Name | Description |
---|---|
code | The new status code for the response. |
Return value
nil
Example
HTTPResponse.setStatusCode(200)
string getStatusMsg()
Used to retrieve the status message from the response.
Return value
The status message of the response.
Example
print(HTTPResponse.getStatusMsg())
nil setStatusMsg(string msg)
Used to set the status message for the response.
Parameters
Name | Description |
---|---|
msg | The new status message for the response. |
Return value
nil
Example
HTTPResponse.setStatusMsg("OK")
Session
This module allows you to access details of the current user session. The user session information will only be available for postazn and response triggers, and will not be available for request triggers. Used to retrieve the session identifier associated with the current user session. The 'user-session-ids' configuration entry, within the '[session]' stanza must be set to 'yes' in order for the user session identifier to be available.
string getSessionId()
Return value
The session identifier.
Example
print(Session.getSessionId())
string getUsername()
Used to retrieve the name of the user associated with the current session.
Return value
The name of the user.
Example
print(Session.getUsername())
boolean containsCredentialAttribute(string name)
Used to determine whether the specified credential attribute exists in the session.
Parameters
Name | Description |
---|---|
name | The name of the credential attribute. |
Return value
true if the credential attribute exists, otherwise false.
Example
if Session.containsCredentialAttribute("AZN_CRED_PRINCIPAL_NAME") then
print(Session.getCredentialAttribute("AZN_CRED_PRINCIPAL_NAME"))
end
table getCredentialAttributeNames()
Used to retrieve the attribute names from the credential.
Return value
A table of attribute names.
Example
table = Session.getCredentialAttributeNames()
string getCredentialAttribute(string name)
Used to retrieve the specified attribute from the credential. If the attribute is multi-valued each attribute value will be returned in the string, separated by a comma.
Parameters
Name | Description |
---|---|
name | The name of the credential attribute. |
Return value
The value of the credential attribute, or \a nil if the credential attribute was not located.
Example
print(Session.getCredentialAttribute("AZN_CRED_PRINCIPAL_NAME"))
HTTPRequest
This module allows you to retrieve details of the current HTTP request, and modify the current HTTP request. The request information, with the exception of the body, is available on all triggers, but the request can only be modified on a request or postazn trigger.
Header Access
boolean containsHeader(string name)
Used to determine whether the specified HTTP header exists in the request.
Parameters
Name | Description |
---|---|
name | The name of the HTTP header. |
Return value
true if the header exists, otherwise false.
Example
if HTTPRequest.containsHeader("my-header") then
print(HTTPRequest.getHeader("my-header"))
end
table getHeaderNames()
Used to retrieve the header names from the request.
Return value
A table of HTTP header names.
Example
table = HTTPRequest.getHeaderNames()
string getHeader(string name)
Used to retrieve the specified header from the request.
Parameters
Name | Description |
---|---|
name | The name of the HTTP header. |
Return value
The value of the HTTP header, or \a nil if the header was not found.
Example
print(HTTPRequest.getHeader("my-header"))
nil setHeader(string name, string value)
Used to set the specified header in the request.
Parameters
Name | Description |
---|---|
name | The name of the HTTP header. |
value | The value of the HTTP header. |
Return value
nil
Example
HTTPRequest.setHeader("my-header", "my-header-value")
nil removeHeader(string name)
Used to remove the specified header from the request.
Parameters
Name | Description |
---|---|
name | The name of the HTTP header. |
Return value
nil
Example
HTTPRequest.removeHeader("my-header")
nil clearHeaders()
Used to remove all headers from the request.
Return value
nil
Example
HTTPRequest.clearHeaders()
Cookie Access
boolean containsCookie(string name)
Used to determine whether the specified cookie exists in the request.
Parameters
Name | Description |
---|---|
name | The name of the cookie. |
Return value
true if the cookie exists, otherwise false.
Example
if HTTPRequest.containsCookie("my-cookie") then
print(HTTPRequest.getCookie("my-cookie"))
end
table getCookieNames()
Used to retrieve the cookie names from the request.
Return value
A table of HTTP cookie names.
Example
table = HTTPRequest.getCookieNames()
string getCookie(string name)
Used to retrieve the specified cookie value from the request.
Parameters
Name | Description |
---|---|
name | The name of the cookie. |
Return value
The value of the cookie, or \a nil if the cookie was not found.
Example
print(HTTPRequest.getCookie("my-cookie"))
nil setCookie(string name, string value)
Used to set the specified cookie value in the request.
Parameters
Name | Description |
---|---|
name | The name of the cookie. |
value | The value of the cookie. |
Return value
nil
Example
HTTPRequest.setCookie("my-cookie", "my-cookie-value")
nil removeCookie(string name)
Used to remove the specified cookie from the request.
Parameters
Name | Description |
---|---|
name | The name of the cookie. |
Return value
nil
Example
HTTPRequest.removeCookie("my-cookie")
nil clearCookies()
Used to remove all cookies from the request.
Return value
nil
Example
HTTPRequest.clearCookies()
Body Access
string getBody()
Used to retrieve the body of the request. You will not be able to use this function to retrieve the body if the body is in binary format. The maximum size of the body which is returned is controlled by the '[server] request-body-max-read' WebSEAL configuration entry.
Return value
The body, or \a nil if the body does not exist.
Example
print(HTTPRequest.getBody())
nil setBody(String body)
Used to replace the body of the request.
Parameters
Name | Description |
---|---|
body | The new request body. |
Return value
nil
Example
HTTPRequest.setBody("field1=value1&field2=value2")
number getContentLength()
Used to retrieve the content length of the body.
Return value
The body content length, or \a nil if the content length is unknown (e.g. for a chunked request).
Example
print(HTTPRequest.getContentLength())
Request Line Access
string getMethod()
Used to retrieve the method from the request line.
Return value
The HTTP method of the request.
Example
print(HTTPRequest.getMethod())
nil setMethod(string method)
Used to set the method for the request.
Parameters
Name | Description |
---|---|
method | The new method for the request. |
Return value
nil
Example
HTTPRequest.setMethod("GET")
string getURL()
Used to retrieve the URL from the request line.
Return value
The HTTP URL of the request.
Example
print(HTTPRequest.getURL())
nil setURL(string url)
Used to set the URL for the request.
Parameters
Name | Description |
---|---|
url | The new URL for the request. |
Return value
nil
Example
HTTPRequest.setURL("/index.html?arg=value")
string getVersion()
Used to retrieve the HTTP version from the request line.
Return value
The HTTP version of the request.
Example
print(HTTPRequest.getVersion())
number getProtocol()
Used to retrieve the HTTP protocol used in the request.
Return value
The protocol used in the request, 0 for HTTP and 1 for HTTPS.
Example
print(HTTPRequest.getProtocol())
Control
This module allows you to control the processing of the request by WebSEAL.
nil includeInRequestLog(boolean includeInLog)
Used to control whether the request should be included in the request log or not.
Parameters
Name | Description |
---|---|
includeInLog | Should the request be included in the log? |
Return value
nil
Example
Control.includeInRequestLog(false)
nil trace(number level, string message)
Used to trace a string using the WebSEAL tracing framework. Use the pdweb.http.transformation tracing component to activate the tracing.
Parameters
Name | Description |
---|---|
level | The trace level |
message | The string which is to be traced. |
Return value
nil
Example
Control.trace(5, string.format("value: %s", value))
string dumpContext()
Used to return a YAML representation of the request context. This includes information associated with the HTTP request, current HTTP response and session. The resultant YAML can be used as input into the isva_lua_transformation_test binary.
Return value
A YAML representation of the request context.
Example
print(Control.dumpContext())
nil returnErrorPage(string message)
Used to return an error to the client. As a result of calling this function an error page will be generated and returned by WebSEAL. The supplied message can be included in the WebSEAL generated error page using the %FAILREASON% macro.
Parameters
Name | Description |
---|---|
message | The error message. |
Return value
nil
Example
Control.returnErrorPage(string.format("An error occurred: %s", reason))
nil responseGenerated(boolean generated)
Used to indicate that a response has been created by the transformation rule. This will ensure that the response is returned directly to the client and WebSEAL will not forward the request onto the junctioned application.
Parameters
Name | Description |
---|---|
generated | Has a response been generated by the rule? |
Return value
nil
Example
Control.responseGenerated(true)
Disabled APIs
To help protect the integrity of the gateway the following functions from the Lua standard libraries have been disabled and are not available to HTTP transformation scripts.
Standard Library | Function |
---|---|
Basic Library | loadfile |
Operating System Facilities | execute exit remove setlocale tmpname |
Input and Output | all functions |
Package Library | loadlib |
Debugging
There is a command line tool which allows Lua transformation rules to be developed and tested offline, iag_lua_transformation_test
. This command is available in the IAG container image.
The input to this command is a YAML representation of the context and the Lua transformation rule script. The context of the request includes the HTTP request, HTTP response and session information.
The output from the binary is a description of the transformation actions which would be performed by IAG.
Example Usage of the iag_lua_transformation_test Tool
The following scenario tests a simple transformation rule using the command line tool.
The example YAML representation of the context, named context.yaml
:
http:
request:
method: "GET"
uri: "/test.html"
headers:
- name: "User-Agent"
value: "curl"
- name: "Host"
value: "www.ibm.com"
response:
status: 201
headers:
- name: "Origin"
value: "https://www.ibm.com"
session:
id: "<Session ID>"
username: "testuser"
credential:
attributes:
- name: "AZN_CRED_PRINCIPAL_NAME"
value: "testuser"
The example transformation rule, named rule.lua
:
if Session.containsCredentialAttribute("AZN_CRED_PRINCIPAL_NAME") then
HTTPRequest.setHeader("IV-USER", Session.getCredentialAttribute("AZN_CRED_PRINCIPAL_NAME"))
end
if HTTPResponse.containsHeader("origin") then
if not (HTTPResponse.containsHeader("access-control-allow-origin")) then
HTTPResponse.setHeader("access-control-allow-origin", HTTPResponse.getHeader("origin"))
end
if not (HTTPResponse.containsHeader("access-control-allow-methods")) then
HTTPResponse.setHeader("access-control-allow-methods", "GET,POST,OPTIONS")
end
if not (HTTPResponse.containsHeader("access-control-max-age")) then
HTTPResponse.setHeader("access-control-max-age", "86400")
end
end
In this docker example, the context and rule are present in the current directory, which is mounted into a temporary container at /lua_test
. Passing in the context and rule produces output which describes the changes IAG would make when applying this rule in this context:
~/test$ ls
context.yaml rule.lua
~/test$ docker run --rm -v "${PWD}":/lua_test \
--entrypoint iag_lua_transformation_test \
icr.io/ibmappgateway/ibm-application-gateway:23.04 \
-c /lua_test/context.yaml -s /lua_test/rule.lua
Request header: set (IV-USER,testuser)
Response header: set (access-control-allow-origin,https://www.ibm.com)
Response header: set (access-control-allow-methods,GET,POST,OPTIONS)
Response header: set (access-control-max-age,86400)
Generating Sample Context YAML
To assist with the generation of the YAML representation of the context of the request, the iag_lua_transformation_test
command can be started with the -t
option to produce a template context YAML file. For example:
docker run --rm \
--entrypoint iag_lua_transformation_test \
icr.io/ibmappgateway/ibm-application-gateway:23.04 \
-t
The produced template YAML context:
http:
request:
method: <method>
url: <url>
version: <version>
protocol: <protocol>
body: <body>
headers:
- name: <hdr-name>
value: <hdr-value>
cookies:
- name: <cookie-name>
value: <cookie-value>
response:
version: <version>
body: <body>
status:
code: <code>
message: <message>
headers:
- name: <hdr-name>
value: <hdr-value>
cookies:
- name: <cookie-name>
value: <cookie-value>
session:
id: <id>
username: <username>
credential:
attributes:
- name: <attribute-name>
value: <attribute-value>
Capturing Context YAML
The dumpContext
function within the Control Lua module can be called from within a running IAG instance to capture a YAML representation of the current request context. For example:
print(Control.dumpContext())
Tracing
Print and trace statements can be used in in Lua transformation rules scripts.
Printing to Container Standard Out
The output from any print statements will be sent to the container standard out. For example:
print(string.format("Host header: %s",
HTTPRequest.getHeader("host")))
Tracing Using the Tracing Framework
The Control
custom module contains a trace
function which can be used to trace a string using the tracing framework. Use the pdweb.http.transformation
tracing component to activate the tracing.
For example:
- Configure
pdweb.http.transformation
in the IAG YAML: - Use the
Control.trace
function to write to the tracing framework.
logging:
tracing:
- file_name: /var/iag/lua_trace.log
component: pdweb.http.transformation
level: 5
Control.trace(5, string.format("Host header: %s",
HTTPRequest.getHeader("host")))
Example Transformation Rules
Modifying the Body of a Response
-- This script is used to test the modification of a JSON based response. It
-- will change the 'modified' field in the JSON payload from false to
-- true.
local cjson = require "cjson"
-- Only modify the response if the content-type is 'application/json'
if HTTPResponse.getHeader("content-type") == "application/json" then
local payload = cjson.decode(HTTPResponse.getBody())
payload["modified"] = true
HTTPResponse.setBody(cjson.encode(payload))
end
Creating a Custom Response
-- This script is used to create a custom HTTP response.
url = HTTPRequest.getURL()
-- Only send our custom response if the URL contains the string:
-- 'custom.response'
if string.find(url, "custom.response") then
HTTPResponse.setHeader("content-type", "text/html")
HTTPResponse.setCookie("my-cookie", "cookie-value; Secure; HttpOnly")
HTTPResponse.setBody("<html><body><h1>This is an example transformation response!</h1></body></html>")
HTTPResponse.setStatusCode(200)
HTTPResponse.setStatusMsg("OK")
Control.responseGenerated(true)
end
Setting the Content Security Policy
-- This script is used to set the CSP header based on the value of the
-- incoming host header.
host = HTTPRequest.getHeader("host")
-- Validate the host header.
local validHosts = {
"www.ibm.com",
"gateway.ibm.com"
}
local isValid = false
for index, data in pairs(validHosts) do
if data == host then
isValid = true
break
end
end
-- If the host header is accepted, we add the CSP header to the response,
-- otherwise we return an error page.
if isValid then
HTTPResponse.setHeader("Content-Security-Policy",
string.format("script-src %s;", host))
else
Control.returnErrorPage(
string.format("An invalid host header was received: %s", host))
end
Updated about 1 month ago