Démonstration de Helm avec Kubernetes et RBAC

Ce court billet permet juste de formaliser les étapes nécessaires à l'installation d'un Wordpress via Helm sur Minikube. Pour rappel, Helm est la partie client (i.e le nom du binaire client) et Tiller est la partie serveur installé sur le cluster Kubernetes.

Etape 1 :

Installer minikube, kubectl et helm sur votre OS

Etape 2 :

Lancer minikube :

$ minikube start

Vérifier que le kubectl addresse le bon contexte :

$ kubectl cluster-info
Kubernetes master is running at https://192.168.99.100:8443
KubeDNS is running at https://192.168.99.100:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

Etape 3 :

Etant donné que RBAC est maintenant la norme, créer un compte de service nommé "tiller" dans le namespace kube-system et lui donner (en première approximation) les droits admin du cluster :

$ kubectl create serviceaccount tiller --namespace kube-system
serviceaccount "tiller" created

$ kubectl create clusterrolebinding crb-tiller --clusterrole=admin --serviceaccount=kube-system:tiller
clusterrolebinding "crb-tiller" created

Vérifier :

$ kubectl get sa -n kube-system | grep tiller
tiller                               1         12m

$ kubectl get clusterrolebinding -n kube-system | grep tiller
crb-tiller                                             12m

Nous n'aborderons pas ici le foisonnant sujet des droits nécessaires et suffisants à attribuer à un compte de service Tiller appartenant à un tenant dans le cadre d'un CaaS. C'est bien évidemment un sujet récurrent pour nos clients qui souhaitent sécuriser leur plateforme, qui est censé être traité au plus haut niveau par le working group SIG-multi-tenant.

Etape 4 :

Initaliser Helm en utilisant le compte de service crée précédemment :

$ helm init --service-account tiller
$HELM_HOME has been configured at /Users/XXX/.helm.
Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.
Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!

Etape 5 :

Installer le Chart Helm Wordpress (avec les options par défaut) :

$ helm install --name my-release stable/wordpress
NAME:   my-release
LAST DEPLOYED: Fri Jun 29 11:24:53 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/SecretNAME                  TYPE    DATA  AGE
my-release-mariadb    Opaque  2     1s
my-release-wordpress  Opaque  2     1s
==> v1/ConfigMap
NAME                      DATA  AGE
my-release-mariadb        1     1s
my-release-mariadb-tests  1     1s
==> v1/PersistentVolumeClaim
NAME                  STATUS  VOLUME                                    CAPACITY  ACCESS MODES  STORAGECLASS  AGE
my-release-wordpress  Bound   pvc-47c48fc2-7b7e-11e8-9227-080027d62ae5  10Gi      RWO           standard      1s
==> v1/Service
NAME                  TYPE          CLUSTER-IP      EXTERNAL-IP  PORT(S)                     AGE
my-release-mariadb    ClusterIP     10.106.224.132  <none>       3306/TCP                    1s
my-release-wordpress  LoadBalancer  10.100.75.203   <pending>    80:31131/TCP,443:31453/TCP  1s
==> v1beta1/Deployment
NAME                  DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
my-release-wordpress  1        1        1           0          1s
==> v1beta1/StatefulSet
NAME                DESIRED  CURRENT  AGE
my-release-mariadb  1        1        1s
==> v1/Pod(related)
NAME                                  READY  STATUS             RESTARTS  AGE
my-release-wordpress-5c766f647-748hf  0/1    ContainerCreating  0         1s
my-release-mariadb-0                  0/1    Pending            0         1s
NOTES:
1. Get the WordPress URL:
  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        Watch the status with: 'kubectl get svc --namespace default -w my-release-wordpress'
  export SERVICE_IP=$(kubectl get svc --namespace default my-release-wordpress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP/admin
2. Login with the following credentials to see your blog
  echo Username: user
  echo Password: $(kubectl get secret --namespace default my-release-wordpress -o jsonpath="{.data.wordpress-password}" | base64 --decode)

Vérifier les PersistentVolume, PersistentVolumeClaim et les Services :

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                               STORAGECLASS   REASON    AGE
pvc-47c48fc2-7b7e-11e8-9227-080027d62ae5   10Gi       RWO            Delete           Bound     default/my-release-wordpress        standard                 44s
pvc-47fbf890-7b7e-11e8-9227-080027d62ae5   8Gi        RWO            Delete           Bound     default/data-my-release-mariadb-0   standard                 43s

$ kubectl get pvc
NAME                        STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-my-release-mariadb-0   Bound     pvc-47fbf890-7b7e-11e8-9227-080027d62ae5   8Gi        RWO            standard       51s
my-release-wordpress        Bound     pvc-47c48fc2-7b7e-11e8-9227-080027d62ae5   10Gi       RWO            standard       51s

$ kubectl get svc --namespace default -w my-release-wordpress
NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
my-release-wordpress   LoadBalancer   10.100.75.203   <pending>     80:31131/TCP,443:31453/TCP   1m


Tout s'est bien passé , il ne reste plus qu'à demander à minikube de lancer le navigateur (notez bien le HTTPS) :

$ minikube service my-release-wordpress

Opening kubernetes service default/my-release-wordpress in default browser...

Opening kubernetes service default/my-release-wordpress in default browser...

 

bt-admin
05 Dec 2018
Linux Capabilities en environnement Docker/Kubernetes

Nous nous sommes intéressés récemment aux Linux Capabilities , dans le cadre de durcissement de containers Docker ou de cluster Kubernetes.

Il existe de nombreuses références à ce sujet sur Internet et dépasse l'objet de cet article.

Les Capabilities , intégrées aux Kernel Linux, permettent de décrire finement les droits nécessaires à un exécutable et sont référencées ici. Celles qui nous intéressent le plus souvent dans le cas du confinement de containers Docker sont  :

/* Allow interface configuration */
 /* Allow administration of IP firewall, masquerading and accounting */
 /* Allow setting debug option on sockets */
 /* Allow modification of routing tables */
 /* Allow setting arbitrary process / process group ownership on
 sockets */
 /* Allow binding to any address for transparent proxying (also via NET_RAW) */
 /* Allow setting TOS (type of service) */
 /* Allow setting promiscuous mode */
 /* Allow clearing driver statistics */
 /* Allow multicasting */
 /* Allow read/write of device-specific registers */
 /* Allow activation of ATM control sockets */

#define CAP_NET_ADMIN 12

/* Allow use of RAW sockets */
 /* Allow use of PACKET sockets */
 /* Allow binding to any address for transparent proxying (also via NET_ADMIN) */

#define CAP_NET_RAW 13

Un exemple de classique utilise le binaire "ping" :

[email protected]:~# ls -lh /bin/ping
-rwsr-xr-x 1 root root 44K May  7  2014 /bin/ping

On constate que le bit S (setuid) est activé car ping nécessite le droit de créer une socket ICMP. Si "root" retire ce droit, le ping ne fonctionne plus pour l'utilisateur "toto":

# chmod -s /bin/ping
# su toto
$ ping randco.fr
ping: icmp open socket: Operation not permitted

Si on ajoute la Capability NET_RAW, ping fonctionne à nouveau et ce nouveau droit est à la fois suffisant et bien plus restreint qu'un bit SETUID :

# setcap cap_net_raw+p /bin/ping
# su toto
$ ping randco.fr
PING randco.fr (164.132.216.245) 56(84) bytes of data.
64 bytes from 164.132.216.245: icmp_seq=1 ttl=57 time=5.72 ms
64 bytes from 164.132.216.245: icmp_seq=2 ttl=57 time=5.30 ms

Dans Kubernetes, les Pod Security Policies (PSP) permettent de détailler au niveau du Cluster les Capabilities à ajouter ou à retirer aux Pods. Ci-dessous un exemple de Policy très contrainte :

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: restricted
spec:
  privileged: false
  # Required to prevent escalations to root.
  allowPrivilegeEscalation: false
  # This is redundant with non-root + disallow privilege escalation,
  # but we can provide it for defense in depth.
  requiredDropCapabilities:
    - ALL
[..]


Pour apprendre par le test, nous ne pouvons que vous conseiller d'explorer https://contained.af/.

Si vous avez des questions plus globales sur la sécurisation de cluster Kubernetes, contactez-nous.

bt-admin
05 Dec 2018