{"id":46666043,"url":"https://github.com/infroware/k8s-janus","last_synced_at":"2026-06-08T00:04:54.049Z","repository":{"id":340661660,"uuid":"1166912308","full_name":"infroware/k8s-janus","owner":"infroware","description":"Just-in-time kubectl exec access for Kubernetes. Request → Approve → Exec → Expire. No permanent permissions. Ever.","archived":false,"fork":false,"pushed_at":"2026-06-01T20:17:47.000Z","size":5754,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-01T22:13:22.337Z","etag":null,"topics":["access-control","devops","fastapi","helm","jit","kubectl","kubernetes","pod","python","rbac","role-based-access-control","security"],"latest_commit_sha":null,"homepage":"https://infroware.github.io/k8s-janus","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/infroware.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-25T18:40:26.000Z","updated_at":"2026-06-01T20:17:53.000Z","dependencies_parsed_at":"2026-03-03T09:02:36.916Z","dependency_job_id":"69d9312b-c59f-4c77-a2e7-74a86ab7da98","html_url":"https://github.com/infroware/k8s-janus","commit_stats":null,"previous_names":["opsmode/k8s-janus","infroware/k8s-janus"],"tags_count":52,"template":false,"template_full_name":null,"purl":"pkg:github/infroware/k8s-janus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infroware%2Fk8s-janus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infroware%2Fk8s-janus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infroware%2Fk8s-janus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infroware%2Fk8s-janus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/infroware","download_url":"https://codeload.github.com/infroware/k8s-janus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infroware%2Fk8s-janus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34041110,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-07T02:00:07.652Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["access-control","devops","fastapi","helm","jit","kubectl","kubernetes","pod","python","rbac","role-based-access-control","security"],"created_at":"2026-03-08T18:05:04.299Z","updated_at":"2026-06-08T00:04:54.044Z","avatar_url":"https://github.com/infroware.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"webui/static/k8s-janus-logo.svg\" width=\"120\" alt=\"K8s-Janus logo\" /\u003e\n\n# K8S-Janus\n\n### *Just-in-Time Kubernetes Pod Access*\n\n[![CI](https://github.com/infroware/k8s-janus/actions/workflows/ci.yaml/badge.svg)](https://github.com/infroware/k8s-janus/actions/workflows/ci.yaml)\n[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/k8s-janus)](https://artifacthub.io/packages/helm/k8s-janus/k8s-janus)\n![Python](https://img.shields.io/badge/Python-3.12-3776AB?logo=python\u0026logoColor=white)\n![Kubernetes](https://img.shields.io/badge/Kubernetes-Operator-326CE5?logo=kubernetes\u0026logoColor=white)\n[![Helm](https://img.shields.io/badge/Helm-Chart-0F1689?logo=helm\u0026logoColor=white)](https://infroware.github.io/k8s-janus)\n![FastAPI](https://img.shields.io/badge/FastAPI-009688?logo=fastapi\u0026logoColor=white)\n![License](https://img.shields.io/badge/License-Apache_2.0-blue)\n\n**Engineers request temporary `kubectl exec` access through a web UI.**\n**Admins approve with one click. The token auto-expires.**\n**No permanent permissions. Ever.**\n\n\u003e *In Roman mythology, **Janus** was the god of doorways — watching every passage in both directions.*\n\u003e *He did not block the gate. He governed it.*\n\n\u003c/div\u003e\n\n---\n\n## 🚨 The Problem\n\nMost Kubernetes access patterns are broken:\n\n| Approach | Problem |\n|----------|---------|\n| 🔴 Permanent RoleBinding | Over-privileged, forgotten forever |\n| 🔴 Sharing cluster-admin | Dangerous, no audit trail |\n| 🔴 Manual token creation | Tedious, tokens never revoked |\n\n**K8s-Janus** replaces all of these with a structured, time-limited, fully auditable workflow.\n\n---\n\n## ✨ Features\n\n|  | Feature | Detail |\n|--|---------|--------|\n| 🌐 | **Web Terminal** | Browser-based `kubectl exec` — no local tools, no kubeconfig, no VPN |\n| 🖥️ | **Split-Pane** | Two pods side-by-side in one tab with independent shell sessions |\n| 📋 | **Pod Logs \u0026 Events** | Real-time logs and K8s events in the terminal sidebar, with structured diagnostics when Kubernetes calls fail |\n| ⚡ | **Quick Commands** | Save and replay one-click shell commands per cluster |\n| 🎨 | **Colored Prompt** | PS1 auto-injected on connect — cyan `user@host`, blue path |\n| 🏢 | **Multi-Cluster** | Manage any number of clusters from one install |\n| 🛰️ | **Remote Agent Mode** | On-prem/private clusters poll central Janus with local ServiceAccount auth; no stored remote kubeconfig |\n| 📦 | **Multi-Namespace** | One request covers multiple namespaces; namespace tab strip in terminal |\n| ✅ | **One-Click Approval** | Approve, deny, or override TTL from the admin dashboard |\n| 🚪 | **Self-Service Withdraw** | Engineers cancel their own Pending or Active requests |\n| ⚡ | **Instant Revoke** | Terminate any active session immediately |\n| ⏰ | **Auto-Cleanup** | SA + Role + RoleBinding deleted on TTL expiry |\n| ⏰ | **Pending Auto-Expiry** | Auto-deny requests that go unapproved beyond a configurable limit |\n| 🔐 | **Native OIDC/SSO** | Google, GitHub, Entra ID, Okta, GitLab, or any OIDC provider — no oauth2-proxy |\n| 📋 | **Full Audit Log** | Every request event, session open/close, command, and revocation logged |\n| 🗄️ | **PostgreSQL Backend** | Optional persistent DB — history survives pod restarts |\n| 🛡️ | **Security Hardened** | Non-root · read-only FS · all capabilities dropped · NetworkPolicy |\n\n---\n\n## 🔄 How It Works\n\n```\nEngineer             Web UI              Agent Operator        Approver\n   │                   │                     │                   │\n   │── submit ────────▶│                     │                   │\n   │  (ns=['a','b'])   │── store in DB ─────▶│                   │\n   │                   │                     │── notify ────────▶│\n   │                   │                     │    (clicks Approve)│\n   │                   │                     │◀── callback ──────│\n   │                   │                     │                   │\n   │                   │        ┌────────────┴────────────┐       │\n   │                   │        │  Agent poll work item   │       │\n   │                   │        │  per-NS: SA + Role +    │       │\n   │                   │        │  RoleBinding + token    │       │\n   │                   │        └─────────────────────────┘       │\n   │◀── terminal ──────│                     │                   │\n   │  (namespace tabs) │                     │                   │\n   │   (TTL expires)   │    Agent: delete SA + Role + RoleBinding │\n```\n\n### Access Lifecycle\n\n```\nPending ──▶ Approved ──▶ Active ──▶ Expired\n         ╲▶ Denied       │\n         (any state) ──▶ Revoked\n         Approved ──▶ Failed  (grant error — check agent logs)\n```\n\n---\n\n## 🏗️ Architecture\n\n```\n┌─────────────────────────────────────────────────┐\n│              Central Cluster                    │\n│                                                 │\n│   ┌──────────────────┐       ┌──────────────┐   │\n│   │     Web UI       │       │  PostgreSQL  │   │\n│   │  (FastAPI+HTMX)  │◀────▶│  (optional)  │   │\n│   └────────┬─────────┘       └──────────────┘   │\n│            │ TokenReview                          │\n└────────────┼────────────────────────────────────┘\n             │\n    ┌────────▼────────┐       ┌────────▼────────┐\n    │  Remote Agent   │       │  Remote Agent   │\n    │  on Cluster A   │  ...  │  on Cluster B   │\n    │  polls central  │       │  polls central  │\n    │  executes grant │       │  executes grant │\n    │  / cleanup work │       │  / cleanup work │\n    └─────────────────┘       └─────────────────┘\n```\n\nEach remote cluster runs a Janus agent that uses its own in-cluster ServiceAccount token. Central validates the agent via TokenReview — **no stored kubeconfigs, no shared secrets, no pre-registration steps.** Works with any Kubernetes cluster.\n\n---\n\n## 🛠️ Tech Stack\n\n| Layer | Technology |\n|-------|------------|\n| **Web UI** | Python · FastAPI · HTMX · xterm.js |\n| **Remote Agent** | Python · Kubernetes client library |\n| **Auth** | Authlib · OIDC/OAuth2 (Google, GitHub, Entra ID, Okta, GitLab, custom) |\n| **Agent Auth** | TokenReview — no shared secrets, no stored kubeconfigs |\n| **Packaging** | Helm |\n| **CI/CD** | GitHub Actions · Docker |\n\n---\n\n## 🚀 Quick Start\n\n**Prerequisites:** `kubectl` and `helm`.\n\n```bash\nhelm repo add k8s-janus https://infroware.github.io/k8s-janus\nhelm repo update\nhelm upgrade --install k8s-janus k8s-janus/k8s-janus \\\n  --namespace k8s-janus --create-namespace\n```\n\n### Register remote clusters via agent\n\nFor any remote cluster, deploy the Janus agent:\n\n```bash\nhelm upgrade --install janus-agent k8s-janus/k8s-janus \\\n  --namespace k8s-janus --create-namespace \\\n  --set remote.enabled=true \\\n  --set agent.enabled=true \\\n  --set agent.clusterName='prod-east' \\\n  --set agent.centralUrl='https://janus.example.com'\n```\n\nThe agent uses its own in-cluster ServiceAccount token to authenticate with central via TokenReview. No tokens in Git, no shared secrets, no manual registration steps.\n\n### Configure RBAC for agent clusters\n\nCreate the `janus-pod-exec` ClusterRole on each remote cluster so agents can create per-request roles with exec access:\n\n```yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: janus-pod-exec\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"pods/exec\"]\n    verbs: [\"create\", \"get\"]\n```\n\n(This ClusterRole is created automatically when using `kubectl apply -f` from the Helm chart or when ArgoCD syncs the remote Application.)\n\n---\n\n## 🔐 Authentication\n\nBy default Janus trusts the `X-Forwarded-Email` header from an upstream proxy. For a self-contained setup, enable native OIDC:\n\n```yaml\noidc:\n  enabled: true\n  provider: google          # google | github | entra | okta | gitlab | custom\n  clientId: \"your-client-id\"\n  clientSecret: \"your-client-secret\"\n  allowedDomains: [\"your-org.com\"]\n```\n\n| Provider | `provider` value | Extra config |\n|----------|-----------------|--------------|\n| Google | `google` | — |\n| GitHub | `github` | — |\n| Microsoft Entra ID | `entra` | `tenantId: \"your-tenant-id\"` |\n| Okta | `okta` | `issuerUrl: \"https://your-org.okta.com\"` |\n| GitLab | `gitlab` | — |\n| Any OIDC provider | `custom` | `issuerUrl: \"https://idp.example.com\"` |\n\n`clientSecret` can be supplied inline, via `existingSecret`, or synced from a secret store via `externalSecrets.enabled: true`.\n\n---\n\n## 🗄️ PostgreSQL Backend\n\nBy default Janus uses SQLite (ephemeral — data lost on pod restart). For persistent history:\n\n```yaml\npostgresql:\n  enabled: true\n  host: \"postgres-host\"\n  database: \"k8s-janus\"\n  username: \"k8s-janus\"\n```\n\nThe password must exist in a Secret named `k8s-janus-postgresql` with key `password`:\n\n```bash\nkubectl create secret generic k8s-janus-postgresql \\\n  --namespace k8s-janus \\\n  --from-literal=password=your-db-password\n```\n\nOr sync it automatically via External Secrets Operator:\n\n```yaml\nexternalSecrets:\n  enabled: true\n  secretStore: \"my-cluster-secret-store\"\npostgresql:\n  secretKey: \"K8S-JANUS-DB-PASSWORD\"\n```\n\n---\n\n## ⚙️ Configuration Reference\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `janus.defaultTtlSeconds` | `3600` | Default access duration in the request form (1h) |\n| `janus.maxTtlSeconds` | `28800` | Hard cap engineers cannot exceed (8h) |\n| `janus.approvalTtlOptions` | `[3600,7200,14400,28800]` | TTL override choices in the admin approval dropdown |\n| `janus.crdRetentionSeconds` | `86400` | Delete Expired/Denied/Revoked CRDs after N seconds |\n| `janus.pendingExpirySeconds` | `0` | Auto-deny Pending requests after N seconds (0 = disabled) |\n| `janus.idleTimeoutSeconds` | `0` | Terminate idle terminal sessions after N seconds (0 = disabled) |\n| `janus.displayTimezone` | `UTC` | IANA timezone for timestamps in the UI |\n| `janus.adminEmails` | `[]` | Emails with approve/deny/revoke privileges |\n| `janus.excludedNamespaces` | `[k8s-janus, kube-system, …]` | Namespaces hidden from the request form |\n|| `postgresql.enabled` | `false` | Persistent DB — survives pod restarts |\n| `networkPolicy.enabled` | `true` | Restrict egress to K8s API + DNS only |\n| `remote.enabled` | `false` | Deploy only RBAC on a target cluster (no controller/webui) |\n| `agent.enabled` | `false` | Run remote agent that polls central for work |\n| `agent.clusterName` | — | Unique name for this cluster in Janus |\n| `agent.centralUrl` | — | URL of the central Janus web UI (e.g. `https://janus.example.com`) |\n| `tokenRefresh.enabled` | `false` | Optional validation CronJob for static-kubeconfig remote clusters; not needed for agent clusters |\n| `oidc.enabled` | `false` | Enable native OIDC/OAuth2 SSO |\n| `webui.authEnabled` | `false` | Trust `X-Forwarded-Email` header from upstream proxy |\n| `replicaCount` | `1` | \u003e1 also creates a PodDisruptionBudget |\n\n---\n\n## 🛡️ Security Model\n\n| Control | Implementation |\n|---------|---------------|\n| 🔑 Token isolation | Token in K8s Secret — never in logs or visible to central |\n| 🎯 Least privilege | Scoped Role per namespace, not ClusterRoleBinding |\n| 🚫 No shared secrets | Agent auth via TokenReview — central validates the agent's own SA token |\n| 🎯 Per-request roles | Each access request creates a unique Role with exec permission, never a shared one |\n| 👤 Non-root | `runAsUser: 1000`, `runAsNonRoot: true` |\n| 📁 Immutable FS | `readOnlyRootFilesystem: true` |\n| 🚫 No capabilities | `capabilities.drop: [ALL]` |\n| 🌐 Network isolation | NetworkPolicy: egress restricted to K8s API (443/6443) and DNS only |\n| ⏰ TTL enforcement | Min 10 min · Max 8 hours · Enforced server-side |\n| 🔏 Signed chart | Helm chart signed with GPG — verify with `helm install --verify` |\n| 📋 Full audit trail | Every session open, close, command, idle timeout, and revocation logged |\n| 🔒 Pod Security Standards | `pod-security.kubernetes.io/enforce: restricted` on the `k8s-janus` namespace |\n\n---\n\n## 📋 Observability\n\nJanus logs every lifecycle event — startup, request state transitions, credential provisioning, cleanup, and WebSocket sessions.\n\nTerminal pod/log/event API failures also emit structured diagnostics: action, cluster, namespace, pod, error code, retryability, and exception detail. The terminal UI shows the same error code and namespace context so TLS, RBAC, token-readiness, and connectivity issues are easier to debug.\n\n```\n[INFO] ✅ k8s-janus agent connected — cluster=prod-eu central=https://janus.ops.example.com\n[INFO] 📥 New AccessRequest [alice-debug-api] from alice@example.com → cluster=prod-eu ns=['default','payments']\n[INFO] ✅ [alice-debug-api] Approved by admin@local — TTL=3600s\n[INFO] 🧹 [alice-debug-api] cleanup succeeded — all credentials removed\n```\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\nApache 2.0 License · Built with ☕ by [infroware](https://github.com/infroware)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfroware%2Fk8s-janus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finfroware%2Fk8s-janus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfroware%2Fk8s-janus/lists"}