Глупый сертификат kubernetes
Куча сертификатов, генерируемых kubeadm, сильно сбивает с толку?Эти вещи не такие уж и волшебные, давайте покопаемся в их белье.
root@k8s-master:/etc/kubernetes/pki# tree
.
|-- apiserver.crt
|-- apiserver-etcd-client.crt
|-- apiserver-etcd-client.key
|-- apiserver.key
|-- apiserver-kubelet-client.crt
|-- apiserver-kubelet-client.key
|-- ca.crt
|-- ca.key
|-- etcd
| |-- ca.crt
| |-- ca.key
| |-- healthcheck-client.crt
| |-- healthcheck-client.key
| |-- peer.crt
| |-- peer.key
| |-- server.crt
| `-- server.key
|-- front-proxy-ca.crt
|-- front-proxy-ca.key
|-- front-proxy-client.crt
|-- front-proxy-client.key
|-- sa.key
`-- sa.pub
1 directory, 22 files
Начните с RSA
Чтобы глубоко понять роль сертификатов, вам сначала нужно понять некоторые принципы и иметь некоторые базовые знания, например, что такое асимметричное шифрование, что такое открытый ключ, закрытый ключ и что такое цифровая подпись. Начнем с алгоритма RSA.
Асимметричное шифрование генерирует ключевую пару, такую как SA.Key SA.Pub выше - ключевая пара, одна для шифрования и один для дешифрования.
открытый текст + открытый ключ => зашифрованный текст
Шифрованный текст + закрытый ключ => открытый текст
Затем, без закрытого ключа в это время трудно расшифровать зашифрованный текст.
Рассмотрим принцип подробнее, если не хотите обращать внимание, можете пропустить следующую принципиальную часть:
Предположим, мы хотим зашифровать слово Цезарь, сначала превратив его в строку чисел, например код Ascii X = 067097101115097114. Это чистый код, который нам нужно зашифровать. Теперь давайте зашифруем X.
- Найдите два больших простых числа P и Q и вычислите их произведение N = P * Q и пусть M = (P - 1)(Q - 1)
- Найдите число E такое, что у E и M нет общих делителей, кроме 1.
- Найдите число D такое, что E, умноженное на D, деленное на M, дает в остатке 1, E * D mod M = 1
Теперь E — открытый ключ, который может быть раскрыт кому угодно для шифрования.
D — закрытый ключ, который используется для расшифровки и должен быть сохранен вами.
N, связывающий открытый ключ и закрытый ключ, является общедоступным. Причина, по которой это может быть раскрыто, заключается в том, что легко вычислить N в соответствии с PQ, но очень сложно разложить N на два больших простых числа PQ, поэтому Также очень сложно раскрыть существующую вычислительную мощность компьютера.
Теперь для шифрования:
pow(X,E) mod N = Y Y - зашифрованный текст, и теперь нет D (закрытый ключ), боги не могут вычислить X (открытый текст)
Расшифровать:
pow(Y,D) mod N = X X — открытый текст, и открытый текст выходит.
Разве математика не удивительна, теперь можно сказать, что sa.key = D sa.pub = E
цифровой подписи
Предположим, вы пишете письмо своему боссу, содержание которого «Босс, я вас обожаю», а затем просите своего коллегу отправить письмо вашему боссу, как вы можете быть уверены, что письмо написано вами, и как вы можете предотвратить ваш коллега от изменения письма на "Босс вам в процессе доставки" Это SB"?
Можно сделать так, сначала генерируете пару ключей, отдаете публичный ключ шефу, потом делаете хеш-дайджест содержимого письма, а потом шифруете дайджест приватным ключом, в результате получается подпись
Таким образом, начальник расшифровывает письмо с помощью открытого ключа и находит, что полученное хеш-значение соответствует хэш-значению письма, тем самым подтверждая, что письмо было написано вами.
Таким образом, цифровая подпись — это применение технологии шифрования.Отличие от полностью зашифрованной информации в том, что информация общедоступна, и ваши коллеги могут видеть, как вы рекламируете своего босса.
Цифровой сертификат
Корневые сертификаты и сертификаты
Обычно, когда мы настраиваем службу https, нам нужно подать заявку на получение сертификата от «авторитета».
Процесс таков:
- Веб-сайт создает пару ключей, предоставляя уполномоченному органу открытый ключ, а также организационную и личную информацию.
- Сертификат, выданный органом
- Друзья, просматривающие веб-страницы, используют открытый ключ корневого сертификата для расшифровки подписи, сравнения реферата и определения легитимности.
- Клиент проверяет срок действия информации о доменном имени и т. д. (браузер в основном имеет встроенные открытые ключи ЦС основных органов)
Этот сертификат содержит следующее:
- открытый ключ заявителя
- Организация заявителя и личная информация
- Выдающее агентство CA информацию, действительное время, серийный номер и т.д.
- Подпись вышеуказанной информации
Корневой сертификат, также известный как самозаверяющий сертификат, — это сертификат, выданный вами. CA (Certificate Authority) называется центром сертификации, а сертификат ca в k8s является корневым сертификатом.
кубернет сертификат
С вышеупомянутой основой, давайте официально начнем. . .
Сначала классифицируйте:
Пара ключей: sa.key sa.pub Корневой сертификат: ca.crt etcd/ca Закрытый ключ: ca.key и т.д. Другие сертификаты
Прежде всего, другие сертификаты выдаются корневым сертификатом ЦС. Kubernetes и etcd используют разные ЦС. Важным моментом является то, используется ли сертификат для проверки клиента или проверки сервера. Давайте посмотрим один за другим:
сервисная пара ключей учетной записи sa.key sa.pub
Предоставляется kube-controller-manager для использования. kube-controller-manager подписывает токен с помощью sa.key, а главный узел проверяет подпись с помощью открытого ключа sa.pub. Если kube-proxy работает в виде пода, то в поде учетная запись службы напрямую используется для аутентификации с помощью kube-apiserver.На данный момент нет необходимости создавать сертификат для kube-proxy отдельно, а проверка токена будет использоваться напрямую.
корневой сертификат
pki/ca.crt
pki/ca.key
Центр выдачи сертификата кластера k8s
сертификат аписервера
pki/apiserver.crt
pki/apiserver.key
кубелет сертификат
pki/apiserver-kubelet-client.crt
pki/apiserver-kubelet-client.key
Чтобы kubelet мог активно обращаться к kube-apiserver, kube-apiserver также должен активно инициировать запросы к kubelet, Поэтому обе стороны должны иметь свой собственный корневой сертификат, а также сертификат сервера и сертификат клиента, выданные корневым сертификатом.В kube-apiserver обычно указывается сертификат сервера для доступа https и клиент с информацией об имени пользователя CN.конечный сертификат. В стартовой конфигурации kubelet обычно указывается только корневой сертификат ca, а сертификат сервера для https-доступа явно не указывается.При генерации сертификата сервера обычно указывается адрес сервера или имя хоста. kube-apiserver не меняется очень часто, поэтому IP-адрес или имя хоста/доменное имя kube-apiserver можно предварительно выделить в начале создания кластера. Однако, поскольку kubelet, развернутый на узле, будет часто меняться из-за изменения масштаба кластера, и невозможно предсказать всю IP-информацию узла, сертификат сервера обычно не указывается в kubelet. Вместо этого укажите только корневой сертификат ЦС и позвольте kubelet автоматически сгенерировать сертификат сервера на основе информации о локальном хосте и сохранить его в настроенной папке cert-dir.
Сертификат агрегации
Корневой сертификат прокси:
pki/front-proxy-ca.crt
pki/front-proxy-ca.key
Сертификат клиента, подписанный корневым сертификатом прокси:
pki/front-proxy-client.crt
pki/front-proxy-client.key
Например, при использовании прокси-сервера kubectl для доступа kube-apiserver использует этот сертификат, чтобы проверить, является ли сертификат клиента сертификатом, выданным им самим.
etcd корневой сертификат
pki/etcd/ca.crt
pki/etcd/ca.key
одноранговые сертификаты для связи между узлами etcd
Подписано корневым сертификатом
pki/etcd/peer.crt
pki/etcd/peer.key
Сертификат клиента зонда Liveness в модуле
pki/etcd/healthcheck-client.crt
pki/etcd/healthcheck-client.key
Вы можете просмотреть конфигурацию зонда yaml:
Liveness: exec [/bin/sh -ec ETCDCTL_API=3 etcdctl \
--endpoints=https://[127.0.0.1]:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key get foo] \
delay=15s timeout=15s period=10s #success=1 #failure=8
сертификат доступа к apiserver и т. д.
pki/apiserver-etcd-client.crt
pki/apiserver-etcd-client.key
Здесь обратите внимание на разницу между сертификатом клиента и сертификатом сервера.Сертификат сервера обычно проверяет адрес и доменное имя.
Код
kubeadm записывает время сертификата в 1 год (client-go записывается до смерти), что является печальной историей, которая привела к тому, что силосам пришлось убрать логику генерации сертификата, чтобы позволить установке поддерживать произвольное время истечения срока действия.
Ниже представлен подробный опыт создания сертификата kubeadm на основе исходного кода. Непосредственный просмотр кода kubeadm может быть немного утомительным. Основной код, извлеченный из каталога sealos/cert, легче читать.
Чтобы выделить основную логику, некоторые детали обработки ошибок удалены из приведенного ниже кода.Если вам интересно, вы можете прочитать исходный код на github.com/fanux/sealos/cert.
генерация пары ключей
// create sa.key sa.pub for service Account
func GenerateServiceAccountKeyPaire(dir string) error {
key, err := NewPrivateKey(x509.RSA)
pub := key.Public()
err = WriteKey(dir, "sa", key)
return WritePublicKey(dir, "sa", pub)
}
Сгенерируйте закрытый ключ, где keyType — x509.RSA.
func NewPrivateKey(keyType x509.PublicKeyAlgorithm) (crypto.Signer, error) {
if keyType == x509.ECDSA {
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}
return rsa.GenerateKey(rand.Reader, rsaKeySize)
}
Создать сертификат ЦС
Возвращает ca.crt (самозаверяющий сертификат) ca.key (закрытый ключ)
func NewCaCertAndKey(cfg Config) (*x509.Certificate, crypto.Signer, error) {
key, err := NewPrivateKey(x509.UnknownPublicKeyAlgorithm)
cert, err := NewSelfSignedCACert(key, cfg.CommonName, cfg.Organization, cfg.Year)
return cert, key, nil
}
Сгенерируйте самоподписанный сертификат на основе закрытого ключа, NotAfter — это время истечения срока действия сертификата, мы очень дружелюбны, чтобы добавить переменную вместо того, чтобы записывать ее до смерти:
// NewSelfSignedCACert creates a CA certificate
func NewSelfSignedCACert(key crypto.Signer, commonName string, organization []string, year time.Duration) (*x509.Certificate, error) {
now := time.Now()
tmpl := x509.Certificate{
SerialNumber: new(big.Int).SetInt64(0),
Subject: pkix.Name{
CommonName: commonName,
Organization: organization,
},
NotBefore: now.UTC(),
NotAfter: now.Add(duration365d * year).UTC(),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
}
certDERBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, key.Public(), key)
return x509.ParseCertificate(certDERBytes)
}
Обратите внимание на поля CommonName и Organization, которые очень полезны, например, мы создаем пользователя k8s, чтобы указать, к какой группе пользователей принадлежит пользователь, что соответствует двум полям выше.
Например, fanux в сертификате принадлежит организации sealyun, тогда генерация kubeconfig эквивалентна наличию пользователя fanux, так что k8s нужно только проверять подпись при выполнении аутентификации, и не нужно обращаться База данных используется для аутентификации, что очень выгодно для горизонтального расширения apiserver.
Создание дополнительных сертификатов
Пара ключей по-прежнему генерируется вами, а затем информация о корневом сертификате будет приведена при подписании сертификата.
func NewCaCertAndKeyFromRoot(cfg Config, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, crypto.Signer, error) {
key, err := NewPrivateKey(x509.UnknownPublicKeyAlgorithm)
cert, err := NewSignedCert(cfg, key, caCert, caKey)
return cert, key, nil
}
На этом этапе необходимо указать CommonName, и Usages также должны указать, используется ли оно сервером или клиентом.Обратите внимание на отличие от SelfSign выше.
// NewSignedCert creates a signed certificate using the given CA certificate and key
func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
if len(cfg.CommonName) == 0 {
return nil, errors.New("must specify a CommonName")
}
if len(cfg.Usages) == 0 {
return nil, errors.New("must specify at least one ExtKeyUsage")
}
certTmpl := x509.Certificate{
Subject: pkix.Name{
CommonName: cfg.CommonName,
Organization: cfg.Organization,
},
DNSNames: cfg.AltNames.DNSNames,
IPAddresses: cfg.AltNames.IPs,
SerialNumber: serial,
NotBefore: caCert.NotBefore,
NotAfter: time.Now().Add(duration365d * cfg.Year).UTC(),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: cfg.Usages,
}
certDERBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, key.Public(), caKey)
return x509.ParseCertificate(certDERBytes)
}
Все сертификаты в kubernetes
Список корневых сертификатов
var caList = []Config{
{
Path: BasePath,
BaseName: "ca",
CommonName: "kubernetes",
Organization: nil,
Year: 100,
AltNames: AltNames{},
Usages: nil,
},
{
Path: BasePath,
BaseName: "front-proxy-ca",
CommonName: "front-proxy-ca",
Organization: nil,
Year: 100,
AltNames: AltNames{},
Usages: nil,
},
{
Path: EtcdBasePath,
BaseName: "ca",
CommonName: "etcd-ca",
Organization: nil,
Year: 100,
AltNames: AltNames{},
Usages: nil,
},
}
Список других сертификатов подписи
var certList = []Config{
{
Path: BasePath,
BaseName: "apiserver",
CAName: "kubernetes",
CommonName: "kube-apiserver",
Organization: nil,
Year: 100,
AltNames: AltNames{// 实际安装时还需要把服务器IP用户自定义域名加上
DNSNames: []string{
"apiserver.cluster.local",
"localhost",
"master",
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
},
IPs: []net.IP{
{127,0,0,1},
},
},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, // 用途是服务端校验
},
{
Path: BasePath,
BaseName: "apiserver-kubelet-client",
CAName: "kubernetes",
CommonName: "kube-apiserver-kubelet-client",
Organization: []string{"system:masters"},
Year: 100,
AltNames: AltNames{},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
{
Path: BasePath,
BaseName: "front-proxy-client",
CAName: "front-proxy-ca",
CommonName: "front-proxy-client",
Organization: nil,
Year: 100,
AltNames: AltNames{},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
{
Path: BasePath,
BaseName: "apiserver-etcd-client",
CAName: "etcd-ca",
CommonName: "kube-apiserver-etcd-client",
Organization: []string{"system:masters"},
Year: 100,
AltNames: AltNames{},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
{
Path: EtcdBasePath,
BaseName: "server",
CAName: "etcd-ca",
CommonName: "etcd", // kubeadm etcd server证书common name使用节点名,这也是调用时需要改动的
Organization: nil,
Year: 100,
AltNames: AltNames{}, // 调用时需要把节点名,节点IP等加上
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
},
{
Path: EtcdBasePath,
BaseName: "peer",
CAName: "etcd-ca",
CommonName: "etcd-peer", // 与etcd server同理
Organization: nil,
Year: 100,
AltNames: AltNames{}, // 与etcd server同理
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
},
{
Path: EtcdBasePath,
BaseName: "healthcheck-client",
CAName: "etcd-ca",
CommonName: "kube-etcd-healthcheck-client",
Organization: []string{"system:masters"},
Year: 100,
AltNames: AltNames{},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
}
Важно отметить, что IP-адрес и доменное имя должны быть добавлены к установке сертификата проверки на стороне сервера, а общее имя etcd также должно быть установлено на имя узла.
Посмотрите на информацию о последнем сгенерированном сертификате:
apiserver:
[root@iZ2ze4ry74x8bh3cweeg69Z pki]# openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout
Certificate:
...
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=kubernetes
Validity
Not Before: Mar 31 09:18:06 2020 GMT
Not After : Mar 8 09:18:06 2119 GMT
Subject: CN=kube-apiserver
...
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Subject Alternative Name:
DNS:iz2ze4ry74x8bh3cweeg69z, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:apiserver.cluster.local, DNS:apiserver.cluster.local, IP Address:10.96.0.1, IP Address:172.16.9.192, IP Address:127.0.0.1, IP Address:172.16.9.192, IP Address:172.16.9.193, IP Address:172.16.9.194, IP Address:10.103.97.2
Signature Algorithm: sha256WithRSAEncryption
etcd server:
[root@iZ2ze4ry74x8bh3cweeg69Z pki]# openssl x509 -in /etc/kubernetes/pki/etcd/server.crt -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1930981199811083392 (0x1acc392ba2b27c80)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=etcd-ca
Validity
Not Before: Mar 31 09:18:07 2020 GMT
Not After : Mar 8 09:18:07 2119 GMT
Subject: CN=iz2ze4ry74x8bh3cweeg69z
...
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Subject Alternative Name:
DNS:iz2ze4ry74x8bh3cweeg69z, DNS:localhost, IP Address:172.16.9.192, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
Signature Algorithm: sha256WithRSAEncryption
Генерация сертификата пользователя и kubeconfig
Сейчас в компанию пришел стажер Fanux, и он хочет использовать k8s.Решительно боится передать ему админский kubeconfig.Что делать? Опираясь на приведенную выше основу, я научу вас, как назначить отдельный kubeconfig для fanux.
-
Загрузить корневой сертификат и закрытый ключ с диска
-
Создайте сертификат для пользователя fanux, обычное имя fanux
-
Кодировать в формат pem
-
записать kubeconfig, записать диск
func GenerateKubeconfig(conf Config) error{ certs, err := cert.CertsFromFile(conf.CACrtFile) caCert := certs[0] cert := EncodeCertPEM(caCert) caKey,err := TryLoadKeyFromDisk(conf.CAKeyFile) // 这里conf.User就是fanux, conf.Groups就是用户组,可以是多个 clientCert,clientKey,err := NewCertAndKey(caCert,caKey,conf.User,conf.Groups,conf.DNSNames,conf.IPAddresses) encodedClientKey,err := keyutil.MarshalPrivateKeyToPEM(clientKey) encodedClientCert := EncodeCertPEM(clientCert) // 构建kubeconfig的三元组信息 config := &api.Config{ Clusters: map[string]*api.Cluster{ conf.ClusterName: { Server: conf.Apiserver, // 集群地址 如 https://apiserver.cluster.local:6443 CertificateAuthorityData: cert, // pem格式的根证书,用于https }, }, Contexts: map[string]*api.Context{ ctx: { // 三元组信息,用户名 fanux, 上面的cluster名,以及namespace这里没写 Cluster: conf.ClusterName, AuthInfo: conf.User, }, }, AuthInfos: map[string]*api.AuthInfo{ // 用户信息, 所以你直接改kubeconfig里的user是没用的,因为k8s只认证书里的名字 conf.User:&api.AuthInfo{ ClientCertificateData: encodedClientCert, // pem格式的用户证书 ClientKeyData: encodedClientKey, // pem格式的用户私钥 }, }, CurrentContext: ctx, // 当前上下文, kubeconfig可以很好支持多用户和多集群 } err = clientcmd.WriteToFile(*config, conf.OutPut) return nil }
Генерируются сертификат пользователя и закрытый ключ.Как и в подписанном сертификате выше, пользователь — это fanux, а группа — это группа пользователей:
func NewCertAndKey(caCert *x509.Certificate, caKey crypto.Signer, user string, groups []string, DNSNames []string,IPAddresses []net.IP) (*x509.Certificate, crypto.Signer, error) {
key,err := rsa.GenerateKey(rand.Reader, 2048)
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
certTmpl := x509.Certificate{
Subject: pkix.Name{
CommonName: user,
Organization: groups,
},
DNSNames: DNSNames,
IPAddresses: IPAddresses,
SerialNumber: serial,
NotBefore: caCert.NotBefore,
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 99).UTC(),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
certDERBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, key.Public(), caKey)
cert,err := x509.ParseCertificate(certDERBytes)
return cert,key,nil
}
Затем генерируется kubeconfig этого маленького друга, а разрешения в это время нет:
kubectl --kubeconfig ./kube/config get pod
Error from server (Forbidden): pods is forbidden: User "fanux" cannot list resource "pods" in API group ...
Наконец, вы можете использовать RBAC, здесь вы можете напрямую привязать права администратора.
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: user-admin-test
subjects:
- kind: User
name: "fanux" # Name is case sensitive
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin # using admin role
apiGroup: rbac.authorization.k8s.io
Суммировать
Принципы аутентификации сертификатов и k8s очень полезны при установке кластера и разработке мультитенантных контейнерных платформ.Я надеюсь, что эта статья даст вам всестороннее и подробное понимание.
```
Эта статья опубликована в блогеOpenWriteвыпускать!