RKE2 Image security Admission Controller V3

Page content

In a previous posts we talked about the anchore-image-validator made by Banzaicloud and the admission-controller made by Anchore. In this post I will show you my own admission-controller for image scanning.

Parts of the K8S Security Lab series

Container Runetime Security
Advanced Kernel Security
Container Network Security
Secure Kubernetes Install
User Security

I found multiple solution for Anchore Engine but only one for Trivy. The trivy-enforcer that is an experimental project and use OPA for enforce the policy. So I decide to create mey own dmission-controller.

How an admission controller works

An Admission Controller Webhook is triggered when a Kubernetes object is created. It sends a JSON formatted HTTP request to a specific Kubernetes Service in a namespace which returns a JSON response. If you whoud like to now more aboute admission controllers you can read about it in my previous post Using Admission Controllers

Writing a Validating Admission Controller

I want to walidate the Pod object to check how many vulnerability has the image in this pod. So I wrote a python script that will pars the JSON request for the pods image, rin a trivy scan on it and sen back the answer. Then I build it to a docker image called devopstales/trivy-scanner-admission:1.0.1. I run it as a deployment:

--- 
apiVersion: v1
kind: PersistentVolumeClaim
metadata: 
  name: trivy-cache
spec: 
  accessModes: 
    - ReadWriteOnce
  resources: 
    requests: 
      storage: 1G
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: trivy-scanner
  labels:
    app: trivy-scanner
  namespace: validation
spec:
  replicas: 1
  selector:
    matchLabels:
      app: trivy-scanner
  template:
    metadata:
      labels:
        app: trivy-scanner
    spec:
      securityContext:
        fsGroup: 10001
      containers:
        - name: trivy-scanner
          image: devopstales/trivy-scanner-admission:1.0.1
          imagePullPolicy: Always
          volumeMounts:
          - name: cache
            mountPath: "/home/kube-trivy-admission/.cache/trivy"
#          - name: config-json
#            mountPath: "/home/kube-trivy-admission/.docker"
      volumes:
      - name: cache
        persistentVolumeClaim:
            claimName: "trivy-cache"
#      - name: config-json
#        secret:
#          secretName: config-json
---
apiVersion: v1
kind: Service
metadata:
  name: trivy-scanner
  namespace: validation
spec:
  selector:
    app: trivy-scanner
  ports:
  - port: 443
    targetPort: 5000

The Service must be an HTTPS port on 443 for the Admission Webhook so I created a self-signed certificate and placed in the docker container.

Create the Admission Webhook

The abow Admission Webhook will send teh HTTP request to my trivy-scanner service:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: trivy-scanner
webhooks:
  - name: trivy-scanner.devopstales.intra
    sideEffects: "None"
    admissionReviewVersions: [v1beta1, v1]
    clientConfig:
      service:
        name: trivy-scanner
        namespace: validation
        path: "/validate"
      caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZMekNDQXhlZ0F3SUJBZ0lVWnBZdlRuUUFWRTgvZk9jMHJWeFhWU1hadTBnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0p6RWxNQ01HQTFVRUF3d2NRV1J0YVhOemFXOXVJRU52Ym5SeWIyeHNaWElnVjJWaWFHOXZhekFlRncweQpNVEExTXpBeE5URTJORE5hRncweU1UQTJNamt4TlRFMk5ETmFNQ2N4SlRBakJnTlZCQU1NSEVGa2JXbHpjMmx2CmJpQkRiMjUwY205c2JHVnlJRmRsWW1odmIyc3dnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDRHdBd2dnSUsKQW9JQ0FRREhpZyt2Yjl2K3A1SldhYzNnUzhiNzJOZ1hFTkFFU1FYMHZWZTlGUzNhUURqRlJWcDhYVlEzZkdKYQp2SmFTdjVuNWtDVkRhZDkvcEtRaEZ5amI1OUJpRmVySVU1dW40c1BhRGRJUThtb25pL0VNbFZRNURNK0JNZzRoCnc0bk12N1BCNFRSbGFFSlNEVVBpaFZqNUlGRE9VbjFoMjVQMGpPSktUT2NWSW5HTXFpeldBenptZlhXb2pnK1UKMnl0VWhNYlBwS2M1TE5XS3p2cmhERUY2eVBXbHN1d0VPalhIOUhxQXQzQmxYVVYvOWV3aWdjRFFjVHk1Ty9WUAo0OEFVam9TTGlIR254QzI5S21qVDhwaHhOUzV5emhXbkxIUkdkWTc0UmZDTlZ4akpqRk8zMlo1TjFMRGJsRTdiCmlSU3ovUHlvcVZFb3NIcmZXV3QrbUhzN21ZODNGc3lhYnFjeklyaGdyR0Z5TSs1MWtJZ1lUbmV5M3VkMHVXd0MKMVlWVHdLQ3Rsa3VMSWUzc29yV2V2THRLYlRuZXpzUWFST0lndmY4dTBjQVpLTzljMFN3SXN2RjZPK3RKSXQ4RQo1MjRMUWhQeDhEejVSVktDUGgwaGxZR3c0VmN5ZFdZNTNGVFBPYmtIaHpVelErTXlBcDZSN1NVbHhIdW1HTVFTCkd5ZzlDZVJFSzA3MWs5bmNpUWcvb3FZQWpxay8rUDUvT0c0ZDFyQjA0cmFzRTdRcXk5YUQvTStLOGFTOWhWVWQKYTlFR2NKdmZyUGtwSTZ6MDhFdU5sSHN0Y0NpMUtyb0VzTXlXSlFuSEhZWXAvckJPdEpSeVBueGZIY05vc3diWgorYnZBbVdlRDE1Wm1NNW1aT01vSlFqY1BnMWdDZXdlRkZiWi9rN0xadTJvTHRjNVU3UUlEQVFBQm8xTXdVVEFkCkJnTlZIUTRFRmdRVTVtOVozQnZjUTRqSEUvSkVRRXBMeUN1ay9pWXdId1lEVlIwakJCZ3dGb0FVNW05WjNCdmMKUTRqSEUvSkVRRXBMeUN1ay9pWXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQwpBZ0VBVWo1NEpDNldCb2JWQ0cwRzQwc0ltNktvZEVQWXZLTnlhSUNFb25HSlFZTlpKYndUdGR5T1NlbXhzdENzCkdHM2h6bk5SUCtCME43ZUhlR2JQZENqcjNzSElSTTYxQmk0UDVoQ1BGSW9YdDgvdWJrSzQyR2dpd3ZNK1I4NlgKWUlTY2FJS3A5OXUyQjBsM3c3Q3pNSDZFcmR0aGM2S2RZY2dCWXhDaVBKR2trQ2wwelUwU1ZuYTVkbTExNlBMTAppL05LbUZjbitBbDl4TThqQmhyZU5mWGNITnVJNzFBSzluYnZzMkNLMkMrSUw2ZGpqaVVNTFdCNzRUZVBTNk1pCkpjblVleUQ1NGxiK2dWMTRZY0NKeGxSSkJQR3FpVFVTbE44cFdwQkpISlo5WmVjcFlHbWM5blNsK0tNZ3RFb0gKNHk3NS9ZZUhlYVo3UktGWDBOZWlpRC81NHFMM0Q3RTNmV25BclQ5ZUVjYi9ON1Z4MDdWczNlSWhheCtSNSt2QgpPcU1jaTVHSzY1NVUyeUpVMlR3SVNFSTFRZmd2TDNLZmxXU0c2WkUwSkg0bE1MZmJSMHg2SmtvajJzTTRYQks2CjE0NXh6eFdIZ2pVTzFqcnpmNVdRUi9MTXd0b3dVcFlBZWwrTWdMNnZBby9sbGw3THl2alNFL1Z6TEdNVFJyL2EKd1VJbFpYaHFreW5LeEJTUTk0Zi8vOTZLeWorQzk4WVQxcVFpVHU1aWQvYS82S2paWlZJVGFaYlRySk9zWnBHLwpRSGdpT3FFbDlWUGpCOUdtTUdhaklSbHJiRkp1R0FHQVlhalpvd2VVeWdaL3BocEd1NUh6dzJTaTRtaHUxT0tpCmVoR3diUzdoTHlvZ3hYelk4VTA1ZXBmcEJuTERFc09HWThjVkd0bVdFNk9HdGhvPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
    rules:
      - apiGroups: ["apps", ""]
        resources:
          - "pods"
        apiVersions:
          - "*"
        operations:
          - CREATE

I placed the root CA of my self-signed certificate in this Validating Webhook Configuration to make my Service’s certiface valid for the Kubernetes api.

Policy

Now If I create a Deployment, Pod … It scan the Image with tryvi. To block an Image I need to add the limits of the maximum numer of vulnerability for the severities.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      annotations:
        trivy.security.devopstales.io/medium: "5"
        trivy.security.devopstales.io/low: "10"
        trivy.security.devopstales.io/critical: "2"
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - name: http
          containerPort: 80