Part 6: The Sands of Time – Enforcing Change Frequency
Or, “Your Password Has Expired. This Is Not The Password You Are Looking For.”
Read the full article here: https://hey-dba.com/articles/part-6-the-sands-of-time-enforcing-change-frequency/
Welcome back, time-traveling guardians of PostgreSQL security! We’ve journeyed through the intricate mazes of Part 1: The Guardians of the Gate – Unmasking PCI DSS Password Rules, prepared our battle station in Part 2: The Environment Setup – Preparing for pgtle Glory, conquered the beast of password complexity in Part 3 and Part 4, and even banished the spectral menace of password reusability in Part 5. Your PostgreSQL database is becoming a fortress worthy of the Galactic Empire (but, you know, for good).
(Just need the quick code snippets and deployment steps for password expiration? Your Quick-Reference: Part 6 – Change Frequency awaits!)
If you’re here to conquer the entire saga, a true hero’s journey, then make sure to check out our Series Overview Page for all the other exciting chapters and quick-reference guides on PostgreSQL PCI DSS compliance.
Before we embark on this temporal mission, a crucial transmission from command: While we’re charting a course through the perilous asteroid fields of PCI DSS password rules, remember this article is a technical showcase, not a Jedi Master’s consultancy advice or a blueprint for your Death Star’s production environment. We’re here to demonstrate the incredible power of pgtle and PostgreSQL’s hooking system – think of it as showing you how to build your own custom lightsaber, using the PCI DSS password requirements as our training dummy. This code has not been tested in any galaxy far, far away (i.e., a production environment), so wield it wisely and test rigorously in your own simulated battles!
Today, we confront the relentless march of time. Passwords, like old droids, can grow stale and vulnerable if left unchanged. Fear not, for PCI DSS v4.0.1 has rules for these temporal threats, and our trusty pgtle is ready to enforce them, ensuring your PostgreSQL password management is as timely as a hyperdrive jump! This is paramount for robust PostgreSQL security and overall PCI DSS compliance.
The PCI DSS Mandate: Time Waits for No Password (PCI DSS 8.3.9 & 8.6.3)
Let’s consult the sacred scrolls of PCI DSS v4.0.1. Two requirements stand out in our temporal battle for password security:
Standard PostgreSQL roles have a rolvaliduntil property, but this only defines an expiration date for the role itself, not a forced password change interval. This is where our pgtle extension will once again prove its worth, helping us implement these critical PostgreSQL security measures by enforcing the VALID UNTIL clause for strict PCI DSS compliance.
The pgtle Strategy: Our Temporal Guardian for Password Expiration
To enforce these time-based password policies, our pgtle strategy will involve:
It’s important to note a key distinction here: PostgreSQL’s native VALID UNTIL clause (or rolvaliduntil) sets an expiration date for the role’s ability to log in. While useful, it can be extended by an ALTER ROLE ... VALID UNTIL command without a new password being set. This means the password itself might not have been updated, which is what PCI DSS 8.3.9 and 8.6.3 truly require. Our pgtle strategy overcomes this by:
Database Design Changes (Version 0.4)
To track these temporal elements and support our password expiration logic, we’ll enhance our existing tables. This is vital for maintaining database security and PCI DSS compliance.
Add max_validity_interval to password_check.profiles:
-- In the pgtle.install_extension_version_sql block:
ALTER TABLE password_check.profiles
ADD COLUMN IF NOT EXISTS max_validity_interval INTERVAL DEFAULT '90 days';
-- Example updates for different roles:
UPDATE password_check.profiles SET max_validity_interval = '1 year' WHERE role = 'pci_app_users';
UPDATE password_check.profiles SET max_validity_interval = '30 days' WHERE role = 'pci_admin_users';
UPDATE password_check.profiles SET max_validity_interval = '15 minutes' WHERE role = 'pci_new_users'; -- For testing
Add valid_until to password_check.password_history:
ALTER TABLE password_check.password_history
ADD COLUMN IF NOT EXISTS valid_until TIMESTAMPTZ;
-- Update existing history records with a calculated valid_until based on current profiles
UPDATE password_check.password_history ph
SET
valid_until = ph.change_timestamp + (
WITH profiles AS (
SELECT
CASE p.role
WHEN 'pci_new_users' then 1
WHEN 'pci_admin_users' then 2
WHEN 'pci_app_users' then 3
WHEN 'pci_standard_users' then 4
END as priority,
p.role,
p.max_validity_interval
FROM
password_check.profiles p
ORDER BY 1
),
members as (
SELECT
r_member.rolname as member,
r_role.rolname as role,
pr.max_validity_interval,
ROW_NUMBER() OVER (PARTITION BY r_member.rolname ORDER BY pr.priority) as rn
FROM pg_catalog.pg_roles AS r_member
JOIN pg_catalog.pg_auth_members AS am ON r_member.oid = am.member
JOIN pg_catalog.pg_roles AS r_role ON am.roleid = r_role.oid
JOIN profiles pr ON pr.role = r_role.rolname
)
SELECT
m.max_validity_interval
FROM
members m
WHERE
rn=1 AND
m.member = ph.username
);
-- Ensure any remaining NULLs (e.g., for users not in a PCI role yet) get a default validity
UPDATE password_check.password_history ph
SET
valid_until = ph.change_timestamp + '15 minutes'::INTERVAL -- A small default for safety
WHERE
valid_until IS NULL;
-- Make the column NOT NULL after populating
ALTER TABLE password_check.password_history
ALTER COLUMN valid_until SET NOT NULL;
Introducing the clientauth_hook: Your Login Gatekeeper for Account Security
Before we dive into the Version 0.4 code, let’s take a moment to understand the clientauth_hook. This is a powerful feature provided by pgtle that allows you to intercept and control client connection attempts before PostgreSQL’s standard authentication methods are even evaluated. This is where the clientauth_hook truly becomes the bouncer at the digital cantina, checking IDs and ensuring only those with valid, unexpired credentials get past the velvet rope. This is fundamental for robust account security and login enforcement.
Updating Our pgtle Extension: Version 0.4 for Enhanced Password Policy
Now, let’s create an update path for our pci_password_check_rules extension, moving from 0.3 to 0.4. This script will:
The full SQL code for this update path is provided below:
Full SQL code Below.
Full code Here:
New Features and Logic Explained (Version 0.4 Changes):
This 0.4 update significantly enhances password validity enforcement by leveraging both passcheck_hook and the new clientauth_hook, solidifying our PostgreSQL security posture.
New max_validity_interval column in password_check.profiles:
SELECT
-- ... existing fields ...
history_limit,
max_validity_interval -- This line is new
INTO
-- ... existing variables ...
current_history_limit,
current_max_validity_interval -- This line is new
FROM
password_check.profiles
WHERE
role='pci_admin_users';
Enforcing Password Validity (PCI DSS 8.3.9 & 8.6.3) via VALID UNTIL in passcheck_hook:
DECLARE
-- ...
current_max_validity_interval INTERVAL;
-- ...
BEGIN
-- ... (after fetching profile parameters) ...
-- Ensure the account is not created/updated with "VALID UNTIL NULL"
IF valid_null THEN
invalid_pw_reason := invalid_pw_reason || 'New user password must have a "VALID UNTIL" date. "VALID UNTIL NULL" is not allowed.';
-- Ensure the "VALID UNTIL" clause is not specified above the maximum value for the role.
ELSE
IF valid_until > (NOW() + current_max_validity_interval) THEN
invalid_pw_reason := invalid_pw_reason || 'Account validity date cannot be more than ' || current_max_validity_interval || ' in the future for this role. ';
END IF;
END IF;
-- ... (rest of the hook logic) ...
-- Include the valid_until in the password history insert
INSERT INTO password_check.password_history (username, password_hash, valid_until)
VALUES (_username_param, new_password_hashed, NOW() + current_max_validity_interval);
Installation/Update: The Time-Warp Protocol for PostgreSQL Security!
To bring your pgtle extension to Version 0.4 and enable these temporal defenses, you’ll need to execute the update path and then enable the clientauth hook globally. This is a crucial step for PostgreSQL security and PCI DSS compliance.
Verification: Did the Time-Warp Succeed for your PostgreSQL Password Management?
After applying the update and restarting your PostgreSQL cluster, let’s confirm everything is shipshape:
-- Check all available pg_tle extensions (should show 0.4 as default_version)
SELECT * FROM pgtle.available_extensions();
-- Check all available versions for your extension
SELECT * FROM pgtle.available_extension_versions();
-- Check the update paths defined for your specific extension
SELECT * FROM pgtle.extension_update_paths('pci_password_check_rules');
-- Verify the new max_validity_interval column was added to profiles
SELECT * FROM password_check.profiles;
-- Verify the valid_until column was added to password_history and is NOT NULL
SELECT username, password_hash, change_timestamp, valid_until FROM password_check.password_history LIMIT 5;
-- Verify clientauth is enabled and postgres is skipped
SELECT name, setting, short_desc, context FROM pg_settings WHERE name LIKE 'pgtle%';
You should see pci_password_check_rules listed with 0.4 as its default_version, and pgtle.enable_clientauth as on, with pgtle.clientauth_users_to_skip set to postgres. This confirms your PostgreSQL security setup is working as intended.
Testing Your Temporal Defenses!
Time to put your new password validity policies to the test! These scenarios will demonstrate your enhanced PostgreSQL password management and adherence to PCI DSS password rules.
Scenario 1: Enforcing VALID UNTIL (PCI DSS 8.3.9 & 8.6.3) via passcheck_hook
Scenario 2: Enforcing Password Expiration on Login (PCI DSS 8.3.9 & 8.6.3) via clientauth_hook
You’ve now added powerful temporal defenses to your PostgreSQL fortress, ensuring passwords don’t overstay their welcome and forcing changes when they do! This is a prime example of proactive PostgreSQL security and PCI DSS compliance in action.
The Adventure Continues…
Our quest for PostgreSQL security is nearing its epic conclusion! In the next thrilling installment, we’ll dive into implementing account lockout and inactive account management to fend off those pesky brute-force attacks and secure dormant user accounts. Stay tuned for Part 7: The Bouncer at the Gate – Implementing Account Lockout and Inactive Account Management!