Kubernetes/Kubernetes Workshop/Manageability and Security
At the end of this module, you should be able to:
- Implement security practices when using Kubernetes.
Step 1: Updating container images
Kubernetes (k8s) is a new execution environment for code, similar to a new operating system, albeit with familiar roots. It is powerful, flexible, and complex, and like other software, it gets new features with each new version.
A new version typically also addresses vulnerabilities that can be used by an attacker to abuse the system. As k8s becomes more successful, guidelines on using and configuring k8s for a safe experience are released.
How to Update a Container Image
Over time software (including base images) installed in a Docker container will become outdated. So far, you have used base images downloaded from Docker Hub without much thought as to their origin or patch level.
Using base images regardless of their origin is sufficient for learning purposes; however, it is not ideal for production purposes. Worthy of note is how you have used images under the control of specific, trustworthy organizations such as Ubuntu and Debian. Of the many Docker images on Docker Hub, most images contain vulnerabilities, many of them with high severity.
Since using an available image is often the quickest way to get an application up and running, it is tempting to do so but quite possibly a bad idea for a production system.
Moreover, attackers have started infiltrating docker registries. If you do use any external images, be sure to exercise some caution about their purpose and the data you store in these systems.
You can perform a quick check on the images you have been using:
1. Create a Dockerfile:
Dockerfile FROM ubuntu ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get upgrade -y
2. Reducing the
/etc/apt/sources.list file to only security sources, will give you an output similar to:
The following packages will be upgraded: gcc-10-base libgcc-s1 libgnutls30 libseccomp2 libstdc++6 perl-base
It is good practice to include Operating System (OS) updates in the Dockerfile. Rebuilding and redeploying application images following standards set by your security team is also a good security practice.
Step 2: Running the container with limited privileges
By default, containers run as the root user, which applies to all the applications you have run so far. 3. Create a Dockerfile:
Dockerfile FROM ubuntu RUN apt-get update && apt-get install -y ca-certificates python3 ADD printuid.py / RUN chmod u+x ./printuid.py CMD ["/printuid.py"]
4. Create a Python script:
printuid.py #!/usr/bin/python3 import os print(os.getuid())
5. Build and run your Docker image:
$ docker build --tag <tag> . $ docker run <image> 0
Getting an output of 0 implies that you are running the container as the root user.
Running Containers on minikube You have been running images on minikube by first uploading images to Docker Hub and then pulling the images from Docker Hub to be run on minikube. However, minikube can run local images, but this process uses a different Docker installation that keeps its images.
1. Build this new Docker installation by running the commands listed below:
$ eval $(minikube docker-env) $ docker images
2. Rebuild and retag the image so minikube can access the image locally:
$ docker build --tag <tag> . $ docker images
apiVersion: batch/v1 kind: Job metadata: name: <name> spec: template: spec: containers: - name: <name> image: <image_name>:<tag> imagePullPolicy: Never restartPolicy: Never
3. Now run the image as a job and check the output. Your pod’s name would be different:
$ minikube start $ kubectl apply -f job.yaml $ kubectl get pods NAME READY STATUS RESTARTS AGE printuid-h56kk 0/1 Completed 0 36s $ kubectl logs printuid-h56kk 0
4. You are running this script as the root user, but you can change the user in the Dockerfile:
FROM ubuntu RUN apt-get update && apt-get install -y ca-certificates python3 ADD printuid.py / USER nobody CMD ["python3”, “printuid.py"]
5. Rebuild and run your docker file and rerun to check:
$ docker build --tag <tag> . $ docker run <image> 65534
6. Run your image as a job and check the output. Your pod’s name would be different:
$ kubectl apply -f job.yaml $ kubectl get pods NAME READY STATUS RESTARTS AGE printuid-h56kk 0/1 Completed 0 36s $ kubectl logs printuid-h56kk 65534
7. Now, to run the simpleapache image as a non-root user, you would create a Dockerfile with contents similar to:
Dockerfile FROM ubuntu ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update RUN apt-get install -y apache2 ENV APACHE_RUN_USER www-data ENV APACHE_RUN_GROUP www-data ENV APACHE_LOG_DIR /var/log/apache2 ENV APACHE_RUN_DIR /var/run ENV APACHE_PID_FILE /var/run/apache2.pid RUN echo 'Hello Apache in Docker' > /var/www/html/index.html RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf RUN sed -r -i 's?isten 80?isten 8080?' /etc/apache2/ports.conf RUN sed -r -i 's?:80?:8080?' /etc/apache2/sites-available/000-default.conf RUN chown -R nobody /var/log/apache2 RUN chown -R nobody /var/run RUN chown -R nobody /var/lock RUN service apache2 restart EXPOSE 8080 CMD ["/usr/sbin/apachectl", "-D FOREGROUND"]
8. Build and run your Docker image:
$ docker build --tag <tag> . $ docker run -p 8080:80 <image>
9. To uninstall this Docker installation, run:
$ eval $(minikube docker-env --unset)
Hands-on Demo: Namespaces
You use Namespaces to partition a Kubernetes cluster logically. For example, you can create a namespace based on your work environments such as development, staging, and production or for different teams or applications.
So far, you have run all our containers in the same namespace called default. Using a namespace will provide separation between these environments, minimizing the possibility of name collisions and making the cluster easier to manage.
Namespaces are a Kubernetes construct, and they are to be created and added to the deployment files. In this demo, you will run the cronpywpchksumbot-dev application in two separate namespaces—development and production.
1. Create a script for the development namespace:
apiVersion: v1 kind: Namespace metadata: name: cronpywpchksumbot-dev
2. The deployment script for this namespace is similar to:
apiVersion: batch/v1beta1 kind: CronJob metadata: name: cronpywpchksumbot namespace: cronpywpchksumbot-dev spec: schedule: "*/5 * * * *" jobTemplate: spec: template: spec: containers: - name: pywpchksumbot image: <userid>/pywpchksumbot imagePullPolicy: IfNotPresent restartPolicy: OnFailure
3. Create a deployment:
$ kubectl apply -f cronpywpchksumbot-dev.yaml $ kubectl apply -f cronpywpchksumbotdeployment1.yaml
4. Create a script for the production workspace: cronpywpchksumbot-prod.yaml
apiVersion: v1 kind: Namespace metadata: name: cronpywpchksumbot-prod
5. The deployment script for this namespace is similar to: cronpywpchksumbotdeployment2.yaml
apiVersion: batch/v1beta1 kind: CronJob metadata: name: cronpywpchksumbot namespace: cronpywpchksumbot-prod spec: schedule: "*/15 * * * *" jobTemplate: spec: template: spec: containers: - name: pywpchksumbot image: <userid>/pywpchksumbot imagePullPolicy: IfNotPresent restartPolicy: OnFailure
6. Create a deployment:
$ kubectl apply -f cronpywpchksumbot-prod.yaml $ kubectl apply -f cronpywpchksumbotdeployment2.yaml
7. You now have to add the
--namespace=<namespace> to the command
kubectl get cronjobs to get information on the cronjob. Alternatively, you can use the
--all-namespaces option to get information on all namespaces.
$ kubectl get cronjobs --namespace=cronpywpchksumbot-dev NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE cronpywpchksumbot */5 * * * * False 0 <none> 83s
Step 3: Kubernetes security and management best practices
Some Kubernetes (k8s) security and management best practices include:
- Regularly update container images.
- Run containers as a regular user, not as the root user.
- Run containers in a separate namespace, not in the default shared namespace.
- Limit the number of container registries.
- Regularly update Kubernetes.
- Regularly update and manage the underlying Operating System (OS) that runs Kubernetes.