Using HAProxy to allow ingress traffic from internet to intranet resources

As mentioned in a previous post, I recently heard about a case where someone wanted to allow an AWS EC2 instance on a public IP address to access a corporate intranet resource. The proposed solution was to run an HAProxy server on the corporate network that could proxy traffic from the AWS compute instance to the intranet server. This post will give a demonstration of that.

Please note: Don’t bypass your organization’s security controls! Implementing a solution like this without prior authorization or approval would likely constitute a violation of an organization’s acceptable use policy or information security policies.

The 3 servers for this example:

  1. The corporate intranet resource, intranet.osric.net or 192.0.2.43
  2. The HAProxy host, haproxy.osric.net or 198.51.100.200
  3. The AWS EC2 instance, ec2-203-0-113-155.compute-1.amazonaws.com or 203.0.113.155

Although I’ve primarily used HAProxy for load balancing in the past, it can easily be used as a basic reverse proxy server. A load-balancer, after all, is basically a reverse proxy with multiple backend servers. I set up a new RedHat Enterprise Linux (RHEL) 10 host, haproxy.osric.net and installed and enabled HAProxy:

$ sudo dnf install haproxy
$ sudo systemctl enable haproxy

I then added an HAProxy config at /etc/haproxy/conf.d/proxy.cfg HAProxy should automatically find a config file ending with .cfg at that location. The default config included at /etc/haproxy/haproxy.cfg will also be loaded. It includes some config options you won’t need!

Here is the content I added to proxy.cfg:

listen http-proxy
    bind :80
    server intranet 192.0.2.43:80

I restarted the haproxy service:

$ sudo systemctl restart haproxy

After that I was able to use curl to access the intranet resource, both from the haproxy host and from the EC2 instance:

From the haproxy host:

$ curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Intranet - osric.net</title>
</head>
<body>
<h1>Intranet - osric.net</h1>
<p>There is nothing to see here.</p>
</body>
</html>

From 203.0.113.155 (the AWS EC2 instance):

$ curl 198.51.100.200
<!DOCTYPE html>
<html>
<head>
<title>Intranet - osric.net</title>
</head>
<body>
<h1>Intranet - osric.net</h1>
<p>There is nothing to see here.</p>
</body>
</html>

Restricting proxy access

As it is, the proxy is open to the world. Anyone can now access resources from intranet.osric.net via the proxy. How can we lock it down so that only the intended IP address, 203.0.113.155, can access it?

I found the article Allow and Block IP Addresses in HAProxy (2026) with some tips. I modified the proxy.cfg file:

$ cat /etc/haproxy/conf.d/proxy.cfg
listen http-proxy
    bind :80
    acl authorized_ip src 203.0.113.155
    http-request deny if !authorized_ip
    server intranet 192.0.2.43:80

From the HAProxy server the HTTP request is denied by HAProxy with an HTTP 403 error:

$ curl localhost
<html><body><h1>403 Forbidden</h1>
Request forbidden by administrative rules.
</body></html>

However, the request still works from the IP address of the authorized AWS EC2 instance:

$ curl 198.51.100.200
<!DOCTYPE html>
<html>
<head>
<title>Intranet - osric.net</title>
</head>
<body>
<h1>Intranet - osric.net</h1>
<p>There is nothing to see here.</p>
</body>
</html>

Host-based firewall restrictions

It’s best if HAProxy never even sees the request from unauthorized sources. We can achieve this by allowing HTTP requests from only the authorized IP address via the host-based firewall.

In this case, I’m using nftables to manage the host-based firewall. There’s a sample configuration for nftables on RHEL 10 at /etc/nftables/main.nft. I added the following line to the allow chain:

ip saddr 203.0.113.155 tcp dport 443 accept

The full allow chain now looks like this:

chain allow {
    ct state established,related accept
    meta l4proto @allowed_protocols accept
    iifname @allowed_interfaces accept
    tcp dport @allowed_tcp_dports accept
    ip saddr 203.0.113.155 tcp dport 443 accept
}

To load the ruleset into nftables, I used the following:

$ sudo nft -f /etc/nftables/main.nft

It’s important when working with nftables that you don’t cut off your own access! The sample file at /etc/nftables/main.nft includes SSH as an allowed port in @allowed_tcp_dports, but the configuration is set to reject any connection that is not explicitly allowed. It is also important to note that the sample file also contains other rules you likely won’t need (it allows port 9090/tcp connections from all sources, for example).

Also important to note: changes to nftables are not persistent by default. For example, if you reboot the system and check for existing nftables rules, nothing is returned:

$ sudo nft list ruleset

To persist the rules on RHEL 10, take the following steps:

Uncomment the following line in /etc/sysconfig/nftables.conf:

#include "/etc/nftables/main.nft"

Then, enable and start the nftables service:

$ sudo systemctl enable nftables
$ sudo systemctl start nftables

The nftables service is not a daemon, it just runs at startup (or when the service is started). It is normal for the service to report that it is exited, as in the excerpt below:

$ systemctl status nftables
● nftables.service - Netfilter Tables
     Loaded: loaded (/usr/lib/systemd/system/nftables.service; enabled; preset: disabled)
     Active: active (exited) since Thu 2026-10-08 22:03:58 UTC; 3h 52min ago

You can check and confirm that the rules are present (and that they persist after a reboot):

$ sudo nft list ruleset

What about TLS connections (HTTPS)?

That’s a great question. I did find a site that has what looks to be helpful info, but I have not tested it yet:

How to Configure HAProxy SSL Passthrough for IPv4 Backend Servers

That will be a project for another day.

Bash alias Magic Eight Ball

I’m perhaps overly-fond of the Magic Eight Ball (or Magic 8-Ball, if you prefer).

It is perhaps surprising, then, that I have never created this Bash alias before:

alias 8ball='shuf -n1 -e "As I See It Yes" "Ask Again Later" "Better Not Tell You Now" "Cannot Predict Now" "Concentrate And Ask Again" "Don'"'"'t Count On It" "It Is Certain" "It Is Decidedly So" "Most Likely" "My Reply Is No" "My Sources Say No" "Outlook Good" "Outlook Not So Good" "Reply Hazy Try Again" "Signs Point To Yes" "Very Doubtful" "Without A Doubt" "Yes" "Yes Definitely" "You May Rely On It"'

"Don'"'"'t Count On It" was the trickiest part, fortunately StackOverflow helped me with How to escape single quotes within single quoted strings?

Harmless fun:

$ 8ball
Ask Again Later
$ 8ball
My Reply Is No
$ 8ball
Very Doubtful

Almost as good as running yes yes or yes no!

nmap scans the top 1000 ports by default, but which 1000?

From man nmap:

The simple command nmap target scans 1,000 TCP ports on the host target.

You might reasonable ask, which 1,000 ports is it? Is the particular port in which I am interested included?

Fortunately, nmap has a list of ports/services that includes how frequently they are used. From this we can get the top 1000:

grep -v '^#' /usr/share/nmap/nmap-services | sort -rk3 | head -n1000
  • The initial grep is to filter out the comments (lines that begin with the hash mark).
  • The sort command sorts in descending order, by the 3rd column (the frequency).
  • The final head command displays only the top 1000 results.

In my cases, I wondered if the radmin port, 4899/tcp, was included in an nmap scan. I piped the above command to grep to find out:

grep -v '^#' /usr/share/nmap/nmap-services | sort -rk3 | head -n1000 | grep 4889
radmin  4899/tcp        0.003337        # Radmin (www.radmin.com) remote PC control software

It is included in a default nmap scan.

Is there an easier way to do this? Drop me a line in the comments!

Combining pcap (packet capture) files

Motivation: I wanted to combine 2 or more packet capture, or pcap, files in order to create an example:

  • One that contains just malicious (or simulated malicious) network traffic
  • Another contains legitimate, non-malicious network traffic

Many example packet capture files focus either specifically on malware, exploits, C2 traffic, etc. (like Security Onion’s list of PCAPs for Testing) or on examples of legitimate traffic (like Wireshark’s Sample Captures). I wanted to create an example that would interweave such sources and intersperse malicious and legitimate traffic, as they would typically occur concurrently.

In addition to tcpdump, there are three CLI tools provided by Wireshark that I used to help accomplish this:

  • capinfos – provides high-level data about a packet capture file
  • mergecap – combines 2 or more packet capture files
  • editcap – modified packet details, such as timestamps, in a packet capture file

Continue reading Combining pcap (packet capture) files

Linux policy based routing

Problem: I have a host that has 2 active network interfaces. One is used as a management port (eth0), one is used as an FTP dropbox (eth1).

Both can route to the Internet, but all connections other than FTP on eth1 are blocked via iptables. The default route uses the interface for the FTP dropbox, but I have a static route configured for the subnet that includes my management and monitoring hosts so that I can SSH to the host and check on host availability, disk space, mail queue, etc.

However, the static route means that I cannot monitor the FTP dropbox, since FTP connection attempts coming in on one interface and IP address are then routed out via the management interface and IP address.

Solution: Use policy-based routing to direct the system to consult a different routing table for connections coming in on the FTP interface.

It sounds easy enough.
Continue reading Linux policy based routing

cp, mv, ownership and attributes

I had always been under the impressions that when moving a file from one Linux filesystem to another (i.e. a new inode is created), that mv is essentially a cp command followed by an rm command.

That’s not quite correct. It is essentially a cp --archive command followed by an rm command.
Continue reading cp, mv, ownership and attributes

Integrating FreeIPA authentication with GitHub Enterprise

The GitHub Enterprise – Using LDAP documentation lists FreeIPA as a supported LDAP service.

Although I was able to successfully test a basic LDAP connection, the test failed after I specified the Email (using value “mail”) and SSH key (using value “ipaSshPubKey”) fields. I received the following error: Continue reading Integrating FreeIPA authentication with GitHub Enterprise

ipactl error: Failed to start Directory Service: Command ‘/bin/systemctl start dirsrv@FREEIPA-OSRIC-NET.service’ returned non-zero exit status 1

Earlier today I got an alert that the LDAP service on my FreeIPA server was down. This was not long after I had received another alert that the drive space on the /var partition was critical. I logged on, freed up some drive space, and tried to start the service:

$ sudo ipactl start
Starting Directory Service
Failed to start Directory Service: Command '/bin/systemctl start dirsrv@FREEIPA-OSRIC-NET.service' returned non-zero exit status 1

I tried running systemctl directly to see the error message:

$ sudo systemctl start dirsrv@FREEIPA-OSRIC-NET.service

Continue reading ipactl error: Failed to start Directory Service: Command ‘/bin/systemctl start dirsrv@FREEIPA-OSRIC-NET.service’ returned non-zero exit status 1

ESXi upgrade from 5.5 to 6.7

ESXi 5.5 recently reached end-of-support (see End of General Support for vSphere 5.5), but my sales rep informed me that I was eligible for a free upgrade. Great! I set about doing just that.

First of all, I should note that you can’t upgrade directly from 5.5 to 6.7, so I upgraded to 6.5 first. I ran into several missteps along the way, which I have documented here: Continue reading ESXi upgrade from 5.5 to 6.7

Creating a histogram with Gnuplot

Gnuplot has plenty of examples on its histograms demo page. The demos use immigration.dat as a datasource, which you can find in gnuplot’s GitHub repository: immigration.dat data source.

While the examples demonstrate many of the available features, it’s not clear what some of the specific options do. You could read the documentation, but who has time for that? Some of us are just trying to create really simple histograms and don’t need to master the nuances of gnuplot.
Continue reading Creating a histogram with Gnuplot