diff --git a/README.md b/README.md index 1e8918d..75fad27 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ When loaded via `shared_preload_libraries`, the extension registers a hook into ``` ERROR: COPY TO command is not allowed +HINT: Contact DBA to request access ERROR: COPY FROM command is not allowed ERROR: COPY TO PROGRAM command is not allowed ``` @@ -163,6 +164,35 @@ COPY (SELECT 1) TO PROGRAM 'cat'; SET block_copy_command.block_program = on; ``` +### GUC: `block_copy_command.hint` + +An optional custom hint appended to the error when a `COPY` command is blocked. Only superusers can change this setting. + +| Value | Effect | +|-------|--------| +| *(empty, default)* | No hint shown | +| any string | Shown as `HINT:` after the error message | + +```sql +SET block_copy_command.hint = 'Contact DBA to request access'; + +-- regular user now sees: +-- ERROR: COPY TO command is not allowed +-- HINT: Contact DBA to request access +``` + +**Cluster-wide** (in `postgresql.conf`): + +``` +block_copy_command.hint = 'Contact DBA to request access' +``` + +**Per-database:** + +```sql +ALTER DATABASE mydb SET block_copy_command.hint = 'Contact DBA to request access'; +``` + ### GUC: `block_copy_command.blocked_roles` A comma-separated list of role names that are **always** blocked from running `COPY`, regardless of superuser status or the `enabled` setting. Only superusers can change this setting. @@ -192,6 +222,7 @@ When a COPY command is blocked, the current username is written to the PostgreSQ ``` LOG: current_user = "someuser" ERROR: COPY TO command is not allowed +HINT: Contact DBA to request access ``` ## Testing diff --git a/src/lib.rs b/src/lib.rs index 37bd42f..70fe54c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ use pgrx::guc::{GucContext, GucFlags, GucRegistry, GucSetting}; use pgrx::is_a; use pgrx::pg_sys; +use pgrx::pg_sys::panic::ErrorReport; use pgrx::prelude::*; use std::ffi::CString; @@ -16,6 +17,8 @@ static BLOCK_TO: GucSetting = GucSetting::::new(true); static BLOCK_FROM: GucSetting = GucSetting::::new(true); // Block COPY TO/FROM PROGRAM for all users, including superusers. static BLOCK_PROGRAM: GucSetting = GucSetting::::new(true); +// Optional hint message shown to users when their COPY command is blocked. +static HINT: GucSetting> = GucSetting::>::new(None); static mut PREV_PROCESS_UTILITY_HOOK: pg_sys::ProcessUtility_hook_type = None; @@ -61,7 +64,19 @@ unsafe fn block_copy_process_utility(args: ProcessUtilityArgs) { pgrx::log!("current_user = {:?}", username); let direction = if is_from { "FROM" } else { "TO" }; let suffix = if is_program { " PROGRAM" } else { "" }; - pgrx::error!("COPY {}{} command is not allowed", direction, suffix); + let msg = format!("COPY {}{} command is not allowed", direction, suffix); + let hint = HINT + .get() + .and_then(|cstr| cstr.to_str().ok().map(str::to_owned)); + let mut report = ErrorReport::new( + PgSqlErrorCode::ERRCODE_INSUFFICIENT_PRIVILEGE, + msg, + "", + ); + if let Some(h) = hint { + report = report.set_hint(h); + } + report.report(PgLogLevel::ERROR); } } @@ -161,6 +176,15 @@ pub extern "C-unwind" fn _PG_init() { GucFlags::default(), ); + GucRegistry::define_string_guc( + c"block_copy_command.hint", + c"Custom hint shown when a COPY command is blocked", + c"When set, this message is appended as a HINT to the error raised when a COPY command is blocked (e.g. 'Contact DBA to request access').", + &HINT, + GucContext::Suset, + GucFlags::default(), + ); + unsafe { PREV_PROCESS_UTILITY_HOOK = pg_sys::ProcessUtility_hook; pg_sys::ProcessUtility_hook = Some(hook_trampoline);