Kubernetes: autenticacion y autorizacion
Introducción
En este artículo vamos a explorar cómo funciona la autenticación y la autorización en Kubernetes. Pero primero, ¿cuál es la diferencia?
Autenticación:
Cuando validás tu identidad contra un servicio o sistema, estás autenticado, lo que significa que el sistema te reconoce como un usuario válido. En Kubernetes, cuando creás los clusters, básicamente creás una CA (Autoridad Certificadora) que luego usás para generar certificados para todos los componentes y usuarios.
Autorización:
Una vez que estás autenticado, el sistema necesita saber si tenés suficientes privilegios para hacer lo que quieras. En Kubernetes, esto se conoce como RBAC (Control de Acceso Basado en Roles), y se traduce en roles como entidades con permisos que están asociados a cuentas de servicio a través de vinculaciones de roles (role bindings) cuando las cosas están delimitadas a un namespace específico. De lo contrario, podés tener un cluster role y un cluster role binding.
Así que vamos a crear un namespace, una serviceaccount, un role y una role binding, luego generamos un kubeconfig para probar todo.
Los recursos para este artículo los podés encontrar aquí: RBAC Example
Vamos al grano
Empecemos. Voy a usar estos generadores, pero estoy guardando los resultados en un archivo y luego aplicándolos.
Namespace:
El recurso de namespace es como un contenedor para otros recursos y es muy útil cuando estás desplegando muchas apps en el mismo cluster o hay varios usuarios:
kubectl create namespace mynamespace -o yaml --dry-run=client
La salida deberia verse asi:
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: null
name: mynamespace
spec: {}
status: {}
Podés ver más información aquí
Service account:
La service account es tu identidad dentro del sistema. Hay algunas diferencias importantes entre cuentas de usuario y cuentas de servicio. Por ejemplo:
- Las cuentas de usuario son para humanos. Las cuentas de servicio son para procesos que se ejecutan en pods.
-
Las cuentas de usuario están pensadas para ser globales. Los nombres deben ser únicos en todos los namespaces de un cluster. Las cuentas de servicio están delimitadas por namespaces.
Para este ejemplo, estamos generando una service account para un pod y una cuenta de usuario para nosotros para usar con kubectl (si quisiéramos un usuario global, deberíamos haber usado clusterrole y clusterrolebinding).
kubectl create serviceaccount myuser -o yaml --dry-run=client
La salida deberia verse asi:
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: null
name: myuser
Podés ver más información aquí
Role:
Este role tiene privilegios similares a los de un admin. Los verbos permitidos son los siguientes, estamos usando * que significa “todos”:
- list
- get
- watch
- create
- patch
- update
- delete
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
creationTimestamp: null
name: myrole
rules:
- apiGroups:
- ""
resources:
- '*'
verbs:
- '*'
Podés ver más información aquí y aquí
Role binding:
Esto es lo que une los permisos del role a la service account que creamos.
kubectl create rolebinding myuser-myrole --role=myrole --serviceaccount=mynamespace:myuser --user=myotheruser -o yaml --dry-run=client
La salida deberia verse asi:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: null
name: myuser-myrole
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: myrole
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: myotheruser
- kind: ServiceAccount
name: myuser
namespace: mynamespace
Podés ver más información aquí
Ejemplo desde un pod
Aquí creamos un pod de ejemplo con curl y le asignamos la service account con --serviceaccount=
kubectl run mypod --image=curlimages/curl:latest --serviceaccount=myuser --dry-run=client -o yaml --command -- sh -c "sleep 3d"
La salida deberia verse asi:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: mypod
name: mypod
spec:
containers:
- image: curlimages/curl:latest
name: mypod
resources: {}
command:
- sh
- -c
- sleep 3d
dnsPolicy: ClusterFirst
restartPolicy: Always
serviceAccountName: myuser
status: {}
Aplicando
Aquí creamos todos los recursos, esto establecerá el namespace en la configuración para que no tengamos que preocuparnos por especificarlo en los manifiestos o durante el apply
kubectl config set-context --current --namespace=mynamespace
La salida deberia verse asi:
Context "kind-kind" modified.
kubectl apply -f .
La salida deberia verse asi:
namespace/mynamespace configured
serviceaccount/myuser created
role.rbac.authorization.k8s.io/myrole created
rolebinding.rbac.authorization.k8s.io/myuser-myrole created
pod/mypod created
Validando desde el pod
Aquí exportamos el token para nuestra service account y hacemos una consulta a la API de Kubernetes.
kubectl exec -ti mypod -- sh
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
Primer test: sin usar el token obtenemos un error de autenticación para “system:anonymous”
curl -k https://kubernetes.default:443
La salida deberia verse asi:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {
},
"code": 403
}
Nota: no puse toda la info para nuestros pods en nuestro namespace porque es demasiado, pero entendés la idea, podés ver todo lo que pasó ahí, notá que estamos usando el namespace porque no podemos listar pods de todos los namespaces con esta serviceaccount. Podés probar /apis y /api/v1/ para investigar más.
curl -k https://kubernetes.default:443/api/v1/namespaces/mynamespace/pods -H "Authorization: Bearer ${TOKEN}"
La salida deberia verse asi:
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/mynamespace/pods",
"resourceVersion": "10915"
},
"items": [
{
"metadata": {
"name": "mypod",
"namespace": "mynamespace",
"selfLink": "/api/v1/namespaces/mynamespace/pods/mypod",
"uid": "835e894e-c4f0-4182-b601-ff086b53fba3",
"resourceVersion": "9824",
"creationTimestamp": "2020-11-29T21:45:24Z",
"labels": {
"run": "mypod"
},
"managedFields": [
{
....
....
....
"lastState": {
},
"ready": true,
"restartCount": 0,
"image": "docker.io/curlimages/curl:latest",
"imageID": "docker.io/curlimages/curl@sha256:5329ee280d3d91f3e48885f18c884af5907b68c6aa80f411927a5a28c4f5df07",
"containerID": "containerd://cdc729aacdc5ce3b1b81ff443ea7c6554ff85a4187e7af2ecda700e28a96fa51",
"started": true
}
],
"qosClass": "BestEffort"
}
}
]
}
Notá que para poder llegar al servicio de Kubernetes, ya que está en un namespace diferente, necesitamos especificarlo
con .default
(porque está en el namespace default). Probá: kubectl get svc -A
para ver todos los servicios.
Todo funcionó bien desde nuestro pod y podemos comunicarnos con la API desde allí. Veamos si también funciona con kubectl.
Generar config para kubectl
Obtené el token (como podés ver,
Está guardado como un secreto de Kubernetes, por lo que se monta en los pods como cualquier otro secreto, pero automáticamente gracias a la service account)
kubectl describe serviceAccounts myuser
La salida deberia verse asi:
Name: myuser
Namespace: mynamespace
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: myuser-token-mckzz
Tokens: myuser-token-mckzz
Events: <none>
kubectl get secrets myuser-token-mckzz -o yaml
La salida deberia verse asi:
apiVersion: v1
data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01URXlPVEl3TkRjME5Wb1hEVE13TVRFeU56SXdORGMwTlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVBZCkdMR2s1QlZ0V091dGhJcldranFIRStlOW5VeDk1cDcydFREa2JnR2JCa2RZZm12dzFadUYrNGx4dnhDOU9CMUIKdTVyUDZsSlNHeW9NbDRGLzlQQ0s0OVovMXFyRm5qMFQzQkorZ2RTMm11YzZVM0QzbkFOV1FUMjJKcERlQ2lpMQorQ2xNbTBwMzVLbXJlS1NyRTlHOC9ISW9YaGRHZk1qWEVLSkxpdmlFUWxCcUVLcWw3dzlsZnlmZFpEV3pVZEN0CmU5ZW9QNlBhV21waVNUS2dYcExvdFFGb2VMWWJGQTlDU2l1YllmUk85eVJLb25GeDB4dHlSaW5kaWtRaHF2ejUKQXVhbVZTdm1xNk5mUXlBL3JWbzN3b3ptazRjWVBab215QlBHMHZreGczcE1TaFVKaHVSVEthN0xNdFBvMS9GNAowMlFtdUdIb1dCUTVPYjQ0VlVjQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDNTZFS1g0T0JSa09xYkpDeVBlaUFVQm9yUUMKajQ3aktld3FkUHREVk01VkZ1MGNtV3lYd1phM2pGbGt0YnRCd1J6SS82R2FpdmhCaEZhak5lUEZaazlQVkV2MQpVekt1bkIxMDBvU0xIL3VscmVsekxYc0FoQXFJKzV3VTVhemhPK2t4UDZlejBmOGh6d3lDSjBuWlB4c2kvZmhWClBwOUt3ek11cnBtb3ArWmhjUEQ3aXIxbWxuTTd1aDNRczRxNk92ZzZpWjdabjQ4OUwyR1ZhczRUUk1QWDFhc1MKYkhzbmR2b2IvOEJLalExaVE0UWI3cHRoK1MzTUZzb25WUzd4VE9XZWlqM3hSUEM4RzlYYUdKWUVxNGczNDBYZgprWE1FZUVKTXI4eWlRUjNWMy83VmlTOFhtSm9EbzJjeVJhbnV2SGpsVXVWaGtpNTB2SDYvbXdIZ2sxbz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
namespace: bXluYW1lc3BhY2U=
token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkltUnRiRUUwZGtSaFZWVkdRblpZUzBaTkxUVnhjSFZGZWw4MVpIWkJRVlZ5V1ZSTVJWTkJWMDVTWVdNaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUp0ZVc1aGJXVnpjR0ZqWlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKdGVYVnpaWEl0ZEc5clpXNHRiV05yZW5vaUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNXVZVzFsSWpvaWJYbDFjMlZ5SWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXpaWEoyYVdObExXRmpZMjkxYm5RdWRXbGtJam9pWXprME9UVXpNekl0TW1GaU1DMDBNbVZsTFRrd1pEQXROVEEwWVROak56VXlaakEzSWl3aWMzVmlJam9pYzNsemRHVnRPbk5sY25acFkyVmhZMk52ZFc1ME9tMTVibUZ0WlhOd1lXTmxPbTE1ZFhObGNpSjkuaVA2UU9YMGFfVHNaVWVLXzlhN0doellxQm13NkVKd3RqUVFTRUFlN0Z1VDRRNjA3MmJWZ3dlVnNXaXJFVW9yOTRpd2VfamhrX1NRWmpRMk5CS2EtWU1qX1N3U21uaDZRQ3lGY2JXcWlpZjFScmZyUWtHb011Q19PLVd3VWtRS0hTVVhZRUUtZlh3Nk1UYVZhZXlpVE5wMVNWQUFYSHVrbV9xZ0J0WTE1OUZ5Vm15anBNRXVSRUYwamJockQxNjBSS0JaLUFoTVc4cWFQSmlGaE1Ia0Z1RHZmMlM2OVFRVGpmVXJhVmdlMThJNzFNUmtmWGRsdHN4dlgzcjRXMmp6Vk1jdFFrR1MzZmRYeWRRRFFlYjlaeURrWlpIRFlhcmx2aUE3djZFMzhrMTctY2k0MV9XalJCNHRFTWxTLUZzbHc1VV9nN0owX1dITmEzVEJibE9rdjF3
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: myuser
kubernetes.io/service-account.uid: c9495332-2ab0-42ee-90d0-504a3c752f07
creationTimestamp: "2020-11-29T21:42:30Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:ca.crt: {}
f:namespace: {}
f:token: {}
f:metadata:
f:annotations:
.: {}
f:kubernetes.io/service-account.name: {}
f:kubernetes.io/service-account.uid: {}
f:type: {}
manager: kube-controller-manager
operation: Update
time: "2020-11-29T21:42:30Z"
name: myuser-token-mckzz
namespace: mynamespace
resourceVersion: "9294"
selfLink: /api/v1/namespaces/mynamespace/secrets/myuser-token-mckzz
uid: 99eb2685-4c08-40b8-97cc-94973dcafb5b
type: kubernetes.io/service-account-token
Usá este ejemplo de kubeconfig y reemplazá los valores
apiVersion: v1
kind: Config
users:
- name: svcs-acct-dply
user:
token: <reemplazá esto con la info del token>
clusters:
- cluster:
certificate-authority-data: <reemplazá esto con la info del certificado>
server: <reemplazá esto con la info del servidor>
name: self-hosted-cluster
contexts:
- context:
cluster: self-hosted-cluster
user: svcs-acct-dply
name: svcs-acct-context
current-context: svcs-acct-context
El resultado sería algo como esto:
apiVersion: v1
kind: Config
users:
- name: myotheruser
user:
token: eyJhbGciOiJSUzI1NiIsImtpZCI6ImRtbEE0dkRhVVVGQnZYS0ZNLTVxcHVFel81ZHZBQVVyWVRMRVNBV05SYWMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJteW5hbWVzcGFjZSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJteXVzZXItdG9rZW4tbWNrenoiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoibXl1c2VyIiwia3ViZXJuZXRzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJjOTQ5NTMzMi0yYWIwLTQyZWUtOTBkMC01MDRhM2M3NTJmMDciLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6bXluYW1lc3BhY2U6bXl1c2VyIn0.iP6QOX0a_TsZUeK_9a7GhzYqBmw6EJwtjQQSEAe7FuT4Q6072bVgweVsWirEUor94iwe_jhk_SQZjQ2NBKa-YMj_SwSmnh6QCyFcbWqiif1RrfrQkGoMuC_O-WwUkQKHSUXYEE-fXw6MTaVaeyiTNp1SVAAXHukm_qgBtY159FyVmyjpMEuREF0jbhrD160RKBZ-AhMW8qaPJiFhMHkFuDvf2S69QQTjfUraVge18I71MRkfXdltsxvX3r4W2jzVMctQkGS3fdXydQDQeb9ZyDkZZHDYarlviA7v6E38k17-ci41_WjRB4tEMlS-Fslw5U_g7J0_WHNa3TBblOkv1w
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01URXlPVEl3TkRjME5Wb1hEVE13TVRFeU56SXdORGMwTlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVBZCkdMR2s1QlZ0V091dGhJcldranFIRStlOW5VeDk1cDcydFREa2JnR2JCa2RZZm12dzFadUYrNGx4dnhDOU9CMUIKdTVyUDZsSlNHeW9NbDRGLzlQQ0s0OVovMXFyRm5qMFQzQkorZ2RTMm11YzZVM0QzbkFOV1FUMjJKcERlQ2lpMQorQ2xNbTBwMzVLbXJlS1NyRTlHOC9ISW9YaGRHZk1qWEVLSkxpdmlFUWxCcUVLcWw3dzlsZnlmZFpEV3pVZEN0CmU5ZW9QNlBhV21waVNUS2dYcExvdFFGb2VMWWJGQTlDU2l1YllmUk85eVJLb25GeDB4dHlSaW5kaWtRaHF2ejUKQXVhbVZTdm1xNk5mUXlBL3JWbzN3b3ptazRjWVBab215QlBHMHZreGczcE1TaFVKaHVSVEthN0xNdFBvMS9GNAowMlFtdUdIb1dCUTVPYjQ0VlVjQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDNTZFS1g0T0JSa09xYkpDeVBlaUFVQm9yUUMKajQ3aktld3FkUHREVk01VkZ1MGNtV3lYd1phM2pGbGt0YnRCd1J6SS82R2FpdmhCaEZhak5lUEZaazlQVkV2MQpVekt1bkIxMDBvU0xIL3VscmVsekxYc0FoQXFJKzV3VTVhemhPK2t4UDZlejBmOGh6d3lDSjBuWlB4c2kvZmhWClBwOUt3ek11cnBtb3ArWmhjUEQ3aXIxbWxuTTd1aDNRczRxNk92ZzZpWjdabjQ4OUwyR1ZhczRUUk1QWDFhc1MKYkhzbmR2b2IvOEJLalExaVE0UWI3cHRoK1MzTUZzb25WUzd4VE9XZWlqM3hSUEM4RzlYYUdKWUVxNGczNDBYZgprWE1FZUVKTXI4eWlRUjNWMy83VmlTOFhtSm9EbzJjeVJhbnV2SGpsVXVWaGtpNTB2SDYvbXdIZ2sxbz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
server: https://127.0.0.1:35617
name: kind
contexts:
- context:
cluster: kind
user: myotheruser
name: kind
current-context: kind
Luego podemos probarlo haciendo
export KUBECONFIG=$(pwd)/kubeconfig-myotheruser
kubectl get all
La salida deberia verse asi:
NAME READY STATUS RESTARTS AGE
pod/task-pv-pod 1/1 Running 0 96m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 97m
Error from server (Forbidden): daemonsets.apps is forbidden: User "system:serviceaccount:mynamespace:myuser" cannot list resource "daemonsets" in API group "apps" in the namespace "default"
Error from server (Forbidden): deployments.apps is forbidden: User "system:serviceaccount:mynamespace:myuser" cannot list resource "deployments" in API group "apps" in the namespace "default"
Error from server (Forbidden): replicasets.apps
is forbidden: User "system:serviceaccount:mynamespace:myuser" cannot list resource "replicasets" in API group "apps" in the namespace "default"
Error from server (Forbidden): statefulsets.apps is forbidden: User "system:serviceaccount:mynamespace:myuser" cannot list resource "statefulsets" in API group "apps" in the namespace "default"
Error from server (Forbidden): horizontalpodautoscalers.autoscaling is forbidden: User "system:serviceaccount:mynamespace:myuser" cannot list resource "horizontalpodautoscalers" in API group "autoscaling" in the namespace "default"
Error from server (Forbidden): jobs.batch is forbidden: User "system:serviceaccount:mynamespace:myuser" cannot list resource "jobs" in API group "batch" in the namespace "default"
Error from server (Forbidden): cronjobs.batch is forbidden: User "system:serviceaccount:mynamespace:myuser" cannot list resource "cronjobs" in API group "batch" in the namespace "default"
Notas: Usé kubectl config view
para descubrir el endpoint de kind que es server: https://127.0.0.1:35617
en mi caso.
Luego reemplacé los valores del secreto para el CA y el token/secreto de la service account. También notá que necesitás
decodificar desde base64 cuando usás kubectl get -o yaml
. Además, verás errores cuando intentes hacer cosas fuera de
nuestro namespace porque simplemente no tenemos permisos. Este es un método muy poderoso para otorgar permisos a los usuarios,
y esto funciona porque creamos el role binding para nuestro usuario adicional y para la service account del pod (tené cuidado al configurar todo).
Clean up
Siempre recordá limpiar tu máquina local / cluster / etc. En mi caso, kind delete cluster
lo hará.
Errata
Si encontrás algún error o tenés alguna sugerencia, mandame un mensaje para corregirlo.
No tienes cuenta? Regístrate aqui
Ya registrado? Iniciar sesión a tu cuenta ahora.
-
Comentarios
Online: 0
Por favor inicie sesión para poder escribir comentarios.