FortiWeb 7.6.0 Script Guide
FortiWeb 7.6.0 Script Guide
FortiWeb 7.6.0
FORTINET DOCUMENT LIBRARY
[Link]
FORTINET BLOG
[Link]
FORTINET COOKBOOK
[Link]
NSE INSTITUTE
[Link]
FORTIGUARD CENTER
[Link]
FEEDBACK
Email: techdocs@[Link]
TABLE OF CONTENTS
Change Log 5
Introduction 6
Configuration overview 7
Predefined packages and classes 10
Global 10
debug(fmt, ..) 10
_id 10
_name 10
Core 10
[Link](level, fmt, ..) 10
[Link](level, …) 10
Policy 11
[Link]() 11
policy.http_ports() 11
policy.https_ports() 11
[Link]() 11
[Link]() / [Link](“cr-name”) 11
IP 12
[Link](“ip-string”) 12
[Link](ip_class_1, “ip-string”) / [Link](ip_class_1, ip_class_2) 12
[Link](“ip-string”) / [Link](ip_class) 12
[Link](“ip-string”) / [Link](ip_class) 12
ip.geo_code(“ip-string”) / ip.geo_code(ip_class) 12
IP address classes 13
Predefined commands 14
IP commands 14
TCP commands 14
LB commands 15
SSL commands 15
SSL:sni() 15
SSL: set_sni(svr_name) 15
SSL:cipher() 16
SSL:version() 16
SSL:alpn() 16
SSL:client_cert_verify() 17
SSL: cert_count() 17
SSL: get_peer_cert_by_idx(index_value) 17
SSL: verify_result() 18
SSL:session(t) [TODO] 18
HTTP Commands 18
Header fetch 18
Header manipulate 20
Custom reply 22
Control 23
Protocol 23
Transaction private data 23
Data Collect 23
Body Rewrite 24
SSL Renegotiate 25
Change Log
Introduction
FortiWeb supports Lua scripts to perform actions that are not currently supported by the built-in feature set. You
can use Lua scripts to write simple, network aware pieces of code that will influence network traffic in a variety of
ways. By using the scripts, you can customize FortiWeb's features by granularly controlling the traffic flow or
even the contents of given sessions or packets.
In FortiWeb, the scripting language only supports HTTP and HTTPS policy.
Configuration overview
You can type or paste the script content into the configuration page.
Before you begin:
l Create a script.
l You must have Read-Write permission for Server Policy settings.
After you have created a script configuration object, you can reference it in the virtual server configuration.
To configure a script:
1. Go to Application Delivery > Scripting.
2. Click Create New to display the configuration editor.
3. Complete the configuration as shown.
Settings Guidelines
Script Events
There are predefined scripts which specify the following events. When the events occur, it will trigger the system
to take the actions defined in the script.
Event priority
FortiWeb supports multiple scripts in one server policy. When a server policy with scripts is enabled, the system
will load scripts one by one. If there are multiple same events defined in the scripts, the event running order is
same as the loading order.
If you want to run a certain event first regardless of the script order, you can define its priority to prioritize its
sequence. The default priority of events is 500. Lower value has higher priority.
For example:
when HTTP_REQUEST priority 499 {
...
}
• Modify print() to FortiWeb version, printing to debug log with level 1. (diag
debug proxyd scripting-user <1-7>)
package Disabled
coroutine Disabled
table Supported
io Disabled
os Disabled
string Supported
math Supported
utf8 Supported
l Global
l Core
l Policy
l IP
Global
debug(fmt, ..)
The string will be printed to debug log with level 1. For example:
debug(“This HTTP Request method is %s.\n”, HTTP:method())
_id
_name
Core
[Link](level, …)
Policy
[Link]()
policy.http_ports()
Return a lua array with all HTTP ports. Port value is integer.
{ 80, 8080 }
policy.https_ports()
Return a lua array with all HTTPS port. Port value is integer.
{ 443, 8443 }
[Link]()
[Link]() / [Link](“cr-name”)
Return lua array with all servers. If the policy has content routing, the caller should pass the “cr-name” argument
to fetch the servers of the specific content routing.
{
{ [“type”] = “ip”, [“ip”] = “[Link]”, [“port”] = 80 },
{ [“type”] = “ip”, [“ip”] = “[Link]”, [“port”] = 80 },
...
}
IP
[Link](“ip-string”)
Compare two IP addresses. The first one must be IP address class and the second one can be IP address class
or IP string.
[Link](“ip-string”) / [Link](ip_class)
Check the reputation of a specific IP. Return Lua array with reputation categories. The reputation categories
are:
"Botnet", "Anonymous Proxy", "Phishing", "Spam", "Others", "Tor"
[Link](“ip-string”) / [Link](ip_class)
Return GEO country name in string. If nothing is found or the IP string is not a valid IP, return nil.
ip.geo_code(“ip-string”) / ip.geo_code(ip_class)
Return GEO country code in string. If nothing is found or the IP string is not a valid IP, return nil.
IP address classes
__eq()
__tostring()
str()
ver()
v4()
Return a new IP address class in v4 version. If the IP address class is v4, copy the IP address class and return.
If the IP address class is v6, the system will try to convert it to v4. If it succeeds, return the v4 IP address class. If
it fails, return nil.
v6()
Return a new IP address class in v6 version. If the IP address class is v6, copy the IP address class and return.
If the IP address class is v4, the system will try to convert it to v6. If it succeeds, return the v6 IP address class. If
it fails, return nil.
eq(“IP-string”) / eq(IP_class)
Compare this IP address class with another one. It can compare IP address class or IP string.
Predefined commands
All commands are Lua classes but they only can be used inside scripting events. Some commands can only be
used in specific events. For example, HTTP commands can only be used inside HTTP events (HTTP_
REQUEST and HTTP_RESPONSE).
IP commands
TCP commands
Close current TCP connection and disable its TCP events. This function can only be used in event SERVER_
CONNECTED.
LB commands
SSL commands
SSL:sni()
SSL: set_sni(svr_name)
Returns true if the server name indication extension has been set, otherwise false.
This function should be used in the script event SEVERSSL_CLIENTHELLO_SEND.
Example
when SERVERSSL_CLIENTHELLO_SEND {
svr_name = "[Link]"
debug("set Server Name Indication(SNI) in ClientHello = %s\n", svr_name)
SSL:set_sni(svr_name)
}
-- a function to print a table, i represents the number of \t for formatting purpose.
function print_table(table, indent)
local space = [Link]('\t',indent)
for key, value in pairs(table) do
if(type(value)=='table') then
debug("%s sub-table[%s]\n", space, key)
print_table(value, indent+1)
else
debug("%s %s: %s\n", space, key, value)
end
end
end
SSL:cipher()
Returns the cipher in handshake (string type, in OPENSSL form). Please note that the name returned is in
standard RFC format.
This function should be used in script events CLIENTSSL_HANDSHAKE and SERVERSSL_HANDSHAKE.
Example
when CLIENTSSL_HANDSHAKE {
local cipher = SSL:cipher()
if cipher then
debug("cipher in client handshake =%s\n", cipher)
end
}
SSL:version()
SSL:alpn()
Returns the ALPN protocol selected in handshake (string type). Returns false if not presented or supported.
This function should be used in script events CLIENTSSL_HANDSHAKE and SERVERSSL_HANDSHAKE.
Example
when CLIENTSSL_HANDSHAKE {
local alpn_protocol = SSL:alpn()
if alpn_protocol then
debug("alpn_protocol in client handshake = %s\n", alpn_protocol)
end
}
SSL:client_cert_verify()
Returns the status of client-certificate-verify, whether or not it is enabled. True represents enabled, otherwise
False.
This function should be ONLY used in script event CLIENTSSL_HANDSHAKE.
SSL: cert_count()
Returns the total number of certificates that the peer has offered, including the peer certificate and client
certificate chains. (Integer)
This function should be ONLY used in script event CLIENTSSL_HANDSHAKE.
Example
when CLIENTSSL_HANDSHAKE {
if SSL:client_cert_verify() then
debug("client cert verify enabled\n")
local cert_cnt = SSL:cert_count()
debug("cert_cnt number %d\n", cert_cnt)
end
}
SSL: get_peer_cert_by_idx(index_value)
Returns the issuer certificate of the index of the X509 SSL certificate in the peer certificate chain, where index is
a value greater than or equal to zero.
A value of zero denotes the first certificate in the chain (aka leaf peer certificate);
A value of one denotes the next, and so on. If the input value is out of range, return nil.
Return type: A table including the information of a client certificate.
This function should be ONLY used in script event CLIENTSSL_HANDSHAKE
Example
when CLIENTSSL_HANDSHAKE {
if cert_cnt >= 1 then
local cert_table = SSL:get_peer_cert_by_idx(0)
print_table(cert_table, 0)
end
debug("verify result: %d\n", SSL: verify_result())
}
SSL: verify_result()
Returns the result code from peer certificate verification. The returned code uses the same values as those of
OpenSSL’s X509 verify_result (X509_V_ERR_) definitions.
Returns type: Integer. Returns -1 if the verification code can not be retrieved
This function should be ONLY used in script event CLIENTSSL_HANDSHAKE.
SSL:session(t) [TODO]
HTTP Commands
Header fetch
HTTP:headers()
Fetch all HTTP request or response headers. When it is called in client side, it returns all HTTP request
headers; When it is called in server side, it returns all HTTP response headers.
Return: lua table of array.
for k, v in pairs(HTTP:headers()) do
for i = 1, #v do
debug("HEADER: %s[%d]: %s\n", k, i, v[i])
end
end
HTTP:header(“header-name”)
HTTP:cookies()
Fetch all cookies. When it is called in client side, it fetches “Cookies”; When it is called in server side, it fetches
“Set-Cookie”.
Return: lua table containing only keys and values.
for k, v in pairs(HTTP:cookies()) do
debug("Cookie: %s = %s\n", k, v)
end
HTTP:cookie(“cookie-name”)
HTTP:args()
HTTP:arg(“arg-name”)
HTTP:host()
HTTP:url()
Return the string of HTTP request URL. It is full URL including path and query.
HTTP:path()
HTTP:method()
HTTP:version()
HTTP:status()
Return two strings including HTTP response status code and reason.
code, reason = HTTP:status()
Header manipulate
HTTP:set_path(“new-path”)
HTTP:set_query(“new-query”)
Change the query in HTTP request header.
Return true for success and false for failure.
HTTP:set_query("test=1")
HTTP:set_url(“new-url”)
Change the whole URL, including the path and query.
Return true for success and false for failure.
HTTP:set_method(“new-method”)
HTTP:set_status(status-code)
HTTP:set_status(status-code, “reason”)
Change the status code and reason in HTTP response header. If reason does not exist, use default reason.
Return true for success and false for failure.
HTTP:set_status(200)
HTTP:set_status(200, "Other Reason")
HTTP:add_header(“header-name”, “header-value”)
Add a header line to HTTP request or response header.
Return true for success and false for failure.
Example:
function rewrite_request(HTTP, IP, args)
debug("%s", IP:client_addr())
client_ip = IP:client_addr()
-- add/del/set header
HTTP:add_header("X-COUNTRY-FMF", [Link](client_ip) or "unknown") -- add a new
header line
end
when HTTP_REQUEST {
local path = HTTP:path()
if path == "/rewrite_request" then
rewrite_request(HTTP, IP, HTTP:args())
end
}
HTTP:del_header(“header-name”)
Remove the header with name “header-name” from HTTP request or response.
Return true for success and false for failure.
HTTP:set_header(“header-name”, header-value-array)
Remove the header with name “header-name” from HTTP request or response, and add this header with new
value header-value-array. The argument header-value-array is a Lua array which is the value got from
HTTP:header().
Return true for success and false for failure.
HTTP:set_header("test", { "line1", "line2", "line3" })
Match the regular expression in all occurrences of header field “header-name” according to “regex”, and
replaces them with the “replace” argument. The replacement value can contain back references like 1,2, …
Return true for success and false for failure.
-- add api to set-cookie path
HTTP:replace_header("set-cookie", [[(.*)(Path=\/)(.*)]], [[\1\2api\3]])
Custom reply
These functions only can be used in HTTP client side event (only HTTP_REQUEST now).
HTTP:redirect (“fmt”, …)
HTTP:reply (response)
Control
HTTP:close()
Close the current HTTP transaction and disable its HTTP events. This function can only be used in event
HTTP_REQUEST.
Protocol
HTTP:is_https()
In Lua, the local value can only be used in function and the global value is shared in whole Lua stack.
In FortiWeb, sometimes a private data is needed for HTTP transaction, and the value is shared in the same
HTTP transaction.
HTTP:setpriv(object)
Store a lua object as the HTTP transaction private data. You can store a lua object in event HTTP_REQUEST
and fetch it by calling HTTP:priv() in event HTTP_RESPONSE.
HTTP:priv()
Fetch the transaction private data that stored by HTTP:setpriv(). If no result is found, it will return an empty lua
table.
Data Collect
HTTP:collect()
Body Rewrite
when HTTP_DATA_REQUEST {
local body_str = HTTP:body(0, 16)
local body_new = body_str:gsub("username:[A-Za-z][A-Za-z0-9_]+", username_first_char_
uppercase)
debug("body old = %s, body new = %s\n", body_str, body_new)
HTTP:set_body(body_new, 0, 16)
}
SSL Renegotiate
SSL_RENEGOTIATE()
When the system evaluates the command under a client-side context, the system immediately renegotiates a
request for the associated client-side connection. This function is temporarily ONLY available in HTTP_
REQUEST event.
Return true for success and false for failure.
Example
In this sample script, when an HTTPS request with the prefix "autotest" is received, it triggers client certificate
verification through SSL renegotiation.
Once the SSL renegotiation is completed, it checks the content-routing policy.
If the client certificate presented by the client meets certain conditions that matches a specific HTTP content
routing policy, the traffic will be directed to a designated server pool.
--
#a function to print a table, i represents the number of \t for formatting
purpose.
function print_table(table, indent)
local space = [Link]('\t',indent)
for key, value in pairs(table) do
if(type(value)=='table') then
debug("%s sub-table[%s]\n", space, key)
print_table(value, indent+1)
else
debug("%s %s: %s\n", space, key, value)
end
end
end
when HTTP_REQUEST {
local url = HTTP:url()
if url:find("^/autotest") and HTTP:is_https() and SSL:client_cert_
verify() then
-- Trigger SSL renegotiate only when it's https request and SSL con-
nection has already been established
-- Example URL-based certificate verify and then Content-Routing
debug("url: %s match rule, need client certificate verify\n", url)
local cert_count = SSL:cert_count()
debug("cert_count = %s\n", cert_count)
if cert_count and cert_count == 0 then
SSL:renegotiate()
debug("emit SSL renegotiation\n")
end
end
when CLIENTSSL_RENEGOTIATE {
local cert_count = SSL:cert_count()
debug("cert_count = %s\n", cert_count)
if cert_count and cert_count > 0 then
local cert_table = SSL:get_peer_cert_by_idx(0)
print_table(cert_table, 0)
local subject = cert_table["subject"]
-- match CN value with regular expression
local cn_value = subject:match("CN%s-=%s-([^,%s]+)")
debug("CN value in X509 subject is: %s\n", cn_value)
if cn_value and cn_value == "test1" then
LB:routing("ctrt")
end
end
}
Copyright© 2024 Fortinet, Inc. All rights reserved. Fortinet®, FortiGate®, FortiCare® and FortiGuard®, and certain other marks are registered trademarks of Fortinet, Inc., and other Fortinet names herein
may also be registered and/or common law trademarks of Fortinet. All other product or company names may be trademarks of their respective owners. Performance and other metrics contained herein were
attained in internal lab tests under ideal conditions, and actual performance and other results may vary. Network variables, different network environments and other conditions may affect performance
results. Nothing herein represents any binding commitment by Fortinet, and Fortinet disclaims all warranties, whether express or implied, except to the extent Fortinet enters a binding written contract,
signed by Fortinet’s General Counsel, with a purchaser that expressly warrants that the identified product will perform according to certain expressly-identified performance metrics and, in such event, only
the specific performance metrics expressly identified in such binding written contract shall be binding on Fortinet. For absolute clarity, any such warranty will be limited to performance in the same ideal
conditions as in Fortinet’s internal lab tests. Fortinet disclaims in full any covenants, representations, and guarantees pursuant hereto, whether express or implied. Fortinet reserves the right to change,
modify, transfer, or otherwise revise this publication without notice, and the most current version of the publication shall be applicable.