From 189a376511d06e6980df80f72d25be5eda0e0177 Mon Sep 17 00:00:00 2001 From: Masaki Yatsu Date: Mon, 10 Nov 2025 13:48:27 +0900 Subject: [PATCH] feat(fairwinds-polaris): install Fairwinds Polaris --- fairwinds-polaris/.gitignore | 1 + fairwinds-polaris/README.md | 188 +++++++++++++++++++ fairwinds-polaris/justfile | 163 +++++++++++++++++ fairwinds-polaris/values.gomplate.yaml | 240 +++++++++++++++++++++++++ justfile | 1 + 5 files changed, 593 insertions(+) create mode 100644 fairwinds-polaris/.gitignore create mode 100644 fairwinds-polaris/README.md create mode 100644 fairwinds-polaris/justfile create mode 100644 fairwinds-polaris/values.gomplate.yaml diff --git a/fairwinds-polaris/.gitignore b/fairwinds-polaris/.gitignore new file mode 100644 index 0000000..7f47975 --- /dev/null +++ b/fairwinds-polaris/.gitignore @@ -0,0 +1 @@ +values.yaml diff --git a/fairwinds-polaris/README.md b/fairwinds-polaris/README.md new file mode 100644 index 0000000..7ea4618 --- /dev/null +++ b/fairwinds-polaris/README.md @@ -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) diff --git a/fairwinds-polaris/justfile b/fairwinds-polaris/justfile new file mode 100644 index 0000000..c2556c0 --- /dev/null +++ b/fairwinds-polaris/justfile @@ -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 diff --git a/fairwinds-polaris/values.gomplate.yaml b/fairwinds-polaris/values.gomplate.yaml new file mode 100644 index 0000000..c64362a --- /dev/null +++ b/fairwinds-polaris/values.gomplate.yaml @@ -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 diff --git a/justfile b/justfile index e56587d..06bce86 100644 --- a/justfile +++ b/justfile @@ -21,6 +21,7 @@ mod longhorn mod metabase mod mlflow mod minio +mod fairwinds-polaris mod oauth2-proxy mod postgres mod prometheus