Skip to main content

Kubernetes TLS

Comprendido cómo funcionan los certificados, CA y claves privadas, podemos seguir adelante.

Un detalle importante es que estaba usando un clúster local k3d para estudiar y este no sigue algunas directrices que debemos conocer por aquí. Recomiendo instalar un clúster Kind.

Toda la comunicación entre los nodos de Kubernetes, ya sean masters y workers, necesitan ser seguras y deben estar cifradas. Todas las interacciones entre todos tus servicios y clientes necesitan ser seguras.

Un admin que interactúa con el clúster por medio de la api o del kubectl necesita establecer seguridad para firmar la conexión.

De la misma forma, toda comunicación entre todos los componentes que existen en Kubernetes necesita estar protegida. Es necesario conocer quién habla con quién para comprender quién necesita de certificado.

comunication

Varios certificados existen por aquí.

Podemos observar que podemos llamar de solamente clientes:

  • Scheduler
  • Controller Manager
  • Kube Proxy
  • Admin (pero no es un componente)

Solamente server:

  • Etcd

Cliente y Servidor:

  • kube-apiserver
  • Kubelet

En el caso del kube-apiserver y del Kubelet es posible usar el mismo certificado como cliente y servidor, no habría problema, pero nada impide crear un certificado cliente para cada una de las comunicaciones que hace.

En ese caso tendríamos en el kube-api:

  • par como servidor
  • par como cliente para el etcd
  • par como cliente para el Kubelet

En el Kubelet:

  • Par como server
  • par como cliente para el kube-apiserver

Y para finalizar necesitamos tener AL MENOS una autoridad certificadora que firme todos esos certificados.

Alt text

Por ejemplo si tuviéramos dos autoridades certificadoras una para el ETCD.

Alt text

Recordando que cada CA posee su propia clave pública y privada.

Generando Certificados

Existen diferentes herramientas disponibles para ayudar en este trabajo de generar certificados.

  • easyrsa
  • openssl
  • cfssl

CA Certificate

Lo primero que necesitamos es resolver la CA que firmará todos los otros certificados. Después generar un CSR que será el certificado pero sin la firma y después firmar con la propia clave privada generada.

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+Apv64GcA1+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

De aquí en adelante ¿qué vamos a hacer? Crear todas las claves privadas, los certificados y firmar con la clave privada del ca.

# admin en este caso necesitamos pasar el grupo que será usado después con el /O Veremos más adelante
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 los componentes del sistema necesitan recibir el prefijo 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

¿Genial, y para qué usamos esos certificados?

Por ejemplo el admin puede ser usado para acceder al clúster a través de la api.

Por ejemplo este kubeconfig aquí.

apiVersion: v1
clusters:
- cluster:
# es nuestro 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=

En el kubeconfig esos valores están encriptados en base64, vamos a decodificarlos y colocarlos en archivo 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-----

Ahora con el comando curl https://0.0.0.0:42861/api/v1/pods --key admin.key --cert admin.crt --cacert ca.crt podemos tener informaciones sobre los pods usando solamente la api.

El kubectl utiliza los certificados como podemos observar, pero directo en el archivo. sería como si tuviéramos.

apiVersion: v1
clusters:
- cluster:
# es nuestro 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

Una copia del ca.crt es necesario estar en todos los componentes, pues ellos necesitan confiar en esa autoridad. Estamos firmando con la clave privada del ca, esta no necesita estar dentro de cada componente, pero el certificado que contiene las informaciones y la clave pública es necesario.

alt text

Server Certificates

Ahora vamos para los certificados de los servidores.

En el caso del ETCD tenemos.

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

Pero el etcd también es un clúster, desplegado dentro de los master o fuera, luego tenemos los certificados de cada uno de sus miembros. No necesitamos mostrar aquí ahora, pero vamos a estudiar sobre eso más tarde cuando crear un clúster desde cero.

alt text

Esos certificados son necesarios en la configuración de cada miembro del etcd.

alt text

El kube-apiserver es el punto central del Kubernetes ya que será el punto de entrada para el clúster y algunos parámetros a más son necesarios. El kube-apiserver es llamado de varios nombres entonces necesitamos pasar más alias para él. Quien no conoce el kubernetes y lo que

En el certificado es necesario tener:

  • Todos los alias y solamente a través de esos nombres que podremos hacer una conexión válida
    • kubernetes
    • kubernetes.default
    • kubernetes.default.svc
    • kubernetes.default.svc.cluster.local
  • Ip del pod
  • Ip del host
openssl genrsa -out apiserver.key 2048
# Creando un archivo de configuración para que sea más fácil en el 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

En la configuración del kube-apiserver tenemos los siguientes parámetros para pasar, recordando que el kube-apiserver necesita comunicarse con el etcd y con el Kubelet de los hosts. Aún no fue mostrado aquí, pero sería necesario generar más certificados, pero también podría ser usado el mismo certificado, es una cuestión de configuración en el montaje del clúster.

alt text

Por último tenemos el Kubelet que corre en cada uno de los worker nodes.

Cada Kubelet dentro de cada node tiene su propio certificado, imagina como cada node siendo un user del sistema. La diferencia es que el CN poseerá el nombre del worker y cuál grupo:role el kube-apiserver dará a este node. Es necesario que esté en el grupo system:node

Los nombres abajo podrían ser todos worker.key worker.scr. worker.crt, para que la configuración del Kubelet dentro de cada node sea igual y facilitar automatización. Coloqué de forma diferente solo para ilustración.

# Usado en el 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 en el 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 en el 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


La buena noticia es que no necesitamos generar esos certificados automáticamente. Utilizando el kubeadm él se encarga de generar esos certificados. Claro si queremos instalar de la manera hard haciendo todo manualmente tendrás ese trabajo. Nosotros haremos eso más adelante para aprender, pero no es usual en el día a día.

El kubeadm crea los componentes como pods en el kubernetes. Los manifiestos están en /etc/kubernetes/manifests en cualquier uno de los nodes.

El archivo del kube-apiserver está en kube-apiserver.yaml En la especificación del pod vamos a tener eso:

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 necesita estar presente en todos los componentes
- --enable-admission-plugins=NodeRestriction
- --enable-bootstrap-token-auth=true
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt # En ese caso el etcd posee su propio 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 el 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 del 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 también tenemos los puntos de montaje, puertos, etc, pero vamos a enfocarnos en los certificados.

Para analizar un certificado, podemos hacer.

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 # Quién fue el emisor, en el caso el CA.
Validity
Not Before: Feb 8 22:57:18 2024 GMT
Not After : Feb 7 23:02:18 2025 GMT # Validez
Subject: CN = kube-apiserver # Vea el 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: # Vea los nombres 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 evaluar los certificados y buscar issuers podemos seguir el siguiente criterio. Intentar hacer una tabla para entender cada una de las cosas.

Este clúster fue montado usando un kind y vea lo que tenemos. En el caso poseemos dos emisores un CA para el Kubernetes y un CA para el 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

Algunas cosas quedaron mejor entendidas un poco más adelante.

Analizando problemas

Para analizar algún problema con certificado.

Si los componentes están corriendo como servicios podemos usar el journalctl -u etcd.service -l o el nombre de otro servicio,

Si fue utilizado el kubeadm podemos mirar el log de los pods kubectl logs etcd-master -n kube-system o algún otro componente. En el caso del kind que estoy usando aquí sería kubectl logs -n kube-system etcd-kind-cluster-control-plane

No existe una regla, existe entender el concepto de las cosas.

Algunas veces es aún necesario bajar un poco más el nivel cuando kube-apiserver o el etcd está down o cuando el comando kubectl no está funcionando.

Una vez que los pods están corriendo como containers, podemos tomar los logs de los containers directamente.

Podemos usar los comandos del crictl dentro del 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."}
...