Ceci est une ancienne révision du document !


Version - 2023.01

Dernière mise-à-jour : 2024/02/20 13:42

DOF308 - Introduction à la Sécurisation de K8s

Contenu du Module

  • DOF308 - Introduction à la Sécurisation de K8s
    • Contenu du Module
    • LAB #1 - Role Based Acces Control et Certificats TLS
      • 1.1 - Présentation
      • 1.2 - Le Fichier /etc/kubernetes/manifests/kube-apiserver.yaml
      • 1.3 - Création d'un serviceAccount
      • 1.4 - Création d'un Utilisateur
      • 1.5 - Certificats TLS
    • LAB #2 - Implémentation de la Sécurité au niveau des Pods
      • 2.1 - Présentation
      • 2.2 - Kubernetes Security Context
        • ReadOnlyRootFilesystem
        • drop
      • 2.3 - Kubernetes Network Policies
      • 2.4 - Kubernetes Resource Allocation Management

LAB #1 - Role Based Acces Control et Certificats TLS

1.1 - Présentation

Un objet Kubernetes est soit lié à un Namespace soit non-lié à un Namespace.

Kubernetes utilise l'API rbac.authorization.k8s.io pour gérer les autorisations. Les acteurs jouant un rôle dans cette API sont :

  • Namespaces,
    • peuvent être considérées comme des clusters virtuels,
    • permettent l'isolation et la segmentation logique,
    • permettent le regroupement d'utilisateurs, de rôles et de ressources,
    • sont utilisés avec des applications, des clients, des projets ou des équipes.
  • Subjects,
    • Regular Users - permettent la gestion des accès autorisés depuis l'extérieur du cluster que cela soit par un utilisateur physique ou sous une autre forme. La gestion des utilisateurs est la responsabilité de l'Administrateur du cluster,
    • ServiceAccounts - permettent la mise en place de permissions au niveau des entités logiciels. Kubernetes crée un certain nombre de serviceAccounts automatiquement mais l'Administrateur peut en créer d'autres. Chaque pod a un serviceAccount qui gère les privilèges accordés au processus et aux conteneurs du pod,
    • User Groups - Kubernetes regroupe des utilisateurs en utilisant des propriétés communes telles le préfixe d'un serviceAccount ou le champ de l'organisation dans un certificat. Il est ensuite possible d'accorder des privilèges de type RBAC aux groupes ainsi créés.
  • Resources,
    • ce sont des entités auxquelles auront accès les Subjects,
    • une ressource est une entité telle un pod, un deployment ou des sous-ressources telles les journaux d'un pod,
    • le Pod Security Policy (PSP) est aussi considéré comme une ressource.
  • Roles et ClusterRoles,
    • Roles - permettent de définir des règles représentant un jeu de permissions, telles GET WATCH LIST CREATE UPDATE PATCH et DELETE, qui peuvent être utilisées avec des ressources dans un Namespace,
      • On ajoute des permissions, on ne les retire pas. Il n'y a pas donc des règles de type deny.
    • ClusterRoles - n'est pas lié à un Namespace. Un ClusterRole est utilisé pour :
      • définir des permissions pour des ressources à être utilisées dans un Namespace
      • définir des permissions pour des ressources à être utilisées dans tous les Namespaces
      • définir des permissions pour des ressources du cluster.

Un exemple d'un Role pour accorder les permissions dans le Namespace default est :

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

Important : apiGroups: [“”] - “” indique le groupe api core ou legacy. Ce groupe se trouve au chemin REST /api/v1. Ce groupe n'est jamais spécifié dans un champs apiVersion, d'où la raison pour laquelle on écrit apiVersion: v1 et non apiVersion api/v1.

Un example d'un ClusterRole pour accorder des permissions de lecture des secrets dans un Namespace spécifique ou dans tous les Namespaces est :

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]
  • RoleBindings et ClusterRoleBindings,
    • permettent d'accorder des permissions définies dans des Roles ou ClusterRoles à des Subjects,
    • RoleBindings sont spécifiques à un NameSpace,
    • ClusterRoleBindings s'appliquent au niveau du Cluster.

1.2 - Le Fichier /etc/kubernetes/manifests/kube-apiserver.yaml

L'utilisation de RBAC est définie par la valeur de la directive –authorization-mode dans le fichier /etc/kubernetes/manifests/kube-apiserver.yaml :

root@kubemaster:~# cat /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.56.2:6443
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=192.168.56.2
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction
    - --enable-bootstrap-token-auth=true
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
    - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
    - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
    - --etcd-servers=https://127.0.0.1:2379
    - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
    - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
    - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
    - --requestheader-allowed-names=front-proxy-client
    - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    - --requestheader-extra-headers-prefix=X-Remote-Extra-
    - --requestheader-group-headers=X-Remote-Group
    - --requestheader-username-headers=X-Remote-User
    - --secure-port=6443
    - --service-account-issuer=https://kubernetes.default.svc.cluster.local
    - --service-account-key-file=/etc/kubernetes/pki/sa.pub
    - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
    - --service-cluster-ip-range=10.96.0.0/12
    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
    image: k8s.gcr.io/kube-apiserver:v1.24.2
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 192.168.56.2
        path: /livez
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    name: kube-apiserver
    readinessProbe:
      failureThreshold: 3
      httpGet:
        host: 192.168.56.2
        path: /readyz
        port: 6443
        scheme: HTTPS
      periodSeconds: 1
      timeoutSeconds: 15
    resources:
      requests:
        cpu: 250m
    startupProbe:
      failureThreshold: 24
      httpGet:
        host: 192.168.56.2
        path: /livez
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    volumeMounts:
    - mountPath: /etc/ssl/certs
      name: ca-certs
      readOnly: true
    - mountPath: /etc/ca-certificates
      name: etc-ca-certificates
      readOnly: true
    - mountPath: /etc/kubernetes/pki
      name: k8s-certs
      readOnly: true
    - mountPath: /usr/local/share/ca-certificates
      name: usr-local-share-ca-certificates
      readOnly: true
    - mountPath: /usr/share/ca-certificates
      name: usr-share-ca-certificates
      readOnly: true
  hostNetwork: true
  priorityClassName: system-node-critical
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  volumes:
  - hostPath:
      path: /etc/ssl/certs
      type: DirectoryOrCreate
    name: ca-certs
  - hostPath:
      path: /etc/ca-certificates
      type: DirectoryOrCreate
    name: etc-ca-certificates
  - hostPath:
      path: /etc/kubernetes/pki
      type: DirectoryOrCreate
    name: k8s-certs
  - hostPath:
      path: /usr/local/share/ca-certificates
      type: DirectoryOrCreate
    name: usr-local-share-ca-certificates
  - hostPath:
      path: /usr/share/ca-certificates
      type: DirectoryOrCreate
    name: usr-share-ca-certificates
status: {}

1.3 - Création d'un serviceAccount

Il est préférable de créer un serviceAccount par service. Ceci permet une configuration plus fine de la sécurité concernant le service. Si un serviceAccount n'est pas spécifié lors de la création des pods, ces pods se verront attribués le serviceAccount par défaut du Namespace.

Imaginons que vous souhaitez que votre application interagisse avec l'API de Kubernetes afin d'obtenir des informations sur les pods dans un Namespace. le serviceAccount par défaut dasn le Namespace default ne peut pas accomplir cette tâche :

root@kubemaster:~# kubectl auth can-i list pods -n default --as=system:serviceaccount:default:default
no

Important : le format de la valeur de l'option –as est system:serviceaccount:namespace:Nom_du_serviceaccount.

Créez maintenant le fichier flask.yaml :

root@kubemaster:~# vi flask.yaml
root@kubemaster:~# cat flask.yaml 
apiVersion: v1
kind: Namespace
metadata:
  name: flask
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: flask-backend
  namespace: flask
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: flask-backend-role
  namespace: flask
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: flask-backend-role-binding
  namespace: flask
subjects:
  - kind: ServiceAccount
    name: flask-backend
    namespace: flask
roleRef:
  kind: Role
  name: flask-backend-role
  apiGroup: rbac.authorization.k8s.io

Ce fichier crée :

  • un Namespace appelé flask,
  • un serviceAccount appelé flask-backend pour le Namespace flask,
  • un Role appelé flask-backend-role qui accorde les permissions get, watch et list sur les pods dans le Namespace flask,
  • un RoleBinding appelé flask-backend-role-binding qui accorde les permissions définies dans le Role flask-backend-role au Subject de type serviceAccount appelé flask-backend.

Appliquez le fichier :

root@kubemaster:~# kubectl create -f flask.yaml 
namespace/flask created
serviceaccount/flask-backend created
role.rbac.authorization.k8s.io/flask-backend-role created
rolebinding.rbac.authorization.k8s.io/flask-backend-role-binding created

Créez maintenant le fichier deployment.yaml qui crée des pods qui utiliseront le serviceAccount appelé flask-backend :

root@kubemaster:~# vi deployment.yaml 
root@kubemaster:~# cat deployment.yaml 
---
apiVersion: apps/v1
kind: Deployment 
metadata:
  name: myapp-deployment
  namespace: flask
  labels:
    app: myapp
    type: front-end
spec:
  template:

    metadata:
      name: myapp-pod
      labels:
        app: myapp
        type: front-end
    spec:
      serviceAccount: flask-backend
      containers:
      - name: nginx-container
        image: nginx

  replicas: 3
  selector: 
    matchLabels:
      type: front-end

Exécutez kubectl :

root@kubemaster:~# kubectl create -f deployment.yaml
deployment.apps/myapp-deployment created

Vérifiez la présence du deployment :

root@kubemaster:~# kubectl get deployment -n flask
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
myapp-deployment   3/3     3            3           32s

Vérifiez maintenant que le serviceAccount flask-backend peut lister les pods dans le Namespace flask :

root@kubemaster:~# kubectl auth can-i list pods -n flask --as=system:serviceaccount:flask:flask-backend
yes

Notez cependant que le serviceAccount flask-backend n'a pas la permission create dans le Namespace flask :

root@kubemaster:~# kubectl auth can-i create pods -n flask --as=system:serviceaccount:flask:flask-backend
no

et que le serviceAccount flask-backend n'a pas la permission list dans le Namespace default :

root@kubemaster:~# kubectl auth can-i list pods -n default --as=system:serviceaccount:flask:flask-backend
no

1.4 - Création d'un Utilisateur

Les utilisateurs font partis du contexte de configuration qui définit le nom du cluster et le nom du Namespace :

root@kubemaster:~# kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin

Important : Un contexte est un élément qui regroupe les paramètres d'accès sous un nom. Les paramètres d'accès sont au nombre de trois, à savoir le cluster, le namespace et l'utilisateur. La commande kubectl utilise les paramètres du contexte courant pour communiquer avec le cluster.

En regardant le contexte courant, on voit que l'utilisateur kubernetes-admin@kubernetes a deux attributs dénommés :

  • client-certificate-data: REDACTED
  • client-key-data: REDACTED
root@kubemaster:~# kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.56.2:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

Important : Le mot REDACTED indique que les valeurs sont cachées pour des raisons de sécurité.

Pour créer un nouveau utilisateur il faut commencer par créer une clef privée pour l'utilisateur :

root@kubemaster:~# openssl genrsa -out trainee.key 2048
Generating RSA private key, 2048 bit long modulus
....................................+++
..............+++
e is 65537 (0x10001)

Créez maintenant un CSR :

root@kubemaster:~# openssl req -new -key trainee.key -out trainee.csr -subj "/CN=trainee/O=examplegroup"

Important : Notez que Kubernetes utilisera la valeur de la clef de l'organisation pour le regroupement des utilisateurs.

Le CSR doit être signé par le CA racine de Kubernetes :

root@kubemaster:~# ls -l /etc/kubernetes/pki/ca.*
-rw-r--r-- 1 root root 1099 juil. 12 13:23 /etc/kubernetes/pki/ca.crt
-rw------- 1 root root 1679 juil. 12 13:23 /etc/kubernetes/pki/ca.key

Signez donc le CSR :

root@kubemaster:~# openssl x509 -req -in trainee.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out trainee.crt
Signature ok
subject=/CN=trainee/O=examplegroup
Getting CA Private Key

Visualisez le certificat de trainee :

root@kubemaster:~# openssl x509 -in trainee.crt -text
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            b6:f7:59:8f:75:19:bc:10
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = kubernetes
        Validity
            Not Before: Jul 14 07:49:14 2022 GMT
            Not After : Aug 13 07:49:14 2022 GMT
        Subject: CN = trainee, O = examplegroup
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:9b:2d:e8:7d:ba:e9:9f:b3:da:8f:14:13:21:83:
                    64:c6:6e:7b:2c:ee:4f:e6:71:65:a7:e4:ca:6a:23:
                    ee:cf:e1:43:18:e0:b0:1f:ef:ff:53:21:de:d2:e8:
                    38:d1:39:ab:b0:8d:78:f4:af:7c:80:b0:1a:c3:a2:
                    cb:64:b4:73:e6:a5:30:33:69:f1:6d:9a:5b:66:2e:
                    58:f6:c2:51:7c:42:95:16:ac:60:0e:1d:4d:09:aa:
                    06:29:51:79:f1:45:70:48:b9:1c:e2:05:fc:5c:33:
                    82:d7:82:5f:a2:31:13:b5:23:4c:10:bf:a5:8a:4f:
                    37:2a:d6:cc:ac:c7:c0:ad:97:71:95:9e:26:4f:60:
                    b5:41:8a:7b:c5:79:38:02:28:b0:88:84:23:0b:18:
                    d2:c2:f9:9f:ff:ec:ec:fb:0a:41:d7:7d:f3:90:2f:
                    29:08:86:1e:e7:cb:ab:cf:56:5e:a9:ba:06:d8:83:
                    c2:3c:1d:38:cc:fa:fd:69:17:4e:c3:7e:79:dd:34:
                    11:9a:ff:5d:32:e4:68:a8:0f:cc:4c:bf:27:bc:2e:
                    19:b7:9d:ad:68:45:d9:87:06:74:9f:e4:ad:bf:df:
                    06:c8:28:c7:a4:78:f2:31:b2:6c:c7:9e:90:b8:bf:
                    48:d4:ae:fd:65:e9:38:fd:8f:30:41:e9:32:f5:de:
                    69:69
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
         6d:c8:0d:cd:7c:34:5c:08:67:98:b6:ae:80:26:e8:73:f1:14:
         3b:02:09:dd:b4:6d:f1:7f:bb:12:8a:16:86:d6:d6:be:ad:92:
         99:a8:23:a1:d7:de:d4:e9:03:ec:6f:b9:19:46:2d:d8:f4:30:
         71:8c:f0:6e:43:ad:d8:10:46:15:ab:9f:46:c1:56:4c:6c:81:
         ab:ba:dd:5b:78:6a:57:82:d3:1a:d7:1a:5f:63:ca:4e:0f:fb:
         ce:fe:f1:a5:78:64:a5:03:41:ad:c5:b7:28:45:62:31:ce:02:
         09:1b:73:1d:e0:96:a4:1b:c4:09:18:a6:b1:5e:8c:88:03:75:
         92:64:47:d3:0c:ce:87:91:9c:25:f7:72:a7:44:9d:36:41:87:
         48:61:71:31:9a:24:ae:36:4f:40:c8:f3:08:32:f5:b1:9d:f5:
         8a:0a:71:80:e6:70:d9:af:e1:96:55:81:9f:a1:95:39:53:b5:
         1b:f3:37:3e:50:d5:a1:6b:d1:4b:d1:c6:75:fb:63:f0:63:06:
         ce:99:fb:c3:15:c1:51:3b:ed:d9:c8:68:43:66:3c:ef:92:ba:
         ae:a5:0d:02:48:8d:42:1a:70:22:13:75:47:ad:69:d5:48:11:
         6b:b1:24:80:7e:d6:0d:f7:92:0c:bb:28:91:6e:d4:4c:a1:14:
         c9:2d:47:2c
-----BEGIN CERTIFICATE-----
MIICujCCAaICCQC291mPdRm8EDANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwpr
dWJlcm5ldGVzMB4XDTIyMDcxNDA3NDkxNFoXDTIyMDgxMzA3NDkxNFowKTEQMA4G
A1UEAwwHdHJhaW5lZTEVMBMGA1UECgwMZXhhbXBsZWdyb3VwMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmy3ofbrpn7PajxQTIYNkxm57LO5P5nFlp+TK
aiPuz+FDGOCwH+//UyHe0ug40TmrsI149K98gLAaw6LLZLRz5qUwM2nxbZpbZi5Y
9sJRfEKVFqxgDh1NCaoGKVF58UVwSLkc4gX8XDOC14JfojETtSNMEL+lik83KtbM
rMfArZdxlZ4mT2C1QYp7xXk4AiiwiIQjCxjSwvmf/+zs+wpB133zkC8pCIYe58ur
z1ZeqboG2IPCPB04zPr9aRdOw3553TQRmv9dMuRoqA/MTL8nvC4Zt52taEXZhwZ0
n+Stv98GyCjHpHjyMbJsx56QuL9I1K79Zek4/Y8wQeky9d5paQIDAQABMA0GCSqG
SIb3DQEBCwUAA4IBAQBtyA3NfDRcCGeYtq6AJuhz8RQ7AgndtG3xf7sSihaG1ta+
rZKZqCOh197U6QPsb7kZRi3Y9DBxjPBuQ63YEEYVq59GwVZMbIGrut1beGpXgtMa
1xpfY8pOD/vO/vGleGSlA0GtxbcoRWIxzgIJG3Md4JakG8QJGKaxXoyIA3WSZEfT
DM6HkZwl93KnRJ02QYdIYXExmiSuNk9AyPMIMvWxnfWKCnGA5nDZr+GWVYGfoZU5
U7Ub8zc+UNWha9FL0cZ1+2PwYwbOmfvDFcFRO+3ZyGhDZjzvkrqupQ0CSI1CGnAi
E3VHrWnVSBFrsSSAftYN95IMuyiRbtRMoRTJLUcs
-----END CERTIFICATE-----

Créez un deuxième utilisateur dans la même Organisation :

root@kubemaster:~# openssl genrsa -out stagiaire.key 2048
Generating RSA private key, 2048 bit long modulus
................................................................................................................................+++
.................+++
e is 65537 (0x10001)

root@kubemaster:~# openssl req -new -key stagiaire.key -out stagiaire.csr -subj "/CN=stagiaire/O=examplegroup"

root@kubemaster:~# openssl x509 -req -in stagiaire.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out stagiaire.crt
Signature ok
subject=/CN=stagiaire/O=examplegroup
Getting CA Private Key

Créez maintenant le contexte trainee :

root@kubemaster:~# kubectl config set-credentials trainee --client-certificate=trainee.crt --client-key=trainee.key
User "trainee" set.

root@kubemaster:~# kubectl config set-context trainee@kubernetes --cluster=kubernetes --user=trainee
Context "trainee@kubernetes" created.

Vérifiez que le contexte soit présent :

root@kubemaster:~# kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin   
          trainee@kubernetes            kubernetes   trainee 

Utilisez le contexte de trainee :

root@kubemaster:~# kubectl config use-context trainee@kubernetes
Switched to context "trainee@kubernetes".

root@kubemaster:~# kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
          kubernetes-admin@kubernetes   kubernetes   kubernetes-admin   
*         trainee@kubernetes            kubernetes   trainee  
          
root@kubemaster:~# kubectl get pods
Error from server (Forbidden): pods is forbidden: User "trainee" cannot list resource "pods" in API group "" in the namespace "default"

Important : Notez que trainee ne peut pas lister les pods parce que les permissions RBAC n'ont pas été définies.

Retournez au contexte de l'administrateur :

root@kubemaster:~# kubectl config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".

root@kubemaster:~# kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin   
          trainee@kubernetes            kubernetes   trainee 

Créez maintenant un clusterrolebinding au groupe examplegroup :

root@kubemaster:~# kubectl create clusterrolebinding examplegroup-admin-binding --clusterrole=cluster-admin --group=examplegroup
clusterrolebinding.rbac.authorization.k8s.io/examplegroup-admin-binding created

Utilisez de nouveau le contexte de trainee :

root@kubemaster:~# kubectl config use-context trainee@kubernetes
Switched to context "trainee@kubernetes".

root@kubemaster:~# kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
          kubernetes-admin@kubernetes   kubernetes   kubernetes-admin   
*         trainee@kubernetes            kubernetes   trainee     
       
root@kubemaster:~# kubectl get pods -n kube-system
NAME                                                READY   STATUS    RESTARTS       AGE
calico-kube-controllers-6766647d54-v4hrm            1/1     Running   0              44h
calico-node-5mrjl                                   1/1     Running   0              41h
calico-node-688lw                                   1/1     Running   0              44h
calico-node-j25xd                                   1/1     Running   0              41h
coredns-6d4b75cb6d-dw4ph                            1/1     Running   0              44h
coredns-6d4b75cb6d-ms2jm                            1/1     Running   0              44h
etcd-kubemaster.ittraining.loc                      1/1     Running   1 (44h ago)    44h
kube-apiserver-kubemaster.ittraining.loc            1/1     Running   1 (44h ago)    44h
kube-controller-manager-kubemaster.ittraining.loc   1/1     Running   10 (75m ago)   44h
kube-proxy-bwctz                                    1/1     Running   0              41h
kube-proxy-j89vg                                    1/1     Running   0              41h
kube-proxy-jx76x                                    1/1     Running   0              44h
kube-scheduler-kubemaster.ittraining.loc            1/1     Running   11 (75m ago)   44h
metrics-server-7cb867d5dc-g55k5                     1/1     Running   0              28h

1.5 - Certificats TLS

Par défaut la communication entre kubectl et l'API Kubernetes est cryptée. Les certificats se trouvent dans le répertoire /var/lib/kubelet/pki/ de chaque noeud :

root@kubemaster:~# ls -l /var/lib/kubelet/pki/
total 12
-rw------- 1 root root 2851 juil. 12 13:23 kubelet-client-2022-07-12-13-23-12.pem
lrwxrwxrwx 1 root root   59 juil. 12 13:23 kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2022-07-12-13-23-12.pem
-rw-r--r-- 1 root root 2367 juil. 12 13:23 kubelet.crt
-rw------- 1 root root 1675 juil. 12 13:23 kubelet.key

Important : Par défaut les certificats de kubelet expirent au bout d'un an.

LAB #2 - Implémentation de la Sécurité au niveau des Pods

2.1 - Présentation

Un Admission Controller est un morceau de code qui intercepte les requêtes à destination de l'API de Kubernetes. L'utilisation des Admission Controllers est définie part la directive –admission-control du fichier /etc/kubernetes/manifests/kube-apiserver.yaml, par exemple :

--admission-control=Initializers, NamespaceLifecycle, LimitRanger, ServiceAccount, PersistentVolumeLabel, DefaultStorageClass, DefaultTolerationSeconds, NodeRestriction, ResourceQuota

Les Admission Controllers les plus importants en termes de sécurité sont :

  • DenyEscalatingExec,
    • interdit l'exécution des commandes avec un escalated container dans un pod priviligié. Les commandes concernées sont exec et attach. Un escalated container dans un pod priviligié n'est pas isolé et permet donc l'accès à l'hôte.
  • NodeRestriction,
    • limite les objets d'un nœud et d'un pod que kubectl est capable de modifier,
  • PodSecurityPolicy,
    • agit lors de la création ou de la modification d'un pod pour décider si celui-ci est admis au cluster en fonction du Contexte de Sécurité et les policies applicables,
  • ValidatingAdmissionWebhooks,
    • permet d'appeler un service externe qui implémente une politique de sécurité, tel que Grafeas.

2.2 - Kubernetes Security Context

La configuration du Contexte de Sécurité se fait du pod ou du conteneur. Voici quelques exemples.

ReadOnlyRootFilesystem

Créez le fichier readonly.yaml :

root@kubemaster:~# vi readonly.yaml
root@kubemaster:~# cat readonly.yaml
apiVersion: v1
kind: Pod
metadata:
  name: flask-ro
  namespace: default
spec:
  containers:
  - image: mateobur/flask
    name: flask-ro
    securityContext:
      readOnlyRootFilesystem: true

Exécutez kubectl :

root@kubemaster:~# kubectl create -f readonly.yaml 
pod/flask-ro created

Vérifiez que le pod est en état de READY :

root@kubemaster:~# kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
flask-ro                                 1/1     Running   0          13m
postgres-deployment-5b8bd66778-j99zz     1/1     Running   7          4d1h
redis-deployment-67d4c466c4-9wzfn        1/1     Running   7          4d1h
result-app-deployment-b8f9dc967-nzbgd    1/1     Running   7          4d1h
result-app-deployment-b8f9dc967-r84k6    1/1     Running   7          3d22h
result-app-deployment-b8f9dc967-zbsk2    1/1     Running   7          3d22h
voting-app-deployment-669dccccfb-jpn6h   1/1     Running   7          4d1h
voting-app-deployment-669dccccfb-ktd7d   1/1     Running   7          3d22h
voting-app-deployment-669dccccfb-x868p   1/1     Running   7          3d22h
worker-app-deployment-559f7749b6-jh86r   1/1     Running   19         4d1h

Connectez-vous au conteneur :

root@kubemaster:~# kubectl exec -it flask-ro bash
root@flask-ro:/#

Notez que le système est en lecture seule :

root@flask-ro:/# mount | grep "/ "
overlay on / type overlay (ro,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/72/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/71/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/70/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/69/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/73/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/73/work)

root@flask-ro:/# touch test
touch: cannot touch 'test': Read-only file system

root@flask-ro:/# exit
exit
command terminated with exit code 1

drop

Créez le fichier drop.yaml :

root@kubemaster:~# vi drop.yaml
root@kubemaster:~# cat drop.yaml
apiVersion: v1
kind: Pod
metadata:
  name: flask-cap
  namespace: default
spec:
  containers:
  - image: mateobur/flask
    name: flask-cap
    securityContext:
      capabilities:
        drop:
          - NET_RAW
          - CHOWN

Exécutez kubectl :

root@kubemaster:~# kubectl create -f drop.yaml
pod/flask-cap created

Vérifiez que le pod est en état de READY :

root@kubemaster:~# kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
flask-cap                                1/1     Running   0          4m4s
flask-ro                                 1/1     Running   0          13m
postgres-deployment-5b8bd66778-j99zz     1/1     Running   7          4d1h
redis-deployment-67d4c466c4-9wzfn        1/1     Running   7          4d1h
result-app-deployment-b8f9dc967-nzbgd    1/1     Running   7          4d1h
result-app-deployment-b8f9dc967-r84k6    1/1     Running   7          3d22h
result-app-deployment-b8f9dc967-zbsk2    1/1     Running   7          3d22h
voting-app-deployment-669dccccfb-jpn6h   1/1     Running   7          4d1h
voting-app-deployment-669dccccfb-ktd7d   1/1     Running   7          3d22h
voting-app-deployment-669dccccfb-x868p   1/1     Running   7          3d22h
worker-app-deployment-559f7749b6-jh86r   1/1     Running   19         4d1h

Connectez-vous au conteneur :

root@kubemaster:~# kubectl exec -it flask-cap -- bash
root@flask-cap:/#

Notez la mise en place des restrictions :

root@flask-cap:/# ping 8.8.8.8
ping: Lacking privilege for raw socket.
root@flask-cap:/# chown daemon /tmp
chown: changing ownership of '/tmp': Operation not permitted

root@flask-cap:/# exit
exit
command terminated with exit code 1

2.3 - Kubernetes Network Policies

Créez le fichier guestbook-all-in-one.yaml :

root@kubemaster:~# vi guestbook-all-in-one.yaml 
root@kubemaster:~# cat  guestbook-all-in-one.yaml 
apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    app: redis
    tier: backend
    role: master
spec:
  ports:
    # the port that this service should serve on
  - port: 6379
    targetPort: 6379
  selector:
    app: redis
    tier: backend
    role: master
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-master
  # these labels can be applied automatically 
  # from the labels in the pod template if not set
  labels:
    app: redis
    role: master
    tier: backend
spec:
  # this replicas value is default
  # modify it according to your case
  replicas: 1
  # selector can be applied automatically 
  # from the labels in the pod template if not set
  # selector:
  #   app: guestbook
  #   role: master
  #   tier: backend
  template:
    metadata:
      labels:
        app: redis
        role: master
        tier: backend
    spec:
      containers:
      - name: master
        image: gcr.io/google_containers/redis:e2e  # or just image: redis
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
  name: redis-slave
  labels:
    app: redis
    tier: backend
    role: slave
spec:
  ports:
    # the port that this service should serve on
  - port: 6379
  selector:
    app: redis
    tier: backend
    role: slave
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-slave
  # these labels can be applied automatically
  # from the labels in the pod template if not set
  labels:
    app: redis
    role: slave
    tier: backend
spec:
  # this replicas value is default
  # modify it according to your case
  replicas: 2
  # selector can be applied automatically
  # from the labels in the pod template if not set
  # selector:
  #   app: guestbook
  #   role: slave
  #   tier: backend
  template:
    metadata:
      labels:
        app: redis
        role: slave
        tier: backend
    spec:
      containers:
      - name: slave
        image: gcr.io/google_samples/gb-redisslave:v1
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        env:
        - name: GET_HOSTS_FROM
          value: dns
          # If your cluster config does not include a dns service, then to
          # instead access an environment variable to find the master
          # service's host, comment out the 'value: dns' line above, and
          # uncomment the line below.
          # value: env
        ports:
        - containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # if your cluster supports it, uncomment the following to automatically create
  # an external load-balanced IP for the frontend service.
  # type: LoadBalancer
  ports:
    # the port that this service should serve on
  - port: 80
  selector:
    app: guestbook
    tier: frontend
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
  # these labels can be applied automatically
  # from the labels in the pod template if not set
  labels:
    app: guestbook
    tier: frontend
spec:
  # this replicas value is default
  # modify it according to your case
  replicas: 3
  # selector can be applied automatically
  # from the labels in the pod template if not set
  # selector:
  #   app: guestbook
  #   tier: frontend
  template:
    metadata:
      labels:
        app: guestbook
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5 
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        env:
        - name: GET_HOSTS_FROM
          value: dns
          # If your cluster config does not include a dns service, then to
          # instead access environment variables to find service host
          # info, comment out the 'value: dns' line above, and uncomment the
          # line below.
          # value: env
        ports:
        - containerPort: 80

Installez l'application Guestbook :

root@kubemaster:~# kubectl create -f guestbook-all-in-one.yaml

Attendez que tous les pods soient dans un état de READY :

root@kubemaster:~# kubectl get pods -o wide
NAME                 READY   STATUS    RESTARTS   AGE   IP               NODE                       NOMINATED NODE   READINESS GATES
flask-cap            1/1     Running   0          53m   192.168.239.26   kubenode1.ittraining.loc   <none>           <none>
flask-ro             1/1     Running   0          59m   192.168.150.14   kubenode2.ittraining.loc   <none>           <none>
frontend-dhd4w       1/1     Running   0          32m   192.168.150.16   kubenode2.ittraining.loc   <none>           <none>
frontend-dmbbf       1/1     Running   0          32m   192.168.150.17   kubenode2.ittraining.loc   <none>           <none>
frontend-rqr6p       1/1     Running   0          32m   192.168.239.29   kubenode1.ittraining.loc   <none>           <none>
redis-master-zrrr4   1/1     Running   0          32m   192.168.239.27   kubenode1.ittraining.loc   <none>           <none>
redis-slave-jsrt6    1/1     Running   0          32m   192.168.150.15   kubenode2.ittraining.loc   <none>           <none>
redis-slave-rrnx9    1/1     Running   0          32m   192.168.239.28   kubenode1.ittraining.loc   <none>           <none>
...

Cette application crée des pods de type backend et frontend :

root@kubemaster:~# kubectl describe pod redis-master-zrrr4 | grep tier
              tier=backend

root@kubemaster:~# kubectl describe pod frontend-dhd4w | grep tier
              tier=frontend

Créez le fichier guestbook-network-policy.yaml qui empêchera la communication d'un pod backend vers un pod frontend :

root@kubemaster:~# vi guestbook-network-policy.yaml
root@kubemaster:~# cat guestbook-network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-backend-egress
  namespace: default
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
    - Egress
  egress:
    - to:
       - podSelector:
           matchLabels:
             tier: backend

Exécutez kubectl :

root@kubemaster:~# kubectl create -f guestbook-network-policy.yaml
networkpolicy.networking.k8s.io/deny-backend-egress created

Connectez-vous au pod redis-master :

root@kubemaster:~# kubectl exec -it redis-master-zrrr4 -- bash
[ root@redis-master-zrrr4:/data ]$ 

Essayez de contacter un pod du même tier :

[ root@redis-master-zrrr4:/data ]$ ping -c 4 192.168.150.15
PING 192.168.150.15 (192.168.150.15) 56(84) bytes of data.
64 bytes from 192.168.150.15: icmp_seq=1 ttl=62 time=0.324 ms
64 bytes from 192.168.150.15: icmp_seq=2 ttl=62 time=0.291 ms
64 bytes from 192.168.150.15: icmp_seq=3 ttl=62 time=0.366 ms
64 bytes from 192.168.150.15: icmp_seq=4 ttl=62 time=0.379 ms

--- 192.168.150.15 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3070ms
rtt min/avg/max/mdev = 0.291/0.340/0.379/0.034 ms

Essayez maintenant de contacter un pod d'un tier frontend :

[ root@redis-master-zrrr4:/data ]$ ping -c 4 192.168.150.16
PING 192.168.150.16 (192.168.150.16) 56(84) bytes of data.

--- 192.168.150.16 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3063ms

Déconnectez-vous du pod redis-master et connectez-vous à un pod frontend :

[ root@redis-master-zrrr4:/data ]$ exit
exit
command terminated with exit code 1

root@kubemaster:~# kubectl exec -it frontend-dhd4w -- bash
root@frontend-dhd4w:/var/www/html#  

Essayez de contacter un pod du même tier :

root@frontend-dhd4w:/var/www/html# ping -c 4 192.168.150.17
PING 192.168.150.17 (192.168.150.17): 56 data bytes
64 bytes from 192.168.150.17: icmp_seq=0 ttl=63 time=0.185 ms
64 bytes from 192.168.150.17: icmp_seq=1 ttl=63 time=0.112 ms
64 bytes from 192.168.150.17: icmp_seq=2 ttl=63 time=0.093 ms
64 bytes from 192.168.150.17: icmp_seq=3 ttl=63 time=0.121 ms
--- 192.168.150.17 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.093/0.128/0.185/0.035 ms

Essayez maintenant de contacter un pod d'un tier backend :

root@frontend-dhd4w:/var/www/html# ping -c 4 192.168.239.27
PING 192.168.239.27 (192.168.239.27): 56 data bytes
64 bytes from 192.168.239.27: icmp_seq=0 ttl=62 time=0.371 ms
64 bytes from 192.168.239.27: icmp_seq=1 ttl=62 time=0.469 ms
64 bytes from 192.168.239.27: icmp_seq=2 ttl=62 time=0.349 ms
64 bytes from 192.168.239.27: icmp_seq=3 ttl=62 time=0.358 ms
--- 192.168.239.27 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.349/0.387/0.469/0.048 ms

Sortez du pod frontend :

root@frontend-dhd4w:/var/www/html# exit
exit
root@kubemaster:~#

2.4 - Kubernetes Resource Allocation Management

Les ressources qui peuvent être limitées au niveau d'un pod sont :

  • CPU
  • Mémoire
  • Stockage local

Créez le fichier flask-resources.yaml :

root@kubemaster:~# vi flask-resources.yaml
root@kubemaster:~# cat flask-resources.yaml
apiVersion: v1
kind: Pod
metadata:
  name: flask-resources
  namespace: default
spec:
  containers:
  - image: mateobur/flask
    name: flask-resources
    resources:
      requests:
        memory: 512Mi
      limits:
        memory: 700Mi

Dans ce fichier on peut constater deux allocations de ressources :

  • requests,
    • la quantité de mémoire qui doit être libre au moment du scheduling du pod,
  • limits,
    • la limite de mémoire pour le pod concerné.

Exécutez kubectl :

root@kubemaster:~# kubectl create -f flask-resources.yaml
pod/flask-resources created

Attendez que le statut du pod soit READY :

root@kubemaster:~# kubectl get pods
NAME                 READY   STATUS    RESTARTS   AGE
flask-cap            1/1     Running   0          67m
flask-resources      1/1     Running   0          53s
flask-ro             1/1     Running   0          74m
...

Connectez-vous au pod :

root@kubemaster:~# kubectl exec -it flask-resources -- bash
root@flask-resources:/# 

Installez le paquet stress :

root@flask-resources:/# apt install stress -y

Testez la limite mise en place :

root@flask-resources:/# stress --cpu 1 --io 1 --vm 2 --vm-bytes 800M
stress: info: [41] dispatching hogs: 1 cpu, 1 io, 2 vm, 0 hdd
stress: FAIL: [41] (416) <-- worker 45 got signal 9
stress: WARN: [41] (418) now reaping child worker processes
stress: FAIL: [41] (452) failed run completed in 1s

Sortez du pod flask-resources :

root@flask-resources:/# exit
exit
root@kubemaster:~#

Copyright © 2023 Hugh Norris

Menu