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:
+358
@@ -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.
|
||||
@@ -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/)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user