This is a text-only version of the following page on https://raymii.org:
---
Title : Create Kubernetes user restricted to one namespace with resource limits
Author : Remy van Elst
Date : 29-07-2024 04:39
URL : https://raymii.org/s/tutorials/Create_Kubernetes_user_restricted_to_one_namespace_with_resource_limits.html
Format : Markdown/HTML
---
This guide shows you how to use Role-based access control (RBAC) to create a user account that only has rights for one specific namespace. I'll also show you how to limit the resource usage of that `Namespace`. Last but not least, I'll also show you how to create a `kubeconfig` file for that specific user.
Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:
I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!
Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.
You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!
You can use this guide to set up a limited resource for one team for example,
to make sure they cannot mess up other parts of the cluster or slow down
other stuff due to claiming too many resources. My sysadmin / devops
experience tells me that no matter what, if there are no limits or guard
rails, developers (especially web and PHP developers, they are the worst
kind, see [this article for example, a hacked cluster and all the
web-developers did was scale up due to "performance". An actually skilled
developer would look into the problem, do profiling and fix pain points
before scaling out (and have their basic security in order).](https://it-notes.dragas.net/2024/07/04/from-cloud-chaos-to-freebsd-efficiency/#lessons-learned))
will mess up operations, no matter how well intended. Better to protect them
against themselves than to be woken up in the middle of the night.
![dashboard](/s/inc/img/k3s-namespace-2.png)
> The Kubernetes Dashboard for this one user with permissions in only that namespace
![resource quotas](/s/inc/img/k3s-namespace-1.png)
> Resource limits in the namespace
To read all my [Kubernetes posts, click here](/s/tags/kubernetes.html). I'm
using Kubernetes / k3s version ` v1.30.2+k3s1` and for the purposes of this
guide I assume you have `kubectl` set up and working with an admin user.
The [official documentation]
(https://web.archive.org/web/20240729082142/https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
is a great resource which explains this stuff in more detail. This is a
practical guide for a single purpose (namely to create a namespace for a user
with resource limits and denying the user access to further namespaces).
Make sure to also read up on [privilege escalation]
(https://web.archive.org/web/20240729102458/https://kubernetes.io/docs/reference/access-authn-authz/rbac/#privilege-escalation-prevention-and-bootstrapping)
in Kubernetes.
A few terms require some more explanation:
- `Role`: A role contains rules that represent a set of permissions. A role is
used to grant access to resources within a namespace. Permissions are
purely additive (there are no "deny" rules). A `Role` is always Namespaced
(as opposed to a `ClusterRole`)
- `RoleBinding`: A role binding is used to grant the permissions defined in a
role to a user or set of users. It holds a list of subjects (users, groups,
or service accounts), and a reference to the role being granted
- `Service Account`: account meant for processes, which run in pods
(something that talks to Kubernetes). Or in our case, `kubeconfig`
Create a folder in which we will put all the `yaml` files:
mkdir user-demo
cd user-demo
### Creating a Namespace with a `ResourceQuota`
Create a file for your namespace:
vim namespace.yml
Contents:
apiVersion: v1
kind: Namespace
metadata:
name: user-demo
Apply it:
kubectl apply -f namespace.yml
Output:
namespace/user-demo created
Create a file with the resource limits for this namespace:
vim resource-quota.yml
Contents:
apiVersion: v1
kind: ResourceQuota
metadata:
name: user-demo-quota
namespace: user-demo
spec:
hard:
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
Apply the quota:
kubectl apply -f resource-quota.yml
The above `ResourceQuota` places these requirements on the `user-demo` namespace:
- For every Pod in the namespace, each container must have a memory request,
memory limit, cpu request, and cpu limit.
- The memory request total for all Pods in that namespace must not exceed 800
MiB. The memory limit total for all Pods in that namespace must not exceed
950 MiB.
- The CPU request total for all Pods in that namespace must not exceed 1 cpu.
- The CPU limit total for all Pods in that namespace must not exceed 2 cpu.
This means that in your `Deployment` yaml files you must make sure there are
resource limits. Below is an example.
### Create a Deployment with resource limits
We'll create two deployments, one of which will succeed and one of which will
fail due to resource limitations.
Create a file for our test deployments:
vim deployment.yml
Contents:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-1
namespace: user-demo
spec:
replicas: 1
selector:
matchLabels:
app: echo-1
template:
metadata:
labels:
app: echo-1
spec:
containers:
- name: echo-1
image: ealen/echo-server:latest
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /?echo_code=200
port: 80
readinessProbe:
httpGet:
path: /?echo_code=200
port: 80
resources:
limits:
cpu: 800m
memory: 600Mi
requests:
cpu: 600m
memory: 400Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-2
namespace: user-demo
spec:
replicas: 1
selector:
matchLabels:
app: echo-2
template:
metadata:
labels:
app: echo-2
spec:
containers:
- name: echo-2
image: ealen/echo-server:latest
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /?echo_code=200
port: 80
readinessProbe:
httpGet:
path: /?echo_code=200
port: 80
resources:
limits:
cpu: 800m
memory: 600Mi
requests:
cpu: 600m
memory: 400Mi
You might wonder what `cpu:600m` means. In our case the limit is `1` and this sort of means, `0.6`. I've used the [echo-server](/s/tutorials/Kubernetes_k3s_Ingress_for_different_domains_like_virtual_hosts.html)
in earlier guides.
Apply the file:
kubectl apply -f deployment.yml
Output
deployment.apps/echo-1 created
deployment.apps/echo-2 created
If you query the status of the deployments you'll see the first being created
successfully, the second is not:
kubectl -n user-demo get deployments
Output:
NAME READY UP-TO-DATE AVAILABLE AGE
echo-1 1/1 1 1 54s
echo-2 0/1 0 0 54s
To figure out why, query the `Deployment`:
kubectl -n user-demo describe deployment echo-2
Output, trimmed:
Conditions:
Type Status Reason
---- ------ ------
Progressing True NewReplicaSetCreated
Available False MinimumReplicasUnavailable
ReplicaFailure True FailedCreate
Something is wrong with our `ReplicaSet`. Let's query that:
$ kubectl -n user-demo get replicaset
Output:
NAME DESIRED CURRENT READY AGE
echo-1-777547b855 1 1 1 2m11s
echo-2-6fbd7564f7 1 0 0 2m11s
Query the second `ReplicaSet`:
kubectl -n user-demo describe replicaset echo-2-6fbd7564f7
Output, trimmed:
Name: echo-2-6fbd7564f7
Namespace: user-demo
[...]
Replicas: 0 current / 1 desired
Pods Status: 0 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
[...]
Limits:
cpu: 800m
memory: 600Mi
Requests:
cpu: 600m
memory: 400Mi
[...]
Conditions:
Type Status Reason
---- ------ ------
ReplicaFailure True FailedCreate
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedCreate 2m31s replicaset-controller Error creating: pods "echo-2-6fbd7564f7-bbnld" is forbidden: exceeded quota: user-demo-quota, requested: requests.cpu=600m, used: requests.cpu=600m, limited: requests.cpu=1
Warning FailedCreate 2m31s replicaset-controller Error creating: pods "echo-2-6fbd7564f7-vhfdk" is forbidden: exceeded quota: user-demo-quota, requested: requests.cpu=600m, used: requests.cpu=600m, limited: requests.cpu=1
Warning FailedCreate 2m31s replicaset-controller Error creating: pods "echo-2-6fbd7564f7-6qpht" is forbidden: exceeded quota: user-demo-quota, requested: requests.cpu=600m, used: requests.cpu=600m, limited: requests.cpu=1
[...]
The error is quite clear, we're out of resources! Exactly what we want. There is more info on resource limits in the documentation, you can also limit [the amount of Pods](https://web.archive.org/web/20240727083328/https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-pod-namespace/) and [here you can find other limits, for Storage or Ingress or Services](https://web.archive.org/web/20240729085730/https://kubernetes.io/docs/concepts/policy/resource-quotas/) for example.
Here is the example resource quota extended with storage, services and ingresses:
spec:
hard:
requests.cpu: "1"
requests.memory: "1Gi"
limits.cpu: "2"
limits.memory: "2Gi"
services.loadbalancers: "2"
count/ingresses.networking.k8s.io: "2"
persistentvolumeclaims: "4"
requests.storage: "8Gi"
Then, when creating more `Loadbalancers` or `Ingresses`, you will receive an
error like so:
Error from server (Forbidden): error when creating "deployment.yml": services "echo-2-service-2" is forbidden: exceeded quota: user-demo-quota, requested: services.loadbalancers=1, used: services.loadbalancers=2, limited: services.loadbalancers=2
Error from server (Forbidden): error when creating "deployment.yml": ingresses.networking.k8s.io "echo-2-ingress-2" is forbidden: exceeded quota: user-demo-quota, requested: count/ingresses.networking.k8s.io=1, used: count/ingresses.networking.k8s.io=2, limited: count/ingresses.networking.k8s.io=2
Delete the test deployments to free up our resources:
kubectl -n user-demo delete -f deployment.yml
### Creating a user restricted to one namespace
Now that we have a namespace with resource limits, we can create a user bound to that namespace.
As we stated earlier, you need a few pieces, not "just" a user. Start with the
`ServiceAccount`, this is comparable to your "User". I'm assuming your
namespace `user-demo` is already created, if not, see the top of this page.
I'm going to use the term `User` interchangeably with `ServiceAccount` in the
rest of this guide.
Create a file for the user:
vim user1-servivceaccount.yml
Contents:
apiVersion: v1
kind: ServiceAccount
metadata:
name: user1
namespace: user-demo
Apply the file to create the service account:
kubectl apply -f user1-serviceaccount.yml
Output:
serviceaccount/user1 created
Next up is the `Role`, this file describes the specific permissions for the
`Role`. The role will be later bound to the `ServiceAccount`. That is a
separate process because one role can be bound to more than one user.
vim namespace-admin-role.yml
Contents:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: namespace-admin
namespace: user-demo
rules:
- apiGroups: ["", "extensions", "apps"]
resources: ["*"]
verbs: ["*"]
- apiGroups: ["batch"]
resources:
- jobs
- cronjobs
verbs: ["*"]
This role grants permissions to perform almost all actions within the
namespace an Admin User would do.
You might want to have a more limited profile, for example, just a
`Deployment` admin user:
[...]
name: deployment-admin
rules:
- apiGroups: ["", "extensions", "apps"]
resources: ["deployments", "replicasets", "pods", "services", "ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Apply the file:
kubectl apply -f namespace-admin-role.yml
Output:
role.rbac.authorization.k8s.io/namespace-admin created
Last step in user / role creation is the `RoleBinding`. This is what couples the `Role` to the User.
vim namespace-admin-rolebinding.yml
Contents:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: namespace-admin-rolebinding
namespace: user-demo
subjects:
- kind: ServiceAccount
name: user1
namespace: user-demo
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: namespace-admin
You can have multiple `subjects` but for our example we only need one. Apply the file:
kubectl apply -f namespace-admin-rolebinding.yml
Output:
rolebinding.rbac.authorization.k8s.io/namespace-admin-rolebinding created
One more step remaining to actually use the new user.
### Creating a kubeconfig file for the user
To use the new user with permissions, the last step is to create a
`kubeconfig` file. You can give that file to someone and they then can use
`kubectl` within that namespace, or create a token for the dashboard for
example.
Start by creating a long-lived token (10 years) for the user. Update the
duration to suite your needs.
kubectl create token user1 -n user-demo --duration=87600h
Output:
eyJhbGciOiJ[...]VwRTkA
Create a `user1-kubeconfig` file:
vim user1-kubeconfig
Contents:
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority-data:
server: https://
name: default
contexts:
- context:
cluster: default
namespace: user-demo
user: user1
name: user1-context
current-context: user1-context
users:
- name: user1
user:
token:
The `cluster.server` can be found using this command:
kubectl cluster-info
Output:
Kubernetes control plane is running at https://192.0.2.60:6443
The token you just created should be pasted into `user.token`. The last
part, `certificate-authority-data` can be (from Kubernetes 1.24 and up)
queried from a `configMap`:
kubectl get configmap kube-root-ca.crt -n kube-public -o jsonpath="{['data']['ca\\.crt']}" | base64 -w 0
Output:
LS0[...]tLS0K
You can now use this file with `kubectl` by providing the `--kubeconfig` parameter:
kubectl --kubeconfig ./kubeconfig.yaml -n user-demo get pods
If you try to list another namespace for which the account has no permissions, you will receive errors:
kubectl --kubeconfig ./kubeconfig.yaml -n default get all
Output:
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:user-demo:user1" cannot list resource "pods" in API group "" in the namespace "default"
Error from server (Forbidden): replicationcontrollers is forbidden: User "system:serviceaccount:user-demo:user1" cannot list resource "replicationcontrollers" in API group "" in the namespace "default"
You can also create the deployment (from earlier in this article) as this user:
kubectl --kubeconfig ./kubeconfig.yaml -n user-demo apply -f deployment.yml
Output:
deployment.apps/echo-1 created
deployment.apps/echo-2 created
Querying, deleting and scaling works as you would expect:
kubectl --kubeconfig ./kubeconfig.yaml -n user-demo get deployment
Output:
NAME READY UP-TO-DATE AVAILABLE AGE
echo-1 1/1 1 1 40s
echo-2 0/1 0 0 40s
kubectl --kubeconfig ./kubeconfig.yaml -n user-demo get pods
Output:
NAME READY STATUS RESTARTS AGE
echo-1-777547b855-ftzzk 1/1 Running 0 41s
kubectl --kubeconfig ./kubeconfig.yaml -n user-demo delete deployment echo-2
Output:
deployment.apps "echo-2" deleted
kubectl --kubeconfig ./kubeconfig.yaml -n user-demo scale deployment echo-1 --replicas 2
Output:
deployment.apps/echo-1 scaled
You can use the token to login to the Kubernetes Dashboard as well, but you
cannot port-forward:
kubectl --kubeconfig ./kubeconfig.yaml -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443
Output:
Error from server (Forbidden): services "kubernetes-dashboard-kong-proxy" is forbidden: User "system:serviceaccount:user-demo:user1" cannot get resource "services" in API group "" in the namespace "kubernetes-dashboard"
This is expected because we do not have permissions in any other namespace.
Set up an Ingress or NodePort for the dashboard in a trusted environment and
you can use the token to login.
---
License:
All the text on this website is free as in freedom unless stated otherwise.
This means you can use it in any way you want, you can copy it, change it
the way you like and republish it, as long as you release the (modified)
content under the same license to give others the same freedoms you've got
and place my name and a link to this site with the article as source.
This site uses Google Analytics for statistics and Google Adwords for
advertisements. You are tracked and Google knows everything about you.
Use an adblocker like ublock-origin if you don't want it.
All the code on this website is licensed under the GNU GPL v3 license
unless already licensed under a license which does not allows this form
of licensing or if another license is stated on that page / in that software:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Just to be clear, the information on this website is for meant for educational
purposes and you use it at your own risk. I do not take responsibility if you
screw something up. Use common sense, do not 'rm -rf /' as root for example.
If you have any questions then do not hesitate to contact me.
See https://raymii.org/s/static/About.html for details.