Security model
Report vulnerabilities through GitHub private vulnerability reporting (https://github.com/fyannk/pgToolBox/security/advisories/new), as SECURITY.md explains; the results of the latest security run (every scanner, the open findings with whether a fix exists, the scanned image digests and the OpenSSF Scorecard score) are on the security scan results page. This page states what the design guarantees and, as plainly, what it does not.
The design in five rules
- Kubernetes owns identity and authorization. The console decides nothing about who a person is or what they may do; it asks the API server (authentication, authorization).
- No level is ever stored. The session cookie holds the person's sealed token and nothing else. Revocation is the next SelfSubjectAccessReview (views, within 10 seconds) or the API server refusing the token (writes, immediately), plus a revocation list for console sessions.
- Writes are the person's. Every write goes to the API server with the person's own token, so the API server authorizes, admits and audits it under their name. A stale cache can never grant a write.
- No cluster-scoped console, no Secret verb. A console's reach is bounded by the
namespaces its Roles name. Its ServiceAccount cannot read a Secret, cannot write,
and cannot impersonate. The one opt-in exception is the
restore drill
teardown:
delete clustersin the drill namespace, used only on Clusters this console sealed. On Kubernetes 1.30+ an admission policy enforces that on the API server (the delete guard). - Credentials stay where they belong. Object-store credentials stay in the Cluster's namespace inside the evidence agent; database passwords stay in Secrets and reach only the pgAdmin Pod's memory-backed volume.
Who can do what
| Component | Reads | Writes | Cannot |
|---|---|---|---|
| Console ServiceAccount | The CloudNativePG kinds, pods, services, PVCs, events, PDBs, leases, jobs, pgadmins, evidenceagents, objectstores in the watched namespaces; pods/log unless withheld; one ConfigMap by name | Nothing. It asks the API server with SelfSubjectAccessReviews sent with the reader's own token, which need no grant | Read a Secret; use impersonate; read outside its namespaces; write |
| A person through the console | What their own RBAC lets them get | Back up, reload, restart, promote, with their token | Anything the API server denies them |
| Operator | Its watched namespaces' CRs and what they reference. Its cache holds Secret metadata only for user Secrets, but its RBAC grants Secret get, list, watch, create, update, patch and delete: in every namespace with kustomize, OLM and the Helm chart left at its default, and only in the namespaces of operator.config.watchNamespaces when the Helm chart sets it (RoleBindings, which also carry bind on the three console ClusterRoles; cluster-wide it keeps TokenReview, SubjectAccessReview, list on Nodes (to compare an exposure's ipBlocks with their podCIDRs), list on SecurityContextConstraints and the evidence agent's ClusterRole and ClusterRoleBinding, with no bind, escalate or impersonate; in its own namespace, its Lease and get on its pgtoolbox-operator-config ConfigMap and Secret) | The children of PgAdmin and EvidenceAgent; their status | Hold a database password |
| Evidence agent ServiceAccount | The object store: with the operator, through credential files mounted in its own Pod; in team mode, through the Secrets its values list, read with a name-scoped Role | Nothing in the store | With the operator, anything on the API server except creating TokenReviews and SubjectAccessReviews; in team mode, anything but its Cluster, ObjectStore and listed Secrets |
| pgAdmin Pod | Offered Secrets and <cluster>-ca, through a name-scoped Role | Its own volumes | Read any other Secret |
Boundaries and their checks
-
Browser to console: TLS at the edge or in the pod; a sealed
__Host-prefixed cookie (HttpOnly,SameSite=Strict); a custom request header plus a same-origin check on every unsafe method; sign-in rate limit; the server-rendered sign-in pages work without JavaScript and carry a Content-Security-Policy that allows no script and no framing. -
Console to API server: informers as the ServiceAccount; reviews (SelfSubjectReview, SelfSubjectAccessReview) and writes as the person, with their own token.
-
Console to instance pods:
/pg/statusover TLS 1.3 verified against a CA bundle the admin supplies. A Cluster not in the bundle is "not verifiable" and never read; there is no insecure mode and no plaintext read on any CloudNativePG version. -
Console to evidence agent: verified TLS to
status.endpointwithstatus.caBundle(trusted only when the operator wrote them), and a kubelet-rotated token bound to the agent namespace's own audience; the agent checks it with TokenReview and SubjectAccessReview (details). -
Operator to workloads: server-side objects the operator owns; a metadata-only view of Secrets, cached without annotations (so not kubectl's
last-applied-configuration, which holds the data) and with only the labels the operator reads;ResourceNotOwnedinstead of adopting. -
pgAdmin to PostgreSQL:
verify-full, SCRAM with channel binding. -
Metrics: authenticated and authorized by token on both the operator and the console.
-
Client addresses:
X-Forwarded-Foris believed only from the proxies named inTRUSTED_PROXY_CIDRS, else ignored, so a Pod that reaches the console directly cannot escape the per-address sign-in budgets or forgeclient_ip. Without trusted CIDRs behind a declared proxy, or with no proxy declared behind a public URL, every client may have the proxy's address, so no per-address budget is charged and only the per-replica bounds limit sign-ins; the operator renders an ingress NetworkPolicy for a published Route (and for an Ingress or Gateway that names its controller) and only then trusts every admitted peer (details). -
What an unauthenticated flood still costs. The per-replica bounds protect the API server and the identity provider, and are kept apart from what a genuine sign-in needs once it has proved something, so junk cannot starve people below these rates (per replica):
- junk OAuth callbacks: a refused code exchange each, at most 100 a second to the IdP, and no SelfSubjectReview (that is charged only after a successful exchange). Above 100 a second new OAuth sign-ins are refused while the flood lasts;
- junk pasted tokens and unknown MCP bearers: a SelfSubjectReview each that the API server refuses, at most 100 a second. Above that, new token sign-ins and new raw bearers are refused; an MCP client whose token the replica accepted before re-reviews from a bucket of its own;
- junk
/mcp/oauth/tokenrequests, and codes or refresh tokens the console sealed but that no longer buy anything (expired, another client's, a failed PKCE check, a revoked person or grant, a spent code replayed): a form parse and a decryption each; they never reach the bucket connected clients refresh from, so holding a token once is not a way to starve them; past 20 a second they get429and no audit line. A live grant spends a bucket of its own (6 at once, 12 a minute), then its person's (12 at once, 24 a minute), before the shared one, so neither one grant refreshing in a loop nor one person's grants nor a handful of identities can starve the others; a replayed refresh token is refused before any of them; /mcp/oauth/register: up to 10 a second sealed and audited; above that, new registrations wait. Registered clients are unaffected.
Open sessions, connected MCP clients and issued grants keep working in every case. Each refusal is counted in
pgtoolbox_console_throttled_requests_total{bucket}; alert on a sustained rate (see metrics) and name the proxies so per-address budgets stop a single sender first.
Writes: token passthrough, never impersonation
The IdP token is an API-server token (OpenShift OAuth, API-server OIDC flags or
AuthenticationConfiguration, or a pasted token), so the console can send the
person's own token to the API server. This is the same prerequisite Headlamp, Argo CD
and the Kubernetes Dashboard have. Platforms where the API server cannot accept the
IdP's tokens use the token provider.
An impersonation mode (the console verifies the OIDC token itself and calls the API
server with its own token plus Impersonate-User) does not exist. If it is ever
added it will be an opt-in with a chart flag, off by default, with loud
documentation: the grant is equivalent to cluster-admin and the console would
become the authority on who the user is, which is exactly what the design avoids.
No Role or ClusterRole in the product contains the impersonate verb.
pgAdmin is a credential-holder
Whoever can open a pgAdmin holds the credentials of every role it offers. The operator therefore:
- offers the application user and applied
DatabaseRoles only, with privileged credentials behind an explicitcredentials.superuser: true, and (admission policy) lets only someone who maygetthe namespace's Secrets ask for them, widen who may sign in (spec.authbut its display name,spec.admins), raise the OpenShift proxy'ssessionMaxAge, or put the settings database on a Cluster of the namespace (configDatabase); - gates opening it by identity (
requiredClaimson Kubernetes,connect pgadmins/<name>on OpenShift, a verb that neitherviewnoreditcarries, even where OLM aggregatesgeton its CRDs intoview), and makespgtoolbox-console-operatorthe only user role that grants that verb (besides the namespaceadmin), and bounds the OpenShift oauth-proxy's session (it reviewsconnectat sign-in only) to the session lifetime, 1 h by default, up toauth.openshiftOAuthProxy.sessionMaxAge; raising it is checked like widening who may sign in (below); - with
configDatabase, creates a login role on that Cluster, whose password the pgAdmin Pod holds: setting it is checked like offering credentials, and its name must start withpgadmin_, because CloudNativePG adopts a role of that name that already exists in PostgreSQL and resets its password and memberships; - keeps passwords out of the operator, out of the Pod spec, out of pgAdmin's file manager and off disk (a memory-backed volume);
- reports, but does not enforce, whether the Pod's egress is confined.
Details: PgAdmin.
Exposure annotations that configure the ingress controller
spec.exposure.annotations, and the annotations of metadataPropagation rules
whose target is All or Exposure, land on the Ingress, Route or HTTPRoute the
operator writes; rules whose target is All or Service land on the Service it
routes to, where Kong and HAProxy read some of the same keys. Some annotations
are configuration an ingress controller runs: written by the operator, they
would let an editor who may not create an Ingress inject proxy configuration,
claim a host outside EXPOSURE_ALLOWED_HOST_SUFFIXES (the operator checks only
spec.exposure.hostname), open the console to other networks, or send pgAdmin's
and the console's traffic, cookies included, to a server they run. The admission
policy (on by default) therefore requires create on the exposure kind
(ingresses, routes or httproutes in the namespace) to add or change one of
these keys, or to switch an object carrying one from clusterIP to an exposure.
In the table, name* is every key that starts with name:
| Prefix | Keys | Controller | What they change |
|---|---|---|---|
| (any) | *snippet* | every controller with snippets | raw proxy configuration |
nginx.ingress.kubernetes.io/ | auth-*, mirror-*, canary*, cors-*, proxy-redirect-*, enable-global-auth, satisfy, proxy-ssl-secret, server-alias, permanent-redirect, temporal-redirect, app-root, default-backend, custom-http-errors, whitelist-source-range, allowlist-source-range, denylist-source-range, enable-cors | ingress-nginx | authentication, extra hosts, source ranges, redirects, another backend, traffic split, cross-origin access |
alb.ingress.kubernetes.io/ | actions.*, conditions.*, auth-*, scheme, group.name, group.order, inbound-cidrs, security-groups, listen-ports | AWS Load Balancer Controller | internet-facing scheme, a shared load balancer, source ranges, routing actions, authentication |
haproxy-ingress.github.io/ | config-*, auth-*, cors-*, allowlist-source-range, whitelist-source-range, denylist-source-range, redirect-from, redirect-to, app-root | haproxy-ingress | raw configuration, authentication, source ranges, redirects |
haproxy.org/ | auth-*, cors-*, allow-list, whitelist, deny-list, blacklist, request-redirect, route-acl | HAProxy Kubernetes Ingress | authentication, source ranges, redirects, routing |
haproxy.router.openshift.io/ | ip_whitelist, ip_allowlist | OpenShift router | source ranges |
traefik.ingress.kubernetes.io/ | router.middlewares, router.entrypoints | Traefik | middlewares, which entry point (network) serves it |
konghq.com/ | plugins, host-aliases | Kong | plugins, extra hosts |
A kept annotation with the same value, on a rule reaching the same objects and
an object exposed the same way, is not checked again. The policy therefore gates
only keys that are added or changed: it does not remove or lower a restriction
that is already there, and it does not make one up. A restriction an administrator
relies on (inbound-cidrs, a source range, scheme) belongs in the ingress
controller or platform configuration (a default or a locked annotation set), not in
an annotation an editor with write access to the CR may later drop. Keys that tune how a
request reaches the workload's own Service are not checked: timeouts, body size,
affinity, rewrite-target, use-regex, upstream-vhost, backend-protocol
(they can break the route, not move it). The Service the operator renders is
always ClusterIP (written with server-side apply), so the cloud load-balancer
keys (service.beta.kubernetes.io/*) do nothing on it. Choosing the exposure is
not gated: spec.exposure.type, the hostname (under
EXPOSURE_ALLOWED_HOST_SUFFIXES), ingressClassName and the Gateway parentRef
(which the Gateway's allowedRoutes accepts or not) are what an editor is
allowed to set; restrict the classes and Gateways a namespace may use where the
platform provides them.
Limits: this is a deny-list of known keys, not every controller's. A
controller run with a custom annotation prefix (ingress-nginx
--annotations-prefix, for example) reads keys this list does not name. A
clusterIP object renders no exposure, so its Service annotations are not
checked; an Ingress someone else creates for that Service makes its controller
read them. Where the policy is off the operator writes what the spec says. Grant
pgadmins and pgtoolboxconsoles writes only to people you would let create
the Ingress.
Secret references need get
A PgToolBoxConsole, a PgAdmin or an EvidenceAgent names Secrets of its own
namespace: the SMTP password, the chat and generic webhook URLs of the digest,
the OIDC or OpenShift client secrets, the Prometheus bearer token, the tracing
exporter's headers, the restore-drill seal keys, an Ingress certificate, image
pull secrets, the Secrets an evidence agent may read (spec.allowedSecrets). The
operator mounts them into the workload, and the workload sends them where the
spec says (an SMTP host, a webhook URL, an identity provider). RBAC on the custom
resource alone would therefore let anyone who may edit a PgToolBoxConsole read
any Secret of the namespace by pointing the console at a server they run.
Editing a PgToolBoxConsole, a PgAdmin or an EvidenceAgent requires get on
every Secret it references. The rule is part of the operator chart's
ValidatingAdmissionPolicy (on by default), which asks the API server's authorizer, as the
requesting user, whether that user may get each named Secret, and refuses the
request otherwise (reason Forbidden, message "editing a PgToolBoxConsole, a PgAdmin
or an EvidenceAgent requires get on every Secret it references").
- It is on by default in the operator chart (
operator.admissionPolicy.enabled;validationActionsselectsDeny,Warnand/orAudit,Denyalone enforces). It is the only check of this rule: where the policy is off, grantcreate/updateonpgtoolboxconsoles,pgadminsandevidenceagentsonly to people you would trust with every Secret of the namespace. - kustomize applies it only when you opt in, and the OLM bundle cannot ship it.
kustomize: add the component
config/components/admission(the chart's policy with its default allow-lists, see kustomize). OLM: render it from the chart and apply it yourself (see OLM):helm template pgtoolbox charts/pgtoolbox --set operator.enabled=true --show-only templates/operator/admissionpolicy.yaml | kubectl apply -f -, or apply the same trust rule as above. - On update, only new or changed references are checked. The operator's own writes (finalizers) and edits that leave the references alone keep working on objects that exist already, and a Secret already referenced at a field, and sent to the same place, is no new disclosure. Moving a Secret to another field, or renaming it, is a change. Objects created before the policy was turned on keep what they reference until they are edited to reference something else: review them when you enable it.
- The policy pairs a reference with where it is sent. Keeping a reference and
changing its destination is a new reference, checked again. The destination is
every field that decides where the Secret goes and whom it is trusted with:
auth.oidc.issuerURLandauth.oidc.caConfigMapReffor both OIDC client secrets,auth.openshift.caConfigMapReffor the OpenShift client secret,prometheus.urlandprometheus.caConfigMapRefforprometheus.tokenSecretRef,digest.delivery.smtp.addressandtlsfor the SMTP password,tracing.endpointfortracing.headersSecretRef,auth.oauth2.issuerURLfor the PgAdmin client secret, and the image'srepositoryfor itspullSecrets. For anEvidenceAgent'sallowedSecretsit isclusterRef,objectStoreRef,allowInsecureEndpointandpgbackrest.repository,endpointandendpointCA. The other references name no destination in the spec: the digest webhooks keep their URL inside the Secret, the seal keys and the Ingress certificate are sent nowhere. A test classifies every field beside a reference as a destination or not, with a reason, and fails on a new one. Limit: what the policy compares is the fields, not what they lead to. Editing the CA ConfigMap itself, theObjectStore,StanzaorArchivean agent reads, a DNS record or a proxy is not seen; so do not let people who may not read a Secret edit those objects. - More than two new references at once need
geton every Secret of the namespace. An authorizer check costs most of a CEL expression's runtime budget, so the policy checks one or two added references name by name, and a larger change with one namespace-wide check. Somebody who may read only some Secrets adds them two at a time. - What is covered: every Secret reference in the three kinds. On a
PgToolBoxConsole:auth.oidc.clientSecretRef,auth.oidc.grantClient.clientSecretRef,auth.openshift.clientSecretRef,prometheus.tokenSecretRef,digest.delivery.*,tracing.headersSecretRef,restoreDrills.sealKeySecretRef,exposure.tls.certificateSecretRefandimage.pullSecrets. On aPgAdmin:auth.oauth2.clientSecretRef,exposure.tls.certificateSecretRefand thepullSecretsof both images. On anEvidenceAgent: each name ofspec.allowedSecrets. The list is what the agent editor consents to the agent reading; it means something only from somebody who may read those Secrets. A test walks the API types (typed references, core/v1 Secret types, map values, and string fields named after Secrets) and fails when a new Secret reference is missing from the policy or has no stated destination. - What is not covered on an
EvidenceAgent.pgbackrest.endpointCAis a CA bundle the agent only trusts, and the agent reads it only ifspec.allowedSecretslists it too.image.pullSecretsgoes to the kubelet forspec.image, which only theimageOverrideallow-list may set.exposure.tls.certificateSecretRefcannot be set, since an agent isclusterIPonly. The Secrets the agent reads for the repository are named by the Cluster and theObjectStore,StanzaorArchive, and limited tospec.allowedSecrets(evidence agent). An emptyallowedSecrets(the deprecated behaviour, barman-cloudObjectStoreonly) lets the agent read what theObjectStorenames, which the backup plugin already sends to the endpoint theObjectStore's editors chose.
A PgAdmin that offers Cluster credentials requires get on the Secrets of its
namespace. Whoever opens pgAdmin holds the credentials it offers
(spec.credentials: the application user and DatabaseRoles by default, the
superuser with superuser: true). Their Secrets are named by the CloudNativePG
Cluster, not by the PgAdmin, so the policy cannot list them by name. It asks
instead whether the requesting user may get Secrets in the namespace (any
name): a Role with get on a few named Secrets does not qualify. The rule
applies when a PgAdmin is created with a credential kind on and a Cluster of
its namespace (clusterRefs or clusterSelector), and on an update that turns a
kind on, adds a Cluster or changes the selector. Narrowing, and edits that leave
the offer alone, are not checked. Without it, a custom role with create pgadmins
and connect pgadmins but no get secrets could ask for superuser: true and
open pgAdmin with the superuser's password. crossNamespaceClusterRefs are
outside the rule: the PgAdminAccess grant of the target namespace, created by
its owner, decides them (privileged credentials only with allowPrivileged).
Changing who may sign in to such a PgAdmin is checked the same way, since it
hands the same credentials to other people: any change to spec.auth (the
issuer, client, required claims, username claim and scopes of oauth2, a switch
between oauth2 and openshiftOAuthProxy, the proxy's image) or to
spec.admins. Only auth.oauth2.displayName, a button label, is left out; a
test fails when a new PgAdmin field is not classified. A PgAdmin that offers
no credentials is not concerned.
Message: "a PgAdmin that offers the credentials of Clusters of its namespace
(spec.credentials) requires get on the Secrets of the namespace".
A PgToolBoxConsole that projects its ServiceAccount token to Prometheus
requires create on that ServiceAccount's token. spec.prometheus.projectedToken
sends the console ServiceAccount's own token to spec.prometheus.url; with the
default audience (the API server's, which OpenShift's thanos-querier expects),
whoever runs that URL can use it against the API server for up to an hour. So
setting it, or changing the url, caConfigMapRef or tokenAudience it goes
with, requires create on serviceaccounts/token for the console's
ServiceAccount (the console's name, or <name>-console when the name does not
contain console): the power to mint the same token. Reading Secrets does not
qualify. Set spec.prometheus.tokenAudience when your Prometheus front accepts
a dedicated audience: the token is then useless against the API server.
Message: "a PgToolBoxConsole that sends its projected ServiceAccount token to
Prometheus (spec.prometheus.projectedToken) requires create on
serviceaccounts/token for the console's ServiceAccount".
Known limitations
These are accepted for the current release and listed so you can decide with them in view.
| Limitation | Effect | Mitigation |
|---|---|---|
Who may write evidenceagents/status in a namespace can receive that namespace's evidence token. The console calls an agent only when the operator's field manager wrote status.endpoint and status.caBundle, but a writer can name itself after the operator. | Read access to the backup metadata of that same namespace's agents only: each namespace has its own token audience. | Grant update/patch on evidenceagents/status to the operator only. Details on the evidence agent. |
| Sign-out does not revoke the token at the identity provider or OpenShift's OAuth server. | A copied token works until it expires. | Short token lifetimes at the IdP; the console's revocation list ends console sessions. |
| The console caches a positive identity for 30 s and a positive SAR for 10 s. | A revoked person may keep seeing views for up to that long; writes stop at once. | None needed; stated for incident response. |
| An expired or revoked access request does not end access already open. Deleting the RoleBinding stops new authorizations only. | Up to the console's caches above (10 s per SAR, 30 s per identity) in the console; in pgAdmin on OpenShift, whose oauth-proxy checks access at sign-in only, for the session lifetime: 1 h by default, up to auth.openshiftOAuthProxy.sessionMaxAge; and as long as they last, pgAdmin's open database connections and websockets. | For an incident, delete the PgAdmin's <name>-pgadmin-proxy-cookie Secret and then its Pod (this ends every pgAdmin session of it), and terminate the user's backends in PostgreSQL (access requests). |
The four-eyes check covers the console's approval path only. Approvers hold create rolebindings and bind on both roles. | An approver can bind pgtoolbox-console-operator to themselves directly with kubectl, with no request and no second person; the operator does not see such a binding. | Grant pgtoolbox-console-approver only to people trusted with the namespace's access; watch RoleBindings to the two roles in the API server's audit log. |
/metrics on the console's probe port is plain HTTP when the console has no certificate. | A scraper's token crosses the pod network in clear text. | Set console.tls.secretName: the probe port, /metrics included, is then HTTPS with the console's certificate (metrics). |
| The OpenShift sign-in using the ServiceAccount's projected token as client secret is not yet verified on a live cluster. | If OpenShift rejects it, sign-in fails with invalid_client. | Use a long-lived token Secret (checklist, step 5). |
| The pgAdmin container's root filesystem is writable. | An attacker with code execution in the container can write to its filesystem. | Restricted Pod Security, no capabilities, optional NetworkPolicy; read-only is an image change on the roadmap. |
| Shared pgAdmin servers carry one identity per role. | PostgreSQL's logs cannot name the person behind a shared server. | The Kubernetes API audit log names who opened pgAdmin on OpenShift; per-person roles are a roadmap item. |
The operator's ServiceAccount is effectively cluster-admin, unless the Helm chart sets watchNamespaces. Its ClusterRole holds Secret, ServiceAccount, Deployment, Role and RoleBinding verbs and bind on the three console ClusterRoles in every namespace, because it creates the Secrets, accounts and Roles of the workloads it runs. With watchNamespaces those rules hold only in the listed namespaces: there it can read and write every Secret, run a Pod as any ServiceAccount and bind the console roles, which amounts to a namespace administrator; cluster-wide it keeps only the review, Nodes and SCC reads and the evidence agent's ClusterRole and binding, and RBAC escalation prevention lets it grant others nothing it does not hold cluster-wide (no bind, escalate or impersonate there). | Whoever obtains its token (a compromised operator, or anyone who can exec into its Pod, edit its Deployment or read a token of its ServiceAccount) can read every Secret, and run a Deployment as any ServiceAccount, in every namespace it holds those rules in: every namespace by default, so treat it as cluster-admin; the listed ones with watchNamespaces. A Pod it runs is checked only by the namespace's Pod Security admission: in a watched namespace that does not enforce restricted, it can run a privileged Pod (host namespaces, host paths), which reaches the Node. The token cannot delete CNPG Clusters itself: binding pgtoolbox-console-restore-drills to its own ServiceAccount, or to any ServiceAccount, leaves it held by the delete guard to sealed drills, and it cannot grant itself every verb (*) on restoredrills, which is what the guard calls an administrator (escalation prevention). It can still have Clusters deleted by others: the guard matches ServiceAccounts only, so the drills role bound to a User or a Group grants an unguarded delete clusters, and a Pod run as a ServiceAccount of the namespace that already holds that right deletes through it. | Run the operator in a namespace of its own where only cluster administrators hold any right; restrict pods/exec, Deployment edits and serviceaccounts/token there. Enforce Pod Security restricted on every namespace it watches (everything it renders complies). Watch the API server's audit log for RoleBindings to pgtoolbox-console-restore-drills whose subject is not a ServiceAccount. With the Helm chart, set operator.config.watchNamespaces to confine the Secret, ServiceAccount, workload and RBAC rules to the listed namespaces (a RoleBinding each); kustomize and OLM always bind them cluster-wide. One operator per cluster is the supported layout. |
pgtoolbox-console-operator is effectively database-superuser. patch clusters covers the whole Cluster spec (enableSuperuserAccess, managed.roles, postgresql.parameters). | Its holder, and the grantee of an operator access request, can become the superuser of the namespace's Clusters. | Grant and approve it as you would the postgres password (authorization). |
Network
NetworkPolicies are optional and off by default, except the console's ingress
policy, which the operator renders for a published Route and for an Ingress or
Gateway that names its controller in spec.exposure.networkPolicy.from. RBAC and SubjectAccessReview
are the isolation the product guarantees; a policy is an additional boundary where
your CNI enforces one. See console,
operator and
PgAdmin.