Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions apps/openbao-init/templates/clustersecretstore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{{- if .Values.clusterSecretStore.enabled }}
# Point d'entrée unique d'ESO vers OpenBao, pour tout le cluster.
#
# Vit ici (et non dans apps/secrets-sync/) parce que c'est du câblage OpenBao
# partagé : n'importe quelle app peut le référencer. Le mettre dans le chart
# secrets-sync créerait une dépendance artificielle de tout le cluster envers
# le cas d'usage GitHub.
#
# Le provider s'appelle `vault` : OpenBao est un fork de Vault et parle la même
# API, ESO n'a pas de provider dédié.
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: {{ .Values.clusterSecretStore.name }}
annotations:
# Wave 2 : après le serveur OpenBao (wave 0) et le job de restore (wave 1).
argocd.argoproj.io/sync-wave: "2"
spec:
provider:
vault:
server: {{ .Values.baoAddr | quote }}
path: {{ .Values.clusterSecretStore.mountPath | quote }}
version: v2
auth:
kubernetes:
mountPath: kubernetes
# Rôle créé manuellement dans OpenBao — voir openbao-claude.md.
# Lié au SA external-secrets/external-secrets via la policy eso-read.
role: {{ .Values.clusterSecretStore.role | quote }}
serviceAccountRef:
name: external-secrets
namespace: external-secrets
{{- end }}
10 changes: 10 additions & 0 deletions apps/openbao-init/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,13 @@ s3:
bucket: ""
uri: ""
extraFlag: ""

# ClusterSecretStore : le pont ESO → OpenBao, partagé par tout le cluster.
# Suppose que ces objets existent côté OpenBao (créés à la main, cf.
# openbao-claude.md) : moteur kv-v2 monté sur `kv/`, auth kubernetes activée,
# policy `eso-read` et rôle `external-secrets`.
clusterSecretStore:
enabled: true
name: openbao
mountPath: kv
role: external-secrets
5 changes: 5 additions & 0 deletions apps/secrets-sync/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v2
name: secrets-sync
description: Push secrets from OpenBao to GitHub Actions secrets via ESO
type: application
version: 0.1.0
25 changes: 25 additions & 0 deletions apps/secrets-sync/templates/externalsecret-github-app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Fait entrer la clé privée de la GitHub App dans le cluster.
#
# C'est le maillon qui évite d'avoir la clé dans git : les SecretStores GitHub
# ont besoin d'un Secret k8s, ESO le fabrique depuis OpenBao.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: github-app-private-key
namespace: {{ .Release.Namespace }}
annotations:
# Wave 0 : la clé doit exister avant les SecretStores qui la référencent.
argocd.argoproj.io/sync-wave: "0"
spec:
refreshInterval: {{ .Values.source.refreshInterval | quote }}
secretStoreRef:
name: {{ .Values.clusterSecretStoreName }}
kind: ClusterSecretStore
target:
name: {{ .Values.github.privateKey.secretName }}
creationPolicy: Owner
data:
- secretKey: {{ .Values.github.privateKey.openbaoKey }}
remoteRef:
key: {{ .Values.github.privateKey.openbaoPath }}
property: {{ .Values.github.privateKey.openbaoKey }}
24 changes: 24 additions & 0 deletions apps/secrets-sync/templates/externalsecrets-targets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Un ExternalSecret par cible : matérialise le secret OpenBao dédié à cette
# cible dans son propre Secret k8s. Isolation volontaire — voir le commentaire
# sur `source` dans values.yaml.
{{- range .Values.targets }}
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: secrets-sync-{{ .name }}
namespace: {{ $.Release.Namespace }}
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
refreshInterval: {{ $.Values.source.refreshInterval | quote }}
secretStoreRef:
name: {{ $.Values.clusterSecretStoreName }}
kind: ClusterSecretStore
target:
name: secrets-sync-{{ .name }}
creationPolicy: Owner
dataFrom:
- extract:
key: {{ $.Values.source.openbaoPathPrefix }}/{{ .name }}
{{- end }}
34 changes: 34 additions & 0 deletions apps/secrets-sync/templates/pushsecrets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Un PushSecret par cible : pousse les clés choisies du Secret k8s intermédiaire
# vers GitHub, à travers le SecretStore correspondant.
{{- range .Values.targets }}
---
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
name: github-{{ .name }}
namespace: {{ $.Release.Namespace }}
annotations:
# Wave 2 : après le SecretStore et le Secret source.
argocd.argoproj.io/sync-wave: "2"
spec:
refreshInterval: {{ $.Values.pushRefreshInterval | quote }}
secretStoreRefs:
- name: github-{{ .name }}
kind: SecretStore
selector:
secret:
name: secrets-sync-{{ .name }}
# `None` : supprimer le PushSecret laisse le secret en place côté GitHub.
# Volontaire — un `helm uninstall` ne doit pas casser des workflows en prod.
deletionPolicy: None
# `Replace` : OpenBao fait autorité, une modification manuelle côté GitHub est
# écrasée au refresh suivant.
updatePolicy: Replace
data:
{{- range .secrets }}
- match:
secretKey: {{ . }}
remoteRef:
remoteKey: {{ . }}
{{- end }}
{{- end }}
41 changes: 41 additions & 0 deletions apps/secrets-sync/templates/secretstores.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Un SecretStore GitHub par cible.
#
# Pourquoi un par cible plutôt qu'un seul : dans le provider GitHub d'ESO, la
# portée du push (organisation / repo / environnement) est portée par le
# SecretStore via `repository` et `environment`. Le PushSecret ne choisit que
# les clés, pas la destination. Deux portées = deux stores, même si le repo
# visé est le même.
{{- range .Values.targets }}
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: github-{{ .name }}
namespace: {{ $.Release.Namespace }}
annotations:
# Wave 1 : après l'ExternalSecret qui fabrique le Secret de la clé privée.
argocd.argoproj.io/sync-wave: "1"
spec:
provider:
github:
# Entiers, pas des chaînes : la CRD les type en int64.
# Le `| int64` n'est pas cosmétique : sans lui Helm rend ces valeurs en
# notation scientifique (4.346625e+06) et la CRD rejette le manifeste.
appID: {{ $.Values.github.appID | int64 }}
installationID: {{ $.Values.github.installationID | int64 }}
organization: {{ $.Values.github.organization }}
{{- if .repository }}
repository: {{ .repository }}
{{- else }}
# Pas de `repository` → secret d'organisation.
orgSecretVisibility: {{ $.Values.orgSecretVisibility }}
{{- end }}
{{- if .environment }}
# L'environnement doit exister côté GitHub : ESO ne le crée pas.
environment: {{ .environment }}
{{- end }}
auth:
privateKey:
name: {{ $.Values.github.privateKey.secretName }}
key: {{ $.Values.github.privateKey.openbaoKey }}
{{- end }}
9 changes: 9 additions & 0 deletions apps/secrets-sync/values-local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Cluster local (minikube).
#
# C'est ici que la V0 est validée : le cluster local pousse réellement vers la
# vraie organisation GitHub. Les cibles de la baseline (values.yaml) suffisent,
# rien à surcharger pour l'instant.
#
# Attention si tu actives un jour secrets-sync sur scaleway en parallèle : les
# deux clusters pousseraient les mêmes clés vers les mêmes secrets GitHub, en
# se réécrivant mutuellement toutes les heures. Voir values-scaleway.yaml.
11 changes: 11 additions & 0 deletions apps/secrets-sync/values-scaleway.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Cluster scaleway (staging).
#
# Volontairement désactivé en V0 : GitHub est une ressource *globale*, pas une
# ressource par cluster. Si local et scaleway synchronisaient les mêmes cibles,
# ils écraseraient tour à tour les mêmes secrets d'organisation — sans dégât
# (les valeurs sont identiques) mais avec deux sources d'écriture concurrentes
# et un historique d'audit illisible côté GitHub.
#
# Pour activer : décider *quel* cluster fait autorité sur la synchro GitHub,
# et ne laisser des `targets` non vides que sur celui-là.
targets: []
81 changes: 81 additions & 0 deletions apps/secrets-sync/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Synchronisation OpenBao → GitHub Actions secrets.
#
# Sens du flux : OpenBao est la source de vérité. ESO lit les valeurs, les
# matérialise dans un Secret k8s intermédiaire, puis les pousse vers GitHub.
# Le provider GitHub d'ESO est write-only : rien ne revient de GitHub.
#
# Aucune valeur de secret ici — uniquement des identifiants publics et des
# chemins. Les valeurs vivent dans OpenBao.

# ClusterSecretStore déclaré par le chart openbao-init.
clusterSecretStoreName: openbao

github:
# appID / installationID sont typés int64 dans la CRD : surtout pas de
# guillemets, sinon le webhook ESO rejette le SecretStore.
appID: 4346625
installationID: 147805215
organization: IntegratedDynamic

# Clé privée de la GitHub App. Lue dans OpenBao, jamais dans git.
privateKey:
# Chemin relatif au mount kv/ (le provider vault préfixe tout seul).
openbaoPath: apps/secrets-sync/github/eso-github-app-private-key
openbaoKey: privateKey.pem
# Secret k8s intermédiaire que les SecretStores référencent.
secretName: github-app-private-key

# Préfixe des secrets OpenBao source — un par cible, sous <prefix>/<target.name>.
# Volontairement PAS un secret unique partagé : deux cibles sans rapport (un
# secret d'org, un secret d'un repo précis) ne doivent pas partager un
# historique de versions Vault ni une policy d'accès. Chaque cible a son propre
# secret, donc sa propre policy possible et son propre historique de rotation.
#
# Le segment `github` isole ce qui est spécifique à ce provider : tout ce qui
# est propre à la synchro (peu importe la destination) vit sous
# apps/secrets-sync/, ce qui est propre à GitHub vit sous
# apps/secrets-sync/github/ — de la place pour un autre provider plus tard
# (ex. apps/secrets-sync/slack/) sans collision de noms.
source:
openbaoPathPrefix: apps/secrets-sync/github
refreshInterval: 1h

# Cibles de publication. Une entrée = un secret OpenBao (à
# <openbaoPathPrefix>/<name>) + un ExternalSecret + un SecretStore + un
# PushSecret, tous dédiés à cette cible.
#
# Le scope (org / repo / environnement) se règle sur le SecretStore, pas sur le
# PushSecret : c'est pour ça qu'il faut une cible distincte par portée, même
# quand deux cibles visent le même repo.
#
# Champs :
# name identifiant k8s de la cible (kebab-case) ET nom du secret
# OpenBao sous le préfixe ci-dessus
# repository omis → secret d'organisation
# environment optionnel, exige `repository` et que l'env existe déjà côté GitHub
# secrets clés à pousser, telles qu'elles apparaissent dans le secret
# OpenBao de la cible ET dans GitHub (mapping 1:1, volontairement
# simple)
targets:
# - name: global
# secrets:
# - TEST_ORGA

# - name: infrastructure
# repository: infrastructure
# secrets:
# - TEST_REPO

- name: infrastructure-scaleway
repository: infrastructure
environment: scaleway
secrets:
- SCW_ACCESS_KEY
- SCW_SECRET_KEY

# Visibilité des secrets d'organisation créés : "all" ou "private".
orgSecretVisibility: private

# Fréquence de repush. ESO ne peut pas relire GitHub (write-only) : il repousse
# à l'aveugle à chaque intervalle, ce qui corrige une dérive manuelle côté GitHub.
pushRefreshInterval: 1h
26 changes: 26 additions & 0 deletions clusters/local/templates/secrets-sync.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: secrets-sync
namespace: argocd
spec:
project: default

source:
repoURL: {{ .Values.repoURL }}
targetRevision: {{ .Values.revision }}
path: apps/secrets-sync
helm:
valueFiles:
- values-local.yaml

destination:
server: https://kubernetes.default.svc
namespace: secrets-sync

syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
29 changes: 29 additions & 0 deletions clusters/scaleway/templates/secrets-sync.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: secrets-sync
namespace: argocd
spec:
project: default

source:
repoURL: {{ .Values.repoURL }}
targetRevision: {{ .Values.revision }}
path: apps/secrets-sync
helm:
# values-scaleway.yaml met `targets: []` : l'Application existe et se
# synchronise, mais ne pousse rien vers GitHub. Voir le fichier pour le
# pourquoi (GitHub est global, un seul cluster doit faire autorité).
valueFiles:
- values-scaleway.yaml

destination:
server: https://kubernetes.default.svc
namespace: secrets-sync

syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Loading