DBDelve
Dive deep into your data. A fast, native, multi-engine database client written in Rust. Free and open source, no telemetry.
翻訳待ち
Web-based terminal for safe Kubernetes debugging - Designed for Devs, not Ops. RBAC, audit logs, auto-sweep.
翻訳待ち
Loading the full README.
Read the full README ↗Web-based debug terminal for Kubernetes. Pick a pod, inject an ephemeral
container with the image you want (busybox, netshoot, psql, …),
attach to it from your browser. Pluggable authN (JWT/OIDC) and authZ
(OPA) so developers reach pods without kubectl, and without the cluster
having to know their corporate identity.
Click the still for the live demo on porthole.runlocal.dev.
Smallest possible install — no auth, no policy. Port-forward and drive the UI immediately.
helm install porthole oci://ghcr.io/bcollard/charts/porthole \
--version 0.2.1 \
--namespace porthole --create-namespace \
--set auth.disabled=true
kubectl -n porthole port-forward svc/porthole 8081:8081 &
open http://localhost:8081/ui/
auth.disabled=true stamps every request as a local-dev principal
and grants admin everywhere. Anything beyond a kubectl port-forward demo needs the OIDC layer below.
The browser can't speak OIDC to porthole on its own; something on the request path has to terminate the handshake and inject the user's id-token into the request headers. Porthole then validates the JWT against the IdP's JWKS and reads the user claims off it.
Reference stack: Envoy Gateway + its SecurityPolicy CR. The
chart renders a generic HTTPRoute; you attach a SecurityPolicy
to it that does the OIDC handshake. Worked recipe in
docs/examples/envoy-gateway/ —
a values.yaml for helm install plus the SecurityPolicy you
apply separately.
The chart handles the awkward bits:
auth.idTokenHeader defaults
to X-ID-Token (Envoy Gateway's forwardIDToken). Point it at
Authorization (with auth.idTokenHeaderPrefix: "Bearer ") when
you'd rather rely on forwardAccessToken. The canonical
Authorization: Bearer fallback is always tried, so
forwardAccessToken: true works with zero extra config.gatewayAPI.pathPrefix=/porthole lets
porthole share a hostname with other apps behind one platform
gateway (api.example.com/porthole). The chart emits a 308 for
the missing trailing slash and a URLRewrite that strips the
prefix; the SPA infers the public prefix from
window.location.pathname at boot. Same image works at root or
under any prefix.For the OIDC handshake itself, see the Envoy Gateway docs: Envoy Gateway SecurityPolicy / OIDC.
Every handler asks the OPA sidecar for a yes/no decision before touching the kube API. The chart bundles OPA as a sidecar; policy + data live in a ConfigMap you can override at install time.
OPA input — pkg/auth/opa.go builds the input on every
request: user claims (sub/email/groups), the requested action,
namespace, namespace labels (fetched lazily, cached 60 s), and
the current timestamp.
Rego logic — policy/porthole.rego carries the matchers:
glob + labels + business-hours, AND-composed, default-deny. Plus
an effective_bindings rule the SPA queries on /api/me so the
topbar can render the user's role chips.
Action vocabulary — fixed set the handlers ask OPA for:
| Action | Triggered by |
|---|---|
list_namespaces | the namespace picker (cluster-wide) |
list_pods | the pod picker, Service Viewer |
list_ec | the EC chip bar refresh |
inject_ec | clicking + Debugger |
attach_ec | opening a WebSocket terminal, extending TTL |
terminate_ec | clicking ×, "Clean up all", or the sweeper |
policy/data.jsonTwo tables: roles (bundle actions) and bindings (group → role → namespace scope). Override via chart values; OPA hot-reloads without a pod restart.
helm upgrade porthole oci://ghcr.io/bcollard/charts/porthole \
--reuse-values \
--set-file opa.data=./my-bindings.json
{
"policy": {
"roles": {
"viewer": ["list_namespaces", "list_pods", "list_ec"],
"debugger": ["list_namespaces", "list_pods", "list_ec",
"inject_ec", "attach_ec", "terminate_ec"]
},
"bindings": [
{ "group": "porthole-admins", "role": "debugger", "namespace_glob": "*" },
{ "group": "team-a", "role": "debugger", "namespace_glob": "team-a-*" },
{ "group": "secops", "role": "debugger", "namespace_labels": { "tier": "production" } },
{ "group": "oncall", "role": "debugger", "namespace_glob": "*", "business_hours": true }
]
}
}
A binding always carries group + role + one or more matchers:
This is the beginning of the README. Read the rest at the original. Read the full README ↗