Database Security

My journey from a public-facing root user to production-grade access control


Table of Contents


Introduction: The Security Audit That Humbled Me

When a security-conscious friend reviewed my blog infrastructure, the first thing he asked was: "What database user does your application use?"

"Postgres," I said.

He stared at me. "postgres is the superuser. Your application has superuser access to your entire database."

I shrugged. "It's just a blog."

"A blog that accepts user uploads, user comments, and runs arbitrary search queries. One SQL injection and an attacker can read your entire database, create new accounts, or drop your tables."

That conversation changed how I think about security permanently. The principle of least privilegeβ€”give every component only the minimum access it needsβ€”isn't paranoia. It's the baseline.


PostgreSQL Authentication and pg_hba.conf

pg_hba.conf (Host-Based Authentication) is PostgreSQL's access control file. It defines who can connect, from where, and how they authenticate.

pg_hba.conf Format

Authentication Methods (newest first):

Method
Security
Notes

scram-sha-256

βœ… Strong

Recommended for all modern setups

md5

⚠️ Weak

Legacy; avoid if possible

peer

βœ… Strong

Unix socket only; matches OS username

trust

❌ None

Never use for network connections

reject

Block

Explicitly deny connections

Reload After Changes


Roles and Privileges: The Foundation of Access Control

PostgreSQL uses a unified role system for both users and groups.

Role Hierarchy for a Blog Application

spinner

Creating Roles and Users

Principle of Least Privilege in Practice

Column-Level Privileges

Revoking Privileges


Row-Level Security (RLS)

Row-Level Security restricts which rows a user can see or modify, based on policies:

Setting Application Context

Multi-Tenant with RLS


SQL Injection: The Most Common Attack

SQL injection occurs when user input is concatenated directly into SQL strings.

The Attack

Imagine a search endpoint:

If user_input = "' OR '1'='1":

If user_input = "'; DROP TABLE posts; --":

Prevention: Always Use Parameterised Queries

Defence in Depth

Even with parameterised queries, apply additional layers:

  1. Least privilege: Application user can't drop tables

  2. Input validation: Reject obviously malicious patterns at the application layer

  3. WAF: Web Application Firewall at the network level

  4. pg_audit: Audit all SQL executed


Encryption: Data at Rest and in Transit

SSL/TLS (Encryption in Transit)

Encrypting Sensitive Columns (pgcrypto)

⚠️ Never store passwords as plain text or simple MD5. Use bcrypt (via crypt() in pgcrypto) or argon2 at the application level.

Transparent Data Encryption

For encryption at rest of the entire database, use filesystem-level encryption (LUKS on Linux, FileVault on macOS) or cloud provider managed encryption (AWS RDS encryption, GCP Cloud SQL encryption).


Auditing and Monitoring

pg_audit Extension

Custom Audit Triggers

Monitor Failed Login Attempts


Connection Security

Connection Limits

Enforce Password Policies

Protect Against Brute Force

PostgreSQL itself doesn't have built-in rate limiting for login attempts. Protect at the infrastructure level:

  • pg_bouncer with connection limits

  • fail2ban watching PostgreSQL auth logs

  • Cloud security groups / VPC firewall blocking non-application IPs


Secrets Management

Never Hardcode Credentials

Use a .env File (Development Only)

Production: Use Secret Managers

  • AWS: AWS Secrets Manager or Parameter Store

  • GCP: Secret Manager

  • Azure: Key Vault

  • Self-hosted: HashiCorp Vault

  • Kubernetes: Sealed Secrets or External Secrets Operator


Security Checklist for Production


What I Learned About Database Security

Security isn't a feature you add at the endβ€”it's a practice you embed from the start. The gap between "it works" and "it's secure" is enormous, and closing it doesn't require heroic effort. Most of it is just setting up the right roles, using parameterised queries, and following the principle of least privilege.

The changes that matter most:

  1. Separate database users for each application/service β€” never share credentials

  2. Parameterised queries everywhere β€” no exceptions

  3. Row-Level Security for multi-tenant or user-scoped data β€” push access control into the database itself

  4. SSL always β€” encryption in transit is non-negotiable

  5. Audit logs for sensitive tables β€” know who changed what, when


Next Steps

With security in place, the final operational concern is: what happens when things go wrong? Backup and Recovery ensures you can always restore your data.


Part of the Database 101 Series

Last updated