InfraEye InfraEye
Documentation
Home GitHub

InfraEye Documentation

Everything you need to deploy, configure, and operate InfraEye — the agentless observability and self-healing command center for modern DevOps teams.

Introduction

InfraEye is an enterprise-grade, agentless observability platform. It provides a unified "Command Center" for your entire infrastructure — from bare-metal Linux servers to complex Kubernetes clusters — featuring real-time telemetry, AI-driven diagnostics, and a proactive self-healing engine.

Instead of installing agents on every target, InfraEye's Go backend reaches out over SSH and the Kubernetes API to collect metrics, stream logs, and execute remediation — so target machines stay untouched.

New to InfraEye? Start with Docker Compose deployment for the fastest path to a running dashboard, or jump to Architecture to understand how it works first.

Key Modules

ModuleDescriptionStatus
Infrastructure NavigatorUnified view of Linux/macOS/Windows servers with real-time CPU, memory, disk & network telemetry.Production
Kubernetes ExplorerLens-style resource explorer for pods, deployments, CRDs, and the Gateway API.Production
Resource CatalogManage databases and internal services via a secure gateway, without exposing SSH.Production
Netra AI AssistantLLM-powered infrastructure consulting, log analysis, and remediation suggestions.Beta
Self-Healing EngineXML-defined alert rules that trigger automated SSH remediation commands.Production
OIDC / SSO AuthenticationKeycloak, Auth0, Okta, and Azure AD integration for enterprise single sign-on.Production
MCP SidecarModel Context Protocol integration for AI-driven cluster troubleshooting.Experimental
SSH TerminalFull browser-based xterm.js terminal over secure SSH tunnels.Production
Infra MapLive topology of servers, clusters, and resources, with optional network discovery.Beta
Netra AgentMulti-step, goal-driven automation where every SSH or cluster action waits for human approval.Beta
Security AuditKernel CVE, server hardening, cluster, resource, code (SAST/SCA), and DAST scanners.Beta
Developer Hub20+ built-in DevOps utilities — CIDR, JWT, TLS, YAML/JSON, cron, Ingress → Gateway API, and more.Beta
Desktop AppNative macOS (Apple Silicon), Linux, and Windows app (Wails) — SQLite-backed, no Docker required.Beta
Infrastructure-as-Code SyncSync server lists and alert rules from a Git repo — non-destructive, credentials never read from Git.Beta

Tech Stack

  • Backend: Go 1.22+, GORM, Gin — a single binary orchestrating SSH pools, WebSockets, and the Kubernetes client-go SDK.
  • Frontend: React 19, TypeScript 5.9, served via Nginx in production.
  • Data layer: PostgreSQL (persistent state), Redis (pub/sub & caching).
  • AI layer: MCP (Model Context Protocol) sidecar for cluster-aware tool calling, with pluggable LLM providers (Claude, Gemini, Mistral, DeepSeek, OpenRouter).
  • Packaging: Docker images published to ghcr.io/mnshchtri/infra-eye; Kubernetes manifests managed via Kustomize.

Architecture — The Distributed Bridge

Unlike traditional monitoring tools that require an agent on every target server, InfraEye uses an Agentless Bridge pattern:

  • Backend (Go): Acts as the central orchestrator and manages a pool of SSH connections.
  • Telemetry collectors: Every 10–60 seconds (configurable via METRICS_INTERVAL), the backend executes lightweight commands (top, df, free, ifconfig) over SSH.
  • Real-time stream: Results are parsed into JSON and pushed to the React frontend via WebSockets.
  • No persistence on targets: Target servers remain clean — no open ports beyond SSH, and no extra processes running.

Request Flow

architecture-diagram.svg
InfraEye architecture diagram: a DevOps Engineer connects over HTTPS/WSS to the React Dashboard, which calls the Go API Gateway over API/RPC. The Go API Gateway reaches Target Servers over SSH (port 22), Kubernetes Clusters over the API (port 6443), the MCP Sidecar over JSON-RPC/SSE, PostgreSQL via GORM, and Redis via Pub/Sub. The MCP Sidecar also calls Kubernetes Clusters directly to run tools.

The React dashboard talks to the Go API over HTTPS and WebSockets. The API is the only component that reaches into your infrastructure — over SSH for servers and the Kubernetes API for clusters — and it persists state to PostgreSQL while using Redis for pub/sub and caching. The MCP sidecar gives Netra AI structured, tool-based access to live cluster state.

Ports & Services

ServiceDocker ComposeKubernetesPurpose
Dashboard / API:8080:30080 (NodePort)React frontend + Go REST/WS API
MCP Sidecar127.0.0.1:8090:8090 (cluster-internal)Kubernetes-aware AI tool calls
PostgreSQL5432ClusterIPPersistent application data
Redis127.0.0.1:6379ClusterIPPub/sub & caching

Deployment: Docker Compose

The fastest way to run InfraEye on a single host. The installer clones the repo, generates a .env, and brings up the full stack with Docker Compose.

install.sh
# One-line install — requires Docker & Git curl -fsSL https://raw.githubusercontent.com/mnshchtri/infra-eye/main/install.sh | bash

What gets deployed

  • app — the InfraEye Go backend, serving the built React frontend and REST/WebSocket API on :8080
  • postgres — PostgreSQL 16, persisted to a named Docker volume
  • redis — Redis 7 for pub/sub and caching
  • mcp-server — the Kubernetes MCP sidecar, bound to 127.0.0.1:8090
  • mcp-init — a one-shot init container that seeds an empty kubeconfig before the sidecar starts
Prerequisites: Docker Engine 24+, Docker Compose v2.20+, Git, and port 8080 free on the host.

After install, open http://<your-ip>:8080 and log in with the default credentials.

Deployment: Kubernetes

Recommended for production. Deploys InfraEye, PostgreSQL, Redis, and the MCP sidecar into the infra-eye namespace via Kustomize, exposed with a direct NodePort.

install-k8s.sh
# Ensure KUBECONFIG is set, e.g. export KUBECONFIG=/etc/rancher/k3s/k3s.yaml curl -fsSL https://raw.githubusercontent.com/mnshchtri/infra-eye/main/install-k8s.sh | bash

The installer checks dependencies, auto-detects your kubeconfig (including k3s), interactively prompts for AI provider keys (press Enter to skip any), generates a gitignored k8s/secret.yaml, and applies everything with kubectl apply -k k8s/. It works with k3s, kubeadm, and managed clusters (EKS, GKE, AKS).

Useful kubectl commands

ActionCommand
Statuskubectl get pods -n infra-eye
Logskubectl logs -n infra-eye deploy/infra-eye -f
Updatekubectl rollout restart deploy/infra-eye -n infra-eye
Uninstallkubectl delete namespace infra-eye

Once the rollout completes, InfraEye is reachable at http://<node-ip>:30080.

Deployment: Desktop App Beta

A fully self-contained native app for macOS, Linux, and Windows — no Docker, no Postgres, no Redis. The same Go backend and React frontend run embedded inside a Wails desktop shell, backed by a local SQLite database instead of Postgres. Intended for single-user local use: a laptop you carry between sites, or a workstation you'd rather not run Docker on.

Prebuilt packages are published as GitHub Release assets. Each OS is built natively on its own CI runner — Wails' native webview layer (WebKit / WebKitGTK) can't be cross-compiled from a single machine, so there's one build job per platform.

Install (macOS, via Homebrew)

homebrew
brew tap mnshchtri/infra-eye brew install --cask infra-eye

Download

PlatformPackageNotes
macOSInfraEye-macOS-arm64.dmgApple Silicon, drag-to-Applications
LinuxInfraEye-Linux.debx86_64, Debian/Ubuntu (Depends: libgtk-3-0, libwebkit2gtk-4.1-0)
Linux (Arch)InfraEye-Linux.pkg.tar.zstx86_64, same binary as the .deb (depends: gtk3, webkit2gtk-4.1) — pacman -U InfraEye-Linux.pkg.tar.zst
WindowsInfraEye-Windows-amd64.exex86_64, NSIS installer (bundles the WebView2 runtime)

Grab the latest build from the GitHub Releases page, or see building from source below.

Intel Mac builds aren't published at this time — the CI workflow targets macOS (Apple Silicon), Linux (amd64), and Windows (amd64). See building from source if you need an Intel Mac build.

macOS: "InfraEye.app is damaged"

The macOS build is ad-hoc signed — its signature is valid, but it isn't signed with an Apple Developer ID and carries no notarization ticket, so Gatekeeper rejects it. macOS flags anything downloaded from the internet with a com.apple.quarantine attribute, and a quarantined app that fails Gatekeeper is reported with the misleading message "InfraEye.app is damaged and can't be opened. You should move it to the Trash." The app isn't damaged and the download isn't corrupt.

Installing with Homebrew avoids this entirely. The cask clears the quarantine attribute on the app it installs, so brew install --cask infra-eye gives you an app that opens on first launch with no extra steps.

If you downloaded the .dmg directly instead, drag InfraEye.app to Applications as usual, then clear the attribute yourself:

terminal
xattr -cr /Applications/InfraEye.app

The app opens normally after that, and the step is only needed once per install. On macOS Ventura and later you can alternatively open System Settings → Privacy & Security and click Open Anyway after the first blocked launch.

What's different from the server install

  • SQLite, not Postgres — a single database file, no separate DB process.
  • No Redis — it was only ever used to probe a user-added Redis resource, never as internal app infrastructure, so nothing is lost.
  • Single-user, local-only by default — the backend binds to 127.0.0.1 only; there's no reverse proxy or public exposure.
  • Kubernetes features are optional — the MCP sidecar (kubernetes-mcp-server) is spawned automatically if found on your PATH, and Kubernetes cluster management is disabled gracefully if it isn't. Linux-server (SSH) monitoring works either way.
  • OIDC/SSO is off by default — it can still be enabled for power users who want to register http://127.0.0.1:8073/api/auth/oidc/callback with their own identity provider.

Local data

OSApp-data directory
macOS~/Library/Application Support/InfraEye
Linux~/.config/InfraEye (or $XDG_CONFIG_HOME/InfraEye)
Windows%AppData%\InfraEye

This directory holds infraeye.db (SQLite, WAL mode), a generated jwt.secret (created once on first launch, persisted across restarts), ssh_known_hosts (pinned SSH host keys — see host-key verification), and an mcp/kubeconfig used only when the MCP sidecar is running.

In-app updates

The desktop build updates itself. The version button at the bottom of the sidebar checks GitHub Releases for a newer desktop-v* tag and, when one exists, downloads and installs the package for your platform — the .dmg on macOS (swapped into place, with the old bundle kept as a backup until the copy succeeds), the .deb or .pkg.tar.zst on Linux (installed via pkexec, so you'll see your desktop's native password prompt), or the NSIS installer on Windows. The app then relaunches on the new version. Downloads are only accepted from GitHub release-asset URLs over HTTPS.

Self-update exists only in the desktop app. Docker Compose and Kubernetes installs never replace their own binary — update those with reload.sh or kubectl rollout restart.

Building from source

build
# Requires Go 1.25+, Node.js 20+, and the Wails v2 CLI go install github.com/wailsapp/wails/v2/cmd/wails@latest # Clone and build — produces a native .app / .exe / binary via Wails git clone https://github.com/mnshchtri/infra-eye && cd infra-eye make desktop-build

This builds the frontend with VITE_API_URL pointed at the desktop backend's fixed local port, copies the build output into backend/cmd/desktop/frontenddist, and runs wails build. The output lands in backend/cmd/desktop/build/bin/.

Linux builds additionally need libgtk-3-dev and libwebkit2gtk-4.1-dev (or 4.0-dev on older distros) installed before running wails build. Windows builds need NSIS (makensis on your PATH) if you want a packaged installer via wails build -nsis — without it, wails build still produces a working .exe, just not the installer.

Developer Setup (Hybrid Mode)

For active development, run the databases in Docker and the app natively for fast rebuilds.

Makefile
# 1. Start core infrastructure (Postgres, Redis in Docker) make infra # 2. Install dependencies make backend-install make frontend-install # 3. Run dev servers — Terminal 1: backend, Terminal 2: frontend make backend make frontend # Or run both natively in one shot make dev-local # Build production binaries make build

Requires Go 1.22+ and Node.js. The backend listens on :8080, the frontend Vite dev server on :5173 (proxying API calls to the backend).

Hot Reload & Updates

When you pull new commits or edit your host .env file after installation, apply changes without a manual teardown:

terminal
cd ~/infra-eye chmod +x reload.sh ./reload.sh

The script pulls the latest commits from main, checks for host-level .env changes, triggers docker compose up -d --build --force-recreate, and purges dangling Docker images to reclaim disk space.

Persistence

These survive restarts, reloads, and upgrades on the host:

  • Database: /var/lib/postgresql/data, on a named Docker volume (Compose) or a PersistentVolumeClaim (Kubernetes).
  • Kubeconfig: passed to the MCP sidecar through a local bind mount (Compose) or an emptyDir (Kubernetes), so the sidecar can reach the clusters you've added.
  • SSH host keys: pinned in /data/ssh_known_hosts inside the container (override with SSH_KNOWN_HOSTS_PATH).

Environment Variables

Core variables read by the Go backend (backend/.env.example):

~/infra-eye/.env
# Server PORT=8080 ENV=development # Database DB_DSN=postgresql://infraeye:infraeye123@localhost:5432/infraeye?sslmode=disable # Redis REDIS_ADDR=localhost:6379 # JWT JWT_SECRET=change-me-in-production-super-secret-key # AI providers — optional, leave empty to use mock responses DEEPSEEK_API_KEY= GEMINI_API_KEY= MISTRAL_API_KEY= OPENAI_API_KEY= # Local LLM — self-hosted, OpenAI-compatible endpoint (Ollama, LM Studio, llama.cpp server, vLLM). No API key needed. LOCAL_LLM_URL= LOCAL_LLM_MODEL=llama3.2 # Notifications GOOGLE_CHAT_WEBHOOK_URL= SLACK_WEBHOOK_URL= # Metrics collection interval in seconds METRICS_INTERVAL=30 # Log streaming max lines LOG_MAX_LINES=500
VariableDescription
DB_DSNPostgreSQL connection string
REDIS_ADDRRedis host:port
JWT_SECRETSigning key for internal auth tokens — set a strong random value in production
DEEPSEEK_API_KEY / GEMINI_API_KEY / MISTRAL_API_KEY / OPENAI_API_KEYOptional per-provider keys that unlock Netra AI diagnostics
LOCAL_LLM_URL / LOCAL_LLM_MODELOptional self-hosted, OpenAI-compatible endpoint (Ollama, LM Studio, llama.cpp server, vLLM) for Netra — no API key required. Each user can also override this from Settings.
SLACK_WEBHOOK_URL / GOOGLE_CHAT_WEBHOOK_URLWebhook targets for the Alert Engine
METRICS_INTERVALTelemetry polling interval in seconds (10–60 typical)
LOG_MAX_LINESMaximum lines returned per log stream (default 500)

Additional variables

Not in .env.example, but read by the backend when set:

VariableDefaultDescription
ADMIN_PASSWORDinfra123Password for the seeded admin user. Enforced on every backend start — see Default Credentials.
OPENROUTER_API_KEY—Server-wide OpenRouter key for Netra and the Netra Agent. Users can override it with their own key in Settings.
DB_DRIVERpostgrespostgres or sqlite. The desktop app sets sqlite automatically.
MCP_SERVER_URLhttp://localhost:8090Where the backend reaches the MCP sidecar.
MCP_SHARED_PATH./shared_mcpDirectory the backend writes the sidecar's merged kubeconfig into.
MCP_HOST_IPS—Comma-separated cluster API-server IPs (e.g. 192.168.1.87,192.168.1.78) to rewrite to host.docker.internal in the sidecar's kubeconfig when running in Docker. Use it for LAN addresses the container can't route to directly (common with OrbStack). localhost/127.0.0.1 are always rewritten.
RESOURCE_GATEWAY_URL / RESOURCE_GATEWAY_TOKEN—Gateway used by the Resource Catalog to test and reach internal services.
SSH_KNOWN_HOSTS_PATH/data/ssh_known_hosts (Docker)File where pinned SSH host keys are stored.
GITSYNC_WORKDIR./shared_gitsyncLocal clone location for IaC Sync when running natively. Inside Docker the path is always /shared_gitsync/repo.
ZAP_API_URL / ZAP_API_KEY—A running OWASP ZAP daemon for DAST scans. Without it, InfraEye runs ZAP on demand through Docker.

For OIDC/SSO-specific variables, see OIDC / SSO Integration below.

Infrastructure Navigator Production

The primary dashboard for server management, covering bare-metal Linux, macOS, and Windows hosts via secure, agentless SSH.

  • Telemetry: Real-time CPU, memory, disk, and network metrics visualized with high-performance Recharts; CPU usage can be broken down per core.
  • Log tailer: Uses tail -f over SSH to stream any log file — syslog, auth, or custom app logs — directly to your browser.
  • Smart tags: Group servers by environment (prod, staging), location, or role.
  • Secure authentication: Upload or paste SSH private keys (or use interactive passwords) directly through the UI — no host-filesystem mounts required.
  • SSH terminal: A full xterm.js PTY terminal in the browser, over the same pooled SSH connection.
  • Folders: Organize servers and resources into folders; IaC Sync can create them for you.

Host-key verification

SSH host keys are verified trust-on-first-use (TOFU). The first connection to a host records its key; every later connection must present the same key. If a host's key changes — a machine-in-the-middle, or a server that was rebuilt — InfraEye refuses the connection instead of silently accepting it. Keys are stored in /data/ssh_known_hosts (Docker), ./data/ssh_known_hosts (native dev), or the desktop app-data directory, and SSH_KNOWN_HOSTS_PATH overrides the location. If you rebuilt a server on purpose, remove its line from that file to re-pin the new key.

SSH Terminal Production

A full interactive shell on any managed server, in the browser. Open a server from Servers and switch to its Terminal tab. Anything you can do in a normal SSH session works here, including vim, htop, tmux, and colored output.

How it works

  1. The browser renders the terminal with xterm.js and connects to the backend over an authenticated WebSocket (/ws/servers/:id/terminal).
  2. The backend opens a new session on that server's pooled SSH connection, using the same key or password you saved for telemetry. No separate credentials, and no port besides 22.
  3. It requests an xterm-256color PTY and relays keystrokes and output in both directions until you close the tab.

If the pooled connection has gone stale (for example after a server reboot), the backend drops it and reconnects once before giving up. Host keys are checked the same way as every other SSH connection. See host-key verification.

Access control

RuleDetail
RolesOnly admin and devops can open a terminal. trainee and intern don't see the tab.
RevocationDeactivating a user closes their open terminals right away. A background sweep also closes, within 30 seconds, any socket whose account became invalid some other way (an OIDC role sync, a direct DB change, a deleted row).
ClustersA cluster added only by kubeconfig has no SSH host, so it has no server terminal. Use the Kubernetes Explorer's pod and node shells instead.
The terminal runs as the SSH user saved for that server. To give someone a shell with fewer privileges, create a restricted OS account with the OS account manager and add the server again with that user.

Kubernetes Explorer Production

A browser-based, Lens-style alternative to kubectl for every connected cluster.

  • Full resource coverage: Pods, workloads, config, storage, RBAC, CRDs, and the Gateway API (Gateways, HTTPRoutes).
  • Resource maps: Visualize relationships between Services → Deployments → Pods.
  • Event streaming: Real-time cluster events catch CrashLoopBackOff or ImagePullBackOff instantly.
  • Actions: Create resources from templates, scale and rollout-restart workloads, and edit live YAML — all from one pane.
  • RBAC-conscious: Operates using the kubeconfig you provide in settings.
  • Pod & node shells: Exec into any container, or open a root shell on a node. Node shells work like kubectl debug node/<name>: a short-lived privileged pod mounts the host filesystem at /host, runs chroot /host sh, and is deleted when the session ends.
  • Port forwarding: Start, list, and stop native port-forward sessions to pods and services from the UI.

Read-only kubeconfigs

Admins and devops users can generate a kubeconfig to hand to someone who needs to look at a cluster but not change it. InfraEye creates a dedicated ServiceAccount in a read-only namespace, binds it to a role that can get/list/watch everything (including pod logs), and returns a ready-to-use kubeconfig backed by a token that lasts up to 365 days. It can't create, update, patch, or delete anything, and it can't exec or port-forward. Existing kubeconfigs are listed per cluster and can be revoked individually, or all at once with kubectl delete ns read-only.

These kubeconfigs can read Secrets cluster-wide, including ServiceAccount tokens that belong to more privileged accounts. That's intentional, since the holder is meant to see the whole cluster, but it's a real escalation path. Only give them to people you'd trust with those credentials.

Resource Catalog & Secure Gateway Production

A dedicated Resources page for managing databases and other internal services without exposing them directly.

  • First-class entries: Add Postgres, MySQL, Redis, Kafka, MinIO, HTTP/S, or generic TCP services with host, port, and metadata.
  • Gateway connectivity: Resources are tested and accessed through a configured gateway URL and token — no need to expose SSH directly on each target.
  • Credential support: Store usernames, passwords, and API secrets safely; they're used only when testing or validating connectivity.
  • Status monitoring: Watch resource status changes and verify reachability from the backend without opening additional public ports.

Data browsers

Each resource's detail page includes a browser for the data inside it:

TypeWhat you can do
Postgres / MySQLBrowse schemas, tables, and columns (type, nullability, default); page through rows; insert, edit, and delete individual rows; run ad-hoc SQL queries.
RedisScan keys by pattern; view a key's type, value, and TTL; edit or delete keys (string, hash, list, set, and sorted set).
KafkaList topics with per-partition offsets, tail recent messages, and inspect consumer groups and their lag. Read-only.

Viewing rows, running queries, and editing data require the admin or devops role. Each resource also has its own security scan.

Networking Inspector Production

OS-native visibility into listening ports, system services, and interfaces — built for Linux (ss/systemctl), macOS (lsof/launchctl), and Windows (PowerShell). See exactly what's exposed on every host.

VPN & Networking bridge

When monitoring remote servers from cloud environments where private IPs are unreachable, InfraEye can auto-provision secure connectivity:

  • Tailscale & ZeroTier: Auto-generates installation commands using your Auth Key/Network ID, letting target servers join your mesh network in one step.
  • OpenVPN: Upload or paste a .ovpn config; InfraEye generates a base64-encoded shell script to auto-provision and connect remote Linux servers instantly.

Infra Map Beta

A live topology diagram of every server, cluster, and cataloged resource you can see, and the connections between them. Open it from Infrastructure → Infra Map.

What's on the map

NodeRepresents
CoreThe InfraEye backend itself, the hub everything is managed from
ServerA managed Linux/macOS/Windows host
ClusterA connected Kubernetes cluster
K8s nodeA cluster node (live scan only)
ResourceA Resource Catalog entry (database, Kafka, MinIO, HTTP…)
DiscoveredA device found by the nmap sweep that InfraEye doesn't manage (dotted outline)

Edges show management links (core → server/cluster), hosted-on placement (a resource whose host matches a server), network traffic (observed TCP connections), and membership (a node belonging to a cluster or segment).

Three levels of detail

  1. Static (default): Built entirely from the database and DNS, so it's instant and never touches your hosts. Servers are grouped into network segments by address. Interface CIDRs recorded by an earlier live scan are reused, so real segments appear even on a normal page load. Private IPs with no interface data go into an assumed /24, and the map labels those segments as a guess.
  2. Live scan: SSHes into each reachable server to read its established TCP connections and real interface CIDRs, and lists every connected cluster's nodes. Segments then reflect real CIDRs, and the map shows which server talks to which. This works on busybox/Alpine as well as full distros, and on macOS and Windows.
  3. Discovery: Adds an nmap ping sweep, run from one server per real segment, to find devices InfraEye doesn't manage yet. It needs nmap installed and only runs when you turn it on, because it actively probes the network.
Only run discovery on networks you're allowed to scan. Some IDS/IPS tools flag ping sweeps.

Controls

  • Zone by network or folder: Group machines by the segment they sit on, or by their InfraEye folder.
  • Find a server: Highlights a node and its direct connections, and expands collapsed zones that contain a match.
  • Zoom / fit to view / refresh, and click a node for details.
  • Export: Download the blueprint as a PNG or SVG image, or the raw topology as JSON for your own tools.
  • Scan issues: If a live scan or discovery fails on some hosts, an issues badge lists every failure with its original error.

Connections and discovered devices are never cached. Each live refresh reads them again, and only the interface CIDRs are kept for the static view. The same data is available from GET /api/topology, ?live=true, and ?live=true&discover=true.

Storage Explorer Production

Answers what is consuming this disk, not just how full it is. Every mount, every heavy folder, every oversized file — read live off the host over SSH, with nothing installed on it.

  • Mount inventory: capacity, usage and inode pressure per filesystem, with mount options and the backing block devices — df/lsblk on Linux, diskutil on macOS, PowerShell on Windows.
  • Path analyzer: point it at any path and it walks the tree with du/find, returning the largest folders and files beneath it. Click a folder to drill deeper; breadcrumbs walk back up. Runs only when you ask — a scan of / on a busy host is real disk I/O, not something to fire on a tab click.
  • Consumption by category: folders and files are classified into the kind of data they hold — Logs, Containers, Databases, Cache, Dependencies, VM images, Backups — and rolled up, so "48% full" becomes "half of it is container images".
  • Honest totals: pooled filesystems (APFS containers, btrfs subvolumes) report the pool's full size from every volume they contain. InfraEye groups them so one 245 GB disk is counted once, not six times.

Kubernetes clusters

A cluster has no single disk to walk, so clusters get the objects that actually hold capacity: StorageClasses, PersistentVolumes, and PersistentVolumeClaims resolved to the pods holding them — plus per-node ephemeral capacity and live DiskPressure status. Provisioned capacity is rolled up by storage class and by namespace.

Live Log Streaming Production

Selectively tail the right stream — journal, syslog, auth, or kernel on Linux; the unified log on macOS; Event Logs on Windows. For clusters, stream Kubernetes events and warnings live. All log output is searchable and persisted.

Self-Healing Engine Production

Define "If-Then" automation logic for your infrastructure — the most powerful module in InfraEye. Alert conditions trigger automated remediation: restart services, scale pods, and recover from failures automatically.

alert-rules.xml
<AlertRules> <Rule name="Auto-Restart Nginx" serverId="1" enabled="true"> <Condition type="cpu" op="gt" value="85" /> <Action type="ssh_command">sudo systemctl restart nginx</Action> </Rule> </AlertRules>

Sync Bridge: paste/export rules as XML via the UI's Import/Export tab, or drive both alert rules and the server list declaratively from a real Git repository — see Infrastructure-as-Code Sync.

Netra AI Assistant Beta

Netra is your context-aware infrastructure co-pilot. It natively queries logs, metrics, and Kubernetes state to diagnose incidents in plain language — then hands you the exact command to fix them.

  • Contextual awareness: When you ask "Why is my server slow?", Netra automatically pulls the latest metrics and logs for that server before answering.
  • Remediation suggestions: Netra doesn't just explain errors — it provides the exact shell command to fix them, with one-click apply.
  • Model choice: Powered by your choice of Claude, Gemini, Mistral, DeepSeek, or OpenRouter, with per-user API keys — or a self-hosted Local LLM (Ollama, LM Studio, llama.cpp server, vLLM), no API key required.

For multi-step work where Netra runs the commands itself, one approved step at a time, see Netra Agent.

Netra Agent Beta

The Agent is Netra with hands, and you keep a hand on the brake. Give it a goal and a target server, e.g. "find out why nginx keeps restarting and fix it." It works toward that goal in a loop. Each turn, the model proposes one action, either a shell command over SSH or a Kubernetes tool call through the MCP sidecar. The action runs only after you approve it, and the real output goes back to the model. This repeats until the model gives its final answer.

Starting a run

  1. Open Operations → Agent and click New Agent Run.
  2. Write the goal in plain language. Be specific about the result you want, e.g. "disk on /var is at 95% — find what's growing and free space safely."
  3. Pick the target server. The run is locked to that one server, and the model can't act on any other.
  4. Choose a provider: Claude (default), OpenRouter, Mistral, or a Local LLM. Optionally override the model.
  5. Click Launch Agent. The run appears as a live flow graph (goal → thinking → steps → result) that updates over a WebSocket as it progresses.

Reviewing steps

When the model proposes an action, the run pauses with Needs your approval and the pending step opens in the inspector, showing the exact command or tool call and its arguments.

Your choiceWhat happens
ApproveThe step runs (SSH commands time out after 30 seconds). Its stdout, stderr, and exit error are recorded and passed to the model exactly as returned.
RejectNothing runs. The model is told the step was rejected and has to try another approach.
Cancel runStops the whole run. No more steps are proposed.

Lifecycle

ObjectStates
Runrunning → awaiting_approval → … → completed | failed | cancelled
Steppending_approval → approved → executed | failed, or rejected

Providers & keys

API keys follow the same rule as Netra: your own key from Settings if you've set one, otherwise the server-wide default (OPENROUTER_API_KEY, MISTRAL_API_KEY, LOCAL_LLM_URL…). The model must support tool calling. For local models that means one that handles OpenAI-style function calls, such as Llama 3.x or Qwen 2.5.

Safety model

  • Nothing runs without a human: Every action needs an explicit approval, and none are auto-approved.
  • Single-server scope: Tool calls must target the run's server. Kubernetes calls use that server's cluster context.
  • Verbatim results: Output and errors are never summarized before the model or you see them, so a failing command looks like a failure.
  • Roles: Only admin and devops can create, approve, or view runs.
  • Audit trail: Every run is kept with its goal, every proposed step, your decision, and the real output, until you delete it.
Kubernetes tools are only offered when the MCP sidecar is reachable. Without it, the Agent can still work on the server over SSH.

MCP Sidecar Experimental

The MCP sidecar gives Netra and the Netra Agent structured, tool-based access to live Kubernetes state. It runs the open-source kubernetes-mcp-server, which speaks the Model Context Protocol (JSON-RPC 2.0 over HTTP/SSE) on port 8090. The model doesn't have to guess what kubectl would print: it calls a tool such as pods_list and gets the real answer back.

The sidecar only powers AI cluster access. The Kubernetes Explorer talks to clusters directly through client-go and keeps working without it.

How it's deployed

InstallHow the sidecar runsWhich clusters it sees
Docker Composeinfra-eye-mcp container, bound to 127.0.0.1:8090. A one-shot mcp-init container creates an empty kubeconfig first, so the sidecar can start before the app.Every cluster you've added. The backend merges their kubeconfigs into ./shared_mcp/kubeconfig, which is mounted read-only into the sidecar.
KubernetesA second container in the infra-eye pod (64–256 Mi memory), started with --cluster-provider in-cluster.The cluster InfraEye is running in, through the infra-eye-sa ServiceAccount (bound to cluster-admin).
Desktop appStarted automatically if kubernetes-mcp-server is on your PATH; skipped with a warning if it isn't.Every cluster you've added, through mcp/kubeconfig in the app-data directory.

Kubeconfig sync

The backend rebuilds the merged kubeconfig at startup, whenever a cluster is added, edited, or deleted, and before status checks and kubectl calls. Each cluster's contexts are prefixed with server-<id>- so names from different kubeconfigs can't collide. When a tool call targets a specific server, InfraEye picks that server's context automatically. When the backend runs in Docker, API-server addresses of localhost/127.0.0.1 are rewritten to host.docker.internal, and so are any IPs listed in MCP_HOST_IPS.

Tools Netra uses

ToolPurposeType
pods_listList pods, optionally in one namespaceRead
pods_logFetch pod logs (with optional tail)Read
events_listCluster eventsRead
resources_get / resources_listRead any resource by apiVersion and kindRead
namespaces_listList namespacesRead
pods_deleteDelete a stuck podMutating
pods_execRun a command in a podMutating
resources_create_or_updateApply YAMLMutating
resources_scaleScale a workloadMutating

In the AI Assistant, Netra asks for a tool with a fenced mcp block, and you run it with the Execute button. The output goes back to Netra automatically. Netra is told to use read tools freely, but before any mutating tool it has to explain why, what it affects, and how to roll back. The Netra Agent gets the sidecar's full tool list and needs approval for every call.

netra → mcp tool call
{"tool": "pods_list", "args": {"namespace": "kube-system"}}

API endpoints

EndpointPurpose
GET /api/mcp/statusRe-syncs the kubeconfig, then reports available, url, and status_code.
GET /api/mcp/toolsLists the tools the sidecar offers.
POST /api/mcp/toolRuns one tool, with the target server's context added automatically.
POST /api/mcp/kubectlRuns a kubectl command through the sidecar against a given server's cluster.
available: true only means something answered on the sidecar's port. A different local process on 8090 can produce a false positive. To confirm the sidecar itself is up, run docker ps | grep infra-eye-mcp, kubectl get pod -n infra-eye (look for 2/2 containers ready), or on desktop ps aux | grep kubernetes-mcp-server. For connection problems, see Troubleshooting.

Alert Engine Production

Define threshold-based alerts on any metric. Receive notifications via Slack and Google Chat webhooks with configurable severity levels.

Admins configure webhooks under System Settings → Alert Notifications, where each channel has a Test button. Only HTTPS URLs on the provider's real webhook host are accepted. SLACK_WEBHOOK_URL and GOOGLE_CHAT_WEBHOOK_URL still work as a fallback: a URL saved in the UI takes precedence, and clearing it falls back to the environment variable.

RBAC & OS Accounts Production

Fine-grained access control lets you grant individual users visibility into specific servers and clusters. A Cockpit-style OS account manager lets admins create real Unix users on target machines with tiered privileges.

RolePermissions
adminFull system access, user management
devopsServer management, resources, alerts
traineeLimited resource access
internRead-only access to the dashboard

On target machines, OS accounts can be provisioned with Admin, Read/Write, Log-Viewer, or Read-Only privileges. These roles also drive OIDC group/role mapping when SSO is enabled.

Infrastructure-as-Code Sync Beta

Point InfraEye at a Git repository containing servers.yaml and/or alert-rules.yaml, and it will periodically (or on demand) create, update, and remove the server list and alert rules to match — without ever touching anything you created by hand in the UI.

Enable it from IaC Sync in the sidebar (under Engineering). Set a repository URL (HTTPS only — a Personal Access Token field covers private repos), branch, and optional subdirectory, then use Test Connection and Sync Now. A run-history table shows exactly what changed, or the raw error verbatim if something's wrong.

File layout

Both files live at the repo root by default:

repo layout
your-repo/ ├── servers.yaml └── alert-rules.yaml

Set a Subdirectory in the settings (e.g. infraeye) if these files live in a subfolder instead — InfraEye will then look for infraeye/servers.yaml and infraeye/alert-rules.yaml. Both files are independently optional.

servers.yaml

servers.yaml
servers: - name: web-01 # required — unique matching key host: 10.0.1.11 port: 22 # optional, default 22 ssh_user: deploy auth_type: key # optional, "key" or "password", default "key" tags: prod,web # optional, comma-separated description: "Primary web node" folder: production # optional — auto-created if missing os: linux # optional, descriptive only is_k8s: false # optional, default false - name: cluster-a is_k8s: true # marks this as a Kubernetes cluster in the UI
There is deliberately no ssh_key_path, ssh_password, or kube_config field. Credentials are never expected to live in a Git repo — a synced server appears as inventory metadata only, and someone still has to open it in InfraEye and add its SSH key/password (or kubeconfig) by hand.

alert-rules.yaml

alert-rules.yaml
alert_rules: - name: high-cpu-web # required, unique key server: web-01 # optional server name; omit for "all servers" condition_type: cpu # required — cpu | mem | disk | load | log_keyword | pod_status condition_op: gt # required — gt | lt | gte | contains condition_value: "85" # required severity: warning # optional, default "warning" action_type: notify # required — notify | ssh_command action_command: "" # only used when action_type is ssh_command cooldown_minutes: 10 # optional, default 5 enabled: true # optional, default false description: "Fires when CPU sustains above 85%" - name: all-servers-disk condition_type: disk condition_op: gt condition_value: "90" action_type: notify enabled: true # no "server:" field — applies to every server

How sync behaves

  • Name is the identity. Each sync matches entries by name: new names are created, existing Git-managed names are updated in place, and names no longer in the file are removed.
  • It only ever touches what it created. If a manually-added server or rule already has that name, sync skips it and reports a conflict in the run history — it never silently adopts or overwrites something made by hand.
  • Editing a synced item in the UI unmanages it. That row becomes a normal manual entry from then on, so a support engineer's emergency fix in the UI is never silently reverted by the next scheduled sync.
  • All or nothing. Changes from one run are applied in a single database transaction. If anything fails, none of that run's changes are kept.

Scheduling

With Enabled on, InfraEye syncs every 15 minutes by default. You can set another interval in the settings. When it's off, scheduled ticks do nothing, but Sync Now still works. The interval is read when the backend starts, so restart InfraEye after changing it. Each sync shallow-fetches (--depth 1) only the configured branch into a working copy under GITSYNC_WORKDIR.

Run history

StatusMeaning
runningSync in progress
successEverything applied. Created/updated/deleted counts are shown for servers and rules.
partialApplied, but some entries were skipped because of name conflicts with manual items. The conflicts are listed on the run.
failedNothing applied: a clone/fetch error, invalid YAML, or a database error. The raw error is shown, with credentials removed.

Permissions & API

EndpointPurposeRoles
GET /api/gitsync/settingsRead settings (the PAT is never returned)admin, devops
PUT /api/gitsync/settingsChange repo, branch, subdirectory, interval, enabled flag, PATadmin
POST /api/gitsync/testCheck that the repo and branch are reachableadmin, devops
POST /api/gitsync/syncRun a sync nowadmin, devops
GET /api/gitsync/runsRun historyadmin, devops, trainee

Security Audit Beta

The Audit group in the sidebar holds six scanners that cover your hosts, clusters, services, source code, and running web apps. Server scans reuse InfraEye's existing SSH connections, so nothing is installed or copied onto the target. External scanners are never bundled: if one is missing, InfraEye says exactly what to install instead of skipping it quietly or faking a result.

ScannerTargetNeeds
Kernel ScannerLinux serversSSH only
Server HardeningLinux serversSSH only (root reveals more)
Cluster SecurityKubernetes clustersKubeconfig only
Resource SecurityCatalog resourcesNothing extra
Code SecurityGit repositoriesGitleaks / Semgrep / Trivy / CodeQL on the backend host
DAST ScannerWeb URLsDocker or a running ZAP daemon

Kernel Scanner

Compares each Linux server's running kernel with a database of known local privilege-escalation CVEs, ported from kernelpwned into Go. A single SSH round trip collects the kernel release, kernel config (/boot/config-*), loaded and auto-loadable modules, relevant /proc//sys settings, and SUID/SGID targets. Every probe is read-only or a dry run (modprobe -n -v, unshare), so nothing on the host changes. Each CVE gets a verdict with the reason, e.g. "esp4 loaded" or "kernel outside affected range". Kubeconfig-only clusters and macOS hosts are excluded.

Server Hardening

CheckSeverity
SSH root login disabledHigh
Only root has UID 0High
No accounts with an empty passwordHigh
SSH password authentication disabledMedium
Host firewall activeMedium
No passwordless sudo entriesMedium
Automatic security updates enabledLow

Probes that can't run (e.g. reading /etc/shadow without root) come back as unknown instead of aborting the whole scan.

Cluster Security

Makes read-only List/Get calls against the live cluster and reports:

  • Workload: privileged containers, hostNetwork/hostPID pods (high), containers explicitly running as UID 0 (medium), containers with no CPU/memory limits (low).
  • RBAC: ClusterRoleBindings that grant cluster-admin (high), and default ServiceAccounts that don't disable token automount (low).
  • Network: LoadBalancer and NodePort services, and namespaces with no NetworkPolicy, meaning no default-deny (low).
  • Version: Kubernetes minor versions past community support (1.20–1.26). This list is a point-in-time snapshot, so cross-check it with upstream.

Resource Security

For each Resource Catalog entry: is it reachable, does it require authentication, and for TLS-capable protocols, is the certificate valid, trusted, and not about to expire? It only dials and completes a TLS handshake, and never guesses or submits credentials. Also available from each resource's detail page.

Code Security

Add a repository (name, HTTPS URL, branch, and an optional Personal Access Token for private repos). On each scan InfraEye shallow-clones it into an isolated temporary directory, runs the selected tools, merges their findings into one table (severity, file, line, rule), and deletes the clone.

ToolFinds
GitleaksHard-coded secrets and credentials
SemgrepStatic analysis (SAST) for most languages
TrivyVulnerable dependencies (SCA), IaC misconfigurations, embedded secrets
CodeQLGitHub's semantic SAST for JS/TS, Python, Ruby, and Go, with no separate build step

Progress streams live to a scan log in the UI. If one tool fails, its raw stderr/exit error is shown next to the other tools' results, so a partial scan is never presented as complete. Semgrep's default auto ruleset needs outbound access to semgrep.dev; offline installs should use a local ruleset or a bundled pack such as p/ci.

DAST Scanner

Add a target URL (http/https only), then scan it with OWASP ZAP. If ZAP_API_URL (and ZAP_API_KEY) point at a running ZAP daemon, InfraEye uses its API. Otherwise it starts ghcr.io/zaproxy/zaproxy:stable in Docker for that scan. Alerts are normalized into the same findings table, and progress streams to a live log.

DAST has two modes. Baseline is passive: it crawls the site and never sends attack payloads. Full is an active scan that sends real SQLi, XSS, and similar payloads at the target, so the API rejects it unless you explicitly confirm. Only run a full scan against systems you're authorized to test.

Installing scanner tools

InfraEye looks for each tool on PATH, then in common install locations (/opt/homebrew/bin, /usr/local/bin, MacPorts, /snap/bin, ~/.local/bin, ~/go/bin, ~/.cargo/bin), and for CodeQL, the usual unzip locations such as ~/codeql-home/codeql/codeql. The Code Security page shows each tool's status and install hint. If a tool lives somewhere else, an admin can set its absolute path there, and InfraEye checks that it exists and is executable when saved.

install scanners (macOS / Linux)
# Homebrew brew install gitleaks semgrep trivy # or: pipx for Semgrep, and your distro / GitHub releases for the others pipx install semgrep # CodeQL: download the CLI bundle and unzip it, e.g. to ~/codeql-home # https://github.com/github/codeql-action/releases
In Docker/Kubernetes installs these tools must be available inside the app container. The desktop app and native dev setups use the host's tools directly.

Results & permissions

Scans always run live when you ask. InfraEye keeps only the latest result per target and scanner, so audit pages show your current posture when they load without re-scanning. GET /api/audit/summary returns the rollup.

ActionRoles
View audit pages & resultsadmin, devops, trainee
Run code & DAST scans, manage DAST targetsadmin, devops
Add / edit / delete code repositories, set tool pathsadmin

Developer Hub Beta

A toolbox of 23 everyday DevOps utilities under Engineering → Developer Hub, so you don't have to paste certificates, tokens, or manifests into random websites. Everything except DNS Lookup runs locally in your browser. The Hub is currently shown to admin users.

Networking

ToolWhat it does
CIDR CalculatorNetwork/broadcast address, usable host range, and netmask for a CIDR block
CIDR Overlap & MatchTests addresses against a list of CIDRs and flags blocks that overlap. Useful for debugging security groups, NetworkPolicy, and VPC peering.
Subnet SplitterSplits an IPv4 block into equal subnets, by target prefix or by how many subnets you need
IPv6 ToolkitExpands/compresses an address, and shows its prefix range, address type, and PTR name
DNS LookupResolves A/AAAA/CNAME/MX/TXT and more over DNS-over-HTTPS

Kubernetes & containers

ToolWhat it does
Ingress → Gateway APIConverts an Ingress into a Gateway and HTTPRoute. Best-effort, so review behavior that depends on annotations yourself.
K8s Manifest CleanerRemoves server-generated fields (status, uid, resourceVersion, managedFields…) from a live manifest so it can be applied to any cluster
Resource UnitsConverts CPU (millicores ↔ cores) and memory (Ki/Mi/Gi/Ti, decimal and binary) quantities
Image Reference ParserSplits an image reference into registry, repository, tag, and digest
Jsonnet EvaluatorEvaluates Jsonnet, the language Tanka uses, into JSON or YAML

Security & certificates

ToolWhat it does
TLS Certificate DecoderShows a PEM certificate's subject, issuer, validity window, and SANs
Certificate Chain InspectorChecks a PEM bundle the way a TLS server loads it: order, missing intermediates, expiry
SSH Key InspectorFingerprints and key strength for a public key or a whole authorized_keys file
JWT Decoder / EncoderDecodes any JWT, or signs a header/payload into an HS256 token for local testing
Hash GeneratorSHA-1/256/384/512 digests and HMAC
UUID / Secret GeneratorUUIDv4s and cryptographically random secrets

Data & text

ToolWhat it does
JSON FormatterPretty-prints, minifies, and validates JSON
YAML ↔ JSONConverts in either direction
Base64 EncoderEncodes and decodes Base64
Diff CheckerLine-by-line comparison of two texts
Cron ParserExplains a 5-field cron expression in plain English and shows its next run times
Epoch ConverterConverts Unix timestamps (s/ms) to and from readable dates
DNS Lookup is the only tool that leaves your machine. It resolves names over DNS-over-HTTPS, so the name you query is sent to an external resolver.

OIDC / SSO Integration

InfraEye supports OpenID Connect (OIDC) authentication, allowing you to integrate with enterprise identity providers like:

  • Keycloak Recommended
  • Auth0
  • Okta
  • Azure AD / Microsoft Entra ID
  • Google Workspace
  • Any OIDC-compliant provider
When OIDC is enabled, users can log in using their existing organizational credentials instead of creating separate InfraEye accounts.

OIDC Prerequisites

  • Keycloak 20.0+ (or another OIDC provider)
  • InfraEye backend with network access to Keycloak
  • Valid SSL certificates (recommended for production)

Keycloak Setup

1. Create a Realm
1
Log in to Keycloak Admin Console

Navigate to your Keycloak admin URL and authenticate.

2
Create the realm

Click Create Realm (or use an existing one). Set Realm name: infraeye (or your preference), then enable the realm.

2. Create a Client
1
Navigate to Clients → Create Client

Configure the client:

  • Client ID: infraeye-web
  • Name: InfraEye Web Application
  • Client Protocol: openid-connect
  • Access Type: confidential
2
Configure Redirect URIs

Click Next and set:

  • Valid Redirect URIs: http://localhost:8080/api/auth/oidc/callback (dev) and https://infraeye.yourdomain.com/api/auth/oidc/callback (prod)
  • Web Origins: http://localhost:3000 (dev) and https://infraeye.yourdomain.com (prod)
3
Copy the Client Secret

Go to the Credentials tab and copy the Client Secret.

3. Configure Client Scopes

Ensure the following scopes are enabled:

  • openid
  • profile
  • email
  • roles (for role mapping)
4. Create Groups / Roles

InfraEye maps Keycloak groups or roles to its internal roles:

Keycloak Group/RoleInfraEye RoleAccess Level
infraeye-adminadminFull system access
infraeye-devopsdevopsServer management, resources
infraeye-traineetraineeLimited access
Default (none)internRead-only dashboard

Option A: Using Groups Recommended

  1. Navigate to Groups → Create Group
  2. Create groups: infraeye-admin, infraeye-devops, infraeye-trainee
  3. Assign users to appropriate groups
  4. Enable group membership in token:
    • Go to Client Scopes → roles → Mappers
    • Create a new Group Membership mapper
    • Set Token Claim Name: groups
    • Enable Full group path: Off

Option B: Using Client Roles Alternative

  1. Navigate to Clients → infraeye-web → Roles
  2. Create roles: admin, devops, trainee
  3. Assign roles to users via Users → Select User → Role Mapping
5. Create Test Users
  1. Navigate to Users → Add User
  2. Set Username and Email
  3. Click Save
  4. Go to Credentials tab → Set Password
  5. Assign to appropriate group or role

OIDC: InfraEye Configuration

Environment Variables

Add these variables to your .env file or environment:

~/infra-eye/.env
# Enable OIDC Authentication OIDC_ENABLED=true # Keycloak Configuration OIDC_ISSUER_URL=https://keycloak.yourdomain.com/realms/infraeye OIDC_CLIENT_ID=infraeye-web OIDC_CLIENT_SECRET=your-client-secret-from-keycloak OIDC_REDIRECT_URL=https://infraeye.yourdomain.com/api/auth/oidc/callback OIDC_SCOPES=openid profile email roles # JWT Secret (for internal token signing) JWT_SECRET=your-strong-random-secret-key

Configuration Reference

VariableDescriptionExample
OIDC_ENABLEDEnable/disable OIDC authtrue or false
OIDC_ISSUER_URLKeycloak realm URLhttps://keycloak.example.com/realms/infraeye
OIDC_CLIENT_IDClient ID from Keycloakinfraeye-web
OIDC_CLIENT_SECRETClient secret from Keycloak credentials tababc123...
OIDC_REDIRECT_URLCallback URL (must match Keycloak config)https://infraeye.example.com/api/auth/oidc/callback
OIDC_SCOPESOAuth scopes to requestopenid profile email roles

Docker Compose

docker-compose.yml
services: app: image: ghcr.io/mnshchtri/infra-eye:latest environment: - OIDC_ENABLED=true - OIDC_ISSUER_URL=https://keycloak.yourdomain.com/realms/infraeye - OIDC_CLIENT_ID=infraeye-web - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET} - OIDC_REDIRECT_URL=https://infraeye.yourdomain.com/api/auth/oidc/callback - OIDC_SCOPES=openid profile email roles - JWT_SECRET=${JWT_SECRET}

Kubernetes

Create a secret:

terminal
kubectl create secret generic infraeye-oidc \ --from-literal=client-secret='your-client-secret' \ --from-literal=jwt-secret='your-jwt-secret' \ -n infra-eye

Add to your deployment manifest:

deployment.yaml
env: - name: OIDC_ENABLED value: "true" - name: OIDC_ISSUER_URL value: "https://keycloak.yourdomain.com/realms/infraeye" - name: OIDC_CLIENT_ID value: "infraeye-web" - name: OIDC_CLIENT_SECRET valueFrom: secretKeyRef: name: infraeye-oidc key: client-secret - name: OIDC_REDIRECT_URL value: "https://infraeye.yourdomain.com/api/auth/oidc/callback" - name: OIDC_SCOPES value: "openid profile email roles" - name: JWT_SECRET valueFrom: secretKeyRef: name: infraeye-oidc key: jwt-secret

OIDC Role Mapping

InfraEye automatically maps Keycloak groups/roles to internal roles.

Mapping Logic

The system checks in this order:

  1. Groups (from groups claim)
  2. Roles (from roles claim)
  3. Default → intern

Mapping Table

Keycloak Group/RoleInfraEye RolePermissions
infraeye-admin or contains adminadminFull system access, user management
infraeye-devops or contains devopsdevopsServer management, resources, alerts
infraeye-trainee or contains traineetraineeLimited resource access
(none or other)internRead-only access to dashboard

Custom Role Mapping

To customize role mapping, edit backend/internal/handlers/oidc.go:

oidc.go
func mapOIDCRoleToInfraEye(userInfo *OIDCUserInfo) string { for _, group := range userInfo.Groups { if group == "engineering-leads" { return "admin" } if strings.HasPrefix(group, "ops-") { return "devops" } } return "intern" }

Frontend Integration

The frontend automatically detects OIDC configuration via /api/auth/oidc/config.

Login Flow

  1. User clicks "Sign in with SSO" button
  2. Frontend calls /api/auth/oidc/login
  3. User is redirected to Keycloak login page
  4. After authentication, Keycloak redirects to /api/auth/oidc/callback
  5. Backend validates the token and creates/updates user
  6. Backend issues internal JWT token
  7. User is logged into InfraEye
Traditional Login Fallback: When OIDC is enabled, you can still use traditional username/password login via /api/auth/login for emergency admin access, local testing, and non-OIDC users.

OIDC Testing

1. Verify OIDC Configuration

terminal
curl http://localhost:8080/api/auth/oidc/config

Expected response:

response.json
{ "enabled": true, "issuer_url": "https://keycloak.yourdomain.com/realms/infraeye", "client_id": "infraeye-web" }

2. Test Discovery Endpoint

terminal
curl https://keycloak.yourdomain.com/realms/infraeye/.well-known/openid-configuration

3. Test Login Flow

  1. Navigate to InfraEye login page
  2. Click "Sign in with SSO"
  3. Log in with test user credentials
  4. Verify you're redirected back and logged in

4. Check Backend Logs

terminal
docker logs infra-eye-app | grep OIDC

Expected output:

logs
OIDC initialized successfully with issuer: https://keycloak.yourdomain.com/realms/infraeye Created new OIDC user: john.doe@example.com (email: john.doe@example.com, role: devops)

OIDC Troubleshooting

"OIDC is not configured"

Cause: OIDC_ENABLED is not set to true or configuration is missing.

Solution:

  1. Verify .env file has OIDC_ENABLED=true
  2. Restart the backend: docker compose restart app
"Failed to fetch OIDC discovery"

Cause: Backend cannot reach Keycloak or issuer URL is wrong.

Solution:

  1. Check network connectivity: curl https://keycloak.yourdomain.com/realms/infraeye/.well-known/openid-configuration
  2. Verify OIDC_ISSUER_URL is correct (no trailing slash before /realms)
  3. Check firewall rules
"Invalid redirect URI"

Cause: Redirect URI in Keycloak doesn't match OIDC_REDIRECT_URL.

Solution:

  1. In Keycloak, go to Clients → infraeye-web → Settings
  2. Add exact redirect URI: https://infraeye.yourdomain.com/api/auth/oidc/callback
  3. Save
"Token exchange failed"

Cause: Invalid client secret or client configuration.

Solution:

  1. Verify OIDC_CLIENT_SECRET matches Keycloak Credentials tab
  2. Ensure Access Type is confidential
  3. Check backend logs for detailed error
"User created with wrong role"

Cause: Role mapping not configured correctly.

Solution:

  1. Verify user has correct group assignment in Keycloak
  2. Check Client Scopes → roles → Mappers includes groups claim
  3. Test token content: decode the ID token at jwt.io
SSL/TLS Errors

Cause: Certificate validation issues.

Solution:

  • Development: Use HTTP for Keycloak or disable cert validation (not recommended)
  • Production: Ensure valid SSL certificates on both InfraEye and Keycloak

OIDC Security Best Practices

  • Use HTTPS in Production: Never send tokens over HTTP in production
  • Strong Client Secret: Generate cryptographically random client secrets (32+ characters)
  • Rotate Secrets: Periodically rotate JWT_SECRET and OIDC_CLIENT_SECRET
  • Limit Token Lifetime: Configure reasonable token expiration in Keycloak
  • Audit Logging: Monitor authentication events in both Keycloak and InfraEye
  • Least Privilege: Assign users to the minimum required role
  • Backup Admin: Keep at least one local admin account for emergency access

Migration from Local Auth

If you're migrating from local authentication:

  1. Enable OIDC but keep local auth working
  2. Match user emails: Ensure Keycloak users have same email as local users
  3. Test thoroughly: Verify all existing users can log in via OIDC
  4. Gradual rollout: Inform users about the new login method
  5. Keep fallback: Maintain local admin account for emergencies
Users will be automatically matched by email when they first log in via OIDC.

Advanced OIDC Configuration

Custom Token Claims

To use custom claims from Keycloak:

  1. Add mapper in Keycloak Client Scopes
  2. Update OIDCUserInfo struct in backend/internal/handlers/oidc.go
  3. Modify syncOIDCUser() to process custom claims
Multiple OIDC Providers

To support multiple providers, you'll need to:

  1. Create separate handler functions for each provider
  2. Add provider-specific routes (e.g., /api/auth/google/login, /api/auth/azure/login)
  3. Store provider information in user record
Session Management

Current implementation issues JWT tokens with 24-hour expiration. To add refresh tokens:

  1. Store OIDC refresh token in secure cookie or database
  2. Implement /api/auth/refresh endpoint
  3. Use refresh token to get new access token before expiration
Last Updated: July 2026  ·  InfraEye: 1.0+  ·  Tested with: Keycloak 23.0, Auth0, Okta

Default Credentials & First Login

On first deploy, a database seed script creates the default roles and an administrative user:

URLhttp://<your-ip>:8080 (Docker) or http://<node-ip>:30080 (Kubernetes)
Usernameadmin
Passwordinfra123
Set ADMIN_PASSWORD to a strong value in your .env (or Kubernetes secret) before exposing InfraEye. On every start, the backend resets the admin password to ADMIN_PASSWORD — or to infra123 if the variable is unset. A password changed only in the User Management panel will revert to the default on the next restart.

Verifying the seed ran

If login fails and you suspect the database didn't seed correctly, check the users table directly:

terminal
# Kubernetes kubectl exec -it postgres-0 -n infra-eye -- psql -U infraeye -d infraeye \ -c "SELECT id, username, role FROM users;" # Docker Compose cd ~/infra-eye docker compose exec postgres psql -U infraeye -d infraeye \ -c "SELECT id, username, role FROM users;"

If the query returns 0 rows, the seed migration hasn't run yet — re-check backend startup logs for migration errors. Restarting the backend reruns the seed, and it also restores the admin password to ADMIN_PASSWORD/infra123, so a restart is usually the fastest way back in. As a last resort, insert the admin user by hand (password infra123):

Manually insert the default admin user
terminal
# Kubernetes kubectl exec -it postgres-0 -n infra-eye -- psql -U infraeye -d infraeye -c " INSERT INTO users (username, password_hash, role, created_at, updated_at) VALUES ('admin', '\$2a\$10\$dJCl5QBnTN85pGZC24.jXuL5as8jNxuzOshgkKprhholdLrzz3PLW', 'admin', NOW(), NOW()) ON CONFLICT (username) DO NOTHING;" # Docker Compose cd ~/infra-eye docker compose exec postgres psql -U infraeye -d infraeye -c " INSERT INTO users (username, password_hash, role, created_at, updated_at) VALUES ('admin', '\$2a\$10\$dJCl5QBnTN85pGZC24.jXuL5as8jNxuzOshgkKprhholdLrzz3PLW', 'admin', NOW(), NOW()) ON CONFLICT (username) DO NOTHING;"

Troubleshooting

Can't log in on a fresh install

See Default Credentials above to verify the seed migration ran.

"OIDC is not configured"

OIDC_ENABLED is missing or not true. Verify your .env and restart the backend. Full walkthrough in OIDC Troubleshooting.

MCP sidecar can't reach the cluster

Confirm shared_mcp/kubeconfig is populated (Docker Compose) or that the infra-eye service account has cluster access (Kubernetes). The backend syncs the kubeconfig to the sidecar on startup — check docker logs infra-eye-mcp or kubectl logs -n infra-eye deploy/infra-eye -c mcp-server.

Kubernetes cluster shows "offline" / no data (Desktop app)

The Kubernetes Explorer connects to each cluster directly via client-go using that server's stored kubeconfig — it doesn't go through the MCP sidecar. A cluster showing no data is almost always a network path problem between your machine and the API server, not an InfraEye bug:

  1. Check the server's status/k8s_connected fields (GET /api/servers) — offline means the backend already tried and failed to reach it.
  2. Test reachability directly: curl -sk https://<api-server-host>:6443/version or nc -zv <host> 6443. A TLS/auth error means the network path is fine; a timeout or "Host is down" means it isn't.
  3. If the cluster runs in a local VM (OrbStack, Docker Desktop, Multipass, etc.), its private network can go stale after sleep/wake or a Wi-Fi/VPN change even though the VM and cluster are healthy. Restarting the VM or the virtualization app usually restores routing.

Desktop app: kubernetes-mcp-server not found / /api/mcp/status is misleading

On desktop, the MCP sidecar only powers the AI assistant's Kubernetes tool-calling — it's separate from the Explorer above. It's auto-spawned only if kubernetes-mcp-server is on PATH; if it's missing, no mcp-server.log appears in the app-data directory. /api/mcp/status reporting available: true isn't proof it's actually running — it just checks whether anything answers on 127.0.0.1:8090, which an unrelated local process can occupy. Confirm with ps aux | grep kubernetes-mcp-server.

Port already in use

Docker Compose binds :8080 (app), 5432 (Postgres), and 127.0.0.1:6379/127.0.0.1:8090 (Redis/MCP, loopback-only). Free the port or edit docker-compose.yml before installing.

Checking backend logs

terminal
# Docker Compose docker logs infra-eye-app -f # Kubernetes kubectl logs -n infra-eye deploy/infra-eye -f

Uninstalling

Docker Compose

uninstall.sh
# Interactive uninstaller — stops containers, removes volumes curl -fsSL https://raw.githubusercontent.com/mnshchtri/infra-eye/main/uninstall.sh | bash
  • Stops and removes all InfraEye containers
  • Removes Docker volumes (database data, caches)
  • Optionally removes InfraEye Docker images and deletes the install directory (~/infra-eye)
  • Leaves shared Docker networks and host credentials untouched

Kubernetes

terminal
# Removes the entire infra-eye namespace and all its resources kubectl delete namespace infra-eye # Optional cleanup kubectl delete pvc --all -n infra-eye kubectl delete secret infra-eye-secrets -n infra-eye rm -rf ~/infra-eye
Neither uninstall path touches other namespaces, cluster config, kubeconfig, or node-level host files.

Roadmap

  • Shipped OIDC / SSO Integration — Keycloak, Auth0, Okta, Azure AD
  • Shipped Granular RBAC & OS Accounts
  • Shipped Gateway API & CRD Explorer
  • Shipped Desktop App — native macOS/Linux/Windows build via Wails, no Docker required
  • Shipped Infrastructure-as-Code Sync — server lists and alert rules from a Git repo
  • Soon Terraform Bridge — visualize and drift-detect Terraform-managed resources
  • Soon Metric Persistence via Prometheus/VictoriaMetrics
  • Soon Mobile Command Center — a PWA for on-call status checks
  • Soon Dynamic Alert Builder — drag-and-drop self-healing conditions

Support

Report an Issue

Open a GitHub issue for bugs or unexpected behavior.

Request a Feature

Suggest an idea on the same issue tracker.

Source Code

github.com/mnshchtri/infra-eye — MIT licensed.

OIDC / SSO Guide

Full walkthrough for Keycloak, Auth0, Okta, and Azure AD.

Keycloak Docs

Keycloak Documentation for provider-specific setup.

OIDC Spec

OpenID Connect Specification for protocol details.