Начиная с Kubernetes 1.6, RBAC-политики включены по умолчанию. К тому же использование этих политик помогает безопасно управлять вашим кластером. Раньше нам приходилось вручную создавать подобные политики, сервисные аккаунты и пользователей. Для каждого нового проекта мы проделывали ручные операции, которые отнимали много времени. Особенно создание пользователей, так как требует множества мелких манипуляций. Готового решения, удовлетворяющего нашим требованиям, нам найти не удалось, поэтому мы написали terraform-модуль, который упрощает этот процесс. Данный модуль позволяет создавать сервисные аккаунты и пользователей, а затем генерировать готовые конфигурационные файлы (kubeconfig) для них. Также при помощи данного модуля можно создавать роли, кластерные роли и привязывать их к определенным сервисным аккаунтам, пользователям или группам. Модуль можно найти в нашем GitHub репозитории. Для использования модуля  потребуется terraform >= 1.0.0 и kubeconfig, с административными правами.

Описание переменных

  • output_files_path: путь до директории где будут сохранены сгенерированные TLS файлы и kubeconfig для пользователей и сервисных аккаунтов. По умолчанию: "./files".

  • k8s_api_endpoint: имя хоста (в URI формате) для Kubernetes API.

  • k8s_insecure: следует ли обращаться к серверу без проверки сертификата TLS. По умолчанию: false.

  • k8s_cluster_name: название Kubernetes кластера.

  • k8s_auth_cluster_ca_certificate: PEM-кодированный корневой сертификат для TLS аутентификации.

    • raw(опционально): сертификат в неизменном виде. Пример: "-----BEGIN CERTIFICATE-----\nMIIELDCCApSgAwIBAgIQcLahmhzRbVMSRZX2cQXtuTANBgkqhkiG9w0BAQsFADAv\n ... \n-----END CERTIFICATE-----\n".

    • encoded(опционально): закодированный в base64 сертификат.

Должно быть задано одно из полей raw или encoded. Если заданы оба, будет использовано поле raw.
Переменные k8s_api_endpoint, k8s_auth_cluster_ca_certificate и k8s_cluster_name необходимы для генерации конфигурационных файлов (kubeconfig).

  • k8s_config_path: путь до kubeconfig с административными правами.

  • k8s_config_context(опционально): контекст для kubeconfig.

  • roles_list: список создаваемых ролей.

    • name: название роли.

    • namespace: окружение.

    • rules: список правил для роли.

      • api_groups: список субъектов.

      • resources: список ресурсов.

      • verbs: список глаголов (verb).

  • cluster_roles_list: список создаваемых кластерных ролей.

    • name: название кластерной роли.

    • rules: список правил для роли.

      • api_groups: список субъектов.

      • resources: список ресурсов.

      • verbs: список глаголов (verb).

  • sa_list: список создаваемых сервисных аккаунтов.

    • name: имя сервисного аккаунта.

    • namespace: окружение.

  • bindings: список RoleBinding и ClusterRoleBinding.

    • type: тип (role_binding или cluster_role_binding).

    • prefix: уникальная строка, которая используется для формирования названия RoleBinding и ClusterRoleBinding.

    • namespaces(опционально): список окружений, в которых создается RoleBinding. Используется только для RoleBinding.

    • sa_list(опционально): список сервисных аккаунтов.

      • name: имя сервисного аккаунта.

      • namespace: окружение.

    • users(опционально): список пользователей. Пользователи создаются из данного списка.

      • name: имя пользователя.

      • group: группа (организация) пользователя.

    • groups(опционально): список групп.

    • roles(опционально): список ролей.

    • cluster_roles(опционально): список кластерных ролей.

Значение для переменной type может быть следующим: role_binding или cluster_role_binding.
Переменная namespaces используется только для RoleBinding. Если переменная будет пустая для RoleBinding, деплой не пройдет.
Одна из переменных sa_list, users, groups должна быть задана для RoleBinding и ClusterRoleBinding.
Пользователи создаются и списка users.
Переменные roles или cluster_roles должны быть установлены для RoleBinding.
Переменная cluster_roles должна быть установлена для ClusterRoleBinding.

Пример использования модуля

Рассмотрим конкретный пример использования данного модуля:

Код
module "k8s-rbac-controller" {
  source = "../k8s-rbac-controller"

  output_files_path = "./files"

  k8s_api_endpoint = "https://172.20.1.2"
  k8s_auth_cluster_ca_certificate = {
    raw     = "-----BEGIN CERTIFICATE-----\nMIIELDCCApSgAwIBAgIQcLahmhzRbVMSRZX2cQXtuTANBgkqhkiG9w0BAQsFADAv\n ... 8hZp/GUpn6jahcXxmuKaAQ==\n-----END CERTIFICATE-----\n"
    encoded = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS..."
  }
  k8s_cluster_name   = "cluster-name"
  k8s_config_path    = "~/.kube/config"
  k8s_config_context = "config-context"

  roles_list = [
    {
      name      = "role-1",
      namespace = "default",
      rules = [
        {
          api_groups = [""],
          resources  = ["pods"],
          verbs      = ["get", "list"],
        }
      ]
    }
  ]

  cluster_roles_list = [
    {
      name = "cluster-role-1",
      rules = [
        {
          api_groups = [""],
          resources  = ["namespaces"],
          verbs      = ["get", "list", "watch", "create"],
        }
      ]
    },
    {
      name = "cluster-role-2",
      rules = [
        {
          api_groups = [""],
          resources  = ["namespaces"],
          verbs      = ["get"]
        },
        {
          api_groups = [""],
          resources  = ["namespaces"],
          verbs      = ["list"]
        }
      ]
    }
  ]

  sa_list = [
    {
      name      = "sa-1"
      namespace = "kube-system"
    },
    {
      name      = "sa-2"
      namespace = "default"
    },
  ]

  bindings = [
    {
      type       = "role_binding"
      prefix     = "prefix-1"
      namespaces = ["default"]
      sa = [
        {
          name      = "sa-1",
          namespace = "kube-system",
        }
      ],
      users = [
        {
          name  = "user-1",
          group = "group-1",
        },
        {
          name  = "user-2",
          group = "group-2",
        }
      ]
      groups        = ["group-1"]
      roles         = ["role-1"]
      cluster_roles = ["cluster-role-2"]
    },
    {
      type   = "cluster_role_binding"
      prefix = "prefix-2"
      sa = [
        {
          name      = "sa-2",
          namespace = "default",
        }
      ],
      users = [
        {
          name  = "user-2",
          group = "group-2",
        }
      ],
      cluster_roles = ["cluster-role-1"]
    },
  ]

}

Данный пример можно найти в GitHub репозитории. В этом примере создается роль role-1 в default окружении со следующими правами:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: role-1
  namespace: default
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - list
  - get

Также создается кластерная роль cluster-role-1 со следующими правами:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-role-1
rules:
- apiGroups:
  - ""
  resources:
  - namespaces
  verbs:
  - get
  - list
  - watch
  - create

и cluster-role-2 со следующими правами:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-role-2
rules:
- apiGroups:
  - ""
  resources:
  - namespaces
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - namespaces
  verbs:
  - list

Дополнительно создаются сервисные аккаунты sa-1 в окружении kube-system и sa-2 в окружении default. Список пользователей для создания берется из поля users в переменной bindings. То есть в данном случае будут созданы 2 пользователя user-1 и user-2 в группах group-1 и group-2 соответственно. Также будут созданы 2 RoleBinding (соответствует количеству элементов в списках roles и cluster_roles, если type = role_binding) cluster-role-2-prefix-1-cluster-role:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cluster-role-2-prefix-1-cluster-role
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ns-viewer
subjects:
- kind: ServiceAccount
  name: sa-1
  namespace: kube-system
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: user-1
  namespace: default
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: user-2
  namespace: default
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: group-1
  namespace: default

и role-1-prefix-1-role следующего вида:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: role-1-prefix-1-role
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: role-1
subjects:
- kind: ServiceAccount
  name: sa-1
  namespace: kube-system
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: user-1
  namespace: default
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: user-2
  namespace: default
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: group-1
  namespace: default

Обратите внимание, что в RoleBinding можно указывать ClusterRole.

Также будет создан ClusterRoleBinding (соответствует количеству элементов в списке cluster_roles, если type = cluster_role_binding) cluster-role-1-prefix-2:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-role-1-prefix-2
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ns-creator
subjects:
- kind: ServiceAccount
  name: sa-2
  namespace: default
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: user-2
  namespace: default

Название для RoleBinding формируется по следующему принципу:
[${Role}|${ClusterRole}]-[${prefix}]-["role"|"cluster-role"]

Приписка ["role"|"cluster-role"] нужна, так как в RoleBinding можно использовать Role и ClusterRole, которые могут совпадать по названию.

Название для ClusterRoleBinding формируется по следующему принципу:
[${ClusterRole}]-[${prefix}]

Что планируется дальше

В будущем планируется добавить следующий функционал:

  • Возможность выбирать output для сгенерированных kubeconfig - как минимум добавить vault помимо локальных файлов.

  • Возможность задавать для пользователя не одну группу, а передавать список. Для этого необходимо законтребьютить в репозиторий hashicorp/terraform-provider-tls, добавив возможность передавать список в поле organization для ресурса tls_cert_request.

  • Возможность генерировать kubeconfig для существующих сервисных аккаунтов.

  • Добавить модуль в terraform registry, чтобы было удобнее его использовать.

Выводы

Данный модуль помог повысить нашу эффективность. Раньше мы могли тратить от 20 до 60 минут на создание RBAC политик. Сейчас же на это уходит от 5 до 10 минут. То есть наша эффективность возрасла в ~4~6 раз. Как правило для проектов мы используем одни и те же роли и кластерные роли. Поэтому модуль позволяет унифицировать данный процесс, так как описание RBAC политик осуществляется на стороне кода. То есть можно повторно использовать зараннее созданные файлы с заполненными переменными.

P.S.: На текущий момент данный модуль прошел обкатку на небольшом количестве проектов, поэтому возможно будут обнаруживаться какие-то баги. Если вы заметите какой-то баг, пожалуйста, создайте issue на GitHub, мы будем оперативно обрабатывать их.

Кстати, мы также делимся полезной информацией по теме DevOps и не только в telegram-канале DevOps FM, присоединяйтесь.

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


  1. Zeroberto
    12.02.2022 03:20

    Если вы используете одни и те же rbac политики во всех проектах, то можно ещё больше ускорить в раз 300-600, простым эплаем ямлика со всеми rbac.


    1. alexey_archimaev Автор
      12.02.2022 08:50

      У нас от проекта к проекту набор политик разный, так как условия у заказчиков бывают разные. Они одинаковы в рамках одного проекта на уровне окружений. Да, конечно можно использовать простые ямлы, однако этот модуль направлен не только на создание RBAC политик, но и на создание пользователей (user) и генерацию готовых кубконфигов. Чтобы мы руками не проделывали данные операции, а получали готовый результат. Данный модуль можно встраивать в CI и автоматизировать процессы. В будущем планируется добавить возможность сохранения кубконфигов в vault.