AWS RDS for PostgreSQL: encryption in transit and at restMon, 23 Aug 2021
There are many critical steps I usually took to make sure that customers data are secure many of those are suggested by AWS itself. Since RDS is running on AWS is already secure; AWS in fact is managing and patching the OS for you.
I guess there are already many articles already about this but I wanted to give you a little bit more insight from someone that is using this setup in production.
This is a series of articles:
Encryption in transit and at rest
Authentication, access control
Networking, connectivity and auditing
Encryption
Encryption can be split in multiple parts:
Encryption in transit: typically a TSL communication between two applications
Data Partition Encryption: storage encryption that can be performed at the file system level or the block level
Column based encryption and clients-side encryption
Encryption in transit
This is often very overlooked, I have consulted and worked for many companies were TSL wasn't even remotely considered for database connectivity. Let me explain why this is important. First you should never assume that your private network is impenetrable, in fact, the local network that includes the database server and clients often have other processes running: tracing, logging and monitoring agents. These software applications may be or may not be compromised by bad actors within your or outside your organization. This of course without counting developers that have to connect daily and perform some checks. Keep in mind that TLS handshakes add an overhead to your connection performance, it is up to you to decide; personally I would always use it as nowadays data is the new oil (and not the olive one). Connection pooling might alleviate this overhead if configured properly.
Usually a PostgreSQL server have no TLS by default, you need to create, manage and configure certificates by yourself. RDS, on the other hand, comes with managed TLS certificates by default, however it is up to the application or user to make an additional step to make those work properly:
Force only ssl connection on the server side
Force only verified connections on the client side
Force RDS to accept only ssl connections
To force the database server to accept only TSL connections, you can enable the rds.force_ssl
parameter by setting it to 1. By default this parameter is set to 0
If you deploy with cloudformation you can use the DBParameterGroup
resource:
DBParameterGroup:
Type: AWS::RDS::DBParameterGroup
Properties:
Description: RDS for postgres custom default
Family: postgres12
Parameters:
rds.force_ssl: 1
Force TLS connections from your application
Usually when you connect to an RDS by default your client is using TLS without any additional configuration. However this is not fully secure as it is possible to spoof the server identity (for example by modifying a DNS record) without the client knowing. In order to prevent this you must:
Change the parameter
sslmode
Be able to verify the server's identity.
SSLMODE
The so called "chain of trust" can be established by downloading the AWS root certificate (CA) certificate and place it into your application. This certificate includes the DB instance endpoint as the SAN (Subject Alternative Names).
There are many ways for the client to validate the leaf certificate sent by the server. If the parameter sslmode
is set to verify-ca
, the client will verify that the server is trustworthy by checking the certificate chain up to the root certificate stored on the client.
If sslmode
is set to verify-full
, will also verify that the server hostname matches the name stored in the server certificate.
Others sslmode are not recommended.
One note, verify-full might not work properly for custom DNS since the certificates has the RDS hostname.
To check if the connection is actually using TLS, you can also load the sslinfo extension and then call the ssl_is_used()
function. The helper method returns t
if the connection is using SSL, otherwise it returns f
.
Verify server identity
Verifying the server identity is something that your application has to handle. I will make here an example with a Go application.
First thing, in my case I am running the application inside a docker container, you need to download the certificate:
RUN mkdir /certs
RUN curl -sS "https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem" > /certs/rds-combined-ca-bundle.pem
You can either bake them in the image, or download them when the application start. After that we need to configure the client to use those certificates, in my case I am using pgx pool. First we need to load the file into our application:
certPath := "/certs/rds-combined-ca-bundle.pem"
caCert, err := ioutil.ReadFile(certPath)
Then we need to create a certificate pool, which is basically a list of CA that our client will trust and add the file that we loaded in memory :
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
Alternative if you have already some system certificates installed you can use the:
x509.SystemCertPool()
Then create a tls config:
tlsConfig := &tls.Config{}
tlsConfig.ServerName = os.Getenv("POSTGRES_HOST")
tlsConfig.RootCAs = caCertPool
The server name is the RDS endpoint, should be something like:
<name>.<id>.<aws_region>.rds.amazonaws.com
You can find the endpoint in the RDS consoleAnd attach it to your pool or client configuration, in pgx should be something like this:
config, _ := pgxpool.ParseConfig("")
config.ConnConfig.TLSConfig = tlsConfig
One more note is that RDS, as today, does not support mutual TLS, so unfortunately you cannot use client certificates.
Encryption at rest
Another way to secure your data is to encrypt your data itself when they are stored into the database.
There are many way to do it
Data partition encryption
Storage encryption can be performed at the file system level or the block level. This mechanism prevents unencrypted data from being read from the drives if the drives or the entire computer is stolen. This does not protect against attacks while the file system is mounted, in fact the data are still readable after the DB...
In RDS you can