Yet Another Kubernetes Intro - Part 8 - Namespaces & Access Control

I’m down to what I think is the second to last post in my introduction to Kubernetes series. Up until the last post about storage, I think I had a pretty ok connection between the posts. However, I should maybe have planned the posts a bit better, because this one did unfortunately become a little disjointed as it covers two pretty separate things. I’m sorry about that, but it is pretty useful information.

Namespaces

The first thing I want to cover in this post is another resource type called namespaces. Namespaces is a way to group, or “section off”, resources in a cluster. This allows you to deploy several different solutions in the same cluster, and still be able to treat them as separate things. You can for example use it to deploy both your CD builds and staging builds in the same cluster without them interfering with each other. Or run a bunch of different applications, all in their own area so they don’t interfere with each other.

You can also set up role based access for namespaces to make sure that users and services are only allowed to access certain things. Something that can be very useful in a company with multiple development teams and solutions.

So, how have we gotten away with not talking about namespaces so far? Well, there is always one default namespace in a cluster. It is even called default. That is the namespace we have been working in so far. And if you don’t specifically tell kubectl that you want to work with another namespace, it will just default to that. This is also why I think I wrote that executing kubectl get all will get you “sort of” everything. Well, first of all, it doesn’t actually get you all resource types, but it also only gets you the resources in the default namespace.

Retrieving resources in namespaces

Pretty much all the kubectl resource commands can take an extra flag called --namespace or -n, allowing you to specify what namespace you want to look in. For example

kubectl get pods -n my-namespace

Or, you can add --all-namespaces or -A to get the resources from literally all namespaces. Like this

kubectl get pods --all-namespaces

Creating namespaces

Ok…so that seems useful, but how do we go about creating a namespace? Well, the easiest way is actually to use kubectl, and execute something like this

kubectl create namespace my-namespace

That will create a new namespace called my-namespace.

However, if you want to stick to YAML-files, which is definitely a more structured approach, the spec file would look something like this

apiVersion: v1
kind: Namespace
metadata:
   name: my-namespace

Creating resources in namespaces

Now that we have a namespace, how do we go about creating resources in that namespace? Well, that is actually very easy. A lot of the resource specs support a metadata.namespace property that allows you to define the namespace that the resource should be created in. For example, to create a pod in the namespace that was created above, you could use a pod spec like this

apiVersion: v1
kind: Pod
metadata:
  name: my-namespace-demo
  namespace: my-namespace
spec:
  containers:
  - name: 
    my-container
    image: zerokoll/helloworld

The other option is to modify your kubectl configuration to target the namespace that you want to work with. This is done by running a command like this

kubectl config set-context --current --namespace=my-namespace

Note: This will change the default namespace for kubectl for all future calls. So if you did want to look in the default namespace, you would need to add -n default. Or change it back by re-running the above command with the namespace parameter set to default.

Resource access between namespaces

Resources inside a namespace only see other resources inside the same namespace. However, there is one notable exception, and that is services. And that makes sense as there are a lot of reasons why you would want to be able to call services in other namespaces.

As we know, services get DNS records set up to make it easy to call them. In most cases, we just use the name of the service. However, in the post about services, I mentioned that the DNS that gets set up actually consists of several parts.

As long as you just want to talk to a service inside the current namespace, you only need the name of the service as a DNS name. But, when we introduce namespaces you can call services in other namespaces by adding the namespace name to the DNS like this <SERVICE_NAME>.<NAMESPACE>. So, if you are in the default namespace and want to call a service called my-service in the my-namespace namespace, the address would be my-service.my-namespace.

Deleting a namespace

Another neat thing about using namespaces, at least for playing around with new things, is that you can easily delete a namespace, and everything inside it, by running

kubectl delete namespace my-namespace

Role based access

Another quite important thing that I haven’t covered this far is access control. And since this is a very important aspect, I think it needs to be covered, at least at a very basic level, even if this is just an introduction.

Note: Yes, it is a somewhat hard switch from namespaces to RBAC. But as I said, this post needed to cover a couple of different things. On the other hand, you can use RBAC for namespace access. So let’s say that that is the way that these topics are connected…

But before we can start talking about RBAC, we kind of need to have a small chat about users in Kubernetes. Mainly because I’m not properly covering that part in this intro. Why? Well, first off, because it is a bit complicated, involving certificates and stuff. And secondly because it should be handled by someone that knows Kubernetes, and knows how to manage your cluster.

Kubernetes users

Having that said, it is important to know that there are 2 types of users in K8s. Service accounts and “normal users”.

Normal users

Normal users are users that are handled by an external system. This can be achieved by using things like OpenID Connect, or some other form of authentication. In the end, it allows a user to authenticate against the K8s cluster by using client certificates, bearer tokens etc. But the users are not “part of” the cluster.

For example, when using Azure AKS, you can use Azure AD to do the authentication, and then assign access based on the AD user accounts.

Note: The authentication can be set up to use several different authentication mechanisms, such as OIDC, basic authentication and even static tokens. However, it requires you to have control over the start up of the kube-api service since the authentication configuration is passed to it as commandline parameters.

Service Accounts

Service accounts are users inside the cluster, managed by the cluster. These are generally used inside the cluster, by the resources running in it. These are much easier to set up, as they are set up inside the cluster using kubectl. Like this

kubectl create serviceaccount my-account

This creates a service account called my-account, which can then be assigned roles to control its access.

Accessing the cluster as a service account

To be able to play around with the things I am talking about in this post, it might be interesting to create a service account and then use that account to access the cluster.

Note: Normally, you should use a normal user account when working with the cluster as an admin. But for demo purposes, and debugging, it can useful to authenticate as a service account.

Let’s start by creating a new service account. I’ll call mine testaccount

kubectl create serviceaccount testaccount

Once the account is created, we need to get an access token to be able to authenticate as this account. Luckily this is not too hard to do using kubectl. We just need to do the following.

Start off by “getting” the account

kubectl get serviceaccount testaccount -o yaml

Note: Adding -o yaml will give you more information about the resource in question. It will basically give you a YAML spec describing the resource.

In the output, locate the secrets entry with the name testaccount-token-XXXXX. This is the name of the secret that contains the token used for authentication. In my case, it was testaccount-token-cgnrv. With that information, it’s time to get the token by “getting” the secret

kubectl get secret testaccount-token-cgnrv -o yaml

In the output from that command, you can see the token in clear text.

__Note:__There will be more talk about contexts and a few other kubectl things in the next post. Just accept the snippets for now.

Now that we have the access token, we need to tell kubectl to use it when authenticating. This is done by configuring a set of credentials for kubectl to use, using that token. This is done by executing

kubectl config set-credentials testcredentials --token=<TOKEN>

This command creates a set of credentials called testcredentials. To use them when running kubectl commands, we need to create a new “context” by running

kubectl config set-context testcontext --cluster=docker-desktop --user=testcredentials 

This will create a new “context” using the cluster named docker-desktop (assuming that you are using Docker Desktop), and the credentials we just created.

Finally, we need to tell kubectl that we want to use that context by executing

kubectl config use-context testcontext

Any command you run now, will run as the service account testaccount.

If you want to back to you regular user, just reset your context by running

kubectl config use-context docker-desktop

Note: Assuming once again that you are using Docker Desktop…

Kubernetes RBAC

Role based access in Kubernetes depends on a few types of resources. Mainly Role, ClusterRole, RoleBinding and ClusterRoleBinding, but there are more.

Roles

Role and ClusterRole resources contain a set of permissions. The difference is that a Role is defined for a specific namespace, and a ClusterRole is defined for the whole cluster and is valid in all namespaces.

Note: Most resources are namespaced, but some, like Node for example aren’t. Resources that are not namespaced can only be controlled using ClusterRoles, while namespaced resources can be controlled by both Role and ClusterRole.

To define a role, you can use a spec similar to this

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-manager
rules:
- apiGroups: [""] 
  resources: ["pods"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

This creates a role called pod-manager. It gives anyone that has been assigned the role full access to Pod resources in the default namespace.

Basically, a role contains a set of rules. Each rule defines what API groups they relate to, what resource types inside those API groups it relates to, and what HTTP verbs the user is allowed to use.

Note: If you wanted to have a role with the same access, but gave the user the same permissions in all namespaces, you would just change the kind to ClusterRole and remove the namespace entry.

There are also built in roles that you can use. If you want to know which ones, you can run kubectl get roles --all-namespaces and kubectl get clusterroles to get a list of all the Role and ClusterRole resources available.

RoleBindings

Once you have a Role that you want to use, you need to assign it to a user. This is done using a RoleBinding or ClusterRoleBinding resource.

Once again, the RoleBinding is namespace specific, and the ClusterRoleBinding is cluster wide.

A role binding looks something like this

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: testaccount-pod-manager
  namespace: default
subjects:
- kind: ServiceAccount
  name: testaccount
  namespace: default
roleRef:
  kind: Role
  name: pod-manager
  apiGroup: rbac.authorization.k8s.io

This RoleBinding definition creates a RoleBinding called testaccount-pod-manager that assigns the pod-manager role to the service account testaccount in the default namespace.

This will make sure that the service account testaccount is only allowed to play around with pods in the default namespace. All other resources will be off limits.

A side note for Docker Desktop

One thing to note though, is that if you are using Docker Desktop, and authenticate as testaccount, you are still allowed to do anything you want. That is because in Docker Desktop, they have added a ClusterRoleBinding that gives all service accounts full access for simplicity.

If you really want to try it out in Docker Desktop, you can remove the ClusterRoleBinding called docker-for-desktop-binding by running

kubectl delete clusterrolebinding docker-for-desktop-binding

This will reset the RBAC rules and limit the access as expected. Just remember that if you want to go back to the old way, which might be needed in some cases. You can do so by adding the ClusterRoleBinding back in using the following spec

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: docker-for-desktop-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: system:serviceaccounts
  namespace: kube-system

Just remember to switch back to a context where you have access to add it before you do. Otherwise adding the binding will fail for obvious reasons. Allowing a user with limited permissions to add role bindings would kind of defeat the purpose…

Summing up RBAC

There is a lot more to learn about Kubernetes RBAC. It has support for a lot more things like policies and groups and so on. But the general gist is that you create roles that have permissions. The roles are then assigned to a user or group. Pretty much what you would expect from an RBAC solution. And yeah, the permissions are based around HTTP verbs for specific resources.

I’ll let you dive deeper into this area on your own!

zerokoll

Chris

Developer-Badass-as-a-Service at your service