When a database holding the work details of 135,000 police officers and criminal justice professionals gets breached, you're not just looking at a data incident. You're looking at a targeting list for every adversary who wants to compromise the justice system itself.
The PNLD breach exposed names and work email addresses from all 43 police forces in England and Wales, plus the Crown Prosecution Service and court services. No passwords were compromised, but credential stuffing, spear-phishing, and social engineering don't need your password when they've got your work identity and organizational context.
If you're managing a database that maps personnel to sensitive functions, here's how to build defenses that actually hold.
Preparing for Database Security
Access to your database infrastructure:
- Administrative credentials for your database management system
- Network access to configure perimeter controls
- Permission to modify authentication and authorization policies
Current state documentation:
- Data classification map showing what's stored and where
- Network topology diagram including database connections
- Current access control list (who has what level of access)
- Existing backup and monitoring configurations
Tools and capabilities:
- Web Application Firewall (WAF) or equivalent
- Database activity monitoring solution (native or third-party)
- Multi-factor authentication system compatible with your directory service
- Encryption tools (TLS certificates, database encryption modules)
- SIEM or centralized logging platform
Stakeholder buy-in:
- Approval for potential service interruption during implementation
- Budget for tools if you're filling gaps
- Agreement on acceptable access latency (MFA adds friction)
Step-by-Step Implementation
1. Segment the Database Network
Isolate your database on a dedicated VLAN or subnet. No direct internet exposure.
Configure firewall rules:
# Allow only application servers on specific ports
iptables -A INPUT -p tcp -s 10.0.2.0/24 --dport 5432 -j ACCEPT
iptables -A INPUT -p tcp --dport 5432 -j DROP
Replace 5432 with your database port (3306 for MySQL, 1433 for SQL Server). Replace the source subnet with your application tier's range.
Deploy a bastion host for administrative access. Never allow direct SSH or RDP to the database server from workstations.
2. Enforce Least-Privilege Access
Map every service account and user to their actual data requirements. The PNLD breach exposed records across multiple agencies because they shared a common database. Partition access by organizational boundary.
Create role-based views instead of granting table access:
CREATE VIEW pnld_west_yorkshire AS
SELECT name, email, organization
FROM personnel
WHERE organization = 'West Yorkshire Police';
GRANT SELECT ON pnld_west_yorkshire TO west_yorkshire_app;
Revoke wildcard privileges:
REVOKE ALL PRIVILEGES ON *.* FROM 'legacy_admin'@'%';
Audit your SHOW GRANTS output. If you see ALL PRIVILEGES or GRANT OPTION on service accounts, you've got cleanup to do.
3. Implement Connection-Level Controls
Deploy certificate-based authentication for application connections. Username and password aren't sufficient when you're protecting targeting lists.
Generate client certificates for each application:
openssl genrsa -out app_client.key 2048
openssl req -new -key app_client.key -out app_client.csr
openssl x509 -req -in app_client.csr -CA ca.crt -CAkey ca.key -out app_client.crt
Configure your database to require certificates. For PostgreSQL:
# postgresql.conf
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_ca_file = 'ca.crt'
# pg_hba.conf
hostssl all all 10.0.2.0/24 cert clientcert=verify-full
4. Enable Query-Level Monitoring
You need to know when someone's pulling bulk exports or querying outside normal patterns. The PNLD incident involved 1.9GB of data, that's not a single lookup.
Configure database audit logging. For PostgreSQL:
ALTER SYSTEM SET log_statement = 'mod';
ALTER SYSTEM SET log_connections = on;
ALTER SYSTEM SET log_disconnections = on;
SELECT pg_reload_conf();
For MySQL:
SET GLOBAL general_log = 'ON';
SET GLOBAL log_output = 'TABLE';
Set up alerting for anomalous queries:
- SELECT statements returning more than 1,000 rows
- Queries from new IP addresses
- Access outside business hours
- Failed authentication attempts (threshold: 5 in 10 minutes)
Ship these logs to your SIEM. Local logs disappear when the server's compromised.
5. Encrypt Data at Rest and in Transit
Enable Transparent Data Encryption (TDE) for your database files. For SQL Server:
CREATE MASTER [Key Wrapping](/glossary/key-wrapping) BY PASSWORD = 'StrongPassword123!';
CREATE CERTIFICATE TDE_Cert WITH SUBJECT = 'TDE Certificate';
CREATE DATABASE ENCRYPTION KEY WITH ALGORITHM = AES_256 ENCRYPTION BY SERVER CERTIFICATE TDE_Cert;
ALTER DATABASE personnel_db SET ENCRYPTION ON;
Force TLS for all connections. Reject plaintext:
-- MySQL
GRANT USAGE ON *.* TO 'app_user'@'%' REQUIRE SSL;
6. Implement Rate Limiting at the Application Layer
Even with proper access controls, a compromised application credential can exfiltrate data. Deploy rate limiting on your API or application tier.
Example Nginx configuration:
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
location /api/personnel {
limit_req zone=api_limit burst=20;
proxy_pass http://backend;
}
Adjust the rate based on legitimate usage patterns. Monitor for 503 errors indicating you're blocking real users.
Validation: How to Verify It Works
Test network segmentation:
# From an unauthorized host
nmap -p 5432 database.internal.domain
# Expected: filtered or no response
Verify certificate enforcement:
# Attempt connection without client cert
psql "host=db.internal.domain sslmode=require" -U app_user
# Expected: connection rejected
Confirm audit logging:
SELECT * FROM personnel LIMIT 10;
-- Then check your logs for the query
tail -f /var/log/postgresql/postgresql.log | grep SELECT
Simulate bulk extraction:
SELECT * FROM personnel LIMIT 10000;
Your SIEM alert should fire within your configured threshold (typically 1-5 minutes).
Test rate limiting:
for i in {1..50}; do curl https://api.domain/personnel; done
You should see 429 Too Many Requests after your burst limit.
Maintenance and Ongoing Tasks
Weekly:
- Review database audit logs for anomalous patterns
- Check certificate expiration dates (alert at 30 days)
- Validate backup integrity (restore test to non-production)
Monthly:
- Audit user and service account access (remove stale accounts)
- Review firewall rules for unnecessary permits
- Test your incident response runbook with a tabletop exercise
Quarterly:
- Rotate database credentials and certificates
- Penetration test from both internal and external perspectives
- Review and update your data classification map
After any personnel change:
- Revoke access for departing staff immediately
- Audit what the departing user accessed in their final 30 days
- Reset shared credentials if the user had access
The PNLD breach happened because someone got in and walked out with a directory of every person tasked with enforcing the law. Your database might not hold police records, but if it maps people to sensitive functions, you're holding a targeting list. Build controls that assume breach and limit what an attacker can take when they get in. Because they will.



