{"id":15441767,"url":"https://github.com/mudler/eirini-secscanner","last_synced_at":"2025-07-04T07:04:40.484Z","repository":{"id":69642267,"uuid":"303409962","full_name":"mudler/eirini-secscanner","owner":"mudler","description":"WIP EiriniX extension for the cf 2020 summit lab","archived":false,"fork":false,"pushed_at":"2020-10-19T14:15:07.000Z","size":84,"stargazers_count":0,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-05-09T20:58:10.194Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mudler.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-10-12T14:01:15.000Z","updated_at":"2024-06-19T07:56:51.534Z","dependencies_parsed_at":null,"dependency_job_id":"f822441d-6df1-4ef4-8ac4-c5d780626172","html_url":"https://github.com/mudler/eirini-secscanner","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mudler/eirini-secscanner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudler%2Feirini-secscanner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudler%2Feirini-secscanner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudler%2Feirini-secscanner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudler%2Feirini-secscanner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mudler","download_url":"https://codeload.github.com/mudler/eirini-secscanner/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudler%2Feirini-secscanner/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263464184,"owners_count":23470473,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T19:22:47.962Z","updated_at":"2025-07-04T07:04:40.465Z","avatar_url":"https://github.com/mudler.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EiriniX Security scanner - CF Summit Lab\nEiriniX extension for the cf 2020 summit lab (content should go eventually to https://github.com/cloudfoundry/summit-hands-on-labs)\n\n\nIn this lab, we will write an extension for Eirini with EiriniX.\n\nThe extension is a security-oriented one. For example, we want to prevent from being pushed Eirini apps that doesn't pass a vulnerability scan.\n\n[trivy](https://github.com/aquasecurity/trivy#embed-in-dockerfile) is a perfect fit, and we will try to run it in an `InitContainer` before starting the Cloud Foundry Application, preventing it to run if it fails `trivy` validations.\n\n## Table of contents\n\u003c!-- TOC --\u003e\n\n- [EiriniX Security scanner - CF Summit Lab](#eirinix-security-scanner---cf-summit-lab)\n    - [Table of contents](#table-of-contents)\n    - [Target Audience](#target-audience)\n        - [Learning Objectives](#learning-objectives)\n        - [Prerequisites](#prerequisites)\n        - [Notes](#notes)\n    - [Preparation](#preparation)\n        - [Setup go.mod for our project](#setup-gomod-for-our-project)\n        - [Prepare GitHub repository](#prepare-github-repository)\n    - [Extension logic](#extension-logic)\n        - [Anatomy of an Extension](#anatomy-of-an-extension)\n        - [Write our \"main.go\"](#write-our-maingo)\n        - [Dockerfile](#dockerfile)\n    - [Commit the code](#commit-the-code)\n        - [Make the Docker image public](#make-the-docker-image-public)\n    - [Let's test it!](#lets-test-it)\n    - [Extension logic, part two](#extension-logic-part-two)\n        - [Security scanner severity](#security-scanner-severity)\n\n\u003c!-- /TOC --\u003e\n\n\n## Target Audience\n\nThis lab is targeted towards the audience who would like to use Cloud Foundry for packaging and deploying applications and Kubernetes as the underlying infrastructure for orchestration of the containers.\n\n### Learning Objectives\n\n - Build an Eirini extension with EiriniX\n - Use Git and Github Actions to build the extension docker image\n - Deploy the extension to your kubernetes cluster\n\n### Prerequisites\n- A machine with golang installed, were we will build our extension\n- A KubeCF Cluster deployed with Eirini\n- Students must have basic knowledge of Cloud Foundry and Kubernetes.\n- Github account / Git experience\n\n### Notes\n\nThe full code used in this lab is available [here](https://github.com/mudler/eirini-secscanner) and if you want to try that extension directly you can: `kubectl apply -f https://raw.githubusercontent.com/mudler/eirini-secscanner/main/contrib/kube.yaml`\n\n## Preparation\n\nBe sure you have an environment where you can build golang source code, see [here for golang installation](https://golang.org/doc/install), and check that your environment can compile the [go hello world program](https://golang.org/doc/tutorial/getting-started). Note the tutorial needs an environment with `Go \u003e=1.14`.\n\n### Setup go.mod for our project\n\nFirst of all, create a new folder, and init it with your project path:\n\n```$ go mod init github.com/user/eirini-secscanner```\n\nAt this point, we can run ```go get code.cloudfoundry.org/eirinix``` at the top level, so it gets added to go.mod.\n\nYou should have a go.mod similar to this one:\n\n```golang\nmodule github.com/[USER]/eirini-secscanner # NOTE: Replace [USER] with your username here\n\nrequire (\n\tcode.cloudfoundry.org/eirinix v0.3.1-0.20200908072226-2c03042398ea\n\tgo.uber.org/zap v1.15.0\n\tk8s.io/api v0.18.6\n\tk8s.io/apimachinery v0.18.6\n\tk8s.io/client-go v0.18.6\n\tsigs.k8s.io/controller-runtime v0.6.2\n)\n\ngo 1.14\n```\n\n### Prepare GitHub repository\n\nFor easy of use, we will use GitHub to store our extension with git, and we will use github actions to build the docker image of our extension. In this way, we can later deploy our extension with `kubectl` in our cluster. \n\nCreate a GitHub account if you don't have one yet, create a new repository and [create a Personal Access Token (PAT)](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token) in GitHub with the [appropriate permissions](https://docs.github.com/en/free-pro-team@latest/packages/getting-started-with-github-container-registry/migrating-to-github-container-registry-for-docker-images#authenticating-with-the-container-registry) and [add a secret in the repository, called `CR_PAT` with the PAT key](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository). For sake of semplicity, we will assume that our repository is called `eirini-secscanner`.\n\nClone the repository, and create a `.github` folder, inside create a new `workflows` folder with a yaml file `docker.yaml` with the following content:\n\n*.github/workflows/docker.yaml*: \n```yaml\nname: Publish Docker image\non:\n  push:\njobs:\n  push_to_registry:\n    name: Push Docker image to GitHub Packages\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out the repo\n        uses: actions/checkout@v2\n      -\n        name: Set up QEMU\n        uses: docker/setup-qemu-action@v1\n      -\n        name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v1\n      -\n        name: Login to GitHub Container Registry\n        uses: docker/login-action@v1\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.CR_PAT }}\n      -\n        name: Build and push\n        id: docker_build\n        uses: docker/build-push-action@v2\n        with:\n          push: true\n          tags: ghcr.io/[USER]/eirini-secscanner:latest # NOTE: Replace [USER] with your username here\n      -\n        name: Image digest\n        run: echo ${{ steps.docker_build.outputs.digest }}\n```\n\n\n```bash\n$ mkdir -p .github/workflows\n$ wget 'https://raw.githubusercontent.com/mudler/eirini-secscanner/main/.github/workflows/docker.yaml' -O .github/workflows/docker.yaml\n$ vim .github/workflows/docker.yaml # Edit the workflow and replace the image name\n$ git add .github\n$ git commit -m \"Add Github action workflow\"\n$ git push\n```\n\nThe GitHub Action will build and push a fresh docker image to the GitHub container registry, that we can later on use it in our cluster to run our extension. \nThe Image should be accessible to a url similar to this: `ghcr.io/user/eirini-secscanner:latest`\n\n## Extension logic\n\nBefore jumping in creating our `main.go`, let's focus on our extension logic. EiriniX does support different kind of extensions, which allows to interact with Eirini applications, or staging pods in different ways:\n\n- MutatingWebhooks -  by having an active component which patches Eirini apps before starting them\n- Watcher - a component that just watch and gets notified if new Eirini apps are pushed\n- Reconcilers - a component that constantly reconciles a desired state for an Eirini app\n\nAn Eirini App workload is represented by a ```StatefulSet```, which then it becomes a pod running in the Eirini namespace. \n\nBefore the app is started, Eirini runs a staging job which builds the image used to start the app.\n\nFor our Security scanner (secscanner) makes sense to use a *MutatingWebhook*, we will try to patch the Eirini runtime pod and inject an InitContainer with [trivy](https://github.com/aquasecurity/trivy#embed-in-dockerfile) preventing to starting it in case has security vulnerability.\n\nSince we want [trivy](https://github.com/aquasecurity/trivy#embed-in-dockerfile) to run as a first action, and check if the filesystem of our app is secure enough, we will have to run the InitContainer with the same image which is used for the Eirini app.\n\nSo our extension will also have to retrieve the image of the Eirini app - and use that one to run the security scanner.\n\n### Anatomy of an Extension\n\n[EiriniX Extensions](https://github.com/cloudfoundry-incubator/eirinix#write-your-extension) which are *MutatingWebhooks* are expected to provide a *Handle* method which receives a request from the Kubernetes API. The request contains\nthe pod definition that we want to mutate, so our extension will start by defining a struct. Create a file `extension.go` with the following:\n\n```golang\npackage main\n\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\n\teirinix \"code.cloudfoundry.org/eirinix\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n)\n\ntype Extension struct{}\n```\n\nOur extension needs a `Handle` method, so we can write:\n\n```golang\nfunc (ext *Extension) Handle(ctx context.Context, eiriniManager eirinix.Manager, pod *corev1.Pod, req admission.Request) admission.Response {\n\n\tif pod == nil {\n\t\treturn admission.Errored(http.StatusBadRequest, errors.New(\"No pod could be decoded from the request\"))\n  }\n\n\treturn eiriniManager.PatchFromPod(req, pod)\n}\n\n```\n\nNote we need to add a bunch of imports, as our new `Handle` method receives structures from other packages:\n\n-  ```ctx``` is the request context, that can be used for background operations. \n- ```eiriniManager``` is EiriniX, it's an instance of the current execution. \n- ```pod``` is the Pod that needs to be patched - in our case will be our Eirini Extension\n- ```req``` is the raw admission request, might be useful for furhter inspection, but we won't use it in our case\n- ```eiriniManager.PatchFromPod(req, pod)``` is computing the diff between the raw request and the pod. It's used to return the actual difference we are introducing in the pod\n\nAs it stands our extension is not much useful, let's make it add a new init container:\n\n```golang\n\nfunc (ext *Extension) Handle(ctx context.Context, eiriniManager eirinix.Manager, pod *corev1.Pod, req admission.Request) admission.Response {\n\n\tif pod == nil {\n\t\treturn admission.Errored(http.StatusBadRequest, errors.New(\"No pod could be decoded from the request\"))\n\t}\n\tpodCopy := pod.DeepCopy()\n\n\tsecscanner := corev1.Container{\n\t\tName:            \"secscanner\",\n\t\tImage:           \"busybox\",\n\t\tArgs:            []string{\"echo 'fancy'\"},\n\t\tCommand:         []string{\"/bin/sh\", \"-c\"},\n\t\tImagePullPolicy: corev1.PullAlways,\n\t\tEnv:             []corev1.EnvVar{},\n\t}\n\n\tpodCopy.Spec.InitContainers = append(podCopy.Spec.InitContainers, secscanner)\n\n\treturn eiriniManager.PatchFromPod(req, podCopy)\n}\n```\n\nWe have added a bunch of things, let's go over it one by one:\n\n- ```podCopy := pod.DeepCopy()``` creates a copy of the pod, to operate over a copy instead of a real pointer\n- ```secscanner....``` it's our `InitContainer` definition. It contains the `Name`, `Image`, and `Args` fields along with `Commands`. As for now it doesn't do anything useful, but it's a start point so we can experience with our extension.\n- ```podCopy.Spec.InitContainers = append(podCopy.Spec.InitContainers, secscanner)``` is appending the InitContainer to the list of the containers in ```podCopy```\n- ```return eiriniManager.PatchFromPod(req, podCopy)``` returns the diff patch from the request to the podCopy\n\n\n### Write our \"main.go\"\n\nLet's now write a short `main.go` which just executes our extension:\n\n```golang\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\n\teirinix \"code.cloudfoundry.org/eirinix\"\n)\n\nconst operatorFingerprint = \"eirini-secscanner\"\n\nvar appVersion string = \"\"\n\nfunc main() {\n\n\teiriniNsEnvVar := os.Getenv(\"EIRINI_NAMESPACE\")\n\tif eiriniNsEnvVar == \"\" {\n\t\tlog.Fatal(\"the EIRINI_NAMESPACE environment variable must be set\")\n\t}\n\n\twebhookNsEnvVar := os.Getenv(\"EXTENSION_NAMESPACE\")\n\tif webhookNsEnvVar == \"\" {\n\t\tlog.Fatal(\"the EXTENSION_NAMESPACE environment variable must be set\")\n\t}\n\n\tportEnvVar := os.Getenv(\"PORT\")\n\tif portEnvVar == \"\" {\n\t\tlog.Fatal(\"the PORT environment variable must be set\")\n\t}\n\tport, err := strconv.Atoi(portEnvVar)\n\tif err != nil {\n\t\tlog.Fatal(\"could not convert port to integer\", \"error\", err, \"port\", portEnvVar)\n\t}\n\n\tserviceNameEnvVar := os.Getenv(\"SERVICE_NAME\")\n\tif serviceNameEnvVar == \"\" {\n\t\tlog.Fatal(\"the SERVICE_NAME environment variable must be set\")\n\t}\n\n\tfilter := true\n\n\text := eirinix.NewManager(eirinix.ManagerOptions{\n\t\tNamespace:           eiriniNsEnvVar,\n\t\tHost:                \"0.0.0.0\",\n\t\tPort:                int32(port),\n\t\tFilterEiriniApps:    \u0026filter,\n\t\tOperatorFingerprint: operatorFingerprint,\n\t\tServiceName:         serviceNameEnvVar,\n\t\tWebhookNamespace:    webhookNsEnvVar,\n\t})\n\n\text.AddExtension(\u0026Extension{})\n\n\tif err := ext.Start(); err != nil {\n\t\tfmt.Println(\"error starting eirinix manager\", \"error\", err)\n\t}\n\n}\n\n```\n\nFirst we collect options from the environment. This will allow us to tweak easily from the kubernetes deployment the various fields:\n- We grab `EIRINI_NAMESPACE` from the environment, it's the namespace used by Eirini to push App\n- `EXTENSION_NAMESPACE` is the namespace used by our extension\n- `PORT` is the listening port where our extension is listening to\n- `SERVICE_NAME` is the Kubernetes service name reserved to our extension. We will need a Kubernetes service resource created before starting our extension. It will be used by Kubernetes to contact our extension while mutating Eirini apps.\n\nMext we construct the EiriniX manager, which will run our extension under the hood, and will create all the necessary boilerplate resources to talk to Kubernetes:\n```golang\n\nfilter := true\n\n\text := eirinix.NewManager(eirinix.ManagerOptions{\n\t\tNamespace:           eiriniNsEnvVar,\n\t\tHost:                \"0.0.0.0\",\n\t\tPort:                int32(port),\n\t\tFilterEiriniApps:    \u0026filter,\n\t\tOperatorFingerprint: operatorFingerprint,\n\t\tServiceName:         serviceNameEnvVar,\n\t\tWebhookNamespace:    webhookNsEnvVar,\n\t})\n```\n\nHere we just map the settings that we collected in environment variables, that we hand over to EiriniX. The ```OperatorFingerprint```  and ```FilterEiriniApps``` are used to set a fingerprint for our runtime and for filtering eirini apps only respectively.\n\n\n### Dockerfile\n\nAt this point we can write up a Dockerfile to build our extension, it just needs to build a go binary and offer it as an entrypoint. Create a file `Dockerfile` with the following content:\n\n```Dockerfile\nARG BASE_IMAGE=opensuse/leap\n\nFROM golang:1.14 as build\nADD . /eirini-secscanner\nWORKDIR /eirini-secscanner\nRUN CGO_ENABLED=0 go build -o eirini-secscanner\nRUN chmod +x eirini-secscanner\n\nFROM $BASE_IMAGE\nCOPY --from=build /eirini-secscanner/eirini-secscanner /bin/\nENTRYPOINT [\"/bin/eirini-secscanner\"]\n```\n\n## Commit the code\n\nTime to try things out!\n\nBuild the code with `go build -o eirini-secscanner` to see if you have any error.\n\nCommit and push the code done so far to github\n\n```bash\n$ git add go.mod go.sum extension.go main.go Dockerfile\n$ git commit -m \"Inject security scanner\"\n$ git push\n```\na workflow will trigger automatically, which can be inspected in the \"Actions\" tab of the repository. \nNow, we should have a docker image, and we are ready to start our extension!\n\n\n### Make the Docker image public\n\nAfter GH Action has been executed and the docker image of your extension has been pushed, change its permission setting to public in the [package settings page](https://docs.github.com/en/free-pro-team@latest/packages/managing-container-images-with-github-container-registry/configuring-access-control-and-visibility-for-container-images#configuring-visibility-of-container-images-for-your-personal-account)\n\n## Let's test it!\n\nWe need at this point to start our extension.\n\nIn the Kubernetes deployment file, we are creating a `serviceAccount` that has permission to register `mutatingwebhooks` at cluster level and that can operate on secrets on the namespace where it belongs. We also give permissions over the target namespace (the Eirini one, and we assume it's `eirini`) to operate on `pods`, `events` and `namespace` resource.\n\nFinally we create a service which will be consumed by the extension.\n\nAt the end should look more or less like the following:\n\n```yaml\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: eirini-secscanner\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: eirini-secscanner\n  namespace: eirini-secscanner\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: eirini-secscanner-webhook\nrules:\n- apiGroups:\n  - admissionregistration.k8s.io\n  resources:\n  - validatingwebhookconfigurations\n  - mutatingwebhookconfigurations\n  verbs:\n  - create  \n  - delete\n  - update\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: eirini-secscanner-secrets\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  verbs:\n  - get\n  - create\n  - delete\n  - list\n  - update\n  - watch\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: eirini-secscanner\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  verbs:\n  - get\n  - list\n  - update\n  - watch\n\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n  - update\n\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - delete\n  - get\n  - list\n  - update\n  - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: watch-eirini-1\n  namespace: eirini\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: eirini-secscanner\nsubjects:\n- kind: ServiceAccount\n  name: eirini-secscanner\n  namespace: eirini-secscanner\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: secrets\n  namespace: eirini-secscanner\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: eirini-secscanner-secrets\nsubjects:\n- kind: ServiceAccount\n  name: eirini-secscanner\n  namespace: eirini-secscanner\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: webhook\n  namespace: eirini-secscanner\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: eirini-secscanner-webhook\nsubjects:\n- kind: ServiceAccount\n  name: eirini-secscanner\n  namespace: eirini-secscanner\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: eirini-secscanner\n  namespace: eirini-secscanner\nspec:\n  type: ClusterIP\n  selector:\n    name: eirini-secscanner\n  ports:\n  - protocol: TCP\n    name: https\n    port: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: eirini-secscanner\n  namespace: eirini-secscanner\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      name: eirini-secscanner\n  template:\n    metadata:\n      labels:\n        name: eirini-secscanner\n    spec:\n      serviceAccountName: eirini-secscanner\n      containers:\n        - name: eirini-secscanner\n          imagePullPolicy: Always\n          image: \"ghcr.io/[USER]/eirini-secscanner:latest\"\n          env:\n            - name: EIRINI_NAMESPACE\n              value: \"eirini\"\n            - name: EXTENSION_NAMESPACE\n              value: \"eirini-secscanner\"\n            - name: PORT\n              value: \"8080\"\n            - name: SERVICE_NAME\n              value: \"eirini-secscanner\"\n            - name: SEVERITY\n              value: \"CRITICAL\"\n```\n\nNotes: replace `\"ghcr.io/[USER]/eirini-secscanner:latest\"` with your image, and then apply the yaml with `kubectl`. Our component will be now on the `eirini-secscanner` namespace, intercepting Eirini Apps.\n\nApply the yaml, and watch the `eirini-secscanner` namespace, a pod should appear and go to running, our extension is up!\n\nLet's try to push a sample app with CF, and then inspect the app pod in the `eirini` namespace, it should have an `InitContainer` named `secscanner` injected (which just echoes) that ran successfully.\n\n## Extension logic, part two\n\nWe have tried our extension, but doesn't do anything useful - yet - it just echoes a text in an `InitContainer`. So let's go ahead and run `trivy` instead of echoing text.\n\nThis time,  we will inject a container, but the container needs to run on the same image of the Eirini App, so we will try to scan the pod that we have intercepted, and we will try to find the container that Eirini created to start our application.\n\n\n```golang\n\nfunc (ext *Extension) Handle(ctx context.Context, eiriniManager eirinix.Manager, pod *corev1.Pod, req admission.Request) admission.Response {\n\n\tif pod == nil {\n\t\treturn admission.Errored(http.StatusBadRequest, errors.New(\"No pod could be decoded from the request\"))\n\t}\n\tpodCopy := pod.DeepCopy()\n\n\tvar image string\n\tfor i := range podCopy.Spec.Containers {\n\t\tc := \u0026podCopy.Spec.Containers[i]\n\t\tswitch c.Name {\n\t\tcase \"opi\":\n\t\t\timage = c.Image\n\t\t}\n  }\n  ....\n```\n\nNow we are looping `podCopy` Containers, and we are looking for a container which is named after `opi` - that's by convention the container created by Eirini. We will grab the image string and we store it into the `image` variable.\n\nWe know now the correct image, so we are ready to tweak our container:\n\n```golang\n\n\n\tsecscanner := corev1.Container{\n\t\tName:            \"secscanner\",\n\t\tImage:           image,\n\t\tArgs:            []string{`mkdir bin \u0026\u0026 curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b bin \u0026\u0026 bin/trivy filesystem --exit-code 1 --no-progress /`},\n\t\tCommand:         []string{\"/bin/sh\", \"-c\"},\n\t\tImagePullPolicy: corev1.PullAlways,\n\t\tEnv:             []corev1.EnvVar{},\n\t}\n```\n\nWe also have to take care of the resource used. If no `requests/limits` are specified, Kubernetes will apply the same limits of sillibings containers to ours, and this will cause our secscanner to get `OOMKilled` if someone pushes an app with a small memory limit set.\n\nWe will then set a specific memory request in our container:\n\n```golang\n  \tq, err := resource.ParseQuantity(\"500M\")\n\t\tif err != nil {\n\t\t\treturn admission.Errored(http.StatusBadRequest, errors.New(\"Failed parsing quantity\"))\n    }\n    ...\n\t\tsecscanner.Resources = corev1.ResourceRequirements{\n\t\t\tRequests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceMemory: q},\n\t\t\tLimits:   map[corev1.ResourceName]resource.Quantity{corev1.ResourceMemory: q},\n\t\t}\n```\n\nmind also to add `resource \"k8s.io/apimachinery/pkg/api/resource\"` to the imports on top of your main.\n\nAs we would like also to be able to run our extension with replicas, in full HA mode, we will adapt our code to be idempotent, so it doesn't try to inject an init container each time. Before injecting the container, we can add a `guard` like so:\n\n```golang\n\n\t// GUARD: Stop if a secscanner was already injected\n\tfor i := range podCopy.Spec.InitContainers {\n\t\tc := \u0026podCopy.Spec.InitContainers[i]\n\t\tif c.Name == \"secscanner\" {\n\t\t\treturn eiriniManager.PatchFromPod(req, podCopy)\n\t\t}\n\t}\n\n\n```\nto return an empty patch so we patch the pod only once.\n\nNow our extension should look something like: \n\n```golang\n\nfunc trivyInject(severity string) string {\n\treturn fmt.Sprintf(\"curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b tmp \u0026\u0026 tmp/trivy filesystem --severity '%s' --exit-code 1 --no-progress /\", severity)\n}\n\n// Extension is the secscanner extension which injects a initcontainer which checks for vulnerability in the container image\ntype Extension struct{}\n\n\n// Handle takes a pod and inject a secscanner container if needed\nfunc (ext *Extension) Handle(ctx context.Context, eiriniManager eirinix.Manager, pod *corev1.Pod, req admission.Request) admission.Response {\n\n\tif pod == nil {\n\t\treturn admission.Errored(http.StatusBadRequest, errors.New(\"No pod could be decoded from the request\"))\n\t}\n\tpodCopy := pod.DeepCopy()\n\n\t// Stop if a secscanner was already injected\n\tfor i := range podCopy.Spec.InitContainers {\n\t\tc := \u0026podCopy.Spec.InitContainers[i]\n\t\tif c.Name == \"secscanner\" {\n\t\t\treturn eiriniManager.PatchFromPod(req, podCopy)\n\t\t}\n\t}\n\n\tvar image string\n\tfor i := range podCopy.Spec.Containers {\n\t\tc := \u0026podCopy.Spec.Containers[i]\n\t\tswitch c.Name {\n\t\tcase \"opi\":\n\t\t\timage = c.Image\n\t\t}\n  }\n  \n  q, err := resource.ParseQuantity(\"500M\")\n\tif err != nil {\n\t\treturn admission.Errored(http.StatusBadRequest, errors.New(\"Failed parsing quantity\"))\n   }\n\n\tsecscanner := corev1.Container{\n\t\tName:            \"secscanner\",\n\t\tImage:           image,\n\t\tArgs:            []string{trivyInject(\"CRITICAL\")},\n\t\tCommand:         []string{\"/bin/sh\", \"-c\"},\n\t\tImagePullPolicy: corev1.PullAlways,\n    Env:             []corev1.EnvVar{},\n    Resources: corev1.ResourceRequirements{\n\t\t\tRequests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceMemory: q},\n\t\t\tLimits:   map[corev1.ResourceName]resource.Quantity{corev1.ResourceMemory: q},\n\t\t},\n\t}\n\n\tpodCopy.Spec.InitContainers = append(podCopy.Spec.InitContainers, secscanner)\n\n\treturn eiriniManager.PatchFromPod(req, podCopy)\n}\n\n\n```\n\ndon't forget about adding `fmt` at the imports.\n\nAt this point the only difference is that we have moved the bash command construction to its own function `trivyInject` so it can take a severity as an option and parametrize the `trivy` execution accordingly.\n\nLet's build and commit the code:\n```bash\n$ git add extension.go\n$ git commit -m \"Inject security scanner\"\n$ git push # This will trigger github actions\n```\n\nAlso git push it, to have a new image built by GitHub. Wait for Github Action to complete and delete the extension pod. Now push an application, and watch the eirini namespace with `watch kubectl get pods -n eirini` to see what happens!\n\nWe should see first a staging eirini pod, that afterwards gets deleted to make space to the real Eirini app. If we inspect it closely with `kubectl describe pod -n eirini PODNAME`, we will see it had injected a `secscanner` container.\n\n### Security scanner severity\n\nNow we can also play with the extension itself - as we saw already `trivy` takes a `--severity` parameter which sets the severity levels of the issues found, if the sevirity found matches with the one you selected, it will make the container to exit so the pod doesn't start.\n\nLet's tweak then our `secscanner` container:\n\n```golang\n\n\tsecscanner := corev1.Container{\n\t\tName:            \"secscanner\",\n\t\tImage:           image,\n\t\tArgs:            []string{trivyInject(os.Getenv(\"SEVERITY\"))},\n\t\tCommand:         []string{\"/bin/sh\", \"-c\"},\n\t\tImagePullPolicy: corev1.PullAlways,\n    Env:             []corev1.EnvVar{},\n    Resources: corev1.ResourceRequirements{\n\t\t\tRequests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceMemory: q},\n\t\t\tLimits:   map[corev1.ResourceName]resource.Quantity{corev1.ResourceMemory: q},\n\t\t},\n\t}\n\n```\n\nIn this way we can specify the severity with env vars, and edit the deployment.yaml accordingly:\n\n```yaml\n      containers:\n        - name: eirini-secscanner\n        ...\n          env:\n        ...\n            - name: SEVERITY\n              value: \"CRITICAL\" # Try to set it to \"HIGH,CRITICAL\"\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmudler%2Feirini-secscanner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmudler%2Feirini-secscanner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmudler%2Feirini-secscanner/lists"}