--- # Read Supabase secrets from Vault; generate and store any that are missing. # # All __supabase_* facts set here are consumed by the K8s secret tasks in main.yml. # # JWT note: if jwt_secret is absent OR either JWT token is absent, all three are # regenerated together — a partial JWT state (e.g. tokens signed by a different # secret) would break auth across all services. # ------------------------------------------------------------------ # Read existing secrets (may fail if path does not exist yet) # ------------------------------------------------------------------ - name: Read supabase secrets from Vault community.hashi_vault.vault_kv2_get: path: "{{ supabase_vault_path }}" engine_mount_point: "{{ supabase_vault_mount }}" register: __supabase_vault_read failed_when: false no_log: "{{ supabase_no_log }}" - name: Set existing vault data fact ansible.builtin.set_fact: __sv: "{{ __supabase_vault_read.secret | default({}) }}" no_log: "{{ supabase_no_log }}" # ------------------------------------------------------------------ # Resolve non-JWT secrets: use existing or generate random values # ------------------------------------------------------------------ - name: Resolve non-JWT secrets (generate any that are missing) ansible.builtin.set_fact: __supabase_db_password: >- {{ __sv.db_password | default(lookup('community.general.random_string', length=32, special=false)) }} __supabase_dashboard_username: >- {{ __sv.dashboard_username | default('supabase') }} __supabase_dashboard_password: >- {{ __sv.dashboard_password | default(lookup('community.general.random_string', length=24, special=false)) }} __supabase_analytics_public_token: >- {{ __sv.analytics_public_token | default(lookup('community.general.random_string', length=32, special=false)) }} __supabase_analytics_private_token: >- {{ __sv.analytics_private_token | default(lookup('community.general.random_string', length=32, special=false)) }} __supabase_realtime_secret_key_base: >- {{ __sv.realtime_secret_key_base | default(lookup('community.general.random_string', length=64, special=false)) }} __supabase_meta_crypto_key: >- {{ __sv.meta_crypto_key | default(lookup('community.general.random_string', length=32, special=false)) }} __supabase_openai_api_key: >- {{ __sv.openai_api_key | default('') }} no_log: "{{ supabase_no_log }}" # ------------------------------------------------------------------ # JWT secret + signed tokens (must be generated as a coherent set) # ------------------------------------------------------------------ - name: Check whether JWT values need to be (re)generated ansible.builtin.set_fact: __supabase_needs_jwt_gen: >- {{ not (__sv.jwt_secret | default('') | string | length > 0) or not (__sv.anon_key | default('') | string | length > 0) or not (__sv.service_key | default('') | string | length > 0) }} - name: Generate JWT signing secret ansible.builtin.set_fact: __supabase_jwt_secret: >- {{ lookup('community.general.random_string', length=64, special=false) }} no_log: "{{ supabase_no_log }}" when: __supabase_needs_jwt_gen | bool - name: Use existing JWT signing secret ansible.builtin.set_fact: __supabase_jwt_secret: "{{ __sv.jwt_secret }}" no_log: "{{ supabase_no_log }}" when: not __supabase_needs_jwt_gen | bool - name: Generate anon and service_role JWT tokens ansible.builtin.command: argv: - python3 - -c - | import hmac, hashlib, base64, json, time, sys def b64url(data): if isinstance(data, str): data = data.encode() return base64.urlsafe_b64encode(data).rstrip(b'=').decode() def make_jwt(secret, role): now = int(time.time()) header = b64url(json.dumps({'alg': 'HS256', 'typ': 'JWT'}, separators=(',', ':'))) payload = b64url(json.dumps( {'role': role, 'iss': 'supabase', 'iat': now, 'exp': now + 157680000}, separators=(',', ':') )) msg = '{}.{}'.format(header, payload) sig = b64url(hmac.new(secret.encode(), msg.encode(), hashlib.sha256).digest()) return '{}.{}'.format(msg, sig) secret = sys.argv[1] print(make_jwt(secret, 'anon')) print(make_jwt(secret, 'service_role')) - "{{ __supabase_jwt_secret }}" register: __supabase_jwt_output changed_when: false no_log: "{{ supabase_no_log }}" when: __supabase_needs_jwt_gen | bool - name: Set generated JWT token facts ansible.builtin.set_fact: __supabase_anon_key: "{{ __supabase_jwt_output.stdout_lines[0] }}" __supabase_service_key: "{{ __supabase_jwt_output.stdout_lines[1] }}" no_log: "{{ supabase_no_log }}" when: __supabase_needs_jwt_gen | bool - name: Use existing JWT tokens from Vault ansible.builtin.set_fact: __supabase_anon_key: "{{ __sv.anon_key }}" __supabase_service_key: "{{ __sv.service_key }}" no_log: "{{ supabase_no_log }}" when: not __supabase_needs_jwt_gen | bool # ------------------------------------------------------------------ # Write back to Vault (always — ensures all fields are present and # any newly generated values are persisted before K8s secrets are written) # ------------------------------------------------------------------ - name: Write supabase secrets to Vault community.hashi_vault.vault_kv2_write: path: "{{ supabase_vault_path }}" engine_mount_point: "{{ supabase_vault_mount }}" data: jwt_secret: "{{ __supabase_jwt_secret }}" anon_key: "{{ __supabase_anon_key }}" service_key: "{{ __supabase_service_key }}" db_password: "{{ __supabase_db_password }}" dashboard_username: "{{ __supabase_dashboard_username }}" dashboard_password: "{{ __supabase_dashboard_password }}" analytics_public_token: "{{ __supabase_analytics_public_token }}" analytics_private_token: "{{ __supabase_analytics_private_token }}" realtime_secret_key_base: "{{ __supabase_realtime_secret_key_base }}" meta_crypto_key: "{{ __supabase_meta_crypto_key }}" openai_api_key: "{{ __supabase_openai_api_key }}" no_log: "{{ supabase_no_log }}" - name: Write postgres_url to oys/dev/supabase in Vault community.hashi_vault.vault_kv2_write: path: oys/dev/supabase engine_mount_point: "{{ supabase_vault_mount }}" data: postgres_url: >- postgresql://postgres:{{ __supabase_db_password }}@{{ supabase_db_external_host }}:{{ supabase_db_nodeport }}/{{ supabase_db_name }} no_log: "{{ supabase_no_log }}" - name: Report vault secret status ansible.builtin.debug: msg: >- {{ 'Generated and stored new Supabase secrets in Vault' if __supabase_needs_jwt_gen | bool else 'Using existing Supabase secrets from Vault' }}