© rukanoga, 123RF.com
Security without firewalls
Gift-WrappedSecurity
Many years ago I remember somebody mentioning that rather than running a firewall, they were just using TCP Wrappers. This piqued my interest, because all my customers talked about when it came to Internet security was how much their proprietary firewall had cost them or which bundled features with their firewall guaranteed greater security for their servers.
Admittedly, the idea of totally dismissing firewalls goes against the grain – and more than just a little – however, you might be surprised to learn that I have successfully run several sets of production servers for many years with the absence of a firewall entirely. If you're wondering what I mean by "successfully," I mean without the servers' being compromised.
My brief addendum to those last two sentences is that running Netfilter [1] – or, to most peoples' minds, the tool that controls Netfilter, iptables – on a Linux server brings a great number of benefits, such as automatically dropping illegitimately formed traffic that might pose a threat to your applications or catching traffic to a port you forgot to close.
A word to the wise, therefore, is that if you fail to implement correctly the approach that I present in this article, iptables is the perfect hero to come to your rescue and make that tiny mistake less disastrous to your servers' security.
Firewalls Are Overrated
With a little planning and some consideration, you can safely connect Linux boxes to the Internet without anything but some Access Control Lists (ACLs) combined with an eye for minimalism. I'm referring to keeping the number of packages (and more specifically network services) to a minimum. By having, say, only three ports open on your server, such as HTTP, SMTP, and SSH, you're significantly limiting the number of attack vectors on your system.
Aside from network ports posing a threat, there's a rule of thumb that says for every thousand lines of code you add to a server, you potentially add another security hole. This is important because you can lock down all the network-facing ports but still make your applications vulnerable by adding screeds of unneeded code to your server.
Additionally, keeping packages at a minimum means not only that your backups are leaner (and thus easier to store because they'll use less storage space, and if they're smaller, they should also be quicker to restore in an emergency) but also that the code on your server is less vulnerable to attack. Another side effect is that fewer packages might mean less services that can accidentally open up a network port, which would otherwise add to your risk. Such a scenario might only be mitigated by running a comprehensive firewalling policy such as an efficacious iptables default-deny.
Gift Wrap Your Security
I mentioned ACLs, and in this case, I'm talking about limiting access to services installed on your server, such as SSH, to specific IP addresses or restricting access with other rulesets. The open ports example I used was that of HTTP, SMTP, and SSH. It's highly unlikely that you would want to restrict all HTTP traffic to your server by IP address (e.g., unless the web server was on an intranet), but you might only want email from specific mail servers locked down by IP address. And, to my mind, almost every Internet-facing SSH server should be locked down by IP address. You can achieve this easily using a fantastic piece of software called TCP Wrappers, which uses tcpd to log, check, restrict, and avoid spoofed network connections efficiently and in real time. TCP Wrappers also handles hostname lookups with ease, whether they're entered in a local database or a public DNS database.
TCP Wrappers has been around since the early days of the Internet and were written by a programmer named Wietse Venema, who is most famous for writing the truly excellent mail server software, Postfix [2]. The story goes that the talented Venema needed to keep track of attacks on workstations at a university and wrote a piece of software capable of limiting port access by rules.
Ignoring the use of a somewhat ancient network service, namely identd, I'll focus on the use of IP addresses and names for configuring who is allowed to connect to ports.
How Do You Use It?
On many of the popular Linux distributions, two files live inside the /etc directory: hosts.allow and hosts.deny. For the sake of simplicity, I'll start by denying all access to a server over the SSH service and then explicitly allow certain users access. It's similar to the deny by default approach I touched on in another article [3]. To achieve this, the /etc/hosts.deny file would look like this:
sshd: ALL
To allow some IP addresses to connect /etc/hosts.allow, the file would simply look like this:
sshd: 10.10.10.10, 1.2.3.4, 21.21.21.21
TCP Wrappers works nicely, even if you change the standard SSH port (it's usually TCP port 22) to port 2222, for example, to keep port scans from filling up your logs. Without TCP Wrappers enabled, scans might run dictionary attacks on your server where password combinations are guessed by one of many automated attack methods.
As well as being able to take individual IP addresses, hosts.allow can happily handle the CIDR notation of classless IP address ranges, such as:
sshd: 10.10.10.0/24, \
1.2.3.4/32, 21.21.21.0/19IP addresses hosts.allow can be set to accept connections from hostnames, too.
One example might be
ALL: *.domain.tld
or you might have an administrative group of machines that live under a set of hostnames:
ALL: *.admin.domain.tld
For clarity, even without the wildcard asterisk at the start of those examples, you can still allow any machine under that hostname umbrella, even if the line just starts with a period. Turning that example on its head entirely, ending with a period
ALL: 12.34.
would allow all machines with the first two octets in their IP address starting 12.34, so, for example, 12.34.56.78 will be allowed to connect to ALL services and not just SSH. As well as these flexible options, you can also declare old school subnets directly:
sshd: 1.2.3.4/255.255.255.0
As well as the ALL parameter, you can use LOCAL, which allows any machine without a dot in its hostname. You could also edit your local hostname database (somewhat confusingly called /etc/hosts) to declare that an oft-used computer called friend had the IP address 10.10.10.10. With the LOCAL switch in your allow file then, access to that service could be granted to all local machines with the following:
sshd: LOCAL
By accepting connections from any hostname without a dot, this might also cover any machine under the same domain name as the server you're working on.
The KNOWN operator also exists, but I think it should be avoided, in case hostnames aren't served correctly when name servers are unavailable.
Buy this article as PDF
(incl. VAT)
Buy ADMIN Magazine
Subscribe to our ADMIN Newsletters
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Most Popular
Focus On Self-Hosting
• Running Your Own Matrix Server with Synapse
• Reliable Network Storage with TrueNAS Community Edition
• Self-Hosted File Syncing with Seafile
• Wiring the Modern Stack with Node-RED
• Self-Hosted Collaboration with Forgejo
• Self-Hosted PaaS with Coolify
• Build and Host Docker Images
• Self-Hosted Pritunl VPN Server with MFA
Support Our Work
ADMIN content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.
