Connecting Python to a Postgresql database
Discover how to connect Python to a Postgresql database without storing credentials or keys on filesystem.
Connecting Python to PostgreSQL is made seamless with the psycopg2 library. This article demonstrates how to connect to a PostgreSQL database using Python without storing credentials in plain text or in the .pgpass file. The architecture we will be using consists of a PostgreSQL database hosted on an Ubuntu 24 (Noble) Amazon Web Services (AWS) EC2 instance and Python 3.10 running on Ubuntu 22 (Jammy). We will use a symmetric AWS KMS key to encrypt and decrypt database credentials.
Requirements
- Python 3 (preferably running on Ubuntu)
- Python libraries: psycopg2, boto3
- AWS: Key Management Service (KMS) Customer managed symmetric key
- PostgreSQL database cluster (needs to accept TCP/IP connections)
Get database connection parameters
First we will need to know the database host and database name which will be used to create a config file with .ini extension in the user's home directory. Then we will prompt for the user name and password using Python's getpass to avoid echoing back the data input by the user. We'll use our AWS KMS key to perform encryption of the user name and password. Lastly the encrypted data will be written to the config file w/ permissions set to non-readable, non-writable, and non-executable. The following script is an example you can adapt. Replace placeholders like 'region' and 'keyid' with values from your own AWS and PostgreSQL setup. You may also want to add your own error handling and logging.
# ------------------------------------------------------------------
# aws_postgresql_config.py
#
# Creates a .ini file containing encrypted credentials used # to connect to an AWS EC2 hosted PostgreSQL database.
# ------------------------------------------------------------------
import pwd
import sys
import os
from getpass import getpass
import json
import traceback
import base64
import boto3
def main():
# Instantiate an AWS KMS client instance.
aws_kms = boto3. client( 'kms', 'region')
# KMS key id.
the_key = 'keyid'
# Prompt for the connection params.
print( 'Please provide connection parameters for database:' + '\n')
db_host = getpass( 'Please enter database host:')
db_name = getpass( 'Please enter database name:')
db_user = getpass( 'Please enter database user name:')
db_auth = getpass( 'Please enter user password:')
# Config file.
config_dir = os . getenv ( 'HOME' ) + '/' + 'conn' + '/' + db_host
config_file = config_dir + '/' + db_name + '.ini'
# Concat + delimit params, convert to bytes, then base64 encode.
delim = chr( 7)
params = db_host + delim + db_name + delim + db_user + delim + db_auth
byte_params = params. encode( 'utf-8')
encode_params = base64. b64encode( byte_params)
# Encrypt the params.
encryption_data = aws_kms. encrypt(
KeyId = the_key,
Plaintext = encode_params,
EncryptionAlgorithm = 'SYMMETRIC_DEFAULT')
# Write the encryption params as binary to the config file.
with open( config_file, 'wb') as file:
file. write( encryption_data[ 'CiphertextBlob'])
# Reset file perms.
os. chmod( config_file, 0o000)
Connecting to the PostgreSQL database
Now that we have the connection config file created we are ready to connect to the PostgreSQL database cluster and begin running SQL queries. The below aws_postgresql_connect.py program reads the encrypted data from the config file created by the aws_postgresql_config.py program then decrypts the byte data with the same AWS KMS key. It is important to include the encryption algorithm (EncryptionAlgorithm='RSAES_OAEP_SHA_256') in the decrypt call if using an asymmetric key in AWS KMS. In this case we are using a symmetric key so the EncryptionAlgorithm='SYMMETRIC_DEFAULT' encryption algorithm property isn't needed but we still include it for posterity sake. The data read is then split by delimiter to derive user name/password and the config file permissions are disabled. Finally we use the imported psycopg2 library to connect to our PostgreSQL cluster in AWS EC2 and run a sample query. This connect script assumes you already ran aws_postgresql_config.py with the same host and database name, and that your AWS credentials allow calls to KMS.
# ------------------------------------------------------------------
# aws_postgresql_connect.py
#
# Connects to an AWS EC2 hosted PostgreSQL database by reading
# and decrypting credentials stored in a config file. Dependent
# on the aws_postgresql_config.py having been run first.
# ------------------------------------------------------------------
import pwd
import sys
import os
import base64
import boto3
import psycopg2
def main():
# Instantiate an AWS KMS client instance.
aws_kms = boto3. client( 'kms', 'region')
# KMS key id.
the_key = 'keyid'
# Config file.
# Replace 'your-db-host' and 'your-db-name' with the values
# you used when running aws_postgresql_config.py.
config_dir = os . getenv ( 'HOME' ) + '/' + 'conn' + '/' + 'your-db-host'
config_file = config_dir + '/' + 'your-db-name' + '.ini'
# Make config file readable.
os. chmod( config_file, 0o440)
# Get the connection credentials from config file.
with open( config_file, 'rb') as file:
credentials_bytes = file. read()
# Reset config file perms.
os. chmod( config_file, 0o000)
# Decrypt the connection credentials.
credentials_decrypt = aws_kms. decrypt(
CiphertextBlob = credentials_bytes,
KeyId = f'{ the_key}' ,
EncryptionAlgorithm = 'SYMMETRIC_DEFAULT')
# Decode the credentials and split by delimiter.
delimiter = chr( 7)
credentials_decode = base64. b64decode( credentials_decrypt[ 'Plaintext'])
credentials = bytes. decode( credentials_decode, 'utf-8')
db_data = credentials. split( delimiter)
db_host = db_data[ 0]
db_name = db_data[ 1]
db_user = db_data[ 2]
db_auth = db_data[ 3]
# Form a connection string & connect.
connection_string = \
" host=" + db_host + \
" dbname=" + db_name + \
" user=" + db_user + \
" password=" + db_auth + \
" port=5432"
conn = psycopg2. connect( connection_string)
# Example query.
cur = conn. cursor()
cur. execute( 'SELECT CURRENT_DATE;')
rs = cur. fetchall()
# Iterate through resultset.
for row in rs:
print( row)
# Close cursor and connection.
cur. close()
conn. close()
Key Takeaways
In this article we have connected to a PostgreSQL database cluster hosted in AWS EC2 without storing database credentials or encryption keys anywhere on the filesystem. Although the code presented could be refactored to eliminate redundancies or abstracted into a class, the key points listed below could help in securely connecting to databases.
- Minimize visibility and access to database credentials
- If hosting in a cloud service provider, make use of symmetric keys to protect sensitive data such as user names or passwords
- Use encryption when storing database credentials on filesystem
- Try to avoid storing encryption keys or plain text user names/passwords