There is no significant change to the *content* here, but a lot of typo fixes and commands added so that the autopilot works correctly.
13 KiB
Authentication and authorization
And first, a little refresher!
-
Authentication = verifying the identity of a person
On a UNIX system, we can authenticate with login+password, SSH keys ...
-
Authorization = listing what they are allowed to do
On a UNIX system, this can include file permissions, sudoer entries ...
-
Sometimes abbreviated as "authn" and "authz"
-
In good modular systems, these things are decoupled
(so we can e.g. change a password or SSH key without having to reset access rights)
Authentication in Kubernetes
-
When the API server receives a request, it tries to authenticate it
(it examines headers, certificates ... anything available)
-
Many authentication methods can be used simultaneously:
-
TLS client certificates (that's what we've been doing with
kubectlso far) -
bearer tokens (a secret token in the HTTP headers of the request)
-
HTTP basic auth (carrying user and password in a HTTP header)
-
authentication proxy (sitting in front of the API and setting trusted headers)
-
-
It's the job of the authentication method to produce:
- the user name
- the user ID
- a list of groups
-
The API server doesn't interpret these; it'll be the job of authorizers
Anonymous requests
-
If any authentication method rejects a request, it's denied
(
401 UnauthorizedHTTP code) -
If a request is neither accepted nor accepted by anyone, it's anonymous
-
the user name is
system:anonymous -
the list of groups is
[system:unauthenticated]
-
-
By default, the anonymous user can't do anything
(that's what you get if you just
curlthe Kubernetes API)
Authentication with TLS certificates
-
This is enabled in most Kubernetes deployments
-
The user name is derived from the
CNin the client certificates -
The groups are derived from the
Ofields in the client certificate -
From the point of view of the Kubernetes API, users do not exist
(i.e. they are not stored in etcd or anywhere else)
-
Users can be created (and given membership to groups) independently of the API
-
The Kubernetes API can be set up to use your custom CA to validate client certs
class: extra-details
Viewing our admin certificate
- Let's inspect the certificate we've been using all this time!
.exercise[
- This command will show the
CNandOfields for our certificate:kubectl config view \ --raw \ -o json \ | jq -r .users[0].user[\"client-certificate-data\"] \ | base64 -d \ | openssl x509 -text \ | grep Subject:
]
Let's break down that command together! 😅
class: extra-details
Breaking down the command
kubectl config viewshows the Kubernetes user configuration--rawincludes certificate information (which shows as REDACTED otherwise)-o jsonoutputs the information in JSON format| jq ...extracts the field with the user certificate (in base64)| base64 -ddecodes the base64 format (now we have a PEM file)| openssl x509 -textparses the certificate and outputs it as plain text| grep Subject:shows us the line that interests us
→ We are user kubernetes-admin, in group system:masters.
Authentication with tokens
-
Tokens are passed as HTTP headers:
Authorization: Bearer and-then-here-comes-the-token -
Tokens can be validated through a number of different methods:
-
static tokens hard-coded in a file on the API server
-
bootstrap tokens (special case to create a cluster or join nodes)
-
OpenID Connect tokens (to delegate authentication to compatible OAuth2 providers)
-
service accounts (these deserve more details, coming right up!)
-
Service accounts
-
A service account is a user that exists in the Kubernetes API
(it is visible with e.g.
kubectl get serviceaccounts) -
Service accounts can therefore be created / updated dynamically
(they don't require hand-editing a file and restarting the API server)
-
A service account is associated with a set of secrets
(the kind that you can view with
kubectl get secrets) -
Service accounts are generally used to grant permissions to applications, services ...
(as opposed to humans)
class: extra-details
Token authentication in practice
-
We are going to list existing service accounts
-
Then we will extract the token for a given service account
-
And we will use that token to authenticate with the API
class: extra-details
Listing service accounts
.exercise[
- The resource name is
serviceaccountorsain short:kubectl get sa
]
There should be just one service account in the default namespace: default.
class: extra-details
Finding the secret
.exercise[
- List the secrets for the
defaultservice account:kubectl get sa default -o yaml SECRET=$(kubectl get sa default -o json | jq -r .secrets[0].name)
]
It should be named default-token-XXXXX.
class: extra-details
Extracting the token
- The token is stored in the secret, wrapped with base64 encoding
.exercise[
-
View the secret:
kubectl get secret $SECRET -o yaml -
Extract the token and decode it:
TOKEN=$(kubectl get secret $SECRET -o json \ | jq -r .data.token | base64 -d)
]
class: extra-details
Using the token
- Let's send a request to the API, without and with the token
.exercise[
-
Find the ClusterIP for the
kubernetesservice:kubectl get svc kubernetes API=$(kubectl get svc kubernetes -o json | jq -r .spec.clusterIP) -
Connect without the token:
curl -k https://$API -
Connect with the token:
curl -k -H "Authorization: Bearer $TOKEN" https://$API
]
class: extra-details
Results
-
In both cases, we will get a "Forbidden" error
-
Without authentication, the user is
system:anonymous -
With authentication, it is shown as
system:serviceaccount:default:default -
The API "sees" us as a different user
-
But neither user has any right, so we can't do nothin'
-
Let's change that!
Authorization in Kubernetes
-
There are multiple ways to grant permissions in Kubernetes, called authorizers:
-
Node Authorization (used internally by kubelet; we can ignore it)
-
Attribute-based access control (powerful but complex and static; ignore it too)
-
Webhook (each API request is submitted to an external service for approval)
-
Role-based access control (associates permissions to users dynamically)
-
-
The one we want is the last one, generally abbreviated as RBAC
Role-based access control
-
RBAC allows to specify fine-grained permissions
-
Permissions are expressed as rules
-
A rule is a combination of:
-
verbs like create, get, list, update, delete ...
-
resources (as in "API resource", like pods, nodes, services ...)
-
resource names (to specify e.g. one specific pod instead of all pods)
-
in some case, subresources (e.g. logs are subresources of pods)
-
From rules to roles to rolebindings
-
A role is an API object containing a list of rules
Example: role "external-load-balancer-configurator" can:
- [list, get] resources [endpoints, services, pods]
- [update] resources [services]
-
A rolebinding associates a role with a user
Example: rolebinding "external-load-balancer-configurator":
- associates user "external-load-balancer-configurator"
- with role "external-load-balancer-configurator"
-
Yes, there can be users, roles, and rolebindings with the same name
-
It's a good idea for 1-1-1 bindings; not so much for 1-N ones
Cluster-scope permissions
-
API resources Role and RoleBinding are for objects within a namespace
-
We can also define API resources ClusterRole and ClusterRoleBinding
-
These are a superset, allowing to:
-
specify actions on cluster-wide objects (like nodes)
-
operate across all namespaces
-
-
We can create Role and RoleBinding resources within a namespaces
-
ClusterRole and ClusterRoleBinding resources are global
Pods and service accounts
-
A pod can be associated to a service account
-
by default, it is associated to the
defaultservice account -
as we've seen earlier, this service account has no permission anyway
-
-
The associated token is exposed into the pod's filesystem
(in
/var/run/secrets/kubernetes.io/serviceaccount/token) -
Standard Kubernetes tooling (like
kubectl) will look for it there -
So Kubernetes tools running in a pod will automatically use the service account
In practice
-
We are going to create a service account
-
We will use an existing cluster role (
view) -
We will bind together this role and this service account
-
Then we will run a pod using that service account
-
In this pod, we will install
kubectland check our permissions
Creating a service account
-
We will call the new service account
viewer(note that nothing prevents us from calling it
view, like the role)
.exercise[
-
Create the new service account:
kubectl create serviceaccount viewer -
List service accounts now:
kubectl get serviceaccounts
]
Binding a role to the service account
-
Binding a role = creating a rolebinding object
-
We will call that object
viewercanview(but again, we could call it
view)
.exercise[
- Create the new role binding:
kubectl create rolebinding viewercanview \ --clusterrole=view \ --serviceaccount=default:viewer
]
It's important to note a couple of details in these flags ...
Roles vs Cluster Roles
-
We used
--clusterrole=view -
What would have happened if we had used
--role=view?-
we would have bound the role
viewfrom the local namespace
(instead of the cluster roleview) -
the command would have worked fine (no error)
-
but later, our API requests would have been denied
-
-
This is a deliberate design decision
(we can reference roles that don't exist, and create/update them later)
Users vs Service Accounts
-
We used
--serviceaccount=default:viewer -
What would have happened if we had used
--user=default:viewer?-
we would have bound the role to a user instead of a service account
-
again, the command would have worked fine (no error)
-
... but our API requests would have been denied later
-
-
What's about the
default:prefix?-
that's the namespace of the service account
-
yes, it could be inferred from context, but ...
kubectlrequires it
-
Testing
- We will run an
alpinepod and installkubectlthere
.exercise[
-
Run a one-time pod:
kubectl run eyepod --rm -ti --restart=Never \ --serviceaccount=viewer \ --image alpine -
Install
curl, then use it to installkubectl:apk add --no-cache curl URLBASE=https://storage.googleapis.com/kubernetes-release/release KUBEVER=$(curl -s $URLBASE/stable.txt) curl -LO $URLBASE/$KUBEVER/bin/linux/amd64/kubectl chmod +x kubectl
]
Running kubectl in the pod
- We'll try to use our
viewpermissions, then to create an object
.exercise[
-
Check that we can, indeed, view things:
./kubectl get all -
But that we can't create things:
./kubectl run tryme --image=nginx -
Exit the container with
exitor^D
]
Testing directly with kubectl
-
We can also check for permission with
kubectl auth can-i:kubectl auth can-i list nodes kubectl auth can-i create pods kubectl auth can-i get pod/name-of-pod kubectl auth can-i get /url-fragment-of-api-request/ kubectl auth can-i '*' services -
And we can check permissions on behalf of other users:
kubectl auth can-i list nodes \ --as some-user kubectl auth can-i list nodes \ --as system:serviceaccount:<namespace>:<name-of-service-account>