Pular para o conteúdo principal

Kubernetes TLS

Entendido como funcionam os certificados, CA e chaves privadas, podemos seguir em frente.

Um detalhe importante é que eu estava usando um cluster local k3d para estudar e este não segue algumas diretrizes que devemos conhecer por aqui. Recomendo instalar um cluster Kind.

Toda a comunicação entre os nodes do Kubernetes, sejam masters e workers, precisam ser seguras e devem ser criptografadas. Todas as interações entre todos os seus serviços e clientes precisam ser seguras.

Um admin que interage com o cluster por meio da api ou do kubectl precisa estabelecer segurança para firmar a conexão.

Da mesma forma, toda comunicação entre todos os componentes que existem no Kubernetes precisa estar protegida. É necessário conhecer quem fala com quem para compreender quem precisa de certificado.

comunication

Vários certificados existem por aqui.

Podemos observar que podemos chamar de somente clientes:

  • Scheduler
  • Controller Manager
  • Kube Proxy
  • Admin (mas não é um componente)

Somente server:

  • Etcd

Cliente e Servidor:

  • kube-apiserver
  • Kubelet

No caso do kube-apiserver e do Kubelet é possível usar o mesmo certificado como cliente e servidor, não teria problema, mas nada impede de criar um certificado cliente para cada uma das comunicações que ele faz.

Nesse caso teríamos no kube-api:

  • pair como servidor
  • pair como cliente para o etcd
  • pair como cliente para o Kubelet

No Kubelet:

  • Pair como server
  • pair como cliente para o kube-apiserver

E para finalizar precisamos ter PELO MENOS uma autoridade certificadora que assine todos esses certificados.

Alt text

Por exemplo se tivéssemos duas autoridades certificadoras uma para o ETCD.

Alt text

Lembrando que cada CA possui sua própria chave pública e privada.

Gerando Certificados

Existem diferentes ferramentas disponíveis para ajudar nesse trabalho de gerar certificados.

  • easyrsa
  • openssl
  • cfssl

CA Certificate

A primeira coisa que precisamos é resolver a CA que irá assinar todos os outros certificados. Depois gerar uma CSR que será o certificado mas sem a assinatura e depois assinar com a própria chave privada gerada.

genrsa -out ca.key 2048
openssl req -new -key ca.key -out ca.csr -subj "/CN=KUBERNETES-CA"
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
Certificate request self-signature ok
subject=CN = KUBERNETES-CA

cat ca.key
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDS8msBzUP5PK78
R5YcmrnJmNQX3fomyEYeOcER5k6JViKDhrqWr7kQVXFGfmgwRh7kBIQGHg1eujkf
ZqQci0NGGDJQ+6idUhW4BIITKcs4OGVfoH5W8U1Ulf4RxQmqzVK23mE+sWWM/gKW
b+uBnANfsbA7uMWmjJUlVdqdCkrsAS1JEXPE6RNoz9akOvQXxLsfFSopd+9/GRtC
hopGY2XCniNlievOeiT4u+nWZ9xnQmNt8WIK1hB+lIsCQ0lsv+BOjjObOz/NtACC
c2pUhXWAX8xvp0dYBML7ClpV34IehCMtpVuergKU4WAypE17fC5HeUcCEjslYXer
h9dRWWmNAgMBAAECggEACidN/EO3kl8X4x/E+3J9H9WB2/Y8zLIWIe1QvD5UKGsj
dSY+g/NIWp9qOOTrueseIIaRHkRDC3y2NCs6Gf+gCNcwUNKULqJdVkKtSiPYWsfo
JT6x5hhuSvR/SrarvPNuGExC7QQX6b3uPPR9Jvtt9CS24WEG7nnU0fu8bpOowIxF
iloJDemwd+N5uJrpYQLDw7xNl4JtZ3JiIAM1CZ+WcCWaNTbFJPYc+35LWurKDuPx
yoMq0aJj4RAnWQGoPaZK3okFYLxsx5s357kRIdaRloesvS6Gsll0GI1Pkso61diP
0LlO1q9yXhF9n2pOnCSyUztwc3TWOkLsh/aPJqaaEQKBgQDafBplPJFmpy5mArN4
LYohs75dsIqwThEG0QTSgMjwaBNifrH2U4c8bH7/ulDWf+37QcAnxhAubY90MFVU
YS24lXx+fjWgVj16XobK9AkcAyGnofeLxwIQ4w6lnxTqVENXtlvtUnUaqv1iAZ7x
rfrnacNu2ReJ1ZSr0e4oc8TmhQKBgQD3KvnIddw1dCx9ABp0fuQl5Y0lMFVAqZpa
nAEgG+Z+oHPrpOhncTCCPqHh2v4mwaItn6MZqS8ZF7ZgiQ2L7Q3p0QS62rw3yj14
y5ct5A980uEem1l514VmAu5tVLucICmxg5y68roZQQVU6X1NUiBM6vUhKp6bzEcc
sD69qLp5aQKBgEZgL0hoyBUeyM1HFXQihxnwAeO/2AyOOOHPNhRwM0ls3MGfOce0
hB0sfYP2Cc/uCUQjm7e0DsZFuC2e2/2AB/ArBpzgHnn1DXx7MkPxc3W4dIDApTI6
+iliWfdzVJNebQq1zMqXAu2CIngNOZtzhRxBbxgniXN2fpsR1frlv4jZAoGBALZI
xpB1g6MUe57wjZIu5vmf8tljbOxUaNa8SQQyL2ph0TwPkaqASsMzh9X22QsiMQYu
vm21XVvHJiTJzujMOj+ffDqGCNuoVm+YznT0xgtLxuT4syNttB8IuGh/XiGFgto8
80DYtDDdyB3YWSXGFLZFbeU49mJ2ZFefD5Z2MphJAoGAe2qARSSS7HLZbCFyE2a9
S5WmTUy8mJKRRnI1Ziml0ES0HBLisZ9mwyp/Px+KAyz4oGwtGvNNPXtlh3g9KI/0
BcVa1qSDgC0mwk+mPZFVYpql/U4qCSX6ndIserseLMXzEY/PDt4acmquAp/poTVt
4U9H8I+IFvYBpSeu7BydEtk=
-----END PRIVATE KEY-----

cat ca.csr
-----BEGIN CERTIFICATE REQUEST-----
MIICXTCCAUUCAQAwGDEWMBQGA1UEAwwNS1VCRVJORVRFUy1DQTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBANLyawHNQ/k8rvxHlhyaucmY1Bfd+ibIRh45
wRHmTolWIoOGupavuRBVcUZ+aDBGHuQEhAYeDV66OR9mpByLQ0YYMlD7qJ1SFbgE
ghMpyzg4ZV+gflbxTVSV/hHFCarNUrbeYT6xZYz+ApZv64GcA1+xsDu4xaaMlSVV
2p0KSuwBLUkRc8TpE2jP1qQ69BfEux8VKil3738ZG0KGikZjZcKeI2WJ6856JPi7
6dZn3GdCY23xYgrWEH6UiwJDSWy/4E6OM5s7P820AIJzalSFdYBfzG+nR1gEwvsK
WlXfgh6EIy2lW56uApThYDKkTXt8Lkd5RwISOyVhd6uH11FZaY0CAwEAAaAAMA0G
CSqGSIb3DQEBCwUAA4IBAQC6Ra53Au+cfLfHAp81P5OShqkMzVFGeS8I+JIX2P69
v+h2+J/gMkYI6AyGV6Nj5Y/FVRNrHSDC4DjZ7VlbJ4QYNSOiZejCZrj6GykusYmC
9clJkL1cw1RdI+nETjm6CPV2zFsN7BtPb560z3FgGcVWrAhltqguNZvCPRkom8Xo
LAYPC2+IAN0uQbSDlEhSu5Uty7aaAO+Q74NHVHk4RuiZYk7fUsOUdb43pK3dBLcp
oP1a3XrEpzJozV+n/KKntnAEZ7P2LCYNtSsVr4kbXODCGcdhtxjpKNby81O8aC9K
WsfHqUszqKcxOOHSnb4cP6us0hDzJjmN4nszoa2wFOUm
-----END CERTIFICATE REQUEST-----

cat ca.crt
-----BEGIN CERTIFICATE-----
MIICtzCCAZ8CFAWTsi01pUIDnIam8zmGDzTRMpfIMA0GCSqGSIb3DQEBCwUAMBgx
FjAUBgNVBAMMDUtVQkVSTkVURVMtQ0EwHhcNMjQwMjA1MDM1NjAzWhcNMjQwMzA2
MDM1NjAzWjAYMRYwFAYDVQQDDA1LVUJFUk5FVEVTLUNBMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA0vJrAc1D+Tyu/EeWHJq5yZjUF936JshGHjnBEeZO
iVYig4a6lq+5EFVxRn5oMEYe5ASEBh4NXro5H2akHItDRhgyUPuonVIVuASCEynL
ODhlX6B+VvFNVJX+EcUJqs1Stt5hPrFljP4Clm/rgZwDX7GwO7jFpoyVJVXanQpK
7AEtSRFzxOkTaM/WpDr0F8S7HxUqKXfvfxkbQoaKRmNlwp4jZYnrznok+Lvp1mfc
Z0JjbfFiCtYQfpSLAkNJbL/gTo4zmzs/zbQAgnNqVIV1gF/Mb6dHWATC+wpaVd+C
HoQjLaVbnq4ClOFgMqRNe3wuR3lHAhI7JWF3q4fXUVlpjQIDAQABMA0GCSqGSIb3
DQEBCwUAA4IBAQAVdpOPie+f7E2jDiubID0l/iPbvp//y/tQ6JRy5lOUbzsS6hEI
bodZwMnjpl6+YXTU9hp/hOWBavN5l3FfESnC3zJIWmzwug+FVpTHCEdLCJtFrnOX
KC4GoZA3NU899tuWguIxe6gc4J5wODCEhCWFq9C709tMCAbovrM5SRPNRb6B+5/s
R2Cl6z+1IzbM6ngp0+Qyv+o3WosaxVLDodoGFhPWTrYwoHR6v8ezz7uRB1jAEewO
kyv2ip/iKEe9RnOLsPllgiPPOOHr93wCHQo1t4L64oQ54oXChZcdD06QDHOzZMJY
CwhCDdMm1hTef6UvZiGHU3s6yxrwiiraBOX0
-----END CERTIFICATE-----

Clients Certificates

Daqui para frente o que vamos fazer? Criar todas as chaves privadas, os certificados e assinar com a chave privada do ca.

#admin nesse caso precisamos passar o grupo que ele será usado depois com o /O Veremos mais para frente
openssl genrsa -out admin.key 2048
openssl req -new -key admin.key -out admin.csr -subj "/CN=kube-admin/O=system:masters"
openssl x509 -req -in admin.csr -signkey ca.key -out admin.crt

# Todos os componentes do sistema precisam receber o prefixo system
openssl genrsa -out scheduler.key 2048
openssl req -new -key scheduler.key -out scheduler.csr -subj "/CN=system:kube-scheduler"
openssl x509 -req -in scheduler.csr -signkey ca.key -out scheduler.crt

openssl genrsa -out controller-manager.key 2048
openssl req -new -key controller-manager.key -out controller-manager.csr -subj "/CN=system:kube-controller-manager"
openssl x509 -req -in controller-manager.csr -signkey ca.key -out controller-manager.crt

openssl genrsa -out kube-proxy.key 2048
openssl req -new -key kube-proxy.key -out kube-proxy.csr -subj "/CN=system:kube-proxy"
openssl x509 -req -in kube-proxy.csr -signkey ca.key -out kube-proxy.crt

Legal, e para que usamos esses certificados?

Por exemplo o admin pode ser usado para acessar o cluster através da api.

Por exemplo esse kubeconfig aqui.

apiVersion: v1
clusters:
- cluster:
# é o nosso ca
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTURRNE1qZzJORGt3SGhjTk1qUXdNVEE1TVRrek1EUTVXaGNOTXpRd01UQTJNVGt6TURRNQpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTURRNE1qZzJORGt3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUMnFnUTR6b3JjeWVLc2xwRWFOREtyMDdtVmdVcDVieEFNUHgreHRjUlUKYUg3c1Q2YnBKTHdIVG15RERtc3VhM1JZdmgzOU9Zc29Dbk9qd0JrdStuUExvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVThqUHhlTm5nSGZpR2FwVmwyc09DCk1ucmRoQUF3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQUlXUnVMbS93d2R0SVBmcUZ5ZjMxN2ZKbVliU3cvRisKaThydDFDaHQvUDhnQWlFQXc2Q0tSS1RMR3Zic084aEp4LzY2R1RHc2ZwdGZicUg4bE1iTjVuUWlJVjA9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: https://0.0.0.0:42861
name: k3d-k3s-default
contexts:
- context:
cluster: k3d-k3s-default
user: admin@k3d-k3s-default
name: k3d-k3s-default
current-context: k3d-k3s-default
kind: Config
preferences: {}
users:
- name: admin@k3d-k3s-default
user:
#Certificado
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrRENDQVRlZ0F3SUJBZ0lJTFZYS2lBbDBjd1l3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOekEwT0RJNE5qUTVNQjRYRFRJME1ERXdPVEU1TXpBME9Wb1hEVEkxTURFdwpPREU1TXpBME9Wb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJJeGlJYitxTGh0T2k2WmYKYUVpb2gyaklvZDFUUHJBUlhvQUFFMEFiVE9TUDF6R3U5RkNoMXFPWk5uL0VCclpMeVNCenN6QUpvM0dMcTJJUwpDMkowdlFTalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCVHF5RkZyS21jcGVLanVid2ZBKzliZTk5WlZ6REFLQmdncWhrak9QUVFEQWdOSEFEQkUKQWlCUnVIQXNTVmFlOFJkbUd1K2QxVFNjUkdnSlk4VEo1V3IwYXJLcWoweFY5Z0lnVHI0c3dHb2p3UXh5dUNBTQpDZ01RM1EyNFZGcVY0UHpXYk9sQ2hDVDdkU2c9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTURRNE1qZzJORGt3SGhjTk1qUXdNVEE1TVRrek1EUTVXaGNOTXpRd01UQTJNVGt6TURRNQpXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTURRNE1qZzJORGt3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSc3pLZ2xRSE9veUZpZk1KWlV2SlVCeHRKSHdiQmN6L3N1YWk0aTQycnYKTVNDZ0JHeDRNVmNRTWIycVhQeERjRjBDRjdLMXZVRGYwbFRaZVRQSnBleUJvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVTZzaFJheXBuS1hpbzdtOEh3UHZXCjN2ZldWY3d3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQUpkajdaaUsvMzB2dG9JQU1VL1JPeTdmMXNGUVFrWWIKbUw4a1J2V05vK1h5QWlBVjlkdWh2OWdibWs2aWtsQUVNU2t6dnkwQU9Mb2t4bk5acG1PVFB4Y1kvZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
#Private key
client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUZ2TU0wZG5kUk5XeDFVZU4zZVREMWdjMVAvYkxLYXZkc2ZXcExjRWpZaFpvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFakdJaHY2b3VHMDZMcGw5b1NLaUhhTWloM1ZNK3NCRmVnQUFUUUJ0TTVJL1hNYTcwVUtIVwpvNWsyZjhRR3RrdkpJSE96TUFtamNZdXJZaElMWW5TOUJBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=

No kubeconfig esse valores estão encriptados em base64, vamos decodificá-los e colocar em arquivo para usar.

echo "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTURRNE1qZzJORGt3SGhjTk1qUXdNVEE1TVRrek1EUTVXaGNOTXpRd01UQTJNVGt6TURRNQpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTURRNE1qZzJORGt3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUMnFnUTR6b3JjeWVLc2xwRWFOREtyMDdtVmdVcDVieEFNUHgreHRjUlUKYUg3c1Q2YnBKTHdIVG15RERtc3VhM1JZdmgzOU9Zc29Dbk9qd0JrdStuUExvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVThqUHhlTm5nSGZpR2FwVmwyc09DCk1ucmRoQUF3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQUlXUnVMbS93d2R0SVBmcUZ5ZjMxN2ZKbVliU3cvRisKaThydDFDaHQvUDhnQWlFQXc2Q0tSS1RMR3Zic084aEp4LzY2R1RHc2ZwdGZicUg4bE1iTjVuUWlJVjA9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" | base64 --decode > ca.crt

cat ca.crt
-----BEGIN CERTIFICATE-----
MIIBeDCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
dmVyLWNhQDE3MDQ4Mjg2NDkwHhcNMjQwMTA5MTkzMDQ5WhcNMzQwMTA2MTkzMDQ5
WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE3MDQ4Mjg2NDkwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAAT2qgQ4zorcyeKslpEaNDKr07mVgUp5bxAMPx+xtcRU
aH7sT6bpJLwHTmyDDmsua3RYvh39OYsoCnOjwBku+nPLo0IwQDAOBgNVHQ8BAf8E
BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8jPxeNngHfiGapVl2sOC
MnrdhAAwCgYIKoZIzj0EAwIDSQAwRgIhAIWRuLm/wwdtIPfqFyf317fJmYbSw/F+
i8rt1Cht/P8gAiEAw6CKRKTLGvbsO8hJx/66GTGsfptfbqH8lMbN5nQiIV0=
-----END CERTIFICATE-----

echo "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrRENDQVRlZ0F3SUJBZ0lJTFZYS2lBbDBjd1l3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOekEwT0RJNE5qUTVNQjRYRFRJME1ERXdPVEU1TXpBME9Wb1hEVEkxTURFdwpPREU1TXpBME9Wb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJJeGlJYitxTGh0T2k2WmYKYUVpb2gyaklvZDFUUHJBUlhvQUFFMEFiVE9TUDF6R3U5RkNoMXFPWk5uL0VCclpMeVNCenN6QUpvM0dMcTJJUwpDMkowdlFTalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCVHF5RkZyS21jcGVLanVid2ZBKzliZTk5WlZ6REFLQmdncWhrak9QUVFEQWdOSEFEQkUKQWlCUnVIQXNTVmFlOFJkbUd1K2QxVFNjUkdnSlk4VEo1V3IwYXJLcWoweFY5Z0lnVHI0c3dHb2p3UXh5dUNBTQpDZ01RM1EyNFZGcVY0UHpXYk9sQ2hDVDdkU2c9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTURRNE1qZzJORGt3SGhjTk1qUXdNVEE1TVRrek1EUTVXaGNOTXpRd01UQTJNVGt6TURRNQpXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTURRNE1qZzJORGt3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSc3pLZ2xRSE9veUZpZk1KWlV2SlVCeHRKSHdiQmN6L3N1YWk0aTQycnYKTVNDZ0JHeDRNVmNRTWIycVhQeERjRjBDRjdLMXZVRGYwbFRaZVRQSnBleUJvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVTZzaFJheXBuS1hpbzdtOEh3UHZXCjN2ZldWY3d3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQUpkajdaaUsvMzB2dG9JQU1VL1JPeTdmMXNGUVFrWWIKbUw4a1J2V05vK1h5QWlBVjlkdWh2OWdibWs2aWtsQUVNU2t6dnkwQU9Mb2t4bk5acG1PVFB4Y1kvZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" | base64 --decode > admin.crt

cat admin.crt
-----BEGIN CERTIFICATE-----
MIIBkDCCATegAwIBAgIILVXKiAl0cwYwCgYIKoZIzj0EAwIwIzEhMB8GA1UEAwwY
azNzLWNsaWVudC1jYUAxNzA0ODI4NjQ5MB4XDTI0MDEwOTE5MzA0OVoXDTI1MDEw
ODE5MzA0OVowMDEXMBUGA1UEChMOc3lzdGVtOm1hc3RlcnMxFTATBgNVBAMTDHN5
c3RlbTphZG1pbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIxiIb+qLhtOi6Zf
aEioh2jIod1TPrARXoAAE0AbTOSP1zGu9FCh1qOZNn/EBrZLySBzszAJo3GLq2IS
C2J0vQSjSDBGMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjAf
BgNVHSMEGDAWgBTqyFFrKmcpeKjubwfA+9be99ZVzDAKBggqhkjOPQQDAgNHADBE
AiBRuHAsSVae8RdmGu+d1TScRGgJY8TJ5Wr0arKqj0xV9gIgTr4swGojwQxyuCAM
CgMQ3Q24VFqV4PzWbOlChCT7dSg=
-----END CERTIFICATE-----

echo "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUZ2TU0wZG5kUk5XeDFVZU4zZVREMWdjMVAvYkxLYXZkc2ZXcExjRWpZaFpvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFakdJaHY2b3VHMDZMcGw5b1NLaUhhTWloM1ZNK3NCRmVnQUFUUUJ0TTVJL1hNYTcwVUtIVwpvNWsyZjhRR3RrdkpJSE96TUFtamNZdXJZaElMWW5TOUJBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=" | base64 --decode > admin.key

cat admin.key
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIFvMM0dndRNWx1UeN3eTD1gc1P/bLKavdsfWpLcEjYhZoAoGCCqGSM49
AwEHoUQDQgAEjGIhv6ouG06Lpl9oSKiHaMih3VM+sBFegAATQBtM5I/XMa70UKHW
o5k2f8QGtkvJIHOzMAmjcYurYhILYnS9BA==
-----END EC PRIVATE KEY-----

Agora com o comando curl https://0.0.0.0:42861/api/v1/pods --key admin.key --cert admin.crt --cacert ca.crt podemos ter informações sobre os pods usando somente a api.

O kubectl utiliza os certificados como podemos ver observar, mas direto no arquivo. seria como se tivéssemos.

apiVersion: v1
clusters:
- cluster:
# é o nosso ca
certificate-authority-data: ca.crt
server: https://0.0.0.0:42861
name: k3d-k3s-default
contexts:
- context:
cluster: k3d-k3s-default
user: admin@k3d-k3s-default
name: k3d-k3s-default
current-context: k3d-k3s-default
kind: Config
preferences: {}
users:
- name: admin@k3d-k3s-default
user:
#Certificado
client-certificate-data: admin.crt
#Private key
client-key-data: admin.key

Uma cópia do ca.crt é necessário estar em todos os componentes, pois eles precisam acreditar nessa autoridade. Estamos assinando com a chave privada do ca, esta não precisa estar dentro de cada componente, mas o certificado que contém as informações e a chave pública é necessário.

alt text

Server Certificates

Agora vamos para o certificados do servidores.

No caso do ETCD temos.

openssl genrsa -out etcdserver.key 2048
openssl req -new -key etcdserver.key -out etcdserver.csr -subj "/CN=etcd-server"
openssl x509 -req -in etcdserver.csr -signkey ca.key -out etcdserver.crt

Mas o etcd também é um cluster, deployado dentro dos master ou fora, logo temos os certificados de cada um dos seus membros. Não precisamos mostrar aqui agora, mas vamos estudar sobre isso mais tarde quando criar um cluster do zero.

alt text

Esses certificados são necessários na configuração de cada membro do etcd.

alt text

O kube-apiserver é o ponto central do Kubernetes já que será o ponto de entrada para o cluster e alguns parâmetros a mais são necessários. O kube-apiserver é chamado de vários nomes então precisamos passar mais aliases para ele. Quem não conhece o kubernetes e o que

No certificado é necessário ter:

  • Todos os aliases e somente através desses nomes que poderemos fazer uma conexão válida
    • kubernetes
    • kubernetes.default
    • kubernetes.default.svc
    • kubernetes.default.svc.cluster.local
  • Ip do pod
  • Ip do host
openssl genrsa -out apiserver.key 2048
# Criando um arquivo de configuração para ficar mais fácil no próximo comando
echo "
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation
subjectAltName = @alt_names

[alt_names]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster.local
IP.1 = 10.96.0.1
IP.2 = 172.17.0.87

[req_distinguished_name]" > openssl.cnf


openssl req -new -key apiserver.key -out apiserver.csr -subj "/CN=kube-apiserver" --config openssl.cnf
openssl x509 -req -in apiserver.csr -signkey ca.key -out apiserver.crt

Na configuração do kube-apiserver temos os seguintes parâmetros para passar, lembrando que o kube-apiserver precisa se comunicar com o etcd e com o Kubelet dos hosts. Ainda não foi mostrado aqui, mas seria necessário gerar mais certificados, mas também poderia ser usado o mesmo certificado, é uma questão de configuração na montagem do cluster.

alt text

Por ultimo temos o Kubelet que roda em cada um dos worker nodes.

Cada Kubelet dentro de cada node tem o seu próprio certificado, imagina como cada node sendo um user do sistema. A diferença é que o CN possuirá o nome do worker e qual grupo:role o kube-apiserver dará a este node. É necessário que esteja no grupo system:node

Os nomes abaixo poderiam ser todos worker.key worker.scr. worker.crt, para que a configuração do Kubelet dentro de cada node seja igual e facilitar automação. Coloquei de forma diferente só para ilustração.

# Usado no node 1
openssl genrsa -out worker1.key 2048
openssl req -new -key worker1.key -out worker1.csr -subj "/CN=system:node:worker-1"
openssl x509 -req -in worker1.csr -signkey ca.key -out worker1.crt

# Usado no node 2
openssl genrsa -out worker2.key 2048
openssl req -new -key worker2.key -out worker2.csr -subj "/CN=system:node:worker-2"
openssl x509 -req -in worker2.csr -signkey ca.key -out worker2.crt

# Usado no node 3
openssl genrsa -out worker3.key 2048
openssl req -new -key worker3.key -out worker3.csr -subj "/CN=system:node:worker-3"
openssl x509 -req -in worker3.csr -signkey ca.key -out worker3.crt

alt text


A boa notícia é que não precisamos gerar esses certificados automaticamente. Utilizando o kubeadm ele se encarrega de gerar esses certificados. Claro se quisermos instalar da maneira hard fazendo tudo manualmente você terá esse trabalho. Nós faremos isso mais para frente para aprender, mas não é usual no dia a dia.

O kubeadm cria os componentes como pods no kubernetes. Os manifestos estão em /etc/kubernetes/manifests em qualquer um dos nodes.

O arquivo do kube-apiserver está em kube-apiserver.yaml Na especificação do pod vamos ter isso:

cat /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 172.18.0.4:6443
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=172.18.0.4
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt #<<<<< CA Certification que precisa estar presente em todos os componentes
- --enable-admission-plugins=NodeRestriction
- --enable-bootstrap-token-auth=true
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt #<<<<< Nesse caso o etcd possui seu próprio ca
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt #<<<<<
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key #<<<<<
- --etcd-servers=https://127.0.0.1:2379
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt #<<<<< certificados para o kubelet
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key #<<<<<
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
- --requestheader-allowed-names=front-proxy-client
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --requestheader-extra-headers-prefix=X-Remote-Extra-
- --requestheader-group-headers=X-Remote-Group
- --requestheader-username-headers=X-Remote-User
- --runtime-config=
- --secure-port=6443
- --service-account-issuer=https://kubernetes.default.svc.cluster.local
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
- --service-cluster-ip-range=10.96.0.0/16
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt #<<<<< certificados do kube-apiserver
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key #<<<<<
image: registry.k8s.io/kube-apiserver:v1.29.1
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 8
httpGet:
host: 172.18.0.4
path: /livez
port: 6443
scheme: HTTPS
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 15
name: kube-apiserver
readinessProbe:
failureThreshold: 3
httpGet:
host: 172.18.0.4
path: /readyz
port: 6443
scheme: HTTPS
periodSeconds: 1
timeoutSeconds: 15
resources:
requests:
cpu: 250m
startupProbe:
failureThreshold: 24
httpGet:
host: 172.18.0.4
path: /livez
port: 6443
scheme: HTTPS
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 15
volumeMounts:
- mountPath: /etc/ssl/certs
name: ca-certs
readOnly: true
- mountPath: /etc/ca-certificates
name: etc-ca-certificates
readOnly: true
- mountPath: /etc/kubernetes/pki
name: k8s-certs
readOnly: true
- mountPath: /usr/local/share/ca-certificates
name: usr-local-share-ca-certificates
readOnly: true
- mountPath: /usr/share/ca-certificates
name: usr-share-ca-certificates
readOnly: true
hostNetwork: true
priority: 2000001000
priorityClassName: system-node-critical
securityContext:
seccompProfile:
type: RuntimeDefault
volumes:
- hostPath:
path: /etc/ssl/certs
type: DirectoryOrCreate
name: ca-certs
- hostPath:
path: /etc/ca-certificates
type: DirectoryOrCreate
name: etc-ca-certificates
- hostPath:
path: /etc/kubernetes/pki
type: DirectoryOrCreate
name: k8s-certs
- hostPath:
path: /usr/local/share/ca-certificates
type: DirectoryOrCreate
name: usr-local-share-ca-certificates
- hostPath:
path: /usr/share/ca-certificates
type: DirectoryOrCreate
name: usr-share-ca-certificates
status: {}
root@kind-cluster-control-plane:/etc/kubernetes/manifests#

Observe que também temos os pontos de montagem, portas, etc, mas vamos focar no certificados.

Para analisar um certificado, podemos fazer.

openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout

Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4509957295476951933 (0x3e969643f515cb7d)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = kubernetes #<<<< Quem foi o emissor, no caso o CA.
Validity
Not Before: Feb 8 22:57:18 2024 GMT
Not After : Feb 7 23:02:18 2025 GMT #<<<< Validade
Subject: CN = kube-apiserver #<<< Veja o CN como mencionado anteriormente
Subject Public Key Info
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:b8:ed:ae:9e:e4:8b:c8:32:47:42:10:32:8f:68:
48:98:3d:da:5f:82:73:58:52:d7:ab:75:c6:1f:d2:
f6:35:5a:ee:d5:54:10:b2:7b:bf:27:26:ad:bb:e5:
55:34:a6:85:2e:76:37:75:d6:08:36:7f:05:80:66:
d5:c4:0a:f3:c5:e6:fd:6b:c9:c8:aa:80:b3:3a:06:
83:3a:b1:bd:04:4e:0a:5c:f2:f2:61:a6:ea:3f:94:
71:e0:98:4d:33:ba:9c:23:5b:cb:e3:32:75:a4:ad:
0a:bf:f8:ba:d0:68:44:d9:91:5a:db:f5:07:0a:77:
7e:09:e9:0b:2f:ad:1a:68:a6:95:b8:51:95:8e:68:
b3:22:2e:73:1a:6c:c3:3d:4e:ed:f7:95:4d:94:07:
5d:d7:86:ad:00:69:77:97:ee:aa:0c:68:08:ad:12:
21:ac:ed:e0:ac:74:d0:7e:43:50:ea:81:e6:61:a1:
39:12:5f:e9:2e:09:3d:9a:3c:80:14:71:85:66:eb:
5a:55:35:68:17:d1:62:93:f5:54:f8:af:08:2c:0f:
66:f8:a1:a0:0d:9a:f9:29:ef:7e:46:7d:2f:89:39:
87:55:0f:d5:72:70:19:29:57:24:e8:90:4f:c0:e7:
d9:36:25:be:85:3d:b4:00:e4:c0:68:b1:fb:4d:c6:
83:5d
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Authority Key Identifier:
1A:11:08:4F:C0:1C:F6:C1:63:A6:EB:A1:44:23:B1:E2:13:E5:75:26
X509v3 Subject Alternative Name: #<<<< Veja os nomes alternativos
DNS:kind-cluster-control-plane, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:localhost, IP Address:10.96.0.1, IP Address:172.18.0.4, IP Address:127.0.0.1
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
86:20:1a:1c:66:3e:71:61:b3:b7:fa:2a:81:2c:97:0d:ed:e8:
80:5b:85:9a:09:5f:e7:b5:bc:c8:14:0f:48:7f:48:bb:bf:76:
69:f9:b7:90:65:e8:60:42:ab:b6:d4:11:e0:b3:31:da:a8:be:
c0:8a:c9:68:80:35:47:a0:4f:7a:9f:55:d9:47:4d:f6:08:14:
15:30:97:74:e5:84:b8:2e:95:8e:a9:4a:db:28:6f:b6:38:11:
66:69:2a:7d:df:51:83:e6:ea:31:ef:51:f9:8a:9d:3a:08:05:
e4:57:6d:e0:9a:bc:e3:b5:64:e2:60:32:b1:14:db:79:bf:57:
14:4d:17:b0:ff:20:41:ae:af:0e:fe:dc:b9:7e:6f:cf:a6:f9:
d5:5c:22:01:84:85:f7:8e:25:f9:f7:43:8d:ae:8f:e0:3f:08:
02:a1:b6:63:d2:ce:f7:09:01:47:3d:ac:13:95:0f:35:e3:81:
75:7b:dc:72:2b:83:f7:1c:b2:bd:00:c7:19:6d:b6:c4:f6:65:
5e:f7:2e:ad:35:1e:a3:75:23:40:b0:12:33:66:f7:e5:cd:1e:
6d:ba:71:b2:34:b2:fe:f7:f9:04:8c:92:f3:e5:9b:a3:d2:fc:
cd:35:60:8f:1f:ec:1b:ee:4f:85:67:b9:b1:b5:d4:c9:45:1d:
ff:4c:0c:42

Para avaliar os certificados e procurar issuers podemos seguir o seguinte critério. Tentar fazer uma tabela para entender cada uma das coisas.

Esse cluster foi montado usando um kind e veja o que temos. No caso possuímos dois emissores um CA para o Kubernetes e um CA para o ETCD.

ComponentTypeCertification PathCN nameAlt NamesOrganizationIssuerExpiration
kube-apiserverserver/etc/kubernetes/pki/apiserver.crtkube-apiserverDNS:kind-cluster-control-plane, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:localhost, IP Address:10.96.0.1, IP Address:172.18.0.4, IP Address:127.0.0.1kubernetesFeb 7 23:02:18 2025
kube-apiserverserver/etc/kubernetes/pki/apiserver.key
kube-apiserverserver/etc/kubernetes/pki/ca.crtkuberneteskuberneteskubernetesFeb 5 23:02:18 2034
kube-apiserverClient (Kubelet)/etc/kubernetes/pki/apiserver-kubelet-client.crtkube-apiserver-kubelet-clientkubeadm:cluster-adminskubernetesFeb 7 23:02:18 2025
kube-apiserverClient (Kubelet)/etc/kubernetes/pki/apiserver-kubelet-client.key
kube-apiserverClient (ETCD)/etc/kubernetes/pki/etcd/ca.crtetcd-caetcd-caFeb 5 23:02:19 2034
kube-apiserverClient (ETCD)/etc/kubernetes/pki/apiserver-etcd-client.crtkube-apiserver-etcd-clientetcd-caFeb 7 23:02:19 2025
kube-apiserverClient (ETCD)/etc/kubernetes/pki/apiserver-etcd-client.key

Algumas coisas ficaram melhores entendidas um pouco mais para frente.

Analisando problemas

Para analisar algum problema com certificado.

Se os componentes estão rodando como serviços podemos usar o journalctl -u etcd.service -l ou o nome de outro serviço,

Se foi utilizando o kubeadm podemos olhar o log dos pods kubectl logs etcd-master -n kube-system ou algum outro componente. No caso do kind que estou usando aqui seria kubectl logs -n kube-system etcd-kind-cluster-control-plane

Não existe uma regra, existe entender o conceito das coisas.

Algumas vezes é ainda necessário descer um pouco mais o nível quando kube-apiserver ou o etcd está down ou quando o comando kubectl não está funcionando.

Uma vez que os pods estão rodando como containers, podemos pegar os logs dos containers diretamente.

Podemos usar os comandos do crictl dentro do node.

crictl ps -a
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
6dae0c6c57fa0 6b5c5e00213a4 About an hour ago Running shell 0 34919e32c8e1b node-shell-a9109e34-e137-45fa-951b-39727baa2b0a
036608ed6eb85 0500518ebaa68 About an hour ago Running local-path-provisioner 0 8cd8d56c7d9b8 local-path-provisioner-7577fdbbfb-5dd24
7900ba6877662 cbb01a7bd410d About an hour ago Running coredns 0 bc0e982a65273 coredns-76f75df574-jtn5f
8805e425edefe cbb01a7bd410d About an hour ago Running coredns 0 e773b051ffab2 coredns-76f75df574-v4rrs
154a74a2bbed1 4950bb10b3f87 About an hour ago Running kindnet-cni 0 e746cf8ee60e0 kindnet-b2jf6
ee0dde4c1f9b4 790e70e8e9faa About an hour ago Running kube-proxy 0 00fa8ba24b9df kube-proxy-v5xpk
3007ff5ee0c46 a0eed15eed449 About an hour ago Running etcd 0 c967bb98832b0 etcd-kind-cluster-control-plane
1fc6b9b833366 cb68e1e8c9a5e About an hour ago Running kube-apiserver 0 3be3cf5c10d16 kube-apiserver-kind-cluster-control-plane
f43291957b1d0 b685d6e28315d About an hour ago Running kube-controller-manager 0 fb16d56656241 kube-controller-manager-kind-cluster-control-plane
ec25b5a7dace6 6cffb863e51bc About an hour ago Running kube-scheduler 0 93cf8d8cff768 kube-scheduler-kind-cluster-control-plane

crictl logs 3007ff5ee0c46
{"level":"warn","ts":"2024-02-08T23:02:23.651875Z","caller":"embed/config.go:676","msg":"Running http and grpc server on single port. This is not recommended for production."}
{"level":"info","ts":"2024-02-08T23:02:23.652008Z","caller":"etcdmain/etcd.go:73","msg":"Running: ","args":["etcd","--advertise-client-urls=https://172.18.0.4:2379","--cert-file=/etc/kubernetes/pki/etcd/server.crt","--client-cert-auth=true","--data-dir=/var/lib/etcd","--experimental-initial-corrupt-check=true","--experimental-watch-progress-notify-interval=5s","--initial-advertise-peer-urls=https://172.18.0.4:2380","--initial-cluster=kind-cluster-control-plane=https://172.18.0.4:2380","--key-file=/etc/kubernetes/pki/etcd/server.key","--listen-client-urls=https://127.0.0.1:2379,https://172.18.0.4:2379","--listen-metrics-urls=http://127.0.0.1:2381","--listen-peer-urls=https://172.18.0.4:2380","--name=kind-cluster-control-plane","--peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt","--peer-client-cert-auth=true","--peer-key-file=/etc/kubernetes/pki/etcd/peer.key","--peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt","--snapshot-count=10000","--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt"]}
{"level":"warn","ts":"2024-02-08T23:02:23.652232Z","caller":"embed/config.go:676","msg":"Running http and grpc server on single port. This is not recommended for production."}
...