One of the most common questions when working with PostgreSQL-style authentication is:
- “Which
pg_hba.confrule actually allowed this connection?”
If you’ve ever stared at a long HBA file wondering why a connection succeeded (or failed), you’re not alone.
YugabyteDB follows PostgreSQL’s first-match-wins HBA evaluation model, but it doesn’t always log the matched rule line number directly. In this tip, we’ll show how to prove exactly which HBA rule was used, even for different authentication methods, using a small, reusable helper function.
We’ll walk through three real-world authentication patterns:
1.
yugabyte– client certificate required2.
appuser– SCRAM password only (no client cert)3.
certuser– client certificate required (non-superuser)
🧱 Environment Setup (YugabyteDB 2025.2)
🔐 Client Certificates
Before starting YugabyteDB, I pre-created all server and client certificates needed for this demo. This mirrors how certificates are typically handled in real environments: created once, stored centrally, and reused consistently across services and users.
Here’s the certificate directory used throughout this post:
[root@localhost ~]# ll /root/yugabyte-certs
total 56
-rw-r--r--. 1 root root 1123 Jan 3 16:38 ca.crt
-rw-------. 1 root root 1704 Jan 3 16:38 ca.key
-rw-r--r--. 1 root root 41 Jan 9 02:19 ca.srl
-rw-r--r--. 1 root root 1155 Jan 9 02:19 certuser-client.crt
-rw-------. 1 root root 1704 Jan 9 02:19 certuser-client.key
-rw-r--r--. 1 root root 1094 Jan 3 16:38 client.crt
-rw-r--r--. 1 root root 887 Jan 3 16:38 client.csr
-rw-------. 1 root root 1704 Jan 3 16:38 client.key
-rw-------. 1 root root 1218 Jan 3 16:38 client.key.pk8
-rw-r--r--. 1 root root 1094 Jan 3 16:38 node.0.0.0.0.crt
-rw-r--r--. 1 root root 891 Jan 3 16:38 node.0.0.0.0.csr
-rw-------. 1 root root 1704 Jan 3 16:38 node.0.0.0.0.key
-rw-r--r--. 1 root root 1155 Jan 9 02:09 yugabyte-client.crt
-rw-------. 1 root root 1704 Jan 9 02:09 yugabyte-client.key
What these certificates represent:
| Certificate | Purpose |
|---|---|
ca.crt / ca.key |
Internal Certificate Authority used to sign all server and client certificates |
node.*.crt / node.*.key |
TLS identity used by YugabyteDB TServers |
yugabyte-client.crt / yugabyte-client.key |
Client certificate for the privileged yugabyte user |
certuser-client.crt / certuser-client.key |
Client certificate for an application user that requires cert-based access |
client.crt / client.key |
Generic client certificate (often used for testing or bootstrapping) |
Each client certificate was created with a distinct Common Name (CN):
●
CN=yugabyte●
CN=certuser
With certificates in place, let’s start YugabyteDB and configure HBA rules that mix client certs and SCRAM authentication.
Start yb-master
/root/yugabyte-2025.2.0.0/bin/yb-master \
--master_addresses=127.0.0.1:7100 \
--rpc_bind_addresses=127.0.0.1:7100 \
--fs_data_dirs=/root/var/data \
--replication_factor=1 \
&> /root/var/logs/yb-master.out &
Start yb-tserver (TLS + mixed auth)
/root/yugabyte-2025.2.0.0/bin/yb-tserver \
--tserver_master_addrs=127.0.0.1:7100 \
--fs_data_dirs=/root/var/data \
--certs_for_client_dir=/root/yugabyte-certs \
--enable_ysql=true \
--ysql_enable_auth=true \
--use_client_to_server_encryption=true \
--ysql_pg_conf_csv="ssl=on,password_encryption=scram-sha-256,log_connections=on" \
--ysql_hba_conf_csv="\
local all yugabyte ident,\
hostssl all yugabyte 127.0.0.1/32 trust clientcert=verify-ca,\
hostssl all certuser 127.0.0.1/32 trust clientcert=verify-ca,\
hostssl all yugabyte all reject,\
hostnossl all all all reject,\
hostssl all +dbaaccounts all scram-sha-256,\
hostssl all all all scram-sha-256" \
&> /root/var/logs/yb-tserver.out &
🧪 Demo Database and Roles
All of the following setup is performed while connected as the yugabyte user, which is the PostgreSQL-compatible superuser in YugabyteDB.
First, connect as yugabyte using the client certificate created earlier:
/root/yugabyte-2025.2.0.0/bin/ysqlsh \
"host=127.0.0.1 port=5433 user=yugabyte sslmode=verify-ca \
sslcert=/root/yugabyte-certs/yugabyte-client.crt \
sslkey=/root/yugabyte-certs/yugabyte-client.key \
sslrootcert=/root/yugabyte-certs/ca.crt"
Once connected, create the demo database and roles:
-- Demo database used throughout this post
CREATE DATABASE hba_demo_db;
-- Role used to demonstrate role-based HBA matching
CREATE ROLE dbaaccounts;
-- Application user using SCRAM (no client cert required)
CREATE USER appuser WITH PASSWORD 'password123';
GRANT dbaaccounts TO appuser;
-- Application-style user that requires a client certificate
CREATE USER certuser;
At this point, we have three distinct authentication patterns available:
●
yugabyte– privileged admin user (client cert required)●
appuser– SCRAM password authentication (no client cert)●
certuser– client certificate required by HBA
These users will be used in the following sections to demonstrate exactly which pg_hba.conf rule allows each connection, and why.
Oh, since we’re already connected as the yugabyte superuser, let’s take a quick look at the parsed HBA rules as YugabyteDB sees them:
SELECT * FROM pg_hba_file_rules ORDER BY line_number;
Example:
yugabyte=# SELECT * FROM pg_hba_file_rules ORDER BY line_number;
line_number | type | database | user_name | address | netmask | auth_method | options | error
-------------+-----------+----------+----------------+-----------+-----------------+---------------+------------------------+-------
4 | local | {all} | {yugabyte} | | | peer | |
5 | hostssl | {all} | {yugabyte} | 127.0.0.1 | 255.255.255.255 | trust | {clientcert=verify-ca} |
6 | hostssl | {all} | {certuser} | 127.0.0.1 | 255.255.255.255 | trust | {clientcert=verify-ca} |
7 | hostssl | {all} | {yugabyte} | all | | reject | |
8 | hostnossl | {all} | {all} | all | | reject | |
9 | hostssl | {all} | {+dbaaccounts} | all | | scram-sha-256 | |
10 | hostssl | {all} | {all} | all | | scram-sha-256 | {clientcert=verify-ca} |
11 | hostssl | {all} | {all} | all | | md5 | |
12 | local | {all} | {yugabyte} | | | trust | |
(9 rows)
🛠 Helper Function: util.show_hba_match()
This function reproduces HBA evaluation logic for the current session and returns only the single matched rule.
Key design points:
● Uses
SECURITY DEFINERto readpg_hba_file_rules● Uses
session_user(notcurrent_user)● Safely exposes why the connection succeeded
CREATE SCHEMA IF NOT EXISTS util;
CREATE OR REPLACE FUNCTION util.show_hba_match()
RETURNS TABLE (
client_ip text,
dbname text,
username text,
application_name text,
ssl boolean,
clientdn text,
matched_line int,
matched_type text,
matched_db text,
matched_user text,
matched_address text,
matched_method text,
matched_options text
)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = pg_catalog
AS $$
DECLARE
v_client_inet inet := inet_client_addr();
v_client_ip text := COALESCE(host(inet_client_addr()), 'local');
v_username text := session_user;
v_dbname text := current_database();
v_appname text := COALESCE(current_setting('application_name', true), '');
v_ssl boolean := COALESCE(
(SELECT s.ssl
FROM pg_stat_ssl s
WHERE s.pid = pg_backend_pid()),
false
);
v_clientdn text := '';
BEGIN
-- pg_stat_ssl column name differs by version: client_dn vs clientdn
IF EXISTS (
SELECT 1
FROM pg_attribute
WHERE attrelid = 'pg_stat_ssl'::regclass
AND attname = 'client_dn'
) THEN
EXECUTE 'SELECT COALESCE(s.client_dn::text, '''')
FROM pg_stat_ssl s
WHERE s.pid = pg_backend_pid()'
INTO v_clientdn;
ELSE
EXECUTE 'SELECT COALESCE(s.clientdn::text, '''')
FROM pg_stat_ssl s
WHERE s.pid = pg_backend_pid()'
INTO v_clientdn;
END IF;
RETURN QUERY
WITH me AS (
SELECT
v_client_inet AS client_inet,
v_client_ip AS client_ip,
v_username AS username,
v_dbname AS dbname,
v_appname AS application_name,
v_ssl AS ssl,
v_clientdn AS clientdn
),
candidates AS (
SELECT r.*
FROM pg_hba_file_rules r, me
WHERE
-- SSL vs non-SSL rule types
((me.ssl AND r.type = 'hostssl') OR ((NOT me.ssl) AND r.type = 'hostnossl'))
-- Database match
AND ('all' = ANY(r.database) OR me.dbname = ANY(r.database))
-- User match: all | exact | +role
AND (
'all' = ANY(r.user_name)
OR me.username = ANY(r.user_name)
OR EXISTS (
SELECT 1
FROM unnest(r.user_name) u(entry)
WHERE left(entry, 1) = '+'
AND pg_has_role(me.username, substr(entry, 2), 'member')
)
)
-- Address match: 'all' OR client_inet within (address/netmask)
AND (
r.address IS NULL
OR r.address = 'all'
OR (
me.client_inet IS NOT NULL
AND me.client_inet <<= (r.address || '/' || masklen(r.netmask::inet))::cidr
)
)
ORDER BY r.line_number
LIMIT 1
)
SELECT
me.client_ip,
me.dbname,
me.username,
me.application_name,
me.ssl,
NULLIF(me.clientdn, '') AS clientdn,
c.line_number,
c.type,
array_to_string(c.database, ',') AS matched_db,
array_to_string(c.user_name, ',') AS matched_user,
CASE
WHEN c.address IS NULL THEN ''
WHEN c.address = 'all' THEN 'all'
ELSE c.address || '/' || masklen(c.netmask::inet)
END AS matched_address,
c.auth_method,
COALESCE(array_to_string(c.options, ','), '') AS matched_options
FROM me
LEFT JOIN candidates c ON true;
END;
$$;
GRANT USAGE ON SCHEMA util TO PUBLIC;
GRANT EXECUTE ON FUNCTION util.show_hba_match() TO PUBLIC;
🔐 Demo 1: yugabyte (Client Certificate Required)
[root@localhost ~]# ysqlsh \
"host=127.0.0.1 port=5433 user=yugabyte sslmode=verify-ca \
sslcert=/root/yugabyte-certs/yugabyte-client.crt \
sslkey=/root/yugabyte-certs/yugabyte-client.key \
sslrootcert=/root/yugabyte-certs/ca.crt" \
-c "SELECT * FROM util.show_hba_match();"
client_ip | dbname | username | application_name | ssl | clientdn | matched_line | matched_type | matched_db | matched_user | matched_address | matched_method | matched_options
-----------+----------+----------+------------------+-----+--------------+--------------+--------------+------------+--------------+-----------------+----------------+----------------------
127.0.0.1 | yugabyte | yugabyte | ysqlsh | t | /CN=yugabyte | 5 | hostssl | all | yugabyte | 127.0.0.1/32 | trust | clientcert=verify-ca
(1 row)
Matched HBA line: 5
Auth: trust + clientcert=verify-ca
🔑 Demo 2: appuser (SCRAM, No Cert)
[root@localhost ~]# PGPASSWORD='password123' ysqlsh \
"host=127.0.0.1 port=5433 dbname=hba_demo_db user=appuser" \
-c "SELECT * FROM util.show_hba_match();"
client_ip | dbname | username | application_name | ssl | clientdn | matched_line | matched_type | matched_db | matched_user | matched_address | matched_method | matched_options
-----------+-------------+----------+------------------+-----+----------+--------------+--------------+------------+--------------+-----------------+----------------+-----------------
127.0.0.1 | hba_demo_db | appuser | ysqlsh | t | | 9 | hostssl | all | +dbaaccounts | all | scram-sha-256 |
(1 row)
Matched HBA line: 9
Auth: SCRAM-SHA-256
🔐 Demo 3: certuser (Client Cert Required)
Successful connection
[root@localhost ~]# ysqlsh \
"host=127.0.0.1 port=5433 dbname=hba_demo_db user=certuser sslmode=verify-ca \
sslcert=/root/yugabyte-certs/certuser-client.crt \
sslkey=/root/yugabyte-certs/certuser-client.key \
sslrootcert=/root/yugabyte-certs/ca.crt" \
-c "SELECT * FROM util.show_hba_match();"
client_ip | dbname | username | application_name | ssl | clientdn | matched_line | matched_type | matched_db | matched_user | matched_address | matched_method | matched_options
-----------+-------------+----------+------------------+-----+--------------+--------------+--------------+------------+--------------+-----------------+----------------+----------------------
127.0.0.1 | hba_demo_db | certuser | ysqlsh | t | /CN=certuser | 6 | hostssl | all | certuser | 127.0.0.1/32 | trust | clientcert=verify-ca
(1 row)
Matched HBA line: 6
Auth: trust + clientcert=verify-ca
Failure without certificate
[root@localhost ~]# ysqlsh \
"host=127.0.0.1 port=5433 dbname=hba_demo_db user=certuser sslmode=verify-ca \
sslrootcert=/root/yugabyte-certs/ca.crt" \
-c "SELECT 1;"
ysqlsh: error: connection to server at "127.0.0.1", port 5433 failed: FATAL: connection requires a valid client certificate
📌 Key Takeaways
● YugabyteDB uses PostgreSQL’s first-match-wins HBA model
● Client certificates are optional and rule-specific
● SCRAM and cert-based auth can coexist on the same port
● Line numbers may shift as rules change — always verify live behavior
● A small helper function can make HBA behavior explainable and provable
🎯 Summary
| User | Authentication | Client Cert | HBA Line |
|---|---|---|---|
yugabyte |
trust + cert | ✅ required | 5 |
certuser |
trust + cert | ✅ required | 6 |
appuser |
SCRAM | ❌ not required | 9 |
Have Fun!
So fun to watch our daughter’s dog, Maple, roll on her back and wrestle her new Christmas toy 😄🐶
