Your Cheat Sheet for PostgreSQL Account Lockout and Inactive Account Management with pgtle
Welcome, vigilant guardians! This quick-reference guide distills the essence of “Part 7: The Bouncer at the Gate – Implementing Account Lockout and Inactive Account Management” into actionable steps. If you’re looking to rapidly implement account lockout and inactive account management using pgtle in PostgreSQL for PCI DSS compliance, you’ve come to the right place! This guide is your essential resource for enhancing PostgreSQL security and ensuring database security best practices.
For the full narrative and deeper explanations, you can always refer back to the main blog post: Part 7: The Bouncer at the Gate – Implementing Account Lockout and Inactive Account Management. And for the entire saga, check out our Series Overview Page.
This guide focuses on Version 0.5, which introduces robust account lockout and inactive account management features.
For the full, executable code, including all SQL scripts discussed in this article, please visit our GitHub repository: hey-dba-pgtle-pcidss. The specific update script for this part can be found here: pci_password_check_rules_0.5_up_0.4-0.5.sql.
The PCI DSS Mandate: Keeping the Gates Secure
- Requirement 8.3.4: Account Lockout.
- Mandate: After a maximum of 10 unsuccessful authentication attempts, the account must be locked out for at least 30 minutes, or until an administrator manually unlocks it. This is crucial for brute-force attack prevention.
- Requirement 8.2.6: Lock or disable inactive user accounts after a maximum of 90 days.
- Mandate: User accounts that have not been active for a maximum of 90 days must be either locked or disabled. This is vital for inactive account management.
The pgtle Strategy: Our Bouncer and Sentry Droid
Our strategy leverages pgtle to enforce these critical account security policies:
- Tracking Login Attempts and Activity: New tables (
password_check.user_login_activity) will meticulously record every login attempt (successful or failed) and the last successful activity for each user. - Modifying
clientauth_hookfor Account Lockout: Our existingclientauth_hook(from Part 6) is enhanced to:- Check for active lockouts in
password_check.locked_accountsand immediately reject connections if an account is locked. - Increment failed login attempts on authentication failure.
- Lock the account in
password_check.locked_accountsif failed attempts exceed the configuredlockout_threshold.
- Check for active lockouts in
- Implementing Inactive Account Management: A new
pgtlefunction (password_check.manage_inactive_accounts) will periodically identify and disable accounts based on theirlast_successful_loginagainst a configurableinactive_threshold.
Database Design Changes (Version 0.5)
These schema changes are crucial for supporting both account lockout and inactive account management:
- New
password_check.user_login_activityTable:- Purpose: Tracks
username,last_successful_login,failed_attempts, andlast_activity. - Snippet:
CREATE TABLE IF NOT EXISTS password_check.user_login_activity ( username TEXT PRIMARY KEY, last_successful_login TIMESTAMPTZ DEFAULT NULL, failed_attempts INTEGER DEFAULT 0 NOT NULL, last_activity TIMESTAMPTZ DEFAULT NULL );
- Purpose: Tracks
- New
password_check.locked_accountsTable:- Purpose: Tracks accounts currently locked due to failed login attempts, including
username,locked_until, andlocked_by. - Snippet:
CREATE TABLE IF NOT EXISTS password_check.locked_accounts ( username TEXT PRIMARY KEY REFERENCES password_check.user_login_activity(username), locked_until TIMESTAMPTZ NOT NULL, locked_by TEXT DEFAULT 'SYSTEM' NOT NULL );
- Purpose: Tracks accounts currently locked due to failed login attempts, including
- Updated
password_check.profilesTable (New Columns):- Purpose: Adds configurable parameters for lockout and inactivity per role.
- New Columns:
lockout_threshold(INTEGER, default 10),lockout_duration_minutes(INTEGER, default 30),inactive_threshold(INTERVAL, default ’90 days’). - Snippet:
ALTER TABLE password_check.profiles ADD COLUMN IF NOT EXISTS lockout_threshold INTEGER DEFAULT 10, ADD COLUMN IF NOT EXISTS lockout_duration_minutes INTEGER DEFAULT 30, ADD COLUMN IF NOT EXISTS inactive_threshold INTERVAL DEFAULT '90 days';
- New
password_check.v_role_members_parametersView:- Purpose: Consolidates user, role, and policy parameters (max validity, lockout, inactivity thresholds) for easy querying.
- Snippet: (Refer to the main blog post for the full
CREATE OR REPLACE VIEWstatement.)
- New
password_check.get_member_priority_roleFunction:- Purpose: Helper function to determine a user’s most prioritized PCI role for dynamic policy application.
- Snippet: (Refer to the main blog post for the full
CREATE OR REPLACE FUNCTIONstatement.)
Key clientauth_hook Logic (Version 0.5 for PCI DSS 8.3.4)
The clientauth_hook is central to account lockout enforcement:
- Initial Lockout Check: Checks
password_check.locked_accounts. IfNOW() < locked_until,RAISE EXCEPTIONto reject login. If expired, deletes entry and resetsfailed_attempts. - Update
user_login_activity: Inserts/updates user record. - Failed Login (
status = -1): Incrementsfailed_attempts. Iffailed_attempts >= lockout_threshold, inserts/updatespassword_check.locked_accountswithlocked_until = NOW() + lockout_duration_minutes. Resetsfailed_attemptsto 0. - Successful Login (
status = 0): Resetsfailed_attemptsto 0, updateslast_activityandlast_successful_login. Includes existing password expiration check from Part 6.
New password_check.manage_inactive_accounts Function (PCI DSS 8.2.6)
This function handles inactive account management:
- Purpose: Identifies and disables accounts inactive for longer than their
inactive_threshold. - Logic: Iterates through
user_login_activityandv_role_members_parameters. Iflast_successful_loginis older thaninactive_threshold, executesALTER ROLE <username> NOLOGIN;. - Execution: Typically run via an external scheduler (e.g., cron job).
Installation/Update: Version 0.5
To bring your pgtle extension to Version 0.5:
- Connect to Your Maintenance Database:
psql -d heydbamaint -U postgres - Execute the Update Path Code: Copy the entire SQL code from the
pci_password_check_rules_0.5_up_0.4-0.5.sqlfile (provided in the main blog post) and paste it into yourpsqlprompt. - Apply the Extension Update:
ALTER EXTENSION pci_password_check_rules UPDATE TO '0.5'; - Ensure
clientauthHook is Enabled (Crucial!):ALTER SYSTEM SET pgtle.clientauth_users_to_skip TO 'postgres'; SELECT pg_catalog.pg_reload_conf(); ALTER SYSTEM SET pgtle.enable_clientauth TO 'on'; -- IMPORTANT: This setting requires a database restart to take full effect.
Verification: Did the Bouncer and Sentry Droid Come Online?
After applying the update and restarting PostgreSQL:
SELECT * FROM pgtle.available_extensions();(should show 0.5)SELECT * FROM pgtle.available_extension_versions();SELECT * FROM pgtle.extension_update_paths('pci_password_check_rules');\d password_check.user_login_activity(verify table exists)\d password_check.locked_accounts(verify table exists)SELECT role, lockout_threshold, lockout_duration_minutes, inactive_threshold FROM password_check.profiles;(verify new columns)SELECT * FROM password_check.v_role_members_parameters LIMIT 5;(verify view)SELECT name, setting, short_desc, context FROM pg_settings WHERE name LIKE 'pgtle%';(verifyclientauthsettings)
Testing Your Account Security Defenses!
Scenario 1: Testing Account Lockout (PCI DSS 8.3.4)
- Create test user:
CREATE ROLE locked_test_user WITH PASSWORD 'SecureP@ssw0rd!'; - Attempt failed logins (10+ times):
psql -U locked_test_user -d heydbamaint- Expected: Subsequent login attempts fail with
FATAL: Account locked_test_user has been locked...
- Expected: Subsequent login attempts fail with
- Verify lockout (as superuser):
SELECT username, failed_attempts, last_activity FROM password_check.user_login_activity WHERE username = 'locked_test_user';SELECT username, locked_until, locked_by FROM password_check.locked_accounts WHERE username = 'locked_test_user'; - Wait for lockout to expire, then log in (should succeed).
Scenario 2: Testing Inactive Account Management (PCI DSS 8.2.6)
- Create new user:
CREATE ROLE inactive_test_user WITH PASSWORD 'InactiveP@ssw0rd!'; GRANT pci_standard_users TO inactive_test_user; - Simulate old login:
UPDATE password_check.user_login_activity SET last_successful_login = NOW() - INTERVAL '91 days' WHERE username = 'inactive_test_user'; - Run function:
SELECT password_check.manage_inactive_accounts();- Expected:
NOTICE: Account inactive_test_user disabled due to inactivity.
- Expected:
- Verify status:
\du inactive_test_user(should show
Cannot login) - Attempt login (should fail).
You’ve successfully implemented robust account lockout and inactive account management policies directly within your PostgreSQL database using pgtle! This significantly strengthens your PostgreSQL security posture and helps you achieve PCI DSS compliance.




