Database Security: Roles, Privileges & Row-Level Security
Implement RBAC, column-level masking, row-level security, encryption, and audit trails.
๐ฏ What You'll Learn
- Role-based access control (RBAC) with role hierarchies
- Column-level privileges and data masking views
- Row-level security (RLS) for multi-tenant isolation
- Encryption at rest and in transit with pgcrypto
- Security audit logging and suspicious activity detection
๐ฅ Roles & Privileges
Database security starts with the principle of least privilege: every user gets only the permissions they need, nothing more. Think of roles as security badges โ a "readonly" badge opens fewer doors than an "admin" badge.
Roles & Privileges
RBAC with role hierarchies and default privileges
-- Database Roles: WHO can access the database
-- Create roles (users are roles with LOGIN privilege)
CREATE ROLE app_readonly;
CREATE ROLE app_readwrite;
CREATE ROLE app_admin;
-- Create login users and assign roles
CREATE USER api_service WITH PASSWORD 'strong_password_here';
CREATE USER analyst WITH PASSWORD 'another_strong_pwd';
CREATE USER dba_admin WITH PASSWORD 'super_secure_pwd';
GRANT app_readwrite TO api_service;
GRANT app_readonly TO analyst;
GRANT app_admin TO dba_admin;
-- Role
...๐ Column-Level Security
Sometimes you need to hide specific columns โ salaries, SSNs, medical records. Column privileges and masking views let analysts query the table without seeing sensitive data.
๐ก Pro Tip โ SECURITY DEFINER Functions
A SECURITY DEFINER function runs with the function owner's privileges, not the caller's. Use it to let users access specific rows of sensitive data (like their own salary) without granting broad table access.
Column-Level Security
Hide columns and mask sensitive data
-- Column-Level Security: hide sensitive data
-- Revoke access to salary column from readonly role:
REVOKE SELECT ON employees FROM app_readonly;
GRANT SELECT (id, name, department, hire_date) ON employees TO app_readonly;
-- app_readonly can see id, name, dept, hire_date โ but NOT salary
-- Create a view that masks sensitive data:
CREATE VIEW v_employees_safe AS
SELECT
id,
name,
department,
hire_date,
-- Mask email: show first 3 chars + domain
LEFT(email, 3) || '***@'
...๐ก๏ธ Row-Level Security (RLS)
RLS is the database equivalent of blinders โ each user sees only the rows they're allowed to see. Essential for multi-tenant SaaS where all tenants share the same table.
โ ๏ธ Common Mistake
Forgetting to also add a WITH CHECK clause. USING filters what you can read; WITH CHECK validates what you can write. Without WITH CHECK, a user could INSERT rows belonging to another tenant.
Row-Level Security
Per-user and per-tenant data isolation
-- Row-Level Security (RLS): filter rows per user
-- "Each user sees only their own data"
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- Policy: users see only their own orders
CREATE POLICY user_orders_policy ON orders
FOR ALL
TO app_readwrite
USING (customer_id = current_setting('app.current_user_id')::int)
WITH CHECK (customer_id = current_setting('app.current_user_id')::int);
-- USING: filters rows on SELECT, UPDATE, DELETE
-- WITH CHECK: validates rows on INSERT, UPDATE
...๐ Encryption
Encryption protects data at rest (stored on disk), in transit (over the network), and at the column level (specific sensitive fields). Defense in depth โ encrypt at every layer.
Encryption Strategies
Column encryption, password hashing, and TDE
-- Data Encryption strategies
-- 1. pgcrypto extension for column-level encryption
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Encrypt sensitive data:
INSERT INTO patient_records (patient_name, ssn_encrypted, diagnosis)
VALUES (
'John Doe',
pgp_sym_encrypt('123-45-6789', 'encryption_key_here'),
pgp_sym_encrypt('Confidential diagnosis', 'encryption_key_here')
);
-- Decrypt when needed (requires the key):
SELECT patient_name,
pgp_sym_decrypt(ssn_encrypted, 'encryption_key_here')
...๐ Audit Trails
Security isn't just prevention โ it's also detection. Audit logs record who changed what and when, enabling forensics and compliance reporting.
Security Auditing
Audit triggers and suspicious activity detection
-- Security Auditing: track who did what
-- 1. Enable logging in postgresql.conf:
-- log_statement = 'all' -- log every SQL statement
-- log_connections = on -- log connections
-- log_disconnections = on -- log disconnections
-- log_line_prefix = '%m [%p] %u@%d ' -- timestamp, PID, user@db
-- 2. Audit trigger (detailed per-row tracking)
CREATE TABLE security_audit_log (
id BIGSERIAL PRIMARY KEY,
table_name TEXT,
operation TEXT,
user_name TEXT DEFAULT curr
...๐ Quick Reference
| Layer | Mechanism |
|---|---|
| Authentication | Roles, passwords, certificates |
| Authorization | GRANT/REVOKE, column privileges |
| Row isolation | Row-Level Security policies |
| Encryption | pgcrypto, TDE, SSL/TLS |
| Auditing | Triggers, pg_audit, logging |
๐ Lesson Complete!
You now have a comprehensive security toolkit for your databases. Next, learn how to defend against SQL injection attacks!
Sign up for free to track which lessons you've completed and get learning reminders.