Skip to content

Commit 1fa4ba4

Browse files
authored
Add pg_hba checksum metric (#4169)
* Add pg_hba checksum metric We want OTel metrics to be as close to parity with the postgres_exporter solution as possible; so this PR adds the initial setup and metric query to get 'ccp_pg_hba_checksum', as well as the util function to mark a pg_hba setting as valid. Note: when adding some comments, some changes were introduced to these functions as they exist in pgMonitor, but the behavior is equivalent. Issues: [PGO-2395]
1 parent 70a0682 commit 1fa4ba4

4 files changed

Lines changed: 176 additions & 4 deletions

File tree

internal/collector/generated/postgres_5m_metrics.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/collector/postgres_5m_metrics.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,15 @@
140140
attribute_columns: ["dbname"]
141141
static_attributes:
142142
server: "localhost:5432"
143-
143+
144+
- sql: SELECT monitor.pg_hba_checksum() AS status;
145+
metrics:
146+
- metric_name: ccp_pg_hba_checksum
147+
value_column: status
148+
description: |
149+
Value of checksum monitoring status for pg_catalog.pg_hba_file_rules (pg_hba.conf).
150+
0 = valid config. 1 = settings changed.
151+
Settings history is available for review in the table `monitor.pg_hba_checksum`.
152+
To reset current config to valid after alert, run monitor.pg_hba_checksum_set_valid(). Note this will clear the history table.
153+
static_attributes:
154+
server: "localhost:5432"

internal/collector/postgres_metrics_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ func TestRemoveMetricsFromQueries(t *testing.T) {
1717
err := json.Unmarshal(fiveMinuteMetrics, &fiveMinuteMetricsArr)
1818
assert.NilError(t, err)
1919

20-
assert.Equal(t, len(fiveMinuteMetricsArr), 3)
20+
assert.Equal(t, len(fiveMinuteMetricsArr), 4)
2121
newArr := removeMetricsFromQueries([]string{"ccp_database_size_bytes"}, fiveMinuteMetricsArr)
22-
assert.Equal(t, len(newArr), 2)
22+
assert.Equal(t, len(newArr), 3)
2323

2424
t.Run("DeleteOneMetric", func(t *testing.T) {
2525
sqlMetricsData := `[

internal/controller/postgrescluster/metrics_setup.sql

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,164 @@ BEGIN
220220
END;
221221
$$ LANGUAGE plpgsql;
222222

223+
/*
224+
* The `pg_hba_checksum` table, functions, and view are taken from
225+
* https://github.com/CrunchyData/pgmonitor/blob/development/postgres_exporter/common
226+
*
227+
* The goal of these table, functions, and view is to monitor changes
228+
* to the pg_hba_file_rules system catalog.
229+
*
230+
* This material is used in the metric `ccp_pg_hba_checksum`.
231+
*/
232+
233+
/*
234+
* `monitor.pg_hba_checksum` table is used to store
235+
* - the pg_hba settings as string (for reference)
236+
* - the pg_hba settings as hash (for quick comparison)
237+
* - the `hba_hash_known_provided` (for overide hash manually given to the `monitor.pg_hba_checksum` function)
238+
* - the `valid` field to signal whether the pg_hba settings have not changed since they were accepted as valid
239+
*
240+
* We create an index on `created_at` in order to pull the most recent entry for
241+
* comparison in the `monitor.pg_hba_checksum` function
242+
*/
243+
DROP TABLE IF EXISTS monitor.pg_hba_checksum;
244+
CREATE TABLE monitor.pg_hba_checksum (
245+
hba_hash_generated text NOT NULL
246+
, hba_hash_known_provided text
247+
, hba_string text NOT NULL
248+
, created_at timestamptz DEFAULT now() NOT NULL
249+
, valid smallint NOT NULL );
250+
COMMENT ON COLUMN monitor.pg_hba_checksum.valid IS 'Set this column to zero if this group of settings is a valid change';
251+
CREATE INDEX ON monitor.pg_hba_checksum (created_at);
252+
253+
/*
254+
* `monitor.pg_hba_checksum(text)` is used to compare the previous pg_hba hash
255+
* with a hash made of the current pg_hba hash, derived from the `monitor.pg_hba_hash` view below.
256+
*
257+
* This function returns
258+
* - 0, indicating NO settings have changed
259+
* - 1, indicating something has changed since last known valid state
260+
*
261+
* `monitor.pg_hba_checksum` can take a hash to be used as an override.
262+
* This may be useful when you have a standby with different pg_hba rules;
263+
* since it will have different rules (and therefore a different hash), you
264+
* could alter the metric function to pass the actual hash, which would be
265+
* used in lieu of this table's value (derived from the primary cluster's rules).
266+
*/
267+
DROP FUNCTION IF EXISTS monitor.pg_hba_checksum(text);
268+
CREATE FUNCTION monitor.pg_hba_checksum(p_known_hba_hash text DEFAULT NULL)
269+
RETURNS smallint
270+
LANGUAGE plpgsql SECURITY DEFINER
271+
SET search_path TO pg_catalog, pg_temp
272+
AS $function$
273+
DECLARE
274+
275+
v_hba_hash text;
276+
v_hba_hash_old text;
277+
v_hba_string text;
278+
v_is_in_recovery boolean;
279+
v_valid smallint;
280+
281+
BEGIN
282+
283+
-- Retrieve the current settings from the `monitor.pg_hba_hash` view below
284+
IF current_setting('server_version_num')::int >= 100000 THEN
285+
SELECT sha256_hash, hba_string
286+
INTO v_hba_hash, v_hba_string
287+
FROM monitor.pg_hba_hash;
288+
ELSE
289+
RAISE EXCEPTION 'pg_hba change monitoring unsupported in versions older than PostgreSQL 10';
290+
END IF;
291+
292+
-- Retrieve the last previous hash from the table
293+
SELECT hba_hash_generated, valid
294+
INTO v_hba_hash_old, v_valid
295+
FROM monitor.pg_hba_checksum
296+
ORDER BY created_at DESC LIMIT 1;
297+
298+
-- If an manual/override hash has been given, we will use that:
299+
-- Do not base validity on the stored value if manual hash is given.
300+
IF p_known_hba_hash IS NOT NULL THEN
301+
v_hba_hash_old := p_known_hba_hash;
302+
v_valid := 0;
303+
END IF;
304+
305+
/* If the table is not empty or a manual hash was given,
306+
* then we want to compare the old hash (from the table)
307+
* with the new hash: if those differ, then we set the validity to 1;
308+
* if they are the same, then we honor what the validity was
309+
* in the table (which would be 1).
310+
*/
311+
IF (v_hba_hash_old IS NOT NULL) THEN
312+
IF (v_hba_hash != v_hba_hash_old) THEN
313+
v_valid := 1;
314+
END IF;
315+
ELSE
316+
v_valid := 0;
317+
END IF;
318+
319+
/*
320+
* We only want to insert into the table if we're on a primary and
321+
* - the table/manually entered hash is empty, e.g., we've just started the cluster; or
322+
* - the hashes don't match
323+
*
324+
* There's no value added by inserting into the table when no change was detected.
325+
*/
326+
IF (v_hba_hash_old IS NULL) OR (v_hba_hash != v_hba_hash_old) THEN
327+
SELECT pg_is_in_recovery() INTO v_is_in_recovery;
328+
IF v_is_in_recovery = false THEN
329+
INSERT INTO monitor.pg_hba_checksum (
330+
hba_hash_generated
331+
, hba_hash_known_provided
332+
, hba_string
333+
, valid)
334+
VALUES (
335+
v_hba_hash
336+
, p_known_hba_hash
337+
, v_hba_string
338+
, v_valid);
339+
END IF;
340+
END IF;
341+
342+
RETURN v_valid;
343+
344+
END
345+
$function$;
346+
347+
/*
348+
* The `monitor.pg_hba_hash` view return both a hash and a string aggregate of the
349+
* pg_catalog.pg_hba_file_rules.
350+
* Note: We use `sha256` to hash to allow this to run on FIPS environments.
351+
*/
352+
DROP VIEW IF EXISTS monitor.pg_hba_hash;
353+
CREATE VIEW monitor.pg_hba_hash AS
354+
-- Order by line number so it's caught if no content is changed but the order of entries is changed
355+
WITH hba_ordered_list AS (
356+
SELECT COALESCE(type, '<<NULL>>') AS type
357+
, array_to_string(COALESCE(database, ARRAY['<<NULL>>']), ',') AS database
358+
, array_to_string(COALESCE(user_name, ARRAY['<<NULL>>']), ',') AS user_name
359+
, COALESCE(address, '<<NULL>>') AS address
360+
, COALESCE(netmask, '<<NULL>>') AS netmask
361+
, COALESCE(auth_method, '<<NULL>>') AS auth_method
362+
, array_to_string(COALESCE(options, ARRAY['<<NULL>>']), ',') AS options
363+
FROM pg_catalog.pg_hba_file_rules
364+
ORDER BY line_number)
365+
SELECT sha256((string_agg(type||database||user_name||address||netmask||auth_method||options, ','))::bytea) AS sha256_hash
366+
, string_agg(type||database||user_name||address||netmask||auth_method||options, ',') AS hba_string
367+
FROM hba_ordered_list;
368+
369+
/*
370+
* The `monitor.pg_hba_checksum_set_valid` function provides an interface for resetting the
371+
* checksum monitor.
372+
* Note: configuration history will be cleared.
373+
*/
374+
DROP FUNCTION IF EXISTS monitor.pg_hba_checksum_set_valid();
375+
CREATE FUNCTION monitor.pg_hba_checksum_set_valid() RETURNS smallint
376+
LANGUAGE sql
377+
AS $function$
378+
379+
TRUNCATE monitor.pg_hba_checksum;
380+
381+
SELECT monitor.pg_hba_checksum();
382+
383+
$function$;

0 commit comments

Comments
 (0)