Closed vickkhera closed 2 months ago
The error occurs because the api_key_id
options should be set to the value of the key_id
column of the vault.secrets
table, not the id
column. Since you want to create the foreign table and set the api key in a migration you can do something like the following:
create extension if not exists wrappers with schema extensions;
create foreign data wrapper stripe_wrapper
handler stripe_fdw_handler
validator stripe_fdw_validator;
-- not yet setting the api_key_id option, it will be set later in the set_stripe_secret function
create server stripe_server
foreign data wrapper stripe_wrapper;
create schema if not exists stripe;
create foreign table stripe.customers (
id text,
email text,
name text,
description text,
created timestamp,
attrs jsonb
)
server stripe_server
options (
object 'customers',
rowid_column 'id'
);
-- this function inserts a new stripe secret in vault and updates
-- the foreign server's api_key_id with the returned key_id
create or replace function set_stripe_secret() returns void as $$
declare
api_key_id uuid;
begin
with inserted_key_id as (
insert into vault.secrets (name, description, secret)
values (
'stripe',
'stripe test key',
'sk_test_XXXXX'
)
returning key_id
)
select key_id into api_key_id from inserted_key_id;
execute 'alter server stripe_server options (api_key_id ' || quote_literal(api_key_id) || ')';
end;
$$ language plpgsql;
select set_stripe_secret();
select * from stripe.customers;
Be careful about embedding a production stripe secret in a migration file though.
Thanks for pointing out my error on the proper ID field. I must say using the key_id
as the reference into the vault does make it very hard to use when I'm running migrations to a preview and production environment. I'm curious what the security implications are of just using the name
column from the vault? It is unique (unlike key_id
).
I was hoping I could just force the key_id
to be my chosen value, but the vault does not permit me to set that column. I ended up with this solution, so I can use it on local development (with a seed.sql) and deploy on my working preview and production environments.
The migration file:
-- not yet setting the api_key_id option, it will be set later by looking up the key_id in the vault
CREATE SERVER stripe_server FOREIGN DATA WRAPPER stripe_wrapper OPTIONS (api_key_id 'placeholder');
-- on PREVIEW and PROD, we fetch the key_id from the vault which was set before this migration. Only if we find it, we update
-- the wrapper server to use it. This allows us to run this on DEV where the key_id is not yet set by the seed file.
CREATE PROCEDURE install_stripe_secret() AS $$
DECLARE
vault_key_id TEXT;
BEGIN
SELECT key_id::TEXT FROM vault.secrets WHERE name='stripe' INTO vault_key_id;
IF vault_key_id IS NOT NULL THEN
EXECUTE 'ALTER SERVER stripe_server OPTIONS (SET api_key_id ' || quote_literal(vault_key_id) || ')';
END IF;
END
$$ LANGUAGE PLPGSQL;
COMMENT ON PROCEDURE install_stripe_secret() IS 'Look up the Stripe key_id from the vault and set it in the server wrapper';
REVOKE ALL ON PROCEDURE install_stripe_secret FROM PUBLIC,authenticated,anon;
CALL install_stripe_secret();
NOTE: I use a 'placeholder' in the original key_id because there is not "ADD OR SET" for the options in the ALTER command. If I use ADD, then that function will emit an error if it is ever run again; if I use SET then it must have a value already but I can re-run my function as I need. I chose the latter option.
Then in my seed.sql
for local dev, I have to also call the function, since it is processed after the migrations.
INSERT INTO vault.secrets(name,description,secret) VALUES ('stripe','Stripe TEST key','sk_test_XXXXX');
CALL install_stripe_secret();
This solves my problem.
All in all, it would be much simpler to make this service reference a vault entry name rather than a computed column value which is different in each environment. I can control the name and make it the same on all environments, which I feel is the point of having the vault be our substitute for not having access to environment variables yet.
Thanks @vickkhera for the suggestion to use name
instead of key_id
. I agree that it would make it easier to manage secrets if they are referenced by name
. I don't see any security implications of this right now. But I'm leaving this open for others to mull this over and implement a solution in which users can write something like the following:
-- note the use of a new `api_key_name` option instead of `api_key_id`.
create server stripe_server
foreign data wrapper stripe_wrapper
options (api_key_name 'stripe secret name');
This is timely - I’m encountering the exact same issue right now. Even better would be if we could reference an env from directly within a migration file. I.e. {{env.API_KEY}}.
This would avoid any risk of exposing secrets to version control via migration file.
The reason we're using key_id
instead of key_name
is because the name
is not unique. You can save multiple api keys in vault with same name, but the foreign server needs exactly only one api key. So I think we'd better keep using key_id
.
The reason we're using
key_id
instead ofkey_name
is because thename
is not unique. You can save multiple api keys in vault with same name, but the foreign server needs exactly only one api key. So I think we'd better keep usingkey_id
.
This is not true.
postgres=> \d vault.secrets
Table "vault.secrets"
Column | Type | Collation | Nullable | Default
-------------+--------------------------+-----------+----------+-------------------------------------
id | uuid | | not null | gen_random_uuid()
name | text | | |
description | text | | not null | ''::text
secret | text | | not null |
key_id | uuid | | | (pgsodium.create_key()).id
nonce | bytea | | | pgsodium.crypto_aead_det_noncegen()
created_at | timestamp with time zone | | not null | CURRENT_TIMESTAMP
updated_at | timestamp with time zone | | not null | CURRENT_TIMESTAMP
Indexes:
"secrets_pkey" PRIMARY KEY, btree (id)
"secrets_name_idx" UNIQUE, btree (name) WHERE name IS NOT NULL
Foreign-key constraints:
"secrets_key_id_fkey" FOREIGN KEY (key_id) REFERENCES pgsodium.key(id)
Triggers:
secrets_encrypt_secret_trigger_secret BEFORE INSERT OR UPDATE OF secret ON vault.secrets FOR EACH ROW EXECUTE FUNCTION vault.secrets_encrypt_secret_secret()
There is a UNIQUE index on secrets name
column, specifically secrets_name_idx
.
There is a UNIQUE index on secrets name column, specifically secrets_name_idx.
oh sorry I missed that index, then adding the api_key_name
option looks a good solution, will sort it out soon.
Bug report
Describe the bug
The stripe wrapper is unable to lookup my secret key ID when selecting from the wrapper tables.
To Reproduce
Steps to reproduce the behavior, please provide code snippets or a repository:
Add the secret key to the vault (inside seed.sql):
NOTE: You have to hard-code the
id
here since there's no way to introspect it within the migration file. I need it to work on all environments (DEV/PREVIEW/PROD) so I just hard-coded it.Create a migration file, and run
npx supabase db reset
to load it.Then interactively in
psql
CLI:As you can see, the vault key ID does exist despite the error. I cannot find what this "SpiTupleTable..." error means on google.
Expected behavior
I expect the query on
stripe.customers
to return a list of my customers (dev mode, so there are only a handful that should come back). Same error on every other wrapped stripe table.Screenshots
Error is in text above.
System information
npx supabase start
in docker.Additional context
Add any other context about the problem here.
Supabase postgres container version 15.1.1.41