diff --git a/README.md b/README.md
index 473ec718..6d2fdb5c 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,10 @@ GitOps repository for managing Kubernetes deployments via ArgoCD. All changes to
│ ├── user-exchange-topology/ # Helm chart
│ ├── users-v1/ # UOWS REST API v1 (legacy, master-v1 branch)
│ ├── users-v2/ # UOWS REST API v2 (current, main branch)
-│ └── vault-operator-config/ # Per-cluster Vault connection config
+│ └── vault-operator-config/ # Per-cluster, per-namespace Vault connection config
+│ └── dev/
+│ ├── apps/ # VaultConnection, VaultAuth, ServiceAccount + RBAC for `apps`
+│ └── cron-jobs/ # Same, for `cron-jobs` (ServiceAccount: cron-jobs-vault-op)
│
└── .github/workflows/
└── create-release-pr.yaml # Called by source repos to create deploy PRs
@@ -95,6 +98,25 @@ generators:
#- name: prod-fallback
```
+The `vault-operator-config` ApplicationSet is the exception: it uses a matrix generator (cluster × namespace), because a `VaultConnection` and `VaultAuth` must exist in every namespace that consumes Vault secrets:
+
+```yaml
+generators:
+- matrix:
+ generators:
+ - list:
+ elements:
+ - name: dev-v3
+ path: dev
+
+ - list:
+ elements:
+ - namespace: apps
+ - namespace: cron-jobs
+```
+
+This produces one Application per cluster/namespace pair,
named `{{.name}}-{{.namespace}}-vault-operator-config`,
sourced from `components/ua/vault-operator-config/{{.path}}/{{.namespace}}`
and deployed into `{{.namespace}}`. If you enable the fallback cluster,
the matching `{{.path}}/apps` and `{{.path}}/cron-jobs` directories must exist first, otherwise the Application will fail to sync.
+
See [moving UA apps to the fallback cluster](https://github.com/isisbusapps/ISISBusApps/wiki/Moving-UA-apps-to-the-fallback-cluster) for details.
### Clusters
@@ -118,7 +140,25 @@ spec:
name: user-office-web-service-v2 # K8s Secret name
```
-The Vault operator and its per-cluster config (`vault-operator-config/`) are deployed at sync-wave `-10` and `-1` respectively, before any application workloads.
+Each namespace that uses Vault needs its own `VaultConnection` and `VaultAuth` (`static-auth`), defined in `components/ua/vault-operator-config/{env}/{namespace}/`.
+
A `VaultStaticSecret` must live in the same namespace as the `VaultAuth` it references. Each `VaultAuth` authenticates with a ServiceAccount in its own namespace, created alongside it in `vault-auth-service-account.yaml` together with the RBAC it needs (including a `system:auth-delegator` ClusterRoleBinding):
+
+| Namespace | ServiceAccount | Config directory (dev) |
+|-----------|----------------|------------------------|
+| `apps` | `vault-op` | `vault-operator-config/dev/apps/` |
+| `cron-jobs` | `cron-jobs-vault-op` | `vault-operator-config/dev/cron-jobs/` |
+
+> **Note:** the per-namespace layout (`apps/`, `cron-jobs/`) currently applies to `dev` only. `prod` still uses a single flat `vault-operator-config/prod/` directory deployed into `apps`.
+
+The `VaultAuth` uses the `cluster` Kubernetes auth role on the `submissions` mount. That role is configured in Vault, not in this repo, and its `bound_service_account_names` and `bound_service_account_namespaces` must include each ServiceAccount and namespace above.
+
+The Vault operator and its per-cluster, per-namespace config (`vault-operator-config/`) are deployed at sync-wave `-10` and `-1` respectively, before any application workloads.
+
+### Adding Vault access for a new namespace
+
+1. Copy `components/ua/vault-operator-config/dev/cron-jobs/` to `.../dev/{namespace}/` and update the namespace and ServiceAccount name in all three files. Keep the ServiceAccount name identical across the `ServiceAccount`, the `RoleBinding` and `ClusterRoleBinding` subjects, and the `VaultAuth`.
+2. Add `- namespace: {namespace}` to the second list in `argocd/dev/ua/vault-operator-configs/app.yaml`.
+3. Add the new ServiceAccount name and namespace to the bound lists of the Vault auth role.
## How Deployments Happen
@@ -146,6 +186,6 @@ See [docs/uows-release-guide.md](docs/uows-release-guide.md) for the UOWS-specif
| Wave | Resources |
|------|-----------|
| -10 | Vault Operator (Helm) |
-| -1 | Vault Operator Config, UA Shared Config |
+| -1 | Vault Operator Config (per namespace. e.g. `apps`, `cron-jobs`), UA Shared Config |
| 0 | All application workloads |
-| 1 | Health Check Service (dev only) |
+| 1 | Health Check Service (dev only) |
\ No newline at end of file
diff --git a/argocd/dev/ua/vault-operator-configs/app.yaml b/argocd/dev/ua/vault-operator-configs/app.yaml
index 8eb54969..cacf1df6 100644
--- a/argocd/dev/ua/vault-operator-configs/app.yaml
+++ b/argocd/dev/ua/vault-operator-configs/app.yaml
@@ -7,27 +7,33 @@ spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- - list:
- elements:
- # Names of clusters to deploy the app to
- - name: dev-v3
- path: dev
- # Uncomment if you want to deploy to dev-microk8s-alternative
- - name: dev-microk8s-alternative
- path: dev-fallback
+ - matrix:
+ generators:
+ - list:
+ elements:
+ # Names of clusters to deploy the app to
+ - name: dev-v3
+ path: dev
+ # Uncomment if you want to deploy to dev-microk8s-alternative
+ # - name: dev-microk8s-alternative
+ # path: dev-fallback
+ - list:
+ elements:
+ - namespace: apps
+ - namespace: cron-jobs
template:
metadata:
- name: '{{.name}}-vault-operator-config'
+ name: '{{.name}}-{{.namespace}}-vault-operator-config'
annotations:
argocd.argoproj.io/sync-wave: "-1"
spec:
project: u-a-project
source:
- path: components/ua/vault-operator-config/{{.path}}
+ path: components/ua/vault-operator-config/{{.path}}/{{.namespace}}
repoURL: 'https://github.com/isisbusapps/gitops'
targetRevision: main
destination:
- namespace: apps
+ namespace: '{{.namespace}}'
name: '{{.name}}'
syncPolicy:
automated:
diff --git a/components/ua/vault-operator-config/dev/vault-auth-service-account.yaml b/components/ua/vault-operator-config/dev/apps/vault-auth-service-account.yaml
similarity index 100%
rename from components/ua/vault-operator-config/dev/vault-auth-service-account.yaml
rename to components/ua/vault-operator-config/dev/apps/vault-auth-service-account.yaml
diff --git a/components/ua/vault-operator-config/dev/vault-auth.yaml b/components/ua/vault-operator-config/dev/apps/vault-auth.yaml
similarity index 85%
rename from components/ua/vault-operator-config/dev/vault-auth.yaml
rename to components/ua/vault-operator-config/dev/apps/vault-auth.yaml
index 21743fa8..9779694d 100644
--- a/components/ua/vault-operator-config/dev/vault-auth.yaml
+++ b/components/ua/vault-operator-config/dev/apps/vault-auth.yaml
@@ -15,4 +15,4 @@ spec:
serviceAccount: vault-op
audiences:
- vault
- - https://kubernetes.default.svc.cluster.local
\ No newline at end of file
+ - https://kubernetes.default.svc.cluster.local
diff --git a/components/ua/vault-operator-config/dev/apps/vault-connection.yaml b/components/ua/vault-operator-config/dev/apps/vault-connection.yaml
new file mode 100644
index 00000000..2054bdfb
--- /dev/null
+++ b/components/ua/vault-operator-config/dev/apps/vault-connection.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: secrets.hashicorp.com/v1beta1
+kind: VaultConnection
+metadata:
+ namespace: apps
+ name: secrets-isis-connection
+spec:
+ address: "https://secrets.isis.rl.ac.uk"
diff --git a/components/ua/vault-operator-config/dev/cron-jobs/vault-auth-service-account.yaml b/components/ua/vault-operator-config/dev/cron-jobs/vault-auth-service-account.yaml
new file mode 100644
index 00000000..99e243f3
--- /dev/null
+++ b/components/ua/vault-operator-config/dev/cron-jobs/vault-auth-service-account.yaml
@@ -0,0 +1,49 @@
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ namespace: cron-jobs
+ name: cron-jobs-vault-op
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: service-account-read
+ namespace: cron-jobs
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: service-account-read
+subjects:
+ - kind: ServiceAccount
+ name: cron-jobs-vault-op
+ namespace: cron-jobs
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: service-account-read-cron-jobs
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: system:auth-delegator
+subjects:
+ - kind: ServiceAccount
+ name: cron-jobs-vault-op
+ namespace: cron-jobs
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: service-account-read
+ namespace: cron-jobs
+rules:
+ - apiGroups: [""]
+ resources: ["serviceaccounts"]
+ verbs: ["get", "watch", "list"]
+ - apiGroups: ["authentication.k8s.io"]
+ resources: ["tokenreviews"]
+ verbs: ["create"]
+ - apiGroups: [ "authentication.k8s.io" ]
+ resources: [ "subjectaccessreviews" ]
+ verbs: [ "create" ]
\ No newline at end of file
diff --git a/components/ua/vault-operator-config/dev/cron-jobs/vault-auth.yaml b/components/ua/vault-operator-config/dev/cron-jobs/vault-auth.yaml
new file mode 100644
index 00000000..f5353442
--- /dev/null
+++ b/components/ua/vault-operator-config/dev/cron-jobs/vault-auth.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: secrets.hashicorp.com/v1beta1
+kind: VaultAuth
+metadata:
+ name: static-auth
+ namespace: cron-jobs
+spec:
+ vaultConnectionRef: secrets-isis-connection
+ method: kubernetes
+ mount: submissions
+ kubernetes:
+ role: cluster
+ serviceAccount: cron-jobs-vault-op
+ audiences:
+ - vault
+ - https://kubernetes.default.svc.cluster.local
\ No newline at end of file
diff --git a/components/ua/vault-operator-config/dev/vault-connection.yaml b/components/ua/vault-operator-config/dev/cron-jobs/vault-connection.yaml
similarity index 87%
rename from components/ua/vault-operator-config/dev/vault-connection.yaml
rename to components/ua/vault-operator-config/dev/cron-jobs/vault-connection.yaml
index ad933107..b8fddeff 100644
--- a/components/ua/vault-operator-config/dev/vault-connection.yaml
+++ b/components/ua/vault-operator-config/dev/cron-jobs/vault-connection.yaml
@@ -2,7 +2,7 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
- namespace: apps
+ namespace: cron-jobs
name: secrets-isis-connection
spec:
address: "https://secrets.isis.rl.ac.uk"
\ No newline at end of file