3/5 - (2 голоса)

Есть несколько уровней инфраструктуры, и каждый из них имеет уязвимое место.

Первый уровень – это кластерные серверы, на которые приходится вся ваша рабочая нагрузка. Следующими уровнями являются кластеры и контейнеры. Наша цель – минимизировать участки, уязвимые к атаке.

Для начала убедитесь, что ваш кластер развернут в частной сети, а трафик поступает с load balancer и сервисов ingress. Не открывайте такие порты как SSH или RDP, старайтесь использовать SSM или вообще ничего, поскольку Kubernetes почти не нужна базовая система конфигураций. Кроме того, используя службы управления Kubernetes, вам даже не нужно беспокоиться о начальных настройках. Вы будете просто управлять операторами.

Непривилегированные пользователи (rootless)

Dockerfile-alpine

FROM  alpine: 3.12 , 
#  Create  user  and  set  ownership  and  permissions  as  required
RUN adduser  - D myuser  &&  chown  - R myuser  / myapp - data
 COPY  myapp  / myapp
 USER  myuser
ENTRYPOINT ["/myapp"]

По умолчанию многие контейнерные службы работают как привилегированный пользователь root. В то же время программы, выполняемые внутри контейнера в качестве root, не нуждаются в привилегированном исполнении. Предотвращение использования root пользователя с помощью non-root или rootless контейнеров поможет снизить шансы скомпрометировать контейнер.

Неизменные контейнерные файловые системы

read-only-deployment.yaml
 apiVersion: apps/v1 
kind: Deployment 
metadata: 
labels: 
app: web 
name: web 
spec: 
selector: 
matchLabels: 
app: web 
template: 
metadata: 
labels: 
app: web 
name: web 
spec: 
containers: 
- command: [ "sleep" ]
 args: ["999"] 
image: ubuntu:latest 
name: web 
securityContext: 
readOnlyRootFilesystem: true 
volumeMounts:
- mountPath: /writeable/location/here
name: volName 
volumes:
- emptyDir: {}
name:volName

Защита от создания файлов, загрузки скриптов и модификации программ в контейнере. Однако эти ограничения также влияют на законные контейнерные программы и могут привести к сбоям. Чтобы избежать повреждения законных программ, вы можете подключить вторичные файловые системы чтения/записи для определенных каталогов, где приложения требуют write-доступа.

Без shell, cat, grep, less, tail, echo и т.д.

Dockerfile
 # Start by Building the application. 
FROM  golang: 1.13 -buster as build
 
WORKDIR  /go/src/app 
ADD  . /go/src/app
 
RUN  go get -d -v ./ ... 
RUN  go Build -o /go/bin/app

# Now copy it в наше base image. 
FROM  gcr.io/distroless/base-debian10 
COPY  --from=build /go/bin/app / 
CMD  [ "/app" ]

«Distoless» изображения содержат только ваше приложение и его зависимости во время выполнения. Они не имеют менеджеров пакетов, shell оболочек или любых других программ, которые вы ожидали бы найти в стандартном дистрибутиве Linux.

Меньше значит лучше

Сосредоточьтесь на меньшем объеме данных, хранящихся внутри контейнера. Вы должны хранить только свои программы, без исходного кода и build зависимости.

# Start by building the application.,  
FROM  golang: 1.13 -buster as build
 
WORKDIR  /go/src/app 
ADD  . /go/src/app
 
RUN  go get -d -v ./ ... 
RUN  go Build -o /go/bin/app

# Now copy it в наше base image. 
FROM  gcr.io/distroless/base-debian10 
COPY  --from=build /go/bin/app / 
CMD  [ "/app" ]

Секреты

a-
 Video: v1 
kind: Pod 
metadata: 
name: volume-test 
spec: 
containers:
- name: container-test
image: busybox 
volumeMounts:
- name: all-in-one
mountPath: "/projected-volume" 
readOnly: true 
volumes:
- name: all-in-one
projected: 
sources:
- secret:
name: mysecret 
items:
- key: username
path: my-group/my-username

Секреты Kubernetes, которые растут по мере жизни самой аппликации, используются для передачи конфиденциальной информации, например паролей или токенов, к событиям аппликации.

Вы можете хранить тип секрет в Kubernetes API, монтировать их как файлы или просто объявлять как переменную среды. Также есть операторы, например, Bitnami Sealed Secret, которые помогают зашифровать содержимое секрета и позволяют отправить конференционные данные в репозиторий. Даже публично.

Сканируйте Docker контейнеры

Хорошая новость заключается в том, что Docker и Snyk недавно объединились, чтобы обеспечить лучшее сканирование уязвимостей контейнеров. Что это значит для вас? Теперь Snyk интегрирован с Docker Hub для сканирования официальных изображений. Кроме того, Docker интегрировал сканирование Snyk прямо в клиенты Docker. Это означает, что теперь вы можете интегрировать его в одну команду для сканирования контейнеров в CI.

Конечно, вы можете использовать другие провайдеры, например Quay. Но они требуют большей интеграции и конфигурации. Кроме того, такие службы как Docker hub, AWS ECR, Quay, обеспечивают сканирование изображения после того, как вы отправили контейнер в Docker registry. Но пока вы будете исправлять эти уязвимости, контейнер, возможно, уже будет использоваться на нескольких средах и в том числе на продакшени.

Также существует несколько служб, которые можно развернуть, например, docker-bench-security. Они будут выполнять сканирование вашего кластера Kubernetes. Впрочем, это может быть лишним, ведь у нас есть Pod Security Policy, которая будет охватывать большинство наших мер безопасности, о чем мы поговорим ниже.

Безопасность Kubernetes

Первый вопрос, который мы должны задать, работая с Kubernetes, это «как я могу установить системные операторы и проектные аппликации в Kubernetes стекластере?» Почему бы не использовать инструменты CLI? Да это возможно. Но не обязательно означает, что это верный путь. Все workloads должны быть структурированы как пакеты и развернуты с определенной гибкостью. Для этого у нас есть Helm.

Helm помогает развертывать те же файлы YALM, но с шаблонизацией, как с переменными и условиями. Кроме того, он имеет историю ревизий, что позволяет восстановить ту или иную версию. Другими словами, Helm сделает вашу жизнь легче. Кроме того, почти все услуги предоставляют свои собственные Helm чарты, которые вы можете установить в один клик. Это так же просто, как установка пакетов в Linux.

Существует два подхода к автоматизированному и безопасному развертыванию Helm черт: push-based и pull-based.

Push-based подход – это то, что я люблю называть классическим подходом. Мы все его знаем, потому что используем каждый день. Скажем, вам нужно выстроить процесс CI/CD. Вы выбрали систему CI; вы создаете и отправляете артефакт, а затем запускаете развертывание непосредственно из CI. Это самый простой способ, и он имеет значительные преимущества, такие как обратная связь.

В нем тоже есть подводные камни. Во-первых, вы должны предоставить доступ к кластеру с CI, который обычно содержит права администратора. Во-вторых, состояние приложения может быть изменено с момента последнего выпуска, или кто-то может изменить конфигурации, и ваш CI будет сломлен. Таким образом, имея доступ администратора, CI может легко использовать, изменять или удалять другие ресурсы. Чтобы избежать этого, мы можем использовать другой подход.

Pull-based подход, также известный как GitOps, основанный на операторе, работающем внутри кластера. Он отслеживает изменения в хранилище и применяет автоматически. Поскольку оператор имеет доступ к репозиторию, нам не нужно предоставлять системе CI доступ к кластеру.

Преимущество использования этого подхода заключается в том, что у вас всегда есть источник истины (SSOT — Single Source of Truth ). Более того, оператор заметит любые изменения, внесенные вручную, и восстановит их в состояние репозитория, поэтому мы никогда не столкнемся со смещением конфигурации. Есть два pull-based инструменты: Flux и ArgoCD. Давайте поговорим об ArgoCD.

kind: Application
metadata:
  name: bookinfo
  namespace: argocd
spec:
  destination:
    namespace: bookinfo
    server: https://kubernetes.default.svc
  project: default
  source:
    path: applications/bookinfo
    repoURL: [email protected]:sqerison/gitops-demo-kubernetes-workloads.git
    targetRevision: main
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
kind: AppProject
metadata:
  name: bookinfo
  namespace: argocd
spec:
  destinations:
    -  namespace:  '*' 
      server:  '*'
  clusterResourceWhitelist:
    -  group :  '*' 
      kind:  '*'
  sourceRepos:
    -  git @github .com:sqerison / gitops - demo - kubernetes - workloads.git
     -  git @github .com:sqerison / gitops - demo - bookinfo - app.git

ArgoCD  это оператор который и отвечает за pull-based подход и следует GitOps принципам. Основная его роль, это менеджмент ресурсов и их обновление при получении изменений из репозитория. ArgoCD работает с двумя основными ресурсами: Application и AppProject.

Application описывает сам ресурс, то есть аппликацию, которую нужно установить. Это может быть helm чарт, или обычные YAML файлы, будь то Kustomize ресурсы. Также мы указываем к какому проекту (AppProject) относится эта аппликация. И еще несколько других опций.

AppProject обеспечивают логическую группировку приложений, что полезно, когда Argo CD используется несколькими командами. Он имеет несколько функций, например, ограничить то, что может быть развернуто, ограничить типы объектов, которые могут или не могут быть развернуты, например, RBAC, CRD, DaemonSets, Network Policy и т.д. При создании аппликации мы можем выбрать, в рамках какого проекта она будет существовать, и какие доступы она получит и не позволит выйти за рамки дозволенного.

kind: AppProject
metadata:
  name: bookinfo
  namespace: argocd
spec:
  destinations:
    -  namespace:  '*' 
      server:  '*'
  clusterResourceWhitelist:
    -  group :  '*' 
      kind:  '*'
  sourceRepos:
    -  git @github .com:sqerison / gitops - demo - kubernetes - workloads.git
     -  git @github .com:sqerison / gitops - demo - bookinfo - app.git

Pod Security Policy

В начале статьи упоминались non-root контейнеры, read-only файловые системы и другие docker практики, как передача сокета docker, или использование сети системы (—net=host).

С помощью PSP мы можем ввести и не допустить выполнения событий, если эти требования не будут удовлетворены.

psp-non-privileged.yaml
 apiVersion: policy/v1beta1 
kind: PodSecurityPolicy 
metadata: 
name: example 
spec: 
privileged: false # Вы не можете пользоваться pods! 
# Занимающиеся fills в некоторых необходимых полях. 
seLinux: 
rule: RunAsAny 
supplementalGroups: 
rule: RunAsAny 
runAsUser: 
rule: MustRunAsNonRoot 
fsGroup: 
rule: RunAsAny 
volumes:
- '*'

Вот обычный пример, для чего можно применить PSP. Запрещает запуск контейнеров в привилегированном режиме (privileged: false) и запрещает работу под пользователем root (MustRunAsNonRoot). Важно! Для того что правила PSP ресурса вступили в силу, их нужно авторизовать с помощью RBAC.

Из-за сложности инженеры часто не используют этот ресурс, потому что вместе с политиками, вам нужно позаботиться о других конфигурациях, ломая голову, как и где их использовать. Вот почему PSP функционал в ближайшее время станет устаревшим.

Но PSP – не единственное, что мы можем использовать для обеспечения функций безопасности. У нас есть Open Policy Agent.

Open Policy Agent

Агент Open Policy, в сущности, является Gatekeeper-ом. Это оператор, который оценивает запросы к контроллеру допуска (admission controller), чтобы определить, соответствуют ли они правилам.

С помощью этого инструмента мы можем расширить контроль над создаваемыми ресурсами. Мы можем контролировать такие вещи как labels, requests и limits. Это важно, когда вы хотите масштабировать ваше приложение. Мы также можем ограничить список репозиториев docker, разрешив только корпоративные или AWS ECR. С помощью Gatekeeper вы можете ограничить любую опцию или аргумент во всех ресурсах Kubernetes.

Но, как и любой мощный инструмент, у него достаточно сложный синтаксис политики. В сущности, это язык REGO. Позвольте показать вам пример.

opa-k8spspprivilegedcontainer-ct.yaml
 apiVersion: templates.gatekeeper.sh/v1beta1 
kind: ConstraintTemplate 
metadata: 
name: k8spspprivilegedcontainer 
spec: 
crd: 
spec: 
name: 
kind: 
K8sPSPPrivilegedContainer
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spspprivileged

violation[{"msg": msg, "details": {}}] {
c := input_containers[_]
c.securityContext.privileged
msg := sprintf( "Privileged container is not allowed: %v, securityContext: %v" , [c.name, c.securityContext])
}
input_containers[c] {
c := input.review.object.spec.containers[_]
}
input_containers[c] {
c := input.review.object.spec.initContainers[_]
}

Это политика для ограничения docker репозиториев с типом ConstraintTemplate. Я не знаю, как писать на REGO. Это только пример, который я нашел в библиотеке, предоставленной Gatekeeper на GitHub. Итак, эта конфигурация – это просто шаблон, и аргументы для этого приведены в следующем примере.

opa-k8sallowedrepos-inuse.yaml
 apiVersion: constraints.gatekeeper.sh/v1beta1 
kind: K8sAllowedRepos 
metadata: 
name: repo-is-openpolicyagent 
spec: 
match: 
kinds: 
- apiGroups: [ "" ]
 kinds: " 
" 
-  "default" 
parameters: 
repos: 
-  "openpolicyagent/" 
-  "quay.io/" 
-  "<aws_account>.dkr.ecr.<region>.amazonaws.com/"

Как только вы создадите шаблон, Gatekeeper на его основе создаст CRD, на который вы сможете ссылаться, описывая какие именно репозитории вы хотите разрешить и для каких namespace-ов это применить. Мы применяем эту политику к модулям и namespace-ам по умолчанию, но вы можете и самостоятельно определить некоторые namespace-ы. В конце концов у нас есть список реестров, которым мы хотим доверять. Самое трудное – это написать шаблоны, которые вообще можно погуглить, а остальное не сложное.

Network Policies

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  namespace: default
  name: deny-from-other-namespaces
spec:
  podSelector:
    matchLabels:
  ingress:
  - from:
    - podSelector: {}

Network Policies гораздо проще в использовании по сравнению с Gatekeeper-ом. Как вы знаете, namespace-ы в Kubernetes не изолированы друг от друга, и любой под может общаться с любым другим подом. Это не очень хорошо, особенно если у вас есть сервисы с конфиденциальными данными или мониторингом, которые должны иметь доступ только к порту метрик. Кроме того, если у вас есть кластер с несколькими клиентами, содержащий аппликации от разных клиентов, вы должны быть уверены, что они не будут взаимодействовать друг с другом.

В этом примере можно увидеть, как разрешить трафик из других namespace-ов на основе лейбов. И да, если вы хотите проэкспериментировать сетью, вы можете запустить под в тестовом namespace-е с меткой prod, и трафик будет разрешен. Но это не очень критично. В крайнем случае всегда можно обратиться к Gatekeeper-a и указать, какие именно лейбы должны присутствовать в данном namespace-е.

 

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: web-allow-prod
spec:
  podSelector:
    matchLabels:
      app: web
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          purpose: production

Чтобы Network Policies работали, вам нужно установить поддерживающий их сетевой плагин (CNI). Calico – хороший кандидат. AWS EKS имеет собственный сетевой плагин, и, в крайнем случае, вы можете использовать Security Groups для просмотра и управления правилами AWS консоли.

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: api-allow-5000
spec:
  podSelector:
    matchLabels:
      app: apiserver
  ingress:
  - ports:
    - port: 5000
    from:
    - podSelector:
        matchLabels:
          role: monitoring

Последний пример – ограничения по порту. Если быть точным, разрешите трафик только в порт с метриками, к которому будет доступна система мониторинга.

Еще одно замечание по AWS CNI. В случае использования Custom Networking Network Policies потеряют свою силу. Поэтому вы должны выбирать, какая система политик вам больше подходит.

Кроме того, есть еще Mesh система Istio, которая имеет свои политики. Она работает на седьмом уровне OSI, и позволяет управлять трафиком более гибко. Но Istio – достаточно широкая тема, поэтому мы сегодня не будем о ней говорить.

Секреты

Всем сейчас интересно, как мы можем безопасно доставить конфиденциальную информацию в кластер, не беспокоясь о потере или раскрытии. Хорошим кандидатом для управления вашими секретами является Bitnami Sealed Secrets. Вам просто нужно зашифровать секрет с помощью ключа шифрования и результатом шифрования уже будет готовый ресурс, который уже можно разворачивать в кластере.

Другой альтернативой является GitCrypt, который шифрует файлы с помощью GPG ключей и может быть расшифрован только другим ключом, которые были предварительно добавлены. Это не самый лучший вариант для секретов Kubernetes, но хороший для других конфиденциальных данных, таких как частные ключи или kubeconfig.

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  annotations:
    sealedsecrets.bitnami.com/cluster-wide: "true"
  creationTimestamp: null
  name: demo-secret
spec:
  encryptedData:
    api-key: AgCK+iL6mZX6woqKiYQPWeELNt4/JrpaiwLR75d24OnshhsNveGB7CqGF1dr+rAxal4gr+d4No4Q+uAQgUizgLnY2IdWvAKVh/3miCgPW8SO8p8BOxpD8U1qBgJBb74M8rPbvxh47L0y2iSymSa4wdf4zcyju5CvoWnnB0Qbsx6lNzGMDnt6rjjzje3Su7ktrj4qnmX6BhRnukGmw+bErT31DzVWDrgrlcd2eQFuAflysckJv7wdIZXKSZwAWHAJzipUcNbG+O+UHJwia7RXwef9F2Ruebnl2jXH5/7iCV+83NLivdl0aW2TzLGOLR1NMG63NtN3T95Qfisame2QkYCBmYRCCCn3iwwxzDXDymAFE9/RqnnIPzhA/K0YayPZnLInoO3pTVxF1DL+RnmWRojUOwoO5ZkY++Behzq7nn9nRrEC+u/aDk2CXwJe9WbHwVgznKM7N6v4IUlcQz93VhRUbDetnWhA3TnD+HDsc85z0hvFp8c2U4giqRL4CnXHQIfBG63hLHoAogWOH8I+paVId180DWFpwjsAsKXVbESUa2ORL7LmuiDg1qKLoVFxiEEVJmnYPv5F8P1XMvJPW6L6QRQnJqj/ntyRSyEKnNh3umRTBoJzfXNDhsDXMPMu0leuYN1D+arx6IHBCKPexevE53iE7JK05bj/Oq8ujCOJRyv6TqjX4gQM3+kgXmi8rnCYB1CJg6lvhH1+pw==
  template:
    data: null
    metadata:
      annotations:
        sealedsecrets.bitnami.com/cluster-wide: "true"
      creationTimestamp: null
      name: demo-secret

Здесь вы можете увидеть пример Sealed Secrets. Как видите, значение зашифровано. И как только вы развернете этот файл в кластере, оператор Bitnami Sealed Secrets. преобразует и расшифрует этот файл в обычный секрет. Как этот.

Как видите, теперь наш Sealed Secret – это обычный секрет. А помните ArgoCD? Как уже упоминалось ранее, вы можете легко предоставить доступ к ArgoCD разработчикам и другим инженерам. Но не забудьте убедиться, что у них доступ только на чтение.

Kubernetes Hardening

Теперь пора поговорить о самих компонентах кластера. Они также имеют свои уязвимые места и злоумышленники с радостью воспользуются ими в случаи неправильных конфигурации.

API Server

API Server является ядром Kubernetes. На некоторых ванильных и старых версиях кластеров, API-сервер работает не только через HTTPS, но и на незащищенном порту (HTTP), который не проверяет аутентификации и авторизации. Обязательно убедитесь, что вы отключили незащищенные порты. Вы также можете попытаться отправить curl запрос на 8080 порт, чтобы проверить, получите ли ответ.

Etcd

Etcd похож на базу данных, которая хранит информацию о кластере и секретах кластера. Это важный компонент, и каждый, кто может писать в etcd может эффективно контролировать ваш Kubernetes кластер. Даже простое чтение содержимого etcd может легко дать полезные подсказки потенциальному злоумышленнику. Сервер etcd должен быть сконфигурирован так, чтобы доверять только сертификатам, которые предназначены API-серверам. Таким образом, он будет доступен только для проверенных компонентов в кластере.

Kubelet

Кубелет. Это агент, получающий инструкции, что делать и где делать. В основном отвечает за планирование ваших аппликаций в кластере. Проверьте наличие анонимного доступа и правильного режима авторизации, и вы будете в безопасности.

Kubernetes dashboard

Kubernetes панель. Эту систему лучше отключить полностью, поскольку у нас есть другие инструменты, которые могут помочь нам понять статус кластера. Как, например, ArgoCD. Вы увидите только ресурсы, созданные Argo, но это хорошо, поскольку обычно проблемы возникают из-за аппликации и ресурсы проекта, а сам кластер достаточно стабильным.

Другие вспомогательные инструменты

Это еще не все. У меня есть несколько инструментов, которые помогут в поиске уязвимых мест.

Kubescape

Чтобы запустить этот инструмент, достаточно выполнить две команды. Одну – для загрузки скрипта, а другую – для его выполнения. В результате вы получите список уязвимостей и неправильных конфигураций с итогом и общей оценкой в ​​конце. Помните рекомендации по усилению Kubernetes? Этот инструмент проверит их для вас. Кроме того, Kubescape использует базы данных, которые соответственно обновляются для обнаружения новых уязвимостей.

Kube-bench

Он почти такой же, как Kubescape, за исключением того, что он выполняется внутри кластера и может быть развернут как Cronjob для регулярного сканирования.

Kubesec

Простой плагин для сканирования модулей Kubernetes.

Kubeaudit

Имеет подобные функции, но используется для обнаружения неисправности (debugging) и создает хороший список с примерами, которые помогут устранить их.