feat: add supabase deployer
This commit is contained in:
152
roles/supabase/tasks/vault_secrets.yml
Normal file
152
roles/supabase/tasks/vault_secrets.yml
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
# 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: 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' }}
|
||||
Reference in New Issue
Block a user