README.md

September 14, 2026 ยท View on GitHub

Gatekeeper Policy Manager (GPM)

GPM Release Helm Chart Release License

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 NameDescriptionDefault
GPM_LISTEN_ADDRESSServer listen address:8080
GPM_LOG_LEVELLog level (DEBUG, INFO, WARN, ERROR)INFO
GPM_EVENTS_SOURCEComma-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_VERIFYSkip 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_NAMESPACERead 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_PATHThe 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)
KUBECONFIGPath 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 NameDescriptionDefault
GPM_AUTH_ENABLEDSet to OIDC or to JWT to protect GPM. Any other value leaves it open.Anonymous
GPM_SECRET_KEYKey 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_SCHEMESet 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_AGEHow long a session lasts, in seconds.28800 (8 hours)
GPM_OIDC_REDIRECT_DOMAINThe public address of GPM, for example https://gpm.example.com. The provider sends users back to <domain>/oidc-auth. Required.
GPM_OIDC_CLIENT_IDClient ID registered with the provider. Required.
GPM_OIDC_CLIENT_SECRETClient secret, if the client is confidential.
GPM_OIDC_SCOPESExtra 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_ISSUERIssuer URL. GPM reads the rest of the provider's configuration from it, unless the endpoints below are set.
GPM_OIDC_AUTHORIZATION_ENDPOINTAuthorization endpoint. Setting any endpoint below turns discovery off, so set them all together.
GPM_OIDC_TOKEN_ENDPOINTToken endpoint. See the note above.
GPM_OIDC_JWKS_URIJWKS URI. See the note above.
GPM_OIDC_END_SESSION_ENDPOINTEnd 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_ENDPOINTAccepted for compatibility with GPM 1.x. Not used.
GPM_OIDC_USERINFO_ENDPOINTAccepted 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:

PathWhy it is open
/healththe liveness and readiness probes run without credentials
/login, /oidc-auth, /logoutthe login flow itself
/metricsPrometheus scrapes it. It holds request counters only, no policy data
/static/*, /favicon.icothe 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 NameDescriptionDefault
GPM_JWT_JWK_SET_URLThe proxy's JWKS. For Pomerium it is https://<pomerium-host>/.well-known/pomerium/jwks.json. Required.
GPM_JWT_AUDIENCEThe 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_NAMEThe 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_ISSUERThe 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_URLWhere 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_ENABLED must be OIDC or JWT. 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_CLAIM must 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:

VariablePurpose
GPM_RBAC_USERNAME_CLAIMRequired. The claim that holds the username the API server knows. Use the claim your API server reads in --oidc-username-claim.
GPM_RBAC_USERNAME_PREFIXThe prefix that --oidc-username-prefix adds, for example oidc:.
GPM_RBAC_GROUPS_CLAIMThe claim that lists the groups. The default is groups.
GPM_RBAC_GROUPS_PREFIXThe 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:

  1. Mount a kubeconfig file with the cluster access configuration in the GPM pods.
  2. Set the KUBECONFIG environment variable to the path of the mounted kubeconfig file. 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.