Skip to main content

Restric API Acess

O kube-apiserver é o componentes central do kubernetes fazendo a comunicação entre todos os componentes. É uma api reutilizável e extensível sendo essa uma das principais razões da popularidade do kubernetes.

Quando enviamos uma request para o kubernetes é com o apiserver que estamos interagindo sendo este responsável por:

  • Autenticação através de certificados (1 level)
    • Quem é você?
  • Authorização através de roles e rolebindings (2 level)
    • Você tem permissão para executar essa ação neste recurso?
  • Admissão (3 level)
    • Você esta infringindo alguma regra de limite imposta no cluster|namespace?

Admission Controllers fazem verificações adicionais sobre limites ou personalização customizadas. Podemos criar nossas próprias limitações com ferramentas de terceiros como por exemplo OPA (Open Policy Agent).

Somente se os 3 levels passarem a ação será executada.

  • Uma request pode vir de:
    • Normal user
    • Service Account
    • Anônimo
  • Toda request deve ser autenticada
    • OU deve ser tratada como um usuário anônimo.

O que podemos fazer para melhorar a segurança do apiserver?

  • Não permitir acesso anônimo:

    root@cks-master:~# curl https://localhost:6443 -k
    {
    "kind": "Status",
    "apiVersion": "v1",
    "metadata": {},
    "status": "Failure",
    "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
    "reason": "Forbidden",
    "details": {},
    "code": 403
    }
    # Isso significa que esta sendo permitido usuário anônimo, vamos alterar.
    # adicine - --anonymous-auth=false no manifesto do kube-apiserver e teste.

    root@cks-master:~# curl https://localhost:6443 -k
    {
    "kind": "Status",
    "apiVersion": "v1",
    "metadata": {},
    "status": "Failure",
    "message": "Unauthorized",
    "reason": "Unauthorized",
    "code": 401
    }
  • Desabilitar portas inseguras: Era possível liberar http no apiserver, mas a partir da versão 1.22 não é mais. Se precisamos de certificados para autenticar o usuário, habilitar as portas inseguras do kubernetes irá fazer um bypass da autenticação e autorização ainda que o admission seja analisado. Isso somente era utilizado para propósitos de Debug, mas é bom conhecer.

  • Prevernir pods de acessar a API: Desabilitar o automount do token do service account nos pods, já foi mostrado anteriormente.

  • Prevenir acesso não autorizado (RBAC). Garantir authorization-mode com RBAC. alt text

  • Apiserver port deveria estar atrás de um firewall / Permitir somente range específico de IP: Essa proteção esta em um nível fora do exame e fora da competência do kubernetes.

  • Não expor o ApiServer fora do cluster. Vou mostrar abaixo em Manual Api Query. Caso necessário usar o Item Acima ↑. Existem outras maneiras e serviços que podemos expor no cluster para aplicar um manifesto.

    • Rodar um pod ou um job kubectl dentro do cluster.
    • SSH para um bastion que dentro da rede que tenha acesso api.
    • Solucões CID/CD.
    • VPN.
  • Restringir acessos dos Nodes para o Apiserver (NodeRestriction)

Podemos enviar requisições de diferentes formas sendo a mais comum o uso da cli kubectl, mas podemos utilizar libs para diferentes linguagens.

É necessário restringir o acesso ao API server uma vez que este é a porta de entrada para o cluster.

Manual API Query

Se quisermos fazer uma chamada para o api usando o kubectl passando os certificados manualmente é possível.


# Esse comando mostra o kubeconfig que estamos utilizando sem expor os certificados
root@cks-master:~# k config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://10.128.0.5:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: david
name: david
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: david
user:
client-certificate-data: DATA+OMITTED
client-key-data: DATA+OMITTED
- name: kubernetes-admin
user:
client-certificate-data: DATA+OMITTED
client-key-data: DATA+OMITTED

Dentro do kubeconfig podemos apontar os paths dos certificados e chaves ao invés de utilizar o próprio conteúdo do certificado embedado, Por comodidade de ter somente um único arquivo acaba-se incluindo os certificados com plaintext em base64 no kubeconfig. O comando abaixo mostra o arquivo completo em ~/.kube/config mesma coisa que fizer um cat ~/.kube/config.

# Vou dar uma reduzida nas saídas para ficar fácil de ler
root@cks-master:~# k config view --raw
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJT...RklDQVRFLS0tLS0K
server: https://10.128.0.5:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1CRUdJT...RFLS0tLS0K
client-key-data: LS0tLS1CRUdJT...tLS0tCg==

Vamos extrair esses certificados para arquivo já fazendo o base64 decode.


# Vou remover um poucos dos caracteres do base64 para facilitar a leitura
root@cks-master:~# echo "LS0tLS1C....FLS0tLS0K" | base64 --decode > ca

root@cks-master:~# cat ca
-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIfy9qvG3J3uEwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDA4MTUyMDM2MDJaFw0zNDA4MTMyMDQxMDJaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDxJ40vI2VhF+5F9SFNNfliOdjbi2+Ml+9SFMfd5WDbNEMqp3hmOpJngXGk
#Reduzido
JU7NAYRagu7ypyzH9P3F2V6pThYBG0hRtt8UJippp399SDLXXLlOJKEKbY1DGeNa
MIxgD80KO92iey328zS1YxBAPC3D3StC4+14kGUAC1qOXa6uF1YvNgYDMZSz8AXL
SHLMRY9GfN2L5GQAcRQgprQeb83kaV0X7mqsnvMbQlrcZCub7wZ9M44WuQ+Qomjn
mOyo9Yb6q+QU

root@cks-master:~# echo "LS0tLS1...VRFLS0tLS0K" | base64 --decode > crt
-----END CERTIFICATE-----

root@cks-master:~# cat crt
-----BEGIN CERTIFICATE-----
MIIDKTCCAhGgAwIBAgIIHvhZBgu4UWkwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDA4MTUyMDM2MDJaFw0yNTA4MTUyMDQxMDVaMDwx
...
wcfJ3r/K5DpOova1rSprr/2RjbqBE+XH82Q9gXsYGLNs+JFsy1Rl4W4DiiftCKZT
Syh1SuqHG8OWRVUNoV9D7rMV5Ix9hwQJaFOCRx2zDqnTRizNwI2L9NqRtUBz9jB0
YiBtpfXMmEGQJdQBU7YiD2zpD3RY9RIh2r0T9ZtXVU0VJx2QgpbwdXekBb3X
-----END CERTIFICATE-----

root@cks-master:~# echo "LS0tLS...0tLS0tCg==" | base64 --decode > key

root@cks-master:~# cat key
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzcGoyR3lmL/uPCCwnd7dMIOv+JSl+lW7BUOLup57sLZvvPKY
HgUpJ/VEKWek+fYM+uoa1Xr1tVYo70LgfGiDNHRJhsm6nMCQJAJEfe6/FfIcr8v/
SvDP18QnP91UIGfrgpr9RWI1IYn8DxAxExkSGbbiGv+IgXp3p3vzIQPghlzv5E0g
...
HxTs4wKBgCqZmA+9QW52ii63i61YBY3m6rQA8FKd3hiPaC6USaXN53hMqcTfNLmk
sspEI8PVyIs75bD0qyK+WJYUjVlu2+iSXVQC9M26v4P1jfLdHeDuRuWsa/kmunJb
qa6+xdpKHvMAyd6pJg2m/T9afh9L2+0AGyqOiDbUzBB/ClyNef9c
-----END RSA PRIVATE KEY-----

E podemos fazer o curl passando o certificados.

root@cks-master:~# curl https://10.128.0.5:6443
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

# Se não passamos os certificados do cliente então somos anônimos
root@cks-master:~# curl https://10.128.0.5:6443 --cacert ca
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}

root@cks-master:~# curl https://10.128.0.5:6443 --cacert ca --cert crt
curl: (58) unable to set private key file: 'crt' type PEM

root@cks-master:~# curl https://10.128.0.5:6443 --cacert ca --cert crt --key key
{
"paths": [
"/.well-known/openid-configuration",
"/api",
"/api/v1",
"/apis",
"/apis/",
"/apis/admissionregistration.k8s.io",
# Removido para diminuir a saída do estudo
## ....
"/readyz/poststarthook/start-system-namespaces-controller",
"/readyz/poststarthook/storage-object-count-tracker-hook",
"/readyz/shutdown",
"/version"
]

Se quisermos acessar o kubernetes fora do cluster precisamos nos atentar ao service que expose o api.

# No momento o service é um clusterapi ou seja, somente acessado dentro do cluster, para que seja exposto é necessário mudar isso para nodeport
root@cks-master:~# k get svc kubernetes
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d12h

root@cks-master:~# k edit svc kubernetes
service/kubernetes edited

root@cks-master:~# k get svc kubernetes
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes NodePort 10.96.0.1 <none> 443:31305/TCP 6d12h

# Podemos pegar o ip pela interface gráfica ou usar o metadata server no caso do gcp
curl -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"
34.30.155.135

# Ou até mesmo usando um site externo que devolve a informação que precisamos
curl ifconfig.me
34.30.155.135

Usando endereço ip externo de qualquer uma dos nodes na porta 31305 podemos chegar no cluster.

Na minha máquina local.

# E chegamos la, mas precisamos de um kubeconfig configurado ou passar os certificados.
curl https://34.30.155.135:31305
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Se copiarmos o kubeconfig que temos no node master para um arquivo e apontar o kubeconfig ao invés de configurar também da certo, mas com alguns ajustes.

# criando o config com o kubeconfig do nosso cluster do jeito que esta
vim config

# Não funciona por que o IP do cluster do config esta apontando para um ip interno do cluster, vamos ajustar
❯ kubectl --kubeconfig config get pods
819678 memcache.go:265] couldnt get current server API group list: Get "https://10.128.0.5:6443/api?timeout=32s": dial tcp 10.128.0.5:6443: i/o timeout

# Vamos alterar a linha abaixo para o nosso https://34.30.155.135:31305
cat config | grep server
server: https://10.128.0.5:6443

vim config

cat config | grep server
server: https://34.30.155.135:31305

kubectl --kubeconfig config get pods
E0822 06:08:03.646491 838578 memcache.go:265] couldnt get current server API group list: Get "https://34.30.155.135:31305/api?timeout=32s": tls: failed to verify certificate: x509: certificate is valid for 10.96.0.1, 10.128.0.5, not 34.30.155.135

# Vamos consertar e explicamos depois.
# Vamos forçar que o nome kubernetes seja traduzido para o ip usando o arquivo de hosts
sudo bash -c 'echo "34.30.155.135 kubernetes" >>/etc/hosts'

# Agora editamos novamente o arquivo config substituindo o IP por kubernetes
vim config
 13s 
cat config | grep server
server: https://kubernetes:31305

❯ kubectl --kubeconfig config get pods
NAME READY STATUS RESTARTS AGE
app1 1/1 Running 1 (8h ago) 3d17h
app2 1/1 Running 1 (8h ago) 3d17h
kube-bench-master-vdsxd 0/1 Completed 0 2d13h

O que aconteceu foi que esse certificado não é valido para esse IP que estamos usando. Vamos conferir no master. Se estamos batendo no apiserver precisamos conferir o certificado dele.

root@cks-master:~# cd /etc/kubernetes/pki/
root@cks-master:/etc/kubernetes/pki# ls
apiserver-etcd-client.crt apiserver.key front-proxy-ca.key
apiserver-etcd-client.key ca.crt front-proxy-client.crt
apiserver-kubelet-client.crt ca.key front-proxy-client.key
apiserver-kubelet-client.key etcd sa.key
apiserver.crt front-proxy-ca.crt sa.pub
root@cks-master:/etc/kubernetes/pki#

# Conferindo o certificado
root@cks-master:/etc/kubernetes/pki# openssl x509 -in apiserver.crt -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 7696322783196691690 (0x6aced4cb612f3cea)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = kubernetes
Validity
Not Before: Aug 15 20:36:02 2024 GMT
Not After : Aug 15 20:41:02 2025 GMT
Subject: CN = kube-apiserver
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:c5:fa:73:fd:19:53:b4:b9:8f:22:fe:68:41:9d:
28:7d:43:1e:51:10:ca:71:49:84:a9:af:29:59:4a:
2a:d4:86:72:d0:bf:a2:57:8d:70:72:ff:97:22:41:
f1:2a:8d:b5:db:96:7a:57:8e:d5:6e:10:ed:3e:d6:
ba:f4:55:7e:a4:2e:49:4b:df:9b:10:44:6a:e7:86:
c4:0b:fa:f0:2e:41:0a:08:f7:e1:6f:48:ae:ea:1a:
c2:46:66:77:f3:82:5a:41:82:d7:9f:84:08:ee:6d:
6d:92:43:a8:e5:80:b0:90:12:1b:22:3d:f6:c5:e4:
ad:55:99:37:44:3f:6a:33:7d:82:95:7f:c1:50:3e:
c2:e4:83:4d:8c:2b:3a:12:63:0d:47:42:ef:aa:89:
49:06:82:b8:2b:84:5c:24:b0:d0:10:e1:82:69:29:
32:ed:ce:74:00:4d:2e:97:5f:db:68:33:61:51:5a:
78:d9:14:ad:75:80:d2:87:84:91:e9:99:af:e0:6f:
12:44:6e:04:4d:da:95:3e:53:fc:f4:61:57:bc:96:
5c:bc:6e:30:05:ab:c6:cb:e9:c6:7e:5d:01:86:99:
32:b1:9b:85:b8:be:c5:f8:c1:0b:2d:38:54:06:a4:
5e:b4:5b:2b:83:98:ee:e0:99:96:85:5b:82:b2:f7:
a6:6d
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:
keyid:BA:A3:CC:43:88:3F:85:E0:68:92:58:7A:6B:22:55:06:74:FA:01:8E
# Observe que esses são todos os nomes que ele aceita
# O IP 34.30.155.135 não esta ai.
# Usamos kuberentes, mas poderia ser cks-master ou qualquer outro desses DNSs.
X509v3 Subject Alternative Name:
DNS:cks-master, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:10.96.0.1, IP Address:10.128.0.5
Signature Algorithm: sha256WithRSAEncryption
d0:9a:bf:c8:c5:87:69:5c:0b:4d:17:ba:38:15:d8:02:6f:f1:
7d:63:b9:6a:a9:49:1f:ed:f0:6c:4f:84:1a:94:57:40:f0:0c:
92:a4:c7:45:81:87:6f:bf:f0:8c:64:49:5c:6f:0d:3d:3d:d5:
c1:bb:ea:46:b7:8d:4f:a6:b3:7d:52:2a:b4:a3:92:de:49:ad:
42:84:99:7e:26:65:84:a9:0e:2b:de:55:c3:44:aa:ea:3a:08:
04:2f:97:e6:17:c0:fe:6f:a6:43:4b:8b:ea:e4:43:06:0f:16:
2e:d2:22:f1:b1:99:31:c7:ef:0b:3a:80:2d:de:e7:ee:f7:2d:
02:d0:cb:34:5e:0b:a9:23:ba:0a:86:5c:a6:53:51:dc:25:39:
0f:c5:39:8e:22:c4:6f:8e:69:c0:09:44:d8:00:d0:99:6e:a1:
40:31:54:11:ee:9d:04:77:ea:d2:86:12:3c:1a:fd:da:88:39:
0e:c6:77:0c:7c:50:a2:f8:c5:bb:2c:df:83:c0:74:cc:58:92:
69:58:31:9a:7e:7f:bd:62:d1:84:78:bc:62:21:07:2f:9e:5c:
d9:3d:23:82:e9:fe:95:01:8c:44:2a:b2:ed:a3:52:63:1e:a6:
41:11:a9:8e:b4:7d:d5:4b:bb:1e:61:47:6b:5e:c3:e3:98:c6:
5a:78:43:c5
-----BEGIN CERTIFICATE-----
MIIDiTCCAnGgAwIBAgIIas7Uy2EvPOowDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDA4MTUyMDM2MDJaFw0yNTA4MTUyMDQxMDJaMBkx
FzAVBgNVBAMTDmt1YmUtYXBpc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxfpz/RlTtLmPIv5oQZ0ofUMeURDKcUmEqa8pWUoq1IZy0L+iV41w
cv+XIkHxKo2125Z6V47VbhDtPta69FV+pC5JS9+bEERq54bEC/rwLkEKCPfhb0iu
6hrCRmZ384JaQYLXn4QI7m1tkkOo5YCwkBIbIj32xeStVZk3RD9qM32ClX/BUD7C
5INNjCs6EmMNR0LvqolJBoK4K4RcJLDQEOGCaSky7c50AE0ul1/baDNhUVp42RSt
dYDSh4SR6Zmv4G8SRG4ETdqVPlP89GFXvJZcvG4wBavGy+nGfl0BhpkysZuFuL7F
+MELLThUBqRetFsrg5ju4JmWhVuCsvembQIDAQABo4HYMIHVMA4GA1UdDwEB/wQE
AwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB8GA1UdIwQY
MBaAFLqjzEOIP4XgaJJYemsiVQZ0+gGOMH8GA1UdEQR4MHaCCmNrcy1tYXN0ZXKC
Cmt1YmVybmV0ZXOCEmt1YmVybmV0ZXMuZGVmYXVsdIIWa3ViZXJuZXRlcy5kZWZh
dWx0LnN2Y4Ika3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FshwQK
YAABhwQKgAAFMA0GCSqGSIb3DQEBCwUAA4IBAQDQmr/IxYdpXAtNF7o4FdgCb/F9
Y7lqqUkf7fBsT4QalFdA8AySpMdFgYdvv/CMZElcbw09PdXBu+pGt41PprN9Uiq0
o5LeSa1ChJl+JmWEqQ4r3lXDRKrqOggEL5fmF8D+b6ZDS4vq5EMGDxYu0iLxsZkx
x+8LOoAt3ufu9y0C0Ms0XgupI7oKhlymU1HcJTkPxTmOIsRvjmnACUTYANCZbqFA
MVQR7p0Ed+rShhI8Gv3aiDkOxncMfFCi+MW7LN+DwHTMWJJpWDGafn+9YtGEeLxi
IQcvnlzZPSOC6f6VAYxEKrLto1JjHqZBEamOtH3VS7seYUdrXsPjmMZaeEPF
-----END CERTIFICATE-----

NodeRestriction

Olhando esta mesma imagem podemos observar que temos o parâmetro --enable-admission-plugins.

alt text

Como havíamos comentado podemos criar e utilizar plugins para definir limites além dos impostos pelo RBAC.

Ativar o NodeRestriction limita as labels dos nodes que o kubelet modificar. Ativar esse plugin limita que um kubelet de um node somente modifique labels do seu próprio node e pods garantindo um isolamento nos workloads.

Imagine que temos uma pod que precisa rodar em um determinado tipo de node e utilizamos affinities e selectors para fazer o schedule enviar o pod para um node específico em que os pods executam em sandbox para garantir mais segurança. Se você fizer um match para schedular o pod para um node com a label grupo: nodesec (poderia ser qualquer coisa) é para lá que ele irá.

O kubelet de outro node poderia simplesmente colocar a label em seu pŕoprio node liberando que esse pod fosse schedulado para ele.

Porém existe uma label que ele não consegue alterar, mesmo sendo do seu próprio node. Toda label que comece com node-restriction.kubernetes.io/* nem o kubelet do próprio node é capaz de alterar, pois não tem permissão para isso. Se fizessemos um affinity usando essas labels (Definidas anteriormente pelo admin), garantiríamos a segurança de que um intrusor em um node não conseguiria puxar esse pod para daquele node.

Vamos testar no worker.

root@cks-worker:~# kubectl get nodes
E0822 12:24:09.174116 166884 memcache.go:265] couldnt get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
E0822 12:24:09.174665 166884 memcache.go:265] couldnt get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
E0822 12:24:09.175920 166884 memcache.go:265] couldnt get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
E0822 12:24:09.176467 166884 memcache.go:265] couldnt get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
E0822 12:24:09.178492 166884 memcache.go:265] couldnt get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
The connection to the server localhost:8080 was refused - did you specify the right host or port?

# Não conseguimos executar por que não temos um config configurado no ~/.kube/config nos workers
root@cks-worker:~# kubectl config view
apiVersion: v1
clusters: null
contexts: null
current-context: ""
kind: Config
preferences: {}
users: null

# O kubectl procura primeiro nessa variável de ambiente se tiver setada, vamos setar
root@cks-worker:~# export KUBECONFIG=/etc/kubernetes/kubelet.conf

# O kubelet não tem permissão para os namespaces
root@cks-worker:~# kubectl get ns
Error from server (Forbidden): namespaces is forbidden: User "system:node:cks-worker" cannot list resource "namespaces" in API group "" at the cluster scope

root@cks-worker:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
cks-master Ready control-plane 6d15h v1.30.3
cks-worker Ready <none> 6d15h v1.30.3

# Podemos setar labels no próprio node.
root@cks-worker:~# kubectl label node cks-worker cks/test=yes
node/cks-worker labeled

# Mas não em outro node
root@cks-worker:~# kubectl label node cks-master cks/test=yes
Error from server (Forbidden): nodes "cks-master" is forbidden: node "cks-worker" is not allowed to modify node "cks-master"

# E não podemos setar uma label inicada com node-restriction.kubernetes.io/*
root@cks-worker:~# kubectl label node cks-worker node-restriction.kubernetes.io/test=yes
Error from server (Forbidden): nodes "cks-worker" is forbidden: is not allowed to modify labels: node-restriction.kubernetes.io/test

# Cuidado por que a /* faz diferença
root@cks-worker:~# kubectl label node cks-worker node-restriction.kubernetes.io=yes
node/cks-worker labeled

root@cks-worker:~# k get nodes cks-worker --show-labels
NAME STATUS ROLES AGE VERSION LABELS
cks-worker Ready <none> 6d15h v1.30.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,cks/test=yes,kubernetes.io/arch=amd64,kubernetes.io/hostname=cks-worker,kubernetes.io/os=linux,node-restriction.kubernetes.io=yes

Sempre que necessário faça os filtros das affinities usando essas labels.