When we built Netfoudry’s platform, we followed a typical bastion security pattern: the stack was a fortress, and you had to be inside to do all the fun stuff. At that early stage, it wasn’t yet feasible to use OpenZiti to create the safe zone that we needed in which to develop the foundational infrastructure.
When OpenZiti was ready, we started to look at how we could apply what we’d built and learned. We knew we wanted to adopt the zero trust mindset that had motivated the development of OpenZiti in the first place. We had one strong layer of defense directly exposed to the internet: a perimeter of bastions. We knew that lots of developers were facing the same problem: first, get it working, then try to make it secure by bolting-on armor. We knew bad things would happen if an attacker somehow slipped inside the fortress, but we didn’t want to impede day-to-day operations too much.
OpenZiti was designed to solve this problem. With OpenZiti, it would become possible to start with secure-by-design without slowing down the getting-it-working part [Why every DevOps person should love OpenZiti]. The only problem was that we didn’t have it yet, so we built a temporary fortress with SSH. This is the story of how we retrofitted our infrastructure for zero trust with OpenZiti without rebuilding or shutting down during the process.
Isn’t Secure Shell…Secure?
There are dimensions to “secure” worth mentioning. The OpenZiti approach to zero trust maturity is to secure the application instead of the network. The best way to secure the application is to embed the OpenZiti SDK directly into your application. This brings strong identity and zero trust principles directly into the process space. We won’t get that far in this episode, but we will in a later post. We’ll start by securing the host device instead of the network.
Our immediate need was to remove our bastions from the open internet because vulnerability exploitation is the second most prevalent infection vector according to IBM’s updated X-Force Threat Intelligence Index. The previous report cited active network scanning as the most prevalent infection vector and so it makes sense that discovering vulnerable targets regularly involves active scanning of exposed server ports. Those vulnerabilities are then exploited, data is compromised, and trust is broken. You can learn more in How Do Ransomware Actors Find Victims by NetFoundry’s chief of security, Mike Gorman. Eliminating the network attack surface makes this problem go away.
OpenSSH server has enjoyed a great security track record for the last few years. However, internet exposure can still lead to problems like denial of service attacks, zero-day exploits, and insider misuse. A bastion presents an attack surface analogous to the gate and walls of a fortress. If there’s one weakness then it will eventually be discovered.
It was popular for a while to obscure the SSH server by configuring a non-standard port to listen for connections or require a port knocking pattern to open the listener port. Those tactics may have seemed clever at the time, but would only delay the discovery of the same weakness. I like the idea of having an assurance of security that is not dependent upon the prospective intruder’s lack of imagination.
Gracefully Going Dark
Our build systems, support engineers, admins, and developers use the SSH infrastructure daily. We realized that we would have to step this forward without too much disruption. Our fortress walls comprised a fleet of Linux hosts, each running an OpenSSH server. According to best practices, they were locked down tight but were still listening on the open internet. Going “dark” would mean the internet access we were using to reach the bastion hosts would no longer be available as soon as the firewall exceptions are removed, disallowing inbound 22/TCP.
Enter the Dark Bastions
We treated our bastions like any other app and applied OpenZiti to control the network-level access to the servers’ listening ports. On the SSH server host, we installed an OpenZiti tunneler as a system daemon. Any tunneler can be configured to provide server or client functionality. For the sake of clarity, I’ll refer to “server tunneler” or “client tunneler”. In our case, the server tunneler was bound to a single OpenZiti service for SSH that shovels packets between the OpenZiti network and localhost:22, the device’s host-only loopback interface. This is a simple thing to set up and works for any services you want to expose securely, on any OS, any device.
We continued using the familiar “ssh” (OpenSSH client) on the admin workstations in tandem with a client tunneler. This means we didn’t have to change our OpenSSH client configuration, the domain names we were using, or the “ssh” command-line arguments and options! The global DNS records for the bastions were still in place to allow for a seamless transition.
A neat feature of an OpenZiti tunneling app is its ability to discover OpenZiti services with its built-in DNS. Our workstations then preferred the built-in OpenZiti DNS above global DNS for name queries that match an authorized OpenZiti service. This was powerful because it enabled a seamless transition! Each workstation gained the ability to jump on and off the OpenZiti solution by merely toggling its client tunneler. We retained the global records to support our transition, but nothing stops us from deleting them entirely.