Skip to content

The Access Model

Read this before granting anyone permission to register checks, thresholds, or events. The trust model behind this framework is simple to state but easy to get wrong in practice, so it's worth understanding before you hand out any permissions at all.

The core trust issue

Whoever can register a check, a threshold, or an event is registering a PostgreSQL function that will later run as whoever actually calls the collection or evaluation engine — often a scheduled job with broad privileges. The collection and evaluation engines are deliberately not given elevated privileges of their own, specifically so that a check, evaluator, or event function runs with the caller's own privileges, not an inflated one.

But PostgreSQL grants permission to run a newly created function to everyone by default, and nothing forces the author of a check, evaluator, or event function to lock that down themselves. In practice, this means a role that's only supposed to be trusted to author checks can register a function that anyone — including whatever elevated identity runs your scheduled health-check job — can directly call.

The practical rule: treat "can author a check, threshold, or event" and "can run checks and evaluations" as one single trust tier, not two. Grant permission to register these things only to roles you'd equally trust to run the collection and evaluation engines themselves. Also grant these permissions one function at a time, rather than in bulk across the whole pghf schema — a blanket grant would also cover this framework's own internal helper functions, which are deliberately left unprotected against certain internal safety checks since their only intended caller is the framework's own seeding function.

Writes to the catalog

Every catalog table — namespaces, categories, checks, suites, suite membership, thresholds, and events — rejects direct inserts, updates, and deletes from ordinary users. They can only be changed through their own dedicated registration functions, which validate everything (that a result type is one of the recognised values, that a registered function actually returns the right shape, formatting rules for namespace and category codes, and more) before writing anything.

There is no separate "retire" or "delete" verb anywhere in this framework for catalog objects — activating and inactivating something is just one more parameter on the same registration function you'd use to create or update it. Registration functions also refuse to act on a check whose namespace or category has been inactivated — see Inactivating a Namespace or Category — and the built-in PGHF namespace itself can never receive a brand-new row from outside the framework's own seeding function, though existing PGHF checks can still be modified through the ordinary update function.

The collection and evaluation engines are both deliberately not given elevated privileges — see their header comments in the source for why running either one with elevated rights would open a path to privilege escalation, given that both dynamically call whatever function happens to be registered in a table.

Reads split into two tiers

  • Catalog and configuration — namespaces, categories, checks, suites, suite membership, thresholds — stay openly readable by anyone. This is schema and configuration, safe to browse freely.
  • Execution history — runs, raw results, run configuration, and evaluations — is not openly readable. Two different audiences need this data, and each is handled differently:
    • Operators — whoever actually calls the collection or evaluation engine, or the retention-purging functions — need real, direct permission on these tables regardless, since those procedures deliberately run with the caller's own privileges rather than an elevated one. Insert and update permission on the execution-history tables is granted to everyone by default; grant the rest explicitly to whichever role operates this framework in your environment. One consequence worth knowing: because insert permission is open by default, any authenticated role could in principle insert a fabricated run with hand-picked results, without ever actually calling the collection or evaluation engine — nothing distinguishes a forged row from a genuine one. This is a reasonable default on a single-tenant cluster, where being able to log in already implies being trusted to report health status; if that assumption doesn't hold in your environment, tighten this permission yourself.
    • Read-only consumers — a notification pipeline, or a database administrator who only wants the results — go through the reporting functions described in How Threshold Evaluation Works instead. This is the only supported way to read execution history without an operator-level grant on the underlying tables. These functions run with elevated privileges specifically so they work without a direct grant; unlike the collection and evaluation engines, they don't dynamically call anything, so the privilege-escalation concern above doesn't apply to them.

Continue to Reading the Catalog Without Table Access.