Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
If you've used mod_rewrite
with Apache web server, you may be familiar with its Rewrite Map functionality, which allows you to create dynamic rewrites based on a text file of mappings. We have a similar feature built on Undertow's Server Rules that mimics Apache's feature.
Note, when in Mult-Site mode, each site has its own rewrite maps, even if they share the same name. There is no overlap between sites!
There is a rewrite-map()
handler which declares a named map file. It accepts an absolute path to the rewrite file, and case sensitivity flag.
The file format matches that of Apache's rewrite map functionality.
Lines starting with #
are a comment
empty lines are ignored
First space-delimited token becomes the key
All remaining tokens become the value
Lines with a single token default the value to an empty string
Here is an example file:
If the date modified on the rewrite map file changes on disk, the data will be automatically re-loaded into memory.
There are two handler/predicates you can use to interact with the rewrite map. Make sure these are in your list of Server Rules AFTER the map definition.
There is a rewrite-map-exists()
predicate which will tell you if a given key exists in the map (Apache doesn't have this)
You can pair this predicate to only use the rewrite map if there is a match.
There is a new %{map:name-name:mapKey|defaultValue}
exchange attribute which mostly follows Apache's syntax. The only limitation is nested exchange attributes must use [] instead of {} due to an Undertow parsing issue).
In the example above, we're using a regex predicate to extract the text after /foo/
in the URL and then we reference that capture group as $[1]
which we pass in as the key to the map. If there is no key in the map for that value, we default to 99
.
So, given our example rule map file above, the url
would be rewritten to
CommandBox servers have a method of locking down secure URLs and or implementing any of the Undertow predicate and handlers via a nice text based language. Undertow supports a “predicate language” that allows a string to be parsed into a graph of predicates (conditions) and handlers (actions to take). Ex:
These rules can be used for any of the following:
Security - Block paths, IPs, or users
URL rewrites - Rewrite incoming URLs to something different
Modifying HTTP requests on the fly - Set headers, cookies, or response codes
Much of this functionality overlaps with the existing Tuckey-based rewrites in CommandBox, but this functionality is built directly into Undertow, has a more streamlined syntax, and allows for easier ad-hoc rules to be layered into a server that allows for you to have custom rules layered on top of built in rules. It can be used to replace what Tuckey does, or added on top.
If you have Tuckey rewrites enabled AND use the Undertow predicate-based server rules, the server rules will fire BEFORE the Tuckey rewrites.
Unlike custom Tuckey-based rewrites that must be placed in a single XML file, server rules can be provided ad-hoc in a variety of locations. They are combined and passed to the server in the order defined. This allows you to easily "layer" custom rules along with out-of-the-box lockdown profiles.
For maximum configuration options, the following mechanisms are supported for specifying the rules for a given server. Rules are processed in the order listed. i.e., a rule defined in your server.json
is processed prior to a rule in your server.default
config setting.
Ad-hoc rule array in sites
object or .site.json
for Multi-site server
Ad-hoc rule array in server.json
External rules files in the sites
object of the server.json
or .site.json
for Multi-site server
External rules files in server.json
in the order defined
Ad-hoc rule array in config setting server.defaults
External rules files in config setting server.defaults
in the order defined
CommandBox built-in rules (web.blockCFAdmin
, web.blockSensitivePaths
)
Any module listening to server interceptions can inject their rules wherever they please in the array.
server.json
RulesYou can specify ad-hoc rules in the web.rules
property as an array of strings in your server.json
as well as specify one or more external rule files in the web.rulesFile
property as an array or list of file glob patterns.
External rule files with a .json
suffix will be expected to be a valid JSON file containing an array of strings. Ex:
External rule files with any extension OTHER than .json
will be expected to be a raw text file with one rule per line. Emtpy lines are ignored and the rules are processed in the order defined.
Rules specified directly in the server.json
or in an external JSON file must be escaped for the JSON they are a part of. Using a plain text external file can help readability since no additional escaping is required for the rules.
server.defaults
RulesLike all other config server defaults, they follow the same pattern as the server.json
file.
For Multi-Site, server rule settings can be configured on a per-site basis in the sites
object of the server.json
or in a .site.json
file.
You can comment out any rule (whether it's in JSON or a text file) by proceeding it with a pound sign (#
).
The Undertow-based web server built into CommandBox will only serve up static files if they are in a list of valid extensions. This is to prevent prying eyes from hitting files they shouldn't be able to access on your server.
The current list of valid extensions is:
If you have a common static file you need to serve, you can add your own custom extensions to the list like so:
For Multi-Site, allowed static files settings can be configured on a per-site basis in the sites
object of the server.json
or in a .site.json
file.
There are endless combinations of predicates and handlers you can apply to your apps. Here's an assortment of examples to get you going. Many of these are contrived, but are just mean to show the range of syntax and functionality.
Rewrite a URL:
Stop processing rules for a given request:
For all GET requests, set a response header called type
with a value of get
If the incoming path ends with .css
it will rewrite the request to .xcss
and set a response header called rewritten
to true
.
Redirect all jpg requests to the same file name but with the png extension. The ${1}
exchange attribute is used to reference the first regex group.
Set a request header for all requests that you can access in your CFML app just like a normal HTTP header.
Set a Cache-Control response header only for default index pages(ie: index.html or index.cfm) in any folder.
Set a response header for a specific file.
Match certain SES-style URLs and store the place holders (referenced as exchange attributes) into HTTP request headers.
In this example, hitting the server with /restart
skips the first rule, the second rule rewrites the request and then restarts back at the first rule which fires the second time through.
Block access to a URL unless coming from a specific IP.
For more control over IP address matches, use the ip-access-control()
handler.
Leading slash is NOT required. Both of these rules are the same:
It is not required to include .*
_ at the end of a regex path unless you’ve set full-match=true
_
Perform a regex a case insensitive search like so:
When attribute values are not quoted, all leading and trailing whitespace will be trimmed. The following are all the same:
But this example is different. The leading and trailing spaces will be preserved in the path string.
Basic MVC rewrite:
Add a CORs header to every request
Rewrite requests to a sub folder based on the domain
Custom SES URL rewrite that turns 6 digit product code into a query string. (If this rule goes in your server.json
, you'll need \\
in front of the dollar sign to escape it twice. Once for the system setting expansion and once for the JSON file.)
Reject requests using an unknown host header.
Create reverse proxy to multiple hosts
Create reverse proxy to a single host
CommandBox's server rules are based directly on the "Predicate Language" feature of JBoss Undertow. All of the official documentation for Undertow's predicate language applies to CommandBox. CommandBox doesn't limit the power of what Undertow provides at all. In fact, we give you some additional out-of-the box predicates and handlers you can use.
There are three concepts you need to understand and you'll be writing your own rules in no time.
Handler - A wrapper around the request that takes some sort of action such as returning a status code or rewriting the request.
Predicate - A conditional that evaluates to true or false. Used to Conditionally apply a handler
Exchange Attribute - An abstracted way to refer to any piece of information about the request or response. Can include URL, query string, cookies, headers, or CGI variables.
Since the parsing of your rules is handled by Undertow, it is case sensitive in many areas and CommandBox cannot control this. The following must always be lower case:
Predicate names
Handler names
Parameter names
Keywords like and
, or
, else
The full undertow docs can be found here: https://undertow.io/undertow-docs/undertow-docs-2.0.0/
A handler will take action on the request. This can redirect the request, rewrite it, abort it, or modify attributes of the request such as setting an HTTP header or status code. If the predicate is omitted, the handler will always fire for all requests. Ex:
set() - Sets an exchange attribute (see below)
rewrite() - Rewrite the request URL
redirect() - Redirect to another URL
header() - Sets an HTTP response header
set-error() - Set HTTP response code and returns the corresponding error page
ip-access-control() - Configure IP-based allow/deny rules (wildcards and IP ranges allowed)
done - Skips remaining rules
request-limit() - Limits concurrent requests
restart - Restarts process back at start of predicate rule chain (combine with rewrite, etc)
proxy() - Creates a proxy to a single host
load-balanced-proxy() - Creates a round robin reverse proxy to a list of hosts
allowed-methods() / disallowed-methods() - Enforces whitelist/blacklist of HTTP methods on current request
A handler is called using the “name” from the docs and passing any required parameters. Params can be named or positional (if there is only one). Quoting values is only required if they have a space, comma or brace. The following are all equivalent:
Handler parameters that accept an array use the Java array literal syntax which is a comma-delimited list of values in curly braces.
More than one handler can be run by wrapping them in curly braces and using semicolons between them
A chain of handlers can be configured for the true AND false evaluation of a single predicate using the “else” keyword.
A predicate is a condition that must be matched for the handler to kick in. If using path-based predicates on Windows, make sure to use the -nocase
version for security rules since your file system is not case sensitive.
path()/path-nocase() - match the incoming request path
path-suffix()/path-suffix-nocase() - match full file/folder names at the end of the path like file.cfm
path-prefix()/path-prefix-nocase() - match full file/folder names at the start of the path like /lucee/admin
is-file / is-directory - check if the %{RELATIVE_PATH}
is an existing file or directory on disk. While relative path is the default, you can pass another exchange attribute to check as is-file( %{q,myQueryParam} )
method() - Match the HTTP method
regex()/regex-nocase() - Match a regular expression against any exchange attribute
equals()/equals-nocase() and contains()/contains-nocase() - Do a text compare on any exchange attribute
path-template() - match a path with placeholders like /foo/{bar}
and the placeholders become variables for use in later predicates or handlers in the chain.
A predicate is called by placing parentheses after the name and passing in the required arguments as specified in the docs for that predicate. Quoting values is only required if they have a space, comma or brace. The following are all equivalent:
Complex conditions can be constructed with more than one predicate using the keywords “and” or “or”.
A predicate can be negated with the keyword “not”
Exchange attributes are an abstraction Undertow provides to reference any part of the HTTP request or response. There is a textual placeholder that can be used in predicates and handlers that will be expanded in-place.
%{REMOTE_IP} - Remote IP address
%{PROTOCOL} - Request protocol
%{METHOD} - Request method
%{REMOTE_USER} - Remote user that was authenticated
%{RESPONSE_TIME} - Time taken to process the request, in ms
%{c,cookie_name} - Any cookie value
%{q,query_param_name} - Any query parameter
%{i,request_header_name} - Any request header
%{o,response_header_name} - Any response header
%{REQUEST_URL} - The requested URL
%{RELATIVE_PATH} - The portion after the domain
${anything-here} - Any value from the predicate context (such as regex capture groups or path-template placeholders)
Use an exchange attribute like you would a variable as the parameter to a handler or predicate.
Attributes, Cookie, query params, request headers, and response header names are not case sensitive.
These are all equivalent:
CommandBox has a few baked in rules that you can apply ad-hoc or as part of a server profile
.
web.blockCFAdmin - Returns 404 error page for any Adobe CF or Lucee Server admin administrator paths
web.blockSensitivePaths - Returns 404 error page for common config files such as box.json
or .env
web.blockFlashRemoting - Blocks all paths related to Flash and Flex remoting calls
If you want to customize the rules above, simply turn them off and include the rules directly in your server.json
where you can modify them as you see fit.
This setting has three possible settings:
true - Block ALL access to ColdFusion and Lucee admins
false - Do not block anything
external - Only block access not coming from localhost.
The exact rule activated when you set this property to true
is:
The exact rule activated when you set this property to external
is:
This setting only allows true
/false
. This is a bit of a catch-all rule for any sort of paths that a user should never be able to make on production that could reveal information or access secret parts of your CF installation. When set to true
, the following rules are activated:
When the profile
is set to production
, the following rule is also added which blocks the ColdFusion RDS access from CF Builder and access to TestBox runners:
If you need to legitimately access any of these paths, you'll need to turn off this setting and custom-add the rules that you want to keep. This setting is a convenience that adds several rules at once.
This setting only allows true
/false
. When set to true
, the following rules are activated:
If you need to legitimately access any of these paths, you'll need to turn off this setting and custom-add the rules that you want to keep. This setting is a convenience that adds several rules at once.
What if you want to go one step deeper? For instance, the blockSensitivePaths
setting blocks a whole bunch of stuff all in one shot. An example might be wanting to open up JUST the RDS IDE integration for your developers, which funnels through the /CFIDE/main/ide.cfm
path which you can see is blocked above.
The solution to this is quite simple and is all based on the ORDER in which your rules are processed. Built-in CommandBox rules are the last to process. This means that if you hijack the predicates with a custom rule FIRST, you can override the behavior of the built in rules. So, if we want to allow access to the /CFIDE/main/ide.cfm path, we just need to add a custom rule that matches that path and then aborts the predicate chain so no further rules fire.
Which gives you the following server.json
The done
handler is what bypasses all subsequent rules for the request.
Since your custom rules are processed BEFORE the built-in rules, that also means that you have the ability to accidentally bypass security rules by applying another rule that intercepts the request! By default a custom rule won't block subsequent rules from firing unless you rewrite the request or use the special done
handler.
And to test/debug your rules, start your server with the trace flag and tail the console output. Every hit in your browser will output several lines of debugging for each rule that is processed.
With the custom rule above, you'd see something like this:
CommandBox bundles some custom predicates and handlers to make your life easier.
The predicate cf-admin()
will returns true
if the incoming URL is to the Lucee or ColdFusion admin:
The handler block-external()
blocks any request not from localhost with a 404 response code. You can pair this with any predicate you choose.
The handler block-cf-admin()
blocks any request to the Lucee or ColdFusion admin with a 404 response code.
You can build your own predicates and handlers by compiling java classes that implement the io.undertow.predicate.PredicateBuilder
or io.undertow.server.handlers.builder.HandlerBuilder
interfaces. If the proper service metadata is present in your jar's META-INF folder, undertow will automatically find and register your builders via Java's service loader.
This isn't super difficult but involves a few moving parts. If you're interested in writing your own handlers and predicates to use in your server rules, reach out on the mailing list and we'll walk you through it.
You can see the custom handlers and predicates documented above in our Runwar source code here:
Custom handlers:
Custom Predicates: