Database Security Hardening: Complete Protection Guide
In this tutorial, you'll learn about Database Security Hardening: Complete Protection Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Database security hardening is the practice of reducing attack surface by implementing encryption, access controls, network isolation, audit logging, and least-privilege principles to protect data from unauthorized access and breaches.
What You'll Learn
You will understand how to encrypt data at rest and in transit, configure RBAC with Least Privilege, prevent SQL Injection, set up audit logging, harden network access, and implement database firewall rules.
Why Security Hardening Matters
Databases contain the most valuable data in any organization. Durga Antivirus Pro stores threat intelligence and customer data; a database breach would expose sensitive information and destroy trust. 83% of data breaches involve databases.
Security Hardening Learning Path
flowchart LR A[Database Design] --> B[Backup and Recovery] B --> C[Security Hardening] C --> D[Replication] C:::current classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
Prerequisites: Database administration experience with PostgreSQL or MySQL. Understanding of network security and basic Linux administration.
Encryption at Rest
Encryption at rest protects data files if storage media is stolen or improperly decommissioned.
PostgreSQL: Filesystem-Level Encryption
PostgreSQL uses the operating system's disk encryption. On Linux, use LUKS.
# Set up encrypted partition for PostgreSQL data
sudo cryptsetup luksFormat /dev/xvdf
sudo cryptsetup open /dev/xvdf pgdata_crypt
sudo mkfs.ext4 /dev/mapper/pgdata_crypt
sudo mount /dev/mapper/pgdata_crypt /var/lib/postgresql/16/main
sudo chown -R postgres:postgres /var/lib/<a href="/databases/postgresql/">PostgreSQL</a>/16/main
PostgreSQL: Transparent Data Encryption (pg_tde)
PostgreSQL 17+ supports TDE via the pg_tde extension.
-- Enable TDE (PostgreSQL 17+)
CREATE EXTENSION pg_tde;
SELECT pg_tde_add_key_provider_file('provider1', '/etc/postgresql/tde_key');
SELECT pg_tde_set_key('my_key', 'provider1');
-- Now create encrypted tables
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
ssn VARCHAR(11),
balance DECIMAL(10,2)
) USING tde_heap;
MySQL: InnoDB Tablespace Encryption
-- Enable encryption for tablespace (MySQL 8.0+)
CREATE TABLESPACE secure_ts ADD DATAFILE 'secure.ibd'
ENCRYPTION='Y' ENGINE=InnoDB;
CREATE TABLE accounts (
id INT PRIMARY KEY,
ssn VARCHAR(11),
balance DECIMAL(10,2)
) TABLESPACE secure_ts;
-- Or per-table encryption
ALTER TABLE accounts ENCRYPTION='Y';
Encryption in Transit (TLS)
PostgreSQL TLS Configuration
# postgresql.conf
ssl = on
ssl_cert_file = '/etc/ssl/certs/server.crt'
ssl_key_file = '/etc/ssl/private/server.key'
ssl_ca_file = '/etc/ssl/certs/ca.crt'
ssl_min_protocol_version = 'TLSv1.3'
ssl_ciphers = 'HIGH:!aNULL:!eNULL:!LOW'
# pg_hba.conf -- Require TLS for all connections
hostssl all all 0.0.0.0/0 scram-sha-256
# pg_hba.conf -- Require client certificate for admin
hostssl all admin 10.0.0.0/8 cert
MySQL TLS Configuration
# my.cnf
[mysqld]
require_secure_transport = ON
ssl_ca = /etc/mysql/ssl/ca.pem
ssl_cert = /etc/mysql/ssl/server-cert.pem
ssl_key = /etc/mysql/ssl/server-key.pem
tls_version = TLSv1.2,TLSv1.3
Role-Based Access Control
PostgreSQL RBAC
-- Principle of least privilege
-- Create read-only role
CREATE ROLE readonly;
GRANT CONNECT ON DATABASE mydb TO readonly;
GRANT USAGE ON SCHEMA public TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly;
-- Create read-write role
CREATE ROLE readwrite;
GRANT CONNECT ON DATABASE mydb TO readwrite;
GRANT USAGE ON SCHEMA public TO readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO readwrite;
-- Create admin role (limited number of users)
CREATE ROLE db_admin WITH LOGIN SUPERUSER;
GRANT db_admin TO admin_user;
-- Assign roles to application users
CREATE USER app_service WITH PASSWORD 'strong_password';
GRANT readwrite TO app_service;
CREATE USER reporting_service WITH PASSWORD 'another_password';
GRANT readonly TO reporting_service;
MySQL RBAC
-- MySQL role-based access
CREATE ROLE 'readonly', 'readwrite';
GRANT SELECT ON mydb.* TO 'readonly';
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'readwrite';
CREATE USER 'app_user'@'%' IDENTIFIED BY 'strong_password';
GRANT 'readwrite' TO 'app_user'@'%';
SET DEFAULT ROLE 'readwrite' TO 'app_user'@'%';
-- Revoke PROCESS privilege from non-admin users
REVOKE PROCESS ON *.* FROM 'app_user'@'%';
SQL Injection Prevention
SQL Injection is the most common database attack. Prevent it at the application layer.
Parameterized Queries (Java)
// VULNERABLE: String concatenation
String query = "SELECT * FROM users WHERE email = '" + email + "'";
// SAFE: Parameterized query
PreparedStatement stmt = connection.prepareStatement(
"SELECT * FROM users WHERE email = ?"
);
stmt.setString(1, email);
ResultSet rs = stmt.executeQuery();
Parameterized Queries (Python)
# VULNERABLE: f-string
query = f"SELECT * FROM users WHERE email = '{email}'"
# SAFE: Parameterized query with psycopg2
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
# SAFE: Parameterized query with SQLAlchemy
result = session.execute(
text("SELECT * FROM users WHERE email = :email"),
{"email": email}
)
Database-Level Protection
-- PostgreSQL: Block fragile superuser functions from apps
REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA public FROM readonly, readwrite;
-- Enable statement timeout to prevent runaway queries
ALTER ROLE app_service SET statement_timeout = '30s';
-- MySQL: Enable sql_mode to reject dangerous patterns
SET GLOBAL sql_mode = 'STRICT_ALL_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
Network Isolation
Database Firewall Rules
# Restrict PostgreSQL to application servers only
sudo ufw allow from 10.0.1.0/24 to any port 5432
sudo ufw deny 5432
# Or use iptables
sudo iptables -A INPUT -p tcp --dport 5432 -s 10.0.1.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 5432 -j DROP
Private Subnet Architecture
# AWS Security Group (Terraform example)
resource "aws_security_group" "database" {
name = "database-sg"
description = "Database security group"
ingress {
description = "PostgreSQL from app tier"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
ingress {
description = "MySQL from app tier"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Audit Logging
PostgreSQL Audit Extension (pgaudit)
# postgresql.conf
shared_preload_libraries = 'pgaudit'
pgaudit.log = 'write,ddl,role'
pgaudit.log_catalog = off
pgaudit.log_level = 'notice'
pgaudit.log_relation = on
-- Create audit role
CREATE ROLE auditor WITH LOGIN PASSWORD 'audit_password';
-- Audit all DDL and DML on sensitive tables
CREATE TABLE audit_test(id INT);
SELECT pgAuditSetRole('auditor');
SELECT * FROM audit_test; -- This is logged
MySQL Audit Plugin
# my.cnf
[mysqld]
plugin-load-add=audit_log.so
audit_log_format=JSON
audit_log_file=/var/log/mysql/audit.log
audit_log_rotate_on_size=100M
Compliance Checklist
| Requirement | PostgreSQL | MySQL | Implementation |
|---|---|---|---|
| Encryption at rest | LUKS / pg_tde | InnoDB encryption | Storage level |
| Encryption in transit | SSL/TLS config | require_secure_transport | TLS 1.2+ |
| Access control | RBAC with roles | RBAC with roles | Least Privilege |
| Audit logging | pgaudit | audit_log plugin | All writes + DDL |
| Network isolation | Firewall + private subnet | Firewall + private subnet | App servers only |
| SQL Injection prevention | Parameterized queries | Parameterized queries | Application layer |
| Password hashing | scram-sha-256 | Caching_sha2_password | Auth method |
Common Security Errors
1. Using Default Ports Without Firewall Rules
Attackers scan default ports (5432, 3306, 27017). Always restrict access by source IP and change ports if exposed to the internet.
2. Storing Passwords in Plaintext in Application Config
Database passwords in config files are a common breach vector. Use a secrets manager (Vault, AWS Secrets Manager, environment variables).
3. Granting SUPERUSER or Root to Application Users
Application code should never run as superuser. Create application-specific roles with minimal permissions.
4. Disabling SSL/TLS for Performance
Unencrypted database traffic can be sniffed on the network. The performance cost of TLS is under 5% and is worth the security.
5. Not Rotating Database Passwords
Static passwords that never change are vulnerable. Implement quarterly password rotation for all database users.
6. Exposing Database Directly to the Internet
Databases should never have public IP addresses. Use bastion hosts or VPNs for admin access.
7. Ignoring Row-Level Security
PostgreSQL Row-Level Security (RLS) restricts which rows a user can see based on policy. Essential for multi-tenant applications.
Practice Questions
1. What is the difference between encryption at rest and encryption in transit?
Encryption at rest protects stored data files. Encryption in transit protects data traveling over the network (TLS/SSL).
2. How do you implement Least Privilege for a reporting user?
Create a readonly role with only SELECT on specific tables or views. GRANT this role to reporting users. Never grant INSERT, UPDATE, or DELETE.
3. What is SQL Injection and how do you prevent it?
SQL Injection is inserting malicious SQL through user input. Prevent with parameterized queries (prepared statements), never string concatenation.
4. How does PostgreSQL Row-Level Security work?
Define a policy that filters rows based on current_user or session variables. Example: CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.tenant_id')::INT).
5. Challenge: Harden a production database.
Given a PostgreSQL database accessible from the internet with a single app_user that has SUPERUSER privileges and plaintext passwords in the application config. List the hardening steps in priority order. Answer: (1) Remove public network access, put in private subnet. (2) Remove SUPERUSER from app_user, create application-specific role. (3) Enable SSL/TLS and require it for all connections. (4) Move passwords to environment variables or secrets manager. (5) Enable audit logging. (6) Implement backup encryption. (7) Set up password rotation schedule.
FAQ
Try It Yourself
Harden a development database:
- Install PostgreSQL and connect without SSL
- Configure TLS certificates and enable
ssl = on - Create a
readonlyrole and areadwriterole - Create users assigned to each role
- Install pgaudit and configure audit logging
- Implement network firewall rules restricting to localhost
- Verify that an unauthenticated connection attempt is rejected
What's Next
You have learned how to encrypt data at rest and in transit, implement RBAC, prevent SQL Injection, and audit database activity. Start by removing public network access from your database and enabling TLS today.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro