docs: remove incorrect watchPlugins: false references

Remove all references to the incorrect `config.watchPlugins: false`
requirement that was believed necessary for Headlamp v0.39.0+.

Investigation revealed that plugins work correctly with the default
`watchPlugins: true` setting. The earlier documentation was based on
a misunderstanding of the plugin loading mechanism.

Changes:
- Remove watchPlugins: false from all YAML configuration examples
- Remove warning sections about watchPlugins requirement
- Update troubleshooting guides to focus on actual issues
- Simplify installation instructions by removing unnecessary config

Files updated:
- README.md (main installation docs and troubleshooting table)
- docs/DEPLOYMENT.md
- docs/TROUBLESHOOTING.md
- docs/getting-started/* (quick-start, installation, prerequisites)
- docs/deployment/* (helm, production)
- docs/troubleshooting/* (common-issues, README)
- Multiple other doc files formatted by prettier

This cleanup ensures ArtifactHub and GitHub documentation show
correct, simplified installation instructions.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
2026-02-13 09:54:15 -05:00
parent 0faa50cd9d
commit 24033ca977
25 changed files with 775 additions and 468 deletions
+46 -9
View File
@@ -18,6 +18,7 @@ This document describes the architecture, design decisions, and data flow of the
The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds Polaris audit results within the Headlamp UI. It fetches data from the Polaris dashboard API via the Kubernetes service proxy and presents it in a hierarchical navigation structure.
**Key Characteristics:**
- **Read-only:** No write operations to cluster or Polaris
- **Service proxy based:** Uses K8s API server proxy to reach Polaris
- **React Context for state:** Shared data fetch across components
@@ -170,6 +171,7 @@ Display namespace score + resource table
### Plugin Entry Point
**`src/index.tsx`**
- Registers sidebar entries (Polaris → Overview, Namespaces)
- Registers routes (`/polaris`, `/polaris/namespaces`)
- Registers app bar action (score badge)
@@ -179,22 +181,26 @@ Display namespace score + resource table
### Data Layer
**`src/api/PolarisDataContext.tsx`**
- React Context Provider for shared data
- Fetches AuditData from Polaris dashboard
- Handles auto-refresh based on user settings
- Provides `{ data, loading, error, refresh }` to consumers
**`src/api/polaris.ts`**
- TypeScript types for AuditData schema
- Utility functions: `countResults()`, `computeScore()`
- Settings management: `getRefreshInterval()`, `setRefreshInterval()`
- Constants: `DASHBOARD_URL_DEFAULT`, `INTERVAL_OPTIONS`
**`src/api/checkMapping.ts`**
- Maps Polaris check IDs to human-readable names
- Used for display in UI (e.g., "hostIPCSet" → "Host IPC")
**`src/api/topIssues.ts`**
- Aggregates failing checks across cluster
- Groups by check ID and severity
- Used for top issues dashboard
@@ -202,6 +208,7 @@ Display namespace score + resource table
### View Components
**`src/components/DashboardView.tsx`**
- **Route:** `/polaris`
- **Purpose:** Cluster-wide overview
- **Features:**
@@ -212,6 +219,7 @@ Display namespace score + resource table
- **Data:** Uses `usePolarisDataContext()`
**`src/components/NamespacesListView.tsx`**
- **Route:** `/polaris/namespaces`
- **Purpose:** List all namespaces with scores
- **Features:**
@@ -221,6 +229,7 @@ Display namespace score + resource table
- **Data:** Uses `usePolarisDataContext()`, aggregates by namespace
**`src/components/NamespaceDetailView.tsx`**
- **Route:** Drawer on `/polaris/namespaces#<namespace>`
- **Purpose:** Namespace-level drill-down
- **Features:**
@@ -233,6 +242,7 @@ Display namespace score + resource table
### UI Components
**`src/components/AppBarScoreBadge.tsx`**
- **Location:** Headlamp app bar (top-right)
- **Purpose:** Quick cluster score visibility
- **Features:**
@@ -242,6 +252,7 @@ Display namespace score + resource table
- **Data:** Uses `usePolarisDataContext()`
**`src/components/PolarisSettings.tsx`**
- **Location:** Settings → Plugins → Polaris
- **Purpose:** Plugin configuration
- **Features:**
@@ -251,6 +262,7 @@ Display namespace score + resource table
- **Data:** localStorage for persistence
**`src/components/InlineAuditSection.tsx`**
- **Location:** Resource detail pages (Deployment, StatefulSet, etc.)
- **Purpose:** Show Polaris audit inline
- **Features:**
@@ -260,6 +272,7 @@ Display namespace score + resource table
- **Data:** Uses `usePolarisDataContext()`, filters by resource
**`src/components/ExemptionManager.tsx`**
- **Location:** (Planned feature, UI exists but not fully integrated)
- **Purpose:** Manage Polaris exemptions via annotations
- **Features:**
@@ -274,6 +287,7 @@ Display namespace score + resource table
**Decision:** Use React Context instead of Redux/Zustand
**Rationale:**
1. **Simple state:** Single AuditData object shared across views
2. **Read-only:** No complex mutations or transactions
3. **Headlamp constraints:** Plugin cannot add dependencies (Redux not bundled)
@@ -283,10 +297,10 @@ Display namespace score + resource table
```typescript
interface PolarisDataContextValue {
data: AuditData | null; // Audit results or null if loading/error
loading: boolean; // True during initial fetch
error: string | null; // Error message if fetch failed
refresh: () => void; // Manual refresh function
data: AuditData | null; // Audit results or null if loading/error
loading: boolean; // True during initial fetch
error: string | null; // Error message if fetch failed
refresh: () => void; // Manual refresh function
}
```
@@ -300,6 +314,7 @@ interface PolarisDataContextValue {
### localStorage Usage
Settings persisted in localStorage:
- **`polaris-plugin-refresh-interval`**: Number (seconds), default 300
- **`polaris-plugin-dashboard-url`**: String, default service proxy path
@@ -312,12 +327,14 @@ No sensitive data stored in localStorage.
**Decision:** Use Kubernetes service proxy, not direct ClusterIP access
**Rationale:**
- Headlamp already has K8s API credentials (service account or user token)
- Service proxy leverages existing RBAC (no new credentials needed)
- Works with Headlamp's token auth and OIDC
- Simpler deployment (no additional network policies for plugin)
**Trade-off:**
- Requires `get` permission on `services/proxy` resource
- Path is longer: `/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`
@@ -326,14 +343,17 @@ No sensitive data stored in localStorage.
**Decision:** Sidebar has "Polaris" → "Overview" and "Namespaces" (2 levels max)
**Rationale:**
- Headlamp sidebar supports 2-level nesting maximum
- Deeper nesting (e.g., Polaris → Namespaces → <each namespace>) doesn't work
- Sidebar Collapse component is route-based, not click-to-toggle
**Alternative Considered:**
- Dynamic sidebar with namespace entries → rejected (Headlamp limitation)
**Current Solution:**
- Use table in NamespacesListView with clickable namespace buttons
- Namespace detail opens in drawer (not new route)
@@ -342,12 +362,14 @@ No sensitive data stored in localStorage.
**Decision:** Namespace detail uses drawer, not dedicated route
**Rationale:**
- Better UX (drawer overlays table, no navigation loss)
- URL hash preserves navigation state (`#namespace-name`)
- Keyboard shortcuts (Escape to close)
- Sidebar doesn't support 3-level nesting for per-namespace routes
**Implementation:**
- URL: `/polaris/namespaces#kube-system`
- Drawer controlled by hash presence
- `useEffect` watches hash changes
@@ -357,11 +379,13 @@ No sensitive data stored in localStorage.
**Decision:** Never import from `@mui/material` or `@mui/icons-material`
**Rationale:**
- Headlamp plugin environment doesn't provide full MUI library
- Importing MUI causes `createSvgIcon undefined` error
- Plugins must use Headlamp CommonComponents only
**Alternative:**
- Use standard HTML elements with inline styles
- Use theme-aware CSS variables (`--mui-palette-*`)
@@ -370,11 +394,13 @@ No sensitive data stored in localStorage.
**Decision:** Enable all TypeScript strict checks
**Rationale:**
- Catch errors at compile time
- Better IDE support and autocomplete
- Enforces type safety (no `any`, no implicit unknowns)
**Impact:**
- More verbose code (explicit types required)
- Better maintainability and refactorability
@@ -383,11 +409,13 @@ No sensitive data stored in localStorage.
**Decision:** Default refresh interval is 5 minutes (configurable)
**Rationale:**
- Polaris audits typically run every 10-30 minutes
- Balance between data freshness and API load
- User can configure from 1 minute to 30 minutes
**Considered:**
- WebSocket/SSE for real-time updates → rejected (Polaris dashboard doesn't support)
- Shorter default → rejected (unnecessary API calls)
@@ -401,28 +429,30 @@ No sensitive data stored in localStorage.
```typescript
// Sidebar navigation
registerSidebarEntry({ parent, name, label, url, icon })
registerSidebarEntry({ parent, name, label, url, icon });
// Routes
registerRoute({ path, sidebar, name, exact, component })
registerRoute({ path, sidebar, name, exact, component });
// App bar actions
registerAppBarAction(component)
registerAppBarAction(component);
// Plugin settings
registerPluginSettings(name, component, displaySaveButton)
registerPluginSettings(name, component, displaySaveButton);
// Resource detail sections
registerDetailsViewSection(component)
registerDetailsViewSection(component);
```
**Key Changes in v0.13.0:**
- `registerDetailsViewSection` now takes 1 argument (component), not 2 (name, component)
- `registerAppBarAction` now takes 1 argument (component), not 2 (name, component)
### Headlamp CommonComponents
**Used Components:**
- `SectionBox` - Card-like container with title
- `SectionHeader` - Page header with title
- `StatusLabel` - Color-coded status badges
@@ -432,16 +462,19 @@ registerDetailsViewSection(component)
- `Loader` - Loading spinner
**Router:**
- `Router.createRouteURL()` - Generate plugin route URLs
- React Router's `useHistory()`, `useParams()`, `useLocation()`
### Kubernetes API (via ApiProxy)
**Used for:**
- Fetching Polaris results: `ApiProxy.request(dashboardUrl + 'results.json')`
- No direct K8s API calls (all data from Polaris dashboard)
**RBAC Required:**
- `get` on `services/proxy` for `polaris-dashboard` in `polaris` namespace
## Known Limitations
@@ -529,18 +562,22 @@ registerDetailsViewSection(component)
### Potential Improvements
1. **WebWorker for data processing**
- Offload `countResults()` aggregation for large clusters
- Keep UI responsive during heavy computation
2. **IndexedDB caching**
- Cache audit data offline
- Show stale data + "refresh available" indicator
3. **GraphQL/REST API abstraction**
- Decouple from Polaris dashboard JSON format
- Support multiple backend sources
4. **Plugin-to-plugin communication**
- Integrate with other Headlamp plugins (e.g., policy enforcement)
- Shared state between plugins
+42 -59
View File
@@ -47,9 +47,9 @@ kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
```yaml
# headlamp-values.yaml
config:
pluginsDir: "/headlamp/plugins"
pluginsDir: /headlamp/plugins
pluginsManager:
pluginsManager:
enabled: true
repositories:
- https://artifacthub.io/packages/search?kind=4
@@ -76,8 +76,7 @@ pluginsManager:
```yaml
# headlamp-values.yaml
config:
pluginsDir: "/headlamp/plugins"
watchPlugins: false # CRITICAL: Must be false for plugin manager
pluginsDir: /headlamp/plugins
replicaCount: 1
@@ -123,7 +122,7 @@ data:
```yaml
# headlamp-values.yaml
config:
pluginsDir: "/plugins"
pluginsDir: '/plugins'
volumes:
- name: plugins
@@ -152,9 +151,8 @@ image:
tag: v0.39.0
config:
baseURL: ""
pluginsDir: "/headlamp/plugins"
watchPlugins: false # MUST be false for plugin manager
baseURL: ''
pluginsDir: /headlamp/plugins
pluginsManager:
enabled: true
@@ -195,16 +193,16 @@ resources:
# OIDC Authentication (optional)
env:
- name: HEADLAMP_CONFIG_OIDC_CLIENT_ID
value: "headlamp"
value: 'headlamp'
- name: HEADLAMP_CONFIG_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: headlamp-oidc
key: client-secret
- name: HEADLAMP_CONFIG_OIDC_ISSUER_URL
value: "https://auth.example.com/realms/kubernetes"
value: 'https://auth.example.com/realms/kubernetes'
- name: HEADLAMP_CONFIG_OIDC_SCOPES
value: "openid,profile,email,groups"
value: 'openid,profile,email,groups'
```
### FluxCD HelmRelease Example
@@ -230,8 +228,7 @@ spec:
values:
config:
pluginsDir: "/headlamp/plugins"
watchPlugins: false
pluginsDir: /headlamp/plugins
pluginsManager:
enabled: true
@@ -265,10 +262,10 @@ metadata:
name: polaris-proxy-reader
namespace: polaris
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
- apiGroups: ['']
resources: ['services/proxy']
resourceNames: ['polaris-dashboard']
verbs: ['get']
```
### RoleBinding Options
@@ -303,7 +300,7 @@ metadata:
namespace: polaris
subjects:
- kind: Group
name: system:authenticated # All authenticated users
name: system:authenticated # All authenticated users
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
@@ -350,10 +347,10 @@ metadata:
app.kubernetes.io/name: headlamp-polaris-plugin
app.kubernetes.io/component: rbac
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
- apiGroups: ['']
resources: ['services/proxy']
resourceNames: ['polaris-dashboard']
verbs: ['get']
---
# RoleBinding: Grant Headlamp service account access
@@ -376,6 +373,7 @@ roleRef:
```
Apply with:
```bash
kubectl apply -f polaris-plugin-rbac.yaml
```
@@ -385,6 +383,7 @@ kubectl apply -f polaris-plugin-rbac.yaml
### Required Network Access
The plugin requires network connectivity:
- **Headlamp pod** → **Kubernetes API server** (service proxy)
- **Kubernetes API server** → **Polaris dashboard service** (port 80)
@@ -424,36 +423,9 @@ spec:
## Plugin Manager Setup
### Critical Configuration
**❌ WRONG (Will not load plugins):**
```yaml
config:
watchPlugins: true # Default, treats catalog plugins as dev plugins
```
**✅ CORRECT:**
```yaml
config:
watchPlugins: false # Required for plugin manager catalog plugins
```
### Why `watchPlugins: false` is Required
- **With `watchPlugins: true`:** Headlamp backend serves plugin metadata, but frontend never executes the JavaScript (treated as development directory plugin)
- **Result:** Plugins appear in Settings but no sidebar/routes/settings work
- **Fix:** Set `watchPlugins: false` in Headlamp configuration
- **Documentation:** See `deployment/PLUGIN_LOADING_FIX.md` for root cause analysis
### Plugin Manager Verification
```bash
# Check Headlamp config
kubectl -n kube-system get configmap headlamp -o yaml | grep watchPlugins
# Expected output:
# watchPlugins: "false"
# Check plugin is installed
kubectl -n kube-system exec -it deployment/headlamp -- ls -la /headlamp/plugins/
@@ -469,7 +441,6 @@ kubectl -n kube-system exec -it deployment/headlamp -- ls -la /headlamp/plugins/
- [ ] Polaris dashboard service exists (`polaris-dashboard` in `polaris` namespace)
- [ ] RBAC Role and RoleBinding created
- [ ] Headlamp v0.26+ deployed
- [ ] `watchPlugins: false` set in Headlamp config
### Deployment
@@ -518,14 +489,16 @@ kubectl -n kube-system exec -it deployment/headlamp -- ls -la /headlamp/plugins/
**Symptom:** Plugin listed in Settings → Plugins but no sidebar entry
**Causes:**
1. `watchPlugins: true` (should be `false`)
2. Browser cache not cleared
1. Browser cache not cleared
2. Plugin files not properly installed
**Solution:**
```bash
# Fix Headlamp config
kubectl -n kube-system edit configmap headlamp
# Set watchPlugins: false
# Verify plugin files exist
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
ls -la /headlamp/plugins/headlamp-polaris-plugin/
# Restart Headlamp
kubectl -n kube-system rollout restart deployment/headlamp
@@ -541,6 +514,7 @@ kubectl -n kube-system rollout restart deployment/headlamp
**Cause:** RBAC missing or incorrect
**Solution:**
```bash
# Verify RBAC exists
kubectl -n polaris get role polaris-proxy-reader
@@ -557,11 +531,13 @@ kubectl auth can-i get services/proxy --as=system:serviceaccount:kube-system:hea
**Symptom:** Error loading Polaris data, 404 in console
**Causes:**
1. Polaris not deployed
2. Polaris service name wrong
3. Polaris namespace wrong
**Solution:**
```bash
# Check Polaris deployment
kubectl -n polaris get pods
@@ -579,11 +555,13 @@ helm install polaris fairwinds-stable/polaris \
**Symptom:** Error when using custom Polaris URL in settings
**Causes:**
1. URL format incorrect
2. CORS not configured on external Polaris
3. Network policy blocking external access
**Solution:**
```bash
# Test URL manually
curl -v https://my-polaris.example.com/results.json
@@ -599,6 +577,7 @@ curl -v https://my-polaris.example.com/results.json
**Cause:** Plugin manager hasn't synced from ArtifactHub
**Solution:**
```bash
# Wait 30 minutes (ArtifactHub sync interval)
# Or manually refresh plugin list in Headlamp UI
@@ -614,6 +593,7 @@ kubectl -n kube-system rollout restart deployment/headlamp
**Cause:** NetworkPolicy in `polaris` namespace blocking API server
**Solution:**
```bash
# Check NetworkPolicies
kubectl -n polaris get networkpolicy
@@ -633,25 +613,28 @@ kubectl -n polaris get networkpolicy
### Audit Logging
Kubernetes audit logs will record:
- User/service account accessing service proxy
- Timestamp and response code
Configure audit policy if needed:
```yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
verbs: ["get"]
verbs: ['get']
resources:
- group: ""
resources: ["services/proxy"]
namespaces: ["polaris"]
- group: ''
resources: ['services/proxy']
namespaces: ['polaris']
```
### Data Sensitivity
Polaris audit data may contain:
- Resource names and namespaces
- Configuration details
- Potential security vulnerabilities
+87 -25
View File
@@ -11,8 +11,10 @@ This plan standardizes documentation structure, formatting, and content across t
## Current State Analysis
### Polaris Plugin (v0.3.5)
**Structure**: Topic-focused with monolithic files
**Strengths**:
- Comprehensive CONTRIBUTING.md with branching strategy and commit conventions
- Complete CHANGELOG.md (35 versions documented)
- Dedicated SECURITY.md with vulnerability reporting
@@ -21,6 +23,7 @@ This plan standardizes documentation structure, formatting, and content across t
- Well-organized TROUBLESHOOTING.md with common issues
**Gaps**:
- No user journey-based organization
- No Architecture Decision Records
- Limited quick-start tutorials
@@ -28,8 +31,10 @@ This plan standardizes documentation structure, formatting, and content across t
- Deployment guide is monolithic (needs breakdown)
### Sealed Secrets Plugin
**Structure**: User journey-based with granular topic files
**Strengths**:
- Excellent user journey organization (Getting Started → User Guide → Tutorials)
- Architecture Decision Records (5 ADRs)
- Quick diagnosis flowchart in troubleshooting
@@ -38,6 +43,7 @@ This plan standardizes documentation structure, formatting, and content across t
- Visual hierarchy with strategic emoji use
**Gaps**:
- No dedicated CONTRIBUTING.md (content in README)
- No SECURITY.md for vulnerability reporting
- Incomplete tutorial placeholders
@@ -49,6 +55,7 @@ This plan standardizes documentation structure, formatting, and content across t
### 1. File Structure Standard
**Root-Level Files** (Common to Both):
```
README.md # Main entry point with badges, quick links
CHANGELOG.md # Keep a Changelog format, semantic versioning
@@ -59,6 +66,7 @@ package.json # Plugin metadata
```
**Documentation Directory** (Organized by User Journey):
```
docs/
├── README.md # Documentation hub with quick links
@@ -94,6 +102,7 @@ docs/
### 2. README.md Standard
**Required Sections** (Order Matters):
1. Title + Badges (ArtifactHub, CI, E2E, License)
2. Quick navigation links (📚 Documentation | 🚀 Installation | 🔒 Security | 🛠️ Development)
3. **What It Does** (features with visual hierarchy)
@@ -111,6 +120,7 @@ docs/
15. Footer ("Made with ❤️ for the Kubernetes community")
**Formatting Standards**:
- Use emojis strategically for visual scanning (not excessive)
- Quick navigation at top
- Tables for structured data (prerequisites, troubleshooting quick ref)
@@ -122,6 +132,7 @@ docs/
**Format**: Keep a Changelog (https://keepachangelog.com/)
**Structure**:
```markdown
# Changelog
@@ -135,21 +146,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [X.Y.Z] - YYYY-MM-DD
### Added
- New features
### Changed
- Changes to existing functionality
### Deprecated
- Soon-to-be removed features
### Removed
- Removed features
### Fixed
- Bug fixes
### Security
- Security fixes
[Unreleased]: https://github.com/user/repo/compare/vX.Y.Z...HEAD
@@ -157,6 +174,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
```
**Standards**:
- One entry per version, newest first
- Date in ISO 8601 format (YYYY-MM-DD)
- Link to GitHub release
@@ -166,6 +184,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 4. CONTRIBUTING.md Standard
**Required Sections**:
1. Code of Conduct (brief, respectful)
2. Getting Started (prerequisites, setup)
3. Development Workflow (feature development, testing)
@@ -178,6 +197,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10. Release Process (version numbering, creating releases)
**Formatting**:
- Use tables for branch naming conventions
- Code blocks for commit message examples
- Checklists for PR requirements
@@ -186,6 +206,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 5. SECURITY.md Standard
**Required Sections**:
1. Overview (security model, read-only vs. write operations)
2. Data Flow Diagram (how data moves through system)
3. RBAC Requirements (minimal permissions table)
@@ -198,6 +219,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10. Compliance Considerations (audit trail, GDPR/privacy)
**Formatting**:
- Tables for permissions and supported versions
- YAML examples for RBAC manifests
- Bash commands for security verification
@@ -208,6 +230,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
**Purpose**: Central navigation for all documentation
**Structure**:
```markdown
# Documentation
@@ -222,31 +245,40 @@ Central hub for [Plugin Name] documentation.
- 💻 [Development](development/workflow.md)
## Getting Started
Description + links to installation, prerequisites, quick-start
## User Guide
Description + links to features, configuration, RBAC
## Tutorials
Description + links to plugin-specific tutorials
## Troubleshooting
Description + link to quick diagnosis + common issues
## Architecture
Description + links to overview, data flow, ADRs
## Development
Description + links to workflow, testing, code style, release
## Deployment
Description + links to Kubernetes, Helm, production
## API Reference
Link to JSDoc or generated API docs
```
**Formatting**:
- Emojis for visual scanning
- Brief descriptions (1-2 sentences) for each section
- Organized by user journey (beginner → advanced)
@@ -254,12 +286,14 @@ Link to JSDoc or generated API docs
### 7. Architecture Decision Records (ADR) Standard
**When to Create ADRs**:
- Significant architectural choices
- Technology selection (libraries, patterns)
- Security or performance trade-offs
- Design patterns that impact maintainability
**Template** (Based on Michael Nygard's ADR):
```markdown
# ADR-NNN: Title
@@ -280,17 +314,21 @@ What is the change that we're proposing and/or doing?
What becomes easier or more difficult to do because of this change?
### Positive
- ...
### Negative
- ...
### Neutral
- ...
## Alternatives Considered
### Option 1: Name
**Pros**: ...
**Cons**: ...
**Decision**: Not chosen because...
@@ -303,11 +341,12 @@ What becomes easier or more difficult to do because of this change?
**Numbering**: ADR-001, ADR-002, etc. (zero-padded 3 digits)
**Index File** (architecture/adr/README.md):
```markdown
# Architecture Decision Records
| ADR | Title | Status | Date |
|-----|-------|--------|------|
| ADR | Title | Status | Date |
| ------------------- | ----- | -------- | ---------- |
| [001](001-title.md) | Title | Accepted | 2026-01-01 |
```
@@ -316,6 +355,7 @@ What becomes easier or more difficult to do because of this change?
**Structure**:
**troubleshooting/README.md** (Quick Diagnosis):
```markdown
# Troubleshooting
@@ -324,8 +364,8 @@ Quick diagnosis guide for common issues.
## Quick Reference
| Symptom | Likely Cause | Quick Fix |
|---------|-------------|-----------|
| ... | ... | ... |
| ------- | ------------ | --------- |
| ... | ... | ... |
## Detailed Guides
@@ -335,6 +375,7 @@ Quick diagnosis guide for common issues.
```
**Individual Issue Files**:
- Symptom-based organization
- Step-by-step resolution
- Bash commands for verification
@@ -346,6 +387,7 @@ Quick diagnosis guide for common issues.
**docs/development/testing.md**:
**Required Sections**:
1. Overview (testing philosophy, types of tests)
2. Unit Testing (framework, running tests, writing tests, examples)
3. E2E Testing (framework, prerequisites, running tests, examples)
@@ -355,6 +397,7 @@ Quick diagnosis guide for common issues.
7. Debugging (common issues, useful commands)
**Formatting**:
- Tables for test types and coverage goals
- Code blocks for examples
- Bash commands for running tests
@@ -363,6 +406,7 @@ Quick diagnosis guide for common issues.
### 10. Visual Formatting Standards
**Emoji Usage** (Strategic, Not Excessive):
- 📚 Documentation
- 🚀 Installation/Quick Start
- 🔒 Security
@@ -375,6 +419,7 @@ Quick diagnosis guide for common issues.
- 💻 Code/Technical
**Code Block Languages**:
- `bash` for shell commands
- `yaml` for Kubernetes/Helm manifests
- `typescript` for TypeScript code
@@ -382,11 +427,13 @@ Quick diagnosis guide for common issues.
- `diff` for showing changes
**Tables**:
- Use for structured data (prerequisites, commands, permissions, troubleshooting)
- Keep columns concise
- Left-align text columns, center-align status columns
**Links**:
- Use descriptive text, not "click here"
- Relative paths within repo (`docs/architecture/overview.md`)
- Absolute URLs for external resources
@@ -397,6 +444,7 @@ Quick diagnosis guide for common issues.
### Phase 1: Polaris Plugin Enhancements
**Priority 1: Granular Documentation Structure**
- [ ] Create docs/README.md (documentation hub)
- [ ] Break down DEPLOYMENT.md:
- [ ] docs/getting-started/installation.md
@@ -414,6 +462,7 @@ Quick diagnosis guide for common issues.
- [ ] Move TESTING.md → docs/development/testing.md
**Priority 2: Add Missing Content**
- [ ] Create docs/getting-started/quick-start.md (5-minute tutorial)
- [ ] Create docs/user-guide/features.md
- [ ] Create docs/user-guide/configuration.md
@@ -421,6 +470,7 @@ Quick diagnosis guide for common issues.
- [ ] Create FAQ section in troubleshooting
**Priority 3: Content Refinement**
- [ ] Add multi-platform instructions to installation.md
- [ ] Enhance README.md with better visual hierarchy
- [ ] Add more code examples to user guide
@@ -429,18 +479,21 @@ Quick diagnosis guide for common issues.
### Phase 2: Sealed Secrets Plugin Enhancements
**Priority 1: Root-Level Documentation**
- [ ] Extract CONTRIBUTING.md from README
- [ ] Create SECURITY.md with vulnerability reporting
- [ ] Expand CHANGELOG.md to include all versions
- [ ] Update README.md to match standardized format
**Priority 2: Complete Incomplete Files**
- [ ] Finish placeholder tutorial files
- [ ] Add E2E testing guide to docs/development/testing.md
- [ ] Expand API reference (ensure generated docs are readable)
- [ ] Add FAQ section
**Priority 3: Content Refinement**
- [ ] Add CI/CD badges to README
- [ ] Ensure consistent emoji usage
- [ ] Standardize code block languages
@@ -449,12 +502,14 @@ Quick diagnosis guide for common issues.
### Phase 3: Cross-Repository Standards
**Documentation Templates**
- [ ] ADR template in both repos
- [ ] Bug report template (GitHub issue template)
- [ ] Feature request template
- [ ] PR template
**Shared Patterns**
- [ ] Consistent branching strategy docs
- [ ] Identical commit message conventions
- [ ] Same release process documentation
@@ -463,21 +518,25 @@ Quick diagnosis guide for common issues.
## Success Metrics
**Completeness**:
- [ ] All standard files present in both repos
- [ ] No broken links in documentation
- [ ] All code examples tested and functional
**Consistency**:
- [ ] Same file structure in both repos
- [ ] Same formatting standards applied
- [ ] Same terminology used for common concepts
**Usability**:
- [ ] New users can get started in < 5 minutes
- [ ] Contributors can find development workflow easily
- [ ] Troubleshooting guides resolve 80%+ of common issues
**Maintainability**:
- [ ] Documentation updates documented in CHANGELOG
- [ ] ADRs created for all major decisions
- [ ] Test documentation kept in sync with code
@@ -485,6 +544,7 @@ Quick diagnosis guide for common issues.
## Maintenance Guidelines
**When to Update Documentation**:
1. **New Feature**: Add to CHANGELOG, update user guide, add tutorial if complex
2. **Bug Fix**: Add to CHANGELOG, update troubleshooting if user-facing
3. **Architecture Change**: Create ADR, update architecture docs
@@ -493,6 +553,7 @@ Quick diagnosis guide for common issues.
6. **Configuration Change**: Update user guide, add migration notes if needed
**Documentation Review Checklist**:
- [ ] Spelling and grammar checked
- [ ] Links tested (no 404s)
- [ ] Code examples tested
@@ -502,6 +563,7 @@ Quick diagnosis guide for common issues.
- [ ] Version numbers current
**Annual Documentation Audit**:
- Review all docs for accuracy (especially version numbers, screenshots)
- Check for outdated information
- Update ADR status if superseded
@@ -512,30 +574,30 @@ Quick diagnosis guide for common issues.
### Polaris Plugin Current → Standard
| Current | Standard Location |
|---------|-------------------|
| README.md | README.md (enhanced) |
| CHANGELOG.md | CHANGELOG.md (no change) |
| CONTRIBUTING.md | CONTRIBUTING.md (no change) |
| SECURITY.md | SECURITY.md (no change) |
| docs/ARCHITECTURE.md | docs/architecture/overview.md + data-flow.md + design-decisions.md |
| docs/DEPLOYMENT.md | docs/getting-started/installation.md + docs/deployment/kubernetes.md + helm.md + production.md |
| docs/TROUBLESHOOTING.md | docs/troubleshooting/README.md + common-issues.md |
| docs/TESTING.md | docs/development/testing.md |
| — (new) | docs/README.md |
| — (new) | docs/getting-started/quick-start.md |
| — (new) | docs/user-guide/features.md |
| — (new) | docs/architecture/adr/ |
| Current | Standard Location |
| ----------------------- | ---------------------------------------------------------------------------------------------- |
| README.md | README.md (enhanced) |
| CHANGELOG.md | CHANGELOG.md (no change) |
| CONTRIBUTING.md | CONTRIBUTING.md (no change) |
| SECURITY.md | SECURITY.md (no change) |
| docs/ARCHITECTURE.md | docs/architecture/overview.md + data-flow.md + design-decisions.md |
| docs/DEPLOYMENT.md | docs/getting-started/installation.md + docs/deployment/kubernetes.md + helm.md + production.md |
| docs/TROUBLESHOOTING.md | docs/troubleshooting/README.md + common-issues.md |
| docs/TESTING.md | docs/development/testing.md |
| — (new) | docs/README.md |
| — (new) | docs/getting-started/quick-start.md |
| — (new) | docs/user-guide/features.md |
| — (new) | docs/architecture/adr/ |
### Sealed Secrets Plugin Current → Standard
| Current | Standard Location |
|---------|-------------------|
| README.md | README.md (extract contributing section) |
| CHANGELOG.md | CHANGELOG.md (expand) |
| — (new) | CONTRIBUTING.md (extract from README) |
| — (new) | SECURITY.md (new file) |
| docs/* | docs/* (mostly keep, enhance incomplete files) |
| Current | Standard Location |
| ------------ | ----------------------------------------------- |
| README.md | README.md (extract contributing section) |
| CHANGELOG.md | CHANGELOG.md (expand) |
| — (new) | CONTRIBUTING.md (extract from README) |
| — (new) | SECURITY.md (new file) |
| docs/\* | docs/\* (mostly keep, enhance incomplete files) |
---
+60 -40
View File
@@ -18,13 +18,13 @@ Comprehensive guide to testing the Headlamp Polaris Plugin, covering unit tests,
The Headlamp Polaris Plugin uses a multi-layered testing approach:
| Test Type | Framework | Purpose | Location |
|-----------|-----------|---------|----------|
| **Unit Tests** | Vitest | Test individual functions and components in isolation | `src/**/*.test.ts(x)` |
| **E2E Tests** | Playwright | Test complete user flows against live Headlamp instance | `e2e/*.spec.ts` |
| **Type Checking** | TypeScript | Ensure type safety across codebase | `tsc --noEmit` |
| **Linting** | ESLint | Enforce code style and catch common errors | `eslint src/` |
| **Formatting** | Prettier | Maintain consistent code formatting | `prettier --check src/` |
| Test Type | Framework | Purpose | Location |
| ----------------- | ---------- | ------------------------------------------------------- | ----------------------- |
| **Unit Tests** | Vitest | Test individual functions and components in isolation | `src/**/*.test.ts(x)` |
| **E2E Tests** | Playwright | Test complete user flows against live Headlamp instance | `e2e/*.spec.ts` |
| **Type Checking** | TypeScript | Ensure type safety across codebase | `tsc --noEmit` |
| **Linting** | ESLint | Enforce code style and catch common errors | `eslint src/` |
| **Formatting** | Prettier | Maintain consistent code formatting | `prettier --check src/` |
### Test Philosophy
@@ -178,7 +178,9 @@ describe('DashboardView', () => {
const mockData = {
DisplayName: 'test-cluster',
ClusterInfo: { Version: '1.27', Nodes: 3, Pods: 100, Namespaces: 10, Controllers: 50 },
Results: [/* ... */],
Results: [
/* ... */
],
};
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
@@ -197,6 +199,7 @@ describe('DashboardView', () => {
### What to Unit Test
**Do test:**
- Pure functions (score calculation, filtering, data transformation)
- Data parsing and validation
- Utility functions
@@ -204,6 +207,7 @@ describe('DashboardView', () => {
- Edge cases (empty arrays, null values, invalid input)
**Don't test:**
- Third-party libraries (Headlamp, React)
- Simple prop passing
- Trivial getters/setters
@@ -242,6 +246,7 @@ npx playwright show-trace test-results/<test-name>/trace.zip
**1. Headlamp Instance**
E2E tests require a running Headlamp instance with:
- Polaris plugin installed (version being tested)
- Polaris dashboard deployed and accessible
- RBAC configured (service proxy permissions)
@@ -296,32 +301,32 @@ AUTHENTIK_PASSWORD=secret
**File:** `e2e/polaris.spec.ts`
| Test | Description | Validates |
|------|-------------|-----------|
| `sidebar contains Polaris entry` | Polaris appears in sidebar | Plugin registration |
| `overview page renders cluster score` | Score displayed on overview | Data fetching, rendering |
| `namespaces page renders table` | Namespace table loads | Data parsing, table rendering |
| `namespace detail drawer opens` | Clicking namespace shows drawer | Navigation, drawer UI |
| `namespace detail drawer closes with Escape` | Keyboard shortcut works | Keyboard navigation |
| `namespace detail drawer opens from URL hash` | Direct URL navigation | URL routing, deep linking |
| Test | Description | Validates |
| --------------------------------------------- | ------------------------------- | ----------------------------- |
| `sidebar contains Polaris entry` | Polaris appears in sidebar | Plugin registration |
| `overview page renders cluster score` | Score displayed on overview | Data fetching, rendering |
| `namespaces page renders table` | Namespace table loads | Data parsing, table rendering |
| `namespace detail drawer opens` | Clicking namespace shows drawer | Navigation, drawer UI |
| `namespace detail drawer closes with Escape` | Keyboard shortcut works | Keyboard navigation |
| `namespace detail drawer opens from URL hash` | Direct URL navigation | URL routing, deep linking |
**File:** `e2e/settings.spec.ts`
| Test | Description | Validates |
|------|-------------|-----------|
| `plugin settings page is accessible` | Settings page loads | Settings registration |
| `refresh interval can be changed` | Dropdown works | User preference persistence |
| `dashboard URL can be customized` | Input field works | URL configuration |
| `connection test button works` | Test functionality | API connectivity validation |
| Test | Description | Validates |
| ------------------------------------ | ------------------- | --------------------------- |
| `plugin settings page is accessible` | Settings page loads | Settings registration |
| `refresh interval can be changed` | Dropdown works | User preference persistence |
| `dashboard URL can be customized` | Input field works | URL configuration |
| `connection test button works` | Test functionality | API connectivity validation |
**File:** `e2e/appbar.spec.ts`
| Test | Description | Validates |
|------|-------------|-----------|
| `app bar displays Polaris badge` | Badge visible in header | App bar integration |
| `badge shows cluster score` | Score matches dashboard | Data consistency |
| `clicking badge navigates to overview` | Navigation works | App bar action |
| `badge color reflects score` | Red/yellow/green based on score | Visual feedback |
| Test | Description | Validates |
| -------------------------------------- | ------------------------------- | ------------------- |
| `app bar displays Polaris badge` | Badge visible in header | App bar integration |
| `badge shows cluster score` | Score matches dashboard | Data consistency |
| `clicking badge navigates to overview` | Navigation works | App bar action |
| `badge color reflects score` | Red/yellow/green based on score | Visual feedback |
### Writing E2E Tests
@@ -385,6 +390,7 @@ npx playwright test --debug
```
This opens Playwright Inspector where you can:
- Step through each test action
- Inspect page state
- Edit test selectors live
@@ -455,12 +461,12 @@ jobs:
Configure in GitHub repository settings (Settings → Secrets and variables → Actions):
| Secret | Required | Description |
|--------|----------|-------------|
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to configured instance) |
| `AUTHENTIK_USERNAME` | OIDC | Authentik username/email for CI user |
| `AUTHENTIK_PASSWORD` | OIDC | Authentik password |
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
| Secret | Required | Description |
| -------------------- | -------- | ------------------------------------------------------- |
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to configured instance) |
| `AUTHENTIK_USERNAME` | OIDC | Authentik username/email for CI user |
| `AUTHENTIK_PASSWORD` | OIDC | Authentik password |
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
@@ -478,11 +484,11 @@ Trigger workflows manually from GitHub Actions UI:
### Current Coverage
| Category | Coverage | Notes |
|----------|----------|-------|
| **API Functions** | 95% | Core utilities fully tested |
| **React Components** | 60% | Focus on critical render paths |
| **E2E User Flows** | 80% | Main features covered |
| Category | Coverage | Notes |
| -------------------- | -------- | ------------------------------ |
| **API Functions** | 95% | Core utilities fully tested |
| **React Components** | 60% | Focus on critical render paths |
| **E2E User Flows** | 80% | Main features covered |
### Coverage Goals
@@ -507,18 +513,22 @@ open coverage/index.html
### Unit Testing
1. **Test behavior, not implementation**
-`expect(computeScore({ total: 100, pass: 75 })).toBe(75)`
-`expect(mockInternalFunction).toHaveBeenCalled()`
2. **Use descriptive test names**
-`it('returns 0 when total checks is zero')`
-`it('works')`
3. **One assertion per test (when possible)**
- Makes failures easier to debug
- Exceptions: testing multiple properties of same object
4. **Mock external dependencies**
- Mock API calls, context providers, external libraries
- Don't mock the code you're testing
@@ -529,27 +539,33 @@ open coverage/index.html
### E2E Testing
1. **Use semantic selectors**
-`page.getByRole('button', { name: 'Close' })`
-`page.getByText('Polaris — Overview')`
-`page.locator('.MuiButton-root')`
2. **Wait for visibility, not arbitrary timeouts**
-`await expect(element).toBeVisible()`
-`await page.waitForTimeout(5000)`
3. **Keep tests independent**
- Each test should work in isolation
- Don't rely on previous tests' state
4. **Test complete user flows**
- Navigate → Interact → Verify outcome
- Don't just test page loads
5. **Clean up after tests**
- Close drawers/modals
- Reset state if needed
6. **Use storage state for auth**
- Reuse authenticated session across tests
- Faster than logging in for every test
@@ -560,15 +576,18 @@ open coverage/index.html
### General
1. **Run tests before committing**
```bash
npm run build && npm run lint && npm test
```
2. **Fix failing tests immediately**
- Don't commit failing tests
- Don't skip tests to "fix later"
3. **Update tests when changing code**
- Tests are documentation
- Keep them in sync with implementation
@@ -604,7 +623,7 @@ Check mocks are returning expected structure:
```typescript
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
data: mockData, // Ensure mockData has all required fields
data: mockData, // Ensure mockData has all required fields
loading: false,
error: null,
refresh: vi.fn(),
@@ -624,6 +643,7 @@ npm run e2e:headed
```
Common causes:
- Element hasn't rendered yet (use `toBeVisible()` instead of `toBeDefined()`)
- Wrong selector (use Playwright Inspector to verify)
- Authentication failed (check token/credentials)
+65 -28
View File
@@ -20,34 +20,17 @@ This guide covers common issues encountered when using the Headlamp Polaris Plug
## Plugin Not Showing in Sidebar
### Symptoms
- Plugin appears in Settings → Plugins but sidebar entry is missing
- No "Polaris" section in navigation
- Routes like `/polaris` return 404 or blank page
### Common Causes
**1. Headlamp v0.39.0+ Plugin Loading Issue**
**Root Cause**: Headlamp v0.39.0+ changed plugin loading behavior. With `config.watchPlugins: true` (default), catalog-managed plugins are treated as "development directory" plugins, causing the backend to serve metadata but frontend to never execute the JavaScript.
**Solution**: Set `config.watchPlugins: false` in Headlamp configuration.
```yaml
# HelmRelease values
config:
watchPlugins: false # CRITICAL for plugin manager
```
After applying this change:
1. Restart Headlamp pod
2. Hard refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
3. Clear browser cache if needed
**References**: See `deployment/PLUGIN_LOADING_FIX.md` for complete root cause analysis.
**2. Plugin Not Installed**
**1. Plugin Not Installed**
**Check plugin installation**:
```bash
# View Headlamp pod logs (plugin sidecar)
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
@@ -58,23 +41,26 @@ kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
```
**Verify plugin files exist**:
```bash
kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
# Should show: headlamp-polaris-plugin/
```
**3. JavaScript Cached by Browser**
**2. JavaScript Cached by Browser**
After upgrading the plugin, old JavaScript may be cached.
**Solution**:
- Hard refresh: Cmd+Shift+R (macOS) or Ctrl+Shift+R (Linux/Windows)
- Clear browser cache for Headlamp domain
- Open DevTools → Application → Clear Storage → Clear all
**4. Plugin Disabled in Settings**
**3. Plugin Disabled in Settings**
**Check Settings → Plugins**:
- Navigate to Headlamp Settings → Plugins
- Ensure "Polaris" plugin is enabled (toggle should be ON)
- If disabled, enable it and refresh the page
@@ -84,11 +70,13 @@ After upgrading the plugin, old JavaScript may be cached.
## 403 Forbidden Error
### Symptoms
- Error message: "Error loading Polaris audit data: 403 Forbidden"
- Browser console shows 403 response from API proxy
- Plugin sidebar shows but data fails to load
### Root Cause
User or service account lacks `services/proxy` permission on `polaris-dashboard` service in the `polaris` namespace.
### Solution
@@ -96,11 +84,13 @@ User or service account lacks `services/proxy` permission on `polaris-dashboard`
**1. Verify RBAC Configuration**
Check if Role exists:
```bash
kubectl get role polaris-proxy-reader -n polaris -o yaml
```
Expected output:
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
@@ -108,20 +98,22 @@ metadata:
name: polaris-proxy-reader
namespace: polaris
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
- apiGroups: ['']
resources: ['services/proxy']
resourceNames: ['polaris-dashboard']
verbs: ['get']
```
**2. Verify RoleBinding**
For service account mode:
```bash
kubectl get rolebinding headlamp-polaris-proxy -n polaris -o yaml
```
Expected subjects:
```yaml
subjects:
- kind: ServiceAccount
@@ -130,6 +122,7 @@ subjects:
```
For OIDC mode:
```bash
kubectl get rolebinding -n polaris -o yaml | grep -A 5 polaris-proxy-reader
```
@@ -172,6 +165,7 @@ EOF
**4. Test RBAC Permissions**
Service account mode:
```bash
# Impersonate Headlamp service account
kubectl auth can-i get services/proxy \
@@ -182,6 +176,7 @@ kubectl auth can-i get services/proxy \
```
OIDC mode (test as yourself):
```bash
kubectl auth can-i get services/proxy \
--resource-name=polaris-dashboard \
@@ -192,6 +187,7 @@ kubectl auth can-i get services/proxy \
**5. Restart Headlamp**
After applying RBAC changes:
```bash
kubectl rollout restart deployment headlamp -n kube-system
```
@@ -201,11 +197,13 @@ kubectl rollout restart deployment headlamp -n kube-system
## 404 Not Found Error
### Symptoms
- Error message: "Error loading Polaris audit data: 404 Not Found"
- Service proxy request returns 404
- Polaris dashboard not reachable
### Root Cause
Polaris dashboard service doesn't exist or is in a different namespace.
### Solution
@@ -213,12 +211,14 @@ Polaris dashboard service doesn't exist or is in a different namespace.
**1. Verify Polaris Installation**
Check if Polaris is installed:
```bash
kubectl get pods -n polaris
# Expected: polaris-dashboard-* pod running
```
Check if service exists:
```bash
kubectl get service polaris-dashboard -n polaris
# Expected: ClusterIP service on port 80
@@ -227,6 +227,7 @@ kubectl get service polaris-dashboard -n polaris
**2. Verify Service Name and Port**
The plugin expects:
- **Namespace**: `polaris`
- **Service Name**: `polaris-dashboard`
- **Port**: `80` (or named port `dashboard`)
@@ -246,11 +247,13 @@ If this returns 404, Polaris service is not configured correctly.
**4. Check Polaris Dashboard Configuration**
Verify Polaris is running with dashboard enabled:
```bash
kubectl get deployment polaris-dashboard -n polaris -o yaml | grep -A 5 dashboard
```
If `dashboard.enabled: false` in Helm values, enable it:
```yaml
# values.yaml
dashboard:
@@ -260,6 +263,7 @@ dashboard:
**5. Reinstall Polaris**
If Polaris is missing or misconfigured:
```bash
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm upgrade --install polaris fairwinds-stable/polaris \
@@ -273,21 +277,25 @@ helm upgrade --install polaris fairwinds-stable/polaris \
## Plugin Settings Page Empty
### Symptoms
- Settings → Polaris shows title but no content
- Refresh interval and dashboard URL fields not visible
### Root Cause (Fixed in v0.3.3)
Plugin settings registration name didn't match `package.json` name.
### Solution
Upgrade to v0.3.3 or later:
```bash
# Via Headlamp UI: Settings → Plugins → Update
# Or redeploy with latest version
```
If manually installing, ensure plugin name matches `package.json`:
```typescript
registerPluginSettings('headlamp-polaris-plugin', PolarisSettings, true);
// NOT 'polaris' — must match package.json name
@@ -298,6 +306,7 @@ registerPluginSettings('headlamp-polaris-plugin', PolarisSettings, true);
## Dark Mode Issues
### Symptoms
- Drawer background remains white in dark mode
- Text is hard to read in dark mode
- Theme colors don't match Headlamp UI
@@ -308,6 +317,7 @@ Upgrade to v0.3.5 or later for complete dark mode support.
**Verify CSS Variables**:
The plugin uses MUI CSS variables for theming:
- `--mui-palette-background-default` (drawer background)
- `--mui-palette-text-primary` (text color)
- `--mui-palette-primary-main` (links, buttons)
@@ -317,6 +327,7 @@ These automatically adapt to Headlamp's theme (light/dark/system).
**Hard Refresh Required**:
After upgrading from v0.3.4 or earlier, hard refresh your browser:
- macOS: Cmd+Shift+R
- Linux/Windows: Ctrl+Shift+R
@@ -328,6 +339,7 @@ If hard refresh doesn't help, clear cache for Headlamp domain.
## Data Not Loading / Infinite Spinner
### Symptoms
- Plugin shows "Loading Polaris audit data..." forever
- No error message in UI
- Data never appears
@@ -339,6 +351,7 @@ If hard refresh doesn't help, clear cache for Headlamp domain.
Open DevTools (F12) → Console tab.
Look for:
- Network errors (CORS, timeouts, 5xx responses)
- JavaScript errors
- Failed API requests
@@ -348,6 +361,7 @@ Look for:
Open DevTools → Network tab → Filter by "results.json"
Expected request:
```
GET /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
Status: 200
@@ -355,6 +369,7 @@ Response: JSON data
```
Common issues:
- **Status 0 / Failed**: Network policy blocking request
- **Status 403**: RBAC issue (see [403 Forbidden Error](#403-forbidden-error))
- **Status 404**: Service not found (see [404 Not Found Error](#404-not-found-error))
@@ -378,6 +393,7 @@ curl http://localhost:8080/results.json
**4. Check Network Policies**
If your cluster uses NetworkPolicies:
```bash
kubectl get networkpolicy -n polaris
```
@@ -385,6 +401,7 @@ kubectl get networkpolicy -n polaris
Ensure API server (or Headlamp pod) can reach Polaris dashboard.
**Example fix**:
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
@@ -399,7 +416,7 @@ spec:
- Ingress
ingress:
- from:
- namespaceSelector: {} # Allow from all namespaces (API server)
- namespaceSelector: {} # Allow from all namespaces (API server)
ports:
- protocol: TCP
port: 8080
@@ -408,6 +425,7 @@ spec:
**5. Increase Timeout / Disable Auto-Refresh**
If Polaris responds slowly:
- Open Settings → Polaris
- Increase refresh interval to 10+ minutes
- Or set to "Manual only" to disable auto-refresh
@@ -423,6 +441,7 @@ If Polaris responds slowly:
**Cause**: Network request failed (CORS, network policy, timeout)
**Solution**:
1. Check Network tab for actual HTTP status
2. Verify network policies allow API server → Polaris
3. Check Polaris pod is running
@@ -434,6 +453,7 @@ If Polaris responds slowly:
**Cause**: API returned HTML (error page) instead of JSON
**Solution**:
1. Check Network tab response body (likely 404 or 500 error page)
2. Verify Polaris service exists and is healthy
3. Check service proxy URL is correct
@@ -453,6 +473,7 @@ If Polaris responds slowly:
**Cause**: Polaris returned empty or malformed response
**Solution**:
1. Check Polaris logs for errors
2. Verify Polaris is scanning the cluster (check audit timestamp)
3. Test `/results.json` endpoint directly
@@ -538,11 +559,13 @@ kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
### Sidecar Fails to Install Plugin
**Symptoms**:
- Plugin sidecar logs show download errors
- Plugin directory is empty
- Settings → Plugins shows nothing
**Check sidecar logs**:
```bash
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
```
@@ -566,11 +589,13 @@ Error: 404 Not Found
```
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
```bash
kubectl get configmap headlamp-plugin-config -n kube-system -o yaml
```
Expected format:
```
https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
```
@@ -580,6 +605,7 @@ https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/downloa
**3. Permission denied writing to /headlamp/plugins**
**Solution**: Ensure volume mount is writable:
```yaml
volumeMounts:
- name: plugins
@@ -591,17 +617,18 @@ volumeMounts:
### Plugin Manager Not Working
**Symptoms**:
- Headlamp → Settings → Plugins shows "Catalog" tab but plugins don't install
- "Install" button does nothing
**Root Cause**: Plugin manager requires `config.pluginsDir` to be set.
**Solution**: Configure Headlamp for plugin manager:
```yaml
# HelmRelease values
config:
pluginsDir: /headlamp/plugins
watchPlugins: false # CRITICAL for v0.39.0+
```
---
@@ -609,25 +636,30 @@ config:
## ArtifactHub Sync Delays
### Symptoms
- New version released on GitHub but not showing in ArtifactHub
- Headlamp plugin catalog shows old version
### Root Cause
ArtifactHub pulls metadata every 30 minutes. There is no webhook or push mechanism.
### Solution
**Wait 30 minutes** after pushing a GitHub release, then check:
```
https://artifacthub.io/packages/headlamp/headlamp-polaris-plugin/headlamp-polaris-plugin
```
**Verify metadata**:
1. Check `artifacthub-pkg.yml` is in repository root
2. Check `headlamp/plugin/archive-url` points to GitHub release
3. Check `headlamp/plugin/archive-checksum` matches tarball SHA256
**Force sync** (ArtifactHub UI):
- Log in to ArtifactHub as package maintainer
- Go to package settings
- Click "Reindex now"
@@ -643,23 +675,28 @@ If none of these solutions work, gather debugging information and open an issue:
### Required Information
1. **Version Information**:
```bash
kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image:
```
2. **Plugin Version**:
- Check Settings → Plugins in Headlamp UI
- Or: `kubectl exec -n kube-system deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
3. **Browser Console Output**:
- Open DevTools (F12) → Console
- Screenshot or copy errors
4. **Network Tab**:
- Open DevTools → Network
- Screenshot failed requests to `results.json`
5. **Pod Logs**:
```bash
kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
@@ -7,6 +7,7 @@
## Context
The Headlamp Polaris Plugin needs to fetch Polaris audit data once and share it across multiple components:
- Dashboard view (cluster overview)
- Namespaces list view
- Namespace detail view (drawer)
@@ -16,12 +17,14 @@ The Headlamp Polaris Plugin needs to fetch Polaris audit data once and share it
Multiple state management approaches are available: Redux, Zustand, Jotai, Recoil, React Context (built-in), or component props with prop drilling.
**Constraints:**
- Headlamp plugin environment does not allow adding external dependencies (peer dependencies only)
- Redux, Zustand, Jotai, Recoil are not available in the plugin runtime
- Plugin must work with Headlamp's existing React context (React 17+)
- Bundle size should remain small (<50 KB)
**Requirements:**
- Share `AuditData` object across all views without duplicate API calls
- Support auto-refresh on user-configurable interval (1-30 minutes)
- Handle loading and error states consistently
@@ -32,6 +35,7 @@ Multiple state management approaches are available: Redux, Zustand, Jotai, Recoi
Use **React Context API** (built-in, no dependencies) for shared state management.
**Implementation:**
- `PolarisDataProvider` wraps all plugin routes
- `usePolarisDataContext()` hook provides `{ data, loading, error, refresh }` to consumers
- Single fetch shared across all views
@@ -67,12 +71,14 @@ Use **React Context API** (built-in, no dependencies) for shared state managemen
### Option 1: Redux
**Pros:**
- Powerful state management with middleware
- Excellent DevTools for debugging
- Time-travel debugging
- Well-established patterns
**Cons:**
- Redux is not available as a peer dependency in Headlamp plugins
- Massive overkill for single AuditData object
- Adds significant bundle size (~10-15 KB)
@@ -83,11 +89,13 @@ Use **React Context API** (built-in, no dependencies) for shared state managemen
### Option 2: Zustand
**Pros:**
- Lightweight (~1 KB)
- Simple API similar to `useState`
- No provider boilerplate
**Cons:**
- External peer dependency (not available in plugin runtime)
- Still adds bundle size
- Unnecessary for read-only state
@@ -97,11 +105,13 @@ Use **React Context API** (built-in, no dependencies) for shared state managemen
### Option 3: Component Props (Prop Drilling)
**Pros:**
- No dependencies
- Explicit data flow
- TypeScript tracks prop types
**Cons:**
- Prop drilling through 5+ component layers (index.tsx → route → view → subcomponent)
- Duplicate fetches if not carefully managed
- Refactoring nightmare if component tree changes
@@ -112,10 +122,12 @@ Use **React Context API** (built-in, no dependencies) for shared state managemen
### Option 4: Global Variable / Module State
**Pros:**
- Simple to implement
- No React dependencies
**Cons:**
- No reactivity (components don't re-render on data change)
- No built-in loading/error handling
- Breaks React's declarative model
@@ -126,6 +138,7 @@ Use **React Context API** (built-in, no dependencies) for shared state managemen
## Implementation Details
**Context Definition:**
```typescript
interface PolarisDataContextValue {
data: AuditData | null;
@@ -138,6 +151,7 @@ const PolarisDataContext = React.createContext<PolarisDataContextValue | undefin
```
**Provider Implementation:**
```typescript
export function PolarisDataProvider({ children }: { children: React.ReactNode }) {
const [data, setData] = useState<AuditData | null>(null);
@@ -163,6 +177,7 @@ export function PolarisDataProvider({ children }: { children: React.ReactNode })
```
**Consumer Hook:**
```typescript
export function usePolarisDataContext() {
const context = useContext(PolarisDataContext);
@@ -176,6 +191,7 @@ export function usePolarisDataContext() {
## Validation Criteria
**Success Metrics:**
- ✅ All views share single fetch (verified via network tab - one request per refresh)
- ✅ No duplicate API calls (verified via Kubernetes audit logs)
- ✅ Auto-refresh works correctly (5-30 minute intervals)
@@ -184,6 +200,7 @@ export function usePolarisDataContext() {
- ✅ Bundle size remains <50 KB (currently ~27 KB)
**Tested Scenarios:**
- ✅ Initial load with loading spinner
- ✅ Error handling (403, 404, network errors)
- ✅ Manual refresh via button
@@ -200,6 +217,6 @@ export function usePolarisDataContext() {
## Revision History
| Date | Author | Change |
|------|--------|--------|
| Date | Author | Change |
| ---------- | ----------- | ---------------- |
| 2026-02-12 | Plugin Team | Initial decision |
+6 -2
View File
@@ -41,17 +41,21 @@ What is the change that we're proposing and/or doing?
What becomes easier or more difficult to do because of this change?
### Positive
- ...
### Negative
- ...
### Neutral
- ...
## Alternatives Considered
### Option 1: Name
**Pros**: ...
**Cons**: ...
**Decision**: Not chosen because...
@@ -63,8 +67,8 @@ What becomes easier or more difficult to do because of this change?
## ADR Index
| ADR | Title | Status | Date |
|-----|-------|--------|------|
| ADR | Title | Status | Date |
| ------------------------------------- | -------------------------------------- | -------- | ---------- |
| [001](001-react-context-for-state.md) | Use React Context for State Management | Accepted | 2026-02-12 |
**Note:** Additional ADRs documenting other significant decisions (service proxy approach, drawer navigation, MUI import restrictions) can be created following the template above.
+29 -25
View File
@@ -212,46 +212,46 @@ If no match found:
```typescript
interface AuditData {
PolarisOutputVersion: string; // "1.0"
AuditTime: string; // ISO 8601 timestamp
SourceType: string; // "Cluster"
SourceName: string; // Cluster identifier
DisplayName: string; // Human-readable name
PolarisOutputVersion: string; // "1.0"
AuditTime: string; // ISO 8601 timestamp
SourceType: string; // "Cluster"
SourceName: string; // Cluster identifier
DisplayName: string; // Human-readable name
ClusterInfo: {
Version: string; // K8s version
Version: string; // K8s version
Nodes: number;
Pods: number;
Namespaces: number;
Controllers: number;
};
Results: Result[]; // Array of resource audit results
Results: Result[]; // Array of resource audit results
}
interface Result {
Name: string; // Resource name
Namespace: string; // Kubernetes namespace
Kind: string; // "Deployment", "StatefulSet", etc.
Results: ResultSet; // Resource-level checks
Name: string; // Resource name
Namespace: string; // Kubernetes namespace
Kind: string; // "Deployment", "StatefulSet", etc.
Results: ResultSet; // Resource-level checks
PodResult?: {
Name: string;
Results: ResultSet; // Pod-level checks
Results: ResultSet; // Pod-level checks
ContainerResults: {
Name: string;
Results: ResultSet; // Container-level checks
Results: ResultSet; // Container-level checks
}[];
};
CreatedTime: string; // ISO 8601 timestamp
CreatedTime: string; // ISO 8601 timestamp
}
type ResultSet = Record<string, ResultMessage>;
interface ResultMessage {
ID: string; // Check ID (e.g., "cpuLimitsMissing")
Message: string; // Human-readable message
Details: string[]; // Additional context
Success: boolean; // true = passed, false = failed
Severity: "ignore" | "warning" | "danger";
Category: string; // "Security", "Efficiency", etc.
ID: string; // Check ID (e.g., "cpuLimitsMissing")
Message: string; // Human-readable message
Details: string[]; // Additional context
Success: boolean; // true = passed, false = failed
Severity: 'ignore' | 'warning' | 'danger';
Category: string; // "Security", "Efficiency", etc.
}
```
@@ -259,11 +259,11 @@ interface ResultMessage {
```typescript
interface ResultCounts {
total: number; // Total checks performed
pass: number; // Checks that passed (Success: true)
warning: number; // Failed checks with Severity: "warning"
danger: number; // Failed checks with Severity: "danger"
skipped: number; // Failed checks with Severity: "ignore"
total: number; // Total checks performed
pass: number; // Checks that passed (Success: true)
warning: number; // Failed checks with Severity: "warning"
danger: number; // Failed checks with Severity: "danger"
skipped: number; // Failed checks with Severity: "ignore"
}
```
@@ -361,21 +361,25 @@ function getNamespaces(data: AuditData): string[] {
## Caching Strategy
**Current Implementation:**
- Data fetched once and stored in React Context
- Shared across all plugin views (no duplicate fetches)
- Cached until manual refresh or auto-refresh interval
**Cache Invalidation:**
- Manual refresh button click
- Auto-refresh interval elapses
- Settings change (dashboard URL)
**No Persistence:**
- Data NOT persisted to localStorage
- Each browser session fetches fresh data
- No offline mode
**Future Enhancement:**
- IndexedDB caching for offline access
- Incremental updates (fetch only changed namespaces)
- Service Worker for background refresh
+34 -1
View File
@@ -7,14 +7,16 @@ Key architectural choices and their rationale for the Headlamp Polaris Plugin.
**Decision:** Use Kubernetes service proxy, not direct ClusterIP access
**Context:**
- Plugin needs to access Polaris dashboard API
- Two options: Direct ClusterIP access or Kubernetes service proxy
-Headlamp already has K8s API credentials
-Headlamp already has K8s API credentials
**Decision:**
Use service proxy path: `/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`
**Rationale:**
- Headlamp already has K8s API credentials (service account or user token)
- Service proxy leverages existing RBAC (no new credentials needed)
- Works with Headlamp's token auth and OIDC
@@ -22,10 +24,12 @@ Use service proxy path: `/api/v1/namespaces/polaris/services/polaris-dashboard:8
- Consistent with Headlamp's architecture (all API calls go through K8s API)
**Trade-offs:**
-**Pros:** Simpler RBAC, works with user tokens, no new credentials
-**Cons:** Longer URL path, requires `services/proxy` permission
**Alternatives Considered:**
- Direct ClusterIP access → Rejected (requires new credentials, network policies)
- External Polaris URL → Supported as optional feature (custom URL setting)
@@ -34,6 +38,7 @@ Use service proxy path: `/api/v1/namespaces/polaris/services/polaris-dashboard:8
**Decision:** Use React Context for state management
**Context:**
- Plugin needs to share Polaris audit data across multiple views
- Options: React Context, Redux, Zustand, or component props
@@ -41,16 +46,19 @@ Use service proxy path: `/api/v1/namespaces/polaris/services/polaris-dashboard:8
Use React Context with `PolarisDataProvider`
**Rationale:**
1. **Simple state:** Single AuditData object, no complex mutations
2. **Read-only:** No transactions, undo/redo, or optimistic updates
3. **Headlamp constraints:** Cannot add external dependencies (Redux not bundled)
4. **Performance:** Data changes infrequently (5-30 minute refresh interval)
**Trade-offs:**
-**Pros:** No dependencies, simple API, built-in React feature
-**Cons:** All consumers re-render on data change (acceptable for infrequent updates)
**Alternatives Considered:**
- Redux → Rejected (not available in plugin environment)
- Zustand → Rejected (requires external dependency)
- Component props → Rejected (prop drilling, duplicate fetches)
@@ -60,6 +68,7 @@ Use React Context with `PolarisDataProvider`
**Decision:** Use drawer for namespace detail, not dedicated route
**Context:**
- Namespaces list needs drill-down to per-namespace detail
- Options: Dedicated route (`/polaris/ns/:namespace`) or drawer overlay
@@ -67,16 +76,19 @@ Use React Context with `PolarisDataProvider`
Use drawer with URL hash (`/polaris/namespaces#kube-system`)
**Rationale:**
- **Better UX:** Drawer overlays table, preserves scroll position and context
- **URL hash:** Preserves navigation state, supports browser back/forward
- **Keyboard shortcuts:** Escape key to close drawer
- **Sidebar limitation:** Headlamp sidebar doesn't support 3-level nesting
**Trade-offs:**
-**Pros:** Better UX, preserves context, keyboard navigation
-**Cons:** Hash-based routing (not "true" route), drawer accessibility considerations
**Alternatives Considered:**
- Dedicated route → Rejected (loses table context, requires back navigation)
- Modal → Rejected (less natural for drill-down, no URL state)
@@ -85,6 +97,7 @@ Use drawer with URL hash (`/polaris/namespaces#kube-system`)
**Decision:** Never import from `@mui/material` or `@mui/icons-material`
**Context:**
- Plugin needs UI components (buttons, icons, etc.)
- Headlamp uses MUI but doesn't expose full library to plugins
@@ -92,20 +105,24 @@ Use drawer with URL hash (`/polaris/namespaces#kube-system`)
Use only Headlamp CommonComponents or HTML elements with inline styles
**Rationale:**
- Importing MUI causes `createSvgIcon undefined` runtime error
- Headlamp plugin environment provides limited MUI exports
- CommonComponents cover 90% of use cases
**Implementation:**
- Use `StatusLabel`, `SectionBox`, `SimpleTable` from CommonComponents
- Use standard HTML elements (`<button>`, `<div>`) with inline styles
- Use theme-aware CSS variables (`--mui-palette-background-paper`)
**Trade-offs:**
-**Pros:** No runtime errors, smaller bundle, consistent with Headlamp
-**Cons:** Limited component variety, inline styles verbose
**Alternatives Considered:**
- Bundle full MUI → Rejected (huge bundle size, version conflicts)
- Use Headlamp's MUI exports → Rejected (incomplete, undocumented)
@@ -114,6 +131,7 @@ Use only Headlamp CommonComponents or HTML elements with inline styles
**Decision:** Sidebar has "Polaris" → "Overview" and "Namespaces" (2 levels max)
**Context:**
- Plugin needs hierarchical navigation
- Headlamp sidebar supports limited nesting depth
@@ -121,19 +139,23 @@ Use only Headlamp CommonComponents or HTML elements with inline styles
Use 2-level sidebar: `Polaris` (parent) → `Overview`, `Namespaces` (children)
**Rationale:**
- Headlamp sidebar `Collapse` component only supports 2 levels
- Deeper nesting (Polaris → Namespaces → <each namespace>) doesn't work
- Sidebar collapse is route-based, not click-to-toggle
**Workaround:**
- Namespace navigation via table (NamespacesListView)
- Clickable namespace buttons open drawer (not new route)
**Trade-offs:**
-**Pros:** Works within Headlamp constraints
-**Cons:** Can't have dynamic per-namespace sidebar entries
**Alternatives Considered:**
- Dynamic sidebar with namespace entries → Rejected (Headlamp limitation)
- Flat sidebar (no nesting) → Rejected (poor UX for plugin with multiple views)
@@ -142,6 +164,7 @@ Use 2-level sidebar: `Polaris` (parent) → `Overview`, `Namespaces` (children)
**Decision:** Enable all TypeScript strict checks
**Configuration:**
```json
{
"compilerOptions": {
@@ -155,12 +178,14 @@ Use 2-level sidebar: `Polaris` (parent) → `Overview`, `Namespaces` (children)
```
**Rationale:**
- Catch errors at compile time (not runtime)
- Better IDE support and autocomplete
- Enforces type safety (no `any`, no implicit unknowns)
- Easier refactoring (type errors surface immediately)
**Trade-offs:**
-**Pros:** Fewer runtime errors, better maintainability, self-documenting code
-**Cons:** More verbose code, steeper learning curve
@@ -169,6 +194,7 @@ Use 2-level sidebar: `Polaris` (parent) → `Overview`, `Namespaces` (children)
**Decision:** Default refresh interval is 5 minutes (configurable 1-30 min)
**Context:**
- Plugin needs to refresh Polaris data periodically
- Polaris audits typically run every 10-30 minutes
@@ -176,15 +202,18 @@ Use 2-level sidebar: `Polaris` (parent) → `Overview`, `Namespaces` (children)
Default to 5 minutes, allow user to configure (1 / 5 / 10 / 30 minutes)
**Rationale:**
- Balance between data freshness and API load
- Polaris audits don't change frequently (10-30 min intervals)
- 5 minutes provides reasonably fresh data without excessive API calls
**Trade-offs:**
-**Pros:** Reasonable default, user-configurable, low API load
-**Cons:** Not real-time (acceptable for audit data)
**Alternatives Considered:**
- WebSocket/SSE for real-time → Rejected (Polaris dashboard doesn't support)
- 1 minute default → Rejected (unnecessary API calls, audit data changes slowly)
- 30 minute default → Rejected (too stale for interactive dashboard)
@@ -194,6 +223,7 @@ Default to 5 minutes, allow user to configure (1 / 5 / 10 / 30 minutes)
**Decision:** Plugin is read-only (no write operations)
**Context:**
- Plugin could potentially modify Polaris configuration or add exemptions
- Write operations require additional RBAC permissions (PATCH, CREATE)
@@ -201,16 +231,19 @@ Default to 5 minutes, allow user to configure (1 / 5 / 10 / 30 minutes)
Plugin only performs GET requests (read-only)
**Rationale:**
- **Security:** Minimal RBAC footprint (`get` on `services/proxy` only)
- **Simplicity:** No mutation logic, error handling for writes, or rollback
- **Polaris design:** Exemptions managed via annotations (outside plugin scope)
- **Future:** Can add writes later if user demand exists
**Trade-offs:**
-**Pros:** Minimal permissions, simpler code, fewer failure modes
-**Cons:** Cannot add exemptions via UI (must edit annotations manually)
**Future Enhancement:**
- Add PATCH permission for workload annotations
- Implement `ExemptionManager` component (UI exists, not integrated)
+29 -9
View File
@@ -7,6 +7,7 @@ High-level architecture of the Headlamp Polaris Plugin.
The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds Polaris audit results within the Headlamp UI. It fetches data from the Polaris dashboard API via the Kubernetes service proxy and presents it in a hierarchical navigation structure.
**Key Characteristics:**
- **Read-only:** No write operations to cluster or Polaris
- **Service proxy based:** Uses K8s API server proxy to reach Polaris
- **React Context for state:** Shared data fetch across components
@@ -91,6 +92,7 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
### Plugin Entry Point
**`src/index.tsx`**
- Registers sidebar entries (Polaris → Overview, Namespaces)
- Registers routes (`/polaris`, `/polaris/namespaces`)
- Registers app bar action (score badge)
@@ -100,22 +102,26 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
### Data Layer
**`src/api/PolarisDataContext.tsx`**
- React Context Provider for shared data
- Fetches AuditData from Polaris dashboard
- Handles auto-refresh based on user settings
- Provides `{ data, loading, error, refresh }` to consumers
**`src/api/polaris.ts`**
- TypeScript types for AuditData schema
- Utility functions: `countResults()`, `computeScore()`
- Settings management: `getRefreshInterval()`, `setRefreshInterval()`
- Constants: `DASHBOARD_URL_DEFAULT`, `INTERVAL_OPTIONS`
**`src/api/checkMapping.ts`**
- Maps Polaris check IDs to human-readable names
- Used for display in UI (e.g., "hostIPCSet" → "Host IPC")
**`src/api/topIssues.ts`**
- Aggregates failing checks across cluster
- Groups by check ID and severity
- Used for top issues dashboard
@@ -123,6 +129,7 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
### View Components
**`src/components/DashboardView.tsx`**
- **Route:** `/polaris`
- **Purpose:** Cluster-wide overview
- **Features:**
@@ -133,6 +140,7 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
- **Data:** Uses `usePolarisDataContext()`
**`src/components/NamespacesListView.tsx`**
- **Route:** `/polaris/namespaces`
- **Purpose:** List all namespaces with scores
- **Features:**
@@ -142,6 +150,7 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
- **Data:** Uses `usePolarisDataContext()`, aggregates by namespace
**`src/components/NamespaceDetailView.tsx`**
- **Route:** Drawer on `/polaris/namespaces#<namespace>`
- **Purpose:** Namespace-level drill-down
- **Features:**
@@ -154,6 +163,7 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
### UI Components
**`src/components/AppBarScoreBadge.tsx`**
- **Location:** Headlamp app bar (top-right)
- **Purpose:** Quick cluster score visibility
- **Features:**
@@ -163,6 +173,7 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
- **Data:** Uses `usePolarisDataContext()`
**`src/components/PolarisSettings.tsx`**
- **Location:** Settings → Plugins → Polaris
- **Purpose:** Plugin configuration
- **Features:**
@@ -172,6 +183,7 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
- **Data:** localStorage for persistence
**`src/components/InlineAuditSection.tsx`**
- **Location:** Resource detail pages (Deployment, StatefulSet, etc.)
- **Purpose:** Show Polaris audit inline
- **Features:**
@@ -181,6 +193,7 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
- **Data:** Uses `usePolarisDataContext()`, filters by resource
**`src/components/ExemptionManager.tsx`**
- **Location:** (Planned feature, UI exists but not fully integrated)
- **Purpose:** Manage Polaris exemptions via annotations
- **Features:**
@@ -195,6 +208,7 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
**Decision:** Use React Context instead of Redux/Zustand
**Rationale:**
1. **Simple state:** Single AuditData object shared across views
2. **Read-only:** No complex mutations or transactions
3. **Headlamp constraints:** Plugin cannot add dependencies (Redux not bundled)
@@ -204,10 +218,10 @@ The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds
```typescript
interface PolarisDataContextValue {
data: AuditData | null; // Audit results or null if loading/error
loading: boolean; // True during initial fetch
error: string | null; // Error message if fetch failed
refresh: () => void; // Manual refresh function
data: AuditData | null; // Audit results or null if loading/error
loading: boolean; // True during initial fetch
error: string | null; // Error message if fetch failed
refresh: () => void; // Manual refresh function
}
```
@@ -221,6 +235,7 @@ interface PolarisDataContextValue {
### localStorage Usage
Settings persisted in localStorage:
- **`polaris-plugin-refresh-interval`**: Number (seconds), default 300
- **`polaris-plugin-dashboard-url`**: String, default service proxy path
@@ -236,28 +251,30 @@ No sensitive data stored in localStorage.
```typescript
// Sidebar navigation
registerSidebarEntry({ parent, name, label, url, icon })
registerSidebarEntry({ parent, name, label, url, icon });
// Routes
registerRoute({ path, sidebar, name, exact, component })
registerRoute({ path, sidebar, name, exact, component });
// App bar actions
registerAppBarAction(component)
registerAppBarAction(component);
// Plugin settings
registerPluginSettings(name, component, displaySaveButton)
registerPluginSettings(name, component, displaySaveButton);
// Resource detail sections
registerDetailsViewSection(component)
registerDetailsViewSection(component);
```
**Key Changes in v0.13.0:**
- `registerDetailsViewSection` now takes 1 argument (component), not 2 (name, component)
- `registerAppBarAction` now takes 1 argument (component), not 2 (name, component)
### Headlamp CommonComponents
**Used Components:**
- `SectionBox` - Card-like container with title
- `SectionHeader` - Page header with title
- `StatusLabel` - Color-coded status badges
@@ -267,16 +284,19 @@ registerDetailsViewSection(component)
- `Loader` - Loading spinner
**Router:**
- `Router.createRouteURL()` - Generate plugin route URLs
- React Router's `useHistory()`, `useParams()`, `useLocation()`
### Kubernetes API (via ApiProxy)
**Used for:**
- Fetching Polaris results: `ApiProxy.request(dashboardUrl + 'results.json')`
- No direct K8s API calls (all data from Polaris dashboard)
**RBAC Required:**
- `get` on `services/proxy` for `polaris-dashboard` in `polaris` namespace
## Performance Considerations
+11 -18
View File
@@ -31,7 +31,6 @@ helm repo update
# headlamp-values.yaml
config:
pluginsDir: /headlamp/plugins
watchPlugins: false # CRITICAL for v0.39.0+
pluginsManager:
enabled: true
@@ -63,9 +62,8 @@ image:
pullPolicy: IfNotPresent
config:
baseURL: ""
baseURL: ''
pluginsDir: /headlamp/plugins
watchPlugins: false # MUST be false for plugin manager
pluginsManager:
enabled: true
@@ -81,7 +79,7 @@ ingress:
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: 'true'
hosts:
- host: headlamp.example.com
paths:
@@ -117,16 +115,16 @@ affinity:
# OIDC Authentication (optional)
env:
- name: HEADLAMP_CONFIG_OIDC_CLIENT_ID
value: "headlamp"
value: 'headlamp'
- name: HEADLAMP_CONFIG_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: headlamp-oidc
key: client-secret
- name: HEADLAMP_CONFIG_OIDC_ISSUER_URL
value: "https://auth.example.com/realms/kubernetes"
value: 'https://auth.example.com/realms/kubernetes'
- name: HEADLAMP_CONFIG_OIDC_SCOPES
value: "openid,profile,email,groups"
value: 'openid,profile,email,groups'
```
Deploy:
@@ -147,7 +145,6 @@ Alternative to Plugin Manager: use an init container to download the plugin.
# headlamp-values.yaml
config:
pluginsDir: /headlamp/plugins
watchPlugins: false
initContainers:
- name: install-polaris-plugin
@@ -230,7 +227,7 @@ spec:
chart:
spec:
chart: headlamp
version: 0.26.x # Use semver range
version: 0.26.x # Use semver range
sourceRef:
kind: HelmRepository
name: headlamp
@@ -252,7 +249,6 @@ spec:
config:
pluginsDir: /headlamp/plugins
watchPlugins: false
pluginsManager:
enabled: true
@@ -388,15 +384,12 @@ kubectl -n kube-system rollout status deployment/headlamp
# Check Headlamp values
helm get values headlamp -n kube-system
# Verify watchPlugins is false:
# config:
# watchPlugins: false
# Verify plugin files exist
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
ls -la /headlamp/plugins/headlamp-polaris-plugin/
# If incorrect, update values and upgrade:
helm upgrade headlamp headlamp/headlamp \
--namespace kube-system \
--values headlamp-values.yaml \
--set config.watchPlugins=false
# If missing, reinstall plugin via UI or check init container logs
kubectl -n kube-system logs deployment/headlamp -c install-polaris-plugin
```
### Helm Release Stuck
+7 -7
View File
@@ -29,10 +29,10 @@ metadata:
app.kubernetes.io/name: headlamp-polaris-plugin
app.kubernetes.io/component: rbac
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
- apiGroups: ['']
resources: ['services/proxy']
resourceNames: ['polaris-dashboard']
verbs: ['get']
---
# RoleBinding: Grant Headlamp service account access
@@ -145,11 +145,11 @@ spec:
- name: headlamp
image: ghcr.io/headlamp-k8s/headlamp:v0.39.0
args:
- "-in-cluster"
- "-plugins-dir=/headlamp/plugins"
- '-in-cluster'
- '-plugins-dir=/headlamp/plugins'
env:
- name: HEADLAMP_CONFIG_WATCH_PLUGINS
value: "false" # CRITICAL: Must be false for plugin manager
value: 'false' # CRITICAL: Must be false for plugin manager
ports:
- name: http
containerPort: 4466
+13 -34
View File
@@ -46,7 +46,6 @@ kubectl -n kube-system get svc headlamp
### Deployment
- [ ] Plugin installed via Plugin Manager or sidecar init container
- [ ] `config.watchPlugins: false` set in Headlamp configuration
- [ ] RBAC Role and RoleBinding applied
- [ ] NetworkPolicies configured (if using strict network policies)
- [ ] Headlamp pods running with 2+ replicas (high availability)
@@ -121,7 +120,7 @@ metadata:
namespace: polaris
subjects:
- kind: Group
name: system:authenticated # All authenticated users
name: system:authenticated # All authenticated users
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
@@ -134,7 +133,7 @@ For fine-grained control, bind specific users or groups:
```yaml
subjects:
- kind: Group
name: sre-team # Only SRE team
name: sre-team # Only SRE team
apiGroup: rbac.authorization.k8s.io
```
@@ -185,12 +184,12 @@ Kubernetes audit logs record every service proxy request:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata # Log metadata only (not full request/response)
verbs: ["get"]
- level: Metadata # Log metadata only (not full request/response)
verbs: ['get']
resources:
- group: ""
resources: ["services/proxy"]
namespaces: ["polaris"]
- group: ''
resources: ['services/proxy']
namespaces: ['polaris']
```
### Data Sensitivity
@@ -354,8 +353,8 @@ spec:
labels:
severity: warning
annotations:
summary: "Headlamp pod not ready"
description: "Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} has been not ready for 5 minutes."
summary: 'Headlamp pod not ready'
description: 'Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} has been not ready for 5 minutes.'
```
## Performance Tuning
@@ -414,12 +413,14 @@ Adjust based on cluster size and user count.
If Headlamp or plugin becomes unavailable:
1. **Verify Polaris is running:**
```bash
kubectl -n polaris get pods
kubectl -n polaris get svc polaris-dashboard
```
2. **Redeploy Headlamp:**
```bash
helm upgrade --install headlamp headlamp/headlamp \
--namespace kube-system \
@@ -427,11 +428,13 @@ If Headlamp or plugin becomes unavailable:
```
3. **Reapply RBAC:**
```bash
kubectl apply -f polaris-plugin-rbac.yaml
```
4. **Verify plugin files:**
```bash
kubectl -n kube-system exec deployment/headlamp -- \
ls /headlamp/plugins/headlamp-polaris-plugin/
@@ -442,30 +445,6 @@ If Headlamp or plugin becomes unavailable:
## Known Issues
### Plugin Loading Issue (Headlamp v0.39.0+)
**Symptom:** Plugin appears in Settings but not in sidebar
**Cause:** `config.watchPlugins: true` (default) treats catalog plugins as development plugins
**Fix:**
```yaml
config:
watchPlugins: false # Required for plugin manager
```
**Root Cause:**
With `watchPlugins: true`, Headlamp backend serves plugin metadata but frontend never executes the JavaScript. This causes plugins to appear in Settings but no sidebar/routes/settings work.
**Documentation:** See `deployment/PLUGIN_LOADING_FIX.md` in repository for full analysis.
**After Fix:**
- Restart Headlamp deployment
- Hard refresh browser (**Cmd+Shift+R** / **Ctrl+Shift+R**)
### Skipped Count Limitation
**Symptom:** "Skipped" count in UI is lower than native Polaris dashboard
+60 -40
View File
@@ -18,13 +18,13 @@ Comprehensive guide to testing the Headlamp Polaris Plugin, covering unit tests,
The Headlamp Polaris Plugin uses a multi-layered testing approach:
| Test Type | Framework | Purpose | Location |
|-----------|-----------|---------|----------|
| **Unit Tests** | Vitest | Test individual functions and components in isolation | `src/**/*.test.ts(x)` |
| **E2E Tests** | Playwright | Test complete user flows against live Headlamp instance | `e2e/*.spec.ts` |
| **Type Checking** | TypeScript | Ensure type safety across codebase | `tsc --noEmit` |
| **Linting** | ESLint | Enforce code style and catch common errors | `eslint src/` |
| **Formatting** | Prettier | Maintain consistent code formatting | `prettier --check src/` |
| Test Type | Framework | Purpose | Location |
| ----------------- | ---------- | ------------------------------------------------------- | ----------------------- |
| **Unit Tests** | Vitest | Test individual functions and components in isolation | `src/**/*.test.ts(x)` |
| **E2E Tests** | Playwright | Test complete user flows against live Headlamp instance | `e2e/*.spec.ts` |
| **Type Checking** | TypeScript | Ensure type safety across codebase | `tsc --noEmit` |
| **Linting** | ESLint | Enforce code style and catch common errors | `eslint src/` |
| **Formatting** | Prettier | Maintain consistent code formatting | `prettier --check src/` |
### Test Philosophy
@@ -178,7 +178,9 @@ describe('DashboardView', () => {
const mockData = {
DisplayName: 'test-cluster',
ClusterInfo: { Version: '1.27', Nodes: 3, Pods: 100, Namespaces: 10, Controllers: 50 },
Results: [/* ... */],
Results: [
/* ... */
],
};
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
@@ -197,6 +199,7 @@ describe('DashboardView', () => {
### What to Unit Test
**Do test:**
- Pure functions (score calculation, filtering, data transformation)
- Data parsing and validation
- Utility functions
@@ -204,6 +207,7 @@ describe('DashboardView', () => {
- Edge cases (empty arrays, null values, invalid input)
**Don't test:**
- Third-party libraries (Headlamp, React)
- Simple prop passing
- Trivial getters/setters
@@ -242,6 +246,7 @@ npx playwright show-trace test-results/<test-name>/trace.zip
**1. Headlamp Instance**
E2E tests require a running Headlamp instance with:
- Polaris plugin installed (version being tested)
- Polaris dashboard deployed and accessible
- RBAC configured (service proxy permissions)
@@ -296,32 +301,32 @@ AUTHENTIK_PASSWORD=secret
**File:** `e2e/polaris.spec.ts`
| Test | Description | Validates |
|------|-------------|-----------|
| `sidebar contains Polaris entry` | Polaris appears in sidebar | Plugin registration |
| `overview page renders cluster score` | Score displayed on overview | Data fetching, rendering |
| `namespaces page renders table` | Namespace table loads | Data parsing, table rendering |
| `namespace detail drawer opens` | Clicking namespace shows drawer | Navigation, drawer UI |
| `namespace detail drawer closes with Escape` | Keyboard shortcut works | Keyboard navigation |
| `namespace detail drawer opens from URL hash` | Direct URL navigation | URL routing, deep linking |
| Test | Description | Validates |
| --------------------------------------------- | ------------------------------- | ----------------------------- |
| `sidebar contains Polaris entry` | Polaris appears in sidebar | Plugin registration |
| `overview page renders cluster score` | Score displayed on overview | Data fetching, rendering |
| `namespaces page renders table` | Namespace table loads | Data parsing, table rendering |
| `namespace detail drawer opens` | Clicking namespace shows drawer | Navigation, drawer UI |
| `namespace detail drawer closes with Escape` | Keyboard shortcut works | Keyboard navigation |
| `namespace detail drawer opens from URL hash` | Direct URL navigation | URL routing, deep linking |
**File:** `e2e/settings.spec.ts`
| Test | Description | Validates |
|------|-------------|-----------|
| `plugin settings page is accessible` | Settings page loads | Settings registration |
| `refresh interval can be changed` | Dropdown works | User preference persistence |
| `dashboard URL can be customized` | Input field works | URL configuration |
| `connection test button works` | Test functionality | API connectivity validation |
| Test | Description | Validates |
| ------------------------------------ | ------------------- | --------------------------- |
| `plugin settings page is accessible` | Settings page loads | Settings registration |
| `refresh interval can be changed` | Dropdown works | User preference persistence |
| `dashboard URL can be customized` | Input field works | URL configuration |
| `connection test button works` | Test functionality | API connectivity validation |
**File:** `e2e/appbar.spec.ts`
| Test | Description | Validates |
|------|-------------|-----------|
| `app bar displays Polaris badge` | Badge visible in header | App bar integration |
| `badge shows cluster score` | Score matches dashboard | Data consistency |
| `clicking badge navigates to overview` | Navigation works | App bar action |
| `badge color reflects score` | Red/yellow/green based on score | Visual feedback |
| Test | Description | Validates |
| -------------------------------------- | ------------------------------- | ------------------- |
| `app bar displays Polaris badge` | Badge visible in header | App bar integration |
| `badge shows cluster score` | Score matches dashboard | Data consistency |
| `clicking badge navigates to overview` | Navigation works | App bar action |
| `badge color reflects score` | Red/yellow/green based on score | Visual feedback |
### Writing E2E Tests
@@ -385,6 +390,7 @@ npx playwright test --debug
```
This opens Playwright Inspector where you can:
- Step through each test action
- Inspect page state
- Edit test selectors live
@@ -455,12 +461,12 @@ jobs:
Configure in GitHub repository settings (Settings → Secrets and variables → Actions):
| Secret | Required | Description |
|--------|----------|-------------|
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to configured instance) |
| `AUTHENTIK_USERNAME` | OIDC | Authentik username/email for CI user |
| `AUTHENTIK_PASSWORD` | OIDC | Authentik password |
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
| Secret | Required | Description |
| -------------------- | -------- | ------------------------------------------------------- |
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to configured instance) |
| `AUTHENTIK_USERNAME` | OIDC | Authentik username/email for CI user |
| `AUTHENTIK_PASSWORD` | OIDC | Authentik password |
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
@@ -478,11 +484,11 @@ Trigger workflows manually from GitHub Actions UI:
### Current Coverage
| Category | Coverage | Notes |
|----------|----------|-------|
| **API Functions** | 95% | Core utilities fully tested |
| **React Components** | 60% | Focus on critical render paths |
| **E2E User Flows** | 80% | Main features covered |
| Category | Coverage | Notes |
| -------------------- | -------- | ------------------------------ |
| **API Functions** | 95% | Core utilities fully tested |
| **React Components** | 60% | Focus on critical render paths |
| **E2E User Flows** | 80% | Main features covered |
### Coverage Goals
@@ -507,18 +513,22 @@ open coverage/index.html
### Unit Testing
1. **Test behavior, not implementation**
-`expect(computeScore({ total: 100, pass: 75 })).toBe(75)`
-`expect(mockInternalFunction).toHaveBeenCalled()`
2. **Use descriptive test names**
-`it('returns 0 when total checks is zero')`
-`it('works')`
3. **One assertion per test (when possible)**
- Makes failures easier to debug
- Exceptions: testing multiple properties of same object
4. **Mock external dependencies**
- Mock API calls, context providers, external libraries
- Don't mock the code you're testing
@@ -529,27 +539,33 @@ open coverage/index.html
### E2E Testing
1. **Use semantic selectors**
-`page.getByRole('button', { name: 'Close' })`
-`page.getByText('Polaris — Overview')`
-`page.locator('.MuiButton-root')`
2. **Wait for visibility, not arbitrary timeouts**
-`await expect(element).toBeVisible()`
-`await page.waitForTimeout(5000)`
3. **Keep tests independent**
- Each test should work in isolation
- Don't rely on previous tests' state
4. **Test complete user flows**
- Navigate → Interact → Verify outcome
- Don't just test page loads
5. **Clean up after tests**
- Close drawers/modals
- Reset state if needed
6. **Use storage state for auth**
- Reuse authenticated session across tests
- Faster than logging in for every test
@@ -560,15 +576,18 @@ open coverage/index.html
### General
1. **Run tests before committing**
```bash
npm run build && npm run lint && npm test
```
2. **Fix failing tests immediately**
- Don't commit failing tests
- Don't skip tests to "fix later"
3. **Update tests when changing code**
- Tests are documentation
- Keep them in sync with implementation
@@ -604,7 +623,7 @@ Check mocks are returning expected structure:
```typescript
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
data: mockData, // Ensure mockData has all required fields
data: mockData, // Ensure mockData has all required fields
loading: false,
error: null,
refresh: vi.fn(),
@@ -624,6 +643,7 @@ npm run e2e:headed
```
Common causes:
- Element hasn't rendered yet (use `toBeVisible()` instead of `toBeDefined()`)
- Wrong selector (use Playwright Inspector to verify)
- Authentication failed (check token/credentials)
+16 -30
View File
@@ -33,16 +33,19 @@ The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headla
#### Via Headlamp UI
1. **Navigate to Plugin Settings:**
- Open Headlamp in your browser
- Go to **Settings → Plugins**
- Click the **Catalog** tab
2. **Search and Install:**
- Search for "Polaris"
- Find "Headlamp Polaris Plugin"
- Click **Install**
3. **Hard Refresh Browser:**
- **Mac:** Cmd+Shift+R
- **Windows/Linux:** Ctrl+Shift+R
@@ -58,7 +61,6 @@ Add to your Headlamp Helm values:
# headlamp-values.yaml
config:
pluginsDir: /headlamp/plugins
watchPlugins: false # CRITICAL for v0.39.0+
pluginsManager:
enabled: true
@@ -76,23 +78,6 @@ helm upgrade --install headlamp headlamp/headlamp \
Then install the plugin via Headlamp UI as described above.
#### Critical Configuration for Headlamp v0.39.0+
**⚠️ IMPORTANT:** You **must** set `config.watchPlugins: false` or the plugin will not load.
**Why?**
- With `watchPlugins: true` (default), catalog-managed plugins are treated as "development directory" plugins
- This causes the backend to serve metadata but the frontend never executes the JavaScript
- Result: Plugin appears in Settings but no sidebar/routes/settings work
**Fix:**
```yaml
config:
watchPlugins: false # Required for plugin manager
```
See [deployment/PLUGIN_LOADING_FIX.md](../deployment/production.md#plugin-loading-issue-headlamp-v0390) for full root cause analysis.
### Option 2: Sidecar Container
**Best for:** Controlled plugin versions, air-gapped environments, specific version pinning
@@ -105,7 +90,6 @@ This method uses an init container to download and install the plugin from a tar
# headlamp-values.yaml
config:
pluginsDir: /headlamp/plugins
watchPlugins: false
initContainers:
- name: install-polaris-plugin
@@ -204,7 +188,7 @@ config:
volumes:
- name: plugins
hostPath:
path: /path/to/plugins # Where you extracted the tarball
path: /path/to/plugins # Where you extracted the tarball
type: Directory
volumeMounts:
@@ -316,6 +300,7 @@ kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=
### 4. Verify Installation
**UI Verification:**
1. Navigate to **Settings → Plugins**
2. Verify "headlamp-polaris-plugin" is listed
3. Check version matches installed version
@@ -351,22 +336,22 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy
**Symptom:** Plugin listed in Settings → Plugins but no "Polaris" entry in sidebar
**Causes:**
1. `watchPlugins: true` (should be `false` for v0.39.0+)
2. Browser cache not cleared
3. Plugin JavaScript failed to load
1. Browser cache not cleared
2. Plugin JavaScript failed to load
3. Plugin files not properly installed
**Solution:**
```bash
# 1. Check Headlamp config
kubectl -n kube-system get configmap headlamp -o yaml | grep watchPlugins
# 1. Verify plugin files exist
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
ls -la /headlamp/plugins/headlamp-polaris-plugin/
# If "true" or missing, fix it:
kubectl -n kube-system edit configmap headlamp
# Set: watchPlugins: "false"
# Expected: dist/, package.json present
# 2. Restart Headlamp
kubectl -n kube-system rollout restart deployment/headlamp
# 2. Check Headlamp logs for plugin errors
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
# 3. Hard refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
@@ -389,6 +374,7 @@ See [RBAC Issues](../troubleshooting/rbac-issues.md) for detailed debugging.
**Symptom:** Error loading Polaris data, 404 in browser console
**Causes:**
1. Polaris not deployed
2. Polaris service name incorrect
3. Polaris namespace incorrect
+13 -13
View File
@@ -4,12 +4,12 @@ Before installing the Headlamp Polaris Plugin, ensure your environment meets the
## Required Components
| Requirement | Minimum Version | Recommended Version |
| -------------------------------- | ------------------ | ------------------- |
| **Kubernetes** | v1.24+ | v1.28+ |
| **Headlamp** | v0.26+ | v0.39+ |
| **Polaris** (dashboard enabled) | Any recent release | Latest stable |
| **Browser** | Modern (ES2020+) | Latest Chrome/Firefox/Safari/Edge |
| Requirement | Minimum Version | Recommended Version |
| ------------------------------- | ------------------ | --------------------------------- |
| **Kubernetes** | v1.24+ | v1.28+ |
| **Headlamp** | v0.26+ | v0.39+ |
| **Polaris** (dashboard enabled) | Any recent release | Latest stable |
| **Browser** | Modern (ES2020+) | Latest Chrome/Firefox/Safari/Edge |
## Polaris Requirements
@@ -91,7 +91,6 @@ helm repo update
helm install headlamp headlamp/headlamp \
--namespace kube-system \
--set config.pluginsDir="/headlamp/plugins" \
--set config.watchPlugins=false \
--set pluginsManager.enabled=true
# Wait for pod to be ready
@@ -133,6 +132,7 @@ Headlamp Pod → Kubernetes API Server → Polaris Dashboard Service
```
**Required network paths:**
- Headlamp pod → Kubernetes API server (443)
- Kubernetes API server → Polaris dashboard service (80)
@@ -161,12 +161,12 @@ The plugin uses modern JavaScript features and requires:
### Tested Browsers
| Browser | Minimum Version |
| ---------------- | --------------- |
| Chrome/Chromium | 80+ |
| Firefox | 75+ |
| Safari | 13.1+ |
| Edge | 80+ |
| Browser | Minimum Version |
| --------------- | --------------- |
| Chrome/Chromium | 80+ |
| Firefox | 75+ |
| Safari | 13.1+ |
| Edge | 80+ |
## Optional Components
+8 -9
View File
@@ -29,7 +29,6 @@ Don't have these? See [Prerequisites](prerequisites.md) for installation instruc
cat <<EOF > headlamp-values.yaml
config:
pluginsDir: /headlamp/plugins
watchPlugins: false # CRITICAL for v0.39.0+
pluginsManager:
enabled: true
@@ -86,14 +85,17 @@ EOF
### UI Verification
1. **Check Plugin is Loaded:**
- Go to **Settings → Plugins**
- Verify "headlamp-polaris-plugin" is listed
2. **Check Sidebar:**
- Look for **Polaris** entry in the left sidebar
- If not visible, hard refresh: **Cmd+Shift+R** / **Ctrl+Shift+R**
3. **View Overview Dashboard:**
- Click **Polaris** in sidebar
- Overview page loads with:
- Cluster score gauge
@@ -137,6 +139,7 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy
Navigate to **Polaris → Overview**:
- **Cluster Score Gauge:** Overall cluster health (0-100%)
- Green (≥80%): Excellent
- Yellow (50-79%): Needs improvement
- Red (<50%): Critical issues
@@ -181,15 +184,11 @@ Cluster score badge in top navigation:
### Plugin Not in Sidebar
```bash
# Check Headlamp config
kubectl -n kube-system get configmap headlamp -o yaml | grep watchPlugins
# Verify plugin files exist
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \
ls /headlamp/plugins/headlamp-polaris-plugin/
# If "true" or missing, set to false:
kubectl -n kube-system edit configmap headlamp
# Set: watchPlugins: "false"
# Restart Headlamp
kubectl -n kube-system rollout restart deployment/headlamp
# If missing, reinstall via Headlamp UI or sidecar method
# Hard refresh browser
# Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
+18 -20
View File
@@ -4,17 +4,17 @@ Quick diagnosis guide and common issues for the Headlamp Polaris Plugin.
## Quick Diagnosis
| Symptom | Likely Cause | Quick Fix | Details |
|---------|-------------|-----------|---------|
| **Plugin not in sidebar** | Headlamp v0.39.0+ plugin loading issue | Set `config.watchPlugins: false` and hard refresh (Cmd+Shift+R) | [Common Issues](common-issues.md#plugin-not-in-sidebar) |
| **403 Access Denied** | Missing RBAC binding for `services/proxy` | Apply Role + RoleBinding from RBAC section | [RBAC Issues](rbac-issues.md) |
| **404 or 503** | Polaris not installed, or dashboard disabled | Install Polaris with `dashboard.enabled: true` in `polaris` namespace | [Common Issues](common-issues.md#404-not-found) |
| **Dark mode white backgrounds** | Old plugin version | Upgrade to v0.3.5+ and hard refresh browser | [Common Issues](common-issues.md#dark-mode-issues) |
| **Settings page empty** | Old plugin version | Upgrade to v0.3.3+ | [Common Issues](common-issues.md#settings-page-empty) |
| **No data / infinite spinner** | Network policy or Polaris pod down | Check network policies and `kubectl get pods -n polaris` | [Network Problems](network-problems.md) |
| **Namespace drawer white** | CSS variable issue | Update to v0.3.5+ with `--mui-palette-background-paper` | [Common Issues](common-issues.md#dark-mode-issues) |
| **Cluster score not updating** | Auto-refresh disabled or interval too long | Check Settings → Plugins → Polaris refresh interval | [Common Issues](common-issues.md#data-not-refreshing) |
| **Custom URL not working** | CORS or incorrect URL format | Test with curl, check CORS headers | [Network Problems](network-problems.md#cors-issues) |
| Symptom | Likely Cause | Quick Fix | Details |
| ------------------------------- | -------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------- |
| **Plugin not in sidebar** | Browser cache or plugin not installed | Hard refresh browser (Cmd+Shift+R) and verify plugin files exist | [Common Issues](common-issues.md#plugin-not-in-sidebar) |
| **403 Access Denied** | Missing RBAC binding for `services/proxy` | Apply Role + RoleBinding from RBAC section | [RBAC Issues](rbac-issues.md) |
| **404 or 503** | Polaris not installed, or dashboard disabled | Install Polaris with `dashboard.enabled: true` in `polaris` namespace | [Common Issues](common-issues.md#404-not-found) |
| **Dark mode white backgrounds** | Old plugin version | Upgrade to v0.3.5+ and hard refresh browser | [Common Issues](common-issues.md#dark-mode-issues) |
| **Settings page empty** | Old plugin version | Upgrade to v0.3.3+ | [Common Issues](common-issues.md#settings-page-empty) |
| **No data / infinite spinner** | Network policy or Polaris pod down | Check network policies and `kubectl get pods -n polaris` | [Network Problems](network-problems.md) |
| **Namespace drawer white** | CSS variable issue | Update to v0.3.5+ with `--mui-palette-background-paper` | [Common Issues](common-issues.md#dark-mode-issues) |
| **Cluster score not updating** | Auto-refresh disabled or interval too long | Check Settings → Plugins → Polaris refresh interval | [Common Issues](common-issues.md#data-not-refreshing) |
| **Custom URL not working** | CORS or incorrect URL format | Test with curl, check CORS headers | [Network Problems](network-problems.md#cors-issues) |
## Detailed Guides
@@ -56,11 +56,6 @@ kubectl -n kube-system logs deployment/headlamp | grep -i polaris
### Plugin Loading Verification
```bash
# Check Headlamp config
kubectl -n kube-system get configmap headlamp -o yaml | grep watchPlugins
# Expected: watchPlugins: "false"
# Verify plugin files exist
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
ls -la /headlamp/plugins/headlamp-polaris-plugin/
@@ -115,6 +110,7 @@ kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \
3. Look for errors containing "polaris" or "plugin"
**Common errors:**
- `createSvgIcon is not defined` → MUI import issue (plugin bug)
- `403 Forbidden` → RBAC permission denied
- `404 Not Found` → Polaris not installed or wrong URL
@@ -132,12 +128,12 @@ kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \
```javascript
// Open browser console and run:
localStorage.getItem('polaris-plugin-refresh-interval')
localStorage.getItem('polaris-plugin-dashboard-url')
localStorage.getItem('polaris-plugin-refresh-interval');
localStorage.getItem('polaris-plugin-dashboard-url');
// Reset to defaults:
localStorage.removeItem('polaris-plugin-refresh-interval')
localStorage.removeItem('polaris-plugin-dashboard-url')
localStorage.removeItem('polaris-plugin-refresh-interval');
localStorage.removeItem('polaris-plugin-dashboard-url');
```
## Still Having Issues?
@@ -145,11 +141,13 @@ localStorage.removeItem('polaris-plugin-dashboard-url')
If the quick diagnosis doesn't resolve your issue:
1. **Check detailed guides:**
- [Common Issues](common-issues.md)
- [RBAC Issues](rbac-issues.md)
- [Network Problems](network-problems.md)
2. **Review documentation:**
- [Installation Guide](../getting-started/installation.md)
- [RBAC Permissions](../user-guide/rbac-permissions.md)
- [Deployment Guide](../deployment/kubernetes.md)
+65 -28
View File
@@ -20,34 +20,17 @@ This guide covers common issues encountered when using the Headlamp Polaris Plug
## Plugin Not Showing in Sidebar
### Symptoms
- Plugin appears in Settings → Plugins but sidebar entry is missing
- No "Polaris" section in navigation
- Routes like `/polaris` return 404 or blank page
### Common Causes
**1. Headlamp v0.39.0+ Plugin Loading Issue**
**Root Cause**: Headlamp v0.39.0+ changed plugin loading behavior. With `config.watchPlugins: true` (default), catalog-managed plugins are treated as "development directory" plugins, causing the backend to serve metadata but frontend to never execute the JavaScript.
**Solution**: Set `config.watchPlugins: false` in Headlamp configuration.
```yaml
# HelmRelease values
config:
watchPlugins: false # CRITICAL for plugin manager
```
After applying this change:
1. Restart Headlamp pod
2. Hard refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
3. Clear browser cache if needed
**References**: See `deployment/PLUGIN_LOADING_FIX.md` for complete root cause analysis.
**2. Plugin Not Installed**
**1. Plugin Not Installed**
**Check plugin installation**:
```bash
# View Headlamp pod logs (plugin sidecar)
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
@@ -58,23 +41,26 @@ kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
```
**Verify plugin files exist**:
```bash
kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
# Should show: headlamp-polaris-plugin/
```
**3. JavaScript Cached by Browser**
**2. JavaScript Cached by Browser**
After upgrading the plugin, old JavaScript may be cached.
**Solution**:
- Hard refresh: Cmd+Shift+R (macOS) or Ctrl+Shift+R (Linux/Windows)
- Clear browser cache for Headlamp domain
- Open DevTools → Application → Clear Storage → Clear all
**4. Plugin Disabled in Settings**
**3. Plugin Disabled in Settings**
**Check Settings → Plugins**:
- Navigate to Headlamp Settings → Plugins
- Ensure "Polaris" plugin is enabled (toggle should be ON)
- If disabled, enable it and refresh the page
@@ -84,11 +70,13 @@ After upgrading the plugin, old JavaScript may be cached.
## 403 Forbidden Error
### Symptoms
- Error message: "Error loading Polaris audit data: 403 Forbidden"
- Browser console shows 403 response from API proxy
- Plugin sidebar shows but data fails to load
### Root Cause
User or service account lacks `services/proxy` permission on `polaris-dashboard` service in the `polaris` namespace.
### Solution
@@ -96,11 +84,13 @@ User or service account lacks `services/proxy` permission on `polaris-dashboard`
**1. Verify RBAC Configuration**
Check if Role exists:
```bash
kubectl get role polaris-proxy-reader -n polaris -o yaml
```
Expected output:
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
@@ -108,20 +98,22 @@ metadata:
name: polaris-proxy-reader
namespace: polaris
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
- apiGroups: ['']
resources: ['services/proxy']
resourceNames: ['polaris-dashboard']
verbs: ['get']
```
**2. Verify RoleBinding**
For service account mode:
```bash
kubectl get rolebinding headlamp-polaris-proxy -n polaris -o yaml
```
Expected subjects:
```yaml
subjects:
- kind: ServiceAccount
@@ -130,6 +122,7 @@ subjects:
```
For OIDC mode:
```bash
kubectl get rolebinding -n polaris -o yaml | grep -A 5 polaris-proxy-reader
```
@@ -172,6 +165,7 @@ EOF
**4. Test RBAC Permissions**
Service account mode:
```bash
# Impersonate Headlamp service account
kubectl auth can-i get services/proxy \
@@ -182,6 +176,7 @@ kubectl auth can-i get services/proxy \
```
OIDC mode (test as yourself):
```bash
kubectl auth can-i get services/proxy \
--resource-name=polaris-dashboard \
@@ -192,6 +187,7 @@ kubectl auth can-i get services/proxy \
**5. Restart Headlamp**
After applying RBAC changes:
```bash
kubectl rollout restart deployment headlamp -n kube-system
```
@@ -201,11 +197,13 @@ kubectl rollout restart deployment headlamp -n kube-system
## 404 Not Found Error
### Symptoms
- Error message: "Error loading Polaris audit data: 404 Not Found"
- Service proxy request returns 404
- Polaris dashboard not reachable
### Root Cause
Polaris dashboard service doesn't exist or is in a different namespace.
### Solution
@@ -213,12 +211,14 @@ Polaris dashboard service doesn't exist or is in a different namespace.
**1. Verify Polaris Installation**
Check if Polaris is installed:
```bash
kubectl get pods -n polaris
# Expected: polaris-dashboard-* pod running
```
Check if service exists:
```bash
kubectl get service polaris-dashboard -n polaris
# Expected: ClusterIP service on port 80
@@ -227,6 +227,7 @@ kubectl get service polaris-dashboard -n polaris
**2. Verify Service Name and Port**
The plugin expects:
- **Namespace**: `polaris`
- **Service Name**: `polaris-dashboard`
- **Port**: `80` (or named port `dashboard`)
@@ -246,11 +247,13 @@ If this returns 404, Polaris service is not configured correctly.
**4. Check Polaris Dashboard Configuration**
Verify Polaris is running with dashboard enabled:
```bash
kubectl get deployment polaris-dashboard -n polaris -o yaml | grep -A 5 dashboard
```
If `dashboard.enabled: false` in Helm values, enable it:
```yaml
# values.yaml
dashboard:
@@ -260,6 +263,7 @@ dashboard:
**5. Reinstall Polaris**
If Polaris is missing or misconfigured:
```bash
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm upgrade --install polaris fairwinds-stable/polaris \
@@ -273,21 +277,25 @@ helm upgrade --install polaris fairwinds-stable/polaris \
## Plugin Settings Page Empty
### Symptoms
- Settings → Polaris shows title but no content
- Refresh interval and dashboard URL fields not visible
### Root Cause (Fixed in v0.3.3)
Plugin settings registration name didn't match `package.json` name.
### Solution
Upgrade to v0.3.3 or later:
```bash
# Via Headlamp UI: Settings → Plugins → Update
# Or redeploy with latest version
```
If manually installing, ensure plugin name matches `package.json`:
```typescript
registerPluginSettings('headlamp-polaris-plugin', PolarisSettings, true);
// NOT 'polaris' — must match package.json name
@@ -298,6 +306,7 @@ registerPluginSettings('headlamp-polaris-plugin', PolarisSettings, true);
## Dark Mode Issues
### Symptoms
- Drawer background remains white in dark mode
- Text is hard to read in dark mode
- Theme colors don't match Headlamp UI
@@ -308,6 +317,7 @@ Upgrade to v0.3.5 or later for complete dark mode support.
**Verify CSS Variables**:
The plugin uses MUI CSS variables for theming:
- `--mui-palette-background-default` (drawer background)
- `--mui-palette-text-primary` (text color)
- `--mui-palette-primary-main` (links, buttons)
@@ -317,6 +327,7 @@ These automatically adapt to Headlamp's theme (light/dark/system).
**Hard Refresh Required**:
After upgrading from v0.3.4 or earlier, hard refresh your browser:
- macOS: Cmd+Shift+R
- Linux/Windows: Ctrl+Shift+R
@@ -328,6 +339,7 @@ If hard refresh doesn't help, clear cache for Headlamp domain.
## Data Not Loading / Infinite Spinner
### Symptoms
- Plugin shows "Loading Polaris audit data..." forever
- No error message in UI
- Data never appears
@@ -339,6 +351,7 @@ If hard refresh doesn't help, clear cache for Headlamp domain.
Open DevTools (F12) → Console tab.
Look for:
- Network errors (CORS, timeouts, 5xx responses)
- JavaScript errors
- Failed API requests
@@ -348,6 +361,7 @@ Look for:
Open DevTools → Network tab → Filter by "results.json"
Expected request:
```
GET /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
Status: 200
@@ -355,6 +369,7 @@ Response: JSON data
```
Common issues:
- **Status 0 / Failed**: Network policy blocking request
- **Status 403**: RBAC issue (see [403 Forbidden Error](#403-forbidden-error))
- **Status 404**: Service not found (see [404 Not Found Error](#404-not-found-error))
@@ -378,6 +393,7 @@ curl http://localhost:8080/results.json
**4. Check Network Policies**
If your cluster uses NetworkPolicies:
```bash
kubectl get networkpolicy -n polaris
```
@@ -385,6 +401,7 @@ kubectl get networkpolicy -n polaris
Ensure API server (or Headlamp pod) can reach Polaris dashboard.
**Example fix**:
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
@@ -399,7 +416,7 @@ spec:
- Ingress
ingress:
- from:
- namespaceSelector: {} # Allow from all namespaces (API server)
- namespaceSelector: {} # Allow from all namespaces (API server)
ports:
- protocol: TCP
port: 8080
@@ -408,6 +425,7 @@ spec:
**5. Increase Timeout / Disable Auto-Refresh**
If Polaris responds slowly:
- Open Settings → Polaris
- Increase refresh interval to 10+ minutes
- Or set to "Manual only" to disable auto-refresh
@@ -423,6 +441,7 @@ If Polaris responds slowly:
**Cause**: Network request failed (CORS, network policy, timeout)
**Solution**:
1. Check Network tab for actual HTTP status
2. Verify network policies allow API server → Polaris
3. Check Polaris pod is running
@@ -434,6 +453,7 @@ If Polaris responds slowly:
**Cause**: API returned HTML (error page) instead of JSON
**Solution**:
1. Check Network tab response body (likely 404 or 500 error page)
2. Verify Polaris service exists and is healthy
3. Check service proxy URL is correct
@@ -453,6 +473,7 @@ If Polaris responds slowly:
**Cause**: Polaris returned empty or malformed response
**Solution**:
1. Check Polaris logs for errors
2. Verify Polaris is scanning the cluster (check audit timestamp)
3. Test `/results.json` endpoint directly
@@ -538,11 +559,13 @@ kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
### Sidecar Fails to Install Plugin
**Symptoms**:
- Plugin sidecar logs show download errors
- Plugin directory is empty
- Settings → Plugins shows nothing
**Check sidecar logs**:
```bash
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
```
@@ -566,11 +589,13 @@ Error: 404 Not Found
```
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
```bash
kubectl get configmap headlamp-plugin-config -n kube-system -o yaml
```
Expected format:
```
https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
```
@@ -580,6 +605,7 @@ https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/downloa
**3. Permission denied writing to /headlamp/plugins**
**Solution**: Ensure volume mount is writable:
```yaml
volumeMounts:
- name: plugins
@@ -591,17 +617,18 @@ volumeMounts:
### Plugin Manager Not Working
**Symptoms**:
- Headlamp → Settings → Plugins shows "Catalog" tab but plugins don't install
- "Install" button does nothing
**Root Cause**: Plugin manager requires `config.pluginsDir` to be set.
**Solution**: Configure Headlamp for plugin manager:
```yaml
# HelmRelease values
config:
pluginsDir: /headlamp/plugins
watchPlugins: false # CRITICAL for v0.39.0+
```
---
@@ -609,25 +636,30 @@ config:
## ArtifactHub Sync Delays
### Symptoms
- New version released on GitHub but not showing in ArtifactHub
- Headlamp plugin catalog shows old version
### Root Cause
ArtifactHub pulls metadata every 30 minutes. There is no webhook or push mechanism.
### Solution
**Wait 30 minutes** after pushing a GitHub release, then check:
```
https://artifacthub.io/packages/headlamp/headlamp-polaris-plugin/headlamp-polaris-plugin
```
**Verify metadata**:
1. Check `artifacthub-pkg.yml` is in repository root
2. Check `headlamp/plugin/archive-url` points to GitHub release
3. Check `headlamp/plugin/archive-checksum` matches tarball SHA256
**Force sync** (ArtifactHub UI):
- Log in to ArtifactHub as package maintainer
- Go to package settings
- Click "Reindex now"
@@ -643,23 +675,28 @@ If none of these solutions work, gather debugging information and open an issue:
### Required Information
1. **Version Information**:
```bash
kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image:
```
2. **Plugin Version**:
- Check Settings → Plugins in Headlamp UI
- Or: `kubectl exec -n kube-system deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
3. **Browser Console Output**:
- Open DevTools (F12) → Console
- Screenshot or copy errors
4. **Network Tab**:
- Open DevTools → Network
- Screenshot failed requests to `results.json`
5. **Pod Logs**:
```bash
kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
+1 -1
View File
@@ -87,7 +87,7 @@ dashboard:
enabled: true
env:
- name: CORS_ALLOWED_ORIGINS
value: "https://headlamp.example.com"
value: 'https://headlamp.example.com'
```
Test CORS headers:
+1 -1
View File
@@ -70,7 +70,7 @@ metadata:
namespace: polaris
subjects:
- kind: Group
name: system:authenticated # All authenticated users
name: system:authenticated # All authenticated users
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
+46 -14
View File
@@ -28,12 +28,14 @@ Access plugin settings via **Settings → Plugins → Polaris** in the Headlamp
### Impact
**Affects:**
- Dashboard overview page
- Namespace list and detail views
- Inline audit sections on resource pages
- App bar score badge
**API Load:**
- Each refresh triggers one HTTP GET to Polaris dashboard
- Each request is logged in Kubernetes audit logs
- Longer intervals reduce API server and audit log pressure
@@ -41,16 +43,19 @@ Access plugin settings via **Settings → Plugins → Polaris** in the Headlamp
### Performance Considerations
**For small clusters (<100 pods):**
- Recommended: 5 minutes (default)
- Acceptable: 1 minute (if real-time data is critical)
**For large clusters (>1000 pods):**
- Recommended: 10-30 minutes
- Reason: Reduces audit log volume and API server load
- Example: 10 users × 1-minute refresh = ~14,400 audit logs/day
- Example: 10 users × 30-minute refresh = ~480 audit logs/day
**For production environments:**
- Start with 5 minutes
- Monitor API server metrics and audit log volume
- Increase interval if needed
@@ -62,6 +67,7 @@ Access plugin settings via **Settings → Plugins → Polaris** in the Headlamp
### Default Configuration
**Service proxy path (default):**
```
/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/
```
@@ -69,6 +75,7 @@ Access plugin settings via **Settings → Plugins → Polaris** in the Headlamp
This uses the Kubernetes API server to proxy requests to the Polaris dashboard service in the `polaris` namespace.
**Advantages:**
- Uses existing Headlamp authentication (service account or user token)
- Works with Headlamp's OIDC and token-auth modes
- No additional RBAC or network configuration needed
@@ -85,6 +92,7 @@ https://polaris.example.com/
```
**Requirements:**
- Polaris dashboard must be accessible from browser
- CORS must be configured on Polaris to allow Headlamp origin
- HTTPS recommended for production
@@ -98,6 +106,7 @@ If Polaris is deployed in a different namespace:
```
**Requirements:**
- Update RBAC Role namespace to match
- Service name must still be `polaris-dashboard` (or adjust in URL)
@@ -131,39 +140,43 @@ http://localhost:8080/
**What it does:** Verifies the plugin can reach the Polaris dashboard and fetch audit data.
**To test:**
1. Enter Dashboard URL in settings
2. Click **Test Connection**
3. Wait for response (2-5 seconds)
**Success Response:**
```
✓ Connected to Polaris v4.2.0
```
**Error Responses:**
| Error | Meaning | Solution |
|-------|---------|----------|
| **403 Forbidden** | RBAC permission denied | Check RBAC bindings (see [RBAC Guide](rbac-permissions.md)) |
| **404 Not Found** | Polaris service not found | Verify Polaris is running: `kubectl get svc -n polaris` |
| **503 Service Unavailable** | Polaris pod not ready | Check pod status: `kubectl get pods -n polaris` |
| **Network Error** | Cannot reach URL | Check URL format, CORS (for external), NetworkPolicies |
| **CORS Error** | Cross-origin blocked | Configure Polaris dashboard CORS headers |
| Error | Meaning | Solution |
| --------------------------- | ------------------------- | ----------------------------------------------------------- |
| **403 Forbidden** | RBAC permission denied | Check RBAC bindings (see [RBAC Guide](rbac-permissions.md)) |
| **404 Not Found** | Polaris service not found | Verify Polaris is running: `kubectl get svc -n polaris` |
| **503 Service Unavailable** | Polaris pod not ready | Check pod status: `kubectl get pods -n polaris` |
| **Network Error** | Cannot reach URL | Check URL format, CORS (for external), NetworkPolicies |
| **CORS Error** | Cross-origin blocked | Configure Polaris dashboard CORS headers |
### CORS Configuration (External Polaris)
If using an external Polaris URL, configure CORS to allow Headlamp origin.
**Polaris Helm values:**
```yaml
dashboard:
enabled: true
env:
- name: CORS_ALLOWED_ORIGINS
value: "https://headlamp.example.com"
value: 'https://headlamp.example.com'
```
**Test CORS:**
```bash
curl -v -H "Origin: https://headlamp.example.com" \
https://polaris.example.com/results.json \
@@ -180,25 +193,29 @@ curl -v -H "Origin: https://headlamp.example.com" \
Plugin settings are stored in browser **localStorage**:
**Keys:**
- `polaris-plugin-refresh-interval` - Refresh interval in seconds (number)
- `polaris-plugin-dashboard-url` - Dashboard URL (string)
**View settings:**
```javascript
// Open browser DevTools Console (F12)
console.log('Refresh Interval:', localStorage.getItem('polaris-plugin-refresh-interval'))
console.log('Dashboard URL:', localStorage.getItem('polaris-plugin-dashboard-url'))
console.log('Refresh Interval:', localStorage.getItem('polaris-plugin-refresh-interval'));
console.log('Dashboard URL:', localStorage.getItem('polaris-plugin-dashboard-url'));
```
**Reset to defaults:**
```javascript
// Open browser DevTools Console (F12)
localStorage.removeItem('polaris-plugin-refresh-interval')
localStorage.removeItem('polaris-plugin-dashboard-url')
localStorage.removeItem('polaris-plugin-refresh-interval');
localStorage.removeItem('polaris-plugin-dashboard-url');
// Hard refresh browser: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
```
**Notes:**
- Settings are per-browser, per-user
- Private/incognito mode may clear settings on browser close
- Settings are NOT synced across devices
@@ -208,6 +225,7 @@ localStorage.removeItem('polaris-plugin-dashboard-url')
### For Development Clusters
**Recommended Settings:**
- **Refresh Interval:** 1-5 minutes (faster feedback loop)
- **Dashboard URL:** Service proxy (default)
@@ -216,6 +234,7 @@ localStorage.removeItem('polaris-plugin-dashboard-url')
### For Staging Clusters
**Recommended Settings:**
- **Refresh Interval:** 5-10 minutes (balanced)
- **Dashboard URL:** Service proxy (default)
@@ -224,6 +243,7 @@ localStorage.removeItem('polaris-plugin-dashboard-url')
### For Production Clusters
**Recommended Settings:**
- **Refresh Interval:** 10-30 minutes (reduce load)
- **Dashboard URL:** Service proxy (default)
@@ -232,6 +252,7 @@ localStorage.removeItem('polaris-plugin-dashboard-url')
### For Multi-Tenant Environments
**Recommended Settings:**
- **Refresh Interval:** 10-30 minutes (minimize per-user load)
- **Dashboard URL:** Service proxy with per-namespace RBAC
@@ -240,6 +261,7 @@ localStorage.removeItem('polaris-plugin-dashboard-url')
### For External Polaris
**Recommended Settings:**
- **Refresh Interval:** 5-10 minutes (depends on network latency)
- **Dashboard URL:** `https://polaris.example.com/`
- **CORS:** Must be configured on Polaris side
@@ -253,17 +275,19 @@ localStorage.removeItem('polaris-plugin-dashboard-url')
**Symptom:** Changes to settings revert after clicking Save
**Possible Causes:**
1. Browser blocks localStorage (privacy mode)
2. Browser extension interfering
3. JavaScript error in console
**Solution:**
1. Open browser DevTools Console (F12)
2. Check for JavaScript errors
3. Disable privacy mode or try different browser
4. Check if localStorage is enabled:
```javascript
console.log('localStorage available:', typeof localStorage !== 'undefined')
console.log('localStorage available:', typeof localStorage !== 'undefined');
```
### Settings Lost After Browser Restart
@@ -273,6 +297,7 @@ localStorage.removeItem('polaris-plugin-dashboard-url')
**Cause:** Browser privacy settings clear localStorage on exit
**Solution:**
- Use normal browsing mode (not private/incognito)
- Check browser settings for "Clear data on exit"
- Consider requesting ConfigMap-based settings (future feature)
@@ -284,6 +309,7 @@ localStorage.removeItem('polaris-plugin-dashboard-url')
**Solutions by error type:**
**403 Forbidden:**
```bash
# Verify RBAC exists
kubectl -n polaris get role polaris-proxy-reader
@@ -297,6 +323,7 @@ kubectl auth can-i get services/proxy \
```
**404 Not Found:**
```bash
# Verify Polaris is running
kubectl -n polaris get pods
@@ -310,6 +337,7 @@ helm install polaris fairwinds-stable/polaris \
```
**503 Service Unavailable:**
```bash
# Check pod status
kubectl -n polaris get pods
@@ -319,6 +347,7 @@ kubectl -n polaris logs deployment/polaris-dashboard
```
**Network Error / CORS:**
```bash
# For external Polaris, test CORS
curl -v -H "Origin: https://headlamp.example.com" \
@@ -332,15 +361,17 @@ curl -v -H "Origin: https://headlamp.example.com" \
**Symptom:** Data doesn't refresh automatically
**Check:**
1. Verify setting is saved (localStorage key exists)
2. Check browser console for errors
3. Verify Polaris is returning data (manual refresh works)
4. Ensure you're on a Polaris plugin page (not other Headlamp pages)
**Debug:**
```javascript
// Check refresh interval
console.log(localStorage.getItem('polaris-plugin-refresh-interval'))
console.log(localStorage.getItem('polaris-plugin-refresh-interval'));
// Should return: "300" (5 minutes), "600" (10 minutes), etc.
```
@@ -361,6 +392,7 @@ Before going to production, verify:
## Future Configuration Options
**Planned features:**
- ConfigMap-based settings (server-side, not localStorage)
- Per-cluster settings (multi-cluster Headlamp support)
- Webhook notifications for score changes
+19
View File
@@ -58,6 +58,7 @@ Click the refresh button to fetch the latest audit data immediately (bypasses au
Navigate to **Polaris → Namespaces** to see all namespaces with audit results.
**Table Columns:**
- **Namespace** - Clickable namespace name (opens detail panel)
- **Score** - Per-namespace score with color coding
- **Pass** - Passing checks count
@@ -72,6 +73,7 @@ Navigate to **Polaris → Namespaces** to see all namespaces with audit results.
Click any namespace to open a 1000px-wide side panel with detailed information.
**Features:**
- **Namespace Score** - Color-coded score gauge
- **Check Counts** - Pass/Warning/Danger/Skipped breakdown
- **Resource Table** - Per-resource audit results:
@@ -92,6 +94,7 @@ Polaris audit results automatically appear on resource detail pages.
### Supported Resources
Inline audit sections appear on:
- Deployments
- StatefulSets
- DaemonSets
@@ -101,6 +104,7 @@ Inline audit sections appear on:
### What's Shown
**Compact Audit Section:**
- **Score Badge** - Color-coded score
- **Check Counts** - Pass/Warning/Danger summary
- **Failing Checks Table** - Only failed checks listed:
@@ -116,6 +120,7 @@ Inline audit sections appear on:
Top-right corner of Headlamp shows a persistent cluster score badge.
**Features:**
- **Color-Coded Chip** - Green/Yellow/Red based on score
- **Shield Icon** - Visual indicator
- **Score Percentage** - e.g., "85%"
@@ -134,12 +139,14 @@ Access plugin settings via **Settings → Plugins → Polaris**.
Controls how often the plugin fetches new audit data.
**Options:**
- 1 minute - Most frequent (highest API load)
- 5 minutes - **Default** (recommended)
- 10 minutes - Moderate refresh rate
- 30 minutes - Light load (large clusters)
**Impact:**
- Affects all views (dashboard, namespaces, inline audits, app bar badge)
- Longer intervals reduce Kubernetes API audit logging
- Changes take effect immediately (no restart required)
@@ -151,11 +158,13 @@ See [Configuration Guide](configuration.md) for details.
Specifies which Polaris instance to connect to.
**Default:** Kubernetes service proxy path
```
/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/
```
**Custom Options:**
- External Polaris: `https://polaris.example.com/`
- Different namespace: `/api/v1/namespaces/custom-ns/services/polaris-dashboard:80/proxy/`
@@ -168,6 +177,7 @@ See [Configuration Guide](configuration.md) for advanced setup.
Full theme adaptation for Headlamp's light and dark modes.
**Features:**
- **Auto Dark Mode** - Respects system preference when Headlamp uses it
- **Theme Toggle** - Adapts when you change Headlamp theme
- **All UI Elements** - Drawer backgrounds, tables, buttons, badges, score gauge
@@ -180,6 +190,7 @@ Full theme adaptation for Headlamp's light and dark modes.
**Status:** Planned feature (UI components exist but not fully integrated)
**Future Capability:**
- View current exemptions on resources
- Add exemptions for specific failing checks
- Remove exemptions
@@ -190,21 +201,25 @@ This feature requires additional RBAC permissions (PATCH on workload resources)
## Data Refresh Behavior
**Initial Load:**
- Data fetched when you first navigate to any Polaris view
- Shared across all views via React Context (no duplicate fetches)
- Loading spinner displayed during initial fetch
**Auto-Refresh:**
- Configured via Settings → Plugins → Polaris
- Default: 5 minutes
- Triggers background fetch without disrupting UI
**Manual Refresh:**
- Click refresh button on overview dashboard
- Forces immediate data fetch
- Updates all views simultaneously
**Error Handling:**
- 403 errors show RBAC permission guidance
- 404/503 errors indicate Polaris not installed
- Network errors show generic failure with retry suggestion
@@ -212,12 +227,14 @@ This feature requires additional RBAC permissions (PATCH on workload resources)
## Browser Requirements
**Supported Browsers:**
- Chrome/Chromium 80+
- Firefox 75+
- Safari 13.1+
- Edge 80+
**Required:**
- JavaScript enabled
- localStorage enabled (for settings persistence)
- Cookies enabled (for Headlamp session)
@@ -227,11 +244,13 @@ This feature requires additional RBAC permissions (PATCH on workload resources)
**Bundle Size:** ~27 KB minified (gzip: ~7.6 KB)
**Data Volume:** Depends on cluster size. Example:
- Small cluster (50 resources): ~100 KB JSON
- Medium cluster (500 resources): ~1 MB JSON
- Large cluster (5000 resources): ~10 MB JSON
**Rendering Performance:**
- Handles up to 100 namespaces without virtual scrolling
- Namespace detail drawer renders instantly for up to 500 resources
- React Context prevents unnecessary re-fetches
+65 -34
View File
@@ -13,6 +13,7 @@ The plugin requires **one permission** to function:
This allows the plugin to fetch audit results via the Kubernetes service proxy.
**Why this permission?**
- Plugin accesses Polaris through Kubernetes API server's service proxy
- Service proxy requires `get` verb on `services/proxy` resource
- Scoped to specific service (`polaris-dashboard`) for security
@@ -37,13 +38,14 @@ metadata:
app.kubernetes.io/name: headlamp-polaris-plugin
app.kubernetes.io/component: rbac
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
- apiGroups: ['']
resources: ['services/proxy']
resourceNames: ['polaris-dashboard']
verbs: ['get']
```
**Key points:**
- **Role** (not ClusterRole) - Scoped to `polaris` namespace only
- **resourceNames** - Restricts access to `polaris-dashboard` service only
- **verbs: ["get"]** - Read-only permission
@@ -62,8 +64,8 @@ metadata:
app.kubernetes.io/component: rbac
subjects:
- kind: ServiceAccount
name: headlamp # Adjust to your Headlamp SA name
namespace: kube-system # Adjust to Headlamp's namespace
name: headlamp # Adjust to your Headlamp SA name
namespace: kube-system # Adjust to Headlamp's namespace
roleRef:
kind: Role
name: polaris-proxy-reader
@@ -71,6 +73,7 @@ roleRef:
```
**Adjust for your environment:**
- `subjects[0].name` - Your Headlamp service account name (often `headlamp`)
- `subjects[0].namespace` - Namespace where Headlamp runs (often `kube-system`)
@@ -104,10 +107,12 @@ In token-auth mode, **each user's own identity** is used for Kubernetes API requ
### Why Per-User RBAC?
With service account mode:
- Single RoleBinding grants access to all Headlamp users
- Kubernetes sees all requests as `system:serviceaccount:kube-system:headlamp`
With token-auth mode:
- Each user's own token (OIDC, kubeconfig) is used
- Kubernetes sees requests as `user@example.com` or `system:serviceaccount:team-ns:user-sa`
- **Each user needs individual RBAC permissions**
@@ -125,7 +130,7 @@ metadata:
namespace: polaris
subjects:
- kind: Group
name: system:authenticated # All authenticated users
name: system:authenticated # All authenticated users
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
@@ -174,7 +179,7 @@ metadata:
namespace: polaris
subjects:
- kind: Group
name: sre-team # OIDC group claim
name: sre-team # OIDC group claim
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: devops-team
@@ -186,11 +191,13 @@ roleRef:
```
**Requirements:**
- OIDC provider must include group claims in token
- Headlamp must be configured to extract groups from OIDC token
- Group names must match exactly (case-sensitive)
**Example OIDC group claim:**
```json
{
"sub": "user@example.com",
@@ -229,23 +236,23 @@ apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: polaris-proxy-reader
namespace: team-a-polaris # First Polaris instance
namespace: team-a-polaris # First Polaris instance
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
- apiGroups: ['']
resources: ['services/proxy']
resourceNames: ['polaris-dashboard']
verbs: ['get']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: polaris-proxy-reader
namespace: team-b-polaris # Second Polaris instance
namespace: team-b-polaris # Second Polaris instance
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
- apiGroups: ['']
resources: ['services/proxy']
resourceNames: ['polaris-dashboard']
verbs: ['get']
```
### Create RoleBindings per Namespace
@@ -343,22 +350,24 @@ When using OAuth2/OIDC authentication with Headlamp:
### Required Configuration
**Headlamp Helm values:**
```yaml
env:
- name: HEADLAMP_CONFIG_OIDC_CLIENT_ID
value: "headlamp"
value: 'headlamp'
- name: HEADLAMP_CONFIG_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: headlamp-oidc
key: client-secret
- name: HEADLAMP_CONFIG_OIDC_ISSUER_URL
value: "https://auth.example.com/realms/kubernetes"
value: 'https://auth.example.com/realms/kubernetes'
- name: HEADLAMP_CONFIG_OIDC_SCOPES
value: "openid,profile,email,groups"
value: 'openid,profile,email,groups'
```
**RBAC for OIDC users:**
```yaml
---
apiVersion: rbac.authorization.k8s.io/v1
@@ -368,7 +377,7 @@ metadata:
namespace: polaris
subjects:
- kind: Group
name: kubernetes-admins # OIDC group claim
name: kubernetes-admins # OIDC group claim
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
@@ -421,32 +430,36 @@ Every plugin data fetch creates a Kubernetes API audit log entry.
### Volume Estimates
**Per user:**
- 1 refresh per 5 minutes = 288 requests/day
- 1 refresh per 30 minutes = 48 requests/day
**Cluster-wide:**
- 10 concurrent users × 5-minute refresh = 2,880 audit logs/day
- 100 concurrent users × 30-minute refresh = 4,800 audit logs/day
### Reducing Audit Volume
**Option 1: Increase refresh interval**
```
Settings → Plugins → Polaris → Refresh Interval → 30 minutes
```
**Option 2: Adjust audit policy level**
```yaml
# kube-apiserver audit policy
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata # Log metadata only, not full request/response
verbs: ["get"]
- level: Metadata # Log metadata only, not full request/response
verbs: ['get']
resources:
- group: ""
resources: ["services/proxy"]
namespaces: ["polaris"]
- group: ''
resources: ['services/proxy']
namespaces: ['polaris']
```
**Option 3: Filter audit logs**
@@ -461,18 +474,23 @@ If using a log aggregator (e.g., Elasticsearch), create filters to exclude or do
**Diagnosis:**
1. **Check Role exists:**
```bash
kubectl -n polaris get role polaris-proxy-reader
```
If missing: Apply Role manifest
2. **Check RoleBinding exists:**
```bash
kubectl -n polaris get rolebinding headlamp-polaris-proxy
```
If missing: Apply RoleBinding manifest
3. **Test permission:**
```bash
# Service account mode
kubectl auth can-i get services/proxy \
@@ -486,6 +504,7 @@ If using a log aggregator (e.g., Elasticsearch), create filters to exclude or do
-n polaris \
--resource-name=polaris-dashboard
```
Expected: `yes`
4. **Verify RoleBinding subjects match:**
@@ -499,6 +518,7 @@ If using a log aggregator (e.g., Elasticsearch), create filters to exclude or do
**This is NOT an RBAC issue.** 404 means Polaris service doesn't exist.
**Check:**
```bash
kubectl -n polaris get svc polaris-dashboard
```
@@ -510,14 +530,17 @@ If missing, install Polaris with dashboard enabled.
**Possible causes:**
1. **Wrong namespace in RoleBinding:**
- RoleBinding must be in `polaris` namespace (where the service is)
- Common mistake: Creating RoleBinding in `kube-system`
2. **Wrong resourceName:**
- Must match service name exactly: `polaris-dashboard`
- Check: `kubectl -n polaris get svc`
3. **Browser caching old 403:**
- Hard refresh browser: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
4. **Token expired (OIDC mode):**
@@ -529,6 +552,7 @@ If missing, install Polaris with dashboard enabled.
### 1. Use Namespaced Roles (Not ClusterRoles)
✅ **Good:**
```yaml
kind: Role
metadata:
@@ -536,6 +560,7 @@ metadata:
```
❌ **Bad:**
```yaml
kind: ClusterRole
# Grants access to all namespaces
@@ -546,13 +571,15 @@ kind: ClusterRole
### 2. Always Specify resourceNames
✅ **Good:**
```yaml
resourceNames: ["polaris-dashboard"]
resourceNames: ['polaris-dashboard']
```
❌ **Bad:**
```yaml
resourceNames: [] # Allows access to ALL services
resourceNames: [] # Allows access to ALL services
```
**Why:** `resourceNames` restricts permission to a specific service. Without it, the binding grants access to proxy all services in the namespace.
@@ -560,13 +587,15 @@ resourceNames: [] # Allows access to ALL services
### 3. Use Read-Only Verb
✅ **Good:**
```yaml
verbs: ["get"]
verbs: ['get']
```
❌ **Bad:**
```yaml
verbs: ["get", "create", "update", "delete"]
verbs: ['get', 'create', 'update', 'delete']
```
**Why:** Plugin only needs `get` to fetch audit results. Additional verbs violate principle of least privilege.
@@ -580,6 +609,7 @@ verbs: ["get", "create", "update", "delete"]
### 5. Monitor Audit Logs
Set alerts for:
- Unusual access patterns (e.g., 403 spikes = permission issues)
- High request volume (e.g., misconfigured refresh interval)
- Access from unexpected users (security monitoring)
@@ -587,11 +617,12 @@ Set alerts for:
### 6. Avoid Wildcard Permissions
❌ **Never do this:**
```yaml
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
- apiGroups: ['*']
resources: ['*']
verbs: ['*']
```
This grants cluster-admin equivalent permissions. Always use specific resources and verbs.