K8s-pki
Заметка о PKI/CA в kubernetes
Эта заметка появилась для того что бы раз и навсегда прояснить какие CA и сертификаты используются в control-plane, для чего они используются и почему именно такие.
Предполагается что у читающего есть базовое понимание TLS/SSL/CA/PKI/OpenSSL
Julia Evans - мой герой! https://jvns.ca/blog/2017/08/05/how-kubernetes-certificates-work/
Структура заметки следующая
- Первая часть - пример настройки PKI вручную для всех сервисов K8S
- Вторая часть - заметки которые были написаны в процессе понимания (хронологически она первая но читать ее не обязательно и написано местами сумбурно)
Используется внешний уже настроенный Vault
- Токен не секретный только в тестовых инсталляциях - в общем случае использовать один рутовый токен для всего плохая идея
Почему возникла эта заметка
Официальная документация гласит:
Kubernetes requires PKI for the following operations:
- Client certificates for the kubelet to authenticate to the API server
- Server certificate for the API server endpoint
- Client certificates for administrators of the cluster to authenticate to the API server
- Client certificates for the API server to talk to the kubelets
- Client certificate for the API server to talk to etcd
- Client certificate/kubeconfig for the controller manager to talk to the API server
- Client certificate/kubeconfig for the scheduler to talk to the API server.
- Client and server certificates for the front-proxy
Note: front-proxy certificates are required only if you run kube-proxy to support an extension API server.
etcd also implements mutual TLS to authenticate clients and peers.
При этом я не нашел никакой информации о том каким свойствами должны обладать сертификаты, должны ли они быть подписаны одним CA?
Практика
Насколько я могу судить можно использовать следующие CA (которые не обязаны но могут быть одним и тем же CA)
СA1 Для авторизации клиентов у kube-apiserver
- Подготовка PKI
mkdir kube-apiserver/client-auth
Подготовка переменных окружения для PKI
Создание промежуточного CA
PKI_PATH - просто человеко-читаемый путь он может быть строго говоря любым
Создание запроса на сертификат для промежуточного CA
Извлечение CSR из ответа и сохранение в удобном формате
Подписать промежуточный сертификат CA основным CA
(подробнее в статье про Vault PKI https://noname.com.ua/mediawiki/index.php/Vault_PKI)
Извлечение сертификата из ответа и сохранение в удобном формате (PEM)
Загрузка подписанного сертификата в Vault
Прописать URL
Создать роль для создания клиентских сертефикатов
Выпустить сертификат для system:kube-controller-manager
1
kube-apiserver
kube-controller-manager
kubelet
Разбор от Джулии с моими комментариями
Вот что пишет по этому поводу Джулия (перевод мой):
В различных компонентах Kubernetes есть ТЫЩИ разных мест, где вы можете разместить a certificate/certificate authority. Когда мы настраивали кластер, я чувствовал, что существует около 10 миллиардов различных аргументов командной строки для сертификатов, ключей и центров сертификации, и я не понимал, как все они сочетаются друг с другом. На самом деле нет 10 миллиардов аргументов командной строки, но их довольно много. Например! Давайте просто посмотрим на аргументы командной строки для сервера API.
У API сервера 16 (ШЕСТНАДЦАТЬ, КАРЛ) аргументов которые так или иначе относятся к шифрованию:
--cert-dir string The directory where the TLS certs are located. If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored. (default "/var/run/kubernetes") --client-ca-file string If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate. --etcd-certfile string SSL certification file used to secure etcd communication. --etcd-keyfile string SSL key file used to secure etcd communication. --etcd-cafile string SSL Certificate Authority file used to secure etcd communication. --kubelet-certificate-authority string Path to a cert file for the certificate authority. --kubelet-client-certificate string Path to a client cert file for TLS. --kubelet-client-key string Path to a client key file for TLS. --proxy-client-cert-file string Client certificate used to prove the identity of the aggregator or kube-apiserver when it must call out during a request. This includes proxying requests to a user api-server and calling out to webhook admission plugins. It is expected that this cert includes a signature from the CA in the --requestheader-client-ca-file flag. That CA is published in the 'extension-apiserver-authentication' configmap in the kube-system namespace. Components recieving calls from kube-aggregator should use that CA to perform their half of the mutual TLS verification. --proxy-client-key-file string Private key for the client certificate used to prove the identity of the aggregator or kube-apiserver when it must call out during a request. This includes proxying requests to a user api-server and calling out to webhook admission plugins. --requestheader-allowed-names stringSlice List of client certificate common names to allow to provide usernames in headers specified by --requestheader-username-headers. If empty, any client certificate validated by the authorities in --requestheader-client-ca-file is allowed. --requestheader-client-ca-file string Root certificate bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified by --requestheader-username-headers --service-account-key-file stringArray File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify ServiceAccount tokens. If unspecified, --tls-private-key-file is used. The specified file can contain multiple keys, and the flag can be specified multiple times with different files. --ssh-keyfile string If non-empty, use secure SSH proxy to the nodes, using this user keyfile --tls-ca-file string If set, this certificate authority will used for secure access from Admission Controllers. This must be a valid PEM-encoded CA bundle. Alternatively, the certificate authority can be appended to the certificate provided by --tls-cert-file. --tls-cert-file string File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to /var/run/kubernetes. --tls-private-key-file string File containing the default x509 private key matching --tls-cert-file. --tls-sni-cert-key namedCertKey A pair of x509 certificate
Отдельно радует именование, блять, параметров: ca-file или cafile???
А может лучше certificate-authority.
Не можем решить и оставим все три варианта!
Четыреждыблядская ярость. На этом месте я подумал что может ну его нахуй и не так уж сильно я хочу изучить Kubernetes.
Еще информация к размышлению (да я тоже был очень удивлен!):
One thing I found surprising about this is – almost everything else in the universe that uses TLS will look in /etc/ssl to find a list of certificate authorities the computer trusts by default. But Kubernetes doesn’t do that, instead it mandates “no, you have to say exactly which CA issued the API server’s TLS cert”.
Другими словами использовать добавленные в систему доверенные CA не получится.
Нужен ли единый CA для кластера (нет)
Такой вопрос возник не только у меня:
Does a Kubernetes cluster have to have a single root certificate authority? (no)
Дальше мой вольный перевод:
Если вы прочитали кучу мануалов про установку кластера, то в каждом из них есть шаг "Постройте свой СА" ("set up a certificate authority".
Kelsey Hightower’s c его прекрасной, но недостаточно подробной инструкцией “kubernetes the hard way” делает это на втором шаге.
инструкция гласит
Every Kubernetes cluster has a cluster root Certificate Authority (CA). The CA is generally used by cluster components to validate the API server’s certificate, by the API server to validate kubelet client certificates, etc.
В общих чертах это работает так
- Настройте свой центр выдачи сертефикатов (обычно предлагают скрипты cfssl)
- Нагенерите сертификатов подписанных своим СА для разных частей кластера
Тут сразу же возникает вопрос - а что делать если я не хочу городить новый PKI для каждого кластера
Фраза (“every Kubernetes cluster has a [single] cluster root Certificate Authority”) ошибочна полностью - можно как использовать один CA для нескольких кластеров так и несколько СА для одного кластера.
kube-apiserver
The API server’s TLS certificate (and certificate authority)
Параметры
- --tls-cert-file string File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to /var/run/kubernetes.
- --tls-private-key-filestring File containing the default x509 private key matching --tls-cert-file.
Относительно простая часть - эти параметры отвечают за серверный сертификат и его ключ который будет на Endpoint. В общем случае неплохо сделать так что бы curl мог подключиться туда без проблем - т е добавить CА который подписал этот сертификат в доверенные (или сделать bundle с промежуточным CA).
Так же важно что бы тут был правильныйдомен(и IP в SAN)
После устновки TLS для API требуется подготовить kubeconfig для других компонетов (таких как kubelet and kubectl) которым требуется коммуникация с API.
kubeconfig выглядит примерно так
current-context: my-context apiVersion: v1 clusters: - cluster: certificate-authority: /path/to/my/ca.crt # Путь к CA которым был подписан сертификат для API указанный в параметре --tls-cert-file kube-apiserver server: https://horse.org:4443 # Доменное имя должно быьть указано в сертификате, который указан в параметре --tls-cert-file kube-apiserver name: my-cluster kind: Config users: - name: green-user user: client-certificate: path/to/my/client/cert # РАССМОТРЕНО НИЖЕ client-key: path/to/my/client/key # РАССМОТРЕНО НИЖЕ
Напоминаю что системные СА игнорируются и нужно ЯВНО указать путь к СА которым подписан сертификат
Вопросы пока без ответа
- current-context
- name: my-cluster
Файл kubeconfig передается компонентам как парамерт --kubeconfig /path/to/kubeconfig.yaml
Итого - первый СА это СА для TLS на API (при этом сам корневой сертификат используется только для проверки подлиности сервера а его ключ нигде не присутствует. (назовем этот СА - СА1) Этот СА может использоваться ТОЛЬКО для указанных целей и не совпадать ни с одним другим СА кластера.
The API server client certificate authority (+ certificates)
--client-ca-file string If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.
Один из возможных способов для компонентов Kubernetes авторизоваться (в оригинале в to authenticate что правильно но я не знаю русского аналога) у API это использовать клиентские сертификаты.
Все эти клиентские сертификаты должны быть выпущены одним и тем же СA, который, повторюсь может отличаться от того СА который выпустил сертификат для HTTPS сервера API
Когда используется файл kubeconfig клиентские сертификаты можно указать примерно так:
kind: Config users: - name: green-user user: client-certificate: path/to/my/client/cert client-key: path/to/my/client/key
Kubernetes ожидает что клиентские сертификаты будут созданы определенным образом.
- Common Name - содержит username
- Organization - содержит group
Если это не то что нужно то вместо клиентских сертификатов нужно использовать аутентифиуирующий прокси (use an authenticating proxy) (но я не знаю как)
Соответственно для авторизации клиентов (пока что это только компоненты кубернетиса) используется СА2
The request header certificate authority (or: using an authenticating proxy)
# API server arguments --requestheader-allowed-names stringSlice List of client certificate common names to allow to provide usernames in headers specified by --requestheader-username-headers. If empty, any client certificate validated by the authorities in --requestheader-client-ca-file is allowed. --requestheader-client-ca-file string Root certificate bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified by --requestheader-username-headers
Другой способ это использовать authenticating proxy.
(мне пока что не нужен по тому не перевел)
If you have a lot of opinions about what usernames and groups should be sent to the API server,
you can set up a proxy which passes usernames & groups to the API server in a HTTP header.
The docs basically explain how this works – the proxy uses a client cert to identify itself,
and the --requestheader-client-ca-file tells the API server which CA to use to verify that client cert.
I don’t have too much to say about this except – we learned pretty quickly that having too many auth methods in your API server (“accept client certificates OR an authenticating proxy OR a token OR…“) makes things confusing. It’s probably better to pick a small number (like 1 or 2?) of authentication methods your API server supports because it makes it easier to debug problems and understand your security model.
serviceaccount private keys (which aren’t signed by a certificate authority)
# API server argument --service-account-key-file stringArray File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify ServiceAccount tokens. If unspecified, --tls-private-key-file is used. The specified file can contain multiple keys, and the flag can be specified multiple times with different files.
# controller manager argument --service-account-private-key-file string Filename containing a PEM-encoded private RSA or ECDSA key used to sign service account tokens.
Controller manager подписывает токены сервисного аккаунта закрытым ключом.
The controller manager signs serviceaccount tokens with a private key.
Но в отличии от всех остальных частных ключей в Kubernetes, ключ сервисного аккаунта не поддерживает проверку подписи с помощью СА
Это (очевидно) означает что нужно иметь один и тот же закрытый ключ на всех запущенных controller manager
Этот (закрытый) ключ не должен иметь подписи СА и сертификата
и может быть просто создан командой
openssl genrsa -out private.key 4096
и распространен по всем нодам controller manager и API server.
Дополню:
Т.е.нужна пара ключей, причем эта пара одна в пределах кластера:
- Для API сервера нужна публичная часть ключа, которая передается параметром --service-account-key-file и используется для проверки токенов
- Для controller manager нужна приватная часть ключа которая используется для подписания токенов
openssl genrsa -out service-account-private.key 4096
openssl rsa -in service-account-private.key -pubout > service-account-public.key
Вот что пишет Джулия:
Using --tls-private-key-file for this seems generally fine to me though,<BR> as long as you give every API server the same TLS key (which I think you usually would?).<BR> (I’m assuming here that you have a HA setup where you run more than one API server and more than one controller manager) If you give 2 different controller managers 2 different keys, they’ll sign serviceaccount tokens with different keys and you’ll end up with invalid serviceaccount tokens (see this issue: https://github.com/kubernetes/kubernetes/issues/22351).<BR> I think this isn’t ideal (Kubernetes should probably support these keys being issued from a CA like it does for ~every other private key). From reading the source code I think the reason it’s set up this way is that jwt-go doesn’t support using a CA to check signatures.
kubelet certificate authorities
Настройки api для связи с kubelet:
# API server arguments --kubelet-certificate-authority string Path to a cert file for the certificate authority. --kubelet-client-certificate string Path to a client cert file for TLS. --kubelet-client-key string Path to a client key file for TLS.
Настройки kubelet:
# kubelet arguments --client-ca-file string If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate. --tls-cert-file string File containing x509 Certificate used for serving HTTPS (with intermediate certs, if any, concatenated after server cert). If --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to the directory passed to --cert-dir. --tls-private-key-file string File containing x509 private key matching --tls-cert-file.
Неплохая мысль проверять и аутентифицировать запросы к кублетам так как они могут исполнять произвольный код на нодах (собственно в этом их задача)
По факту тут в настройках 2 СА - аналогично API сервера
- kubelet-certificate-authority - собственно СА которым API сервер подписывает клиентские сертификаты
С другой стороны у kubelet
- Параметры с tls - это сертификат HTTP(s) endpoint
- --client-ca-file - это СА с помощью которого можно проверить клиентский сертификат
There are actually 2 CAs here. I won’t go into too much detail because this is the same as the setup for the APi server – the kubelet has a TLS cert (that it uses to serve TLS requests) and also supports client cert authentication. You tell the API server what certificate authority to use to check the kubelet’s TLS cert, and what client certificate to use when talking to the kubelet. Again these 2 CAs could be totally different from each other.
so many possible CAs
So far we have found 5 different certificate authorities you can specify as part of setting up a Kubernetes cluster! They’re all handled independently and in theory they could all be totally different. I didn’t discuss every single CA setting that Kubernetes supports (there are still more!) but hopefully this gives you some of the tools you need to read the docs about the rest. Again – it almost certainly doesn’t makes sense to make them all different from each other but I think it’s useful to understand how all this is set up if you have your own requirements around how you want to handle your certificate authorities for Kubernetes and don’t want to do exactly what the docs suggest