docs: add service proxy RBAC/security requirements and update outdated docs
The README, CLAUDE.md, and artifacthub-pkg.yml all referenced the old ConfigMap-based data source. Updated to document the actual Kubernetes service proxy path, the correct RBAC Role/RoleBinding (services/proxy get), and added sections for token-auth mode, NetworkPolicy, audit logging, and troubleshooting. Also updated the project structure and feature descriptions to match the current codebase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,23 +6,28 @@ A [Headlamp](https://headlamp.dev/) plugin that surfaces [Fairwinds Polaris](htt
|
|||||||
|
|
||||||
## What It Does
|
## What It Does
|
||||||
|
|
||||||
Adds a **Polaris** sidebar entry to Headlamp that displays:
|
Adds a **Polaris** top-level sidebar section to Headlamp with the following views:
|
||||||
|
|
||||||
- **Cluster Score** -- overall Polaris score as a percentage (color-coded green/amber/red)
|
- **Overview** -- cluster score as a percentage (color-coded green/amber/red), check summary (pass/warning/danger counts), and cluster info (nodes, pods, namespaces, controllers)
|
||||||
- **Check Summary** -- total, pass, warning, and danger counts across all workloads
|
- **Full Audit** -- same as overview but includes skipped checks in the totals
|
||||||
- **Cluster Info** -- node, pod, namespace, and controller counts
|
- **Namespace drill-down** -- per-namespace score, check counts, and a resource table showing pass/warning/danger per workload. Namespace entries appear dynamically in the sidebar based on live audit data.
|
||||||
|
- **External link** -- quick jump to the native Polaris dashboard via the Kubernetes service proxy
|
||||||
|
|
||||||
Data is read from the `ConfigMap/polaris-dashboard` in the `polaris` namespace (key: `dashboard.json`), which is created by the standard Polaris Helm chart. The plugin is read-only -- it never writes to the cluster.
|
Data is fetched from the Polaris dashboard API through the Kubernetes service proxy (`/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`). The plugin is read-only -- it never writes to the cluster.
|
||||||
|
|
||||||
Results are cached and refreshed on a user-configurable interval (1 / 5 / 10 / 30 minutes, default 5). The setting persists in the browser's localStorage.
|
Results are refreshed on a user-configurable interval (1 / 5 / 10 / 30 minutes, default 5). The setting is available in **Settings > Plugins > Polaris** and persists in the browser's localStorage.
|
||||||
|
|
||||||
Error states are handled explicitly: RBAC denied (403), Polaris not installed (404), malformed JSON, and loading.
|
Error states are handled explicitly: RBAC denied (403), Polaris not installed (404/503), malformed JSON, and loading.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **Headlamp** >= v0.26 deployed in your cluster
|
| Requirement | Minimum version |
|
||||||
- **Polaris** installed via the [official Helm chart](https://github.com/FairwindsOps/polaris) with the dashboard component enabled
|
|-------------|----------------|
|
||||||
- The Headlamp service account must have RBAC permission to `get` ConfigMaps in the `polaris` namespace
|
| 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
|
## Installing
|
||||||
|
|
||||||
@@ -78,35 +83,70 @@ npm run build
|
|||||||
npx @kinvolk/headlamp-plugin extract . /headlamp/plugins
|
npx @kinvolk/headlamp-plugin extract . /headlamp/plugins
|
||||||
```
|
```
|
||||||
|
|
||||||
## RBAC
|
## RBAC / Security Setup
|
||||||
|
|
||||||
The plugin reads a single ConfigMap. Minimum RBAC required for the Headlamp service account:
|
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
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: Role
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp-polaris-reader
|
name: polaris-proxy-reader
|
||||||
|
namespace: polaris
|
||||||
rules:
|
rules:
|
||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["configmaps"]
|
resources: ["services/proxy"]
|
||||||
resourceNames: ["polaris-dashboard"]
|
resourceNames: ["polaris-dashboard"]
|
||||||
verbs: ["get"]
|
verbs: ["get"]
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: RoleBinding
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp-polaris-reader
|
name: headlamp-polaris-proxy
|
||||||
roleRef:
|
namespace: polaris
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
kind: ClusterRole
|
|
||||||
name: headlamp-polaris-reader
|
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp # adjust to match your Headlamp service account
|
||||||
namespace: headlamp
|
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
|
## Development
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
@@ -142,29 +182,30 @@ npm run tsc
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
index.tsx -- Entry point. Registers sidebar entry and route at /polaris.
|
index.tsx -- Entry point. Registers sidebar entries and routes.
|
||||||
api/
|
api/
|
||||||
polaris.ts -- TypeScript types matching the Polaris AuditData schema,
|
polaris.ts -- TypeScript types (AuditData schema), usePolarisData hook,
|
||||||
usePolarisData() hook with caching, countResults() utility,
|
countResults utilities, refresh interval settings.
|
||||||
and refresh interval settings (localStorage).
|
PolarisDataContext.tsx -- React context provider; shared data fetch across views.
|
||||||
components/
|
components/
|
||||||
PolarisView.tsx -- Main page component. Score badge, check summary cards,
|
DashboardView.tsx -- Overview / Full Audit page (score, check summary, cluster info).
|
||||||
cluster info, error states, refresh interval selector.
|
NamespaceDetailView.tsx -- Per-namespace drill-down with resource table.
|
||||||
|
DynamicSidebarRegistrar.tsx -- Registers sidebar entries dynamically from audit namespaces.
|
||||||
|
PolarisSettings.tsx -- Plugin settings page (refresh interval selector).
|
||||||
```
|
```
|
||||||
|
|
||||||
## Data Source
|
## Data Source
|
||||||
|
|
||||||
The plugin reads from:
|
The plugin fetches live audit results from the Polaris dashboard HTTP API via the Kubernetes service proxy:
|
||||||
|
|
||||||
- **ConfigMap**: `polaris-dashboard`
|
```
|
||||||
- **Namespace**: `polaris`
|
GET /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||||
- **Key**: `dashboard.json`
|
```
|
||||||
|
|
||||||
This ConfigMap is created automatically when Polaris is installed with the dashboard enabled. The JSON structure matches Polaris's `AuditData` schema (`pkg/validator/output.go`):
|
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
|
AuditData
|
||||||
Score -- cluster score (0-100)
|
|
||||||
ClusterInfo -- nodes, pods, namespaces, controllers
|
ClusterInfo -- nodes, pods, namespaces, controllers
|
||||||
Results[] -- per-workload results
|
Results[] -- per-workload results
|
||||||
Results{} -- top-level check results (ResultSet)
|
Results{} -- top-level check results (ResultSet)
|
||||||
@@ -174,7 +215,7 @@ AuditData
|
|||||||
Results{} -- container-level check results
|
Results{} -- container-level check results
|
||||||
```
|
```
|
||||||
|
|
||||||
Each check in a `ResultSet` has `Success` (bool) and `Severity` (`"warning"` or `"danger"`).
|
Each check in a `ResultSet` has `Success` (bool) and `Severity` (`"warning"`, `"danger"`, or `"ignore"`). The cluster score is computed client-side as `pass / total * 100`.
|
||||||
|
|
||||||
## Releasing
|
## Releasing
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -2,7 +2,13 @@ version: 0.0.9
|
|||||||
name: headlamp-polaris-plugin
|
name: headlamp-polaris-plugin
|
||||||
displayName: Polaris
|
displayName: Polaris
|
||||||
createdAt: "2026-02-05T19:00:00Z"
|
createdAt: "2026-02-05T19:00:00Z"
|
||||||
description: Surfaces Fairwinds Polaris audit results inside the Headlamp UI.
|
description: >-
|
||||||
|
Surfaces Fairwinds Polaris audit results inside the Headlamp UI.
|
||||||
|
Shows cluster score, check summary, and per-namespace drill-downs
|
||||||
|
with per-resource pass/warning/danger breakdowns. Data is fetched
|
||||||
|
read-only via the Kubernetes service proxy to the Polaris dashboard.
|
||||||
|
Requires a Role granting `get` on `services/proxy` for the
|
||||||
|
`polaris-dashboard` service in the `polaris` namespace.
|
||||||
license: MIT
|
license: MIT
|
||||||
homeURL: "https://github.com/cpfarhood/headlamp-polaris-plugin"
|
homeURL: "https://github.com/cpfarhood/headlamp-polaris-plugin"
|
||||||
category: security
|
category: security
|
||||||
|
|||||||
@@ -29,14 +29,63 @@ npx eslint src/
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── index.tsx # Entry point: registerSidebarEntry + registerRoute for /polaris
|
├── index.tsx # Entry point: registers sidebar entries + routes
|
||||||
├── api/
|
├── api/
|
||||||
│ └── polaris.ts # Types (AuditData schema), usePolarisData hook, countResults utility, refresh settings
|
│ ├── polaris.ts # Types (AuditData schema), usePolarisData hook, countResults utilities, refresh settings
|
||||||
|
│ └── PolarisDataContext.tsx # React context provider for shared data fetch
|
||||||
└── components/
|
└── components/
|
||||||
└── PolarisView.tsx # Main page: score badge, check summary, cluster info, error states, refresh interval selector
|
├── DashboardView.tsx # Overview / Full Audit page (score, check summary, cluster info)
|
||||||
|
├── NamespaceDetailView.tsx # Per-namespace drill-down with resource table
|
||||||
|
├── DynamicSidebarRegistrar.tsx # Registers namespace sidebar entries from live audit data
|
||||||
|
└── PolarisSettings.tsx # Plugin settings (refresh interval selector)
|
||||||
```
|
```
|
||||||
|
|
||||||
Single sidebar page at `/polaris`. Data is fetched via `ApiProxy.request` to the Polaris dashboard service proxy and refreshed on a user-configurable interval (stored in localStorage under `polaris-plugin-refresh-interval`, default 5 minutes). Score is computed from result counts (pass/total).
|
Top-level sidebar section at `/polaris` with sub-routes for full audit (`/polaris/full-audit`) and per-namespace views (`/polaris/ns/:namespace`). Data is fetched via `ApiProxy.request` to the Polaris dashboard service proxy and refreshed on a user-configurable interval (stored in localStorage under `polaris-plugin-refresh-interval`, default 5 minutes). Score is computed from result counts (pass/total).
|
||||||
|
|
||||||
|
## Security / RBAC Requirements
|
||||||
|
|
||||||
|
The plugin reaches Polaris through the Kubernetes API server's service proxy sub-resource (`/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/...`). The Headlamp service account (or the user's bearer token when Headlamp runs in token-auth mode) must be granted:
|
||||||
|
|
||||||
|
| Verb | API Group | Resource | Resource Name | Namespace |
|
||||||
|
|------|-----------|----------|---------------|-----------|
|
||||||
|
| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` |
|
||||||
|
|
||||||
|
Minimal RBAC example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
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 SA
|
||||||
|
namespace: kube-system
|
||||||
|
roleRef:
|
||||||
|
kind: Role
|
||||||
|
name: polaris-proxy-reader
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional considerations:
|
||||||
|
|
||||||
|
- **NetworkPolicy**: If the `polaris` namespace enforces network policies, allow ingress from the Headlamp pod (or the API server, since it performs the proxy hop) to `polaris-dashboard` on port 80.
|
||||||
|
- **Polaris dashboard listen address**: The Polaris Helm chart exposes the dashboard on a ClusterIP service (`polaris-dashboard:80`). If the chart is installed with `dashboard.enabled: false`, the service won't exist and the proxy request will 404.
|
||||||
|
- **No write operations**: The plugin only performs `GET` requests through the proxy. No `create`, `update`, or `delete` verbs are required. Do not grant broader service proxy access than `get`.
|
||||||
|
- **Token-auth mode**: When Headlamp is configured for user-supplied tokens (rather than a fixed service account), each user's own RBAC bindings must include the role above. A 403 from the plugin means the logged-in user lacks the binding.
|
||||||
|
- **Audit logging**: Kubernetes API audit logs will record every proxied request as a `get` on `services/proxy` in the `polaris` namespace. Set an appropriate audit policy level if request volume from the auto-refresh interval is a concern.
|
||||||
|
|
||||||
## Key Constraints
|
## Key Constraints
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user