Major new features: - App bar score badge showing cluster Polaris score - Inline audit results in Deployment/StatefulSet/DaemonSet/Job/CronJob detail views - Exemption management UI with annotation PATCH support - Top issues table on overview dashboard - Audit time display and manual refresh button - Connection test button in settings - Check ID to human-readable name mapping - Enhanced error messages with context Technical improvements: - Added triggerRefresh to PolarisDataContext for manual refresh - Created checkMapping.ts for check metadata - Created topIssues.ts for extracting common failures - Enhanced DashboardView with top issues and refresh - Enhanced PolarisSettings with connection test - Created InlineAuditSection for details view integration - Created AppBarScoreBadge for app bar integration - Created ExemptionManager for annotation patches UI enhancements: - 1000px namespace detail panel - Theme-aware styling throughout - Improved formatting and layout - Better status indicators Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
13 KiB
headlamp-polaris-plugin
A Headlamp plugin that surfaces Fairwinds Polaris audit results directly in the Headlamp UI.
What It Does
Adds a Polaris top-level sidebar section to Headlamp with comprehensive security, reliability, and efficiency audit integration:
Main Views
- Overview Dashboard -- cluster score with percentage gauge, check distribution charts, top 10 most common failing checks across the cluster, cluster statistics, and last audit time with manual refresh button
- Namespaces -- table of all namespaces with per-namespace score and check counts; click a namespace to open a detailed side panel (1000px wide, theme-aware)
- Namespace Detail Panel -- per-namespace score, check counts, resource-level audit results, external Polaris dashboard link, and exemption management
Integrated Features
- App Bar Score Badge -- cluster Polaris score displayed as a colored chip in the top navigation bar (green ≥80%, yellow ≥50%, red <50%); click to navigate to overview
- Inline Resource Audits -- Polaris audit results automatically injected into detail views for Deployments, StatefulSets, DaemonSets, Jobs, and CronJobs; shows compact score, failing checks table, and link to full report
- Exemption Management -- add or remove Polaris exemptions via annotation patches directly from the UI; supports per-check exemptions or exempt-all
- Configurable Dashboard URL -- supports both Kubernetes service proxy URLs and full HTTP/HTTPS URLs for external Polaris deployments
- Connection Testing -- test button in settings to verify Polaris dashboard connectivity and show version info
Data & Refresh
Data is fetched from the Polaris dashboard API through the Kubernetes service proxy (/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json) or custom URLs. The plugin is primarily read-only; it only writes when explicitly applying exemption annotations.
Results are refreshed on a user-configurable interval (1 / 5 / 10 / 30 minutes, default 5). Settings are available in Settings > Plugins > Polaris and persist in browser localStorage.
Error states are handled explicitly with context-specific messages: RBAC denied (403), Polaris not installed (404/503), malformed JSON, network failures, and CORS issues.
Prerequisites
| Requirement | Minimum version |
|---|---|
| Headlamp | v0.26+ |
| Polaris (with dashboard enabled) | Any recent release |
| Kubernetes | v1.24+ |
Polaris must be deployed in the polaris namespace with the dashboard component enabled (dashboard.enabled: true in the Helm chart, which is the default). The plugin reads from the polaris-dashboard ClusterIP service on port 80.
Installing
Option 1: Artifact Hub + Headlamp plugin manager (recommended)
The plugin is published on Artifact Hub. Configure Headlamp's pluginsManager in your Helm values to install it automatically:
pluginsManager:
sources:
- url: https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin
Headlamp will fetch and install the plugin on startup.
Option 2: Docker init container
The plugin ships as a container image at git.farh.net/farhoodliquor/headlamp-polaris-plugin.
Add it as an init container in your Headlamp Helm values:
initContainers:
- name: polaris-plugin
image: git.farh.net/farhoodliquor/headlamp-polaris-plugin:latest
command: ["sh", "-c", "cp -r /plugins/* /headlamp/plugins/"]
volumeMounts:
- name: plugins
mountPath: /headlamp/plugins
volumes:
- name: plugins
emptyDir: {}
volumeMounts:
- name: plugins
mountPath: /headlamp/plugins
Option 3: Manual tarball install
Download the .tar.gz from the GitHub releases page or the Gitea releases page, then extract into Headlamp's plugin directory:
tar xzf headlamp-polaris-plugin-<version>.tar.gz -C /headlamp/plugins/
Option 4: Build from source
npm install
npm run build
npx @kinvolk/headlamp-plugin extract . /headlamp/plugins
Installing Dev/Preview Versions
Dev preview versions are not currently available through the Headlamp plugin manager. Stable versions can be installed from ArtifactHub via the plugin manager UI.
RBAC / Security Setup
The plugin fetches audit data through the Kubernetes API server's service proxy sub-resource. The identity making the request (Headlamp's service account, or the user's own token in token-auth mode) must be granted:
| Verb | API Group | Resource | Resource Name | Namespace |
|---|---|---|---|---|
get |
"" (core) |
services/proxy |
polaris-dashboard |
polaris |
Minimal RBAC manifests
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: polaris-proxy-reader
namespace: polaris
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: headlamp-polaris-proxy
namespace: polaris
subjects:
- kind: ServiceAccount
name: headlamp # adjust to match your Headlamp service account
namespace: kube-system # adjust to match the namespace Headlamp runs in
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
Apply with kubectl apply -f polaris-rbac.yaml.
Token-auth mode
When Headlamp is configured for user-supplied tokens (rather than a fixed service account), each user must have the RoleBinding above attached to their own identity. A 403 error in the plugin means the currently logged-in user lacks this binding.
NetworkPolicy
If the polaris namespace enforces network policies, ensure ingress is allowed from the Kubernetes API server (which performs the proxy hop) to polaris-dashboard on port 80.
Read-only access
The plugin only performs GET requests through the service proxy. No create, update, delete, or patch verbs are required. Do not grant broader access than get on services/proxy.
Audit logging
Every proxied request is recorded in Kubernetes API audit logs as a get on services/proxy in the polaris namespace. If the auto-refresh interval generates more audit volume than desired, increase the refresh interval in the plugin settings or adjust your audit policy.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| 403 Access Denied | Missing RBAC binding for services/proxy |
Apply the Role + RoleBinding from the RBAC section above |
| 404 or 503 | Polaris not installed, or dashboard disabled | Install Polaris with dashboard.enabled: true in the polaris namespace |
| No data | Polaris running but no workloads scanned yet | Wait for the next Polaris audit cycle or restart the Polaris pod |
| Stale data | Refresh interval too long | Lower the interval in Settings > Plugins > Polaris |
Development
Setup
git clone https://github.com/cpfarhood/headlamp-polaris-plugin.git
cd headlamp-polaris-plugin
npm install
Run locally (hot reload)
npm start
This starts the Headlamp plugin dev server. Point a running Headlamp instance at the dev server to see changes live.
Build for production
npm run build # outputs dist/main.js
npm run package # creates headlamp-polaris-plugin-<version>.tar.gz
Type-check, lint, format, and test
npm run tsc # type-check without emitting
npm run lint # eslint
npm run format:check # prettier check
npm test # vitest unit tests
Project Structure
src/
index.tsx -- Entry point. Registers sidebar entries and routes.
api/
polaris.ts -- TypeScript types (AuditData schema), usePolarisData hook,
countResults utilities, refresh interval settings.
polaris.test.ts -- Unit tests for utility functions (vitest).
PolarisDataContext.tsx -- React context provider; shared data fetch across views.
components/
DashboardView.tsx -- Overview page (score, check summary with skipped, cluster info).
NamespacesListView.tsx -- Namespace list with scores and links to detail views.
NamespaceDetailView.tsx -- Per-namespace drill-down with resource table.
PolarisSettings.tsx -- Plugin settings page (refresh interval selector).
vitest.config.mts -- Vitest configuration (jsdom environment).
Data Source
The plugin fetches live audit results from the Polaris dashboard HTTP API via the Kubernetes service proxy:
GET /api/v1/namespaces/polaris/services/polaris-dashboard/proxy/results.json
This endpoint is served by the polaris-dashboard ClusterIP service, which is created by the Polaris Helm chart when dashboard.enabled: true. The JSON response matches Polaris's AuditData schema (pkg/validator/output.go):
AuditData
ClusterInfo -- nodes, pods, namespaces, controllers
Results[] -- per-workload results
Results{} -- top-level check results (ResultSet)
PodResult
Results{} -- pod-level check results
ContainerResults[]
Results{} -- container-level check results
Each check in a ResultSet has Success (bool) and Severity ("warning", "danger", or "ignore"). Checks with Severity: "ignore" and Success: false are counted as skipped. The cluster score is computed client-side as pass / total * 100.
Known Limitations
Skipped Count and Annotation-Based Exemptions
The Skipped count shown in the plugin only reflects checks with Severity: "ignore" in the Polaris API response. It does not include annotation-based exemptions (e.g., polaris.fairwinds.com/privilegeEscalationAllowed-exempt: "true").
Why? Polaris completely omits exempted checks from the results.json endpoint. The native Polaris dashboard UI computes the "skipped" count client-side by:
- Querying Kubernetes resources (Deployments, DaemonSets, StatefulSets, Pods) directly
- Parsing their annotations for
polaris.fairwinds.com/*-exemptkeys - Counting how many checks were exempted
This plugin only has access to the processed audit results via the service proxy and does not query raw Kubernetes resources. To show accurate exemption counts, the plugin would need to:
- Request cluster-wide read access to all workload types (requires additional RBAC grants beyond
services/proxy) - Parse annotations on every workload in every namespace
- Cross-reference with the Polaris check catalog to count exemptions
This is a significant architectural change and is not currently implemented. Hover over the "Skipped" count in the UI to see a tooltip explaining this limitation.
Workaround: Use the "View in Polaris Dashboard" link from any namespace detail view to see the full exemption count in the native dashboard.
Releasing
Releases are automated via CI. To cut a release:
# Bump version in package.json and artifacthub-pkg.yml (version + archive-url), then:
git add package.json artifacthub-pkg.yml
git commit -m "chore: bump version to X.Y.Z"
git tag vX.Y.Z
git push origin main vX.Y.Z
This triggers the Gitea Actions release workflow (.gitea/workflows/release.yaml):
- Build the plugin in a
node:20container - Package a
.tar.gztarball - Build and push a Docker image to
git.farh.net/farhoodliquor/headlamp-polaris-plugin:{tag}and:latest - Create a Gitea release with the tarball attached
- Create a GitHub release with the same tarball (for Artifact Hub)
- Update
artifacthub-pkg.ymlchecksum on main and force-move the tag to match
A guard step prevents infinite loops: if the release tarball checksum already matches the metadata, the build is skipped.
CI secrets
| Secret | Where | Purpose |
|---|---|---|
REGISTRY_TOKEN |
Gitea | Personal access token with package:write scope for Docker image push |
GH_PAT |
Gitea | GitHub personal access token for creating GitHub releases |
The Gitea release uses the built-in github.token. The archive-checksum in artifacthub-pkg.yml is updated automatically by the release workflow.
Links
License
MIT