Three scenarios for implementing time-based security and content switching on your load balancer
How-tos Published on •10 mins Last updatedIt can sometimes be useful to make load balancing decisions based on the time and date. This allows you to conditionally refuse or redirect connections based on the time they're received, for example redirecting users outside of a certain period of time.
Presented below are three different scenarios along with examples of how to implement the logic they describe. I've provided examples that you can apply on a Loadbalancer.org appliance at the proxy layer (HAProxy) and/or our WAF (web application firewall), for maximum flexibility.
Make sure that your load balancer's time zone, date and time, and NTP server settings are correctly configured. These settings can be found in the WebUI under Local Configuration > System Date & Time.
We'll be relying on these settings being correctly configured: if they aren't, you will experience unpredictable results!
Scenario 1: ‘Office hours’-based security and content switching
My web application is only legitimately accessed during office hours. Redirect to a ‘sorry’ page outside of office hours.
This can be surprisingly effective. If your web app is work-focused and your users all work Monday to Friday, 9-to-5, then immediately we can say that an access attempt at 04:30 on a Sunday morning is not a genuine user and may be someone trying to do something malicious.
The HAProxy method
We can apply this logic directly to a Layer 7 HTTP-mode virtual service. First, we make use of the date
function in HAProxy to get a Unix timestamp to work with. We then use the ltime
converter, which indirectly calls the strftime
function: this means we can define a format string that contains the conversion specifiers we're interested in. For example, we can use %H
to get the hour of the day in the 24 hour clock, %d
for the day of the month, and so on.
On a Linux machine, you can test out what a format string will look like by using the
date
command, like so:$ date "+%Y-%m-%d %H:%M:%S"
2023-05-17 18:31:12
- Under Cluster Configuration > Layer 7 - Virtual Services, click Modify next to the virtual service in question.
- Now we'll create our office hours condition.
In the ACL Rules section, click the Add Rule button. - From the Type drop-down list, select Free Type.
- Clear out the Freetype box and add in the following:
acl office-hours date,ltime(%H) -m int 9:16
The%H
returns us the hour of the day in the 24 hour clock, which we then compare (as an integer) against the range 9 to 16, inclusive. - Click Ok.
- Next we'll add the weekday condition.
In the ACL Rules section, click the Add Rule button. - From the Type drop-down list, select Free Type.
- Clear out the Freetype box and add in the following:
acl weekday date,ltime(%u) -m int 1:5
The%u
returns the day of the week, with 1 representing Monday and 7 representing Sunday. - Click Ok.
- Finally, we'll create our action.
Click the Add Rule button again. - From the Type drop-down list, select Free Type.
- Clear out the Freetype box and add in the following:
redirect location /closed.html unless office-hours weekday || { path -m str /closed.html }
This will redirect all requests over to the pageclosed.html
unless theoffice-hours
andweekday
conditions are both true or the request is for the sorry pageclosed.html
itself (to prevent an infinite loop of redirection). - Click Ok.
- Click Update.
- Reload services as directed in the WebUI to apply the new changes.
I'm not so interested in performing a redirect: just deny all traffic outside of office hours.
That's easy enough to do, it just requires using a different action.
Instead of using redirect location /closed.html...
use the following:
http-request deny unless office-hours weekday
This will return a 403 Forbidden status code outside of office hours.
I need to apply this logic to a non-HTTP based service.
That's fine: the same logic can be applied to an HAProxy TCP mode virtual service. We can't perform an HTTP redirect, though, as the underlying protocol may not be HTTP. Instead, we can reject the TCP connection.
Instead of using redirect location /closed.html...
use the following:
tcp-request connection reject unless office-hours weekday
The WAF method
If you're already using a WAF service then you may want to add this time-based logic straight into your custom WAF rules. This helps to keep everything together, and any clients falling foul of your time-based rules will be logged alongside clients that are blocked by the usual OWASP Core Rule Set WAF rules.
In this example, we'll be blocking connections that arrive outside of office hours, which is more likely what you'll be wanting to do when working with a WAF service (redirection is possible but rarely used in practice).
You should not add a WAF service purely to implement these time-based rules. Only consider this option if you already have a WAF service in place.
- Under Cluster Configuration > WAF - Manual Configuration, select the WAF service in question from the drop-down list.
- Add a pair of new SecRules like the following, being sure to pick unique rule IDs that fit in with your WAF service's numbering scheme:
# Deny access outside of office hours, 9-to-5
SecRule TIME_HOUR "!@within 09 10 11 12 13 14 15 16" \
"id:1000,\
phase:1,\
deny,\
log,\
msg:'Outside of office hours access attempt blocked.'"
# Deny access outside of weekdays
SecRule TIME_WDAY "!@within 1 2 3 4 5" \
"id:1010,\
phase:1,\
deny,\
log,\
msg:'Outside of office hours access attempt blocked.'"
3. Click Update.
4. Reload services as directed in the WebUI to apply the new changes.
Ensure that Rule Engine Traffic Blocking is Enabled for the WAF service in question (under Cluster Configuration > WAF Gateway > Modify) otherwise actions like 'deny' and 'redirect' will have no effect.
…but make an exemption for my remote workers who are in another time zone!
It's easy to add an exemption like this. I'm going to assume that you know the IP addresses of your remote workers / satellite offices. Rather than setting up per-time zone sets of rules, which would get messy very quickly, we'll take the simple approach of exempting specific known, trusted workers/offices from our office hours rules.
Let's assume that we have a remote worker at 198.51.100.22 and a satellite office subnet at 203.0.113.0/24 to accommodate.
An HAProxy exemption
We can add the excluded IP addresses and subnets to the end of our action, like so:
redirect location /closed.html unless office-hours weekday || { path -m str /closed.html } || { src 198.51.100.22 203.0.113.0/24 }
The same would apply for the TCP mode variant:
tcp-request connection reject unless office-hours weekday || { src 198.51.100.22 203.0.113.0/24 }
A WAF exemption
We can add in logic to skip over execution of the office hours rules if a client's IP address is known to us.
# Known, trusted remote workers and satellite offices skip the office hours
# checks, so they can access services whenever, whichever time zone they're in.
SecRule REMOTE_ADDR "@ipMatch 198.51.100.22,203.0.113.0/24" \
"id:1020,\
phase:1,\
pass,\
nolog,\
skipAfter:END-OFFICE-HOURS-CHECK"
# Deny access outside of office hours, 9-to-5
SecRule TIME_HOUR "!@within 09 10 11 12 13 14 15 16" \
"id:1000,\
phase:1,\
deny,\
log,\
msg:'Outside of office hours access attempt blocked. %{TIME_HOUR}'"
# Deny access outside of weekdays
SecRule TIME_WDAY "!@within 1 2 3 4 5" \
"id:1010,\
phase:1,\
deny,\
log,\
msg:'Outside of office hours access attempt blocked.'"
SecMarker "END-OFFICE-HOURS-CHECK"
Scenario 2: Powering a competition with time-based content switching
I'm running a competition which ends at midnight on 3 June. Redirect to a ‘competition now closed’ page after that time.
The condition of “midnight on 3 June” means that as soon as the date flips over to 3 June the competition is closed and redirection should start to take place.
The HAProxy method
To keep things simple, we'll look at the day of the year, also known as the ordinal day number. This is a scheme where each day of the year is assigned a number, starting at 1 and going up to 365 (or 366 in a leap year).
What is the ‘day of the year’ for 3 June? (You can search for day of the year calculators, and NASA have a table you can use here.) 2023 isn't a leap year, so 3 June is day 154.
- Under Cluster Configuration > Layer 7 - Virtual Services, click Modify next to the virtual service in question.
- First we'll create our competition page condition.
In the ACL Rules section, click the Add Rule button. - From the Type drop-down list, select path.
- Set URL/Text to the path of the competition page, e.g. /comp-page.html.
- Set the Action to Set Flag.
- Set the Location/Value to is_competition_page.
- Click Ok.
- Now we'll create our day of the year condition.
In the ACL Rules section, click the Add Rule button. - From the Type drop-down list, select Free Type.
- Clear out the Freetype box and add in the following:
acl competition-closed date,ltime(%j) -m int ge 154
The%j
returns us the day of the year, which we then compare (as an integer) to check whether it is greater than or equal to (ge
) 154, i.e. whether the date is 3 June or later. - Click Ok.
- Next, we'll create our action.
Click the Add Rule button again. - From the Type drop-down list, select Free Type.
- Clear out the Freetype box and add in the following:
redirect location /competition-closed.html if is_competition_page competition-closed
This will redirect all requests to/competition-closed.html
if the requested path is the competition page/comp-page.html
and the date is 3 June or later, i.e. the competition has closed. - Click Ok.
- Click Update.
- Reload services as directed in the WebUI to apply the new changes.
The WAF method
Redirection doesn't happen very often in the WAF, but it is possible to accomplish.
- Under Cluster Configuration > WAF - Manual Configuration, select the WAF service in question from the drop-down list.
- Add new SecRules like the following, being sure to pick unique rule IDs that fit in with your WAF service's numbering scheme:
# If the requested path is not the competition path then skip over all of the
# competition redirection logic, as it's irrelevant.
SecRule REQUEST_FILENAME "!@beginsWith /comp-page.html" \
"id:1000,\
phase:1,\
pass,\
nolog,\
skipAfter:END-COMPETITION-REDIRECT"
# If it's earlier than June, skip over the redirect (competition still open)
SecRule TIME_MON "@lt 6" \
"id:1010,\
phase:1,\
pass,\
nolog,\
skipAfter:END-COMPETITION-REDIRECT"
# If it *is* June but it's still earlier than 3 June, skip over the redirect
# (competition still open)
SecRule TIME_MON "@eq 6" \
"id:1020,\
phase:1,\
pass,\
nolog,\
skipAfter:END-COMPETITION-REDIRECT,\
chain"
SecRule TIME_DAY "@lt 3"
# If we've made it this far then we want to go ahead and perform the redirect.
SecAction \
"id:1030,\
phase:1,\
redirect:/competition-closed.html,\
log,\
msg:'Redirected client attempting to access the closed competition page. %{TIME_MON}'"
SecMarker "END-COMPETITION-REDIRECT"
3. Click Update.
4. Reload services as directed in the WebUI to apply the new changes.
Ensure that Rule Engine Traffic Blocking is Enabled for the WAF service in question (under Cluster Configuration > WAF Gateway > Modify) otherwise actions like 'deny' and 'redirect' will have no effect.
Scenario 3: Simplifying maintenance with time-based content switching
I want to send all traffic to my fallback server after 22:50 on 28 May.
This can be useful in advance of performing maintenance on the real back end servers. Beware that this is not the same as performing a graceful server drain via the System Overview page of the load balancer's WebUI: any established client sessions to back end servers will be cut.
The HAProxy method
For simplicity, we'll refer to “22:50 [UTC] on 28 May [2023]” using Unix time / Epoch time and test to see whether we've passed it yet or not.
You can find plenty of Unix time calculators online. Here's the one I used for this example: https://unixtime.org/
The equivalent Unix time is 1685314200. This is what we'll compare against. Again, we'll make use of the date
function in HAProxy to get a Unix timestamp which we'll compare directly: no need to convert to a human-readable time format in this case.
- Under Cluster Configuration > Layer 7 - Virtual Services, click Modify next to the virtual service in question.
- First we'll create our Unix time condition.
In the ACL Rules section, click the Add Rule button. - From the Type drop-down list, select Free Type.
- Clear out the Freetype box and add in the following:
acl maintenance-time date ge 1685314200
Note how we don't use anltime
sample converter this time: we're using the raw Unix time fromdate
, so converting into a human-friendly format like hours and minutes isn't necessary. - Click Ok.
- Next, we'll create our action.
Click the Add Rule button again. - From the Type drop-down list, select Free Type.
- Clear out the Freetype box and add in the following:
use-server backup if maintenance-time
This will send all connections to the backup (fallback) server once themaintenance-time
condition has been met, i.e. it is 22:50 [UTC] on 28 May [2023] or later. - Click Ok.
- Click Update.
- Reload services as directed in the WebUI to apply the new changes.
Wrapping up
There you have it: three different time-based scenarios, and many different ways to tackle them, with lots of variations for you to mix and match, too. You can build on and extend these examples and adapt them to fulfil whatever needs you might have. And if you still have questions after all of that, please feel free to reach out to our support team (support@loadbalancer.org) who can provide further assistance with whatever you might be trying to achieve!