From 647fff265acf3353f952be1aad421db51a7ba68c Mon Sep 17 00:00:00 2001 From: Lars Wilhelmsen Date: Mon, 1 Jan 2024 22:53:58 +0100 Subject: [PATCH] initial release --- .cargo/config.toml | 3 + .github/workflows/rust.yml | 26 ++++++ .gitignore | 7 ++ .licensure.yml | 96 ++++++++++++++++++++ Cargo.toml | 40 +++++++++ LICENSE_APACHE | 176 +++++++++++++++++++++++++++++++++++++ LICENSE_MIT | 25 ++++++ README.md | 83 +++++++++++++++++ accumulo_access_pg.control | 6 ++ src/lib.rs | 75 ++++++++++++++++ 10 files changed, 537 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore create mode 100644 .licensure.yml create mode 100644 Cargo.toml create mode 100644 LICENSE_APACHE create mode 100644 LICENSE_MIT create mode 100644 README.md create mode 100644 accumulo_access_pg.control create mode 100644 src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..13c456b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[target.'cfg(target_os="macos")'] +# Postgres symbols won't be available until runtime +rustflags = ["-Clink-arg=-Wl,-undefined,dynamic_lookup"] diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..9d0bc86 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,26 @@ +name: Rust + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Install PGRX + run: cargo install cargo-pgrx && cargo pgrx init --pg15=download + - name: Build + run: cargo build --verbose --release + - name: Run tests + run: cargo pgrx test pg15 --verbose + - name: Package extension + run: cargo pgrx package -c `cargo pgrx info pg-config 15` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4c5593 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.idea/ +/target +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log diff --git a/.licensure.yml b/.licensure.yml new file mode 100644 index 0000000..c96ea41 --- /dev/null +++ b/.licensure.yml @@ -0,0 +1,96 @@ + +# Regexes which if matched by a file path will always be excluded from +# getting a license header +excludes: + - \.gitignore + - .*lock + - \.git/.* + - \.licensure\.yml + - README.* + - LICENSE-APACHE.* + - NOTICE.* + - .*\.toml + - .*\.(md|rst|txt) +# Definition of the licenses used on this project and to what files +# they should apply. +# +# No default license configuration is provided. This section must be +# configured by the user. +licenses: + - files: any + ident: Apache-2.0 + authors: + - name: Lars Wilhelmsen + email: sral-backwards@sral.org + template: | + Copyright [year] [name of author]. All rights reserved. Use of + this source code is governed by the [ident] license that can be + found in the LICENSE file. + +# Define type of comment characters to apply based on file extensions. +comments: + # The extensions (or singular extension) field defines which file + # extensions to apply the commenter to. + - extensions: + - js + - rs + - go + # The commenter field defines the kind of commenter to + # generate. There are two types of commenters: line and block. + # + # This demonstrates a line commenter configuration. A line + # commenter type will apply the comment_char to the beginning of + # each line in the license header. It will then apply a number of + # empty newlines to the end of the header equal to trailing_lines. + # + # If trailing_lines is omitted it is assumed to be 0. + commenter: + type: line + comment_char: "//" + trailing_lines: 0 + - extensions: + - css + - cpp + - c + # This demonstrates a block commenter configuration. A block + # commenter type will add start_block_char as the first character + # in the license header and add end_block_char as the last character + # in the license header. If per_line_char is provided each line of + # the header between the block start and end characters will be + # line commented with the per_line_char + # + # trailing_lines works the same for both block and line commenter + # types + commenter: + type: block + start_block_char: "/*\n" + end_block_char: "*/" + per_line_char: "*" + trailing_lines: 0 + # In this case extension is singular and a single string extension is provided. + - extension: html + commenter: + type: block + start_block_char: "" + - extensions: + - el + - lisp + commenter: + type: line + comment_char: ";;;" + trailing_lines: 0 + # The extension string "any" is special and so will match any file + # extensions. Commenter configurations are always checked in the + # order they are defined, so if any is used it should be the last + # commenter configuration or else it will override all others. + # + # In this configuration if we can't match the file extension we fall + # back to the popular '#' line comment used in most scripting + # languages. + - extension: any + commenter: + type: line + comment_char: '#' + trailing_lines: 0 + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f219adf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "accumulo-access-pg" +version = "0.1.0" +edition = "2021" +authors = ["Lars Wilhelmsen "] +description = "PostgreSQL extension for parsing and evaluating Accumulo Access Expressions" +license = "MIT OR Apache-2.0" +repository = "https://github.com/larsw/accumulo-access-pg" +readme = "README.md" + +[lib] +crate-type = ["cdylib"] + +[features] +default = ["pg15"] +pg11 = ["pgrx/pg11", "pgrx-tests/pg11" ] +pg12 = ["pgrx/pg12", "pgrx-tests/pg12" ] +pg13 = ["pgrx/pg13", "pgrx-tests/pg13" ] +pg14 = ["pgrx/pg14", "pgrx-tests/pg14" ] +pg15 = ["pgrx/pg15", "pgrx-tests/pg15" ] +pg16 = ["pgrx/pg16", "pgrx-tests/pg16" ] +pg_test = [] + +[dependencies] +#accumulo-access = { path = "../accumulo-access-rs/accumulo-access", features = ["caching"] } # for local dev +accumulo-access = "0.1.3" +pgrx = "=0.11.2" +serde = { version = "1.0.194", features = ["derive"] } + +[dev-dependencies] +pgrx-tests = "=0.11.2" + +[profile.dev] +panic = "unwind" + +[profile.release] +panic = "unwind" +opt-level = 3 +lto = "fat" +codegen-units = 1 diff --git a/LICENSE_APACHE b/LICENSE_APACHE new file mode 100644 index 0000000..1b5ec8b --- /dev/null +++ b/LICENSE_APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/LICENSE_MIT b/LICENSE_MIT new file mode 100644 index 0000000..c6e3699 --- /dev/null +++ b/LICENSE_MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 Lars Wilhelmsen + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5d389b --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# Accumulo Access Expressions for PostgreSQL + +## Introduction + +This project provides a PostgreSQL extension that allows to parse, evaluate and filter rows (Row-Level Security) with Accumulo access expressions to be used in PostgreSQL queries. The extension is implemented as a Rust extension to PostgreSQL. + +The development wouldn't have been possible without the excellent [pgrx project](https://github.com/pgcentralfoundation/pgrx). + +## Installation + +_TODO_ + +```bash +cargo install cargo-pgrx +cargo pgrx init --pg15=download +cargo build --release +cargo pgrx run pg15 +#cargo pgrx package +``` + +```sql +CREATE EXTENSION accumulo_access_pg; +``` + +## Usage + +### Example with Row Level Security + +```sql +create role users; +create user johnny; +grant users to johnny; + +create table secret_stuff(id serial primary key, data text not null, authz_expr text not null); +alter table secret_stuff enable row level security; +insert into secret_stuff(data, authz_expr) values('pretty secret', 'label1'); +insert into secret_stuff(data, authz_expr) values('moar secret', 'label1|label2'); +insert into secret_stuff(data, authz_expr) values('wat', 'label2'); +insert into secret_stuff(data, authz_expr) values('win', 'label2 & (label3 | label4)'); + +grant select on secret_stuff to users; + +create policy evaluate_policies on secret_stuff using ( sec_authz_check(authz_expr, current_setting('session.authorizations'), ',')); + +-- ... +set session authorization johnny; +select current_user,session_user; +-- current_user | session_user +----------------+-------------- +-- johnny | johnny + +set session.authorizations = 'label1'; + +select * from secret_stuff; +-- id | data | authz_expr +------+---------------+--------------- +-- 1 | pretty secret | label1 +-- 2 | moar secret | label1|label2 +-- (2 rows) + +set session.authorizations = 'label2,label3'; +select * from secret_stuff; +-- id | data | authz_expr +------+-------------+---------------------------- +-- 2 | moar secret | label1|label2 +-- 3 | wat | label2 +-- 4 | win | label2 & (label3 | label4) +-- (3 rows) +``` + +## TODO + +* Make the caching feature configurable (strategy, size) +* Implement some benchmarks. +* Support for signed authorizations (JWT? Just raw signatures?) + +## License + +This project is licensed under both the Apache 2.0 license and the MIT license. See the `LICENSE_APACHE` and `LICENSE_MIT` files for details. + +## Contributions + +Contributions are welcome. Please open an issue or a pull request. \ No newline at end of file diff --git a/accumulo_access_pg.control b/accumulo_access_pg.control new file mode 100644 index 0000000..95a795f --- /dev/null +++ b/accumulo_access_pg.control @@ -0,0 +1,6 @@ +comment = 'accumulo_access_pg: Accumulo access expression parser and evaluator extension.' +default_version = '@CARGO_VERSION@' +module_pathname = '$libdir/accumulo_access_pg' +relocatable = false +superuser = true + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b68f29a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,75 @@ +// Copyright 2024 Lars Wilhelmsen . All rights reserved. +// Use of this source code is governed by the MIT or Apache-2.0 license that can be found in the LICENSE-MIT or LICENSE-APACHE files. + +use accumulo_access::caching::{authz_cache_stats, check_authorization_csv, clear_authz_cache}; +use pgrx::prelude::*; +use serde::{Deserialize, Serialize}; + +pg_module_magic!(); + +#[pg_extern] +fn sec_authz_check(expression: String, tokens: String) -> bool { + match check_authorization_csv(expression, tokens) { + Ok(result) => result, + Err(e) => { + let msg = format!("Error parsing expression: {}", e); + error!("{}", msg) + } + } +} + +#[derive(Serialize, Deserialize, PostgresType, Debug)] +pub struct SecAuthzCacheStats { + pub hits: u64, + pub misses: u64, + pub size: usize, +} + +#[pg_extern] +fn sec_authz_cache_stats() -> SecAuthzCacheStats { + match authz_cache_stats() { + Ok(stats) => SecAuthzCacheStats { hits: stats.hits, misses: stats.misses, size: stats.size}, + Err(e) => { + let msg = format!("Error getting cache stats: {}", e); + error!("{}", msg) + } + } +} + +#[pg_extern] +fn sec_authz_clear_cache() -> bool { + match clear_authz_cache() { + Ok(_) => true, + Err(e) => { + let msg = format!("Error clearing cache: {}", e); + error!("{}", msg) + } + } +} + +#[cfg(any(test, feature = "pg_test"))] +#[pg_schema] +mod tests { + use pgrx::prelude::*; + + #[pg_test] + fn test_accumulo_check_authorization() { + let expression = String::from("label1 & label5 & (label2 | \"label 🕺\")"); + let tokens = String::from("label1,label5,label 🕺"); + assert_eq!(true, crate::sec_authz_check(expression, tokens)); + } +} + +/// This module is required by `cargo pgrx test` invocations. +/// It must be visible at the root of your extension crate. +#[cfg(test)] +pub mod pg_test { + pub fn setup(_options: Vec<&str>) { + // perform one-off initialization when the pg_test framework starts + } + + pub fn postgresql_conf_options() -> Vec<&'static str> { + // return any postgresql.conf settings that are required for your tests + vec![] + } +}