Helm Input Validation - Additional Security layer for Kubernetes
There’s no doubt that Kubernetes adoption has increased a lot since its first release.
However, Kubernetes is insecure by design and the cloud only makes it worse. Not everyone has the same security needs, and some developers and engineers might want more granular control on specific configurations. Kubernetes offers the ability to enforce security practices as you need, and the evolution of the available security options has improved a lot over the years.
Today’s post covers a few suggestions on what you can do to make your Kubernetes workloads more secure by implementing input validation at the code level(Helm Charts), which is shifting left your security to avoid any vulnerabilities even before they come vulnerabilities in the infrastructure.
Image Credits snyk
Input validation also known as data validation is performed to ensure only properly formed or approved data is entering the workflow in an information system.
It is widely used for the testing input received by the application for compliance against a standard defined within the application. It can be as simple as strictly typing a parameter and as complex as using regular expressions or business logic to validate input.
In this article, we will see how Input validation can be applied for infrastructure as code (IAC) to detect unauthorised input before it is processed while installing, upgrading and managing Kubernetes applications.
Input validation strategies
Input validation should be applied on both syntactical and Semantic level.
Syntactic validation should enforce correct syntax of structured fields (e.g. SSN, date, currency symbol).
Semantic validation should enforce correctness of their values in the specific business context (e.g. start date is before end date, price is within expected range).
The Chart File Structure
According to Helm docs, a Chart is organized as a collection of files inside of a directory. The directory name is the name of the chart (without versioning information).
Thus, a chart describing WordPress would be stored in a wordpress/ directory.
Inside of this directory, Helm will expect a structure that matches this:
wordpress/ Chart.yaml # A YAML file containing information about the chart LICENSE # OPTIONAL: A plain text file containing the license for the chart README.md # OPTIONAL: A human-readable README file values.yaml # The default configuration values for this chart values.schema.json # OPTIONAL: A JSON Schema for imposing a structure on the values.yaml file charts/ # A directory containing any charts upon which this chart depends. crds/ # Custom Resource Definitions templates/ # A directory of templates that, when combined with values, # will generate valid Kubernetes manifest files. templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes _helpers.tpl # file is the default location for template partials
Input validation in Helm Charts
Helm Charts are simply Kubernetes YAML manifests combined into a single package that can be advertised to your Kubernetes clusters. A single chart might be used to deploy something simple, like a memcached pod, or something complex, like a full web app stack with HTTP servers, databases, caches, and so on.
Incorporating some sort of Input validation is considered a good practise to avoid developers from doing any mistakes unintentionally/intentionally and to ensure deployments go as-planned.
In this post we will be working with values.yaml, deployment.yaml and helpers.tpl using the ‘required‘ and ‘fail‘ functions.
The better-known function of the two is the required function. According to the Helm docs,
The required function gives developers the ability to declare a value entry as required for template rendering. If the entry is empty in values.yml, the template will not render and will return an error message supplied by the developer.
Another function that comes in handy is the fail function. This function is part of the Sprig library. According to the Sprig documentation, Unconditionally returns an empty string and an error with the specified text. This is useful in scenarios where other conditionals have determined that template rendering should fail.
Whereas required checked for a value’s existence, fail can ensure that the provided value is valid.
The two same functions can be used to check the mandatory security requirements are enforced in the Helm Charts while installing and managing Kubernetes applications.
Enforce Kubernetes Security with Input validation
1) Only use images from trusted Sources
Authenticity of Docker images is a challenge. We put a lot of trust into these images as we are literally using them as the container that runs our code in production. Therefore, it is critical to make sure the image we pull is the one that is approved and pushed by the publisher, and it's important only to use container images from trusted vendors because there are a lot of vulnerable and malware ingested containers.
We can enforce this as a requirement to download and use images only from approved registries and fail the deployment/up-gradation if the image is being pulled from other than approved registry.
The below code snippet is an example that Returns the proper image name based on the registryName, repositoryName and tag values and also enforces that the image should be pulled from the approved registries as shown in line 2 otherwise invokes the fail function in line 6 and Deployment will fail with the message “Can Pull Images only from Approved Registries/Repositories” as shown in the screenshot enforcing this check using the fail function.
Now, when we pass the registry value as ‘docker.io’ which is not approved in line num in the above snippet, the deployment fails.
This way we can enforce that the deployment fails when images are not pulled from approved registries. Similarly, we can extend this check to ensure that only hardened and approved images are being used in the infrastructure.
2) Security Context for a POD
As you probably already know, Docker containers typically run with root privileges by default which exposes you to high risk once you put your containers into a production environment.
Hence, it is advised that changing the configuration of your containers to make them run as non-root adds an extra layer of security.
The below code snippet is an example that fails the deployment if the runAsNonRoot spec is set to false in the POD security context which enforces that the runAsNonRoot spec must be set to true in the securityContext of the pod.
Passing runAsNonRoot spec with value set to false
Deployment Fails as the runAsNonRoot spec is set to False
3) Avoid mount sensitive directories to host
Using mount to host is risky in terms of security, as it can potentially provide access to the host’s file system.
If you have sensitive data on the host’s file system to which you do not wish to provide any access, avoid using mount to host as much as possible, when you cannot completely avoid the mounting, we can restrict the mount paths to reduce the risk.
As shown in the below snippet, when someone tries to mount any unapproved paths (Line Num 4) or blacklisted paths (Line Num 18), the deployment will fail.
Deployment Failed when approved path is not passed in values.yaml
Deployment Failed because blacklisted path is passed in values.yaml
This way we can restrict the paths that can be mounted onto containers when needed for activities such as log monitoring.
4) Security Context for a Container
AllowPrivilegeEscalation spec Controls whether a process can gain more privileges than its parent process. This bool directly controls whether the no_new_privs flag gets set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged OR 2) has CAP_SYS_ADMIN.
It is always advised that this spec is enabled and the value is set to false which enhances the container level security.
The below code snippet is an example that fails the deployment if the AllowPrivilegeEscalation spec is set to true in the Container security context or if the value is not set ( line num 2 ) which enforces that the AllowPrivilegeEscalation spec must always be provided and also set to false ( line num 3) .
allowPrivilegeEscalation is passed with value set to true
Deployment Fails as allowPrivilegeEscalation value is set to true
allowPrivilegeEscalation is not passed during deployment
Deployment Failed as allowPrivilegeEscalation value is not passed but required
Input validation can be enforced as an additional layer of security with these two functions!!
It is important to realise that input validation is not a replacement for other security controls, but an additional strong layer of security.
Happy Helming!! 😊