From 1300d76890ff0b725b72a22f40b119512ac9343a Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Tue, 20 Oct 2020 21:19:08 +0200 Subject: [PATCH] Update dashboard content --- ...dashboard.yaml => dashboard-insecure.yaml} | 0 ...hboard.yaml => dashboard-recommended.yaml} | 0 k8s/dashboard-with-token.yaml | 336 ++++++++++++++++++ slides/k8s/dashboard.md | 166 +++++++-- 4 files changed, 482 insertions(+), 20 deletions(-) rename k8s/{insecure-dashboard.yaml => dashboard-insecure.yaml} (100%) rename k8s/{kubernetes-dashboard.yaml => dashboard-recommended.yaml} (100%) create mode 100644 k8s/dashboard-with-token.yaml diff --git a/k8s/insecure-dashboard.yaml b/k8s/dashboard-insecure.yaml similarity index 100% rename from k8s/insecure-dashboard.yaml rename to k8s/dashboard-insecure.yaml diff --git a/k8s/kubernetes-dashboard.yaml b/k8s/dashboard-recommended.yaml similarity index 100% rename from k8s/kubernetes-dashboard.yaml rename to k8s/dashboard-recommended.yaml diff --git a/k8s/dashboard-with-token.yaml b/k8s/dashboard-with-token.yaml new file mode 100644 index 00000000..d794b6fb --- /dev/null +++ b/k8s/dashboard-with-token.yaml @@ -0,0 +1,336 @@ +# This file is based on the following manifest: +# https://github.com/kubernetes/dashboard/blob/master/aio/deploy/recommended.yaml +# It adds a ServiceAccount that has cluster-admin privileges on the cluster, +# and exposes the dashboard on a NodePort. It makes it easier to do quick demos +# of the Kubernetes dashboard, without compromising the security too much. + +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Namespace +metadata: + name: kubernetes-dashboard + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard + +--- + +kind: Service +apiVersion: v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard +spec: + type: NodePort + ports: + - port: 443 + targetPort: 8443 + selector: + k8s-app: kubernetes-dashboard + +--- + +apiVersion: v1 +kind: Secret +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard-certs + namespace: kubernetes-dashboard +type: Opaque + +--- + +apiVersion: v1 +kind: Secret +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard-csrf + namespace: kubernetes-dashboard +type: Opaque +data: + csrf: "" + +--- + +apiVersion: v1 +kind: Secret +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard-key-holder + namespace: kubernetes-dashboard +type: Opaque + +--- + +kind: ConfigMap +apiVersion: v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard-settings + namespace: kubernetes-dashboard + +--- + +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard +rules: + # Allow Dashboard to get, update and delete Dashboard exclusive secrets. + - apiGroups: [""] + resources: ["secrets"] + resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"] + verbs: ["get", "update", "delete"] + # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map. + - apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["kubernetes-dashboard-settings"] + verbs: ["get", "update"] + # Allow Dashboard to get metrics. + - apiGroups: [""] + resources: ["services"] + resourceNames: ["heapster", "dashboard-metrics-scraper"] + verbs: ["proxy"] + - apiGroups: [""] + resources: ["services/proxy"] + resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"] + verbs: ["get"] + +--- + +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard +rules: + # Allow Metrics Scraper to get metrics from the Metrics server + - apiGroups: ["metrics.k8s.io"] + resources: ["pods", "nodes"] + verbs: ["get", "list", "watch"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: kubernetes-dashboard +subjects: + - kind: ServiceAccount + name: kubernetes-dashboard + namespace: kubernetes-dashboard + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: kubernetes-dashboard +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kubernetes-dashboard +subjects: + - kind: ServiceAccount + name: kubernetes-dashboard + namespace: kubernetes-dashboard + +--- + +kind: Deployment +apiVersion: apps/v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard +spec: + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + k8s-app: kubernetes-dashboard + template: + metadata: + labels: + k8s-app: kubernetes-dashboard + spec: + containers: + - name: kubernetes-dashboard + image: kubernetesui/dashboard:v2.0.0 + imagePullPolicy: Always + ports: + - containerPort: 8443 + protocol: TCP + args: + - --auto-generate-certificates + - --namespace=kubernetes-dashboard + # Uncomment the following line to manually specify Kubernetes API server Host + # If not specified, Dashboard will attempt to auto discover the API server and connect + # to it. Uncomment only if the default does not work. + # - --apiserver-host=http://my-address:port + volumeMounts: + - name: kubernetes-dashboard-certs + mountPath: /certs + # Create on-disk volume to store exec logs + - mountPath: /tmp + name: tmp-volume + livenessProbe: + httpGet: + scheme: HTTPS + path: / + port: 8443 + initialDelaySeconds: 30 + timeoutSeconds: 30 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsUser: 1001 + runAsGroup: 2001 + volumes: + - name: kubernetes-dashboard-certs + secret: + secretName: kubernetes-dashboard-certs + - name: tmp-volume + emptyDir: {} + serviceAccountName: kubernetes-dashboard + nodeSelector: + "kubernetes.io/os": linux + # Comment the following tolerations if Dashboard must not be deployed on master + tolerations: + - key: node-role.kubernetes.io/master + effect: NoSchedule + +--- + +kind: Service +apiVersion: v1 +metadata: + labels: + k8s-app: dashboard-metrics-scraper + name: dashboard-metrics-scraper + namespace: kubernetes-dashboard +spec: + ports: + - port: 8000 + targetPort: 8000 + selector: + k8s-app: dashboard-metrics-scraper + +--- + +kind: Deployment +apiVersion: apps/v1 +metadata: + labels: + k8s-app: dashboard-metrics-scraper + name: dashboard-metrics-scraper + namespace: kubernetes-dashboard +spec: + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + k8s-app: dashboard-metrics-scraper + template: + metadata: + labels: + k8s-app: dashboard-metrics-scraper + annotations: + seccomp.security.alpha.kubernetes.io/pod: 'runtime/default' + spec: + containers: + - name: dashboard-metrics-scraper + image: kubernetesui/metrics-scraper:v1.0.4 + ports: + - containerPort: 8000 + protocol: TCP + livenessProbe: + httpGet: + scheme: HTTP + path: / + port: 8000 + initialDelaySeconds: 30 + timeoutSeconds: 30 + volumeMounts: + - mountPath: /tmp + name: tmp-volume + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsUser: 1001 + runAsGroup: 2001 + serviceAccountName: kubernetes-dashboard + nodeSelector: + "kubernetes.io/os": linux + # Comment the following tolerations if Dashboard must not be deployed on master + tolerations: + - key: node-role.kubernetes.io/master + effect: NoSchedule + volumes: + - name: tmp-volume + emptyDir: {} + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + k8s-app: kubernetes-dashboard + name: cluster-admin + namespace: kubernetes-dashboard + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard-cluster-admin +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: cluster-admin + namespace: kubernetes-dashboard diff --git a/slides/k8s/dashboard.md b/slides/k8s/dashboard.md index 90588303..5e04c1e7 100644 --- a/slides/k8s/dashboard.md +++ b/slides/k8s/dashboard.md @@ -2,47 +2,65 @@ - Kubernetes resources can also be viewed with a web dashboard -- That dashboard is usually exposed over HTTPS - - (this requires obtaining a proper TLS certificate) - - Dashboard users need to authenticate -- We are going to take a *dangerous* shortcut + (typically with a token) + +- The dashboard should be exposed over HTTPS + + (to prevent interception of the aforementioned token) + +- Ideally, this requires obtaining a proper TLS certificate + + (for instance, with Let's Encrypt) --- -## The insecure method +## Three ways to install the dashboard -- We could (and should) use [Let's Encrypt](https://letsencrypt.org/) ... +- Our `k8s` directory has no less than three manifests! -- ... but we don't want to deal with TLS certificates +- `dashboard-recommended.yaml` -- We could (and should) learn how authentication and authorization work ... + (purely internal dashboard; user must be created manually) -- ... but we will use a guest account with admin access instead +- `dashboard-with-token.yaml` -.footnote[.warning[Yes, this will open our cluster to all kinds of shenanigans. Don't do this at home.]] + (dashboard exposed with NodePort; creates an admin user for us) + +- `dashboard-insecure.yaml` aka *YOLO* + + (dashboard exposed over HTTP; gives root access to anonymous users) --- -## Running a very insecure dashboard +## `dashboard-insecure.yaml` -- We are going to deploy that dashboard with *one single command* +- This will allow anyone to deploy anything on your cluster -- This command will create all the necessary resources + (without any authentication whatsoever) - (the dashboard itself, the HTTP wrapper, the admin/guest account) +- **Do not** use this, except maybe on a local cluster -- All these resources are defined in a YAML file + (or a cluster that you will destroy a few minutes later) -- All we have to do is load that YAML file with with `kubectl apply -f` +- On "normal" clusters, use `dashboard-with-token.yaml` instead! + +--- + +## What's in the manifest? + +- The dashboard itself + +- An HTTP/HTTPS unwrapper (using `socat`) + +- The guest/admin account .exercise[ - Create all the dashboard resources, with the following command: ```bash - kubectl apply -f ~/container.training/k8s/insecure-dashboard.yaml + kubectl apply -f ~/container.training/k8s/dashboard-insecure.yaml ``` ] @@ -89,11 +107,26 @@ The dashboard will then ask you which authentication you want to use. -- -.warning[By the way, we just added a backdoor to our Kubernetes cluster!] +.warning[Remember, we just added a backdoor to our Kubernetes cluster!] --- -## Running the Kubernetes dashboard securely +## Closing the backdoor + +- Seriously, don't leave that thing running! + +.exercise[ + +- Remove what we just created: + ```bash + kubectl delete -f ~/container.training/k8s/dashboard-insecure.yaml + ``` + +] + +--- + +## The risks - The steps that we just showed you are *for educational purposes only!* @@ -105,6 +138,99 @@ The dashboard will then ask you which authentication you want to use. --- +## `dashboard-with-token.yaml` + +- This is a less risky way to deploy the dashboard + +- It's not completely secure, either: + + - we're using a self-signed certificate + + - this is subject to eavesdropping attacks + +- Using `kubectl port-forward` or `kubectl proxy` is even better + +--- + +## What's in the manifest? + +- The dashboard itself (but exposed with a `NodePort`) + +- A ServiceAccount with `cluster-admin` privileges + + (named `kubernetes-dashboard:cluster-admin`) + +.exercise[ + +- Create all the dashboard resources, with the following command: + ```bash + kubectl apply -f ~/container.training/k8s/dashboard-with-token.yaml + ``` + +] + +--- + +## Obtaining the token + +- The manifest creates a ServiceAccount + +- Kubernetes will automatically generate a token for that ServiceAccount + +.exercise[ + +- Display the token: + ```bash + kubectl --namespace=kubernetes-dashboard \ + describe secret cluster-admin-token + ``` + +] + +The token should start with `eyJ...` (it's a JSON Web Token). + +Note that the secret name will actually be `cluster-admin-token-xxxxx`. +
+(But `kubectl` prefix matches are great!) + +--- + +## Connecting to the dashboard + +.exercise[ + +- Check which port the dashboard is on: + ```bash + kubectl get svc --namespace=kubernetes-dashboard + ``` + +] + +You'll want the `3xxxx` port. + + +.exercise[ + +- Connect to http://oneofournodes:3xxxx/ + + + +] + +The dashboard will then ask you which authentication you want to use. + +--- + +## Dashboard authentication + +- Select "token" authentication + +- Copy paste the token (starting with `eyJ...`) obtained earlier + +- We're logged in! + +--- + ## Other dashboards - [Kube Web View](https://codeberg.org/hjacobs/kube-web-view)