Argo CD, 1Password et… Forgejo

Jusqu’à présent, Argo CD me semblait être une complication inutile dans mon cluster Kubernetes utilisé au sein de mon réseau local. Puis… je n’ai pas changé d’avis, mais j’ai eu un besoin mineur comme bonne excuse pour me former sur le sujet.

Contexte (pourquoi ?)

Je vais être un peu long sur le contexte, mais ça me semble important tellement il y a de situations différentes, et de fonctionnalité possibles, parfois très poussées, dans la mise en oeuvre d’Argo CD. Mon cas d’usage est probablement le plus simple qui soit.

J’ai un mini-pc au sein de mon réseau local qui héberge un cluster (d’un seul noeud) K3S, dans lequel tournent un certain nombre de services. Par le passé, j’ai démarré avec K3S au sein de mon NAS QNAP, mais ce n’était pas une bonne solution car le NAS a été très vite surchargé (le CPU est trop peu puissant notamment, et la RAM est vite saturée également), sans compter que les mises à jour assez régulières de QNAP rendaient mon cluster indisponible pendant suffisamment longtemps pour que ce soit dérangeant. K3S tourne très bien sur de petits CPU, cependant le NAS que j’ai a un CPU ridiculement peu puissant (un Celeron J1900 qui est déjà très sollicité par sa fonction première de serveur de fichiers).

Comme mon cluster Kubernetes fonctionnait très bien, j’ai ajouté progressivement les services dont j’avais envie. Au bout d’un moment, j’avais donc un certain nombre d’URLs à avoir en bookmark. Pour centraliser tout ça, j’ai donc voulu créer une « home page » dans mon réseau local avec tous mes liens internes. J’ai trouvé que homepage répondait exactement à ce besoin. Il s’intègre très facilement dans Kubernetes, et toute sa configuration se fait via un Config Map, c’est donc très simple à maintenir et à enrichir au fil du temps.

J’ai également une instance Forgejo dans mon cluster Kubernetes pour gérer mes dépôt de code (réellement) privés, plutôt que de m’appuyer sur GitHub (que j’utilise pour quelques dépôts publics). Forgejo est un magnifique clone open source de GitHub, qu’on peut déployer sur son infrastructure gratuitement.

Je maintiens mes manifestes pour Kubernetes (déploiements, config maps, services, etc.) dans mon instance Forgejo. Cela sert à la fois d’une forme de backup, et surtout me permet de conserver l’historique des modifications, ce qui est bien utile. J’évite d’y stocker les secrets mais comme je commit le manifeste du secret, il pouvait m’arriver d’oublier de retirer le secret qu’il contient (heureusement peu de conséquences dans mon dépôt vraiment privé, mais ça ne fait pas propre).

A chaque fois que je devais modifier ma homepage, il fallait éditer le config map, mettre à jour le dépôt git dans Forgejo, appliquer le manifeste dans Kubernetes. Potentiellement plusieurs fois en cas d’erreurs à corriger. C’est là que j’ai pensé à Argo CD. Certes toujours objectivement overkill dans ma situation, mais légèrement fun de voir ma homepage se rafraichir en quasi temps réel après juste un push sur mon serveur git.

Comme mon cas d’usage avec homepage et Argo CD s’est finalement révélé assez simple à mettre en oeuvre, je me suis laissé aller à vouloir mettre dans Argo CD le maximum de mes autres services. Le premier vrai obstacle rencontré a été la gestion des secrets, objet de ce présent article. Une fois la solution en place, je la trouve assez élégante car cela va aussi limiter mon risque d’erreur d’enregistrer mes secrets dans mon dépôt git privé, puisque ce n’est plus moi qui vais les déployer (je n’aurai donc plus à inclure le secret dans le manifeste adéquat, l’appliquer dans Kubernetes, remplacer le secret du manifeste par une fausse valeur avec risque d’oubli, et commiter dans git).

Pour mes (vrais) secrets, il se trouve que j’utilise 1Password. Il se trouve aussi que l’intégration avec Argo CD est possible via le plugin argocd-vault-plugin (qui supporte la connexion à un grand nombre de types de vaults).

Pour résumer, je disposais de:

– Kubernetes (K3S) avec de nombreux services déjà déployés manuellement.
Forgejo dans Kubernetes en guise de serveur git privé.
Argo CD à installer.
– Un abonnement individuel à 1Password. La documentation de 1Password semble indiquer qu’il faut au minimum un abonnement « Family » pour utiliser 1Password Connect, mais dans mon cas cela fonctionne avec l’abonnement le plus basique (payant malgré tout).

Contenu de cet article (quoi ?)

Cet article présente :

– Anecdotique mais utile: Si seul le Config Map change, Argo CD déploiera celui-ci, mais cela ne déclenche pas automatiquement le rafraichissement des déploiements qui en dépendent. On installera donc également le contrôleur Kubernetes Reloader afin que les pods qui dépendent d’un Config Map soit redéployés automatiquement si le Config Map change.
– Le détail de la mise en oeuvre de Argo CD (v2.12)
– pour déployer les ressources Kubernetes à partir de manifestes stockés dans un dépôt git (sur un serveur git qui dans mon cas est Forgejo),
– avec la gestion des secrets stockés dans 1Password via argocd-vault-plugin (v1.18.1).
– Le plugin argocd-vault-plugin sera ainsi capable de remplacer des placeholders dans les manifestes des secrets par la valeur du secret stocké dans 1Password.

Je ne détaille pas l’installation du cluster K3S (v1.30.3), ni celle de l’instance Forgejo (v7.*) au sein de ce cluster qui n’est pas un petit sujet. C’est en revanche très bien documenté sur leur site.

J’ai fait le minimum pour l’intallation de Argo CD et du plugin argocd-vault-plugin, je n’ai par exemple pas remplacé le certifcat TLS auto-signé par un véritable certificat (de confiance). Je n’ai pas intégré le plugin argocd-vault-plugin avec Helm ou Kustomize.

Comment installer Reloader (contrôleur Kubernetes) ?

Si vous avez manuellement défini une network policy dans le namespace « default » qui empêche aux pods tout appel extérieur par défaut (egress), et en supposant qu’on va installer Reloader dans ce namespace, il faut au préalable définir une network policy qui va autoriser Reloader à communiquer avec l’API Kubernetes. Voici un exemple d’une telle network policy qui autorisera Reloader à faire des appels dans notre réseau local sur la plage d’IP « 10.0.0.0/10 » (10.0.0.0-10.63.255.255):


apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: reloader-networkpolicy
spec:
  podSelector:
    matchLabels:
      app: "reloader-reloader"
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/10
  policyTypes:
  - Egress

Si Reloader se voit refuser un accès à cause d’une network policy, on observera ce type de message d’erreur dans ses logs:


failed to list *v1.ConfigMap: Get "https://10.43.0.1:443/api/v1/namespaces/default/configmaps?resourceVersion=1833186": dial tcp 10.43.0.1:443: connect: connection refused
Failed to watch *v1.ConfigMap: failed to list *v1.ConfigMap: Get "https://10.43.0.1:443/api/v1/namespaces/default/configmaps?resourceVersion=1833186": dial tcp 10.43.0.1:443: connect: connection refused

Ensuite pour installer proprement dit Reloader, j’ai suivi la documentation officielle à partir du Helm Chart sur leur site:


helm repo add stakater https://stakater.github.io/stakater-charts
helm repo update
helm install reloader stakater/reloader -n default --set reloader.watchGlobally=false

Les instructions post-installation expliquent comment utiliser Reloader:


NOTES:
- For a `Deployment` called `foo` have a `ConfigMap` called `foo-configmap`. Then add this annotation to main metadata of your `Deployment`
  configmap.reloader.stakater.com/reload: "foo-configmap"

- For a `Deployment` called `foo` have a `Secret` called `foo-secret`. Then add this annotation to main metadata of your `Deployment`
  secret.reloader.stakater.com/reload: "foo-secret"

- After successful installation, your pods will get rolling updates when a change in data of configmap or secret will happen.

Dans l’exemple du premier déploiement avec homepage, on mettra en oeuvre ces instructions.

Comment déployer Argo CD ?

Je me suis contenté de suivre les étapes décrites dans la documentation officielle et je n’ai rencontré aucune difficulté particulière:


kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

Pour tester l’accès à l’UI Argo CD, ouvrez un nouveau terminal pour lancer la commande suivante:


kubectl port-forward svc/argocd-server -n argocd 8080:443

Puis accédez à l’UI via https://localhost:8080

Vous aurez une erreur de certificat dans le navigateur car il s’agit d’un certificat auto-signé. Il faudra accepter de continuer malgré tout.

Argo CD

Si vous souhaitez rendre l’UI accessible en permanence, il faudra l’exposer via un service dans Kubernetes (soit en patchant le service existant, soit en ajoutant un autre service). Dans mon cas, cela me convient très bien de ne pas le laisser accessible en permanence.

Le nom d’utilisateur est « admin » et le mot de passe par défaut de Argo CD est généré lors de l’installation et peut être récupéré via cette commande Powershell:


kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | ForEach-Object { [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($_)) };

Une fois le déploiement opérationnel, télécharger le CLI argocd et lancer la commande suivante pour initialiser son accès au serveur argo CD. Cela suppose de le lancer dans un terminal qui contient la configuration adéquate du cluster Kubernetes auquel se connecter (typiquement défini par la variable d’environnement KUBECONFIG pointant vers le fichier .kube config en question). Notez qu’on définit le namespace « argocd » dans le contexte courant de kubectl. C’est nécessaire pour la plupart des commandes avec le CLI argocd. Pour restaurer le namespace « default » dans le contexte courant, il suffira de rejouer la même commande avec « default » au lieu de « argocd » comme valeur de namespace.


kubectl config set-context --current --namespace=argocd
argocd login --core

Noter que cette commande ne dépend pas du mot de passe de Argo CD car elle se connecte directement à l’API Kubernetes pour contrôler Argo CD.

Note pour plus tard: la mise à jour d’Argo CD est assez simple et nécessite une seul commande décrite dans la documentation officielle (il faudra bien évidemment lire les release notes pour tenir compte des éventuels changements impactants).

Intégration avec Forgejo

On va supposer que vous avez créé un dépôt git accessible en https se trouve sur (Forgejo ou autre compatible): https://git.local/soho/deployments-cicd.git

Nous supposons toujours que ce dépôt contient un répertoire apps dans lequel se trouve un sous-répertoire par application à déployer.

Forgejo

Notre première application de test sera homepage, ses manifestes se trouvent donc dans https://git.local/soho/deployments-cicd/src/branch/main/apps/homepage. Il contient typiquement un config map, un deployment et un service. Pour une autre application (composée de simples manifestes Kubernetes à déployer), vous adapterez aisément ces instructions.

Forgejo

Comment configurer un dépôt git privé dans Argo CD ?

Si notre dépôt git est privé, il faut créer un access token depuis le serveur git et l’enregistrer dans Argo CD.

Dans l’UI Forgejo, c’est très simple et c’est très similaire à GitHub: dérouler le menu du compte utilisateur, Settings, puis section Applications dans laquelle on peut créer un access token qu’on nommera par exemple « argocd » avec un accès à l’API repository en lecture.

Forgejo

Dans l’UI Argo CD, il faut aller dans Settings, Repositories, Connect, et déclarer l’URL de notre dépôt git (https://git.local/soho/deployments-cicd.git dans nos exemples), avec comme nom d’utilisateur celui utilisé dans Forgejo, et comme mot de passe l’access token que vous venez de générer.

Argo CD

Comment configurer un webhook dans Forgejo ?

Toute cette section pré-suppose que vous avez déployé Argo CD et Forgejo dans le même cluster Kubernetes. Si vous utilisez GitHub (sur Internet), vous ne pourrez pas facilement mettre en oeuvre cette solution puisque par défaut, GitHub ne pourra pas invoquer votre serveur Argo CD. Il existe bien sûr des solutions alternatives mais ce n’est pas l’objet de cet article. Sans webhook, la synchronisation automatique par Argo CD fonctionnera, mais basée sur un délai (par défaut une vérification toutes les 3 minutes).

Pour autoriser Forgejo à envoyer un webhook à Argo CD (pour déclencher une synchronisation automatique), il faut s’assurer que les paramètres suivants sont bien définis dans les variables d’environnement du déploiement Forgejo. Le manifeste qui suit est seulement un extrait partiel et ne contient que les deux paramètres ALLOWED_HOST_LIST et SKIP_TLS_VERIFY. Le premier indique qu’on autorise Forgejo à envoyer des webhooks à toute adresse IP privée (dans un réseau local), le second indique qu’on accepte les certificats auto-signés. La documentation est ici.


apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: forgejo
        image: codeberg.org/forgejo/forgejo:7-rootless
        env:
        - name: FORGEJO__webhook__ALLOWED_HOST_LIST
          value: "private"
        - name: FORGEJO__webhook__SKIP_TLS_VERIFY
          value: "true"

Une fois Forgejo mis à jour avec ces paramètres, on peut créer notre webhook depuis notre dépôt git Forgejo vers Argo CD.

Dans les settings du dépôt (bouton Settings dans https://git.local/soho/deployments-cicd), à la section Webhooks, on crée un webhook de type « Forgejo » (c’est effectivement contre-intuitif). L’URL du webhook sera http://argocd-server.argocd:80/api/webhook (« argocd-server » est le nom par défaut du service qui expose l’API Argo CD et « argocd » est le namespace). Les autres paramètres par défaut sont les bons: HTTP method « POST », content type « application/json », trigger on: « Push events ». Dans mon cas j’ai aussi précisé le nom de la branche pour laquelle je souhaite déclencher le webhook (branche « main »).

Forgejo webhook

Pour vérifier la cohérence de notre URL, vous pouvez la comparer avec le résultat de la commande suivante:


kubectl get services -n argocd

NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)
argocd-server   ClusterIP   10.43.77.86             80/TCP,443/TCP

Après avoir créé le webhook, vous pouvez le tester directement depuis Forgejo en l’éditant (il y a un bouton de test). Dans la même page, vous aurez l’historique récent des appels et s’ils ont réussi ou pas.

Comment faire un premier déploiement sans secret ?

Toujours en supposant que l’adresse de notre dépôt git est https://git.local/soho/deployments-cicd.git et que s’y trouve un répertoire « apps/homepage » avec les manifestes de notre application:

Si l’application contient un config map nommé « homepage-config », assurez-vous d’ajouter l’annotation suivante dans le manifeste du déploiement:


metadata:
  annotations:
    configmap.reloader.stakater.com/reload: "homepage-config"

Cela indique au contrôleur Kubernetes Reloader installé précédemment qu’on souhaite redéployer le pod qui dépend du config map indiqué en annotation, si ce config map change.

Comme indiqué précédemment, il faut s’assurer que le contexte courant de kubectl est sur le namespace « argocd » avant d’utiliser les commandes argocd:


kubectl config set-context --current --namespace=argocd

Puis on peut déployer notre première application via ces commandes Powershell:


$appname="homepage"
argocd app create $appname --repo https://git.local/soho/deployments-cicd.git --path apps/$appname --dest-server https://kubernetes.default.svc --dest-namespace default
argocd app set $appname --sync-policy automated

Si tout se passe bien, l’application est créée et visible quasiment instantanément dans l’UI Argo CD. La dernière commande active la synchronisation automatique.

Vous pouvez tester dès maintenant la synchronisation automatique en publiant dans le dépôt git une modification d’un manifeste (par exemple un config map). Par défaut, Argo CD sonde le dépôt git toutes les 3 minutes pour déployer tout changement détecté dans les manifestes. Si vous avez configuré le webhook de Argo CD dans le dépôt git, les synchronisations seront faites en quasi temps réel.

Comment supprimer une applicaton via Argo CD ?

Dans mon cas, l’application homepage était déjà déployée dans Kubernetes quand j’ai créé l’application dans Argo CD. Argo CD n’a donc rien eu à faire. J’ai donc testé la suppression de l’application afin de la recréer.

Attention: ne faite pas cela à la légère: dans le cas de homepage, il n’y aucun volume persistant, toutes les données sont statiques, dans le config map. Donc aucun danger de perte de données. Si vous supprimez une application via Argo CD, il va par défaut supprimer toutes ses ressources déployées dans Kubernetes, y compris les PVC (Persistent Volume Claim), donc potentiellement vos données. Un bon conseil avant de supprimer une application d’Argo CD: modifiez la Reclaim Policy de vos volumes (par défaut « Delete », à passer en « Retain », ce qui nécessitera de supprimer manuellement ces volumes, même si vous supprimez par erreur la ressource PVC correspondante).

Dans l’UI Argo CD, on peut facilement supprimer l’application homepage que l’on vient de déployer. On constera que les ressources disparaissent rapidement du cluster Kubernetes.

Il suffira de répéter la commande précédente argocd app create pour la recréer dans Argo CD et constater que les ressources seront automatiquement déployées dans Kubernetes.

On pourra aussi tester le mode de suppression de l’application « Non cascading », qui veut dire que seule l’application d’Argo CD sera supprimée mais qu’Argo CD ne touchera pas aux ressources déployées dans Kubernetes. On constatera après ce type de suppression que notre pod homepage est toujours présent (avec la commande kubectl get pods -n default). Il suffira de répéter la commande précédente argocd app create pour que notre application homepage réapparaisse dans Argo CD.

Enfin, il est possible de marquer une ressource comme non supprimable par Argo CD lorsqu’on supprime l’application depuis Argo CD. J’applique cette annotation sur tous mes manifestes de type Persistent Volume Claim pour limiter le risque de suppression des données par Argo CD (cela n’empêche pas de supprimer la ressource spécifiquement, la protection n’opère que sur la suppression en cascade à partir de l’application dans Argo CD):


metadata:
  annotations:
    argocd.argoproj.io/sync-options: Delete=false

Comment installer son serveur 1Password Connect dans Kubernetes ?

La première étape pour intégrer argocd-vault-plugin à 1Password est d’installer dans Kubernetes un serveur 1Password Connect dont le rôle sera de servir de pont entre argocd-vault-plugin et 1Password (avec un cache local synchronisé par le serveur 1Password Connect). C’est ce que 1Password appelle un « Secrets Automation workflow ».

La documentation de 1Password Connect pour Kubernetes est ici. Contrairement à ce qu’indique la documentation, un abonnement individuel 1Password est suffisant pour que ça fonctionne.

La première étape est de créer un nouveau vault dans 1Password qui sera dédié à 1Password Connect. En effet, celui-ci n’a pas accès aux vaults par défaut tels que le vault « Personal ». C’est aussi une bonne idée d’avoir un vault dédié ne contenant que les secrets à « exposer » à Argo CD. Ce sera également pratique dans certains cas particulier où vous souhaitez que le secret soit en BASE64: argocd-vault-plugin ne transcode pas (sauf dans certains cas bien particuliers), il faut donc stocker le secret dans le vault 1Password sous la forme souhaitée à sa destination dans le manifeste du secret. Dans d’autres cas, vous souhaiterez peut-être dupliquer un item dans plusieurs vaults 1Password si vous ne souhaitez exposer à 1Password Connect que le strict nécessaire pour Argo CD, sans données annexes inutiles.

La seconde étape est de créer un access token qui donne accès en lecture seule à ce vault. Cela se fait en vous rendant à cette adresse: https://start.1password.com/developer-tools/C.

1Password Connect Developer Tools

Une fois l’accès créé, vous disposez d’un access token et d’un fichier 1password-credentials.json. Le fichier sera utilisé par notre serveur 1Password Connect. L’access token sera utilisé par argocd-vault-plugin pour communiquer avec 1Password Connect. Pensez à stocker ces deux informations dans un endroit sécurisé (typiquement dans votre vault Personal de 1Password). 1Password permet de créer gratuitement jusqu’à 3 access tokens chacun ayant accès à 1 vault (ou 1 access token ayant accès à 3 vaults maximum). Au-delà de cette limite, ce service spécifique pour 1Password Connect est payant.

La dernière étape est d’installer 1Password Connect via son Helm Chart:


helm repo add 1password https://1password.github.io/connect-helm-charts/
helm install connect 1password/connect --set-file connect.credentials=1password-credentials.json -n 1password --create-namespace 

Si l’installation réussie, vous pouvez constater que 3 ressources sont déployées: le déploiement onepassword-connect, son service et son pod (qui contient deux conteneurs en sidecar: connect-api et connect-sync). On pourra noter que par défaut le service est de type NodePort, c’est-à-dire accessible depuis l’extérieur du cluster Kubernetes, ce qui est inutile dans notre cas pour Argo CD qui tourne au sein du même cluster.


kubectl get all -n 1password

NAME                                       READY   STATUS    RESTARTS   AGE
pod/onepassword-connect-58cd469687-hzxvs   2/2     Running   0          49s
NAME                          TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
service/onepassword-connect   NodePort   10.43.179.111           8081:31126/TCP,8080:31672/TCP   49s
NAME                                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/onepassword-connect   1/1     1            1           49s
NAME                                             DESIRED   CURRENT   READY   AGE
replicaset.apps/onepassword-connect-58cd469687   1         1         1       49s

Vous pouvez regarder les logs du pod onepassword-connect, mais il ne s’y passe pas grand chose tant qu’on ne sollicite aucun secret. C’est seulement à ce moment que la première synchronisation avec 1Password sera déclenchée.

Après avoir déployé 1Password Connect, je vous recommande de supprimer le fichier local « 1password-credentials.json ».

Comment installer argocd-vault-plugin (en sidecar) ?

La documentation est ici.

Argo CD recommande d’installer les plugins en « sidecar », il faut donc suivre la section « InitContainer and configuration via sidecar »:

Un premier Config Map:


apiVersion: v1
kind: ConfigMap
metadata:
  name: cmp-plugin
  namespace: argocd
data:
  avp.yaml: |
    apiVersion: argoproj.io/v1alpha1
    kind: ConfigManagementPlugin
    metadata:
      name: argocd-vault-plugin
    spec:
      allowConcurrency: true
      discover:
        find:
          command:
            - sh
            - "-c"
            - "find . -name '*.yaml' | xargs -I {} grep \"<path\\|avp\\.kubernetes\\.io\" {} | grep ."
      generate:
        command:
          - argocd-vault-plugin
          - generate
          - "."
      lockRepo: false
---

Que l’on peut appliquer via la commande:


kubectl apply -f cmp-plugin.yaml

Puis un secret Kubernetes contenant la configuration de argocd-vault-plugin spécifiquement pour 1Password Connect:


kind: Secret
apiVersion: v1
metadata:
  name: vault-configuration
  namespace: argocd
stringData:
  AVP_TYPE: "1passwordconnect"
  OP_CONNECT_HOST: "http://onepassword-connect.1password:8080"
  OP_CONNECT_TOKEN: "ACCESS TOKEN"

Il est important que OP_CONNECT_HOST contienne le protocole (http) et le port (8080) pour l’accès au service 1Password Connect, et pas seulement le nom d’hôte. La documentation de argocd-vault-plugin n’est pas claire à ce sujet (au moment où cet article est rédigé). Evidemment, il faut remplacer « ACCESS TOKEN » par votre access token.

On peut l’appliquer via la commande:


kubectl apply -f vault-configuration.yaml

Après avoir déployé le secret, je vous recommande de retirer l’access token du fichier « vault-configuration.yaml » (ou bien ne pas conserver ce fichier).

Enfin, troisième et dernier manifeste à créer pour patcher le déploiement argocd-repo-server:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: argocd-repo-server
spec:
  template:
    spec:
      automountServiceAccountToken: true
      volumes:
        - name: cmp-plugin
          configMap:
            name: cmp-plugin
        - name: custom-tools
          emptyDir: {}
        - name: cmp-tmp
          emptyDir: {}
      initContainers:
      - name: download-tools
        image: registry.access.redhat.com/ubi8:8.10-1088
        imagePullPolicy: IfNotPresent
        env:
          - name: AVP_VERSION
            value: "1.18.1"
        command: [sh, -c]
        args:
          - >-
            curl -L https://github.com/argoproj-labs/argocd-vault-plugin/releases/download/v$(AVP_VERSION)/argocd-vault-plugin_$(AVP_VERSION)_linux_amd64 -o argocd-vault-plugin &&
            chmod +x argocd-vault-plugin &&
            mv argocd-vault-plugin /custom-tools/            
        volumeMounts:
          - mountPath: /custom-tools
            name: custom-tools
      containers:
      - name: avp
        command: [/var/run/argocd/argocd-cmp-server]
        image: registry.access.redhat.com/ubi8:8.10-1088
        imagePullPolicy: IfNotPresent
        securityContext:
          runAsNonRoot: true
          runAsUser: 999
        volumeMounts:
          - mountPath: /var/run/argocd
            name: var-files
          - mountPath: /home/argocd/cmp-server/plugins
            name: plugins
          # Starting with v2.4, do NOT mount the same tmp volume as the repo-server container. The filesystem separation helps mitigate path traversal attacks.
          - mountPath: /tmp
            name: cmp-tmp

          # Register plugins into sidecar
          - mountPath: /home/argocd/cmp-server/config/plugin.yaml
            subPath: avp.yaml
            name: cmp-plugin

          # Important: Mount tools into $PATH
          - name: custom-tools
            subPath: argocd-vault-plugin
            mountPath: /usr/local/bin/argocd-vault-plugin
        envFrom:
          - secretRef:
              name: vault-configuration

Vous pourriez avoir besoin d’adapter la version du plugin (1.18.1 est la dernière version lorsque cet article est rédigé) et celle de l’image registry.access.redhat.com/ubi8.

La documentation du plugin est peu claire car ils se mélangent un peu entre les différents modes de déploiements. Le manifeste présenté ici s’appuie sur leur exemple, mais je l’ai modifié en suivant d’autres instructions notamment:

– La dernière partie avec la référence au secret « vault-configuration » qu’on a déployé juste avant.
– Le volume « cmp-tmp » initialisé à partir d’un emptyDir, tel que documenté ici.

Cette configuration inclut le minimum vital pour le plugin. La documentation du plugin montre d’autres exemples pour s’intégrer à Helm ou à Kustomize.

On peut patcher argocd-repo-server avec cette nouvelle configuration:


kubectl patch deployment argocd-repo-server --patch-file argocd-repo-server-patch.yaml -n argocd

Si la commande réussie, argocd-vault-plugin est prêt à l’emploi comme on le voit à la section suivante.

Comment faire un deuxième déploiement avec un secret ?

Je prendrai comme exemple l’application bagetter, qui permet de déployer son propre serveur Nuget. C’est un bon exemple car son déploiement est très simple et contient une Api Key qui permet d’uploader des packages Nuget et que l’on peut considérer comme secret.

Notre dépôt git https://git.local/soho/deployments-cicd.git contient donc un répertoire « apps/bagetter ». Ce répertoire contient 4 manifestes: le deployment, le persistent volume claim, un service, et un secret:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: bagetter-deployment
  labels:
    app: bagetter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bagetter
  template:
    metadata:
      labels:
        app: bagetter
    spec:
      volumes:
      - name: data-volume
        persistentVolumeClaim:
          claimName: bagetter-pvc
      containers:
      - name: bagetter
        image: docker.io/bagetter/bagetter:1.4
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: data-volume
          mountPath: /data
        env:
        - name: ApiKey
          valueFrom:
            secretKeyRef:
              name: bagetter-secrets
              key: apiKey
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: bagetter-pvc
  namespace: default
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
  name: bagetter-service
  labels:
    app: bagetter
spec:
  selector:
    app: bagetter
  type: NodePort
  ports:
  - name: http
    port: 8080
    protocol: TCP
    targetPort: 8080
    nodePort: 30005
---
kind: Secret
apiVersion: v1
metadata:
  name: bagetter-secrets
  annotations:
    avp.kubernetes.io/path: "vaults/<VAULT UUID>/items/<ITEM UUID>"
type: Opaque
stringData:
  apiKey: <password>

Le secret, en dernier, est ce qui nous intéresse ici: l’annotation avp.kubernetes.io/path indique le chemin de l’item dans 1Password. Il vous faut remplacer <VAULT UUID> et <ITEM UUID> par les identifiants adéquats.

La valeur <password> est le placeholder pour argocd-vault-plugin. Il faut laisser les chevrons qui représentent le placeholder, mais vous pourriez avoir besoin d’adapter le nom du placeholder (password) selon le champ de l’item 1Password à injecter (ce sera « password » la plupart du temps).

Comment connaître <VAULT UUID> et <ITEM UUID> ? Dans l’application desktop 1Password, un clic droit sur l’item pour obtenir son URL de la forme suivante:


https://start.1password.com/open/i?a=XXXXXXXXXXXXXXXXXXXXXXXX&v=XXXXXXXXXXXXXXXXXXXXXXXX&i=XXXXXXXXXXXXXXXXXXXXXXXX&h=...

– Le Vault UUID correspond à la valeur de « v » dans la query string.
– L’Item UUID correspond à la valeur de « i » dans la query string.

Donc considérant que le dépôt git est à jour de ces informations, l’ajout de cette application dans Argo CD est strictement identique au cas d’un déploiement sans secret:


$appname="bagetter"
argocd app create $appname --repo https://git.local/soho/deployments-cicd.git --path apps/$appname --dest-server https://kubernetes.default.svc --dest-namespace default
argocd app set $appname --sync-policy automated

Si tout se passe bien, c’est que le secret a pu être interpolé avec succès. Sinon, une erreur sera remontée par argocd-vault-plugin dans la sortie de cette commande (et l’application ne sera pas créée dans Argo CD).

Bon à savoir

Paradoxe de l’œuf et de la poule

Dans mon cas, et je pense que c’est partagé ailleurs, il peut se poser le paradoxe de l’œuf et de la poule: mon cluster Kubernetes héberge à la fois Argo CD, Forgejo (serveur Git utilisé par Argo CD). Il est donc évident qu’on ne peut pas gérer le déploiement initial de Forgejo via Argo CD (je suppose qu’on peut le mettre à jour via Argo CD en espérant que ça se passe bien, car sinon Argo CD ne fonctionnera plus tant que Frogejo ne sera pas rétabli). Et ce problème se retrouve avec d’autres services. Dans mon cas, j’utilise un serveur nginx comme reverse proxy. Ce n’est sans doute pas la meilleure solution mais je trouve sa configuration tellement plus simple… Donc si mon reverse proxy nginx dans Kubernetes n’est plus disponible, mon serveur Forgejo n’est plus accessible, ce qui encore une fois rend Argo CD inopérant.

Je suis sûr qu’en y passant beaucoup de temps, on peut optimiser cela. Par exemple, on doit pouvoir gérer l’installation de Reloader via Argo CD pour minimiser les étapes manuelles. C’est une optimisation inutile dans mon cas.

Comment les secrets sont gérés dans Argo CD ?

Argo CD fait le choix de se concentrer sur la gestion des manifestes (à savoir comparer les manifestes des ressources déployées avec ceux d’un dépôt git) et de déléguer la gestion des secrets aux plugins. Le rôle du plugin est d’interpoler des chaînes de caractères des manifestes stockés dans git, avec la valeur d’un secret stocké dans un vault. Le résultat (manifeste final contenant des secrets) est malgré tout stocké par Argo CD, probablement dans un système de fichier local au pod, mais aussi et surtout dans son instance Redis.

L’instance Redis est protégée par une authentication par mot de passe généré à l’installation d’Argo CD. Il y a également une network policy qui empêche tout accès à Redis depuis d’autres pods que ceux d’Argo CD (en théorie, mais ce n’est pas infaillible car contournable et la protection par mot de passe me semble être plus solide). La documentation d’Argo CD recommande même de séparer Argo CD et les applications dans deux cluster Kubernetes distinct (donc avoir un cluster dédié à Argo CD pour isoler les secrets centralisés dans le cache d’Argo CD de tous les autres pods qui pourraient exploiter une faille pour y accéder plus facilement au sein du même cluster).

Diagnostic des erreurs liées à argocd-vault-plugin

En cas d’erreur au moment de créer l’application dans Argo CD, il faudra essayer de comprendre d’où peut venir le problème, adapter la configuration, puis re-essayer. Mais avant de re-essayer, il faut supprimer les deux pods « argocd-repo-server » et « argocd-redis ». Le premier est celui qui s’initialise avec le config map d’argocd-vault-plugin. Le second (redis) contient le cache des manifestes, qu’il y ait eu une erreur ou pas! Voici à quoi peuvent ressembler les messages d’erreur du plugin:


time="2024-08-17T18:09:07+02:00" 
level=fatal 
msg="rpc error: code = InvalidArgument desc = application spec for bagetter is invalid: InvalidSpecError: 
Unable to generate manifests in apps/bagetter: rpc error: code = Unknown 
desc = plugin sidecar failed. error generating manifests in cmp: 
rpc error: code = Unknown desc = error generating manifests: `argocd-vault-plugin generate .` 
failed exit status 1: Error: Get \"onepassword-connect.1password/v1/vaults/xxx/items?filter=title+eq+%22xxx%22\": unsupported protocol scheme"

Ce deuxième message d’erreur nous indique que l’erreur provient du cache (et que donc on a oublié de supprimer le pod argocd-redis avant de re-essayer après correction de la configuration):


time="2024-08-17T18:14:33+02:00" 
level=fatal 
msg="rpc error: code = InvalidArgument desc = application spec for bagetter is invalid: InvalidSpecError: 
Unable to generate manifests in apps/bagetter: rpc error: code = Unknown 
desc = Manifest generation error (cached): plugin sidecar failed. error generating manifests in cmp: 
rpc error: code = Unknown desc = error generating manifests: `argocd-vault-plugin generate .` 
failed exit status 1: Error: Get \"onepassword-connect.1password/v1/vaults/xxx/items?filter=title+eq+%22xxx%22\": unsupported protocol scheme 

L’erreur « unsupported protocol scheme » était, dans mon cas, lié au fait que j’avais défini le paramètre OP_CONNECT_HOST avec uniquement le nom d’hôte « onepassword-connect.1password » alors qu’il faut inclure le protocole (http) et le port (8080).

S’affranchir de connexion Internet avec AVP

Tel que mis en place, AVP (ArgoCD Vault Plugin) dépend d’une connexion Internet. Cela semble évident et non problématique au premier abord, mais si le container argocd-server doit être recréé (suite à un redémarrage du serveur par exemple) et que la connexion Internet à ce moment est défaillante, ArgoCD ne pourra pas être démarré.

Une astuce que j’ai utilisé sur mon installation: adapter le fichier patch du déploiement « argocd-repo-server » décrit plus haut pour ne plus pointer github.com, mais son propre serveur Forgejo. Il suffit de créer un miroir du dépôt https://github.com/argoproj-labs/argocd-vault-plugin sur votre instance Forgejo, puis d’y créer manuellement la release du plugin que vous souhaitez utiliser.