Skip to main content

Authorization

Ya vimos que el kube-apiserver tiene la función de autenticar un usuario por medio de los certificados, pero ¿qué puede hacer en el cluster?

Como administrador podemos hacer cualquier cosa en el cluster, pero no queremos que otros usuarios puedan modificar configuraciones, aprobar certificados, trabajar en namespaces del kube-system, eliminar secrets, etc.

Podemos permitir que vean, pero no modifiquen, por ejemplo.

alt text

Un escenario muy interesante es que un cluster puede ser dividido en diferentes organizaciones o equipos y cada uno debe trabajar en sus respectivos namespaces. Es en esto que autorización también puede ayudar dentro del cluster.

Existen diferentes mecanismos de autorización:

  • Node
  • ABAC
  • RBAC
  • Webhook
  • AlwaysAllow (No recomendable)
  • AlwaysDeny (No recomendable)

Node

Sabemos que usuarios pueden acceder al kube-apiserver, pero que también el Kubelet dentro de los workers nodes también accede. Cuando el certificado de los Kubelet fue creado también fue especificado que debería pertenecer al grupo system:nodes ¿recuerdas?

El grupo system:nodes ya contiene permisos específicos para nodes en Kubernetes. El Kubelet necesita crear los pods en el node, actualizar el kube-apiserver, actualizar el status del node, acceder a los storages, acceder a otros services, etc.

¿Qué podría hacer un node?

kubectl get clusterroles system:node -o yaml

# Para que sea más fácil separé los pedazos
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: "2024-02-08T23:02:26Z"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:node
resourceVersion: "84"
uid: 923e2cc5-fd5e-47e5-b4ce-69f57431c2ba
rules:

##################
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create

##################
- apiGroups:
- authorization.k8s.io
resources:
- localsubjectaccessreviews
- subjectaccessreviews
verbs:
- create

################## SERVICIOS
- apiGroups:
- ""
resources:
- services #<<<
verbs:
- get
- list
- watch

##################
- apiGroups:
- ""
resources:
- nodes <------------------------|
verbs: |
- create |
- get |
- list |
- watch |
|
################## ^
- apiGroups: |
- "" |
resources: |
- nodes/status |
verbs: |
- patch |
- update |
|
################## ^
- apiGroups: |
- "" |
resources: |
- nodes # MISMO DE DOS GRUPOS ARRIBA
verbs:
- patch
- update

################## CREAR Y ACTUALIZAR EVENTOS
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- update

################## OBSERVE AQUÍ. TENEMOS DOS GRUPOS PARA EL MISMO RECURSO PODS
# SI SE JUNTARA DARÍA LO MISMO, PERO SIMPLEMENTE FUE SEPARADO PERMISO PARA LECTURA Y ESCRITURA COMO UNA FORMA DE ORGANIZACIÓN
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch

- apiGroups:
- ""
resources:
- pods #<< MISMO GRUPO DEL DE ARRIBA
verbs:
- create
- delete

##################
- apiGroups:
- ""
resources:
- pods/status
verbs:
- patch
- update

##################
- apiGroups:
- ""
resources:
- pods/eviction
verbs:
- create

##################
- apiGroups:
- ""
resources:
- configmaps
- secrets
verbs:
- get
- list
- watch

##################
- apiGroups:
- ""
resources:
- persistentvolumeclaims
- persistentvolumes
verbs:
- get

##################
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get

##################
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- create
- get
- list
- watch

##################
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- create
- delete
- get
- patch
- update

##################
- apiGroups:
- storage.k8s.io
resources:
- volumeattachments
verbs:
- get

##################
- apiGroups:
- ""
resources:
- serviceaccounts/token
verbs:
- create

##################
- apiGroups:
- ""
resources:
- persistentvolumeclaims/status
verbs:
- get
- patch
- update

##################
- apiGroups:
- storage.k8s.io
resources:
- csidrivers
verbs:
- get
- list
- watch

##################
- apiGroups:
- storage.k8s.io
resources:
- csinodes
verbs:
- create
- delete
- get
- patch
- update

##################
- apiGroups:
- node.k8s.io
resources:
- runtimeclasses
verbs:
- get
- list
- watch

Observando la salida anterior podríamos hacer esto más pequeño, pero es una cuestión de organización.

ABAC (Attribute-Based Access Control) - DESCONTINUADO

El estudio aquí vale para entender la historia de Kubernetes pues a partir de la versión 1.25 fue removido.

En este método asociamos un usuario o un grupo de usuarios que tiene permisos en determinados recursos y los permisos sobre cada recurso.

No es posible crear un archivo YAML para definir esto. Es necesario que un archivo JSON sea pasado por parámetro al kube-apiserver en su manifesto o servicio, dependiendo de cómo fue creado el cluster.

Por ejemplo, en el manifesto del kube-apiserver tendríamos:

...
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=172.18.0.4
- --allow-privileged=true
- --authorization-mode=Node,RBAC,ABAC ## añadido ABAC
- --abac-file=/meu/caminho/para/abac.json ## el Archivo Json
- --client-ca-file=/etc/kubernetes/pki/ca.crt
...

El modo AlwaysAllow es definido por defecto si ningún --authorization-mode es definido. En ese caso, todos los usuarios tienen acceso irrestricto a todos los recursos del cluster. AlwaysDeny crearía un cluster totalmente restringido, hasta para tareas administrativas. Sería usado en caso de pruebas de seguridad cuando todo sea negado.

En ese archivo json tendríamos.

{
"apiVersion": "abac.authorization.kubernetes.io/v1beta1",
"kind": "Policy",
"spec": {
"policies": [
{
"apiVersion": "abac.authorization.kubernetes.io/v1beta1",
"kind": "Policy",
"spec": {
"user": "david",
"namespace": "default",
"resource": "*", #TODO LIBERADO
"readonly": false
}
}
{
"apiVersion": "abac.authorization.kubernetes.io/v1beta1",
"kind": "Policy",
"spec": {
"user": "joao",
"namespace": "default",
"resource": "daemonset", # SOLO ACEPTA UN RECURSO A LA VEZ
"verbs": ["get", "list", "watch"]
}
}
{
"apiVersion": "abac.authorization.kubernetes.io/v1beta1",
"kind": "Policy",
"spec": {
"user": "joao",
"namespace": "default",
"resource": "deployments",
"verbs": "verbs": ["get", "list", "watch", "create", "update", "delete"]
}
}
]
}
}

Y para que haga efecto necesitaríamos reiniciar el kube-apiserver. Cada política solo acepta un nombre a la vez; en el caso de * es aceptable para todos, pero no conseguimos colocar apenas 2 o 3. Es necesario otro bloque como puede observar arriba.

Esto ya no se usa más ni caerá en el examen, pero queda de conocimiento lo trabajoso que era para configurar.

Webhook

¿Y si queremos tercerizar todos los mecanismos de autorización en lugar de usar el RBAC?

Aquí algunas herramientas con ese propósito:

alt text

Es necesario activar el Webhook en el kube-apiserver como siendo un modo de autorización. Él intenta autorizar siguiendo el orden establecido cuando muchos modos son usados.

--authorization-mode=Node,RBAC,Webhook

Node > RBAC > Webhook

--authorization-mode=Node,Webhook,RBAC

Node > Webhook > RBAC

RBAC (Role-Based Access Control)

Este es el método built-in de kubernetes.

Definimos un role (función en el cluster) y asociamos usuarios a ese role.

Por ejemplo el role de developer puede hacer tales cosas en el cluster y los usuarios que son developers actúan usando ese role. Administradores usan roles de administradores, y así sucesivamente.

Una vez que cambiamos los permisos de un role todos los usuarios que asumen ese role son afectados de una única vez. Es una de las formas que tenemos de agrupar las cosas.

¿Recuerdas aquel usuario david que hicimos para el cluster?

Vamos a crear un role que él pueda usar.

Primeramente necesitamos entender que existen dos tipos de role:

  • Role: Usada para dar permiso a nivel de namespace.
  • ClusterRole: Usado para dar permiso a nivel de cluster.

Por ejemplo el get nodes está en qué nivel? De cluster.

Si queremos hacer un role admin por ejemplo vamos a dar permiso a nivel de cluster.

Primero necesitamos crear el role y después hacer permitir que un usuario pueda asumir ese role usando un binding. Vamos a crear dos en el mismo archivo para que pueda quedar fácil de entender.

Roles y RoleBindings

Vamos a comenzar por un role normal con alcance de namespaces.

cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dev-role
rules:
- apiGroups:
- ""
resources:
- deployments # <<< Fíjate en esto
- pods
- services
# ESTOS VERBOS SON APLICADOS PARA TODOS LOS RECURSOS ARRIBA
verbs:
- create
- list
- get
- update
- delete
- watch
- apiGroups: [""]
resources: ["ConfigMap"]
verbs: ["create"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: david-binding-dev-role
subjects:
- kind: User
name: david
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: dev-role
apiGroup: rbac.authorization.k8s.io
EOF

role.rbac.authorization.k8s.io/dev-role created
rolebinding.rbac.authorization.k8s.io/david-binding-role created

Entonces creamos un role que tiene permiso para trabajar con pods y deployments, pero no especificamos un namespace. Vamos a verificar.

kubectl get role
NAME CREATED AT
dev-role 2024-02-11T09:32:38Z

kubectl describe role dev-role
Name: dev-role
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
deployments [] [] [create list get update delete watch]
pods [] [] [create list get update delete watch]
services [] [] [create list get update delete watch]
configmaps [] [] [create]

kubectl get rolebindings
NAME ROLE AGE
david-binding-dev-role Role/dev-role 25s

kubectl describe rolebindings david-binding-dev-role
Name: david-binding-dev-role
Labels: <none>
Annotations: <none>
Role:
Kind: Role
Name: dev-role
Subjects:
Kind Name Namespace
---- ---- ---------
User david

Vamos a verificar entonces si un user lo que un usuario podría hacer

## En este caso estoy verificando para el usuario que estoy en el momento
kubectl auth can-i create deployments
yes

## Verificando para otro usuario
kubectl auth can-i create deployments --as david
no # ¿POR QUÉ?

kubectl auth can-i get pods --as david
yes

kubectl auth can-i create configmaps --as david
yes

kubectl auth can-i delete configmaps --as david
no

¿Por qué david no puede crear deployments y consigue crear pods? ¿Por qué uno funcionó y el otro no?

Porque deployments es de un apigroup diferente ¿recuerdas? Cuando no definimos un apigroup él automáticamente entenderá que es el core o sea api, luego deployment está en otro grupo.

En el core tenemos:

  • bindings
  • componentstatuses
  • configmaps
  • endpoints
  • events
  • limitranges
  • namespaces
  • nodes
  • persistentvolumeclaims
  • persistentvolumes
  • pods
  • podtemplates
  • replicationcontrollers
  • resourcequotas
  • secrets
  • serviceaccounts
  • services

En apis tenemos? ¡Dale un vistazo allá para recordar!

Para que deployment funcione es necesario añadir esto en el role, separando el deployment.

- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["create", "list", "get", "update", "delete", "watch"]
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dev-role
rules:
- apiGroups: [""]
resources: ["pods", "services"] # Removido deployments de aquí
verbs: ["create", "list", "get", "update", "delete", "watch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["create"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["create", "list", "get", "update", "delete", "watch"]
EOF

kubectl describe role dev-role
Name: dev-role
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
pods [] [] [create list get update delete watch]
services [] [] [create list get update delete watch]
deployments.apps [] [] [create list get update delete watch]
configmaps [] [] [create]

kubectl auth can-i create deployments --as david
yes

kubectl auth can-i create deployments --as david --namespace test
no

Cuando no pasamos el namespace que queremos él asume el default pues Role tiene un alcance de namespace. Es una buena práctica siempre especificar el namespace, a pesar de que no lo hice aquí.

Si no conoces apigroups no entenderás RBAC. El motivo de que los resources estén en plural es exactamente porque dentro de las apigroups están en plural. Otra observación que podemos hacer es que en la segunda vez no alteramos el binding, solamente el role y todo funcionó.

Aún podemos dar permiso a un recurso con nombre específico como por ejemplo un pod llamado nginx usando el resourceNames

Crear un role puede también ser usado el comando kubectl

## verbs resources y resource-name son listas
create role new-role --verb=list,get,create,delete --resource=pods --resource-name=nginx
role.rbac.authorization.k8s.io/new-role created

kubectl describe role new-role
Name: new-role
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
pods [] [nginx] [list get create delete]


kubectl create rolebinding new-role-binding --user=david --role=new-role
rolebinding.rbac.authorization.k8s.io/new-role-binding created

kubectl describe rolebindings new-role-binding
Name: new-role-binding
Labels: <none>
Annotations: <none>
Role:
Kind: Role
Name: new-role
Subjects:
Kind Name Namespace
---- ---- ---------
User david

ClusterRoles y ClusterRoleBindings

Mirando dentro de un recurso podemos ver si un recurso está a nivel de namespace o no.

Si miras nuevamente en /api/v1 tenemos allá los recursos, tomé dos de ejemplo:

{
...
{
"name": "nodes",
"singularName": "node",
"namespaced": false, # Nivel de cluster
"kind": "Node",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"no"
],
"storageVersionHash": "XwShjMxG9Fs="
},
...
{
"name": "pods",
"singularName": "pod",
"namespaced": true, # Nivel de namespace
"kind": "Pod",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"po"
],
"categories": [
"all"
],
"s
...
}
...

Ya que los recursos con alcance de cluster no necesitamos de un namespace no necesitamos especificarlos.

Para saber obtener todos los recursos de nivel de cluster podemos hacer.

kubectl api-resources --namespaced=false

NAME SHORTNAMES APIVERSION NAMESPACED KIND
componentstatuses cs v1 false ComponentStatus
namespaces ns v1 false Namespace
nodes no v1 false Node
persistentvolumes pv v1 false PersistentVolume
mutatingwebhookconfigurations admissionregistration.k8s.io/v1 false MutatingWebhookConfiguration
validatingwebhookconfigurations admissionregistration.k8s.io/v1 false ValidatingWebhookConfiguration
customresourcedefinitions crd,crds apiextensions.k8s.io/v1 false CustomResourceDefinition
apiservices apiregistration.k8s.io/v1 false APIService
selfsubjectreviews authentication.k8s.io/v1 false SelfSubjectReview
tokenreviews authentication.k8s.io/v1 false TokenReview
selfsubjectaccessreviews authorization.k8s.io/v1 false SelfSubjectAccessReview
selfsubjectrulesreviews authorization.k8s.io/v1 false SelfSubjectRulesReview
subjectaccessreviews authorization.k8s.io/v1 false SubjectAccessReview
certificatesigningrequests csr certificates.k8s.io/v1 false CertificateSigningRequest
flowschemas flowcontrol.apiserver.k8s.io/v1 false FlowSchema
prioritylevelconfigurations flowcontrol.apiserver.k8s.io/v1 false PriorityLevelConfiguration
ingressclasses networking.k8s.io/v1 false IngressClass
runtimeclasses node.k8s.io/v1 false RuntimeClass
clusterrolebindings rbac.authorization.k8s.io/v1 false ClusterRoleBinding
clusterroles rbac.authorization.k8s.io/v1 false ClusterRole
priorityclasses pc scheduling.k8s.io/v1 false PriorityClass
csidrivers storage.k8s.io/v1 false CSIDriver
csinodes storage.k8s.io/v1 false CSINode
storageclasses sc storage.k8s.io/v1 false StorageClass
volumeattachments storage.k8s.io/v1 false VolumeAttachment

El proceso es el mismo para Roles y RoleBindings pero cambiando Role para ClusterRole y Binding para ClusterRoleBinding.

Varios clusterroles son creados cuando un cluster es creado por primera vez.

Aquí sería un role que podríamos hacer cualquier cosa.

cat <<EOF | kubectl apply -f -
# DEFINIMOS UN ROLE LIBERANDO TODO
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: admin-role
rules:
- apiGroups: ["*"] # PARA TODOS LOS APIGROUPS
resources: ["*"] # PARA TODOS LOS RECURSOS
verbs: ["*"] # TODOS LOS VERBOS
---

# AHORA BINDEAMOS ESE ROLE CON EL USUARIO DAVID
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: david-binding-role
subjects:
- kind: User
name: david
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: admin-role
apiGroup: rbac.authorization.k8s.io
EOF

Una cosa importante de mencionar es que podemos colocar recursos de namespaces dentro de un ClusterRole normalmente, pero no lo contrario. La diferencia es que estamos liberando TODOS LOS NAMESPACES.