Crate postguard

Source
Expand description

Test Postgres-compatible statements against a set of CORS-like rules.

§Why

Postgres has a rich ROLE system for managing privileges around data in a database. But those privileges are often too permissive by default, and are difficult to restrict with by statement or function name.

postguard provides a Guard statement analyzer for protecting databases from malicious or invalid queries. This Guard can be used in any Rust application that has access to statements (perhaps from untrusted sources) before they are run. Under the hood, postguard uses the libpg_query library to parse queries down to a syntax tree before checking the entire tree for disallowed nodes.

§Usage

use postguard::{AllowedFunctions, AllowedStatements, Command, Guard};

// If AllowedFunctions and AllowedStatements both are set to their 'All' variants
// then no parsing is done and all statements pass the guard
#[test]
fn it_does_nothing_by_default() {
    Guard::new(AllowedStatements::All, AllowedFunctions::All)
        .guard("create table test (id serial primary key)")
        .expect("all statements are permitted by default");
}

// Statements are checked against the list of allowed statements when a 'List' variant
// is provided. Statement-checking is done recursively, so nested disallowed statements
// are also caught by the guard
#[test]
fn it_restricts_statements() {
    let statement_guard = Guard::new(AllowedStatements::List(vec![Command::Select]));

    statement_guard
        .guard("create table test (id serial primary key)")
        .expect_err("privileged statements are restricted");

    statement_guard
        .guard("select * from items")
        .expect("select statements are permitted");

    statement_guard
        .guard("insert into items default values")
        .expect_err("commands in top-level queries are restricted");

    statement_guard
        .guard("
            with cte as (
                insert into items default values
                returning id
            )
            select * from cte
        ")
        .expect_err("disallowed commands in nested queries or expressions are restricted");
}

// Functions are also guarded by name. To disallow all functions, leave the 'List' empty.
#[test]
fn it_restricts_functions() {
    let statement_guard = Guard::new(
        AllowedStatements::List(vec![Command::Select]),
        AllowedFunctions::List(vec!["uuid_generate_v4".to_string()])
    );

    statement_guard
        .guard("select pg_sleep(1)")
        .expect_err("built-in functions and stored procs are restricted");

    statement_guard
        .guard("select uuid_generate_v4() as id")
        .expect("allowed functions are permitted");
}

Structs§

Guard
CORS-like statement analyzer for guarding queryable methods

Enums§

AllowedFunctions
Configurations for Allowed Functions (by name)
AllowedStatements
Configurations for Allowed Statements
Command
Possible Commands to filter statements by. These commands are intentionally restricted to basic CRUD operations only.
Error
Errors related to statement rejection or parsing failures