feat(fairwinds-polaris): install Fairwinds Polaris

This commit is contained in:
Masaki Yatsu
2025-11-10 13:48:27 +09:00
parent c4db1b8cd2
commit 189a376511
5 changed files with 593 additions and 0 deletions

1
fairwinds-polaris/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
values.yaml

188
fairwinds-polaris/README.md Normal file
View File

@@ -0,0 +1,188 @@
# Fairwinds Polaris
Fairwinds Polaris is a Kubernetes security audit tool that validates cluster configurations against best practices.
## Features
- Dashboard for visualizing security audit results
- Checks for security, efficiency, and reliability issues
- Customizable security policies
- Support for exemptions
- Real-time cluster scanning
## Prerequisites
- Kubernetes cluster (k3s)
- Helm 3
- kubectl configured
## Installation
Install Fairwinds Polaris with interactive configuration:
```bash
just fairwinds-polaris::install
```
During installation, you will be prompted to:
1. **Enable Ingress?**
- Yes: Expose via Ingress (requires FQDN)
- No: Access via port-forward (recommended for development)
2. **Enable OAuth2 Proxy authentication?** (only if Ingress is enabled)
- Yes: Keycloak SSO authentication
- No: Public access without authentication
### Access Options
**Ingress (if enabled):**
- Without OAuth2 Proxy: Direct access via `https://fairwinds-polaris.yourdomain.com`
- With OAuth2 Proxy: Keycloak authentication required via `https://fairwinds-polaris.yourdomain.com`
**Port-forward (without Ingress):**
```bash
just fairwinds-polaris::port-forward
# Opens on http://localhost:8080
```
## Usage
### View Audit Results
Port-forward to dashboard:
```bash
just fairwinds-polaris::port-forward
```
Or fetch JSON results:
```bash
just fairwinds-polaris::audit
```
### Upgrade
```bash
just fairwinds-polaris::upgrade
```
### Uninstall
```bash
just fairwinds-polaris::uninstall
```
## Configuration
Configuration is managed through `values.gomplate.yaml`.
### Security Checks
Polaris performs the following security checks:
- **Security**
- `hostIPCSet`: danger
- `hostPIDSet`: danger
- `notReadOnlyRootFilesystem`: warning
- `privilegeEscalationAllowed`: danger
- `runAsRootAllowed`: warning
- `runAsPrivileged`: danger
- `insecureCapabilities`: warning
- `dangerousCapabilities`: danger
- **Efficiency**
- `cpuRequestsMissing`: warning
- `cpuLimitsMissing`: warning
- `memoryRequestsMissing`: warning
- `memoryLimitsMissing`: warning
- **Reliability**
- `tagNotSpecified`: danger
- `readinessProbeMissing`: warning
- `livenessProbeMissing`: warning
- `deploymentMissingReplicas`: ignore (disabled)
- **Network**
- `hostNetworkSet`: warning
- `hostPortSet`: warning
- `missingNetworkPolicy`: warning
### Exemptions
System components are pre-configured with exemptions:
- kube-system controllers
- Monitoring tools (Prometheus, Grafana)
- Networking components (Flannel, Calico)
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `FAIRWINDS_POLARIS_NAMESPACE` | `fairwinds-polaris` | Kubernetes namespace |
| `FAIRWINDS_POLARIS_CHART_VERSION` | `5.19.0` | Helm chart version |
| `FAIRWINDS_POLARIS_HOST` | - | FQDN for Ingress (when enabled) |
| `FAIRWINDS_POLARIS_INGRESS_ENABLED` | `false` | Enable Ingress |
| `KEYCLOAK_REALM` | `buunstack` | Keycloak realm |
| `KEYCLOAK_HOST` | - | Keycloak host |
## Understanding Results
Polaris categorizes issues by severity:
- 🔴 **Danger**: Critical security issues
- 🟡 **Warning**: Important but not critical
- 🟢 **Success**: Passed all checks
### Score Calculation
Each check has a severity level that contributes to the overall score:
- Danger: -10 points
- Warning: -1 point
- Success: +1 point
## Best Practices
1. **Regular Scanning**: Run Polaris regularly to catch configuration drift
2. **Address Dangers First**: Focus on danger-level issues before warnings
3. **Review Exemptions**: Periodically review exempted resources
4. **CI/CD Integration**: Consider integrating Polaris into your deployment pipeline
## Troubleshooting
### Dashboard Not Accessible
Check if the service is running:
```bash
kubectl get pods -n polaris
kubectl get svc -n polaris
```
### Port-forward Fails
Ensure the dashboard service is ready:
```bash
kubectl get svc polaris-dashboard -n polaris
```
### Ingress Not Working
Check IngressRoute and OAuth2 Proxy:
```bash
kubectl get ingressroute -n polaris
kubectl get pods -n polaris | grep oauth2-proxy
```
## References
- [Polaris Documentation](https://polaris.docs.fairwinds.com/)
- [GitHub Repository](https://github.com/FairwindsOps/polaris)
- [Helm Chart](https://github.com/FairwindsOps/charts/tree/master/stable/polaris)

163
fairwinds-polaris/justfile Normal file
View File

@@ -0,0 +1,163 @@
set fallback := true
export FAIRWINDS_POLARIS_NAMESPACE := env("FAIRWINDS_POLARIS_NAMESPACE", "fairwinds-polaris")
export FAIRWINDS_POLARIS_CHART_VERSION := env("FAIRWINDS_POLARIS_CHART_VERSION", "5.19.0")
export FAIRWINDS_POLARIS_HOST := env("FAIRWINDS_POLARIS_HOST", "")
export FAIRWINDS_POLARIS_INGRESS_ENABLED := env("FAIRWINDS_POLARIS_INGRESS_ENABLED", "false")
export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "buunstack")
export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "")
[private]
default:
@just --list --unsorted --list-submodules
# Add Helm repository
add-helm-repo:
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm repo update
# Remove Helm repository
remove-helm-repo:
helm repo remove fairwinds-stable
# Create namespace
create-namespace:
@kubectl get namespace ${FAIRWINDS_POLARIS_NAMESPACE} &>/dev/null || \
kubectl create namespace ${FAIRWINDS_POLARIS_NAMESPACE}
# Delete namespace
delete-namespace:
@kubectl delete namespace ${FAIRWINDS_POLARIS_NAMESPACE} --ignore-not-found
# Install Fairwinds Polaris
install:
#!/bin/bash
set -euo pipefail
echo "Installing Fairwinds Polaris..."
just create-namespace
just add-helm-repo
enable_ingress="false"
enable_oauth2="false"
if gum confirm "Enable Ingress for external access?"; then
if [ -z "${FAIRWINDS_POLARIS_HOST}" ]; then
while [ -z "${FAIRWINDS_POLARIS_HOST}" ]; do
FAIRWINDS_POLARIS_HOST=$(
gum input --prompt="Fairwinds Polaris host (FQDN): " --width=100 \
--placeholder="e.g., fairwinds-polaris.example.com"
)
done
just env::set FAIRWINDS_POLARIS_HOST="${FAIRWINDS_POLARIS_HOST}"
fi
if gum confirm "Enable OAuth2 Proxy authentication with Keycloak?"; then
enable_oauth2="true"
enable_ingress="false"
echo "Creating OAuth2 Proxy for Fairwinds Polaris..."
just oauth2-proxy::setup-for-app \
polaris \
"${FAIRWINDS_POLARIS_HOST}" \
"${FAIRWINDS_POLARIS_NAMESPACE}" \
"polaris-dashboard.${FAIRWINDS_POLARIS_NAMESPACE}.svc.cluster.local:80"
else
enable_ingress="true"
fi
fi
export FAIRWINDS_POLARIS_INGRESS_ENABLED="${enable_ingress}"
gomplate -f values.gomplate.yaml -o values.yaml
helm upgrade --cleanup-on-fail --install polaris \
fairwinds-stable/polaris \
--version ${FAIRWINDS_POLARIS_CHART_VERSION} \
-n ${FAIRWINDS_POLARIS_NAMESPACE} \
--wait \
-f values.yaml
echo ""
echo "=== Fairwinds Polaris installed ==="
if [ "${enable_ingress}" = "true" ]; then
echo "Fairwinds Polaris URL: https://${FAIRWINDS_POLARIS_HOST}"
if [ "${enable_oauth2}" = "true" ]; then
echo "Authentication: OAuth2 Proxy with Keycloak"
echo "Users can sign in with their Keycloak credentials"
else
echo "Authentication: None (consider using OAuth2 Proxy for production)"
fi
else
echo "Fairwinds Polaris dashboard is running in namespace: ${FAIRWINDS_POLARIS_NAMESPACE}"
echo ""
echo "To access the dashboard, run:"
echo " just fairwinds-polaris::port-forward"
echo ""
echo "Then open http://localhost:8080 in your browser"
fi
# Upgrade Fairwinds Polaris
upgrade:
#!/bin/bash
set -euo pipefail
echo "Upgrading Fairwinds Polaris..."
if helm get values polaris -n ${FAIRWINDS_POLARIS_NAMESPACE} -o json | jq -e '.dashboard.ingress.enabled == true' &>/dev/null; then
export FAIRWINDS_POLARIS_INGRESS_ENABLED="true"
if [ -z "${FAIRWINDS_POLARIS_HOST}" ]; then
FAIRWINDS_POLARIS_HOST=$(helm get values polaris -n ${FAIRWINDS_POLARIS_NAMESPACE} -o json | \
jq -r '.dashboard.ingress.hosts[0].host // empty')
if [ -z "${FAIRWINDS_POLARIS_HOST}" ]; then
while [ -z "${FAIRWINDS_POLARIS_HOST}" ]; do
FAIRWINDS_POLARIS_HOST=$(
gum input --prompt="Fairwinds Polaris host (FQDN): " --width=100 \
--placeholder="e.g., fairwinds-polaris.example.com"
)
done
fi
fi
else
export FAIRWINDS_POLARIS_INGRESS_ENABLED="false"
fi
gomplate -f values.gomplate.yaml -o values.yaml
helm upgrade polaris \
fairwinds-stable/polaris \
--version ${FAIRWINDS_POLARIS_CHART_VERSION} \
-n ${FAIRWINDS_POLARIS_NAMESPACE} \
--wait \
-f values.yaml
echo "Fairwinds Polaris upgraded successfully"
# Uninstall Fairwinds Polaris
uninstall:
#!/bin/bash
set -euo pipefail
echo "Uninstalling Fairwinds Polaris..."
helm uninstall polaris -n ${FAIRWINDS_POLARIS_NAMESPACE} --ignore-not-found
kubectl delete ingressroute polaris -n ${FAIRWINDS_POLARIS_NAMESPACE} --ignore-not-found
just oauth2-proxy::remove-for-app polaris ${FAIRWINDS_POLARIS_NAMESPACE} || true
just delete-namespace
echo "Fairwinds Polaris uninstalled"
# Port forward to Fairwinds Polaris dashboard
port-forward port='8080':
kubectl port-forward --namespace ${FAIRWINDS_POLARIS_NAMESPACE} svc/polaris-dashboard {{ port }}:80
# Show Fairwinds Polaris audit results
audit:
#!/bin/bash
set -euo pipefail
echo "Fetching Fairwinds Polaris audit results..."
kubectl get validatingwebhookconfigurations polaris-webhook -o json 2>/dev/null | \
jq -r '.webhooks[0].clientConfig.caBundle' | base64 -d > /tmp/polaris-ca.crt || true
if kubectl get svc polaris-dashboard -n ${FAIRWINDS_POLARIS_NAMESPACE} &>/dev/null; then
kubectl port-forward -n ${FAIRWINDS_POLARIS_NAMESPACE} svc/polaris-dashboard 18080:80 &
PF_PID=$!
sleep 2
curl -s http://localhost:18080/results.json | jq '.' || echo "Dashboard not ready yet"
kill $PF_PID 2>/dev/null || true
else
echo "Fairwinds Polaris dashboard service not found. Please install Polaris first."
fi

View File

@@ -0,0 +1,240 @@
configUrl: null
dashboard:
replicas: 1
port: 8080
service:
type: ClusterIP
annotations: {}
{{- if eq .Env.FAIRWINDS_POLARIS_INGRESS_ENABLED "true" }}
ingress:
enabled: true
ingressClassName: traefik
hosts:
- {{ .Env.FAIRWINDS_POLARIS_HOST }}
{{- else }}
ingress:
enabled: false
{{- end }}
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
webhook:
enable: false
# Audit job runs a one-time audit. This is used internally at Fairwinds, and is not needed for dashboard mode.
audit:
enable: false
outputURL: ""
config:
checks:
# Security
hostIPCSet: danger
hostPIDSet: danger
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
runAsRootAllowed: warning
runAsPrivileged: danger
insecureCapabilities: warning
dangerousCapabilities: danger
# Efficiency
cpuRequestsMissing: warning
cpuLimitsMissing: warning
memoryRequestsMissing: warning
memoryLimitsMissing: warning
# Reliability
tagNotSpecified: danger
pullPolicyNotAlways: ignore
readinessProbeMissing: warning
livenessProbeMissing: warning
deploymentMissingReplicas: ignore
priorityClassNotSet: ignore
# Network
hostNetworkSet: warning
hostPortSet: warning
missingNetworkPolicy: warning
exemptions:
- controllerNames:
- kube-apiserver
- kube-proxy
- kube-scheduler
- etcd-manager-events
- kube-controller-manager
- kube-dns
- etcd-manager-main
rules:
- hostPortSet
- hostNetworkSet
- readinessProbeMissing
- livenessProbeMissing
- cpuRequestsMissing
- cpuLimitsMissing
- memoryRequestsMissing
- memoryLimitsMissing
- runAsRootAllowed
- runAsPrivileged
- notReadOnlyRootFilesystem
- hostPIDSet
- controllerNames:
- kube-flannel-ds
rules:
- notReadOnlyRootFilesystem
- runAsRootAllowed
- notReadOnlyRootFilesystem
- readinessProbeMissing
- livenessProbeMissing
- cpuLimitsMissing
- controllerNames:
- cert-manager
rules:
- notReadOnlyRootFilesystem
- runAsRootAllowed
- readinessProbeMissing
- livenessProbeMissing
- controllerNames:
- cluster-autoscaler
rules:
- notReadOnlyRootFilesystem
- runAsRootAllowed
- readinessProbeMissing
- controllerNames:
- vpa
rules:
- runAsRootAllowed
- readinessProbeMissing
- livenessProbeMissing
- notReadOnlyRootFilesystem
- controllerNames:
- datadog
rules:
- runAsRootAllowed
- readinessProbeMissing
- livenessProbeMissing
- notReadOnlyRootFilesystem
- controllerNames:
- nginx-ingress-controller
rules:
- privilegeEscalationAllowed
- insecureCapabilities
- runAsRootAllowed
- controllerNames:
- dns-controller
- datadog-datadog
- kube-flannel-ds
- kube2iam
- aws-iam-authenticator
- datadog
- kube2iam
rules:
- hostNetworkSet
- controllerNames:
- aws-iam-authenticator
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- dnsmasq
- autoscaler
- kubernetes-dashboard
- install-cni
- kube2iam
rules:
- readinessProbeMissing
- livenessProbeMissing
- controllerNames:
- aws-iam-authenticator
- nginx-ingress-default-backend
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- kubedns
- dnsmasq
- autoscaler
- tiller
- kube2iam
rules:
- runAsRootAllowed
- controllerNames:
- aws-iam-authenticator
- nginx-ingress-controller
- nginx-ingress-default-backend
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- kubedns
- dnsmasq
- autoscaler
- tiller
- kube2iam
rules:
- notReadOnlyRootFilesystem
- controllerNames:
- cert-manager
- dns-controller
- kubedns
- dnsmasq
- autoscaler
- insights-agent-goldilocks-vpa-install
- datadog
rules:
- cpuRequestsMissing
- cpuLimitsMissing
- memoryRequestsMissing
- memoryLimitsMissing
- controllerNames:
- kube2iam
- kube-flannel-ds
rules:
- runAsPrivileged
- controllerNames:
- kube-hunter
rules:
- hostPIDSet
- controllerNames:
- polaris
- kube-hunter
- goldilocks
- insights-agent-goldilocks-vpa-install
rules:
- notReadOnlyRootFilesystem
- controllerNames:
- insights-agent-goldilocks-controller
rules:
- livenessProbeMissing
- readinessProbeMissing
- controllerNames:
- insights-agent-goldilocks-vpa-install
- kube-hunter
rules:
- runAsRootAllowed

View File

@@ -21,6 +21,7 @@ mod longhorn
mod metabase mod metabase
mod mlflow mod mlflow
mod minio mod minio
mod fairwinds-polaris
mod oauth2-proxy mod oauth2-proxy
mod postgres mod postgres
mod prometheus mod prometheus