Retire direct Dashboard deployer after Argo adoption
Validate, publish and promote Kubernetes Dashboard chart / validate (push) Successful in 1s
Validate, publish and promote Kubernetes Dashboard chart / publish (push) Successful in 2s

This commit is contained in:
feedc0de committed 2026-10-23 14:48:06 +02:00
1 parent 0441ce9d7f
commit e36f2d233e
14 files changed
+32 -938

No files matched your search

-74
View File
@@ -1,74 +0,0 @@
name: Validate and deploy Kubernetes Dashboard
on:
push:
pull_request:
env:
DASHBOARD_CHART_VERSION: 7.14.0
HELM_VERSION: v4.2.2
KUBECTL_VERSION: v1.36.3
KUBERNETES_API: https://host.containers.internal:6443
KUBERNETES_TLS_SERVER_NAME: 192.168.0.2
NAMESPACE: kubernetes-dashboard
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pinned Helm
run: |
curl -fsSL -o "${RUNNER_TEMP}/helm.tgz" "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz"
tar -xzf "${RUNNER_TEMP}/helm.tgz" -C "${RUNNER_TEMP}" --strip-components=1 linux-amd64/helm
echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}"
- run: bash test.sh
deploy:
if: gitea.ref == 'refs/heads/main'
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pinned tools
run: |
curl -fsSL -o "${RUNNER_TEMP}/helm.tgz" "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz"
tar -xzf "${RUNNER_TEMP}/helm.tgz" -C "${RUNNER_TEMP}" --strip-components=1 linux-amd64/helm
curl -fsSL -o "${RUNNER_TEMP}/kubectl" "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
chmod 0700 "${RUNNER_TEMP}/helm" "${RUNNER_TEMP}/kubectl"
echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}"
- name: Configure Kubernetes access
env:
KUBE_CONFIG_BASE64: ${{ secrets.KUBE_CONFIG_BASE64 }}
run: |
test -n "${KUBE_CONFIG_BASE64}"
printf '%s' "${KUBE_CONFIG_BASE64}" | base64 --decode > "${RUNNER_TEMP}/kubeconfig"
chmod 0600 "${RUNNER_TEMP}/kubeconfig"
export KUBECONFIG="${RUNNER_TEMP}/kubeconfig"
kubectl config set-cluster cluster --server="${KUBERNETES_API}" --tls-server-name="${KUBERNETES_TLS_SERVER_NAME}"
- name: Apply public desired state and verify
run: |
export KUBECONFIG="${RUNNER_TEMP}/kubeconfig"
./render.sh "${RUNNER_TEMP}/dashboard.yaml"
kubectl apply --server-side --force-conflicts --dry-run=server --validate=false \
--filename "${RUNNER_TEMP}/dashboard.yaml" \
--filename auth-proxy.yaml \
--filename kubernetes-dashboard-ingressroute.yaml
kubectl apply --server-side --force-conflicts --validate=false \
--filename "${RUNNER_TEMP}/dashboard.yaml" \
--filename auth-proxy.yaml \
--filename kubernetes-dashboard-ingressroute.yaml
bash ./wait-for-deployments.sh \
kubernetes-dashboard-api \
kubernetes-dashboard-auth \
kubernetes-dashboard-web \
kubernetes-dashboard-metrics-scraper \
kubernetes-dashboard-kong \
kubernetes-dashboard-auth-proxy
status=$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' --max-time 15 https://kubernetes.brunner.ninja/)
case "${status}" in
302|303|307) ;;
*) echo "Expected Authentik redirect, received HTTP ${status}" >&2; exit 1 ;;
esac
+30 -51
View File
@@ -1,57 +1,36 @@
# Kubernetes Dashboard on arschrock
# Kubernetes Dashboard chart
This repository pins the final official Kubernetes Dashboard chart, `7.14.0`,
directly from its GitHub release. The old Helm index
now returns 404 because Kubernetes Dashboard is archived and unmaintained.
Upstream recommends migrating to Headlamp; this deployment should therefore be
treated as an interim compatibility service, not a long-term platform choice.
This repository maintains a narrowly patched copy of the final upstream
Kubernetes Dashboard `7.14.0` chart, with the optional Authentik-facing
authentication proxy used by this homelab. Dashboard is
[archived upstream](https://github.com/kubernetes-retired/dashboard), so treat
this as a compatibility deployment rather than a source of application updates.
## Authentication and TLS
The chart keeps the upstream resource names and the Kong subchart. Its sole
upstream-template change adds `app.security.existingCsrfSecret`: when true, the
chart references the existing `kubernetes-dashboard-csrf` Secret but does not
render or rotate it. The Secret must contain `private.key` and is deliberately
not stored in the public GitOps repository. For a fresh, ordinary install,
leave this option false so the upstream chart creates the key. The optional
`authProxy.enabled` adds the private nginx proxy, projected service-account
token, Service, ConfigMap, NetworkPolicy, and PDB. The site-specific
cluster-admin binding and public Traefik route are not part of this chart.
The public route at `https://kubernetes.brunner.ninja` is protected by a
namespace-local Traefik forward-auth middleware that uses the shared Authentik
embedded outpost. Keeping the middleware local allows Traefik's safer
`allowCrossNamespace=false` setting to remain enabled. Authentik permits only
members of `authentik Admins`. A private nginx proxy then adds a projected token for the
existing `admin-user` service account. Kubernetes issues and rotates that token
automatically every hour; it is never stored in Git, a static Secret, Gitea, or
Traefik configuration. Supplying the Authorization header makes Dashboard skip
its bearer-token login page.
For an independent test installation:
Every Dashboard container retains a CPU request, right-sized to `25m` from an
observed idle use of roughly `1-2m`. Requests are not removed: they still give
the scheduler a placement signal while the higher CPU limits leave room for
interactive bursts.
```sh
./install.sh
```
This preserves the previous cluster-admin behavior, but Kubernetes audit events
identify every Dashboard action as the same service account. Native Authentik
OIDC would provide per-user identity, but requires kube-apiserver configuration
and another OAuth proxy because this Dashboard has no native OIDC login. Prefer
Headlamp with native OIDC when replacing this retired application.
The homelab deployment is managed by Argo CD from
[homelab-gitops](https://code.brunner.ninja/feedc0de/homelab-gitops/src/branch/main/bootstrap/kubernetes-dashboard.yaml).
Do not run `install.sh` against that Argo-managed release. Its values and
cluster-admin binding are in `apps/kubernetes-dashboard/` in GitOps; the
existing CSRF Secret is a separately preserved cluster bootstrap object.
Argo skips the vendored Kong CRDs because this release does not use Kong's
Ingress Controller.
The Authentik application and provider are owned by the adjacent `authentik`
repository. Run its `install.sh` once before exposing this route; that installer
also attaches the provider to the embedded outpost. No Dashboard OAuth client
secret is required for forward authentication.
Traefik terminates browser-facing HTTPS. Kong's internal HTTPS listener remains
enabled because the auth proxy sends a privileged bearer token to it. Kong's
plain HTTP listener is disabled.
## Bootstrap and CI/CD
Run `./install.sh` once with administrator access. It downloads and verifies the
pinned chart, repairs the existing failed Helm release, creates the bootstrap
RBAC, and waits for all six deployments. Run `./test.sh` for a secret-free chart
render and schema validation.
Then run `./create-ci-kubeconfig.sh` and store its final line as the Gitea
Actions secret `KUBE_CONFIG_BASE64`. The CI identity can update only named,
already-existing Dashboard objects. It cannot read Secrets, edit the
`admin-user` cluster-admin binding, create resources, or delete resources.
Deployment readiness is checked by polling those exact object names; the CI
identity deliberately has no namespace-wide Deployment `list/watch` access.
The deploy workflow runs only for `main`; pull requests and all pushes run the
validation job. The chart's generated CSRF Secret is created by the bootstrap
Helm installation and deliberately filtered out of the restricted CI render.
On main, Gitea Actions lints and packages the chart, publishes it to the
public indexed repository, and promotes its exact chart version in GitOps.
The Git tag `v7.14.0` follows the upstream Dashboard chart release;
chart-only commits append Git describe count and hash.
-248
View File
@@ -1,248 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: kubernetes-dashboard-auth-proxy
namespace: kubernetes-dashboard
data:
nginx.conf.template: |
pid /tmp/nginx.pid;
error_log /dev/stderr warn;
events {}
http {
access_log /dev/stdout;
client_body_temp_path /tmp/client-body;
proxy_temp_path /tmp/proxy;
fastcgi_temp_path /tmp/fastcgi;
uwsgi_temp_path /tmp/uwsgi;
scgi_temp_path /tmp/scgi;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 8080;
server_name _;
location = /healthz {
access_log off;
default_type text/plain;
return 200 'ok\n';
}
location / {
proxy_http_version 1.1;
proxy_pass https://kubernetes-dashboard-kong-proxy.kubernetes-dashboard.svc.cluster.local:443;
proxy_ssl_name kubernetes-dashboard-kong-proxy.kubernetes-dashboard.svc.cluster.local;
proxy_ssl_server_name on;
# Kong uses its chart-generated internal certificate.
proxy_ssl_verify off;
proxy_set_header Authorization "Bearer @@DASHBOARD_TOKEN@@";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
}
run.sh: |
#!/bin/sh
set -eu
token_file=/var/run/secrets/dashboard/token
template=/config/nginx.conf.template
rendered=/tmp/nginx.conf
render_config() {
dashboard_token=$(tr -d '\n' < "$token_file")
test -n "$dashboard_token"
sed "s|@@DASHBOARD_TOKEN@@|$dashboard_token|g" "$template" > "$rendered.new"
chmod 0600 "$rendered.new"
mv "$rendered.new" "$rendered"
unset dashboard_token
}
stop() {
nginx -c "$rendered" -s quit 2>/dev/null || true
exit 0
}
trap stop INT TERM
render_config
token_digest=$(sha256sum "$token_file" | awk '{print $1}')
nginx -t -c "$rendered"
nginx -c "$rendered"
while :; do
sleep 60
next_digest=$(sha256sum "$token_file" | awk '{print $1}')
if [ "$next_digest" != "$token_digest" ]; then
render_config
nginx -t -c "$rendered"
nginx -c "$rendered" -s reload
token_digest=$next_digest
fi
done
---
apiVersion: v1
kind: Service
metadata:
name: kubernetes-dashboard-auth-proxy
namespace: kubernetes-dashboard
spec:
selector:
app.kubernetes.io/name: kubernetes-dashboard-auth-proxy
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubernetes-dashboard-auth-proxy
namespace: kubernetes-dashboard
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: kubernetes-dashboard-auth-proxy
template:
metadata:
labels:
app.kubernetes.io/name: kubernetes-dashboard-auth-proxy
app.kubernetes.io/part-of: kubernetes-dashboard
spec:
serviceAccountName: admin-user
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
fsGroup: 101
seccompProfile:
type: RuntimeDefault
containers:
- name: proxy
image: docker.io/nginxinc/nginx-unprivileged:1.30.6-alpine
imagePullPolicy: IfNotPresent
command: [/bin/sh, /config/run.sh]
ports:
- name: http
containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
cpu: 200m
memory: 128Mi
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 20
volumeMounts:
- name: config
mountPath: /config
readOnly: true
- name: token
mountPath: /var/run/secrets/dashboard
readOnly: true
- name: temporary
mountPath: /tmp
volumes:
- name: config
configMap:
name: kubernetes-dashboard-auth-proxy
defaultMode: 0555
- name: token
projected:
defaultMode: 0440
sources:
- serviceAccountToken:
path: token
audience: https://kubernetes.default.svc.cluster.local
expirationSeconds: 3600
- name: temporary
emptyDir:
sizeLimit: 64Mi
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: kubernetes-dashboard-auth-proxy
namespace: kubernetes-dashboard
spec:
maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/name: kubernetes-dashboard-auth-proxy
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: kubernetes-dashboard-auth-proxy
namespace: kubernetes-dashboard
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: kubernetes-dashboard-auth-proxy
policyTypes: [Ingress, Egress]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: default
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- podSelector:
matchLabels:
app.kubernetes.io/instance: kubernetes-dashboard
app.kubernetes.io/name: kong
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 8443
-130
View File
@@ -1,130 +0,0 @@
# Bootstrap once with an administrator identity. This credential can update
# only the existing Dashboard resources and cannot read Kubernetes Secrets.
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubernetes-dashboard-deployer
namespace: kubernetes-dashboard
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kubernetes-dashboard-deployer
namespace: kubernetes-dashboard
rules:
- apiGroups: [""]
resources: [serviceaccounts]
resourceNames:
- kubernetes-dashboard-kong
- kubernetes-dashboard-api
- kubernetes-dashboard-metrics-scraper
- kubernetes-dashboard-web
verbs: [get, patch, update]
- apiGroups: [""]
resources: [configmaps]
resourceNames:
- kong-dbless-config
- kubernetes-dashboard-web-settings
- kubernetes-dashboard-auth-proxy
verbs: [get, patch, update]
- apiGroups: [""]
resources: [services]
resourceNames:
- kubernetes-dashboard-kong-proxy
- kubernetes-dashboard-api
- kubernetes-dashboard-auth
- kubernetes-dashboard-metrics-scraper
- kubernetes-dashboard-web
- kubernetes-dashboard-auth-proxy
verbs: [get, patch, update]
- apiGroups: [apps]
resources: [deployments]
resourceNames:
- kubernetes-dashboard-kong
- kubernetes-dashboard-api
- kubernetes-dashboard-auth
- kubernetes-dashboard-metrics-scraper
- kubernetes-dashboard-web
- kubernetes-dashboard-auth-proxy
verbs: [get, patch, update]
- apiGroups: [policy]
resources: [poddisruptionbudgets]
resourceNames:
- kubernetes-dashboard
- kubernetes-dashboard-auth-proxy
verbs: [get, patch, update]
- apiGroups: [networking.k8s.io]
resources: [networkpolicies]
resourceNames: [kubernetes-dashboard-auth-proxy]
verbs: [get, patch, update]
- apiGroups: [rbac.authorization.k8s.io]
resources: [roles]
resourceNames:
- kubernetes-dashboard-api
- kubernetes-dashboard-web
verbs: [get, patch, update, escalate]
- apiGroups: [rbac.authorization.k8s.io]
resources: [rolebindings]
resourceNames:
- kubernetes-dashboard-api
- kubernetes-dashboard-web
verbs: [get, patch, update, bind]
- apiGroups: [traefik.io]
resources: [ingressroutes]
resourceNames: [kubernetes-dashboard-ingressroute]
verbs: [get, patch, update]
- apiGroups: [traefik.io]
resources: [middlewares]
resourceNames: [kubernetes-dashboard-authentik]
verbs: [get, patch, update]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kubernetes-dashboard-deployer
namespace: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubernetes-dashboard-deployer
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard-deployer
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kubernetes-dashboard-deployer
rules:
- apiGroups: [rbac.authorization.k8s.io]
resources: [clusterroles]
resourceNames: [kubernetes-dashboard-metrics-scraper]
verbs: [get, patch, update, escalate]
- apiGroups: [rbac.authorization.k8s.io]
resources: [clusterrolebindings]
resourceNames: [kubernetes-dashboard-metrics-scraper]
verbs: [get, patch, update, bind]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard-deployer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubernetes-dashboard-deployer
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard-deployer
namespace: kubernetes-dashboard
---
apiVersion: v1
kind: Secret
metadata:
name: kubernetes-dashboard-deployer-token
namespace: kubernetes-dashboard
annotations:
kubernetes.io/service-account.name: kubernetes-dashboard-deployer
type: kubernetes.io/service-account-token
-50
View File
@@ -1,50 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
namespace=kubernetes-dashboard
account=kubernetes-dashboard-deployer
secret=kubernetes-dashboard-deployer-token
remote=${KUBECTL_SSH_HOST:-}
cluster_kubectl() {
if [[ -n "${remote}" ]]; then
ssh "${remote}" kubectl "$@"
else
kubectl "$@"
fi
}
if [[ -n "${remote}" ]]; then
cluster_kubectl apply --filename=- < "${project_dir}/ci-deployer.yaml" >&2
else
cluster_kubectl apply --filename "${project_dir}/ci-deployer.yaml" >&2
fi
for attempt in {1..30}; do
data=$(cluster_kubectl --namespace "${namespace}" get secret "${secret}" \
--output=jsonpath='{.data.token}' 2>/dev/null || true)
[[ -n "${data}" ]] && break
sleep 1
done
[[ -n "${data:-}" ]] || { echo "Token was not issued" >&2; exit 1; }
workdir=$(mktemp --directory)
trap 'rm -rf "${workdir}"' EXIT
server=$(cluster_kubectl config view --minify --output=jsonpath='{.clusters[0].cluster.server}')
cluster_kubectl --namespace "${namespace}" get secret "${secret}" \
--output=jsonpath='{.data.ca\.crt}' | base64 --decode > "${workdir}/ca.crt"
export KUBECONFIG="${workdir}/config"
kubectl config set-cluster cluster --server="${server}" \
--certificate-authority="${workdir}/ca.crt" --embed-certs=true >/dev/null
kubectl config set-credentials "${account}" \
--token="$(printf '%s' "${data}" | base64 --decode)" >/dev/null
kubectl config set-context kubernetes-dashboard --cluster=cluster \
--user="${account}" --namespace="${namespace}" >/dev/null
kubectl config use-context kubernetes-dashboard >/dev/null
echo "Store the following line as Gitea Actions secret KUBE_CONFIG_BASE64; do not commit it." >&2
base64 --wrap=0 "${KUBECONFIG}"
printf '\n'
-23
View File
@@ -1,23 +0,0 @@
function emit_document() {
if (document != "" && !is_secret) {
printf "%s", document
}
}
$0 == "---" {
emit_document()
document = "---\n"
is_secret = 0
next
}
{
document = document $0 "\n"
if ($0 ~ /^kind:[[:space:]]*Secret[[:space:]]*$/) {
is_secret = 1
}
}
END {
emit_document()
}
-8
View File
@@ -1,8 +0,0 @@
name: pin-images
version: 1.0.0
type: postrenderer/v1
apiVersion: v1
runtime: subprocess
runtimeConfig:
platformCommand:
- command: "${HELM_PLUGIN_DIR}/../../pin-images.sh"
+2 -44
View File
@@ -1,48 +1,6 @@
#!/bin/sh
set -eu
chart_version=${DASHBOARD_CHART_VERSION:-7.14.0}
project_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
chart_archive=$(mktemp)
helm_cache=$(mktemp -d)
trap 'rm -f "$chart_archive"; rm -rf "$helm_cache"' EXIT HUP INT TERM
for command in curl helm kubectl; do
if ! command -v "$command" >/dev/null 2>&1; then
echo "Required command not found: $command" >&2
exit 1
fi
done
kubectl create namespace kubernetes-dashboard --dry-run=client --output=yaml \
| kubectl apply --filename=-
# This privileged identity is deliberately bootstrap-only. CI cannot edit the
# cluster-admin binding, and no long-lived service-account token is created.
kubectl apply --filename "$project_dir/kubernetes-dashboard-user.yaml"
curl --fail --silent --show-error --location --output "$chart_archive" \
"https://github.com/kubernetes-retired/dashboard/releases/download/kubernetes-dashboard-${chart_version}/kubernetes-dashboard-${chart_version}.tgz"
HELM_CACHE_HOME="$helm_cache" HELM_PLUGINS="$project_dir/helm-plugins" \
helm upgrade --install kubernetes-dashboard "$chart_archive" \
--namespace kubernetes-dashboard \
--values "$project_dir/kubernetes-dashboard-values.yaml" \
--server-side=true --take-ownership --force-conflicts --rollback-on-failure \
helm upgrade --install kubernetes-dashboard "$project_dir/chart" \
--namespace kubernetes-dashboard --create-namespace \
--wait --timeout 15m
kubectl apply --filename "$project_dir/auth-proxy.yaml"
kubectl apply --filename "$project_dir/kubernetes-dashboard-ingressroute.yaml"
for deployment in \
kubernetes-dashboard-api \
kubernetes-dashboard-auth \
kubernetes-dashboard-web \
kubernetes-dashboard-metrics-scraper \
kubernetes-dashboard-kong \
kubernetes-dashboard-auth-proxy
do
kubectl --namespace kubernetes-dashboard rollout status \
"deployment/$deployment" --timeout=10m
done
-40
View File
@@ -1,40 +0,0 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: kubernetes-dashboard-authentik
namespace: kubernetes-dashboard
spec:
forwardAuth:
address: http://authentik-server.authentik.svc.cluster.local/outpost.goauthentik.io/auth/traefik
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-entitlements
- X-authentik-email
- X-authentik-name
- X-authentik-uid
- X-authentik-jwt
- X-authentik-meta-jwks
- X-authentik-meta-outpost
- X-authentik-meta-provider
- X-authentik-meta-app
- X-authentik-meta-version
trustForwardHeader: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: kubernetes-dashboard-ingressroute
namespace: kubernetes-dashboard
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`kubernetes.brunner.ninja`)
middlewares:
- name: kubernetes-dashboard-authentik
services:
- kind: Service
name: kubernetes-dashboard-auth-proxy
port: http
-21
View File
@@ -1,21 +0,0 @@
# Bootstrap-only privileged identity. No static token Secret is created.
# The auth proxy receives an automatically rotated projected token.
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
-106
View File
@@ -1,106 +0,0 @@
# Public, secret-free values for the final upstream Kubernetes Dashboard chart.
# pin-images.sh adds immutable linux/amd64 digests after Helm has rendered the
# version labels; the chart otherwise puts invalid digest strings in labels.
app:
image:
pullPolicy: IfNotPresent
security:
podDisruptionBudget:
enabled: true
maxUnavailable: 1
settings:
global:
clusterName: arschrock
defaultNamespace: default
auth:
image:
repository: docker.io/kubernetesui/dashboard-auth
tag: 1.4.0
scaling:
replicas: 2
revisionHistoryLimit: 3
containers:
resources:
requests:
cpu: 25m
memory: 200Mi
limits:
cpu: 250m
memory: 400Mi
api:
image:
repository: docker.io/kubernetesui/dashboard-api
tag: 1.14.0
scaling:
replicas: 2
revisionHistoryLimit: 3
containers:
resources:
requests:
cpu: 25m
memory: 200Mi
limits:
cpu: 250m
memory: 400Mi
web:
image:
repository: docker.io/kubernetesui/dashboard-web
tag: 1.7.0
scaling:
replicas: 2
revisionHistoryLimit: 3
containers:
resources:
requests:
cpu: 25m
memory: 200Mi
limits:
cpu: 250m
memory: 400Mi
metricsScraper:
enabled: true
image:
repository: docker.io/kubernetesui/dashboard-metrics-scraper
tag: 1.2.2
scaling:
revisionHistoryLimit: 3
containers:
resources:
requests:
cpu: 25m
memory: 200Mi
limits:
cpu: 250m
memory: 400Mi
# Browser TLS terminates at Traefik. Kong still uses TLS because the private
# auth proxy sends the privileged projected bearer token on this hop.
kong:
enabled: true
replicaCount: 2
resources:
requests:
cpu: 25m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
proxy:
type: ClusterIP
http:
enabled: false
tls:
enabled: true
cert-manager:
enabled: false
nginx:
enabled: false
metrics-server:
enabled: false
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
version=${DASHBOARD_CHART_VERSION:-7.14.0}
output=${1:?Usage: render.sh OUTPUT_FILE}
chart=$(mktemp)
raw=$(mktemp)
helm_cache=$(mktemp --directory)
trap 'rm -f "${chart}" "${raw}"; rm -rf "${helm_cache}"' EXIT
curl --fail --silent --show-error --location --output "${chart}" \
"https://github.com/kubernetes-retired/dashboard/releases/download/kubernetes-dashboard-${version}/kubernetes-dashboard-${version}.tgz"
HELM_CACHE_HOME="${helm_cache}" HELM_PLUGINS="${project_dir}/helm-plugins" \
helm template kubernetes-dashboard "${chart}" \
--namespace kubernetes-dashboard \
--values "${project_dir}/kubernetes-dashboard-values.yaml" \
--set-string app.security.csrfKey=ci-render-only-not-used > "${raw}"
# The chart always renders a random CSRF Secret. It is created and retained by
# the bootstrap Helm installation; restricted CI never receives Secret access.
awk -f "${project_dir}/filter-secrets.awk" "${raw}" > "${output}"
-75
View File
@@ -1,75 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
workdir=$(mktemp --directory)
trap 'rm -rf "${workdir}"' EXIT
sh -n "${project_dir}/install.sh"
bash -n \
"${project_dir}/render.sh" \
"${project_dir}/create-ci-kubeconfig.sh" \
"${project_dir}/wait-for-deployments.sh" \
"${project_dir}/test.sh"
awk -f "${project_dir}/filter-secrets.awk" /dev/null >/dev/null
"${project_dir}/render.sh" "${workdir}/dashboard.yaml"
if grep -Eq '^kind:[[:space:]]*Secret[[:space:]]*$' "${workdir}/dashboard.yaml"; then
echo "Restricted CI render contains a Secret" >&2
exit 1
fi
if rg -i 'Bearer [A-Za-z0-9._-]{20,}|token:[[:space:]]*[A-Za-z0-9._-]{20,}' "${project_dir}" \
--glob '*.yaml' --glob '*.yml'; then
echo "A bearer token appears to be committed" >&2
exit 1
fi
grep -Fq 'kubernetes-dashboard-auth-proxy' "${project_dir}/kubernetes-dashboard-ingressroute.yaml"
grep -Fq 'name: kubernetes-dashboard-authentik' "${project_dir}/kubernetes-dashboard-ingressroute.yaml"
grep -Fq 'authentik-server.authentik.svc.cluster.local/outpost.goauthentik.io/auth/traefik' \
"${project_dir}/kubernetes-dashboard-ingressroute.yaml"
if grep -Fq 'namespace: default' "${project_dir}/kubernetes-dashboard-ingressroute.yaml"; then
echo "Dashboard route must not use a cross-namespace middleware" >&2
exit 1
fi
if grep -Fq 'rollout status' "${project_dir}/.gitea/workflows/deploy.yml"; then
echo "Restricted CI must poll exact Deployment names instead of list/watch" >&2
exit 1
fi
grep -Fq 'expirationSeconds: 3600' "${project_dir}/auth-proxy.yaml"
grep -Fq 'proxy_pass https://kubernetes-dashboard-kong-proxy' "${project_dir}/auth-proxy.yaml"
grep -Fq 'containerPort: 8443' "${workdir}/dashboard.yaml"
grep -Fq 'dashboard-api:1.14.0' "${workdir}/dashboard.yaml"
request_count=$(awk '
$1 == "cpu:" && $2 == "25m" { count++ }
END { print count + 0 }
' "${workdir}/dashboard.yaml")
if [[ "${request_count}" -lt 5 ]] || grep -Fq 'cpu: 100m' "${workdir}/dashboard.yaml"; then
echo "Expected every Dashboard chart component to request only 25m CPU" >&2
exit 1
fi
image=ghcr.io/yannh/kubeconform:v0.7.0
if command -v podman >/dev/null 2>&1; then
container_runtime=podman
elif command -v docker >/dev/null 2>&1; then
container_runtime=docker
else
echo "Podman or Docker is required for kubeconform validation" >&2
exit 1
fi
for file in \
"${workdir}/dashboard.yaml" \
"${project_dir}/kubernetes-dashboard-user.yaml" \
"${project_dir}/auth-proxy.yaml" \
"${project_dir}/kubernetes-dashboard-ingressroute.yaml" \
"${project_dir}/ci-deployer.yaml"
do
"${container_runtime}" run --rm --interactive "${image}" \
-strict -exit-on-error -ignore-missing-schemas -summary < "${file}"
done
echo "Dashboard public desired state is valid and contains no static credentials."
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
namespace=${NAMESPACE:-kubernetes-dashboard}
timeout_seconds=${DEPLOYMENT_TIMEOUT_SECONDS:-600}
poll_seconds=${DEPLOYMENT_POLL_SECONDS:-5}
if [[ "$#" -eq 0 ]]; then
echo "Usage: wait-for-deployments.sh DEPLOYMENT..." >&2
exit 2
fi
for deployment in "$@"; do
deadline=$((SECONDS + timeout_seconds))
state="not observed"
complete=false
while ((SECONDS < deadline)); do
state=$(kubectl --namespace "${namespace}" get "deployment/${deployment}" \
--output=jsonpath='{.metadata.generation}{"|"}{.status.observedGeneration}{"|"}{.spec.replicas}{"|"}{.status.updatedReplicas}{"|"}{.status.readyReplicas}{"|"}{.status.availableReplicas}{"\n"}')
IFS='|' read -r generation observed desired updated ready available <<< "${state}"
observed=${observed:-0}
desired=${desired:-0}
updated=${updated:-0}
ready=${ready:-0}
available=${available:-0}
if [[ "${observed}" -ge "${generation}" \
&& "${updated}" -eq "${desired}" \
&& "${ready}" -eq "${desired}" \
&& "${available}" -eq "${desired}" ]]; then
echo "deployment/${deployment} is ready (${ready}/${desired})"
complete=true
break
fi
sleep "${poll_seconds}"
done
if [[ "${complete}" != true ]]; then
echo "Timed out waiting for deployment/${deployment}; generation observed desired updated ready available: ${state}" >&2
exit 1
fi
done