-
Notifications
You must be signed in to change notification settings - Fork 171
Update security restrictions to allow non-superuser extension installation #572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e2947ed
731024d
dafa945
d5dbc6f
fe034d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ extern "C" { | |
| #include "nodes/nodeFuncs.h" | ||
| #include "catalog/namespace.h" | ||
| #include "utils/builtins.h" | ||
| #include "utils/guc.h" | ||
| #include "utils/lsyscache.h" | ||
| #include "utils/rel.h" | ||
| #include "utils/snapmgr.h" | ||
|
|
@@ -193,8 +194,28 @@ ReadDuckdbExtensions() { | |
| static bool | ||
| DuckdbInstallExtension(Datum name_datum) { | ||
| auto extension_name = DatumToString(name_datum); | ||
| auto install_extension_command = duckdb::StringUtil::Format("INSTALL %s;", extension_name.c_str()); | ||
|
|
||
| auto install_extension_command = "INSTALL " + duckdb::KeywordHelper::WriteQuoted(extension_name); | ||
|
|
||
| /* | ||
| * Temporily allow all filesystems for this query, because INSTALL needs | ||
| * local filesystem access. Since this setting cannot be changed inside | ||
| * DuckDB after it's set to LocalFileSystem this temporary configuration | ||
| * change only really has effect duckdb.install_extension is called as the | ||
| * first DuckDB query for this session. Since we cannot change it back. | ||
| * | ||
| * While that's suboptimal it's also not a huge problem. Users only need to | ||
| * install an extension once on a server. So doing that on a new connection | ||
| * or after calling duckdb.recycle_ddb() should not be a big deal. | ||
| * | ||
| * NOTE: Because each backend has its own DuckDB instance, this setting | ||
| * does not impact other backends and thus cannot cause a security issue | ||
| * due to a race condition. | ||
| */ | ||
| auto save_nestlevel = NewGUCNestLevel(); | ||
| SetConfigOption("duckdb.disabled_filesystems", "", PGC_SUSET, PGC_S_SESSION); | ||
|
||
| pgduckdb::DuckDBQueryOrThrow(install_extension_command); | ||
| AtEOXact_GUC(false, save_nestlevel); | ||
|
|
||
| Oid arg_types[] = {TEXTOID}; | ||
| Datum values[] = {name_datum}; | ||
|
|
@@ -319,6 +340,7 @@ DECLARE_PG_FUNCTION(cache) { | |
| } | ||
|
|
||
| DECLARE_PG_FUNCTION(cache_info) { | ||
| pgduckdb::RequireDuckdbExecution(); | ||
| ReturnSetInfo *rsinfo = (ReturnSetInfo *)fcinfo->resultinfo; | ||
| Tuplestorestate *tuple_store; | ||
| TupleDesc cache_info_tuple_desc; | ||
|
|
@@ -359,12 +381,14 @@ DECLARE_PG_FUNCTION(cache_info) { | |
| } | ||
|
|
||
| DECLARE_PG_FUNCTION(cache_delete) { | ||
| pgduckdb::RequireDuckdbExecution(); | ||
| Datum cache_key = PG_GETARG_DATUM(0); | ||
| bool result = pgduckdb::DuckdbCacheDelete(cache_key); | ||
| PG_RETURN_BOOL(result); | ||
| } | ||
|
|
||
| DECLARE_PG_FUNCTION(pgduckdb_recycle_ddb) { | ||
| pgduckdb::RequireDuckdbExecution(); | ||
| /* | ||
| * We cannot safely run this in a transaction block, because a DuckDB | ||
| * transaction might have already started. Recycling the database will | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| from .utils import Postgres | ||
|
|
||
| import pytest | ||
| import psycopg.errors | ||
| import psycopg.sql | ||
|
|
||
|
|
||
| def test_community_extensions(pg: Postgres): | ||
| pg.create_user("user1", psycopg.sql.SQL("IN ROLE duckdb_group")) | ||
| # Raw extension installation should not be possible non-superusers, because | ||
| # that would allow installing extensions from community repos. | ||
| with pg.cur() as cur: | ||
| cur.sql("SET ROLE user1") | ||
| print(cur.sql("SHOW ROLE")) | ||
| cur.sql("SET duckdb.force_execution = false") | ||
| with pytest.raises( | ||
| psycopg.errors.InternalError, | ||
| match="Permission Error: File system LocalFileSystem has been disabled by configuration", | ||
| ): | ||
| cur.sql( | ||
| "SELECT * FROM duckdb.raw_query($$ INSTALL avro FROM community; $$)" | ||
| ) | ||
|
|
||
| # Even if such community extensions somehow get installed, it's not possible | ||
| # to load them without changing allow_community_extensions. Not even for a | ||
| # superuser. | ||
| with pg.cur() as cur: | ||
| cur.sql("SET duckdb.force_execution = false") | ||
| cur.sql("SELECT * FROM duckdb.raw_query($$ INSTALL avro FROM community; $$)") | ||
| with pytest.raises( | ||
| Exception, | ||
| match="IO Error: Extension .* could not be loaded because its signature is either missing or invalid and unsigned extensions are disabled by configuration", | ||
| ): | ||
| cur.sql("SELECT * FROM duckdb.raw_query($$ LOAD avro; $$)") | ||
|
|
||
| # But it should be possible to load them after changing that setting. | ||
| with pg.cur() as cur: | ||
| cur.sql("SET duckdb.allow_community_extensions = true") | ||
| cur.sql("SET duckdb.force_execution = false") | ||
| cur.sql("SELECT * FROM duckdb.raw_query($$ LOAD avro; $$)") | ||
|
|
||
| # And that setting is only changeable by superusers | ||
| with pg.cur() as cur: | ||
| cur.sql("SET ROLE user1") | ||
| with pytest.raises( | ||
| psycopg.errors.InsufficientPrivilege, | ||
| match='permission denied to set parameter "duckdb.allow_community_extensions"', | ||
| ): | ||
| cur.sql("SET duckdb.allow_community_extensions = true") |
Uh oh!
There was an error while loading. Please reload this page.