RKE2 Image security Admission Controller V3
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
- Part1: How to deploy CRI-O with Firecracker?
- Part2: How to deploy CRI-O with gVisor?
- Part3: How to deploy containerd with Firecracker?
- Part4: How to deploy containerd with gVisor?
- Part5: How to deploy containerd with kata containers?
Advanced Kernel Security
- Part1: Hardening Kubernetes with seccomp
- Part2: Linux user namespace management wit CRI-O in Kubernetes
- Part3: Hardening Kubernetes with seccomp
Network Security
- Part1: RKE2 Install With Calico
- Part2: RKE2 Install With Cilium
- Part3: CNI-Genie: network separation with multiple CNI
- Part3: Configurre network wit nmstate operator
- Part3: Kubernetes Network Policy
- Part4: Kubernetes with external Ingress Controller with vxlan
- Part4: Kubernetes with external Ingress Controller with bgp
- Part4: Central authentication with oauth2-proxy
- Part5: Secure your applications with Pomerium Ingress Controller
- Part6: CrowdSec Intrusion Detection System (IDS) for Kubernetes
- Part7: Kubernetes audit logs and Falco
Secure Kubernetes Install
- Part1: Best Practices to keeping Kubernetes Clusters Secure
- Part2: Kubernetes Secure Install
- Part3: Kubernetes Hardening Guide with CIS 1.6 Benchmark
- Part4: Kubernetes Certificate Rotation
User Security
- Part1: How to create kubeconfig?
- Part2: How to create Users in Kubernetes the right way?
- Part3: Kubernetes Single Sign-on with Pinniped OpenID Connect
- Part4: Kubectl authentication with Kuberos Depricated !!
- Part5: Kubernetes authentication with Keycloak and gangway Depricated !!
- Part6: kube-openid-connect 1.0 Depricated !!
Image Security
Pod Security
- Part1: Using Admission Controllers
- Part2: RKE2 Pod Security Policy
- Part3: Kubernetes Pod Security Admission
- Part4: Kubernetes: How to migrate Pod Security Policy to Pod Security Admission?
- Part5: Pod Security Standards using Kyverno
- Part6: Kubernetes Cluster Policy with Kyverno
Secret Security
- Part1: Kubernetes and Vault integration
- Part2: Kubernetes External Vault integration
- Part3: ArgoCD and kubeseal to encript secrets
- Part4: Flux2 and kubeseal to encrypt secrets
- Part5: Flux2 and Mozilla SOPS to encrypt secrets
Monitoring and Observability
- Part6: K8S Logging And Monitoring
- Part7: Install Grafana Loki with Helm3
Backup
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