Secrets Management for Geospatial Platforms
A geospatial portal holds an unusual number of credentials for its size. The catalogue reaches a spatial database; the rendering engine reaches the same database with a different role; workers reach a message broker and an object store; harvesters hold tokens for partner catalogues; the tile plane holds an upstream credential; and the whole thing sits behind an identity provider with its own client secret. Each of those is a small decision, and together they decide how long an incident takes and whether a rotation is a routine task or a project. This topic sits inside the Core Portal Architecture & Security Boundaries framework, alongside the boundary and identity work it depends on.
The failure this topic exists to prevent is not dramatic. It is a portal where nobody can say with confidence how many copies of the database password exist, which of them are still valid, or what would break if one were changed — and where, consequently, nothing is ever rotated.
Where a Secret Can Live, and What Each Choice Costs
There are only four places a running component can obtain a credential from, and they differ in exactly one property that matters more than the others: whether the secret exists as a durable copy outside the secret store.
Reading the last column explains why the bottom two rows are worth the extra machinery. A credential that exists only inside a secret store can be rotated by changing one thing; a credential that exists in a manifest, an image, three developers’ shells and a backup can only be rotated by finding all of them first, which is the step that never completes. The rotating GeoServer credentials without downtime guide is largely an exercise in dealing with the consequences of the second row.
The fourth row — dynamically issued, short-lived database credentials — is genuinely available for PostGIS through a secret manager’s database engine, and it changes the character of the problem entirely: there is no rotation procedure because there is nothing durable to rotate. It costs a dependency in the connection path, which is a real trade-off, and it interacts awkwardly with connection pooling, since a pooled connection outlives the credential that opened it.
Not Every Secret Deserves the Same Treatment
Applying the strongest control to everything is how a secrets programme stalls. Sort the portal’s credentials by what an attacker gains and how quickly you would need to act, and let that decide the mechanism.
The bottom row carries a caveat worth making explicit, because it is where the reasoning most often goes wrong. “Low value, assuming the network boundary holds” is a statement about the network policy described in security boundary mapping for OGC services — if that boundary is weaker than believed, an internal service token is an entry point, not a formality. Revisit the classification whenever the network model changes.
The Credential That Fetches the Credentials
Every secret store creates one problem it cannot solve for itself: something has to authenticate to the store. If that something is a token in a manifest, the whole scheme has been reduced to protecting one secret instead of many — better, but not different in kind. The pattern that resolves it is workload identity: the platform vouches for the workload, and the store trusts the platform rather than a shared secret.
Two consequences are worth planning for. The store now depends on the platform’s identity service, so a failure there is a failure to start any pod that needs a secret — cache the last good value on the node and stagger start-up fetches so that a brief outage does not become a thundering herd against the store. And the grant is now expressed in terms of workload identity rather than of people, which is what makes it auditable: the question “which components may read the database password” has a single answer that can be listed, rather than being distributed across whoever holds a copy.
Secrets Are Not Only in the Control Path
A portal’s credential inventory usually starts and stops at the deployment: what the application, the workers and the renderer need to start. That list is incomplete in a way specific to geospatial platforms, because a substantial part of a portal’s credential surface lives in data rather than in configuration.
Datastore definitions inside the OGC engine embed database passwords in their own configuration files, encoded rather than encrypted, and they are copied into every backup of that engine’s data directory. Harvest configurations carry tokens for partner catalogues. Published service definitions sometimes carry an upstream credential for a cascading WMS. Notebooks and desktop project files saved into a shared workspace carry whatever connection string their author used. None of these are in the deployment manifests, none are managed by the secret store, and all of them are real credentials that grant real access.
The practical remedy is to enumerate credentials from the systems that accept them rather than from the systems that store them. The spatial database can list its roles and, from pg_stat_activity, which of them are actually in use and from where. The object store can list its access keys and their last use. The identity provider can list its clients. That enumeration is the only one that finds the credential nobody documented, and it is the starting point for the inventory the rotating GeoServer credentials without downtime procedure depends on.
Two further habits keep the data path from re-accumulating secrets. Treat the engine’s data directory as sensitive material in backups — encrypted, access-controlled, and covered by the same retention rules as the database itself — because it contains credentials in a recoverable form. And give analysts and integrators a way to obtain a scoped, short-lived credential on demand, because the alternative is not that they work without one: it is that a long-lived credential is pasted into a project file and shared with a colleague.
Delivery: How the Secret Reaches the Process
Having chosen a store, there are three ways the value reaches the process that needs it, and they differ in how a rotation propagates.
An environment variable injected at start-up is the simplest and the least responsive: the process holds the value for its lifetime, so a rotation requires a restart of every consumer. A mounted file, refreshed in place allows a process that re-reads the file to pick up a new value without restarting — which most database drivers will not do on their own, so it usually needs application support. A sidecar or agent that fetches and renews puts the renewal logic outside the application entirely, which is the most robust and the most operationally intrusive.
For a portal, the pragmatic arrangement is usually the second for long-lived components and the first for short-lived jobs, with the caveat that “refreshed in place” is only useful if something actually re-reads. A file that updates while the application holds the old value in memory is a rotation that appears to work and silently has not.
Rotation Is a Property of the System, Not an Event
The reason rotation is painful in most portals is that it is treated as an occasional project rather than as a routine the system is designed for. Three design choices make it routine.
Support two valid credentials at once, wherever the backing system allows it. A database can hold two roles with identical grants; an object store can hold two access keys; an identity provider can publish two signing keys. Overlap is what turns a cutover into a sequence of independent, reversible steps.
Give every consumer a way to be told to reload without restarting, or accept that rotation means a rolling restart and make that cheap. Both are acceptable; what is not is discovering during the rotation which of the two you have.
Record the rotation cadence per credential class and the date each was last rotated, next to the credential’s owner. A quarterly rotation nobody has performed since the platform was built is a finding waiting to be made by an auditor; the same fact recorded honestly is a backlog item.
An Operational Maturity Checklist
The difference between a portal that can rotate a credential on a Tuesday afternoon and one that cannot is a small number of properties, each of which is either true or false and can be checked without a project.
- Every credential has a named owner. Not a team mailbox — a person who can be asked what breaks if it changes. Credentials without an owner are the ones that survive rotations by being forgotten.
- No credential exists in version control in plaintext. Verified by a secret scanner in the pipeline, not by inspection, because inspection finds the ones you remembered to look for.
- The inventory is enumerated from the systems, not from documents. Database roles, object-store keys and identity-provider clients are listed from the systems themselves and reconciled against the expected set on a schedule.
- Two credentials can be valid at once, for every class where the backing system allows it. This is what makes rotation a sequence of reversible steps rather than a cutover.
- Every consumer can be reloaded without a redeploy, or a rolling restart is cheap and rehearsed. Either is fine; not knowing which one applies is not.
- Rotation has been performed at least once, deliberately, outside an incident. A procedure that has never been run is a document, not a capability — and the surprises are always in the surrounding systems rather than in the credential itself.
- The store’s access log is retained for the audit window. The question “who read this, and when” has an answer that does not depend on anybody’s memory.
- Backups of the engine’s data directory are treated as credential-bearing. They contain datastore passwords in recoverable form and belong under the same handling rules as the database.
- Analysts and integrators have a supported route to a scoped, short-lived credential. Without one, they will create their own, and it will be long-lived and shared.
Working through that list usually surfaces two or three items that are false and cheap to fix, and one that is false and expensive. The expensive one is worth writing into the plan explicitly rather than leaving as an implicit exception, because it is the one that will be quoted back during the next audit — and stating it as a known, dated gap is a materially better position than discovering it in the room.
The list is also a reasonable acceptance test for any change to the platform’s deployment. A new component that arrives with a credential in its manifest, no owner, and no rotation story has not been integrated; it has been added, and the difference shows up a year later when somebody tries to change the database password.
One caution on using the list as a target. Every item is worth having, and pursuing all nine simultaneously on a small team produces a half-finished migration in which some components read from the store and some still read from manifests — which is harder to reason about than either end state. Take them in the order above: ownership and inventory first, because they cost nothing and make everything else measurable; then plaintext removal; then the overlap and reload properties that make rotation routine; then the audit and backup handling. Each step leaves the platform in a coherent state, which is the property that matters when the work is interrupted for a quarter, as it usually is.
Operational Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| A rotated credential works for new pods but not existing ones | The value was injected at start-up and is held in memory | Roll the deployment, or add reload support and verify it re-reads |
| A secret appears in a pod’s environment dump in a support bundle | Delivered as an environment variable and collected by diagnostics | Move to a mounted file, and scrub environment dumps in the collector |
| The secret store becomes a single point of failure at boot | Every pod fetches at start-up with no cache and no retry | Cache the last good value on the node, and stagger start-up fetches |
| Dynamic database credentials expire mid-session | The pooler holds connections longer than the credential’s lease | Align the pool’s maximum connection lifetime below the lease duration |
| Nobody knows which credentials exist | No inventory, and secrets added ad hoc during incidents | Enumerate from the systems themselves — roles, keys, tokens — not from documents |
| A rotation broke a partner integration nobody knew about | The credential was shared with an external consumer years ago | Never share a credential; issue each consumer its own, with its own scope |
| Audit cannot tell who read a secret | The store has no access log, or it is not retained | Enable access logging in the store and retain it as long as the audit window |
FAQ
Is a Kubernetes Secret enough, or is a dedicated secret manager necessary?
A cluster Secret is a real improvement over a value in a manifest, and for a single-cluster portal with encryption at rest enabled and tight access control it may be sufficient. What it does not give you is rotation machinery, dynamic issuance, an access audit trail, or a boundary that survives cluster compromise. Those are the reasons to reach for a dedicated store, and they arrive in that order as the platform grows.
How do secrets fit with GitOps, where everything is supposed to be in version control?
By putting a reference in version control and the value elsewhere, or by putting an encrypted value in version control that only the cluster can decrypt. Both patterns are established; what does not work is the instinct to keep plaintext out of Git by keeping it in a wiki instead, which moves the copy rather than removing it. The mechanics are in sealing GeoNode secrets for GitOps.
What should happen when a credential is believed to be compromised?
Rotate first and investigate afterwards, in that order, and design so that rotating first is possible. If rotation requires a change window, an approval, and a coordinated restart of six components, the practical outcome under pressure is that somebody argues about whether the compromise was real. Short rotation paths are a security control in themselves.
How long should a database credential live?
Short enough that an undetected leak has a bounded window, long enough that renewal is not the platform’s most fragile dependency. For statically managed roles a quarterly rotation is a defensible baseline. For dynamically issued credentials, hours rather than days — with the pooler’s connection lifetime set below the lease so a connection is never using an expired identity.
Does encrypting secrets at rest remove the need for any of this?
No. Encryption at rest protects a stolen disk or a leaked backup; it does nothing about a credential read by a process that should not have it, a value printed into a log, or a token shared with a partner two years ago. It is one control among the ones described here, and the least likely to be the one that fails.
Related
- Storing GeoServer Secrets in Vault with Kubernetes Auth — workload identity instead of a bootstrap credential.
- Sealing GeoNode Secrets for GitOps — keeping an encrypted value safely in version control.
- Auditing Secret Access for Agency Compliance — answering who read what, and when.
- Rotating GeoServer Credentials Without Downtime — the rotation procedure these choices make easy or hard.
Up one level: Core Portal Architecture & Security Boundaries.