Kubernetes Authentication Options using KubeVirt Client Library
- Introduction
- Requirements
- Setup
- Running our application externally leveraging a kubeconfig file
- Running in a Kubernetes Cluster
- Extending RBAC Role across Namespaces
- Creating Custom RBAC Roles
- Conclusion
- References
Introduction
Most interaction with the KubeVirt service can be handled using the virtctl command, or raw yaml applied to your Kubernetes cluster. But what if you want to have more direct programmatic control over the instantiation and management of those virtual machines? The KubeVirt project supplies a Go client library for interacting with KubeVirt called client-go. This library allows you to write your own applications that interact directly with the KubeVirt api quickly and easily.
In this post, we will use a simple application to demonstrate how the KubeVirt client library authenticates with your Kubernetes cluster both in and out of your cluster. This application is based on the example application in the âclient-goâ library with a few small modifications to it, to allow for running both locally and within in the cluster. This tutorial assumes you have some knowledge of Go, and is not meant to be a Go training doc.
Requirements
In order to compile and run the test application locally you will need to have the Go programming language installed on your machine. If you do not have the latest version of Go installed, follow the steps on the Downloads page of the Go web site before proceeding with the rest of the steps in this blog. The steps listed here were tested with Go version 1.16.
You will need a Kubernetes cluster running with the KubeVirt operator installed. If you do not have a cluster available, the easiest way to do this is to follow the steps outlined in the Quick Start with Minikube lab.
The example application we will be using to demonstrate the authentication methods lists out the VMI and VM instances in your cluster in the current namespace. If you do not have any running VMs in your cluster, be sure to create at least one new virtual machine instance in your cluster. For guidance in creating a quick test vm see the Use KubeVirt lab.
Setup
Compiling our test application
Start by cloning the example application repo https://github.com/xphyr/kubevirt-apiauth and compiling our test application:
git clone https://github.com/xphyr/kubevirt-apiauth.git
cd kubevirt-apiauth/listvms
go build
Once the program compiles, test to ensure that the application compiled correctly. If you have a working Kubernetes context, running this command may return some values. If you do not have a current context, you will get an error. This is OK, we will discuss authentication next.
$ ./listvms
2021/06/23 16:51:28 cannot obtain KubeVirt vm list: Get "http://localhost:8080/apis/kubevirt.io/v1alpha3/namespaces/default/virtualmachines": dial tcp 127.0.0.1:8080: connect: connection refused
As long as the program runs, you are all set to move onto the next step.
Running our application externally leveraging a kubeconfig file
The default authentication file for Kubernetes is the kubeconfig file. We will not be going into details of this file, but you can click the link to goto the documentation on the kubeconfig file to learn more about it. All you need to know at this time is that when you use the kubectl command you are using a kubeconfig file for your authentication.
Using the default kubeconfig
If you havenât already done so, validate that you have a successful connection to your cluster with the âkubectlâ command:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane,master 5d21h v1.20.7
We now have a valid kubeconfig. On *nix OS such as Linux and OSX, this file is stored in your home directory at ~/.kube/config
. You should now be able to run our test application and get some results (assuming you have some running vms in your cluster).
$ ./listvms/listvms
Type Name Namespace Status
VirtualMachine testvm default false
VirtualMachineInstance testvm default Scheduled
This is great, but there is an issue. The authentication method we used is your primary Kubernetes authentication. It has roles and permissions to do many different things in your k8s cluster. Wouldnât it be better if we could scope that authentication and ensure that your application had a dedicated account, with only the proper permissions to interact with just what your application will need. This is what Kubernetes Service Accounts are for.
Service Accounts are accounts for processes as opposed to users. By default they are scoped to a namespace, but you can give service accounts access to other namespaces through RBAC rules that we will discuss later. In this demo, we will be using the âdefaultâ project/namespace, so the service account we create will be initially scoped only to this namespace.
Start by creating a new service account called âmykubevirtrunnerâ using your default Kubernetes account:
$ kubectl create sa mykubevirtrunner
$ kubectl describe sa mykubevirtrunner
Name: mykubevirtrunner
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: mykubevirtrunner-token-pd2mq
Tokens: mykubevirtrunner-token-pd2mq
Events: <none>
In the describe output you can see that a token and a mountable secret have been created. Letâs take a look at the contents of the secret:
$ kubectl describe secret mykubevirtrunner-token-pd2mq
Name: mykubevirtrunner-token-pd2mq
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: mykubevirtrunner
kubernetes.io/service-account.uid: f401493b-658a-489d-bcce-0ccce39160a0
Type: kubernetes.io/service-account-token
Data
====
namespace: 7 bytes
token: eyJhbGciOiJS...
ca.crt: 1111 bytes
The data listed for the âtokenâ key is the information we will use in the next step, your output will be much longer, it has been truncated for this document. Ensure when copying the value that you get the entire token value.
Creating a kubeconfig for the service account
We will create a new kubeconfig file that leverages the service account and token we just created. The easiest way to do this is to create an empty kubeconfig file, and use the âkubectlâ command to log in with the new token. Open a NEW terminal window. This will be the window we use for the service account. In this new terminal window start by setting the KUBECONFIG environment variable to point to a file in our local directory, and then using the âkubectlâ command to generate a new kubeconfig file:
export KUBECONFIG=$(pwd)/sa-kubeconfig
kubectl config set-cluster minikube --server=https://<update IP address>:8443 --insecure-skip-tls-verify
kubectl config set-credentials mykubevirtrunner --token=<paste token from last step here>
kubectl config set-context minikube --cluster=minikube --namespace=default --user=mykubevirtrunner
kubectl config use-context minikube
We can test that the new kubeconfig file is working by running a kubectl command:
$ kubectl get pods
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:mykubevirtrunner" cannot list resource "pods" in API group "" in the namespace "default"
Note that the âUserâ is now listed as âsystem:serviceaccount:default:mykubevirtrunnerâ so we know we are using our new service account. Now try running our test program and note that it is using the service account as well:
$ listvms/listvms
2021/07/07 14:53:23 cannot obtain KubeVirt vm list: virtualmachines.kubevirt.io is forbidden: User "system:serviceaccount:default:mykubevirtrunner" cannot list resource "virtualmachines" in API group "kubevirt.io" in the namespace "default"
You can see we are now using our service account in our application, but that service account doesnât have the right permissions⌠We now need to assign a role to our service account to give it the proper API access. We will start simple and give the service account the kubevirt.io:view role, which will allow the service account to see the KubeVirt objects within the âdefaultâ namespace:
$ kubectl create clusterrolebinding kubevirt-viewer --clusterrole=kubevirt.io:view --serviceaccount=default:mykubevirtrunner
clusterrolebinding.rbac.authorization.k8s.io/kubevirt-viewer created
Now run the listvms command again:
./listvms/listvms
Type Name Namespace Status
VirtualMachineInstance vm-fedora-ephemeral myvms Running
Success! Our application is now using the service account that we created for authentication to the cluster. The service account can be extended by adding additional default roles to the account, or by creating custom roles that limit the scope of the service account to only the exact actions you want to take. When you install KubeVirt you get a set of default roles including âViewâ, âEditâ and âAdminâ. Additional details about these roles are available here: KubeVirt Default RBAC Cluster Roles
Running in a Kubernetes Cluster
So all of this is great if you want to run the application outside of your cluster ⌠but what if you want your application to run INSIDE you cluster. You could create a kubeconfig file, and add it to your namespace as a secret and then mount that secret as a volume inside your pod, but there is an easier way that continues to leverage the service account that we created. By default Kubernetes creates a few environment variables for every pod that indicate that the container is running within Kubernetes, and it makes a Kubernetes authentication token for the service account that the container is running as available at /var/run/secrets/kubernetes.io/serviceaccount/token. The client-go KubeVirt library can detect that it is running inside a Kubernetes hosted container and will transparently use the authentication token provided with no additional configuration needed.
A container image with the listvms binary is available at quay.io/markd/listvms. We can start a copy of this container using the deployment yaml file located in the âlistvms/listvms_deployment.yamlâ file.
Switch back to your original terminal window that is using your primary kubeconfig file, and using the âkubectlâ command deploy one instance of the test pod, and then check the logs of the pod:
$ kubectl create -f listvms/listvms_deployment.yaml
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
listvms-7b8f865c8d-2zqqn 1/1 Running 0 7m30s
virt-launcher-vm-fedora-ephemeral-4ljg4 2/2 Running 0 24h
$ kubectl logs listvms-7b8f865c8d-2zqqn
2021/07/07 19:06:42 cannot obtain KubeVirt vm list: virtualmachines.kubevirt.io is forbidden: User "system:serviceaccount:default:default" cannot list resource "virtualmachines" in API group "kubevirt.io" in the namespace "default"
NOTE: Be sure to deploy this demo application in a namespace that contains at least one running VM or VMI.
The application is unable to run the operation, because it is running as the default service account in the âdefaultâ namespace. If you remember previously we created a service account in this namespace called âmykubevirtrunnerâ. We need only update the deployment to use this service account and we should see some success. Use the âkubectl edit deployment/listvmsâ command to update the container spec to include the âserviceAccount: mykubevirtrunnerâ line as show below:
spec:
containers:
- name: listvms
image: quay.io/markd/listvms
serviceAccount: mykubevirtrunner
securityContext: {}
schedulerName: default-scheduler
This change will trigger Kubernetes to redeploy your pod, using the new serviceAccount. We should now see some output from our program:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
listvms-7b8f865c8d-2qzzn 1/1 Running 0 7m30s
virt-launcher-vm-fedora-ephemeral-4ljg4 2/2 Running 0 24h
$ kubectl logs listvms-7b8f865c8d-2qzzn
Type Name Namespace Status
VirtualMachineInstance vm-fedora-ephemeral myvms Running
awaiting signal
Extending RBAC Role across Namespaces
As currently configured, the mykubevirtrunner service account can only âviewâ KubeVirt resources within its own namespace. If we want to extend that ability to other namespaces, we can add the view role for other namespaces to the mykubevirtrunner serviceAccount.
kubectl create namespace myvms
<launch an addition vm here>
kubectl create clusterrolebinding kubevirt-viewer --clusterrole=kubevirt.io:view --serviceaccount=default:mykubevirtrunner -n myvms
We can test that the ServiceAccount has been updated to also have permissions to view in the âmyvmsâ namespace by running our listvms command one more time, this time passing in the optional flag ânamespaces. Switch to your terminal window that is using the service account kubeconfig file and run the following command:
$ listvms/listvms --namespaces myvms
additional namespaces to check are: myvms
Checking the following namespaces: [default myvms]
Type Name Namespace Status
VirtualMachine testvm default false
VirtualMachineInstance testvm default Scheduled
VirtualMachine testvm myvms false
You can see that now, the ServiceAccount can view the vm and vmi that are in both the default namespace as well as the myvms namespace.
Creating Custom RBAC Roles
In this demo we used RBAC roles created as part of the KubeVirt install. You can also create custom RBAC roles for KubeVirt. Documentation on how this can be done is available in the KubeVirt documentation Creating Custom RBAC Roles
Conclusion
It is possible to control and manage your KubeVirt machines with the use of Kubernetes service accounts and the âclient-goâ library. When using service accounts, you want to ensure that the account has the minimum role or permissions to do itâs job to ensure the security of your cluster. The âclient-goâ library gives you options on how you authenticate with your Kubernetes cluster, allowing you to deploy your application both in and out of your Kubernetes cluster.