diff --git a/docs/architecture/adr/001-react-context-for-state.md b/docs/architecture/adr/001-react-context-for-state.md new file mode 100644 index 0000000..b53ab2e --- /dev/null +++ b/docs/architecture/adr/001-react-context-for-state.md @@ -0,0 +1,205 @@ +# ADR-001: Use React Context for State Management + +**Status:** Accepted +**Date:** 2026-02-12 +**Deciders:** Plugin maintainers + +## 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) +- Inline audit sections on resource detail pages +- App bar score badge + +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 +- Minimal re-renders (data updates infrequently) + +## Decision + +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 +- Auto-refresh via `useEffect` + interval timer + +## Consequences + +### Positive + +- ✅ **No additional dependencies** - Plugins cannot add external libraries (Headlamp constraint) +- ✅ **Simple implementation** - Single AuditData object, read-only, no complex mutations +- ✅ **Built into React** - No learning curve, well-documented, stable API +- ✅ **Small bundle impact** - 0 KB additional (built-in feature) +- ✅ **Works with Headlamp** - Compatible with Headlamp's React version and plugin system +- ✅ **TypeScript support** - Full type safety with `React.createContext()` + +### Negative + +- ❌ **Less powerful for complex state** - No built-in middleware, time-travel debugging, or DevTools +- ❌ **Potential for unnecessary re-renders** - All consumers re-render on context update + - **Mitigated by:** Data updates every 5-30 minutes (low frequency), memoization not needed +- ❌ **No built-in async handling** - Must implement loading/error states manually + - **Mitigated by:** Simple `useState` + `useEffect` pattern sufficient + +### Neutral + +- Performance is excellent for this use case (infrequent updates, small consumer count) +- Context providers work well for read-only or mostly-read state +- Standard React pattern, familiar to contributors + +## Alternatives Considered + +### 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) +- Requires additional boilerplate (actions, reducers, store) + +**Decision:** Rejected (dependency not available, too heavy) + +### 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 + +**Decision:** Rejected (dependency not available) + +### 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 +- Each route would need its own fetch logic + +**Decision:** Rejected (poor code organization, maintenance burden) + +### 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 +- Testing difficulties (global mutable state) + +**Decision:** Rejected (not idiomatic React, no reactivity) + +## Implementation Details + +**Context Definition:** +```typescript +interface PolarisDataContextValue { + data: AuditData | null; + loading: boolean; + error: string | null; + refresh: () => void; +} + +const PolarisDataContext = React.createContext(undefined); +``` + +**Provider Implementation:** +```typescript +export function PolarisDataProvider({ children }: { children: React.ReactNode }) { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [refreshKey, setRefreshKey] = useState(0); + + const refresh = useCallback(() => { + setRefreshKey(k => k + 1); + }, []); + + useEffect(() => { + // Fetch logic here + // Auto-refresh on interval + }, [refreshKey]); + + return ( + + {children} + + ); +} +``` + +**Consumer Hook:** +```typescript +export function usePolarisDataContext() { + const context = useContext(PolarisDataContext); + if (!context) { + throw new Error('usePolarisDataContext must be used within PolarisDataProvider'); + } + return context; +} +``` + +## 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) +- ✅ Loading states consistent across views +- ✅ Error handling consistent across views +- ✅ 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 +- ✅ Auto-refresh interval (configurable via settings) +- ✅ Multiple views consuming same data (no duplicate fetches) +- ✅ Navigation between routes (data persists) + +## References + +- [React Context API](https://react.dev/reference/react/useContext) +- [React Context Performance](https://react.dev/reference/react/useContext#optimizing-re-renders-when-passing-objects-and-functions) +- [Headlamp Plugin Constraints](https://headlamp.dev/docs/latest/development/plugins/) +- [Plugin Implementation](../../api/PolarisDataContext.tsx) + +## Revision History + +| Date | Author | Change | +|------|--------|--------| +| 2026-02-12 | Plugin Team | Initial decision | diff --git a/docs/architecture/adr/README.md b/docs/architecture/adr/README.md index 6f77578..8a07d5b 100644 --- a/docs/architecture/adr/README.md +++ b/docs/architecture/adr/README.md @@ -65,10 +65,9 @@ What becomes easier or more difficult to do because of this change? | ADR | Title | Status | Date | |-----|-------|--------|------| -| [001](001-service-proxy-over-direct-access.md) | Use Service Proxy Over Direct Access | Accepted | 2025-01-15 | -| [002](002-react-context-for-state.md) | React Context for State Management | Accepted | 2025-01-15 | -| [003](003-drawer-navigation.md) | Drawer Navigation for Namespace Detail | Accepted | 2025-01-18 | -| [004](004-no-mui-direct-imports.md) | Prohibit Direct MUI Imports | Accepted | 2025-01-20 | +| [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. ## Creating a New ADR diff --git a/docs/deployment/kubernetes.md b/docs/deployment/kubernetes.md index 360a2a8..63312d8 100644 --- a/docs/deployment/kubernetes.md +++ b/docs/deployment/kubernetes.md @@ -255,7 +255,7 @@ kubectl -n kube-system logs deployment/headlamp -c install-plugins # Plugin installation complete # Verify plugin files exist -kubectl -n kube-system exec deployment/headlamp -- \ +kubectl -n kube-system exec deployment/headlamp -c headlamp -- \ ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected output: diff --git a/docs/deployment/production.md b/docs/deployment/production.md index 26cb8cd..0eb513d 100644 --- a/docs/deployment/production.md +++ b/docs/deployment/production.md @@ -71,7 +71,7 @@ kubectl -n kube-system logs deployment/headlamp | grep -i polaris # Expected: No errors related to plugin loading # 4. Verify plugin files exist -kubectl -n kube-system exec deployment/headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ +kubectl -n kube-system exec deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected: dist/, package.json present ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index bb99689..9e7055f 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -161,7 +161,7 @@ helm upgrade --install headlamp headlamp/headlamp \ kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s # Verify plugin files -kubectl -n kube-system exec -it deployment/headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ +kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected output: # drwxr-xr-x dist/ @@ -327,7 +327,7 @@ kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name= ```bash # Verify plugin files exist -kubectl -n kube-system exec -it deployment/headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ +kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected output: # drwxr-xr-x dist/ diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 96c60c1..e20dadf 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -109,7 +109,7 @@ EOF ```bash # Verify plugin files exist -kubectl -n kube-system exec -it deployment/headlamp -- \ +kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \ ls /headlamp/plugins/headlamp-polaris-plugin/dist/ # Expected output: diff --git a/docs/troubleshooting/README.md b/docs/troubleshooting/README.md index 3265d79..9c54349 100644 --- a/docs/troubleshooting/README.md +++ b/docs/troubleshooting/README.md @@ -62,7 +62,7 @@ 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 -- \ +kubectl -n kube-system exec deployment/headlamp -c headlamp -- \ ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected output: diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md new file mode 100644 index 0000000..8eac1fa --- /dev/null +++ b/docs/user-guide/configuration.md @@ -0,0 +1,380 @@ +# Configuration Guide + +Customize the Headlamp Polaris Plugin to fit your environment. + +## Plugin Settings + +Access plugin settings via **Settings → Plugins → Polaris** in the Headlamp UI. + +## Refresh Interval + +**What it does:** Controls how often the plugin fetches the latest audit data from Polaris. + +### Available Options + +- **1 minute** - Most frequent updates, highest API load +- **5 minutes** - **Default**, balanced load and freshness +- **10 minutes** - Moderate refresh rate +- **30 minutes** - Light load, best for large clusters + +### How to Change + +1. Navigate to **Settings → Plugins → Polaris** +2. Click the **Refresh Interval** dropdown +3. Select your desired interval +4. Click **Save** +5. Changes take effect immediately (no browser refresh needed) + +### 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 + +### 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 + +## Dashboard URL + +**What it does:** Specifies which Polaris instance the plugin connects to. + +### Default Configuration + +**Service proxy path (default):** +``` +/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/ +``` + +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 +- Respects Kubernetes NetworkPolicies + +### Custom URL Scenarios + +#### External Polaris (HTTPS) + +If Polaris is deployed outside the cluster with an external URL: + +``` +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 + +#### Custom Namespace + +If Polaris is deployed in a different namespace: + +``` +/api/v1/namespaces/custom-namespace/services/polaris-dashboard:80/proxy/ +``` + +**Requirements:** +- Update RBAC Role namespace to match +- Service name must still be `polaris-dashboard` (or adjust in URL) + +#### Non-Standard Port + +If Polaris dashboard uses a different port: + +``` +/api/v1/namespaces/polaris/services/polaris-dashboard:8080/proxy/ +``` + +#### Local Development + +For local Polaris development instance: + +``` +http://localhost:8080/ +``` + +**Note:** Browser may block mixed content (HTTPS Headlamp → HTTP Polaris). + +### How to Change Dashboard URL + +1. Navigate to **Settings → Plugins → Polaris** +2. Update the **Dashboard URL** field +3. Click **Test Connection** to verify (recommended) +4. Click **Save** if connection test succeeds + +### Connection Testing + +**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 | + +### 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" +``` + +**Test CORS:** +```bash +curl -v -H "Origin: https://headlamp.example.com" \ + https://polaris.example.com/results.json \ + | grep -i "access-control" + +# Expected: +# Access-Control-Allow-Origin: https://headlamp.example.com +``` + +## Advanced Configuration + +### Persistent Settings Storage + +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')) +``` + +**Reset to defaults:** +```javascript +// Open browser DevTools Console (F12) +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 + +## Configuration Best Practices + +### For Development Clusters + +**Recommended Settings:** +- **Refresh Interval:** 1-5 minutes (faster feedback loop) +- **Dashboard URL:** Service proxy (default) + +**Why:** Development clusters are typically small, so API load is minimal. Faster refresh helps catch issues quickly during development. + +### For Staging Clusters + +**Recommended Settings:** +- **Refresh Interval:** 5-10 minutes (balanced) +- **Dashboard URL:** Service proxy (default) + +**Why:** Staging should mirror production configuration. 5-10 minutes provides reasonable freshness without excessive load. + +### For Production Clusters + +**Recommended Settings:** +- **Refresh Interval:** 10-30 minutes (reduce load) +- **Dashboard URL:** Service proxy (default) + +**Why:** Production clusters are larger and more critical. Longer intervals reduce audit log volume and API pressure. Polaris audits typically run every 10-30 minutes anyway, so more frequent plugin refreshes don't provide much value. + +### For Multi-Tenant Environments + +**Recommended Settings:** +- **Refresh Interval:** 10-30 minutes (minimize per-user load) +- **Dashboard URL:** Service proxy with per-namespace RBAC + +**Why:** Many concurrent Headlamp users can create significant API load. Longer intervals prevent thundering herd issues. + +### 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 + +**Why:** External Polaris avoids Kubernetes service proxy overhead but requires CORS configuration and network accessibility. + +## Troubleshooting Configuration + +### Settings Not Saving + +**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') + ``` + +### Settings Lost After Browser Restart + +**Symptom:** Settings reset to defaults when you reopen browser + +**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) + +### Connection Test Fails + +**Symptom:** Test Connection button shows error + +**Solutions by error type:** + +**403 Forbidden:** +```bash +# Verify RBAC exists +kubectl -n polaris get role polaris-proxy-reader +kubectl -n polaris get rolebinding headlamp-polaris-proxy + +# Test permission +kubectl auth can-i get services/proxy \ + --as=system:serviceaccount:kube-system:headlamp \ + -n polaris \ + --resource-name=polaris-dashboard +``` + +**404 Not Found:** +```bash +# Verify Polaris is running +kubectl -n polaris get pods +kubectl -n polaris get svc polaris-dashboard + +# If missing, install Polaris +helm install polaris fairwinds-stable/polaris \ + --namespace polaris \ + --create-namespace \ + --set dashboard.enabled=true +``` + +**503 Service Unavailable:** +```bash +# Check pod status +kubectl -n polaris get pods + +# Check pod logs +kubectl -n polaris logs deployment/polaris-dashboard +``` + +**Network Error / CORS:** +```bash +# For external Polaris, test CORS +curl -v -H "Origin: https://headlamp.example.com" \ + https://polaris.example.com/results.json + +# Check for Access-Control-Allow-Origin header +``` + +### Refresh Interval Not Working + +**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')) + +// Should return: "300" (5 minutes), "600" (10 minutes), etc. +``` + +## Configuration Checklist + +Before going to production, verify: + +- [ ] Refresh interval set appropriately (10-30 min for large clusters) +- [ ] Dashboard URL tested and working +- [ ] Connection test passes +- [ ] RBAC permissions granted (see [RBAC Guide](rbac-permissions.md)) +- [ ] NetworkPolicies allow API server → Polaris (if using network policies) +- [ ] CORS configured (if using external Polaris) +- [ ] Browser localStorage enabled +- [ ] Settings persist across browser restarts + +## Future Configuration Options + +**Planned features:** +- ConfigMap-based settings (server-side, not localStorage) +- Per-cluster settings (multi-cluster Headlamp support) +- Webhook notifications for score changes +- Custom check severity overrides +- Exemption management UI (requires RBAC PATCH permission) + +## Next Steps + +- **[Features Guide](features.md)** - Learn about all plugin features +- **[RBAC Permissions](rbac-permissions.md)** - Configure advanced RBAC for token-auth, OIDC +- **[Troubleshooting](../troubleshooting/README.md)** - Diagnose common configuration issues + +## References + +- [Polaris Configuration](https://polaris.docs.fairwinds.com/customization/checks/) +- [Kubernetes Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/) +- [CORS Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) diff --git a/docs/user-guide/features.md b/docs/user-guide/features.md new file mode 100644 index 0000000..a381c26 --- /dev/null +++ b/docs/user-guide/features.md @@ -0,0 +1,269 @@ +# Features Guide + +Learn about all features in the Headlamp Polaris Plugin. + +## Overview Dashboard + +The main dashboard provides cluster-wide visibility. Navigate to **Polaris → Overview**. + +### Cluster Score Gauge + +Overall cluster health score (0-100%) with color-coded status: + +- **Green (≥80%):** Excellent - cluster follows best practices +- **Yellow (50-79%):** Needs attention - some issues present +- **Red (<50%):** Critical - significant security/reliability concerns + +The score is calculated as: `(passing checks / total checks) × 100` + +### Check Distribution + +Visual breakdown of all Polaris checks across the cluster: + +- **Pass** - Checks that passed (green) +- **Warning** - Failed checks with warning severity (yellow) +- **Danger** - Failed checks with danger severity (red) +- **Skipped** - Checks with severity "ignore" (gray) + +**Note:** Skipped count only reflects checks with `Severity: "ignore"` from Polaris config. Annotation-based exemptions (e.g., `polaris.fairwinds.com/cpuLimitsMissing-exempt: "true"`) are not included. See "View in Polaris Dashboard" link for full exemption count. + +### Top 10 Failing Checks + +Most common issues across the entire cluster: + +- Grouped by check type (e.g., "CPU Limits Missing", "Host IPC Set") +- Shows count and severity +- Helps identify cluster-wide patterns +- Click check name for details + +### Cluster Statistics + +Quick cluster metadata: + +- **Polaris Version** - e.g., "4.2.0" +- **Last Audit** - ISO 8601 timestamp of most recent audit +- **Nodes** - Total node count +- **Pods** - Total pod count +- **Namespaces** - Total namespace count +- **Controllers** - Total workload controller count + +### Manual Refresh + +Click the refresh button to fetch the latest audit data immediately (bypasses auto-refresh interval). + +## Namespace Views + +### Namespaces List + +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 +- **Warning** - Warning severity failures +- **Danger** - Danger severity failures +- **Skipped** - Skipped checks count + +**Sorting:** Table is sortable by any column. Default sort is by score (lowest first) to surface problematic namespaces. + +### Namespace Detail Panel + +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: + - Resource name + - Resource kind (Deployment, StatefulSet, DaemonSet, Job, CronJob) + - Pass/Warning/Danger counts per resource +- **External Link** - "View in Polaris Dashboard" button for full Polaris UI +- **URL Hash Navigation** - Browser back/forward works with drawer state +- **Keyboard Shortcut** - Press **Escape** to close panel +- **Click-to-Close** - Click backdrop to close panel + +The drawer respects Headlamp's theme (light/dark mode). + +## Inline Resource Audits + +Polaris audit results automatically appear on resource detail pages. + +### Supported Resources + +Inline audit sections appear on: +- Deployments +- StatefulSets +- DaemonSets +- Jobs +- CronJobs + +### What's Shown + +**Compact Audit Section:** +- **Score Badge** - Color-coded score +- **Check Counts** - Pass/Warning/Danger summary +- **Failing Checks Table** - Only failed checks listed: + - Check name (human-readable) + - Severity badge (Warning/Danger) + - Message describing the issue +- **Link to Full Report** - Navigate to namespace detail for complete audit + +**If no audit data:** Shows "No audit data available for this resource" message. + +## App Bar Score Badge + +Top-right corner of Headlamp shows a persistent cluster score badge. + +**Features:** +- **Color-Coded Chip** - Green/Yellow/Red based on score +- **Shield Emoji (🛡️)** - Visual indicator +- **Score Percentage** - e.g., "85%" +- **Clickable** - Click to navigate to Polaris overview +- **Real-Time Updates** - Updates on auto-refresh interval +- **Always Visible** - Appears on all Headlamp pages + +**Example:** `🛡️ 85%` (green chip) + +## Settings & Configuration + +Access plugin settings via **Settings → Plugins → Polaris**. + +### Refresh Interval + +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) + +See [Configuration Guide](configuration.md) for details. + +### Dashboard URL + +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/` + +**Test Connection Button:** Verifies connectivity before saving. + +See [Configuration Guide](configuration.md) for advanced setup. + +## Dark Mode Support + +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 +- **CSS Variables** - Uses MUI theme variables (`--mui-palette-*`) + +**No configuration required** - works automatically with Headlamp's theme. + +## Exemption Management + +**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 +- Apply via annotation patches (`polaris.fairwinds.com/*-exempt`) + +This feature requires additional RBAC permissions (PATCH on workload resources) and is not yet enabled by default. + +## 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 + +## 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) + +## Performance Characteristics + +**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 + +## Known Limitations + +### Skipped Count Incomplete + +The "Skipped" count only reflects checks with `Severity: "ignore"` in Polaris configuration. Annotation-based exemptions are not counted because: + +- Polaris API omits exempted checks from `results.json` +- Native Polaris dashboard computes skipped count by querying raw Kubernetes resources +- Plugin only has access to processed audit results (not raw resources) + +**Workaround:** Use "View in Polaris Dashboard" link for accurate exemption count. + +### Single Cluster Support + +Plugin shows data for the current Headlamp cluster only. Multi-cluster aggregation is not supported. + +### No Real-Time Updates + +Data refreshes on interval (1-30 minutes), not real-time. Polaris dashboard doesn't support WebSocket/SSE. + +## Next Steps + +- **[Configuration Guide](configuration.md)** - Customize refresh intervals, dashboard URLs, test connections +- **[RBAC Permissions](rbac-permissions.md)** - Advanced RBAC setup for token-auth, OIDC, multi-user +- **[Troubleshooting](../troubleshooting/README.md)** - Quick diagnosis for common issues + +## References + +- [Fairwinds Polaris Documentation](https://polaris.docs.fairwinds.com/) +- [Headlamp Documentation](https://headlamp.dev/docs/) +- [Kubernetes Best Practices](https://kubernetes.io/docs/concepts/configuration/overview/) diff --git a/docs/user-guide/rbac-permissions.md b/docs/user-guide/rbac-permissions.md new file mode 100644 index 0000000..23a5c84 --- /dev/null +++ b/docs/user-guide/rbac-permissions.md @@ -0,0 +1,610 @@ +# RBAC Permissions Guide + +Understanding and configuring RBAC for the Headlamp Polaris Plugin. + +## Quick Reference + +The plugin requires **one permission** to function: + +| Verb | API Group | Resource | Resource Name | Namespace | +| ----- | ----------- | ---------------- | ------------------- | --------- | +| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` | + +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 +- Read-only (no write operations) + +## Standard Setup (Service Account Mode) + +**Best for:** Headlamp running with a fixed service account in the cluster (in-cluster mode) + +This is the most common deployment pattern for production Headlamp instances. + +### Step 1: Create Role + +```yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: polaris-proxy-reader + namespace: polaris + labels: + app.kubernetes.io/name: headlamp-polaris-plugin + app.kubernetes.io/component: rbac +rules: + - 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 + +### Step 2: Create RoleBinding + +```yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: headlamp-polaris-proxy + namespace: polaris + labels: + app.kubernetes.io/name: headlamp-polaris-plugin + app.kubernetes.io/component: rbac +subjects: + - kind: ServiceAccount + name: headlamp # Adjust to your Headlamp SA name + namespace: kube-system # Adjust to Headlamp's namespace +roleRef: + kind: Role + name: polaris-proxy-reader + apiGroup: rbac.authorization.k8s.io +``` + +**Adjust for your environment:** +- `subjects[0].name` - Your Headlamp service account name (often `headlamp`) +- `subjects[0].namespace` - Namespace where Headlamp runs (often `kube-system`) + +### Step 3: Apply and Verify + +```bash +# Apply RBAC manifests +kubectl apply -f polaris-rbac.yaml + +# Verify Role exists +kubectl -n polaris get role polaris-proxy-reader + +# Verify RoleBinding exists +kubectl -n polaris get rolebinding headlamp-polaris-proxy + +# Test permission +kubectl auth can-i get services/proxy \ + --as=system:serviceaccount:kube-system:headlamp \ + -n polaris \ + --resource-name=polaris-dashboard + +# Expected output: yes +``` + +## Token-Auth Mode (Per-User Permissions) + +**Best for:** Headlamp configured for user-supplied tokens, OIDC, or external authentication + +In token-auth mode, **each user's own identity** is used for Kubernetes API requests (not a shared service account). + +### 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** + +### Option 1: Grant to All Authenticated Users + +**Use case:** Everyone with cluster access should see Polaris data + +```yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: authenticated-users-polaris-proxy + namespace: polaris +subjects: + - kind: Group + name: system:authenticated # All authenticated users + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: Role + name: polaris-proxy-reader + apiGroup: rbac.authorization.k8s.io +``` + +**Security consideration:** This grants Polaris access to **everyone** with cluster access. Ensure Polaris data is not sensitive in your environment. + +### Option 2: Grant to Specific Users + +**Use case:** Fine-grained control, only SRE/DevOps teams + +```yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: sre-team-polaris-proxy + namespace: polaris +subjects: + - kind: User + name: alice@example.com + apiGroup: rbac.authorization.k8s.io + - kind: User + name: bob@example.com + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: Role + name: polaris-proxy-reader + apiGroup: rbac.authorization.k8s.io +``` + +**Maintenance:** Add/remove users as team membership changes. + +### Option 3: Grant to OIDC Groups + +**Use case:** OIDC provider with group claims (most flexible) + +```yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: oidc-group-polaris-proxy + namespace: polaris +subjects: + - kind: Group + name: sre-team # OIDC group claim + apiGroup: rbac.authorization.k8s.io + - kind: Group + name: devops-team + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: Role + name: polaris-proxy-reader + apiGroup: rbac.authorization.k8s.io +``` + +**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", + "groups": ["sre-team", "developers"] +} +``` + +### Verify User Permission + +```bash +# Test specific user +kubectl auth can-i get services/proxy \ + --as=user@example.com \ + -n polaris \ + --resource-name=polaris-dashboard + +# Test OIDC group +kubectl auth can-i get services/proxy \ + --as=user@example.com \ + --as-group=sre-team \ + -n polaris \ + --resource-name=polaris-dashboard + +# Expected output: yes (if bound correctly) +``` + +## Multi-Namespace Polaris Deployments + +**Scenario:** Polaris deployed in multiple namespaces (e.g., per-team Polaris instances) + +### Create Role per Namespace + +```yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: polaris-proxy-reader + namespace: team-a-polaris # First Polaris instance +rules: + - 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 +rules: + - apiGroups: [""] + resources: ["services/proxy"] + resourceNames: ["polaris-dashboard"] + verbs: ["get"] +``` + +### Create RoleBindings per Namespace + +```yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: headlamp-polaris-proxy + namespace: team-a-polaris +subjects: + - kind: ServiceAccount + name: headlamp + namespace: kube-system +roleRef: + kind: Role + name: polaris-proxy-reader + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: headlamp-polaris-proxy + namespace: team-b-polaris +subjects: + - kind: ServiceAccount + name: headlamp + namespace: kube-system +roleRef: + kind: Role + name: polaris-proxy-reader + apiGroup: rbac.authorization.k8s.io +``` + +**Plugin configuration:** +Users can switch between instances via **Settings → Plugins → Polaris → Dashboard URL**. + +## Network Security + +### NetworkPolicy Requirements + +If the `polaris` namespace enforces NetworkPolicies, ensure the Kubernetes API server can reach Polaris dashboard. + +**Why?** The Kubernetes API server proxies plugin requests, so it needs network access to Polaris. + +```yaml +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-apiserver-to-polaris + namespace: polaris +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: polaris + app.kubernetes.io/component: dashboard + policyTypes: + - Ingress + ingress: + # Allow from Kubernetes API server + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + - podSelector: + matchLabels: + component: kube-apiserver + ports: + - protocol: TCP + port: 80 +``` + +**Note:** Headlamp pod itself does NOT need direct network access to Polaris (API server does the proxying). + +### Service Mesh Considerations + +If using Istio, Linkerd, or other service meshes: + +**No special configuration needed** - Service proxy requests bypass the mesh (go through API server). + +## OAuth2 / OIDC Integration + +When using OAuth2/OIDC authentication with Headlamp: + +### How It Works + +1. **User authenticates** with OIDC provider (e.g., Google, Okta, Keycloak) +2. **OIDC provider issues token** with user identity and group claims +3. **Headlamp receives token** and passes it to Kubernetes API +4. **Plugin makes request** using user's token (not service account) +5. **Kubernetes RBAC evaluates** user's permissions against RoleBinding + +### Required Configuration + +**Headlamp Helm values:** +```yaml +env: + - name: HEADLAMP_CONFIG_OIDC_CLIENT_ID + 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" + - name: HEADLAMP_CONFIG_OIDC_SCOPES + value: "openid,profile,email,groups" +``` + +**RBAC for OIDC users:** +```yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: oidc-polaris-proxy + namespace: polaris +subjects: + - kind: Group + name: kubernetes-admins # OIDC group claim + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: Role + name: polaris-proxy-reader + apiGroup: rbac.authorization.k8s.io +``` + +### Testing OIDC Permissions + +```bash +# Simulate OIDC user with group +kubectl auth can-i get services/proxy \ + --as=user@example.com \ + --as-group=kubernetes-admins \ + -n polaris \ + --resource-name=polaris-dashboard + +# Expected: yes +``` + +## Audit Logging Considerations + +Every plugin data fetch creates a Kubernetes API audit log entry. + +### Example Audit Log + +```json +{ + "kind": "Event", + "apiVersion": "audit.k8s.io/v1", + "level": "Metadata", + "verb": "get", + "user": { + "username": "system:serviceaccount:kube-system:headlamp" + }, + "sourceIPs": ["10.96.0.1"], + "objectRef": { + "resource": "services", + "subresource": "proxy", + "namespace": "polaris", + "name": "polaris-dashboard", + "apiVersion": "v1" + }, + "responseStatus": { + "code": 200 + } +} +``` + +### 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"] + resources: + - group: "" + resources: ["services/proxy"] + namespaces: ["polaris"] +``` + +**Option 3: Filter audit logs** +If using a log aggregator (e.g., Elasticsearch), create filters to exclude or downsample Polaris proxy requests. + +## Troubleshooting RBAC + +### "403 Forbidden" Error in Plugin + +**Symptom:** Plugin shows "Access denied (403)" error when loading data + +**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 \ + --as=system:serviceaccount:kube-system:headlamp \ + -n polaris \ + --resource-name=polaris-dashboard + + # Token-auth mode (replace with your username) + kubectl auth can-i get services/proxy \ + --as=user@example.com \ + -n polaris \ + --resource-name=polaris-dashboard + ``` + Expected: `yes` + +4. **Verify RoleBinding subjects match:** + ```bash + kubectl -n polaris get rolebinding headlamp-polaris-proxy -o yaml + ``` + Check `subjects[].name` and `subjects[].namespace` match your Headlamp SA or user + +### "404 Not Found" Error + +**This is NOT an RBAC issue.** 404 means Polaris service doesn't exist. + +**Check:** +```bash +kubectl -n polaris get svc polaris-dashboard +``` + +If missing, install Polaris with dashboard enabled. + +### Permission Test Passes but Plugin Still Shows 403 + +**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):** + - Re-authenticate with OIDC provider + - Check token expiration in browser DevTools (Application → Session Storage) + +## Security Best Practices + +### 1. Use Namespaced Roles (Not ClusterRoles) + +✅ **Good:** +```yaml +kind: Role +metadata: + namespace: polaris +``` + +❌ **Bad:** +```yaml +kind: ClusterRole +# Grants access to all namespaces +``` + +**Why:** Namespaced Roles limit scope to `polaris` namespace only. ClusterRoles would allow access to service proxies in all namespaces. + +### 2. Always Specify resourceNames + +✅ **Good:** +```yaml +resourceNames: ["polaris-dashboard"] +``` + +❌ **Bad:** +```yaml +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. + +### 3. Use Read-Only Verb + +✅ **Good:** +```yaml +verbs: ["get"] +``` + +❌ **Bad:** +```yaml +verbs: ["get", "create", "update", "delete"] +``` + +**Why:** Plugin only needs `get` to fetch audit results. Additional verbs violate principle of least privilege. + +### 4. Review Bindings Quarterly + +- Remove users who no longer need access +- Update OIDC group bindings when org structure changes +- Audit who has access: `kubectl -n polaris get rolebindings -o yaml` + +### 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) + +### 6. Avoid Wildcard Permissions + +❌ **Never do this:** +```yaml +rules: + - apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +``` + +This grants cluster-admin equivalent permissions. Always use specific resources and verbs. + +## Next Steps + +- **[Features Guide](features.md)** - Learn about plugin capabilities +- **[Configuration Guide](configuration.md)** - Configure refresh intervals and dashboard URL +- **[Troubleshooting RBAC](../troubleshooting/rbac-issues.md)** - Detailed RBAC debugging + +## References + +- [Kubernetes RBAC Documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) +- [Service Proxy Authorization](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/) +- [Headlamp OIDC Configuration](https://headlamp.dev/docs/latest/installation/in-cluster/configuration/#oidc-configuration) +- [Kubernetes Audit Logging](https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/)