Raymii.org
Quis custodiet ipsos custodes?Home | About | All pages | Cluster Status | RSS Feed
Create Kubernetes user restricted to one namespace with resource limits
Published: 29-07-2024 04:39 | Author: Remy van Elst | Text only version of this article
Table of Contents
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).) 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.
The Kubernetes Dashboard for this one user with permissions in only that namespace
Resource limits in the namespace
To read all my Kubernetes posts, click here. 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 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 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). ARole
is always Namespaced (as opposed to aClusterRole
)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 grantedService 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
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 and here you can find other limits, for Storage or Ingress or Services 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: <base64-encoded-CA-cert>
server: https://<your-cluster-endpoint>
name: default
contexts:
- context:
cluster: default
namespace: user-demo
user: user1
name: user1-context
current-context: user1-context
users:
- name: user1
user:
token: <your-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.
Tags: armbian , cloud , k3s , k8s , kubernetes , linux , permissions , rbac , resourcequota , role , rolebinding , tutorials