diff --git a/infra/configs/openbao-k8s-se-role.yaml b/infra/configs/openbao-k8s-se-role.yaml new file mode 100644 index 0000000..3f58f47 --- /dev/null +++ b/infra/configs/openbao-k8s-se-role.yaml @@ -0,0 +1,32 @@ +# Roles with needed access for OpenBao's Kubernetes secret engine +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: k8s-full-secrets-abilities +rules: +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["get"] +- apiGroups: [""] + resources: ["serviceaccounts", "serviceaccounts/token"] + verbs: ["create", "update", "delete"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["rolebindings", "clusterrolebindings"] + verbs: ["create", "update", "delete"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "clusterroles"] + verbs: ["bind", "escalate", "create", "update", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: openbao-token-creator-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: k8s-full-secrets-abilities +subjects: +- kind: ServiceAccount + name: openbao + namespace: openbao diff --git a/infra/kustomization.yaml b/infra/kustomization.yaml index 58ba45b..4f543fd 100644 --- a/infra/kustomization.yaml +++ b/infra/kustomization.yaml @@ -25,3 +25,4 @@ resources: - configs/openbao-volume.yaml - controllers/openbao.yaml + - configs/openbao-k8s-se-role.yaml diff --git a/utils/synchronize-vault.py b/utils/synchronize-vault.py index 6ca39bf..f8562a0 100755 --- a/utils/synchronize-vault.py +++ b/utils/synchronize-vault.py @@ -2,6 +2,7 @@ import argparse import os +import pathlib from typing import Any, cast import hvac @@ -42,7 +43,7 @@ def synchronize_auth_kubernetes_config(client: hvac.Client): def synchronize_kubernetes_roles(client: hvac.Client): kubernetes = Kubernetes(client.adapter) - policy_dir = os.path.join(os.path.dirname(__file__), '../vault/kubernetes-roles/') + policy_dir = os.path.join(os.path.dirname(__file__), '../vault/kubernetes-auth-roles/') roles: dict[str, Any] = {} # pyright:ignore[reportExplicitAny] for filename in os.listdir(policy_dir): @@ -67,6 +68,47 @@ def synchronize_kubernetes_roles(client: hvac.Client): # Using write data instead of kubernetes.create_role, we can pass raw yaml _ = client.write_data(f'/auth/kubernetes/role/{role_name}', data=role_content) # pyright:ignore[reportAny] +def synchronize_approle_auth(client: hvac.Client): + if client.sys.list_auth_methods().get('approle/') is None: + print('Enabling AppRole auth method') + client.sys.enable_auth_method('approle', 'AppRole authorization for CI') + + +def synchronize_kubernetes_secretengine(client: hvac.Client): + # Ensure kubernetes secret engine is enabled + if client.sys.list_mounted_secrets_engines().get('kubernetes/') is None: + print('Enabling kubernetes secret engine') + client.sys.enable_secrets_engine('kubernetes', 'kubernetes', 'Cluster access') + + # Write empty config (all defaults, working on the same cluster) + client.write('kubernetes/config', None) + + policy_dir = pathlib.Path(__file__).parent.joinpath('../vault/kubernetes-se-roles/') + roles: dict[str, Any] = {} + + for filename in policy_dir.iterdir(): + with filename.open('r') as f: + role = yaml.safe_load(f.read()) + assert type(role) is dict + # generated_role_rules must be json or yaml formatted string, convert it + if 'generated_role_rules' in role and type(role['generated_role_rules']) is not str: + role['generated_role_rules'] = yaml.safe_dump(role['generated_role_rules']) + roles[filename.stem] = role + + roles_on_vault: list[str] = [] + roles_response = client.list("kubernetes/roles") + if roles_response is not None: + roles_on_vault = roles_response['data']['keys'] + + for role in roles_on_vault: + if role not in roles: + print(f'Deleting role: {role}') + client.delete(f'kubernetes/roles/{role}') + + for role_name, role_content in roles.items(): + print(f'Updating role: {role_name}') + client.write_data(f'kubernetes/roles/{role_name}', data=role_content) + if __name__ == '__main__': parser = argparse.ArgumentParser( prog="synchronizeVault", @@ -82,5 +124,11 @@ if __name__ == '__main__': print('Synchronizing kubernetes config') synchronize_auth_kubernetes_config(client) - print('Synchronizing kubernetes roles') + print('Synchronizing kubernetes auth roles') synchronize_kubernetes_roles(client) + + print('Synchronizing AppRole auth method') + synchronize_approle_auth(client) + + print('Synchronizing kubernetes secret engine') + synchronize_kubernetes_secretengine(client) diff --git a/vault/kubernetes-roles/authentik.yaml b/vault/kubernetes-auth-roles/authentik.yaml similarity index 100% rename from vault/kubernetes-roles/authentik.yaml rename to vault/kubernetes-auth-roles/authentik.yaml diff --git a/vault/kubernetes-roles/backup.yaml b/vault/kubernetes-auth-roles/backup.yaml similarity index 100% rename from vault/kubernetes-roles/backup.yaml rename to vault/kubernetes-auth-roles/backup.yaml diff --git a/vault/kubernetes-roles/cert-manager.yaml b/vault/kubernetes-auth-roles/cert-manager.yaml similarity index 100% rename from vault/kubernetes-roles/cert-manager.yaml rename to vault/kubernetes-auth-roles/cert-manager.yaml diff --git a/vault/kubernetes-roles/crawl4ai.yaml b/vault/kubernetes-auth-roles/crawl4ai.yaml similarity index 100% rename from vault/kubernetes-roles/crawl4ai.yaml rename to vault/kubernetes-auth-roles/crawl4ai.yaml diff --git a/vault/kubernetes-roles/frigate-camera.yaml b/vault/kubernetes-auth-roles/frigate-camera.yaml similarity index 100% rename from vault/kubernetes-roles/frigate-camera.yaml rename to vault/kubernetes-auth-roles/frigate-camera.yaml diff --git a/vault/kubernetes-roles/immich.yaml b/vault/kubernetes-auth-roles/immich.yaml similarity index 100% rename from vault/kubernetes-roles/immich.yaml rename to vault/kubernetes-auth-roles/immich.yaml diff --git a/vault/kubernetes-roles/llama-proxy.yaml b/vault/kubernetes-auth-roles/llama-proxy.yaml similarity index 100% rename from vault/kubernetes-roles/llama-proxy.yaml rename to vault/kubernetes-auth-roles/llama-proxy.yaml diff --git a/vault/kubernetes-roles/ollama-proxy.yaml b/vault/kubernetes-auth-roles/ollama-proxy.yaml similarity index 100% rename from vault/kubernetes-roles/ollama-proxy.yaml rename to vault/kubernetes-auth-roles/ollama-proxy.yaml diff --git a/vault/kubernetes-roles/openwebui.yaml b/vault/kubernetes-auth-roles/openwebui.yaml similarity index 100% rename from vault/kubernetes-roles/openwebui.yaml rename to vault/kubernetes-auth-roles/openwebui.yaml diff --git a/vault/kubernetes-roles/renovate.yaml b/vault/kubernetes-auth-roles/renovate.yaml similarity index 100% rename from vault/kubernetes-roles/renovate.yaml rename to vault/kubernetes-auth-roles/renovate.yaml diff --git a/vault/kubernetes-roles/woodpecker.yaml b/vault/kubernetes-auth-roles/woodpecker.yaml similarity index 100% rename from vault/kubernetes-roles/woodpecker.yaml rename to vault/kubernetes-auth-roles/woodpecker.yaml diff --git a/vault/kubernetes-se-roles/flux-reconcile.yaml b/vault/kubernetes-se-roles/flux-reconcile.yaml new file mode 100644 index 0000000..69d0eea --- /dev/null +++ b/vault/kubernetes-se-roles/flux-reconcile.yaml @@ -0,0 +1,6 @@ +allowed_kubernetes_namespaces: flux-system +generated_role_rules: + rules: + - apiGroups: ["kustomize.toolkit.fluxcd.io"] + resources: ["gitrepositories"] + verbs: ["update", "watch"] diff --git a/vault/kubernetes-secretengine-config.yaml b/vault/kubernetes-secretengine-config.yaml new file mode 100644 index 0000000..e69de29