Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
26 changes: 25 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -16,6 +17,8 @@ static BLOCK_TO: GucSetting<bool> = GucSetting::<bool>::new(true);
static BLOCK_FROM: GucSetting<bool> = GucSetting::<bool>::new(true);
// Block COPY TO/FROM PROGRAM for all users, including superusers.
static BLOCK_PROGRAM: GucSetting<bool> = GucSetting::<bool>::new(true);
// Optional hint message shown to users when their COPY command is blocked.
static HINT: GucSetting<Option<CString>> = GucSetting::<Option<CString>>::new(None);

static mut PREV_PROCESS_UTILITY_HOOK: pg_sys::ProcessUtility_hook_type = None;

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
Expand Down
Loading