Or, “SQL-Powered Password Fortresses: Building Beyond ‘Password123’!”
Welcome back, brave coders and database defenders! In Part 1: The Guardians of the Gate – Unmasking PCI DSS Password Rules, we deciphered the ancient scrolls of PCI DSS v4.0.1 password rules. Then, in Part 2: The Environment Setup – Preparing for pgtle
Glory, we meticulously prepared our PostgreSQL environment and successfully completed our pgtle
installation. Finally, Part 3: The Enigma of Complexity – Taming Passwords with pgtle
(Part 1) laid out our grand strategy for tackling password complexity.
(Just need the quick code snippets and deployment steps? Your Quick-Reference: Part 4 – Complexity (Part 2) 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. This series is your ultimate resource for achieving PostgreSQL PCI DSS compliance.
Now, with our pgtle
extension standing ready in our PostgreSQL fortress and our battle plan for password complexity understood, it’s time for the main event: deploying the code! This installment of The Epic Quest for Secure Passwords is all about getting your hands dirty with PL/pgSQL and transforming those theoretical PCI DSS password complexity requirements into active, in-database enforcement. Get ready to witness your PostgreSQL database actively rejecting weak passwords with the sternness of a grumpy wizard, enhancing your overall PostgreSQL password security!
Before We Code: The Essential pgtle
Setup Ritual (Again!)
Before you unleash the code, ensure your PostgreSQL environment is properly configured for pgtle
‘s powerful password validation capabilities. Think of these as the pre-flight checks before launching your security rocket! This pgtle
setup is crucial for robust database security.
- Create Your Maintenance Database: This is where your
pg_tle
extension and its related code will live, keeping your core application databases clean. We’ll call itheydbamaint
– because even database security needs a home base!Create your maintenance database using:
CREATE DATABASE heydbamaint;
- Grant
pgtle_admin
to Your Superuser: This role is essential for managingpgtle
extensions and their password policies. In most cases, you’ll grant it to yourpostgres
superuser. It’s like giving your main character the “Admin Privileges” perk forpgtle
configuration.Grant
pgtle_admin
to your superuser using:GRANT pgtle_admin TO postgres;
- Point
pgtle
to Your Maintenance DB: This tellspgtle
where to find its password-checking functions. Without this,pgtle
will be looking in all the wrong places, like a Stormtrooper looking for droids. This is a keypgtle
configuration step for effective PostgreSQL password security.Point
pgtle
to your maintenance DB and reload the configuration using:ALTER SYSTEM SET pgtle.passcheck_db_name TO 'heydbamaint'; SELECT pg_catalog.pg_reload_conf(); -- Apply the change without a full restart
- Enable
pgtle
Password Checking: This activates thepasscheck
hook, which is the heart of our password validation system.'on'
will enable the hook but only execute your function if it’s registered. It’s polite, but optional.'require'
will force the hook to be present and registered; if not, password changes will fail. This is the “no excuses” mode, ideal for strict PCI DSS compliance. Choose wisely, young Padawan!
Enable
pgtle
Password Checking and reload the configuration using:ALTER SYSTEM SET pgtle.enable_password_check TO 'on'; -- OR -- ALTER SYSTEM SET pgtle.enable_password_check TO 'require'; SELECT pg_catalog.pg_reload_conf(); -- Apply the change
Once these pgtle
setup steps are complete, you’re ready to deploy your custom pgtle
extension and its powerful PostgreSQL password policy enforcement!
The Grand Unveiling: Our pci_password_check_rules
Extension Code – Version 0.1
Our journey begins with pci_password_check_rules_0.1.sql
. This script represents our initial foray into pgtle
-powered password validation. It’s a “proof of concept” that focuses solely on enforcing the core PCI DSS 8.3.6 complexity rules for standard users. It does not yet include the sophisticated profiles logic for role separation. Think of it as building a basic, but functional, laser blaster before you upgrade to a lightsaber. It ensures your passwords meet the minimum length and character type requirements.
The full SQL code for this version is available in a separate document: pci_password_check_rules_0.1.sql – Initial PCI DSS Password Complexity Code.
SELECT
PGTLE.INSTALL_EXTENSION (
'pci_password_check_rules', -- Name of your custom pg_tle extension
'0.1', -- Incremented version for non-human account handling
'Enforces PCI DSS 4.0.1 password complexity', -- Description of the extension
$_pgtle_$
CREATE SCHEMA IF NOT EXISTS password_check;
REVOKE ALL ON SCHEMA password_check FROM PUBLIC;
GRANT USAGE ON SCHEMA password_check TO PUBLIC;
CREATE OR REPLACE FUNCTION password_check.passcheck_hook(
username TEXT,
password TEXT,
password_type pgtle.password_types,
valid_until TIMESTAMPTZ,
valid_null BOOLEAN
) RETURNS VOID AS $_FUNCTION_$ -- ADDED THIS BLOCK LABEL
DECLARE
invalid_pw_reason TEXT := '';
current_min_length INTEGER := 12;
current_require_special_char BOOLEAN := TRUE;
current_require_uppercase BOOLEAN := TRUE;
current_require_lowercase BOOLEAN := TRUE;
current_require_digit BOOLEAN := TRUE;
BEGIN
--- 1. Apply Password Complexity Checks (PCI DSS 8.3.6) ---
IF length(password) < current_min_length THEN
invalid_pw_reason := invalid_pw_reason || 'Password must be at least ' || current_min_length || ' characters long. ';
END IF;
IF current_require_uppercase AND password !~ '[A-Z]' THEN
invalid_pw_reason := invalid_pw_reason || 'Password must contain at least one uppercase letter. ';
END IF;
IF current_require_lowercase AND password !~ '[a-z]' THEN
invalid_pw_reason := invalid_pw_reason || 'Password must contain at least one lowercase letter. ';
END IF;
IF current_require_digit AND password !~ '[0-9]' THEN
invalid_pw_reason := invalid_pw_reason || 'Password must contain at least one number. ';
END IF;
IF current_require_special_char AND password !~ '[^a-zA-Z0-9\s]' THEN
invalid_pw_reason := invalid_pw_reason || 'Password must contain at least one special character. ';
END IF;
--- 2. Final Check and Raise Exception / Update History ---
IF invalid_pw_reason != '' THEN
RAISE EXCEPTION 'Password validation failed for user %: %', username, invalid_pw_reason;
END IF;
END;
$_FUNCTION_$ LANGUAGE plpgsql;
REVOKE ALL ON FUNCTION password_check.passcheck_hook(TEXT, TEXT, pgtle.password_types, TIMESTAMPTZ, BOOLEAN) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION password_check.passcheck_hook(TEXT, TEXT, pgtle.password_types, TIMESTAMPTZ, BOOLEAN) TO PUBLIC;
SELECT pgtle.register_feature_if_not_exists('password_check.passcheck_hook', 'passcheck');
$_pgtle_$
);
Logic Explained: pci_password_check_rules_0.1.sql
This script uses pgtle.install_extension
to package our custom logic into a pg_tle
extension. Here’s a breakdown of its key components and their purpose:
CREATE SCHEMA IF NOT EXISTS password_check;
This line creates a dedicated schema named
password_check
. This is a best practice for organizing database objects, keeping our custom functions and tables separate from the defaultpublic
schema and other system objects. It enhances database security by providing a clear namespace for our password policy components.CREATE OR REPLACE FUNCTION password_check.passcheck_hook(...)
This defines our main
PL/pgSQL
function,passcheck_hook
. This is the function thatpgtle
will call every time a password is created or changed.- It declares variables to store
invalid_pw_reason
(to accumulate error messages) and hardcodedcurrent_min_length
,current_require_special_char
, etc., which represent the PCI DSS 8.3.6 requirements (12 characters, requiring special, upper, lower, and digit). - The
BEGIN...END
block contains a series ofIF
statements. EachIF
statement checks a specific password complexity rule (e.g.,length(password) < current_min_length
). If a rule is violated, a descriptive message is appended to theinvalid_pw_reason
variable. - Finally,
IF invalid_pw_reason != '' THEN RAISE EXCEPTION ... END IF;
is the critical part. If any rule was violated (meaninginvalid_pw_reason
is not empty), it raises an SQL exception with all the accumulated reasons. This exception prevents the password change from completing, ensuring only compliant passwords are set.
- It declares variables to store
REVOKE ALL ON FUNCTION ... FROM PUBLIC;
and
GRANT EXECUTE ON FUNCTION ... TO PUBLIC;
These lines manage the function’s permissions. By revoking all and then granting only
EXECUTE
toPUBLIC
, we ensure thatpg_tle
(which runs as a superuser) can call our hook, but regular users cannot directly execute it, maintaining a secure execution context.SELECT pgtle.register_feature_if_not_exists('password_check.passcheck_hook', 'passcheck');
This is the command that registers our
passcheck_hook
function withpgtle
‘spasscheck
feature. This tellspgtle
to invoke this specific function whenever a password validation event occurs.
Installation: Unleashing Version 0.1!
Ready to make your PostgreSQL database a bastion of password strength? Follow these steps to install our shiny new pci_password_check_rules
extension (Version 0.1):
- Connect to Your Maintenance Database: Open your
psql
client and connect to the maintenance database you created (e.g.,heydbamaint
) as a superuser (postgres
).psql -d heydbamaint -U postgres
- Execute the Extension Code: Copy the entire SQL code from the pci_password_check_rules_0.1.sql – Initial PCI DSS Password Complexity Code document and paste it into your
psql
prompt, then press Enter. This will install thepci_password_check_rules
extension at version0.1
, immediately applying robust PostgreSQL password policy enforcement for enhanced PostgreSQL password security.If you prefer to run it from a file: Save the code to a.sql
file (e.g.,install_pci_password_rules_v0.1.sql
) and then run:psql -d heydbamaint -U postgres -f install_pci_password_rules_v0.1.sql
Version 0.2: The Evolution – Role-Based Policies
Now, let’s level up! PCI DSS v4.0.1 (especially 8.6.3) distinguishes between human and non-human accounts, often requiring even stronger policies for the latter. This version 0.2
update introduces role-based password policies, allowing you to define different rules for different user groups. It’s like giving your Jedi Masters a different, more powerful lightsaber than your standard troopers.
This update path handles:
- Role Verification/Creation: Ensures the necessary group roles (
pci_admin_users
,pci_app_users
,pci_standard_users
) exist, creating them if they don’t. - Profile Table: Introduces
password_check.profiles
to store distinct password rules for each role. - Intelligent Hook: Modifies the
passcheck_hook
to dynamically fetch and apply rules based on the user’s role membership. - New User Handling: Allows new users to be created with a default policy, but then requires them to be assigned to a PCI role before their password can be changed. This is an initial and crucial logic that will be used later to enforce “Mandate a Password Change After First Login” (PCI DSS 8.3.5) on Part 8.
The full SQL code for this update path is available in a separate document: pci_password_check_rules_0.2_up_0.1-0.2.sql – Role-Based Password Policies Update Code.
SELECT
PGTLE.INSTALL_UPDATE_PATH (
'pci_password_check_rules', -- Name of your custom pg_tle extension
'0.1',
'0.2', -- Incremented version to add role separation for non-human account handling
$_pgtle_$
--Check if the ROLES exists. It's a requirement that the roles exists in order to proceed.
--pci_admin_users,pci_app_users,pci_standard_users
do
$$
declare
v_arr_rolnames text ARRAY;
v_rolname text;
begin
v_arr_rolnames := '{"pci_admin_users","pci_app_users","pci_standard_users"}';
FOREACH v_rolname IN ARRAY v_arr_rolnames
LOOP
if not exists (SELECT 1 FROM pg_catalog.pg_roles r WHERE r.rolname = v_rolname) then
RAISE NOTICE 'Role % does not exist, creating it.', v_rolname;
EXECUTE format($e$
CREATE ROLE %1$s
$e$, v_rolname);
end if;
END LOOP;
end
$$
;
--Create the table to hold the profiles.
--The table must check if the group roles already exists (create a trigger).
--Or maybe the table must not allow any update to its values, and make it fixed.
CREATE TABLE IF NOT EXISTS password_check.profiles (
role TEXT PRIMARY KEY,
min_length INTEGER DEFAULT 15 NOT NULL ,
require_special_char BOOLEAN DEFAULT true NOT NULL,
require_uppercase BOOLEAN DEFAULT true NOT NULL,
require_lowercase BOOLEAN DEFAULT true NOT NULL,
require_digit BOOLEAN DEFAULT true NOT NULL
);
INSERT INTO password_check.profiles (role,min_length) values ('pci_admin_users',15) ON CONFLICT (role) DO NOTHING;
INSERT INTO password_check.profiles (role,min_length) values ('pci_app_users',15) ON CONFLICT (role) DO NOTHING;
INSERT INTO password_check.profiles (role,min_length) values ('pci_standard_users',12) ON CONFLICT (role) DO NOTHING;
INSERT INTO password_check.profiles (role,min_length) values ('pci_new_users',12) ON CONFLICT (role) DO NOTHING;
-- Helper function to check if a given username is a member of a specified PostgreSQL role (group).
CREATE OR REPLACE FUNCTION password_check.is_member_of_role(
target_username TEXT,
group_role_name TEXT
) RETURNS BOOLEAN AS $$
DECLARE
is_member BOOLEAN := FALSE;
BEGIN
SELECT EXISTS (
SELECT 1
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
WHERE r_member.rolname = target_username
AND r_role.rolname = group_role_name
) INTO is_member;
RETURN is_member;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER; --Executed with the privileges of the user that owns the function.
-- The main passcheck hook function that enforces password policies.
CREATE OR REPLACE FUNCTION password_check.passcheck_hook(
username TEXT,
password TEXT,
password_type pgtle.password_types,
valid_until TIMESTAMPTZ,
valid_null BOOLEAN
) RETURNS VOID AS $_FUNCTION_$ -- ADDED THIS BLOCK LABEL
DECLARE
invalid_pw_reason TEXT := '';
current_min_length INTEGER := 12;
current_require_special_char BOOLEAN := TRUE;
current_require_uppercase BOOLEAN := TRUE;
current_require_lowercase BOOLEAN := TRUE;
current_require_digit BOOLEAN := TRUE;
-- Add on 0.2: Flag to check if the user already exists in pg_roles
-- This helps differentiate between CREATE ROLE and ALTER ROLE.
user_exists BOOLEAN;
BEGIN
-- Check if the user already exists in pg_roles.
-- This helps differentiate between CREATE ROLE and ALTER ROLE.
SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = passcheck_hook.username)
INTO user_exists;
--- 1. Determine Role-Based Policies ---
--- For CREATE ROLE, we allow a default policy. For ALTER ROLE, we enforce role membership.
IF user_exists AND password_check.is_member_of_role(username, 'pci_admin_users') THEN
SELECT
min_length,
require_special_char,
require_uppercase,
require_lowercase,
require_digit
INTO
current_min_length,
current_require_special_char,
current_require_uppercase,
current_require_lowercase,
current_require_digit
FROM
password_check.profiles
WHERE
role='pci_admin_users';
ELSIF user_exists AND password_check.is_member_of_role(username, 'pci_app_users') THEN -- NOW FOR NON-HUMAN APP ACCOUNTS
SELECT
min_length,
require_special_char,
require_uppercase,
require_lowercase,
require_digit
INTO
current_min_length,
current_require_special_char,
current_require_uppercase,
current_require_lowercase,
current_require_digit
FROM
password_check.profiles
WHERE
role='pci_app_users';
ELSIF user_exists AND password_check.is_member_of_role(username, 'pci_standard_users') THEN
SELECT
min_length,
require_special_char,
require_uppercase,
require_lowercase,
require_digit
INTO
current_min_length,
current_require_special_char,
current_require_uppercase,
current_require_lowercase,
current_require_digit
FROM
password_check.profiles
WHERE
role='pci_standard_users';
--If the user does not exists (It's a CREATE ROLE), allow it to be created and set the default password rules.
--Later, the user will not be allowed to CHANGE THE PASSWORD if not set to any of the PCI roles.
ELSIF NOT user_exists THEN
SELECT
min_length,
require_special_char,
require_uppercase,
require_lowercase,
require_digit
INTO
current_min_length,
current_require_special_char,
current_require_uppercase,
current_require_lowercase,
current_require_digit
FROM
password_check.profiles
WHERE
role='pci_new_users';
RAISE NOTICE 'Policy: Default for NEW user (CREATE ROLE)';
RAISE NOTICE 'Assign a PCI ROLE to the user IMMEDIATELY';
ELSE
-- If the user exists but does not belong to any defined PCI role, prevent password change.
RAISE EXCEPTION 'Password change not allowed for user %: User must be assigned to one of the defined roles (pci_admin_users, pci_app_users, pci_standard_users).', username;
END IF;
--- 2. Apply Password Complexity Checks (PCI DSS 8.3.6) ---
IF length(password) < current_min_length THEN
invalid_pw_reason := invalid_pw_reason || 'Password must be at least ' || current_min_length || ' characters long. ';
END IF;
IF current_require_uppercase AND password !~ '[A-Z]' THEN
invalid_pw_reason := invalid_pw_reason || 'Password must contain at least one uppercase letter. ';
END IF;
IF current_require_lowercase AND password !~ '[a-z]' THEN
invalid_pw_reason := invalid_pw_reason || 'Password must contain at least one lowercase letter. ';
END IF;
IF current_require_digit AND password !~ '[0-9]' THEN
invalid_pw_reason := invalid_pw_reason || 'Password must contain at least one number. ';
END IF;
IF current_require_special_char AND password !~ '[^a-zA-Z0-9\s]' THEN
invalid_pw_reason := invalid_pw_reason || 'Password must contain at least one special character. ';
END IF;
--- 3. Final Check and Raise Exception / Update History ---
IF invalid_pw_reason != '' THEN
RAISE EXCEPTION 'Password validation failed for user %: %', username, invalid_pw_reason;
END IF;
END;
$_FUNCTION_$ LANGUAGE plpgsql;
REVOKE ALL ON FUNCTION password_check.passcheck_hook(TEXT, TEXT, pgtle.password_types, TIMESTAMPTZ, BOOLEAN) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION password_check.passcheck_hook(TEXT, TEXT, pgtle.password_types, TIMESTAMPTZ, BOOLEAN) TO PUBLIC;
SELECT pgtle.register_feature_if_not_exists('password_check.passcheck_hook', 'passcheck');
$_pgtle_$
);
Logic Explained: pci_password_check_rules_0.2_up_0.1-0.2.sql
This update script builds upon Version 0.1 by introducing a more granular, role-based approach to password policies.
do $$ declare ... end $$ ;
This anonymous PL/pgSQL block is executed once during the update.
- It defines an array
v_arr_rolnames
containing the names of the required PCI roles:pci_admin_users
,pci_app_users
, andpci_standard_users
. - It then iterates through this array. For each role, it checks if the role already exists in
pg_catalog.pg_roles
. If a role does not exist, it usesEXECUTE format('CREATE ROLE %1$I', v_rolname)
to dynamically create that role. This ensures that the necessary group roles are present in your PostgreSQL instance before they are referenced.
- It defines an array
CREATE TABLE IF NOT EXISTS password_check.profiles (...)
This creates a new table,
password_check.profiles
, within ourpassword_check
schema. This table is the heart of our role-based policy system.- It has a
role
column (TEXT PRIMARY KEY) to store the PostgreSQL role name. - It includes columns like
min_length
,require_special_char
,require_uppercase
,require_lowercase
, andrequire_digit
(all BOOLEAN or INTEGER with NOT NULL constraints). These columns will store the specific password requirements for each defined role.
- It has a
INSERT INTO password_check.profiles (...) ON CONFLICT (role) DO NOTHING;
These
INSERT
statements populate thepassword_check.profiles
table with default password policies for each of our defined roles (pci_admin_users
,pci_app_users
,pci_standard_users
) and a specialpci_new_users
profile.ON CONFLICT (role) DO NOTHING
is important: it prevents errors if you run the script multiple times and the rows already exist, ensuring idempotency.- Notice the different
min_length
values (e.g., 15 for admin/app users, 12 for standard/new users), reflecting PCI DSS best practices for different account types.
CREATE OR REPLACE FUNCTION password_check.is_member_of_role(...)
This is a new helper function.
- It takes a
target_username
and agroup_role_name
as input. - It queries
pg_catalog.pg_roles
andpg_catalog.pg_auth_members
(which stores role memberships) to determine if thetarget_username
is a member of thegroup_role_name
. - It is defined as
SECURITY DEFINER
, meaning it runs with the privileges of the user who created it (typically a superuser). This is necessary because queryingpg_catalog.pg_roles
requires elevated privileges.
- It takes a
CREATE OR REPLACE FUNCTION password_check.passcheck_hook(...)
(Updated)
The core
passcheck_hook
function is significantly modified.- It first determines
user_exists
by checkingpg_catalog.pg_roles
. This helps differentiate betweenCREATE ROLE
(whereuser_exists
is false) andALTER ROLE
(whereuser_exists
is true). - It then uses a series of
IF...ELSIF
statements, along with the newpassword_check.is_member_of_role
function, to determine which password profile (frompassword_check.profiles
) applies to the currentusername
. - If the user is a member of
pci_admin_users
,pci_app_users
, orpci_standard_users
, it fetches the specific policy parameters (min_length
,require_special_char
, etc.) from thepassword_check.profiles
table for that role. - If
NOT user_exists
, it applies thepci_new_users
policy, and aRAISE NOTICE
reminds the administrator to assign a proper PCI role. - Crucially, if
user_exists
is true but the user does not belong to any of the predefined PCI roles, itRAISE EXCEPTION
s, preventing the password change until the user is correctly assigned. This enforces proper role management. - The subsequent password complexity checks (length, character types) then use the dynamically fetched
current_min_length
andcurrent_require_...
variables, making the policy adaptable per role.
- It first determines
SELECT pgtle.register_feature_if_not_exists(...)
This re-registers the updated
passcheck_hook
function withpgtle
, ensuring the new logic is active.
To update your extension to Version 0.2:
- Ensure you have already installed Version 0.1.
- Connect to your heydbamaint database as a superuser.
- Execute the entire content of pci_password_check_rules_0.2_up_0.1-0.2.sql – Role-Based Password Policies Update Code.
- Then, run the
ALTER EXTENSION
command:ALTER EXTENSION pci_password_check_rules UPDATE TO '0.2';
Verification: Did It Work, Captain?
After installing your pgtle
extension, you can verify its presence and version information using these handy commands. It’s like checking the diagnostics on your starship’s new warp core!
Check available extensions using:
SELECT * FROM pgtle.available_extensions();
Check all available versions for your extension using:
SELECT * FROM pgtle.available_extension_versions();
You should see pci_password_check_rules
listed with 0.2
as its default_version
. If you encounter any errors during installation, double-check your pgtle
setup ritual (especially the pgtle.passcheck_db_name
setting and the pg_reload_conf()
!).
Check the update paths defined for your specific extension:
SELECT * FROM pgtle.extension_update_paths('pci_password_check_rules');
Testing Your New Fortress!
Now for the fun part: testing! Try creating users and changing passwords to see your new password validation rules in action. This will demonstrate your enhanced PostgreSQL password security and adherence to PCI DSS compliance.
Example 1: Creating a pci_standard_users
member with a weak password (should fail):
Try creating a user with a weak password, like:
CREATE ROLE test_standard_user WITH PASSWORD 'short';
Expected error: Password validation failed for user test_standard_user: Password must be at least 12 characters long.
Or with insufficient complexity:
CREATE ROLE another_standard_user WITH PASSWORD 'nopassword';
Expected error: Password validation failed for user another_standard_user: Password must contain at least one uppercase letter. Password must contain at least one number. Password must contain at least one special character.
Example 2: Creating a pci_admin_users
member with a strong password (should succeed):
CREATE ROLE test_admin_user WITH PASSWORD 'P@ssw0rdF0rAdm1n!'; -- Should succeed (17 chars, complex)
GRANT pci_admin_users TO test_admin_user; -- Assign to the role
This should succeed (17 chars, complex), demonstrating effective PostgreSQL password policy for administrators.
Example 3: Creating a pci_app_users
member with a strong password (should succeed, demonstrating application accounts security):
CREATE ROLE test_app_service WITH PASSWORD 'AppS3rv!ce_P@ssw0rd_2025!'; -- Should succeed (26 chars, complex)
GRANT pci_app_users TO test_app_service; -- Assign to the role
This should succeed (26 chars, complex), showcasing robust service accounts security.
Example 4: Trying to change password for a user not assigned to a PCI role (should fail after initial creation):
Create an unassigned user:
CREATE ROLE unassigned_user WITH PASSWORD 'InitialP@ssw0rd1!';
This succeeds initially (uses pci_new_users
profile).
Now try to change its password without assigning it to a PCI role:
ALTER ROLE unassigned_user WITH PASSWORD 'NewP@ssw0rd2!';
Expected error: Password change not allowed for user unassigned_user: User must be assigned to one of the defined PCI roles (pci_admin_users, pci_app_users, pci_standard_users).
Assign it to a role, then try again (should succeed if new password is valid for that role):
GRANT pci_standard_users TO unassigned_user;
ALTER ROLE unassigned_user WITH PASSWORD 'NewP@ssw0rd2!';
This should now succeed if it meets the standard user policy, confirming your dynamic PostgreSQL password policy.
This robust pgtle
extension ensures that your PostgreSQL password security is now directly integrated and enforced at the database level, making your system significantly more compliant with PCI DSS v4.0.1. This is a prime example of PostgreSQL security best practices in action!
The Adventure Continues…
Congratulations, brave DBA! You’ve just deployed a powerful pgtle
extension that enforces PCI DSS password complexity, even differentiating rules based on user roles. This is a huge leap in securing your PostgreSQL fortress and a major win for PostgreSQL password security!
In the next parts of our saga, we’ll continue to build upon this foundation, tackling other crucial PCI DSS password requirements like reusability, change frequency, account lockout, and more. Stay tuned, because the quest for ultimate PostgreSQL security best practices is far from over!