Kubernetes: autenticacion y autorizacion


kubernetes

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).


Podés ver más aquí y aquí


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.

Iniciar session con GitHub
Iniciar sesion con Google
  • Comentarios

    Online: 0

Por favor inicie sesión para poder escribir comentarios.

by Gabriel Garrido