Most people are familiar with HTTPS (HTTP over TLS). If you are unfamiliar with how it works, web browsers first establish a TLS connection with a web server. Then they communicate using the HTTP protocol via this encrypted connection. In addition, browsers have reasonably well-defined rules for validating server certificates. However, they vary in how the user interface works when validation fails.
Aembit acts as a multiprotocol proxy. The Aembit Agent/Proxy:
- proxies connections from a client workload to a server workload,
- injects credentials into the protocol,
- upgrades connections from clear-text to encrypted, and so on.
And as we were developing Postgres TLS support for Aembit, we stumbled upon numerous details that made Postgres TLS different from HTTPS. The rest of the article covers some of these gotchas.
As I mentioned, we are used to TLS being established first and the application protocol riding on top of it.
That is not how Postgres works. Instead, Postgres establishes the application protocol first (in clear) and negotiates whether or not TLS is needed. If TLS is required, a TLS handshake happens, and the rest of the communication goes over TLS.
I think it was done like this because Postgres didn’t support TLS initially, and it was added in a backward-compatible way to avoid breaking either older clients or older servers. However, that resulted in this not-so-elegant TLS negotiation scheme. I believe multiple other older protocols suffer from this, by the way.
Client TLS Modes
Postgres supports six different modes for how a client decides whether to negotiate TLS.
Frankly, I was speechless when I read this for the first time. I was appalled by “I don’t care about security” or the default: “I don’t care about encryption, but I wish to pay the overhead of encryption if the server supports it.”
By the way, all these “I don’t care” guidelines were taken to heart by the community. An excellent article explains that most Postgres instances on the internet are insecure. This article touches on many of the topics that I will mention below.
The table below is copied directly from the official documentation.
|sslmode||Eavesdropping protection||MITM protection||Statement|
||No||No||I don’t care about security, and I don’t want to pay the overhead of encryption.|
||Maybe||No||I don’t care about security, but I will pay the overhead of encryption if the server insists on it.|
||Maybe||No||I don’t care about encryption, but I wish to pay the overhead of encryption if the server supports it.|
||Yes||No||I want my data to be encrypted, and I accept the overhead. I trust that the network will make sure I always connect to the server I want.|
||Yes||Depends on CA policy||I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server that I trust.|
||Yes||Yes||I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server I trust, and that it’s the one I specify.|
The difference between
verify-full depends on the policy of the root CA. If a public CA is used,
verify-ca allows connections to a server that somebody else may have registered with the CA. In this case,
verify-full should always be used. If a local CA is used, or even a self-signed certificate, using
verify-ca often provides enough protection.
The default value for
sslmode is prefer. As is shown in the table above, this makes no sense from a security point of view, and it only promises performance overhead if possible. It is only provided as the default for backward compatibility and is not recommended in secure deployments.
The only two modes that could be secure are
verify-full. The difference between the two modes is subtle.
verify-ca does the certificate and chain of trust validation without checking that the hostname matches.
verify-full does both certificate and chain of trust validation, and it verifies the hostname, too – i.e., that the hostname matches what is in the certificate CN or SubjectAlternativeName.
That again gives me pause. It means that
verify-ca allows impersonating a Postgres server with ANY certificate/private key issued from the same certificate authority. That is incredibly dangerous if this CA issued more than one certificate.
However, it doesn’t stop there. Various client libraries implement
verify-full differently because the Postgres documentation doesn’t specify whether to accept or reject outdated certificate versions, deprecated encryption algorithms, etc. As a result, it’s the Wild West regarding how much protection you will get even by using the strongest mode.
We initially implemented strong
verify-full (based on RustTLS). However, as we implemented it, we realized that it might not work correctly with Postgres RDBMS hosted by different cloud providers.
So, I investigated what Amazon, Google, and Microsoft did with Postgres. I configured AWS RDS, GCP Cloud SQL, and Azure Database to see what was happening.
And here is a summary of server-side certificates that they use and how easy it is to
verify-full with each of them.
|AWS RDS (Postgres)||GCP Cloud SQL (Postgres)||Azure Database for Postgres|
|Is Root CA publicly trusted?||No||No||Yes – DigiCert Global Root CA|
|Is Root CA shared between different instances?||Yes – Shared between all non-Gov regions.||No – A new self-signed CA is created for each instance.||Yes|
|Server certificate CN / Subject alternative name||FQDN of externally accessible endpoint||<projectname>:<instancename>||<id>.database.azure.com|
|Server certificate Subject alternative name||FQDN of externally accessible endpoint||None||<id>.database.azure.com FQDN|
|Can we use verify-full?||Yes||No||Yes|
BAWS did a decent job. However, it would be nice if they used a Root CA which is publicly trusted. Strangely, they didn’t do it, considering they own publicly trusted root CAs and a whole certificate management infrastructure.
CGoogle recommends using verify-ca (vs. verify-full). Since their self-signed CA has just one certificate, verity-ca will have a similar scope of threats as verify-full. However, none of the libraries that do only verity-full will work with it because of the certificate CN/Subjective Alternative name.
They did the best job to let people secure connections to Postgres efficiently and correctly.
First, if you need help with Postgres TLS, understanding it, or making it work, please ping us, and we will be happy to share our knowledge.
Second, we recommend checking that your Postgres RDBMS has TLS enabled and enforced. And your client libraries utilize
verify-full rather than extremely weak default or prefer.
Aembit can help you secure Postgres – both authentication and ensuring that you utilize the most robust verification mode with proper server certificate validation.
Aembit is the Identity Platform that lets DevOps and Security manage, enforce, and audit access between federated workloads.
We invite you to try it today!