Traefik, cert-manager, Ovh et Let's Encrypt sont une combinaison gagnante lorsqu'il s'agit de sécuriser vos services avec des certificats dans Kubernetes. Aujourd'hui, nous allons installer et configurer Traefik, le proxy natif cloud et l'équilibreur de charge, en tant que contrôleur d'entrée Kubernetes. Nous allons ensuite installer et configurer cert-manager pour gérer les certificats de notre cluster. Nous allons configurer Let's Encrypt en tant qu'émetteur de cluster afin que cert-manager puisse provisionner automatiquement des certificats TLS et même des certificats génériques à l'aide du défi DNS Ovh tout à fait gratuitement. Nous allons parcourir tout cela, étape par étape, afin que vous puissiez aider à sécuriser votre cluster dès aujourd'hui.
Il vous faut d'abord un cluster K3S fonctionnel.
Ensuite il vous faut installer HELM :
Il est possible de l'installer soit sur un des masters du cluster ou directement sur votre machine à partir du moment ou votre utilisateur courant à bien dans son "HOME/.KUBE" le fichier de config du serveur.
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
Pour d'autres façons d'installer Helm, consultez la documentation d'installation ici.
Vérifier que vous pouvez communiquer avec votre cluster
kubectl get nodes
Tu devrais voir
NAME STATUS ROLES AGE VERSION
lab-k8s-master01 Ready control-plane,etcd,master 5d20h v1.24.4+k3s1
lab-k8s-master02 Ready control-plane,etcd,master 5d20h v1.24.4+k3s1
lab-k8s-master03 Ready control-plane,etcd,master 5d20h v1.24.4+k3s1
lab-k8s-worker01 Ready <none> 5d20h v1.24.4+k3s1
lab-k8s-worker02 Ready <none> 5d20h v1.24.4+k3s1
Vérifier que helm est installée
version.BuildInfo{Version:"v3.10.0", GitCommit:"ce66412a723e4d89555dc67217607c6579ffcb21", GitTreeState:"clean", GoVersion:"go1.18.6"}
Ajoutez un dépôt
helm repo add traefik https://helm.traefik.io/traefik
Mettre à jour le dépôt
helm repo update
Créer notre namespaces
kubectl create namespace traefik
Obtenir tous les namespaces
kubectl get namespaces
Nous devrions voir
NAME STATUS AGE
default Active 21h
kube-node-lease Active 21h
kube-public Active 21h
kube-system Active 21h
metallb-system Active 21h
traefik Active 12s
Il faut maintenant créer l'ensemble des fichiers nécessaires pour le bon fonctionnement de Traefik. Pour ce faire commencez par créer un dossier traefik ou l'on mettra nos manifests
mkdir traefik
cd traefik
créer ensuite deux fichiers, un qui s'appelle values.yaml et l'autre default-headers.yaml
touch values.yaml
touch default-headers.yaml
Ensuite on créer un dossier dashboard
mkdir dashboard
cd dashboard
Dans ce dossier 3 fichiers sont à créer : ingress.yaml, middleware.yaml et secret-dashboard.yaml
touch ingress.yaml
touch middleware.yaml
touch secret-dashboard.yaml
Vous devriez obtenir cette arborescence
└── traefik
├── dashboard
│ ├── ingress.yaml
│ ├── middleware.yaml
│ └── secret-dashboard.yaml
├── default-headers.yaml
└── values.yaml
A présent il faut modifier le fichier values.yaml avec le contenu suivant
globalArguments:
- "--global.sendanonymoususage=false"
- "--global.checknewversion=false"
additionalArguments:
- "--serversTransport.insecureSkipVerify=true"
- "--log.level=INFO"
deployment:
enabled: true
replicas: 3
annotations: {}
podAnnotations: {}
additionalContainers: []
initContainers: []
ports:
web:
redirectTo: websecure
websecure:
tls:
enabled: true
ingressRoute:
dashboard:
enabled: false
providers:
kubernetesCRD:
enabled: true
ingressClass: traefik-external
kubernetesIngress:
enabled: true
publishedService:
enabled: false
rbac:
enabled: true
service:
enabled: true
type: LoadBalancer
annotations: {}
labels: {}
spec:
loadBalancerIP: 192.168.40.75 # Cela devrait être une adresse IP dans votre range MetalLB
loadBalancerSourceRanges: []
externalIPs: []
Pensez à bien modifier la valeur loadBalancerIP avec la vôtre.
Maintenant nous pouvons installer Traefik en s'appuyant sur les valeurs contenues dans le fichier values.yaml
helm install --namespace=traefik traefik traefik/traefik --values=values.yaml
Vérifiez l'état du service de l'ingress controler traefik
kubectl get svc --all-namespaces -o wide
Nous devrions voir traefik avec l'adresse IP spécifiée
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
default kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 16h <none>
kube-system kube-dns ClusterIP 10.43.0.10 <none> 53/UDP,53/TCP,9153/TCP 16h k8s-app=kube-dns
kube-system metrics-server ClusterIP 10.43.182.24 <none> 443/TCP 16h k8s-app=metrics-server
metallb-system webhook-service ClusterIP 10.43.205.142 <none> 443/TCP 16h component=controller
traefik traefik LoadBalancer 10.43.156.161 192.168.40.75 80:30358/TCP,443:31265/TCP 22s app.kubernetes.io/instance=traefik,app.kubernetes.io/name=traefik
Vérifions aussi que nos 3 replicas Traefik ont bien été créé dans le namespaces traefik
kubectl get pods --namespace traefik
Nous devrions voir des pods dans le namespaces traefik
NAME READY STATUS RESTARTS AGE
traefik-6b89cfbcc9-d9mpp 1/1 Running 1 (101m ago) 24h
traefik-6b89cfbcc9-vb774 1/1 Running 1 (101m ago) 24h
traefik-6b89cfbcc9-zrszt 1/1 Running 1 (101m ago) 24h
Modifiez le fichier default-headers.yaml comme ceci
piVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: default-headers
namespace: default
spec:
headers:
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 15552000
customFrameOptionsValue: SAMEORIGIN
customRequestHeaders:
X-Forwarded-Proto: https
Appliquez le middleware
kubectl apply -f default-headers.yaml
Obtenir le middleware
kubectl get middleware
Nous devrions voir nos en-têtes
NAME AGE
default-headers 25s
Installer htpassword
sudo apt-get update
sudo apt-get install apache2-utils
Générez un identifiant/mot de passe encodé en base64
htpasswd -nb Administrateur password | openssl base64
Mettez l'identifiant et le mot de passe que vous souhaitez.
Voici la sortie de la commande ci-dessus
QWRtaW5pc3RyYXRldXI6JGFwcjEkSWxjMDgub2IkTmt6bGN6NjJ2by4uQlJ0Y3NTWjB3LgoK
Maintenant on modifie le fichier secret-dashboard.yaml
---
apiVersion: v1
kind: Secret
metadata:
name: traefik-dashboard-auth
namespace: traefik
type: Opaque
data:
users: QWRtaW5pc3RyYXRldXI6JGFwcjEkSWxjMDgub2IkTmt6bGN6NjJ2by4uQlJ0Y3NTWjB3LgoK # On renseigne ici la sortie de notre hash en base64
Pensez à bien changer la valeur de users par votre hash en base64.
Appliquez le secret
kubectl apply -f secret-dashboard.yaml
Obtenez le secret
kubectl get secrets --namespace traefik
On modifie le fichier middleware.yaml comme ceci
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: traefik-dashboard-basicauth
namespace: traefik
spec:
basicAuth:
secret: traefik-dashboard-auth
Appliquez le middleware
kubectl apply -f middleware.yaml
On modifie le fichier ingress.yaml comme ceci
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
namespace: traefik
annotations:
kubernetes.io/ingress.class: traefik-external
spec:
entryPoints:
- websecure
routes:
- match: Host(`www.traefik.example-domain.fr`)
kind: Rule
middlewares:
- name: traefik-dashboard-basicauth
namespace: traefik
services:
- name: api@internal
kind: TraefikService
- match: Host(`traefik.example-domain.fr`)
kind: Rule
middlewares:
- name: traefik-dashboard-basicauth
namespace: traefik
services:
- name: api@internal
kind: TraefikService
#tls:
#secretName: example-domain-fr-tls
Il faut modifiez les valeurs qui se trouvent dans Host(' ') par les vôtres.
Il faut que le nom que vous renseignez soit résolu par votre dns. Si vous avez votre propre serveur dns interne créer une nouvelle entrée qui pointe vers l'IP renseigner dans loadBalancerIP dans values.yaml ou modifiez votre fichier hosts.
Le dashboard ne sera accessible que depuis l'adresse renseignée dans Host(' ').
Pour les valeurs tls et secretName commentées, vous pourrez les décommenter en renseignant votre certificat.
Nous le mettrons en place plus loin dans ce tuto. Cela vous permettra de certifier votre dashboard en HTTPS.
Appliquer le tableau de bord
kubectl apply -f ingress.yaml
Il faut maintenant créer l'ensemble des fichiers nécessaires pour le bon fonctionnement de Cert-manager. Pour ce faire commencez par créer un dossier cert-manager ou l'on mettra nos manifests
mkdir cert-manager
cd cert-manager
créer ensuite un fichier qui s'appelle values.yaml
touch values.yaml
Ensuite on créer deux dossiers certificate et issuers
mkdir certificates
mkdir issuers
Dans le dossier certificates créez deux sous-dossiers : production et staging
mkdir certificates/production
mkdir certificates/staging
Puis dans chacun de ses sous dossier un fichier local-example-com.yaml
touch certificates/production/local-example-com.yaml
touch certificates/staging/local-example-com.yaml
Ensuite dans le dossier issuers, créer 3 fichiers : letsencrypt-production.yaml, letsencrypt-staging.yaml et cert-manager-webhook-ovh.yaml
touch issuers/letsencrypt-production.yaml
touch issuers/letsencrypt-staging.yaml
touch issuers/cert-manager-webhook-ovh.yaml
Vous devriez obtenir cette arborescence
└── cert-manager
├── certificates
│ └── production
│ └── local-production-example-com.yaml
│ └── staging
│ └── local-staging-example-com.yaml
├── issuers
│ ├── letsencrypt-production.yaml
│ ├── letsencrypt-staging.yaml
│ └── cert-manager-webhook-ovh.yaml
└── values.yaml
Ajouter un dépôt
helm repo add jetstack https://charts.jetstack.io
Mettez-le à jour
helm repo update
Créer notre espace de noms
kubectl create namespace cert-manager
Obtenir tous les namespaces
kubectl get namespaces
Nous devrions voir
NAME STATUS AGE
cert-manager Active 12s
default Active 21h
kube-node-lease Active 21h
kube-public Active 21h
kube-system Active 21h
metallb-system Active 21h
traefik Active 4h35m
Installez CRDS
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml
Remarque : Assurez-vous de le remplacer par la dernière version de cert-manager.
A présent il faut modifier le fichier values.yaml avec le contenu suivant
installCRDs: false
replicaCount: 3
extraArgs:
- --dns01-recursive-nameservers=1.1.1.1:53,9.9.9.9:53
- --dns01-recursive-nameservers-only
podDnsPolicy: None
podDnsConfig:
nameservers:
- "1.1.1.1"
- "9.9.9.9"
Installez avec Helm cert-manager
helm install cert-manager jetstack/cert-manager --namespace cert-manager --values=values.yaml --version v1.9.1
Ensuite installez Git
sudo apt-get update
sudo apt-get install git
Clonez le depôt suivant
git clone https://github.com/baarde/cert-manager-webhook-ovh.git
Puis lancez l'installation du webhook
helm install cert-manager-webhook-ovh ./cert-manager-webhook-ovh/deploy/cert-manager-webhook-ovh \
--set groupName='<VOTRE_UNIQUE_NOM_DE_GROUPE>'
Choisissez un nom de groupe unique pour identifier votre entreprise ou organisation (par exemple acme.mycompany.example).
Si vous avez personnalisé l'installation de cert-manager, vous devrez peut-être également définir les valeurs certManager.namespace et certManager.serviceAccountName
Créer une nouvelle clé API d'OVH avec les droits suivants
Pour avoir plus d'informations sur le fonctionnement de l'API d'OVH, allez ici
Créer un nouveau secret pour le stocker dans votre application secret
kubectl create secret generic ovh-credentials --from-literal=applicationSecret='<OVH_APPLICATION_SECRET>'
Accordez l'autorisation d'accès au secret pour le compte de service cert-manager-webhook-ovh en remplissant le fichier cert-manager-webhook-ovh.yaml dans le dossier issuers
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cert-manager-webhook-ovh:secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["ovh-credentials"]
verbs: ["get", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cert-manager-webhook-ovh:secret-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: cert-manager-webhook-ovh:secret-reader
subjects:
- apiGroup: ""
kind: ServiceAccount
name: cert-manager-webhook-ovh
Appliquez le cert-manager-webhook-ovh.yaml
kubectl apply -f issuers/cert-manager-webhook-ovh.yaml
Ensuite dans le fichier letsencrypt-production.yaml vous devrez remplir comme ceci
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: '<VOTRE_ADRESSE_MAIL>'
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- dns01:
webhook:
groupName: '<VOTRE_UNIQUE_NOM_DE_GROUPE>'
solverName: ovh
config:
endpoint: ovh-eu
applicationKey: '<OVH_APPLICATION_KEY>'
applicationSecretRef:
key: applicationSecret
name: ovh-credentials
consumerKey: '<OVH_CONSUMER_KEY>'
Remarque : la valeur pour <VOTRE_UNIQUE_NOM_DE_GROUPE> est celle mis plus haut lors de la création du webhook (par exemple acme.mycompany.example).
Puis dans le fichier letsencrypt-staging.yaml vous devrez remplir comme ceci
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: '<VOTRE_ADRESSE_MAIL>'
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- dns01:
webhook:
groupName: '<VOTRE_UNIQUE_NOM_DE_GROUPE>'
solverName: ovh
config:
endpoint: ovh-eu
applicationKey: '<OVH_APPLICATION_KEY>'
applicationSecretRef:
key: applicationSecret
name: ovh-credentials
consumerKey: '<OVH_CONSUMER_KEY>'
Le staging n'est la que pour faire des tests en environnent de développement.
Appliquez les deux fichiers letsencrypt
kubectl apply -f issuers/letsencrypt-production.yaml
kubectl apply -f issuers/letsencrypt-staging.yaml
Maintenant, créons les demandes de certificat.
Pour ce faire remplissez comme suit le fichier local-example-com.yaml qui se trouve dans certificat/production.
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: local-example-com
namespace: default
spec:
secretName: local-example-com-tls
issuerRef:
name: letsencrypt-production
kind: ClusterIssuer
commonName: "*.local.example.com"
dnsNames:
- "local.example.com"
- "*.local.example.com"
Remarque : Pour ne pas vous embêter à gèrer un certificat par sous-domaine, créez directement un certificat wildcard que vous pourrez attribuer à l'ensemble de vos services.
Appliquez le certificat
kubectl apply -f certificates/production/local-example-com.yaml
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: local-example-com-staging
namespace: default
spec:
secretName: local-example-com-staging-tls
issuerRef:
name: letsencrypt-staging
kind: ClusterIssuer
commonName: "*.local.example.com"
dnsNames:
- "local.example.com"
- "*.local.example.com"
Remarque : Pour ne pas vous embêter à gèrer un certificat par sous-domaine, créez directement un certificat wildcard que vous pourrez attribuer à l'ensemble de vos services
Appliquez le certificat
kubectl apply -f certificates/staging/local-example-com.yaml
Maintenant pour vérifier que l'ensemble du processus fonctionne, on peut aller voir les logs des pods cert-manager
Tous d'abord il faut récupérer le nom des pods avec la commande suivante
kubectl get po -n cert-manager
voici la sortie
NAME READY STATUS RESTARTS AGE
cert-manager-585f78bd4b-8nwlr 1/1 Running 1 (6h1m ago) 27h
cert-manager-585f78bd4b-dd5jp 1/1 Running 1 (6h1m ago) 27h
cert-manager-585f78bd4b-vhwcp 1/1 Running 1 (6h1m ago) 27h
cert-manager-cainjector-bbdb88874-fqgkn 1/1 Running 1 (6h1m ago) 28h
cert-manager-webhook-5774d5d8f7-knss4 1/1 Running 1 (6h1m ago) 28h
Vérifier les journaux
kubectl logs -n cert-manager -f cert-manager-585f78bd4b-vhwcp
Remarque : Il vous faudra vérifier les 3 pods pour savoir lequel traite la demande.
Vérifier si les certificats sont valides
kubectl get certificates
NAME READY SECRET AGE
cert-manager-webhook-ovh-ca True cert-manager-webhook-ovh-ca 27h
cert-manager-webhook-ovh-webhook-tls True cert-manager-webhook-ovh-webhook-tls 27h
local-example-com True local-example-com-tls 26h
local-example-com-staging True local-example-com-staging-tls 26h
Si la valeur est à True c'est que la demande est bien valide et le certificat fonctionnel. Dans le cas contraire regardez les logs des pods de cert-manager pour trouver l'erreur. La validation d'un certificat peut prendre quelques minutes.
Pour vérifier que ça fonctionne, nous allons lancer un service composé de 3 pods nginx.
Créer le fichier suivant : deployment.yaml
Avec ce contenu
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
replicas: 3
progressDeadlineSeconds: 600
revisionHistoryLimit: 2
strategy:
type: Recreate
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: "nginx:latest"
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
selector:
app: nginx
ports:
- name: http
targetPort: 80
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: nginx
namespace: default
annotations:
kubernetes.io/ingress.class: traefik-external
spec:
entryPoints:
- websecure
routes:
- match: Host(`www.nginx.example.com`)
kind: Rule
services:
- name: nginx
port: 80
- match: Host(`nginx.example.com`)
kind: Rule
services:
- name: nginx
port: 80
middlewares:
- name: default-headers
tls:
secretName: local-example-com-tls
Appliquez le manifest
kubectl apply -f deployment.yaml
Bien sûr pour accéder à votre service il faudra que celui soit défini dans votre serveur DNS à l'IP attribué à Traefik avec comme enregistrement nginx.example.com ou directement dans votre fichier hosts.