Validating Helm charts using the Datree GitHub Action
In this guide, I will show you how to set up Datree to test your Helm charts against best practices and security misconfigurations using GitHub Actions.
In this guide, I will show you how to set up Datree to test your Helm charts against best practices and security misconfigurations using GitHub Actions.
What is Datree
Datree is a tool that helps to prevent misconfigurations of Kubernetes resources by running policy checks against the definition of these objects. It can be used as a CLI tool inside your CI/CD pipelines as well as an admission controller inside your Kubernetes cluster.
It provides you with a cloud backend that can be used to display the history of your Datree executions, manage different policies, show violations inside your cluster, and much more. If you want to learn more about Datree and the different ways it can be used, check out this video by TechWorld with Nana.
In this guide, I will only focus on setting up and using Datree for checking your Helm charts through GitHub Actions.
Assumptions
Datree account and token
Before we start with any work on GitHub, you need to register on app.datree.io and create a token or use the default token. Simply follow this link to go to the token management area of Datree.
GitHub repository structure
I obviously assume that you are using GitHub to store your Helm charts and use the default repository layout, meaning every Helm chart is located in a separate folder under the root folder called charts
. The configuration we'll use to check our Helm charts will be stored in .github/config/datree.yaml
and the workflow definition inside of .github/workflows/datree.yaml
. Your layout should there look like this:
your-github-repository/
├─ .github/
│ ├─ config/
│ │ ├─ datree.yaml
│ ├─ workflows/
│ │ ├─ datree.yaml
├─ charts/
│ ├─ helm-chart-1
│ ├─ helm-chart-2
GitHub Action Secret
Lastly, after setting up your Datree account, obtaining a token, and creating the GitHub repository structure, you need to add the token as an Action secret to the GitHub repository. For this go to Settings
--> Security
--> Secrets
--> Actions
or simply append /settings/secrets/actions
to the URL of your GitHub repository. Press the New repository secret
button, set the name to DATREE_TOKEN
, and paste the token from the Datree cloud backend.
How to setup Datree in GitHub
We need two files to make this work. The first defines the policy configuration used to validate the Helm charts and the other defines our GitHub Action workflow. I will call both files datree.yaml
and store them in .github/config
and .github/workflows
.
Policy definition
Let's start with the definition of our policy in .github/config/datree.yaml
:
apiVersion: v1
customRules: null
policies:
- name: helm_charts
isDefault: true
rules:
- identifier: INGRESS_INCORRECT_HOST_VALUE_PERMISSIVE
messageOnFailure: Incorrect value for key `host` - specify host instead of using a wildcard character ("*")
- identifier: SERVICE_INCORRECT_TYPE_VALUE_NODEPORT
messageOnFailure: Incorrect value for key `type` - `NodePort` will open a port on all nodes where it can be reached by the network external to the cluster
- identifier: CRONJOB_INVALID_SCHEDULE_VALUE
messageOnFailure: 'Incorrect value for key `schedule` - the (cron) schedule expressions is not valid and, therefore, will not work as expected'
- identifier: WORKLOAD_INVALID_LABELS_VALUE
messageOnFailure: Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it
- identifier: WORKLOAD_INCORRECT_RESTARTPOLICY_VALUE_ALWAYS
messageOnFailure: Incorrect value for key `restartPolicy` - any other value than `Always` is not supported by this resource
- identifier: HPA_MISSING_MINREPLICAS_KEY
messageOnFailure: Missing property object `minReplicas` - the value should be within the accepted boundaries recommended by the organization
- identifier: CRONJOB_MISSING_STARTINGDEADLINESECOND_KEY
messageOnFailure: Missing property object `startingDeadlineSeconds` - set a time limit to the cron execution to allow killing it if exceeded
- identifier: K8S_DEPRECATED_APIVERSION_1.16
messageOnFailure: Incorrect value for key `apiVersion` - the version you are trying to use is not supported by the Kubernetes cluster version (>=1.16)
- identifier: K8S_DEPRECATED_APIVERSION_1.17
messageOnFailure: Incorrect value for key `apiVersion` - the version you are trying to use is not supported by the Kubernetes cluster version (>=1.17)
- identifier: CONTAINERS_INCORRECT_PRIVILEGED_VALUE_TRUE
messageOnFailure: Incorrect value for key `privileged` - this mode will allow the container the same access as processes running on the host
- identifier: CONTAINERS_MISSING_IMAGE_VALUE_DIGEST
messageOnFailure: 'Incorrect value for key `image` - add a digest tag (starts with `@sha256:`) to represent an immutable version of the image'
- identifier: CRONJOB_MISSING_CONCURRENCYPOLICY_KEY
messageOnFailure: Missing property object `concurrencyPolicy` - the behavior will be more deterministic if jobs won't run concurrently
- identifier: RESOURCE_MISSING_NAME
messageOnFailure: Missing key `name` or `generateName` - one of them must be set to apply resource to a cluster
This is of course only a small example of the rules you can define. You can find a list of the built-in rules here. The documentation of Datree also shows examples of how to define your own custom rules.
Workflow definition
Next, let's continue with the workflow definition in .github/workflows/datree.yaml
:
on:
pull_request:
branches: [main]
paths: ["charts/**"]
push:
branches: [main]
paths: ["charts/**"]
env:
DATREE_TOKEN: ${{ secrets.DATREE_TOKEN }}
jobs:
datree:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Update dependencies
run: find charts/ ! -path charts/ -maxdepth 1 -type d -exec helm dependency update {} \;
- name: Run Datree Policy Check
uses: datreeio/action-datree@main
with:
path: 'charts'
isHelmChart: true
cliArguments: '--only-k8s-files --policy-config .github/config/datree.yaml'
First, we check out the GitHub repository and update the dependencies of our Helm charts. This is required because of a limitation of Datree, described in this issue. Finally, we can run the policy check on the path charts
. We add the flag --only-k8s-files
to only check K8s manifests, else there would be errors for values.yaml
and other files as they are not valid Kubernetes resources. The flag --policy-config
is used to specify the path to the policy we want to use.
And that's already it. With every PR or Push on the main
branch which changes anything under charts
our workflow will be triggered and execute the policy checks.
Available options for Datree
Datree comes with a lot of options to tune the execution of the policy checks. In the next section, I will show some options that may be useful for you.
Defining the Kubernetes schema version
With the flag --schema-version <K8S_VERSION>
we are able to define which Kubernetes version should be used for the schema validation of our resources. It makes sense to validate your Helm charts against multiple Kubernetes versions using multiple step definition, like this:
- name: Run Datree Policy Check against K8S v1.23.0
uses: datreeio/action-datree@main
with:
path: 'charts'
isHelmChart: true
cliArguments: '--only-k8s-files --policy-config .github/config/datree.yaml --schema-version 1.23.0'
- name: Run Datree Policy Check against K8S v1.24.0
uses: datreeio/action-datree@main
with:
path: 'charts'
isHelmChart: true
cliArguments: '--only-k8s-files --policy-config .github/config/datree.yaml --schema-version 1.24.0'
Using a cloud policy
If you want to use a policy that is defined in the Datree cloud backend, simply use the --policy <POLICY_NAME>
instead of --policy-config .github/config/datree.yaml
.
# Configure Kubernetes schema version
helm datree test <PATH/TO/HELM/CHART> --schema-version <K8S_VERSION>
# Define policy to use from the Datree cloud
helm datree test <PATH/TO/HELM/CHART> --policy <POLICY_NAME>
Don't use the cloud backend
If you don't want the results to end up in the Datree cloud backend, you can add the flag --no-record
as a cliArgument
to the definition.
- name: Run Datree Policy Check
uses: datreeio/action-datree@main
with:
path: 'charts'
isHelmChart: true
cliArguments: '--only-k8s-files --policy-config --no-record'