diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml deleted file mode 100644 index a0a6be9..0000000 --- a/.github/workflows/stale.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: Close inactive issues -on: - schedule: - - cron: "30 1 * * *" - -jobs: - close-issues: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v3 - with: - days-before-issue-stale: 30 - days-before-issue-close: 10 - stale-issue-label: "stale" - stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." - close-issue-message: "This issue was closed because it has been inactive for 10 days since being marked as stale." - days-before-pr-stale: -1 - days-before-pr-close: -1 - repo-token: ${{ secrets.ISSUES_TOKEN }} \ No newline at end of file diff --git a/kubernetes/datree/README.md b/kubernetes/datree/README.md new file mode 100644 index 0000000..d905875 --- /dev/null +++ b/kubernetes/datree/README.md @@ -0,0 +1,386 @@ + +# Introduction to Datree + +## Installation + +Best place to start is the [documentation](https://hub.datree.io/) + +I like to start all my work inside a docker container.
+Let's run a small Alpine linux container + +``` +docker run -it -v ${PWD}:/work -v ${HOME}/.kube/:/root/.kube/ -w /work --net host alpine sh +``` + +### Install some dependancies + +Let's install `curl` and `unzip` because the installation script uses those.
+We will also install `sudo` since we are running in a container as root and install scripts have `sudo` commands in them. + +``` +apk add curl unzip bash sudo +``` + +### Automatic Installation + +We can install the latest version of Datree with the command advertised: + +``` +curl https://get.datree.io | /bin/bash +``` + +### Manual Installation + +Or we can grab a specific version of `datree` on the GitHub releases page.
+For example: [1.5.20](https://github.com/datreeio/datree/releases/tag/1.5.20) binary + +``` +curl -L https://github.com/datreeio/datree/releases/download/1.5.20/datree-cli_1.5.20_Linux_x86_64.zip -o /tmp/datree.zip + +unzip /tmp/datree.zip -d /tmp && \ +chmod +x /tmp/datree && \ +mv /tmp/datree /usr/local/bin/datree + +``` + +Now we can run the `datree` command: + +``` +datree +Datree is a static code analysis tool for kubernetes files. Full code can be found at https://github.com/datreeio/datree + +Usage: + datree [command] + +Available Commands: + completion Generate completion script for bash,zsh,fish,powershell + config Configuration management + help Help about any command + kustomize Render resources defined in a kustomization.yaml file and run a policy check against them + publish Publish policies configuration for given . + test Execute static analysis for given + version Print the version number + +Flags: + -h, --help help for datree + +Use "datree [command] --help" for more information about a command. + +``` + +## Testing Kubernetes Manifests + +We have a number of Kubernetes manifests in this repo.
+Datree does a few things for us:
+* YAML validation ( Is this YAML well formatted ? ) +* Schema validation. ( Is this a Kubernetes YAML file ? For the right version ? ) +* Policy checks ( Checks YAML to ensure good practises are followed ) + +
+ +Let's test my example manifests under our datree folder `kubernetes\datree\example` + +### YAML validation + +If we break the YAML file format, we can detect that with the YAML validation feature + +``` +datree test ./kubernetes/datree/example/deployment.yaml +``` + +### Policy checks + +When we fix our YAML file, notice if we run `datree test` again, we get some policy checks failing + +``` +datree test ./kubernetes/datree/example/deployment.yaml + +``` + +Let's test some other types of Kubernetes objects + +``` +datree test ./kubernetes/services/service.yaml +datree test ./kubernetes/configmaps/configmap.yaml +datree test ./kubernetes/statefulsets/statefulset.yaml +datree test ./kubernetes/ingress/ingress.yaml +``` + +### Schema validation + +Datree can also check if our YAML matches the target Kubernetes version schema. +For example, our Ingress YAML is a newer version of Kubernetes + +``` +datree test --schema-version 1.14.0 ./kubernetes/ingress/ingress-nginx-example.yaml +datree test --schema-version 1.19.0 ./kubernetes/ingress/ingress-nginx-example.yaml + +``` + +We can also test a directory of YAML files and include `*` wildcard in your scans.
+Let's test my latest Kubernetes tutorial that contains a Wordpress + MySQL + Ingress setup: + +``` +datree test kubernetes/tutorials/basics/yaml/*.y*ml +``` + +# Policies + +Now if we take a look at the CLI output of `datree` we notice a link in the Summary output.
+The URL is in the form of `https://app.datree.io/login?t=`
+ +``` +(Summary) + +- Passing YAML validation: 4/4 + +- Passing Kubernetes (1.20.0) schema validation: 4/4 + +- Passing policy check: 2/4 + ++-----------------------------------+------------------------------------------------------+ +| Enabled rules in policy "Default" | 21 | +| Configs tested against policy | 5 | +| Total rules evaluated | 84 | +| Total rules skipped | 0 | +| Total rules failed | 14 | +| Total rules passed | 70 | +| See all rules in policy | https://app.datree.io/login?t=xxxxxxxxxxxxxxxxxxxxxx | ++-----------------------------------+------------------------------------------------------+ +``` + +We can use this URL to access the Datree UI to get a view of the policy management screens
+Checkout the link to access the UI which helps us manage our policies.
+ +## Policy examples + +One of the key features about policies is that we can apply rule sets for specific environments.
+Perhaps you have a development environment where policies are a little loose and a staging server that has tighter restrictions to match production, or even a regulated environment that has very tight controls.
+ +We can use the Datree UI to create policies with different sets of rules.
+We can then tell `datree` about the policy we want it to test against: + +``` +datree test kubernetes/datree/example/deployment.yaml -p production +``` + +For a new policy, we notice that 0 rules are enabled, so now we have the flexibility to set up the rules we want to protect this environment.
+ +## Helm + +What if I don't use `kubectl` and use `helm` instead ?
+Let's install `helm` in our container
+ +``` +apk add tar git +curl -L https://get.helm.sh/helm-v3.5.4-linux-amd64.tar.gz -o /tmp/helm.tar.gz && \ +tar -xzf /tmp/helm.tar.gz -C /tmp && \ +chmod +x /tmp/linux-amd64/helm && \ +mv /tmp/linux-amd64/helm /usr/local/bin/helm + +``` + +Let's install the `helm` plugin for `datree`
+ +``` +helm plugin install https://github.com/datreeio/helm-datree + +``` + +Now we can test a `helm` chart we have in our repo from my `helm` tutorial
+ +``` + +cd kubernetes/helm + +helm datree test example-app \ +-- --values ./example-app/example-app-01.values.yaml +``` + +## Kustomize + +What if I don't use `helm` and use `kustomize` instead ?
+Datree has out the box built-in `kustomize` support
+Let's test our `kustomize` template from a video I did on `kustomize` + +``` +datree kustomize test .\kubernetes\kustomize\application\ +``` + +# CI/CD examples + +We can even run datree in GitHub Actions and various [CI/CD integrations](https://hub.datree.io/cicd-examples).
+ + +# Admission Controller + +So far, `datree` helps us detect misconfigurations on our local machine as well as at our CI level.
+But what about the things that don't flow via our CI ?
+ +When folks deploy stuff directly to our clusters via `kubectl` or `helm`.
+Datree now allows us to not only detect but prevent misconfigurations being applied using a new admission controller feature.
+ +The admission controller is available [here](https://github.com/datreeio/admission-webhook-datree) + +## Create a Kubernetes cluster + +Let's start by creating a local `kind` [cluster](https://kind.sigs.k8s.io/) + +Note that we create a Kubernetes 1.23 cluster.
+So we want to use `datree` to validate and ensure our manifests comply with that version of Kubernetes.
+ +``` +kind create cluster --name datree --image kindest/node:v1.23.6 +``` + +Let's also grab `kubectl`: + +``` +curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.23.6/bin/linux/amd64/kubectl +chmod +x ./kubectl +mv ./kubectl /usr/local/bin/kubectl +``` + +We'll need a `datree` token so our admission controller can read our policies + +``` +export DATREE_TOKEN=[your-token] + +``` + +## Installation + +I will need some dependencies since I am running in a lightweight `alpine` container.
+OpenSSL is needed by the webhook install to generate certificates.
+ +``` +apk add openssl +``` + +Let's grab the `datree` manifests +``` +curl -L https://get.datree.io/admission-webhook -o datree.sh +chmod +x datree.sh +bash datree.sh +``` + +With the admission controller now deployed, `datree` will validate things coming into the cluster.
+For example, if we bypass our CI/CD, `datree` will catch our deployment and run our policy checks + +I have a separate example deployment in our datree folder that we can play with: + +``` +kubectl apply -f kubernetes/datree/example/deployment.yaml +``` + +Output: + +``` +kubectl apply -f kubernetes/deployments/deployment.yaml +Error from server: error when creating "kubernetes/deployments/deployment.yaml": admission webhook "webhook-server.datree.svc" denied the request: +--- +webhook-example-deploy-Deployment.tmp.yaml + +[V] YAML validation +[V] Kubernetes schema validation + +[X] Policy check + +āŒ Ensure each container has a configured liveness probe [1 occurrence] + - metadata.name: example-deploy (kind: Deployment) +šŸ’” Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks + +āŒ Ensure each container has a configured readiness probe [1 occurrence] + - metadata.name: example-deploy (kind: Deployment) +šŸ’” Missing property object `readinessProbe` - add a properly configured readinessProbe to notify kubelet your Pods are ready for traffic + +āŒ Prevent workload from using the default namespace [1 occurrence] + - metadata.name: example-deploy (kind: Deployment) +šŸ’” Incorrect value for key `namespace` - use an explicit namespace instead of the default one (`default`) + + +(Summary) + +- Passing YAML validation: 1/1 + +- Passing Kubernetes (v1.23.6) schema validation: 1/1 + +- Passing policy check: 0/1 + ++-----------------------------------+-----------------------+ +| Enabled rules in policy "Default" | 21 | +| Configs tested against policy | 1 | +| Total rules evaluated | 21 | +| Total rules skipped | 0 | +| Total rules failed | 3 | +| Total rules passed | 18 | +| See all rules in policy | https://app.datree.io | ++-----------------------------------+-----------------------+ +``` + +Now to get this deployment fixed up, let's go ahead and comply to some of the policies
+Under the `deployment.yaml` I have included a `livenessProbe` as well as a `readinessProbe`
+Let's add those in.
+And finally we need to also add CPU and Memory requests and limit values.
+ +The last one is simple. We should avoid using the default namespace. So I will create an `example` namespace where I will keep all example apps. + +``` +kubectl create ns examples +``` + +And finally we can deploy our resource, and specify a namespace: + +``` +kubectl apply -n examples -f kubernetes/datree/example/deployment.yaml +deployment.apps/example-deploy created + +``` + +## Kubectl + +But what about resources already in your cluster ?
+Datree covers this with their `kubectl` plugin. + +We can grab the install script right off the [GitHub Release](https://github.com/datreeio/kubectl-datree/releases) page.
+For this demo I'll grab the `v0.11` version
+ +Installation: + +``` +curl -L https://github.com/datreeio/kubectl-datree/releases/download/v0.1.1/manual_install.sh -o /tmp/kubectl-plugin.sh +chmod +x /tmp/kubectl-plugin.sh +bash /tmp/kubectl-plugin.sh + +``` + +Now we have datree inside `kubectl` and can perform checks in our cluster.
+We can check our entire namespace now, which should be pretty clean: + +``` +kubectl datree test -- --namespace examples +Fetching resources, this may take some time depending on the amount of resources in your cluster... + +(Summary) + +- Passing YAML validation: 1/1 + +- Passing Kubernetes (1.24.2) schema validation: 1/1 + +- Passing policy check: 1/1 + ++-----------------------------------+------------------------------------------------------+ +| Enabled rules in policy "Default" | 21 | +| Configs tested against policy | 1 | +| Total rules evaluated | 21 | +| Total rules skipped | 0 | +| Total rules failed | 0 | +| Total rules passed | 21 | +| See all rules in policy | https://app.datree.io/login?t=xxxxxxxxxxxxxxxxxxxxxx | ++-----------------------------------+------------------------------------------------------+ + +The following cluster resources in namespace 'examples' were checked: + +deployment.apps/example-deploy + +``` \ No newline at end of file diff --git a/kubernetes/datree/datree.sh b/kubernetes/datree/datree.sh new file mode 100755 index 0000000..9d8589e --- /dev/null +++ b/kubernetes/datree/datree.sh @@ -0,0 +1,174 @@ +#!/bin/sh + +# Sets up the environment for the admission controller webhook in the active cluster. +# check that user have kubectl installed and openssl +# generate TLS keys +generate_keys () { + printf "šŸ”‘ Generating TLS keys...\n" + + chmod 0700 "${keydir}" + cd "${keydir}" + + cat >server.conf < /dev/null;then + printf '%s\n' "openssl doesn't exist, please install openssl" + exit 1 + fi + + if ! command -v kubectl &> /dev/null;then + printf '%s\n' "kubectl doesn't exist, please install kubectl" + exit 1 + fi +} + +verify_datree_namespace_not_existing () { + local namespace_exists + namespace_exists="$(kubectl get namespace/datree --ignore-not-found)" + + if [ -n "${namespace_exists}" ] ; + then + printf '%s\n' "datree namespace already exists" + exit 1 + fi +} + +verify_webhook_resources_not_existing () { + local validating_webhook_exists + validating_webhook_exists="$(kubectl get validatingwebhookconfiguration.admissionregistration.k8s.io/webhook-datree --ignore-not-found)" + + if [ -n "${validating_webhook_exists}" ] ; + then + printf '%s\n' "datree validating webhook already exists" + exit 1 + fi +} + +are_you_sure () { + read -p "Are you sure you want to run as anonymous user? (y/n) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo true + else + echo false + fi +} + +verify_correct_token_regex () { + if ! [[ $datree_token =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ + || $datree_token =~ ^[0-9a-zA-Z]{22}$ + || $datree_token =~ ^[0-9a-zA-Z]{20}$ ]] ; then + echo "🚫 Invalid token format" + exit 1 + fi +} + +verify_datree_namespace_not_existing + +verify_webhook_resources_not_existing + +verify_prerequisites + +set -eo pipefail + +# Create Temporary directory for TLS keys +keydir="$(mktemp -d)" + +# Generate keys into a temporary directory. +generate_keys + +basedir="$(pwd)/deployment" + +# Create the `datree` namespace. This cannot be part of the YAML file as we first need to create the TLS secret, +# which would fail otherwise. +printf "\nšŸ  Creating datree namespace...\n" +kubectl create namespace datree + +# Label datree namespace to avoid deadlocks in self hosted webhooks +# https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#avoiding-deadlocks-in-self-hosted-webhooks +kubectl label namespaces datree admission.datree/validate=skip + +# label kube-system namespace to avoid operating on the kube-system namespace +# https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#avoiding-operating-on-the-kube-system-namespace +kubectl label namespaces kube-system admission.datree/validate=skip + +# Override DATREE_TOKEN env +if [ -z "$DATREE_TOKEN" ] ; +then + echo + echo ===================================== + echo === Finish setting up the webhook === + echo ===================================== + + token_set=false + while [ "$token_set" = false ]; do + echo "šŸ‘‰ Insert token (available at https://app.datree.io/settings/token-management)" + echo "ā„¹ļø The token is used to connect the webhook with your account." + read datree_token + token_set=true + + if [ -z "$datree_token" ]; then + is_sure=$(are_you_sure) + if [ $is_sure = false ]; then + token_set=false + fi + fi + done +else + datree_token=$DATREE_TOKEN +fi + +verify_correct_token_regex + +# Create the TLS secret for the generated keys. +kubectl -n datree create secret tls webhook-server-tls \ + --cert "${keydir}/webhook-server-tls.crt" \ + --key "${keydir}/webhook-server-tls.key" + +printf "\nšŸ”— Creating webhook resources...\n" + +# Read the PEM-encoded CA certificate, base64 encode it, and replace the `${CA_PEM_B64}` placeholder in the YAML +# template with it. Then, create the Kubernetes resources. +ca_pem_b64="$(openssl base64 -A <"${keydir}/ca.crt")" +curl "https://raw.githubusercontent.com/datreeio/admission-webhook-datree/main/deployment/admission-webhook-datree.yaml" | sed -e 's@${CA_PEM_B64}@'"$ca_pem_b64"'@g' \ + | sed 's@${DATREE_TOKEN}@'"$datree_token"'@g' \ + | kubectl create -f - + +# Delete the key directory to prevent abuse (DO NOT USE THESE KEYS ANYWHERE ELSE). +rm -rf "${keydir}" + +# Wait for deployment rollout +rolloutExitCode=0 +(kubectl rollout status deployment webhook-server -n datree --timeout=180s) || rolloutExitCode=$? + +if [ "$rolloutExitCode" != "0" ]; then + printf "\nāŒ datree webhook rollout failed, please try again. If this keeps happening please contact us: https://github.com/datreeio/admission-webhook-datree/issues\n" +else + printf "\nšŸŽ‰ DONE! The webhook server is now deployed and configured\n" +fi diff --git a/kubernetes/datree/example/deployment.yaml b/kubernetes/datree/example/deployment.yaml new file mode 100644 index 0000000..c55157a --- /dev/null +++ b/kubernetes/datree/example/deployment.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: example-deploy + labels: + app: example-app +spec: + selector: + matchLabels: + app: example-app + replicas: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app: example-app + spec: + containers: + - name: example-app + image: aimvector/python:1.0.4 + imagePullPolicy: Always + ports: + - containerPort: 5000 + # livenessProbe: + # httpGet: + # path: /status + # port: 5000 + # initialDelaySeconds: 3 + # periodSeconds: 3 + # readinessProbe: + # httpGet: + # path: /status + # port: 5000 + # initialDelaySeconds: 3 + # periodSeconds: 3 + # resources: + # requests: + # memory: "64Mi" + # cpu: "50m" + # limits: + # memory: "256Mi" + # cpu: "500m" \ No newline at end of file diff --git a/kubernetes/datree/github-actions/datree.yaml b/kubernetes/datree/github-actions/datree.yaml new file mode 100644 index 0000000..c1e1762 --- /dev/null +++ b/kubernetes/datree/github-actions/datree.yaml @@ -0,0 +1,37 @@ +on: + workflow_dispatch: + push: + branches: [ datree ] + pull_request: + branches: [ datree ] + +env: + DATREE_TOKEN: ${{ secrets.DATREE_TOKEN }} + +jobs: + k8sPolicyCheck: + runs-on: ubuntu-latest + + steps: + - name: checkout + uses: actions/checkout@v2 + - name: run datree policy check + uses: datreeio/action-datree@main + with: + path: 'kubernetes/datree/example/deployment.yaml' + cliArguments: '--only-k8s-files' + - name: docker login + env: + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + docker login -u $DOCKER_USER -p $DOCKER_PASSWORD + - name: build + run: | + docker build ./c# -t aimvector/csharp:1.0.0 + - name: push + run: | + docker push aimvector/csharp:1.0.0 + - name: deploy + run: | + echo 'deploying...' \ No newline at end of file