Limiting network egress

Blocking incoming traffic is only half of a firewall's features. Today let's look at limiting outgoing traffic!

Now that we have a firewall enabled, we need to focus on better securing our network.

Despite our best intentions, developers make mistakes. We write bugs, leave diagnostic code in a file during a commit, or mistakenly hard-code credentials in a file meant to access an external API. Many of these issues can be discovered and patched during a code review, but they can still crop up. Especially when a team is in a hurry to push a release.

Yesterday’s examples used ufw to block incoming traffic from a potential attacker. There’s also a chance that your application itself might be the party responsible for a breach. A malicious piece of code might attempt to exfiltrate data to a foreign server. Or an engineer might accidentally upload test content on a staging server that triggers a production API integration.

It’s a good idea to lock down not just what systems can talk to your server; it’s also a good idea to lock down what systems your server can talk to.

Rather than allowing all outgoing connections as we did above, we can lock things down by first blocking all outgoing traffic:

sudo ufw default deny outgoing

You can then whitelist specific outgoing ports to any destination. For example, the following rule allows outgoing DNS queries against any host:

sudo ufw allow out 53

Assume, for example, your server also needs to leverage an API hosted at api.displace.tech. You know that this API lives at the IP address 234.56.78.90, so you’ll whitelist that IP for only TLS encrypted traffic:

sudo ufw allow out to 234.56.78.90 port 443

By allowing DNS queries, your application can dynamically resolve api.displace.tech to 234.56.78.90. By unblocking outgoing HTTPS calls to that host, your application can reach out to the server and interact with it remotely. However, the application cannot send requests to http://api.displace.tech (note the missing s). Network communication over HTTP uses port 80 rather than 443.

The application similarly cannot send requests to https://api.maliciousdomain.com (regardless of the TLS specification). The server will be able to resolve the domain, but as the IP address it resolves to is missing from the allow list the server won’t be able to talk to it.