Afin de réduire le nombre de vulnérabilités, le nombre de fichier, le poids etc... D'un Dockerfile, les best practices préconisent de faire du multi-stage building :
FROM node AS build # Etape build
RUN mkdir -p /var/node/
ADD src/ /var/node/
WORKDIR /var/node
RUN npm install
FROM node:alpine
ARG VERSION=V1.1
LABEL org.label-schema.version=$VERSION
ENV NODE_ENV="production"
COPY --from=build /var/node /var/node # On copie les fichiers /var/node depuis l'étape build
WORKDIR /var/node
EXPOSE 3000
ENTRYPOINT ["./bin/www"]
L'image finale ne contiendra que le dernier stage.
Afin de sécuriser au mieux ses images :
RUN addgroup -S appgroup && adduser -S appuser -G appgroup -h /home/appuser
USER appuser
filesystem
en read_only (aussi faisable via kubernetes) :RUN chmod a-w /etc
RUN rm -rf /bin/*
L'analyse statique permet de checker le code sources, les fichiers, la création de pods... et remonter des erreurs si cela ne correspond pas aux règles misent en place.
Kubesec permet de faire de l'analyse de risque sur des ressources Kubernetes. Kubesec est open source. Kubesec est basé sur une liste de best practices définie.
Voici comment l'utiliser :
Antoine@cks-master:~$ k run nginx --image=nginx -o yaml --dry-run=client > pod.yaml
Antoine@cks-master:~$ docker run -i kubesec/kubesec:v2 scan /dev/stdin < pod.yaml
[
{
"object": "Pod/nginx.default",
"valid": true,
"fileName": "API",
"message": "Passed with a score of 0 points",
"score": 0,
"scoring": {
"advise": [
{
"id": "ApparmorAny",
"selector": ".metadata .annotations .\"container.apparmor.security.beta.kubernetes.io/nginx\"",
"reason": "Well defined AppArmor policies may provide greater protection from unknown threats. WARNING: NOT PRODUCTION READY",
"points": 3
},
{
"id": "ServiceAccountName",
"selector": ".spec .serviceAccountName",
"reason": "Service accounts restrict Kubernetes API access and should be configured with least privilege",
"points": 3
},
...
En complément OPA (Open Policy Agent) avec des règles custom peut aussi faire de l'analyse statique. Nous pouvons utiliser l'image conftest
afin de faire les tests statiques dans une pipeline sur des fichiers YAML Kubernetes ou Dockerfile.
conftest
et dans celui-ci, un dossier policy
deployment.rego
c'est ici que l'on va mettre notre règle de check.# from https://www.conftest.dev
package main
deny[msg] {
input.kind = "Deployment"
not input.spec.template.spec.securityContext.runAsNonRoot = true
msg = "Containers must not run as root"
}
deny[msg] {
input.kind = "Deployment"
not input.spec.selector.matchLabels.app
msg = "Containers must provide app label for pod selectors"
}
conftest/
:apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: test
name: test
spec:
replicas: 1
selector:
matchLabels:
app: test
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: test
spec:
containers:
- image: httpd
name: httpd
resources: {}
status: {}
$ docker run --rm -v $(pwd):/project openpolicyagent/conftest test deploy.yaml
FAIL - deploy.yaml - Containers must not run as root
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
Il existe des outils permettant de faire des scan de vulnérabilités sur les images. Nous allons utiliser trivy
mais il en existe d'autre (Clair
par exemple).
$ docker run aquasec/trivy:0.29.2 image python:3.10-alpine
2022-07-03T15:20:20.450Z INFO Need to update DB
2022-07-03T15:20:20.450Z INFO DB Repository: ghcr.io/aquasecurity/trivy-db
2022-07-03T15:20:20.450Z INFO Downloading DB...
...
2022-07-03T15:20:25.292Z INFO Vulnerability scanning is enabled
2022-07-03T15:20:25.293Z INFO Secret scanning is enabled
2022-07-03T15:20:25.293Z INFO If your scanning is slow, please try '--security-checks vuln' to disable secret scanning
2022-07-03T15:20:25.293Z INFO Please see also https://aquasecurity.github.io/trivy/v0.29.2/docs/secret/scanning/#recommendation for faster secret detection
2022-07-03T15:20:28.485Z INFO Detected OS: alpine
2022-07-03T15:20:28.486Z INFO Detecting Alpine vulnerabilities...
2022-07-03T15:20:28.486Z INFO Number of language-specific files: 1
2022-07-03T15:20:28.486Z INFO Detecting python-pkg vulnerabilities...
python:3.10-alpine (alpine 3.16.0)
==================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
Python (python-pkg)
===================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
Les images alpine sont souvent celles avec le moins de vulnérabilités.
Récupération d'une image d'un registre privée :
kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>
imagePullSecrets
:apiVersion: v1
kind: Pod
metadata:
name: private-reg
spec:
containers:
- name: private-reg-container
image: <your-private-image>
imagePullSecrets:
- name: regcred
Nous utilisons souvent les images de la manière suivante nginx:1.23.0
cependant rien nous prouve que l'on télécharge la bonne image (mise à part qu'on est sur le repository officiel). Afin de sécuriser la récupération de la bonne image, on peut utiliser le digest de celle-ci. Soit : nginx@sha256:3536d368b898eef291fb1f6d184a95f8bc1a6f863c48457395aab859fda354d1
cela ne récupérera l'image que si le digest est le bon.
Afin de n'autoriser que certains repository dans le cluster, nous pouvons whitelist certains repositories via OPA :
- ConstraintTemplate :
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8strustedimages
spec:
crd:
spec:
names:
kind: K8sTrustedImages
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8strustedimages
violation[{"msg": msg}] {
image := input.review.object.spec.containers[_].image
not startswith(image, "docker.io/")
not startswith(image, "k8s.gcr.io/")
msg := "not trusted image!"
}
- Constraint :
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sTrustedImages
metadata:
name: pod-trusted-images
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
Pour activer cette fonctionnalité, il faut rajouter un Admission Plugin
:
/etc/kubernetes/manifests/kube-apiserver.yaml
- --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook
git clone https://github.com/killer-sh/cks-course-environment.git
cp -r cks-course-environment/course-content/supply-chain-security/secure-the-supply-chain/whitelist-registries/ImagePolicyWebhook/ /etc/kubernetes/admission
/etc/kubernetes/manifests/kube-apiserver.yaml
: - --admission-control-config-file=/etc/kubernetes/admission/admission_config.yaml
volumeMounts:
- mountPath: /etc/kubernetes/admission
name: k8s-admission
readOnly: true
volumes:
- hostPath:
path: /etc/kubernetes/admission
type: DirectoryOrCreate
name: k8s-admission
Une fois effectué, nous pouvons essayer de lancer un pod :
$ k run pod --image=nginx
Error from server (Forbidden): pods "pod" is forbidden: Post "https://external-service:1234/check-image?timeout=30s": dial tcp: lookup external-service on 169.254.169.254:53: no such host
On voit que dès la création du pod il essaie d'atteindre l'url du webhook.