\set VERBOSITY terse /* * rdf_fdw_describe() and rdf_fdw_clone_table() take the *name* of an object * rather than the object itself, so the executor never builds a range table * entry for it and none of the permission checks a plain query would get ever * run. Both entry points therefore have to check for themselves. * * Nothing here contacts the endpoint: the checks happen before the request is * built, which is the point -- the server option may name any host at all. */ CREATE SERVER priv_srv FOREIGN DATA WRAPPER rdf_fdw OPTIONS (endpoint 'http://127.0.0.1:9/sparql', connect_timeout '1'); CREATE FOREIGN TABLE priv_ft ( s rdfnode OPTIONS (variable '?s') ) SERVER priv_srv OPTIONS (sparql 'SELECT ?s WHERE {?s ?p ?o}'); CREATE TABLE priv_stolen (s rdfnode); CREATE ROLE priv_role LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE; SET ROLE priv_role; /* the SPARQL function API is reachable without any grant of its own: * the extension grants USAGE on its schema to PUBLIC */ SELECT sparql.ucase('"hi"'); ucase ------- "HI" (1 row) /* no privileges at all: selecting from the table is refused by PostgreSQL */ SELECT * FROM priv_ft; ERROR: permission denied for foreign table priv_ft /* EXECUTE on the procedure is granted to PUBLIC, since the extension script * issues no REVOKE, so without a check of its own rdf_fdw_clone_table() would * read straight through a foreign table the caller was explicitly denied */ CALL rdf_fdw_clone_table( foreign_table => 'priv_ft', target_table => 'priv_stolen', create_table => false, verbose => false); ERROR: permission denied for foreign table priv_ft /* rdf_fdw_describe() has the same gap for its SERVER argument, and nothing * shields it: schema "sparql" is reachable by PUBLIC, so the check has to * be its own. */ SELECT * FROM sparql.describe('priv_srv', 'DESCRIBE '); ERROR: permission denied for foreign server priv_srv RESET ROLE; /* SELECT on the table alone is not enough: reaching the endpoint also uses * whatever credentials the DBA put in the server's user mapping, so USAGE on * the server is required as well */ GRANT SELECT ON priv_ft TO priv_role; SET ROLE priv_role; CALL rdf_fdw_clone_table( foreign_table => 'priv_ft', target_table => 'priv_stolen', create_table => false, verbose => false); ERROR: permission denied for foreign server priv_srv RESET ROLE; /* with both privileges held the checks pass and the call proceeds to the * endpoint, which is what the unreachable address below then reports */ GRANT USAGE ON FOREIGN SERVER priv_srv TO priv_role; SET ROLE priv_role; SELECT * FROM sparql.describe('priv_srv', 'DESCRIBE '); WARNING: ExecuteSPARQL: request to 'priv_srv' failed (1) WARNING: ExecuteSPARQL: request to 'priv_srv' failed (2) WARNING: ExecuteSPARQL: request to 'priv_srv' failed (3) ERROR: unable to connect to 'priv_srv' RESET ROLE; /* clean up */ DROP TABLE priv_stolen; DROP SERVER priv_srv CASCADE; NOTICE: drop cascades to foreign table priv_ft DROP ROLE priv_role;