docs: add Priority 2 documentation (ARCHITECTURE, DEPLOYMENT, SECURITY)

- Add docs/ARCHITECTURE.md with system architecture, data flow diagrams, component hierarchy, design decisions, and known limitations
- Add docs/DEPLOYMENT.md with comprehensive installation guide including Helm integration, RBAC configuration, network policies, plugin manager setup, and troubleshooting
- Add SECURITY.md with security model, RBAC requirements, network security, vulnerability reporting, dependency scanning, and compliance considerations

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
2026-02-11 23:20:41 -05:00
parent 0e711f5dfd
commit 7ad5b7ecc3
3 changed files with 1603 additions and 0 deletions
+358
View File
@@ -0,0 +1,358 @@
# Security Policy
## Overview
The Headlamp Polaris Plugin is a read-only visualization tool that displays Fairwinds Polaris audit results within the Headlamp UI. Security considerations primarily revolve around Kubernetes RBAC, network policies, and data access controls.
## Security Model
### Read-Only Operation
The plugin performs **only read operations** via the Kubernetes API server's service proxy mechanism:
- **No write operations**: The plugin never creates, updates, or deletes Kubernetes resources
- **No CRD installation**: No custom resource definitions or cluster-level modifications
- **No secrets**: The plugin does not read or store Kubernetes secrets
- **No PII**: Polaris audit data contains resource metadata but no personally identifiable information
### Data Flow
```
User Browser
↓ (HTTPS)
Headlamp Pod
↓ (in-cluster service account or user token)
Kubernetes API Server
↓ (service proxy: /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/)
Polaris Dashboard Service
↓ (returns audit JSON)
Plugin Frontend (React)
```
All communication uses Kubernetes authentication and authorization mechanisms. The plugin never stores credentials or bypasses RBAC.
## RBAC Requirements
### Minimal Permissions
The plugin requires only one permission:
| Verb | API Group | Resource | Resource Name | Namespace |
|------|-----------|----------|---------------|-----------|
| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` |
**Example minimal Role:**
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: polaris-proxy-reader
namespace: polaris
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
```
### RoleBinding Options
**Option 1: Service Account (Recommended)**
Bind to the Headlamp service account for all users:
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: headlamp-polaris-proxy
namespace: polaris
subjects:
- kind: ServiceAccount
name: headlamp
namespace: kube-system
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
```
**Option 2: OIDC Groups**
Bind to user groups for OIDC authentication:
```yaml
subjects:
- kind: Group
name: "developers"
apiGroup: rbac.authorization.k8s.io
```
**Option 3: Specific Users**
Bind to individual users:
```yaml
subjects:
- kind: User
name: "jane@example.com"
apiGroup: rbac.authorization.k8s.io
```
### ⚠️ Security Best Practices
1. **Principle of Least Privilege**: Grant only `services/proxy` access, not broader `services` permissions
2. **Namespace Scoping**: Use a namespaced `Role`, not a `ClusterRole`, to limit access to the `polaris` namespace only
3. **Resource Name Restriction**: Always specify `resourceNames: ["polaris-dashboard"]` to prevent proxy access to other services
4. **Audit Logging**: Enable Kubernetes audit logging to track all service proxy requests
5. **Network Policies**: Restrict network access to the Polaris dashboard service (see Network Security below)
## Network Security
### Network Policies
If your cluster uses NetworkPolicies, ensure the Headlamp pod (or more specifically, the Kubernetes API server performing the proxy hop) can reach the Polaris dashboard service.
**Example NetworkPolicy for Polaris namespace:**
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-server-to-polaris
namespace: polaris
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: polaris
policyTypes:
- Ingress
ingress:
# Allow from API server (adjust based on your cluster setup)
- from:
- namespaceSelector: {} # API server typically runs in kube-system or no namespace label
ports:
- protocol: TCP
port: 8080 # Polaris dashboard default port
```
**Note**: The Kubernetes API server performs the service proxy hop, so network policies should allow traffic from the API server to Polaris, not directly from Headlamp to Polaris.
### TLS/HTTPS
- **External Access**: Always access Headlamp over HTTPS, especially when using OIDC authentication
- **Internal Communication**: Communication between Headlamp and the Kubernetes API server uses the service account token over the cluster's internal network
- **Service Proxy**: The API server → Polaris dashboard communication happens over HTTP within the cluster (ClusterIP service)
## Authentication Methods
### Service Account (Default)
Headlamp runs with a dedicated service account (`headlamp` in `kube-system`). All users share the same permissions defined by this service account's RBAC bindings.
**Security Considerations:**
- All users have identical access to the plugin
- Suitable for trusted internal environments
- Simpler RBAC management
### OIDC Token Authentication
Headlamp can be configured for OIDC authentication, where each user provides their own bearer token. RBAC is enforced per-user.
**Security Considerations:**
- Fine-grained access control per user
- Users without the `polaris-proxy-reader` role will see 403 errors
- Requires OIDC provider integration
- Suitable for multi-tenant or compliance-focused environments
**Configuration Example:**
```yaml
config:
oidc:
clientID: "headlamp"
clientSecret: "secret"
issuerURL: "https://authentik.example.com/application/o/headlamp/"
scopes: "openid profile email groups"
```
When OIDC is enabled, each user's token is used for API requests, including service proxy calls.
## Vulnerability Reporting
### Supported Versions
We apply security updates to the latest release only. Please ensure you are running the most recent version.
| Version | Supported |
| ------- | ------------------ |
| latest | :white_check_mark: |
| < latest| :x: |
### Reporting a Vulnerability
If you discover a security vulnerability in this plugin, please report it via:
1. **GitHub Security Advisories**: [Report a vulnerability](https://github.com/cpfarhood/headlamp-polaris-plugin/security/advisories/new)
2. **Email**: Create a GitHub issue and mark it as "security" if advisories are not available
**Please do not:**
- Open public GitHub issues for security vulnerabilities
- Disclose vulnerabilities publicly before a fix is available
**Response Timeline:**
- **Acknowledgment**: Within 48 hours
- **Initial Assessment**: Within 1 week
- **Fix Timeline**: Depends on severity (critical: 1-2 weeks, high: 2-4 weeks, medium/low: next release cycle)
## Dependency Security
### Dependency Scanning
The project uses:
- **npm audit**: Runs automatically during `npm install`
- **Dependabot**: GitHub Dependabot monitors dependencies and creates PRs for updates
- **GitHub Actions**: CI workflow runs `npm audit` on every commit
### Updating Dependencies
Security patches are applied as follows:
1. **Critical vulnerabilities**: Emergency patch release within 48 hours
2. **High severity**: Patched in next minor release (typically within 1-2 weeks)
3. **Medium/Low severity**: Included in regular release cycle
### Headlamp Plugin API
This plugin depends on `@kinvolk/headlamp-plugin` as a peer dependency. Security updates to Headlamp itself should be applied by upgrading your Headlamp installation.
**Minimum supported Headlamp version**: v0.26.0
## Deployment Security
### Production Checklist
Before deploying to production, verify:
- [ ] **RBAC configured**: `polaris-proxy-reader` Role and RoleBinding exist
- [ ] **Network policies**: Allow API server → Polaris dashboard traffic
- [ ] **TLS enabled**: Headlamp accessible only via HTTPS
- [ ] **OIDC configured** (if using per-user auth): Token-based authentication working
- [ ] **Audit logging enabled**: Kubernetes API audit logs capture service proxy requests
- [ ] **Plugin version**: Running latest release
- [ ] **Dependencies audited**: No critical vulnerabilities in npm dependencies
- [ ] **Polaris version**: Polaris dashboard is up-to-date
### Kubernetes Cluster Security
The plugin's security posture depends on your cluster's security:
- **API Server Access**: Ensure API server is not publicly accessible without authentication
- **Service Account Tokens**: Use projected volume tokens with short expiration (Kubernetes 1.21+)
- **Pod Security Standards**: Apply appropriate pod security policies/standards to the Headlamp namespace
- **RBAC Auditing**: Regularly review RoleBindings to ensure least privilege
## Common Security Scenarios
### Scenario 1: 403 Forbidden Error
**Symptom**: Plugin shows "403 Forbidden" when loading data
**Cause**: User or service account lacks `services/proxy` permission on `polaris-dashboard`
**Resolution**:
1. Verify RoleBinding exists in `polaris` namespace
2. Check RoleBinding references correct subject (service account, group, or user)
3. Confirm Role includes `resourceNames: ["polaris-dashboard"]`
**Security Note**: This is expected behavior when RBAC is correctly enforced. Do not grant broader permissions to "fix" 403 errors.
### Scenario 2: Exposing Polaris Dashboard Externally
**Question**: Can I expose Polaris dashboard via Ingress instead of using service proxy?
**Recommendation**: **Avoid exposing Polaris dashboard externally**. The service proxy approach:
- Enforces Kubernetes RBAC on every request
- Avoids exposing internal services to the internet
- Prevents authentication bypass attacks
If you must expose Polaris externally:
- Use OAuth2 proxy or similar authentication layer
- Configure NetworkPolicies to restrict access
- Enable TLS with valid certificates
- Consider IP allowlisting
### Scenario 3: Multi-Tenant Clusters
**Question**: How do I restrict plugin access in a multi-tenant cluster?
**Solution**: Use OIDC authentication with per-user RoleBindings:
```yaml
# Bind only to specific groups or users
subjects:
- kind: Group
name: "team-a"
apiGroup: rbac.authorization.k8s.io
```
Users not in `team-a` will receive 403 errors when accessing the plugin, preventing unauthorized access to Polaris audit data.
## Compliance Considerations
### Data Residency
All data remains within your Kubernetes cluster. The plugin does not:
- Send data to external services
- Store data in browser localStorage (except refresh interval preference)
- Use third-party analytics or tracking
### Audit Trail
All service proxy requests are logged in Kubernetes API audit logs (if enabled):
```json
{
"verb": "get",
"requestURI": "/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json",
"user": {
"username": "system:serviceaccount:kube-system:headlamp",
"groups": ["system:serviceaccounts", "system:authenticated"]
}
}
```
### GDPR/Privacy
The plugin processes only technical metadata (resource names, namespaces, check results). No personal data is collected, stored, or transmitted.
## Security Updates and Notifications
### Notification Channels
Subscribe to security updates via:
1. **GitHub Watch**: Click "Watch" → "Custom" → "Security alerts"
2. **GitHub Releases**: Monitor [releases page](https://github.com/cpfarhood/headlamp-polaris-plugin/releases)
3. **ArtifactHub**: Follow package at [ArtifactHub](https://artifacthub.io/packages/headlamp/headlamp-polaris-plugin/headlamp-polaris-plugin)
### Security Patch Process
When a security vulnerability is identified:
1. **Private Fix**: Develop fix in private fork
2. **Security Advisory**: Publish GitHub Security Advisory
3. **Release**: Create new version with fix
4. **Notification**: Update advisory with fix version
5. **Disclosure**: Public disclosure after fix is available
## Contact
- **Security Issues**: [GitHub Security Advisories](https://github.com/cpfarhood/headlamp-polaris-plugin/security/advisories)
- **General Questions**: [GitHub Discussions](https://github.com/cpfarhood/headlamp-polaris-plugin/discussions)
- **Bug Reports**: [GitHub Issues](https://github.com/cpfarhood/headlamp-polaris-plugin/issues)
## License
This plugin is provided under the MIT License. See [LICENSE](LICENSE) for details.
+556
View File
@@ -0,0 +1,556 @@
# Architecture
This document describes the architecture, design decisions, and data flow of the Headlamp Polaris Plugin.
## Table of Contents
- [Overview](#overview)
- [System Architecture](#system-architecture)
- [Data Flow](#data-flow)
- [Component Hierarchy](#component-hierarchy)
- [State Management](#state-management)
- [Design Decisions](#design-decisions)
- [Integration Points](#integration-points)
- [Known Limitations](#known-limitations)
## Overview
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
- **Headlamp plugin API:** Integrates via official plugin system
- **Type-safe:** Full TypeScript with strict mode
## System Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Headlamp UI (React) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ App Bar │ │ Sidebar │ │ Routes │ │
│ │ (Badge) │ │ (Navigation)│ │ (Views) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Plugin Registry │ │
│ └────────┬────────┘ │
│ │ │
│ ┌─────────────▼──────────────┐ │
│ │ Polaris Plugin (This!) │ │
│ ├────────────────────────────┤ │
│ │ • registerSidebarEntry │ │
│ │ • registerRoute │ │
│ │ • registerAppBarAction │ │
│ │ • registerPluginSettings │ │
│ │ • registerDetailsViewSection│ │
│ └─────────────┬──────────────┘ │
│ │ │
│ ┌─────────────▼──────────────┐ │
│ │ PolarisDataContext │ │
│ │ (React Context Provider) │ │
│ └─────────────┬──────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ │ │ │ │
│ ┌────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
│ │Dashboard │ │ Namespaces │ │ Namespace │ │
│ │View │ │ ListView │ │ Detail │ │
│ └──────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
┌───────▼────────┐
│ ApiProxy │
│ (Headlamp) │
└───────┬────────┘
┌───────▼────────┐
│ Kubernetes │
│ API Server │
└───────┬────────┘
┌───────▼────────┐
│ Service Proxy │
│ /api/v1/ns/ │
│ polaris/svcs/ │
│ polaris- │
│ dashboard/ │
│ proxy/ │
└───────┬────────┘
┌───────▼────────┐
│ Polaris │
│ Dashboard │
│ (ClusterIP) │
└───────┬────────┘
┌───────▼────────┐
│ results.json │
│ (AuditData) │
└────────────────┘
```
## Data Flow
### 1. Initial Load
```
User loads Headlamp
Headlamp loads plugins
Plugin registers routes, sidebar, app bar actions
User navigates to /polaris
DashboardView mounts
PolarisDataContext.Provider wraps component
usePolarisDataContext() hook triggers fetch
ApiProxy.request() → K8s API → Service Proxy → Polaris
AuditData returned and cached in Context
Components receive data and render
```
### 2. Data Refresh
```
User clicks "Refresh" button or auto-refresh interval elapses
refresh() function called in Context
setRefreshKey() increments (forces re-fetch)
useEffect dependency triggers new fetch
ApiProxy.request() → Polaris Dashboard
Context state updated with new data
All consuming components re-render automatically
```
### 3. Navigation Flow
```
User clicks "Polaris" in sidebar
Route: /c/main/polaris (DashboardView)
Display cluster score, check distribution
User clicks "Namespaces" submenu
Route: /c/main/polaris/namespaces (NamespacesListView)
Display table of namespaces with scores
User clicks namespace button in table
Drawer opens, URL hash updates (#namespace-name)
NamespaceDetailView renders in drawer
Display namespace score + resource table
```
## Component Hierarchy
### Plugin Entry Point
**`src/index.tsx`**
- Registers sidebar entries (Polaris → Overview, Namespaces)
- Registers routes (`/polaris`, `/polaris/namespaces`)
- Registers app bar action (score badge)
- Registers plugin settings page
- Registers details view section (inline audit)
### 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
### View Components
**`src/components/DashboardView.tsx`**
- **Route:** `/polaris`
- **Purpose:** Cluster-wide overview
- **Features:**
- Cluster score (percentage)
- Check distribution (pass/warning/danger/skipped)
- Cluster info (Polaris version, last audit time)
- Refresh button
- **Data:** Uses `usePolarisDataContext()`
**`src/components/NamespacesListView.tsx`**
- **Route:** `/polaris/namespaces`
- **Purpose:** List all namespaces with scores
- **Features:**
- Table with namespace, score, pass/warning/danger counts
- Clickable namespace buttons (opens drawer)
- Sorted by score (lowest first)
- **Data:** Uses `usePolarisDataContext()`, aggregates by namespace
**`src/components/NamespaceDetailView.tsx`**
- **Route:** Drawer on `/polaris/namespaces#<namespace>`
- **Purpose:** Namespace-level drill-down
- **Features:**
- Namespace score
- Resource table (kind, name, score, counts)
- URL hash navigation
- Keyboard shortcuts (Escape to close)
- **Data:** Filters `usePolarisDataContext()` by namespace
### UI Components
**`src/components/AppBarScoreBadge.tsx`**
- **Location:** Headlamp app bar (top-right)
- **Purpose:** Quick cluster score visibility
- **Features:**
- Color-coded badge (green ≥80%, orange ≥50%, red <50%)
- Clickable (navigates to `/polaris`)
- Shield emoji icon
- **Data:** Uses `usePolarisDataContext()`
**`src/components/PolarisSettings.tsx`**
- **Location:** Settings → Plugins → Polaris
- **Purpose:** Plugin configuration
- **Features:**
- Refresh interval selector (1 min to 30 min)
- Dashboard URL input (custom Polaris instances)
- Connection test button
- **Data:** localStorage for persistence
**`src/components/InlineAuditSection.tsx`**
- **Location:** Resource detail pages (Deployment, StatefulSet, etc.)
- **Purpose:** Show Polaris audit inline
- **Features:**
- Pass/warning/danger counts
- Check details with messages
- Severity badges
- **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:**
- View current exemptions
- Add exemptions for failing checks
- Remove exemptions
## State Management
### Why React Context?
**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)
4. **Performance:** Data changes infrequently (refresh interval 1-30 min)
### Context Structure
```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 Fetching Strategy
1. **Initial fetch:** On first mount of any component using the context
2. **Auto-refresh:** Based on user setting (default 5 minutes)
3. **Manual refresh:** Via refresh button in UI
4. **Caching:** Data persists in context until refresh (no per-route refetch)
### localStorage Usage
Settings persisted in localStorage:
- **`polaris-plugin-refresh-interval`**: Number (seconds), default 300
- **`polaris-plugin-dashboard-url`**: String, default service proxy path
No sensitive data stored in localStorage.
## Design Decisions
### 1. Service Proxy vs. Direct Access
**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`
### 2. Two-Level Sidebar Nesting
**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)
### 3. Drawer Navigation Instead of Routes
**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
### 4. No MUI Direct Imports
**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-*`)
### 5. TypeScript Strict Mode
**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
### 6. Auto-Refresh Default: 5 Minutes
**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)
## Integration Points
### Headlamp Plugin API
**Version:** ≥ v0.13.0
**Registration Functions Used:**
```typescript
// Sidebar navigation
registerSidebarEntry({ parent, name, label, url, icon })
// Routes
registerRoute({ path, sidebar, name, exact, component })
// App bar actions
registerAppBarAction(component)
// Plugin settings
registerPluginSettings(name, component, displaySaveButton)
// Resource detail sections
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
- `NameValueTable` - Key-value table layout
- `SimpleTable` - Data table with sorting
- `Drawer` - Right-side overlay panel
- `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
### 1. Sidebar Nesting Depth
**Limitation:** Headlamp sidebar supports only 2 levels
**Impact:** Cannot have dynamic per-namespace sidebar entries
**Workaround:** Use table with drawer navigation
### 2. Skipped Checks Visibility
**Limitation:** Skipped checks (severity "ignore") counted but details not shown in dashboard
**Reason:** Polaris API groups skipped checks but doesn't provide per-check details
**Impact:** Users see skipped count but can't drill down to specific skipped checks
**Documented:** README, tooltip on skipped count
### 3. No Write Operations
**Limitation:** Plugin cannot modify Polaris configuration or exemptions
**Reason:** Read-only by design (service proxy only has `get` permission)
**Impact:** Exemption manager UI exists but requires manual annotation edits
**Future:** Could add PATCH permission to enable exemption annotations via UI
### 4. No Real-Time Updates
**Limitation:** Data refreshes on interval (1-30 minutes), not real-time
**Reason:** Polaris dashboard doesn't support WebSocket/SSE
**Impact:** Users may see stale data between refreshes
**Workaround:** Manual refresh button, configurable interval
### 5. MUI Import Restrictions
**Limitation:** Cannot import MUI components directly
**Reason:** Headlamp plugin environment doesn't provide full MUI bundle
**Impact:** Must use Headlamp CommonComponents or HTML elements
**Documented:** CLAUDE.md, CONTRIBUTING.md
### 6. Single Cluster Support
**Limitation:** Plugin shows data for current cluster only
**Reason:** Headlamp's multi-cluster support is route-based (`/c/<cluster>/...`)
**Impact:** Users must switch clusters in Headlamp to see different cluster's Polaris data
**Future:** Could enhance to aggregate multi-cluster if Headlamp API supports it
## Performance Considerations
### Bundle Size
- **Current:** ~27 KB minified (gzip: ~7.6 KB)
- **Target:** Keep under 50 KB to ensure fast loading
- **Strategy:** No heavy dependencies, tree-shaking enabled
### Data Fetching
- **Lazy loading:** Data not fetched until user navigates to plugin
- **Caching:** Single fetch shared across all views (React Context)
- **Refresh strategy:** User-controlled interval prevents excessive API calls
### Rendering
- **React.memo:** Not needed (data changes infrequently)
- **Virtual scrolling:** Not needed (namespace/resource lists typically <100 items)
- **Component splitting:** Lazy load views if bundle grows significantly
## Future Architecture Enhancements
### 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
5. **Incremental updates**
- Fetch only changed namespaces/resources
- Reduce bandwidth and processing
## References
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
- [Fairwinds Polaris Documentation](https://polaris.docs.fairwinds.com/)
- [React Context API](https://react.dev/reference/react/useContext)
- [Kubernetes Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/)
+689
View File
@@ -0,0 +1,689 @@
# Deployment Guide
This document provides comprehensive deployment instructions for the Headlamp Polaris Plugin in production Kubernetes environments.
## Table of Contents
- [Prerequisites](#prerequisites)
- [Installation Methods](#installation-methods)
- [Helm Integration](#helm-integration)
- [RBAC Configuration](#rbac-configuration)
- [Network Policies](#network-policies)
- [Plugin Manager Setup](#plugin-manager-setup)
- [Production Checklist](#production-checklist)
- [Troubleshooting](#troubleshooting)
## Prerequisites
### Required Components
1. **Kubernetes Cluster:** v1.19 or later
2. **Headlamp:** v0.26 or later (v0.39+ recommended)
3. **Polaris:** Deployed and accessible via service
4. **RBAC:** Permissions to create Roles and RoleBindings
### Pre-Deployment Verification
```bash
# Verify Polaris is deployed
kubectl -n polaris get pods
kubectl -n polaris get svc polaris-dashboard
# Verify Polaris dashboard is responding
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
# Verify Headlamp is deployed
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
```
## Installation Methods
### Method 1: Headlamp Plugin Manager (Recommended)
**Best for:** Production deployments, managed updates
1. **Enable Plugin Manager in Headlamp:**
```yaml
# headlamp-values.yaml
config:
pluginsDir: "/headlamp/plugins"
pluginsManager:
enabled: true
repositories:
- https://artifacthub.io/packages/search?kind=4
```
2. **Deploy/Update Headlamp:**
```bash
helm upgrade --install headlamp headlamp/headlamp \
--namespace kube-system \
--values headlamp-values.yaml
```
3. **Install Plugin via UI:**
- Navigate to Headlamp → Settings → Plugins
- Search for "Polaris"
- Click "Install"
- Refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
### Method 2: Sidecar Container (Alternative)
**Best for:** Controlled plugin versions, air-gapped environments
```yaml
# headlamp-values.yaml
config:
pluginsDir: "/headlamp/plugins"
watchPlugins: false # CRITICAL: Must be false for plugin manager
replicaCount: 1
initContainers:
- name: install-polaris-plugin
image: node:lts-alpine
command:
- sh
- -c
- |
npm install -g @kinvolk/headlamp-plugin
headlamp-plugin install --config /config/plugin.yml --plugins-dir /plugins
volumeMounts:
- name: plugins
mountPath: /plugins
- name: plugin-config
mountPath: /config
volumes:
- name: plugins
emptyDir: {}
- name: plugin-config
configMap:
name: headlamp-plugin-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: headlamp-plugin-config
namespace: kube-system
data:
plugin.yml: |
- name: headlamp-polaris-plugin
version: 0.3.4
url: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.3.4/headlamp-polaris-plugin-0.3.4.tar.gz
```
### Method 3: Volume Mount (Development)
**Best for:** Local testing, development
```yaml
# headlamp-values.yaml
config:
pluginsDir: "/plugins"
volumes:
- name: plugins
hostPath:
path: /path/to/plugins
type: Directory
volumeMounts:
- name: plugins
mountPath: /plugins
readOnly: true
```
Then manually place `headlamp-polaris-plugin/` in the host path.
## Helm Integration
### Complete Helm Values Example
```yaml
# headlamp-values.yaml
replicaCount: 2
image:
repository: ghcr.io/headlamp-k8s/headlamp
tag: v0.39.0
config:
baseURL: ""
pluginsDir: "/headlamp/plugins"
watchPlugins: false # MUST be false for plugin manager
pluginsManager:
enabled: true
repositories:
- https://artifacthub.io/packages/search?kind=4
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: headlamp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: headlamp-tls
hosts:
- headlamp.example.com
serviceAccount:
create: true
name: headlamp
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
# OIDC Authentication (optional)
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"
```
### FluxCD HelmRelease Example
```yaml
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: headlamp
namespace: kube-system
spec:
interval: 30m
chart:
spec:
chart: headlamp
version: 0.26.x
sourceRef:
kind: HelmRepository
name: headlamp
namespace: flux-system
interval: 12h
values:
config:
pluginsDir: "/headlamp/plugins"
watchPlugins: false
pluginsManager:
enabled: true
repositories:
- https://artifacthub.io/packages/search?kind=4
service:
type: ClusterIP
ingress:
enabled: true
className: nginx
hosts:
- host: headlamp.example.com
paths:
- path: /
pathType: Prefix
```
## RBAC Configuration
### Minimal Role for Plugin
The plugin requires **read-only** access to the Polaris dashboard service proxy.
```yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: polaris-proxy-reader
namespace: polaris
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
```
### RoleBinding Options
#### Option A: Headlamp Service Account (In-Cluster Mode)
```yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: headlamp-polaris-proxy
namespace: polaris
subjects:
- kind: ServiceAccount
name: headlamp
namespace: kube-system
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
```
#### Option B: User Groups (Token/OIDC Mode)
```yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: 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
```
#### Option C: Specific Users (Fine-Grained Control)
```yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: devops-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
- kind: Group
name: devops-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
```
### Complete RBAC Manifest
```yaml
---
# Role: Read-only access to Polaris service proxy
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"]
---
# RoleBinding: Grant Headlamp service account access
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
namespace: kube-system
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
```
Apply with:
```bash
kubectl apply -f polaris-plugin-rbac.yaml
```
## Network Policies
### Required Network Access
The plugin requires network connectivity:
- **Headlamp pod** → **Kubernetes API server** (service proxy)
- **Kubernetes API server** → **Polaris dashboard service** (port 80)
### Network Policy Example
If your `polaris` namespace has strict NetworkPolicies:
```yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-headlamp-to-polaris
namespace: polaris
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: polaris
app.kubernetes.io/component: dashboard
policyTypes:
- Ingress
ingress:
# Allow from API server (service proxy)
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
- podSelector:
matchLabels:
component: kube-apiserver
ports:
- protocol: TCP
port: 80
```
**Note:** The API server performs the proxy hop, not the Headlamp pod directly.
## 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/
# Expected output:
# drwxr-xr-x headlamp-polaris-plugin/
```
## Production Checklist
### Pre-Deployment
- [ ] Polaris deployed and running
- [ ] 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
- [ ] Plugin installed via plugin manager or sidecar
- [ ] Headlamp pods restarted (if config changed)
- [ ] Browser cache cleared (Cmd+Shift+R / Ctrl+Shift+R)
### Post-Deployment Verification
```bash
# 1. Verify Polaris is accessible via service proxy
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
# Expected: "1.0" or similar
# 2. Verify RBAC is correct
kubectl auth can-i get services/proxy --as=system:serviceaccount:kube-system:headlamp -n polaris --resource-name=polaris-dashboard
# Expected: yes
# 3. Check Headlamp logs
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 -it deployment/headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
# Expected: dist/, package.json present
```
### UI Verification
- [ ] Navigate to Headlamp → Settings → Plugins
- [ ] Plugin "headlamp-polaris-plugin" listed
- [ ] Sidebar shows "Polaris" entry
- [ ] Click "Polaris" → Overview page loads
- [ ] Cluster score displays correctly
- [ ] Namespaces page shows table
- [ ] App bar shows Polaris score badge
## Troubleshooting
### Plugin Not Appearing in Sidebar
**Symptom:** Plugin listed in Settings → Plugins but no sidebar entry
**Causes:**
1. `watchPlugins: true` (should be `false`)
2. Browser cache not cleared
**Solution:**
```bash
# Fix Headlamp config
kubectl -n kube-system edit configmap headlamp
# Set watchPlugins: false
# Restart Headlamp
kubectl -n kube-system rollout restart deployment/headlamp
# Clear browser cache
# Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
```
### 403 Forbidden Error
**Symptom:** Error loading Polaris data, 403 in console
**Cause:** RBAC missing or incorrect
**Solution:**
```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
# If "no", create RBAC (see RBAC Configuration section)
```
### 404 Not Found Error
**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
kubectl -n polaris get svc polaris-dashboard
# If service doesn't exist, install Polaris:
helm install polaris fairwinds-stable/polaris \
--namespace polaris \
--create-namespace \
--set dashboard.enabled=true
```
### Custom Dashboard URL Not Working
**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
# For external Polaris, check CORS headers
# Must allow Headlamp origin
```
### Plugin Shows Old Version
**Symptom:** Plugin version in Settings doesn't match expected
**Cause:** Plugin manager hasn't synced from ArtifactHub
**Solution:**
```bash
# Wait 30 minutes (ArtifactHub sync interval)
# Or manually refresh plugin list in Headlamp UI
# Force Headlamp restart
kubectl -n kube-system rollout restart deployment/headlamp
```
### Network Policy Blocking Access
**Symptom:** Timeout or connection errors despite correct RBAC
**Cause:** NetworkPolicy in `polaris` namespace blocking API server
**Solution:**
```bash
# Check NetworkPolicies
kubectl -n polaris get networkpolicy
# Test connectivity from API server (if possible)
# Add NetworkPolicy to allow API server → Polaris dashboard (see Network Policies section)
```
## Security Considerations
### Least Privilege
- Grant only `get` on `services/proxy`, not broader permissions
- Use `resourceNames` to restrict to specific service (`polaris-dashboard`)
- Scope to `polaris` namespace only (Role, not ClusterRole)
### 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"]
resources:
- group: ""
resources: ["services/proxy"]
namespaces: ["polaris"]
```
### Data Sensitivity
Polaris audit data may contain:
- Resource names and namespaces
- Configuration details
- Potential security vulnerabilities
**Recommendation:** Restrict plugin access to authorized users only (not `system:authenticated` group unless appropriate).
## Upgrading
### Plugin Upgrade via Plugin Manager
1. Navigate to Settings → Plugins
2. Find "headlamp-polaris-plugin"
3. Click "Update" if new version available
4. Refresh browser (Cmd+Shift+R / Ctrl+Shift+R)
### Sidecar Method Upgrade
1. Update ConfigMap with new version/URL
2. Restart Headlamp deployment
3. Verify new version in Settings → Plugins
```bash
kubectl -n kube-system edit configmap headlamp-plugin-config
# Update version and URL
kubectl -n kube-system rollout restart deployment/headlamp
```
## References
- [Headlamp Deployment](https://headlamp.dev/docs/latest/installation/)
- [Headlamp Helm Chart](https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp)
- [Polaris Installation](https://polaris.docs.fairwinds.com/infrastructure-as-code/)
- [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
- [Kubernetes Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/#manually-constructing-apiserver-proxy-urls)