README.md
September 14, 2026 ยท View on GitHub
Gatekeeper Policy Manager (GPM)
Gatekeeper Policy Manager is a read-only web UI that shows the status of OPA Gatekeeper policies in a Kubernetes cluster.
The target Kubernetes cluster can be the same one where GPM runs, or a remote cluster that GPM connects to with a kubeconfig file. You can also run GPM locally on a client machine and connect to a remote cluster.
GPM lets you see in detail:
- Constraint Templates with their rego code.
- Constraints with their current status, violations, enforcement action, matches definitions, etc.
- Mutations defined and their details.
- Events emitted by OPA Gatekeeper (alpha feature).
- Gatekeeper Configuration custom resource values.
You can see some screenshots below โคต.
Requirements
GPM needs OPA Gatekeeper in your cluster. It also needs some constraint templates and constraints. Without them, GPM has nothing to show.
Tip
You can deploy Gatekeeper to your cluster with the SIGHUP Distribution Policy Module (also open source).
Deploying GPM
Deploy using Kustomize
To deploy Gatekeeper Policy Manager to your cluster, apply the kustomization file with this command:
kubectl apply -k https://github.com/sighupio/gatekeeper-policy-manager
By default, this creates a deployment and a service named gatekeeper-policy-manager in the gatekeeper-system namespace. To configure more, see the kustomization.yaml file.
Note
GPM can run as a Pod in a Kubernetes cluster, or locally with a kubeconfig file. It autodetects the correct configuration.
If you did not configure an ingress, use port-forward to access the web UI:
kubectl -n gatekeeper-system port-forward svc/gatekeeper-policy-manager 8080:80
Then open http://127.0.0.1:8080 in your browser.
Deploy using Helm
You can also deploy GPM with the Helm chart.
First create a values file, for example my-values.yaml, with your custom values for the release. See the chart's readme and the default values.yaml for more information.
From v2.1.0 the chart is published as an OCI artifact on quay.io, next to the container image. There is no helm repo add step any more. You need Helm 3.8 or later, which supports OCI registries. Then execute:
helm upgrade --install --namespace gatekeeper-system --set image.tag=v2.1.0 --values my-values.yaml gatekeeper-policy-manager oci://quay.io/sighup/charts/gatekeeper-policy-manager --version 0.20.0
Important
Replace my-values.yaml with the path to your values file, and --version 0.20.0 with the chart version you want.
Running locally
You can also run GPM locally with Docker (or another container runtime) and a kubeconfig. If the kubeconfig file is at ~/.kube/config, run this command:
docker run -v ~/.kube/config:/home/nonroot/.kube/config -p 8080:8080 quay.io/sighup/gatekeeper-policy-manager:v2.1.0
Then open http://127.0.0.1:8080 in your browser.
You can also run the app binary directly. See the development section for more information.
Configuration
GPM is a stateless application. You can configure it with environment variables. The possible configurations are:
| Env Var Name | Description | Default |
|---|---|---|
GPM_LISTEN_ADDRESS | Server listen address | :8080 |
GPM_LOG_LEVEL | Log level (DEBUG, INFO, WARN, ERROR) | INFO |
GPM_EVENTS_SOURCE | Comma-separated event source components to show. Gatekeeper tags admission events with gatekeeper-webhook and audit events with gatekeeper-audit. | gatekeeper-webhook,gatekeeper-audit |
GPM_SKIP_TLS_VERIFY | Skip TLS certificate verification while connecting to the Kubernetes API Server. Needed on clusters whose CA certificate is missing the AKI/SKI extensions, as happens on EKS. USE WITH CAUTION. | false |
GPM_EVENTS_NAMESPACE | Read events from this namespace only. Empty means every namespace, which needs a cluster-wide read on events. See Events and RBAC. | `` (every namespace) |
GPM_BASE_PATH | The subpath for GPM, for example /gpm. The image sets this value from the PUBLIC_URL build argument. See Running behind a reverse proxy on a subpath. | `` (the domain root) |
KUBECONFIG | Path to a kubeconfig file, if provided while running inside a cluster this configuration file will be used instead of the cluster's API. | $HOME/.kube/config |
Authentication
GPM is unauthenticated by default. There are two ways to protect it:
GPM_AUTH_ENABLED=OIDC. GPM runs the login flow itself against an OpenID Connect provider.GPM_AUTH_ENABLED=JWT. An authenticating proxy in front of GPM identifies the user. See Behind an authenticating proxy.
| Env Var Name | Description | Default |
|---|---|---|
GPM_AUTH_ENABLED | Set to OIDC or to JWT to protect GPM. Any other value leaves it open. | Anonymous |
GPM_SECRET_KEY | Key used to sign and encrypt the session cookie. Required when authentication is on: GPM refuses to start if it is still the 1.x default, which is published in this repository, so anyone can forge a session. | g8k1p3rp0l1c7m4n4g3r (the 1.x default) |
GPM_PREFERRED_URL_SCHEME | Set to https when GPM is served over TLS, so the session cookie is marked Secure. A GPM_OIDC_REDIRECT_DOMAIN that starts with https:// also marks it Secure. | http |
GPM_SESSION_MAX_AGE | How long a session lasts, in seconds. | 28800 (8 hours) |
GPM_OIDC_REDIRECT_DOMAIN | The public address of GPM, for example https://gpm.example.com. The provider sends users back to <domain>/oidc-auth. Required. | |
GPM_OIDC_CLIENT_ID | Client ID registered with the provider. Required. | |
GPM_OIDC_CLIENT_SECRET | Client secret, if the client is confidential. | |
GPM_OIDC_SCOPES | Extra scopes for the login request, separated by spaces or commas. openid, profile and email are always requested. Add the scope that carries group membership when your provider keeps it behind one. | |
GPM_OIDC_ISSUER | Issuer URL. GPM reads the rest of the provider's configuration from it, unless the endpoints below are set. | |
GPM_OIDC_AUTHORIZATION_ENDPOINT | Authorization endpoint. Setting any endpoint below turns discovery off, so set them all together. | |
GPM_OIDC_TOKEN_ENDPOINT | Token endpoint. See the note above. | |
GPM_OIDC_JWKS_URI | JWKS URI. See the note above. | |
GPM_OIDC_END_SESSION_ENDPOINT | End session endpoint. Discovered automatically when the provider advertises one. If GPM has one, a logout from GPM also ends your session at the provider. | |
GPM_OIDC_INTROSPECTION_ENDPOINT | Accepted for compatibility with GPM 1.x. Not used. | |
GPM_OIDC_USERINFO_ENDPOINT | Accepted for compatibility with GPM 1.x. Not used. |
Important
Register <GPM_OIDC_REDIRECT_DOMAIN>/oidc-auth as a valid redirect URI with your provider, and
<GPM_OIDC_REDIRECT_DOMAIN>/logout as a valid post logout redirect URI.
Set GPM_SECRET_KEY to a long random string. GPM will not start with the old default.
Set GPM_PREFERRED_URL_SCHEME=https whenever GPM is reachable over HTTPS.
GPM uses PKCE, so the authorization code cannot be used by anyone who intercepts it.
The session is a cookie. GPM signs the cookie and also encrypts it, so the contents are not
readable. GPM derives the two keys for this from GPM_SECRET_KEY with HKDF. Every replica derives
the same keys from the same secret, and so does the same replica after a restart.
Note
GPM does not keep sessions on the server. A logout clears the cookie in your browser. When the
provider supports it, GPM also ends the session at the provider. But GPM cannot cancel a copy of
the cookie that someone took to a different machine. Such a copy stays valid until
GPM_SESSION_MAX_AGE expires it. If this risk is a problem for you, use a short value.
On a subpath deployment the cookie is scoped to that subpath. A different application on the same host does not receive it.
Once authentication is on, everything requires a session except these paths, which have to stay reachable for a user who is not logged in yet:
| Path | Why it is open |
|---|---|
/health | the liveness and readiness probes run without credentials |
/login, /oidc-auth, /logout | the login flow itself |
/metrics | Prometheus scrapes it. It holds request counters only, no policy data |
/static/*, /favicon.ico | the assets the login and logout pages need |
Everything else โ every page, including the list of clusters โ needs a valid session.
When the session expires, GPM sends the user to /login to sign in again. The login route accepts
?next= with a same-site path that says where the user lands after signing in.
Behind an authenticating proxy
Set GPM_AUTH_ENABLED=JWT when a proxy such as Pomerium already identifies your users. The proxy
signs an assertion for each request, and GPM verifies that signature against the proxy's keys.
| Env Var Name | Description | Default |
|---|---|---|
GPM_JWT_JWK_SET_URL | The proxy's JWKS. For Pomerium it is https://<pomerium-host>/.well-known/pomerium/jwks.json. Required. | |
GPM_JWT_AUDIENCE | The aud claim GPM accepts. Set it to the host GPM is served on, for example gpm.example.com. Required, and see the warning below. | |
GPM_JWT_HEADER_NAME | The header that carries the assertion. Set it to Authorization for oauth2-proxy with --set-authorization-header. A Bearer prefix is removed. | X-Pomerium-Jwt-Assertion |
GPM_JWT_ISSUER | The iss claim GPM accepts. Not checked when this is empty. Set it when the keys belong to an identity provider. See the note below. | |
GPM_JWT_LOGOUT_URL | Where the Log out button points, for example https://gpm.example.com/.pomerium/sign_out. GPM shows no Log out button when this is empty. |
GPM verifies the signature, the audience, the issuer when you set one, and the expiry time. A request with no assertion, or with one that does not pass, gets a 401 and no data. GPM accepts RS256 and ES256. Pomerium signs with ES256.
This mode has no login page and no session cookie, so GPM does not read GPM_SECRET_KEY. GPM reads
the assertion again on every request, which is also why a change to a user's groups takes effect
immediately.
Warning
GPM_JWT_AUDIENCE is required, and GPM does not start without it.
A proxy signs every route it serves with the same key, and it writes the route into the aud
claim. Without this check, GPM accepts an assertion that the proxy made for a different route. A
user who is allowed on that other route can then read GPM.
For Pomerium the value is the bare host, with no scheme and no trailing slash.
Note
Set GPM_JWT_ISSUER when GPM_JWT_JWK_SET_URL points at an identity provider and not at the
proxy. With Pomerium, one key set serves the routes of one proxy, and GPM_JWT_AUDIENCE already
names this route, so the issuer adds little. With a shared identity provider, one key set and one
client ID cover many tenants, and the issuer is the claim that separates them.
GPM writes a warning at start when GPM_JWT_ISSUER is empty.
Note
GPM fetches the keys from GPM_JWT_JWK_SET_URL over HTTPS. A plaintext http:// URL is refused:
these keys decide who every user is. If your proxy uses a certificate from
a private CA, mount that CA into the GPM Pod. GPM has no setting to skip this check.
Authentication is not authorization. The proxy decides who reaches GPM. To also limit what each person sees, turn on RBAC-aligned views. That feature works the same way in both modes.
Running behind a reverse proxy on a subpath
GPM assumes by default that it is served from the domain root. If you put it behind a reverse proxy
on a subpath, for example example.com/gpm, set the GPM_BASE_PATH environment variable to that
subpath. GPM prepends it to the paths it hands the browser: the asset URLs, the login URL and the
OIDC redirects.
You can also set the subpath at build time with the PUBLIC_URL build argument, which sets
GPM_BASE_PATH in the image:
docker build --build-arg PUBLIC_URL=/gpm -t gatekeeper-policy-manager:subpath .
If you set neither, GPM runs at the domain root, as before.
Important
Configure your reverse proxy to strip the subpath before forwarding to GPM. GPM matches its
routes at the root, so it expects to receive /constraints and /static/..., not
/gpm/constraints. With nginx, a proxy_pass ending in a slash does this for you.
The image published on quay.io is built for the root path. If you need a subpath deployment, build
your own image with the argument above and push it to your own registry, or reference the
Dockerfile from your CI pipeline with the same --build-arg.
If you enable OIDC, keep GPM_OIDC_REDIRECT_DOMAIN as the scheme and host only, for example
https://example.com. GPM adds the subpath. Register https://example.com/gpm/oidc-auth with your
provider as the redirect URI.
Events and RBAC
The events view reads the events resource of the Kubernetes core API. By default GPM reads events
from every namespace. This needs a ClusterRole with read access to events in the whole cluster,
which is more access than the other views need.
To make the access smaller, set GPM_EVENTS_NAMESPACE to the namespace that OPA Gatekeeper runs
in, usually gatekeeper-system. Then GPM reads events from that namespace only. You can move the
read on events out of the ClusterRole and into a Role in that namespace.
The Helm chart does both steps for you. Set config.eventsNamespace and the chart removes events
from the ClusterRole. The chart then creates a Role and a RoleBinding in the namespace that
you named.
Note
GPM_EVENTS_NAMESPACE has priority over the ?namespace= parameter of the events endpoint. A
request cannot read a namespace that the deployment is not configured for.
RBAC-aligned views
By default every person who reaches GPM sees the whole cluster. Set GPM_RBAC_FILTERING=true and
GPM shows each person only what their own Kubernetes account can read.
GPM asks the Kubernetes API server, with a SubjectAccessReview, whether the logged-in person can
list the data behind a view. A view whose answer is no does not appear in the navigation, and a
request for it gets a 403. A request for the dashboard sends the reader to the Resources view
instead, because a browser lands there by default. The Resources view is always available, and it lists only the objects
that the person can read. Someone with access to one namespace sees that namespace.
GPM does not act as the user. It reads the cluster with its own ServiceAccount and asks about the
user separately, so it needs create subjectaccessreviews and not the impersonate permission. The
Helm chart adds this rule when you set config.rbacFiltering.enabled.
The feature narrows what a person sees through GPM. It does not narrow what GPM reads. The ServiceAccount keeps its cluster-wide read on the Gatekeeper objects, and a person who can run code in the pod reads all of them.
This feature needs three things, and GPM refuses to start without them:
- Authentication. Without an identity there is nobody to ask about.
GPM_AUTH_ENABLEDmust beOIDCorJWT. Both modes work the same way here: GPM reads the same claims, applies the same prefixes, and sends the same reviews. - One cluster. One identity cannot be authorized against several clusters, so GPM refuses to start when the kubeconfig names more than one context.
- A named username claim.
GPM_RBAC_USERNAME_CLAIMmust name the claim the API server reads. Without it the reviews carry whichever claim the token happens to hold, and two people can be authorized as one identity.
The name that GPM sends must be the name that the API server knows. Many clusters add a prefix with
--oidc-username-prefix, and some read the username from a different claim:
| Variable | Purpose |
|---|---|
GPM_RBAC_USERNAME_CLAIM | Required. The claim that holds the username the API server knows. Use the claim your API server reads in --oidc-username-claim. |
GPM_RBAC_USERNAME_PREFIX | The prefix that --oidc-username-prefix adds, for example oidc:. |
GPM_RBAC_GROUPS_CLAIM | The claim that lists the groups. The default is groups. |
GPM_RBAC_GROUPS_PREFIX | The prefix that --oidc-groups-prefix adds. |
Note
A wrong name denies everything, and the page is empty. To find the name that GPM used, put the
pointer on the "scoped to your access" label next to the page title. Compare that name with the
subject of your RoleBinding. GPM writes the same name to its log.
Important
Your API server must authenticate the same users. GPM asks it about a person by name, and an API server that does not know that name denies it, exactly as it denies a person with no access. The page then says that the person can read nothing, which hides the real cause.
Before you turn this on, confirm that your API server runs with --oidc-issuer-url,
--oidc-username-claim and the matching prefixes. GPM cannot check this for you.
In JWT mode GPM reads these claims from the proxy's assertion, on every request, instead of from
an ID token at login. A change to a person's groups therefore takes effect at once. The claims must
still be the ones the API server reads, and the proxy must put them in the assertion. For Pomerium,
email and groups are both present.
Multi-cluster support
GPM can show information from more than one cluster. To use this, provide a kubeconfig with more than one context. Each context points to a different cluster. GPM lets you choose the context (cluster) from the UI.
To run GPM in a cluster with multi-cluster support, do these steps:
- Mount a
kubeconfigfile with the cluster access configuration in the GPM pods. - Set the
KUBECONFIGenvironment variable to the path of the mountedkubeconfigfile. Or mount it at/home/nonroot/.kube/config, and GPM detects it automatically.
Important
The user for the clusters must have the correct permissions. Use the manifests/rbac.yaml file as a reference.
The cluster where GPM runs must reach the other clusters. This needs network connectivity.
When you run GPM locally, you already use a kubeconfig file to connect to the clusters. You see all your contexts and can switch between them from the UI.
AWS IAM Authentication
To use a kubeconfig with IAM authentication, you must customize the GPM container image. The IAM authentication uses external AWS binaries. The image does not include them by default.
You can customize the container image with a Dockerfile like this one:
FROM curlimages/curl:7.81.0 as downloader
RUN curl https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/v0.5.5/aws-iam-authenticator_0.5.5_linux_amd64 --output /tmp/aws-iam-authenticator
RUN chmod +x /tmp/aws-iam-authenticator
FROM quay.io/sighup/gatekeeper-policy-manager:v2.1.0
COPY --from=downloader --chown=root:root /tmp/aws-iam-authenticator /usr/local/bin/
You can also add the aws CLI for debugging. Use the same approach as before.
Note
Make sure that your kubeconfig has the apiVersion set as client.authentication.k8s.io/v1beta1
You can read more in this issue.
Screenshots
Development
GPM is written in Go. It uses the Echo framework and renders the UI on the server with the standard
library's html/template, plus a small amount of Alpine.js for interactivity. There is no separate
frontend build.
Alpine.js is vendored as static/ssr/alpine.min.js. Its version is pinned in package.json so
Dependabot tracks it; after a bump, run mise run vendor-alpine to refresh the vendored file (the
check-alpine-version task, part of mise run lint, fails if the two drift).
To develop GPM, run these commands:
# Install the dependencies
$ go mod download
# Run the development server
$ GPM_LOG_LEVEL=DEBUG go run .
Tip
A Kubernetes cluster with OPA Gatekeeper deployed helps you debug the application.
Contributing
Let us know if you use GPM and which features you want. Create an issue here on GitHub ๐ช๐ป
To contribute, pick one of the open issues and work on it. It is better to tell us first on the issue.
When you are happy with your work, open a Pull Request.
We try to stick to conventional commits when writing commit messages.








