Giter VIP home page Giter VIP logo

biscuit-rust's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

biscuit-rust's Issues

Rule Parsing Error

When testing the example code

  let mut v3 = biscuit2.verify(public_key)?;
  v3.add_resource("/a/file2.txt");
  v3.add_operation("read");
  v3.add_rule("allow if right(#authority, \"/a/file2.txt\", #read)");
  v1.deny();

I am getting this error
rule parsing error: Error(Error { input: "if right(#authority, \"/a/file2.txt\", #read)", code: Char, message: None })

What is wrong with the rule?
This code is part of the example for Rust

Where can I get more information about rule construction?

I would appreciate any help you can provide on this matter.

Return the matched query + variable bindings in the success type

It's an improvement i made in the haskell lib, which makes it convenient to extract data from the token for use by application code. This makes it easy to ensure that the token carries the needed info and avoid having failures happen after the auth check.

add an extract method

even if it is recommended to encode verifier caveats in the logic language, we should provide a way to query a token for information (like the list of revocation ids for all the blocks) and process it outside of the logic engine

Rework error hierarchy

Top level and format level errors are not very clear, and we need proper Display usage

Parameter interpolation in datalog blocks

Currently, datalog is constructed from strings that are parsed at runtime. Integrating dynamic values in those strings can be done two ways:

  1. string concatenation: this is heavily discouraged for security reasons. all the SQL injection issues can apply, and it requires great care with escaping / formatting data
  2. variable substitution: the trick is to put datalog variables as placeholders, and then to call .set() on the fact (or rule, or check, or policy) to replace relevant variables
let f = Fact::from_str("parent(\"alice\", $child)")?.set("child", "Bob")?;

While the option 2 prevents injections and security issues, it's not satisfying for a couple reasons:

  • it repurposes datalog variables into something they are not:
    • that can be confusing
    • that can lead to collisions between regular variable names and variable-only-really-used-for-interpolation names
  • it only works were variables can appear (that will be an issue with 3rd party blocks, where we will want to inject public keys in scope annotations)

I suggest a dedicated interpolation syntax instead, which:

  • makes it clear we're injecting values from the host language
  • avoids collisions between datalog variable names and parameter names
  • can appear in places variables can't
  • will play nice with a macro for compile-time-parsing

Incidentally, that's what is done in the haskell library (for now parameter interpolation only works for compile-time parsing)

Proposed solution

  • add a new Param(String) alternative in datalog::Term (we don't need string interning here)
  • recognize {paramName} in the datalog syntax where it makes sense (currently, everywhere a term can appear, including inside set literals)
  • either:
    • add a set(Map<String, impl ToParamValue>) function on BlockBuilder and AuthorizerBuilder that would walk the tree and perform replacements. The ToParamValue trait would allow directly passing regular rust values without having to wrap them in a Term manually (and would also, when 3rd party blocks are implemented, let the caller provide a public key where it makes sense)
    • add a Map<String, impl ToParamValue> parameter to BiscuitBuilder
  • check that all parameters have been replaced when the builder is used

Currently there are multiple ways to do the same things, when it comes to parsing datalog from strings. This feature could be the occasion to rationalize things a bit. Especially since this feature will be a first step towards compile-time parsing

Compile-time parsing of datalog blocks

This builds upon #66

Once we have a compelling solution for parameter substitution, the next step is to move parsing to compile-time, through a proc macro.

Here's how is could look like:

let b = block!(r"check if time($time), $time < {0};", now);
let a = authorizer(r"allow if {cond};", cond = true);

So something really close to println!(), but

  • without support for implicit parameter naming (either they are numbered, or they are named);
  • without support for formatting modifiers (here we're building an AST, so we can use values as is

I'm not sure about support for directly referring expressions withing curly braces, as is possible in println on rust 1.58+ (it might be hard to support).

It would work with standard rust values through TryInto<Term>.

For reference, biscuit-haskell does something quite similar (with direct slicing of variables in the datalog string instead of named parameters), and that provides both good DX (it removes the need for error checks at runtime) and perf (parsing is paid only once, at compile time).

Cleanup API

Some old parts of the API are still there and might not be useful anymore (they mix somewhat arbitrary high-level datalog idioms with library-level code).

I think they can be safely removed, or at least put in a separate module.

// in BlockBuilder
pub fn check_resource // imo this should be removed altogether
pub fn check_operation // imo this should be removed altogether
pub fn resource_prefix  // this should at least moved to a dedicated module, or removed
pub fn resource_suffix   // this should be at least moved to a dedicated model, or removed
pub fn expiration_date  // this could be moved to a dedicated module, rewritten using a datalog string, with the variable renamed to `$time` to use the default symbol table

// In Authorizer
pub fn allow // this should be moved to a dedicated module (and maybe renamed to `allow_all`)
pub fn deny // this should be moved to a dedicated module (and maybe renamed to `deny_all`)

// In Biscuit
pub fn create_block // this just calls `BlockBuilder::new()`. i think it's a bit misleading to have it a method on `Biscuit`, since
                    // it seems to imply that the builder is somehow linked to the biscuit when it isn't. 

One of the goals of datalog is to use common logic across languages, so i see using language-specific helpers as a bit of an anti-pattern, i'd rather have easy-to-read datalog than ad-hoc helpers. Parameter substitution solves the issue of injecting dynamic values, and compile-time parsing will solve the perf issue of using datalog-as-strings.

We should also provide a way to combine BlockBuilders: since the macro feature generates block builders from datalog code, it would be convenient when needing to create facts by iterating over vecs for instnace, while keeping a consistent high-level api.

Expressions: group feature

With the boolean operation, we'll soon need a grouping option like parenthesis for operation ordering.

for cases like:

$a > 12 and $b == 42 or $c != 0
($a > 12 and $b == 42) or $c != 0
$a > 12 and ($b == 42 or $c != 0)

Integration with serde

It would be baller if there was some fancy way to insert/extract facts using serde, at the moment it's kind of painful to manage facts.

`query_exactly_one()`

In many cases, we want the query to return exactly one result. A canonical example would be querying a user id from a token.

The current API makes it easier to discard extra results than to throw an error in the case of extra results.

I think it would be a good addition to have a query function that only returns a result when there exactly one match

CLI long execution time

Laptop spec:
Apple M1
RAM 16GO
macOS Ventura 13.0

The code below executes in ~90ms locally:

entity("entity:justine");

organization_parent("mof", "mof:tax", false, false);
organization_parent("mof:tax", "mof:tax:auditor", false, false);
organization_parent("mof:tax:auditor", "entity:justine", true, true);
organization_parent("e&y", "e&y:auditor", false, false);
organization_parent("e&y:auditor", "entity:justine", true, true);

policy($id, $organization, $resource, $action, $policy_type) <-
  raw_policy($id, $organization, $resource, $action, $policy_type),
  ["mof", "e&y"].contains($organization) || $policy_type == "deny";

policy($child_id, $child, $child_resource, $child_action, "allow") <- 
  organization_parent($parent, $child, $propagate_allow, $propagate_deny),
  policy($parent_id, $parent, $parent_resource, $parent_action, "allow"),
  raw_policy($child_id, $child, $child_resource, $child_action, "allow"),
  $child_resource.matches("^" + $parent_resource + "(\\w|:)*"),
  $child_action.matches("^" + $parent_action + "(\\w|:)*");

policy($id, $child, $parent_resource, $parent_action, $policy_type) <- 
  organization_parent($parent, $child, $propagate_allow, $propagate_deny),
  policy($id, $parent, $parent_resource, $parent_action, $policy_type),
  $propagate_allow && $policy_type == "allow" ||
  $propagate_deny && $policy_type == "deny";

allow($id, $operation_resource, $operation_action) <- 
  entity($user),
  policy($id, $user, $policy_resource, $policy_action, "allow"),
  operation($operation_resource, $operation_action),
  $operation_resource.matches("^" + $policy_resource + "(\\w|:)*"),
  $operation_action.matches("^" + $policy_action + "(\\w|:)*");

deny($id, $operation_resource, $operation_action) <- 
  entity($user),
  policy($id, $user, $policy_resource, $policy_action, "deny"),
  operation($operation_resource, $operation_action),
  $operation_resource.matches("^" + $policy_resource + "(\\w|:)*"),
  $operation_action.matches("^" + $policy_action + "(\\w|:)*");

raw_policy(1, "mof", "bucket:eu", "bucket:create", "allow");

raw_policy(2, "mof", "bucket:eu:fr1:f15ec67", "bucket:file_system", "allow");
raw_policy(2, "mof", "bucket:eu:fr2:66d925a", "bucket:file_system", "allow");
raw_policy(2, "mof", "bucket:eu:ger3:9f870d4", "bucket:file_system", "allow");

raw_policy(11, "mof:tax", 
  "bucket:eu:fr1:f15ec67:enterprises:financial_reports", "bucket:file_system:read", "allow");
raw_policy(11, "mof:tax", 
  "bucket:eu:fr2:66d925a:enterprises:financial_reports", "bucket:file_system:read", "allow");
raw_policy(11, "mof:tax", 
  "bucket:eu:ger3:9f870d4:enterprises:financial_reports", "bucket:file_system:read", "allow");
 
raw_policy(12, "mof:tax",
  "bucket:eu:fr1:f15ec67:tax", "bucket:file_system", "allow");
raw_policy(12, "mof:tax",
  "bucket:eu:fr2:66d925a:tax", "bucket:file_system", "allow");
raw_policy(12, "mof:tax",
  "bucket:eu:ger3:9f870d4:tax", "bucket:file_system", "allow");

raw_policy(13, "mof:tax:auditor", 
  "bucket:eu:fr1:f15ec67:enterprises:financial_reports", "bucket:file_system:read", "allow");
raw_policy(13, "mof:tax:auditor", 
  "bucket:eu:fr2:66d925a:enterprises:financial_reports", "bucket:file_system:read", "allow");
raw_policy(13, "mof:tax:auditor", 
  "bucket:eu:ger3:9f870d4:enterprises:financial_reports", "bucket:file_system:read", "allow");

raw_policy(14, "mof:tax:auditor",
  "bucket:eu:fr1:f15ec67:tax:audit_report", "bucket:file_system", "allow");
raw_policy(14, "mof:tax:auditor:audit_report",
  "bucket:eu:fr2:66d925a:tax:audit_report", "bucket:file_system", "allow");
raw_policy(14, "mof:tax:auditor:audit_report",
  "bucket:eu:ger3:9f870d4:tax:audit_report", "bucket:file_system", "allow");

raw_policy(15, "entity:justine", 
  "bucket:eu:ger3:9f870d4:enterprises:financial_reports:eti:airbus", "bucket:file_system:read", "deny");

raw_policy(16, "entity:justine", 
  "bucket:eu:ger3:9f870d4:tax:audit_report:eti:airbus", "bucket:file_system", "deny");

operation(
  "bucket:eu:fr1:f15ec67:enterprises:financial_reports:tpe:boulangerie:2022:bilan.pdf", 
  "bucket:file_system:read:file");

operation(
  "bucket:eu:fr2:66d925a:enterprises:financial_reports:pme:polypack:2022:bilan.pdf", 
  "bucket:file_system:read:file");

operation(
  "bucket:eu:ger3:9f870d4:enterprises:financial_reports:eti:airbus:2022:bilan.pdf", 
  "bucket:file_system:read:file");

check if allow($id, $resource, $action),
  $resource == "bucket:eu:ger3:9f870d4:enterprises:financial_reports:eti:airbus:2022:bilan.pdf",
  $action == "bucket:file_system:read:file";

check if allow($id, $resource, $action),
  $resource == "bucket:eu:fr1:f15ec67:enterprises:financial_reports:tpe:boulangerie:2022:bilan.pdf",
  $action == "bucket:file_system:read:file";

check if allow($id, $resource, $action),
  $resource == "bucket:eu:fr2:66d925a:enterprises:financial_reports:pme:polypack:2022:bilan.pdf",
  $action == "bucket:file_system:read:file";

deny if deny($id, $resource, $action);

allow if true;

Support heterogeneous sets

It is possible to create a token containing heterogeneous sets (through parameters). Such a token can be serialized and parsed into a Biscuit value without errors (since the protobuf -> datalog parsing only happens later).

biscuit-auth/biscuit#140 makes a case for allowing heterogeneous sets and gives a bit more context.

Biscuit CLI

Since https://github.com/CleverCloud/biscuit-rust is the canonical implementation of biscuit, it would make sense for it to harbor a canonical CLI tool, allowing people to manage biscuits.

Here are a couple ideas based on my experience with macaroons at my current job.

I think the default format for biscuits should be base64, with a --raw flag for raw binary.

Inspecting a biscuit

The goal here is to display contents in a human-readable manner (ideally with a syntax that could be used as-is with libs: for instance, facts, rules and checks separated with ;\n.

An extra --check-signature option would allow to verify the signature, with a provided public key.

An issue with the equivalent feature for macaroons is that displaying caveats + signature is equivalent to displaying the macaroon itself. People are careful not sharing a token, as they correctly understand it's sensitive info, but i've seen people share caveats + signatures without realizing it was equivalent to sharing the token. So here, inspect should not display the whole signature (or anything allowing to reconstruct the biscuit) without an explicit flag.

biscuit inspect <<< $BISCUIT

biscuit inspect --check-signature $PUBLIC_KEY <<< $BISCUIT

Verifying a biscuit

cat verifier <<EOF
allow if true;
EOF
biscuit verify --max-facts 50 --verifier ./verifier < ./biscuit

Generating a biscuit

The rationale for reading the private key from an env var is to avoid having it in the command history or in a clear text file

BISCUIT_PRIVATE_KEY=$(get the private key from a secure storage) biscuit generate << EOF
operation(#authority, #read);
check if current_time(#ambient, $time), $time < 2021-05-15T20:18:52+02:00;
EOF

Adding a block

This could be something close to biscuit-generate (without the private key). I'm not sure which of the biscuit or the block contents should read from stdin and which should be read from an argument or a file.

Guide request: simple authentication with biscuit

Hello people, I would like to request a guide on a simple authentication service (à la django).
I want to try biscuit in a personal project, but being no expert in security I fear doing something wrong.
I think it would also be good for adoption to have a simple example for people to tinker with it, the RBAC guide seems to be useful for a bigger project.

Some doubts I have (sorry for my ignorance):

  • can I use a biscuit token as part of the confirmation email after registration?
  • in the datalog lang, would the roles replace something like the more traditional groups? Could we have some kind of comparison? what would be a simple role to start with and how to extend it later?
  • if started with a monolith, how easy is to transition to microservices with biscuit? how would it work?

Let me know what you think and thanks in advance!
Regards

Parsing chained method calls

The following doesn't parse

[1].intersection([1]).contains(1);

The following parses:

([1].intersection([1])).contains(1);

i have noticed the same issue in biscuit haskell, so i suspect it's related to how the parsers are written. i'm not sure yet how hard it would be to fix (to make chained methods left-associative instead of right-associative). In any case, the Display implementation on expressions correctly parenthesizes the expression (since it is not aware of precedence rules, it adds parens everywhere, to be sure).

So it's not something someone could copy from generated datalog code, but it's still something that could be expected to work.

Querying not functioning

I create a token like so:

builder
    .add_authority_fact(&format!("user(\"{}\")", user.user_uuid) as &str)
    .unwrap();

and when I later try to query it:

#[derive(Debug)]
pub struct StringWrap(String);

impl From<biscuit_auth::builder::Fact> for StringWrap {
    fn from(b: biscuit_auth::builder::Fact) -> Self {
        StringWrap(format!("{}", b))
    }
}

let users: Vec<StringWrap> = b
                    .authorizer()
                    .unwrap()
                    .query("data($id) <- user($id)")
                    .unwrap();

dbg!(&users);

I always receive an empty array:

[src/lib.rs:70] &users = []

but in the Symbols of the biscuit, it seems that it does properly contain it:

symbols: [
            "authority",
            "ambient",
            "resource",
            "operation",
            "right",
            "current_time",
            "revocation_id",
            "user",
            "5f85453c-227c-4219-bbbe-f2a27895e7cc",
        ],

Also it seems that there is nothing that implements TryFrom<Fact>, which is why I added the StringWrap struct.

Expressions: prevent panics & overflows

Math operations needs to be checked for invalid operations (such as Div by zero) or potential overflow / underflow. In such case, the expression must returns an error

Expression: Negate: drop int support

Integer support for the unary Negate operation can probably be dropped, as integers are already signed and can be multiplied by -1 to negate them.

On the parser side, it only accept negating integers right now (parsing "-" <int>). The "-" sign should probably be part of a parsed integer instead of the negate operation, and the operation could then parse a boolean negation like "!" <bool>

update Authorizer::save and Authorizer::dump

the introduction of scopes made things more complicated for those methods, and they have not been updated yet. They should export facts, rule and checks with clear indication of origin and scope

API issue: authority level verifier caveats

with the following case:

  • a token with only the authority block
  • a verifier caveat that checks for authority facts only

The verifier caveat will not run, because currently they are only run per block.

There are 2 options to fix this:

  • disallow tokens with only the authority block (not very elegant, and we might want to use the token with only the authority, which would work basically like a JWT)
  • support authority level caveats in the verifier

The second option looks more useful, but the API should make it very clear which caveats apply where. We could also have the verifier analyse the caveats and promote some of them to authority caveats if they only check authority level facts

samples validation failure

when running the samples generation with the `--test option, I get different keys than what was displayed at creation. This does not change the result of the validation though, so probably an issue when printing the keys

< left / > right
 Biscuit {
     symbols: []
     public keys: ["
<a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
>229697541ba393f2a30559ef12b6d7d2403c8d9fe9b8164024894b5cc58638c9
 "]
     authority: Block {
             symbols: []
             version: 4
             context: ""
             external key: 
             public keys: ["
<a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
>229697541ba393f2a30559ef12b6d7d2403c8d9fe9b8164024894b5cc58638c9
 "]
             scopes: []
             facts: [
                 right("read")
             ]
             rules: []
             checks: [
                 check if group("admin") trusting ed25519/
<a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
>229697541ba393f2a30559ef12b6d7d2403c8d9fe9b8164024894b5cc58638c9
 
             ]
         }
     blocks: [
         Block {
             symbols: []
             version: 4
             context: ""
             external key: 
<a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
>229697541ba393f2a30559ef12b6d7d2403c8d9fe9b8164024894b5cc58638c9
 
             public keys: []
             scopes: []
             facts: [
                 group("admin")
             ]
             rules: []
             checks: [
                 check if right("read")
             ]
         }
     ]
 }

< left / > right
 Biscuit {
     symbols: []
     public keys: ["
<3c8aeced6363b8a862552fb2b0b4b8b0f8244e8cef3c11c3e55fd553f3a90f59", "ecfb8ed11fd9e6be133ca4dd8d229d39c7dcb2d659704c39e82fd7acf0d12dee", "2e0118e63beb7731dab5119280ddb117234d0cdc41b7dd5dc4241bcbbb585d14
>a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25", "b7e2c7cea042431f9e7e0e0decd8503d58569330e6ed6eaa13187f518102a284", "3c1c4fa6c463ba8fb4ab60ec907d0282425d1e6c2e153df941fb917cfb877c2b
 "]
     authority: Block {
             symbols: []
             version: 4
             context: ""
             external key: 
             public keys: ["
<3c8aeced6363b8a862552fb2b0b4b8b0f8244e8cef3c11c3e55fd553f3a90f59
>a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
 "]
             scopes: []
             facts: [
                 query(0)
             ]
             rules: []
             checks: [
                 check if true trusting previous, ed25519/
<3c8aeced6363b8a862552fb2b0b4b8b0f8244e8cef3c11c3e55fd553f3a90f59
>a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
 
             ]
         }
     blocks: [
         Block {
             symbols: []
             version: 4
             context: ""
             external key: 
<3c8aeced6363b8a862552fb2b0b4b8b0f8244e8cef3c11c3e55fd553f3a90f59
<            public keys: ["ecfb8ed11fd9e6be133ca4dd8d229d39c7dcb2d659704c39e82fd7acf0d12dee
>a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
>            public keys: ["b7e2c7cea042431f9e7e0e0decd8503d58569330e6ed6eaa13187f518102a284
 "]
             scopes: []
             facts: [
                 query(1)
             ]
             rules: [
                 query(1, 2) <- query(1), query(2) trusting ed25519/
<ecfb8ed11fd9e6be133ca4dd8d229d39c7dcb2d659704c39e82fd7acf0d12dee
>b7e2c7cea042431f9e7e0e0decd8503d58569330e6ed6eaa13187f518102a284
 
             ]
             checks: [
                 check if query(2), query(3) trusting ed25519/
<ecfb8ed11fd9e6be133ca4dd8d229d39c7dcb2d659704c39e82fd7acf0d12dee,
<                check if query(1) trusting ed25519/3c8aeced6363b8a862552fb2b0b4b8b0f8244e8cef3c11c3e55fd553f3a90f59
>b7e2c7cea042431f9e7e0e0decd8503d58569330e6ed6eaa13187f518102a284,
>                check if query(1) trusting ed25519/a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
 
             ]
         },
 	Block {
             symbols: []
             version: 4
             context: ""
             external key: 
<ecfb8ed11fd9e6be133ca4dd8d229d39c7dcb2d659704c39e82fd7acf0d12dee
>b7e2c7cea042431f9e7e0e0decd8503d58569330e6ed6eaa13187f518102a284
 
             public keys: []
             scopes: []
             facts: [
                 query(2)
             ]
             rules: []
             checks: [
                 check if query(2), query(3) trusting ed25519/
<ecfb8ed11fd9e6be133ca4dd8d229d39c7dcb2d659704c39e82fd7acf0d12dee,
<                check if query(1) trusting ed25519/3c8aeced6363b8a862552fb2b0b4b8b0f8244e8cef3c11c3e55fd553f3a90f59
>b7e2c7cea042431f9e7e0e0decd8503d58569330e6ed6eaa13187f518102a284,
>                check if query(1) trusting ed25519/a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
 
             ]
         },
 	Block {
             symbols: []
             version: 4
             context: ""
             external key: 
<ecfb8ed11fd9e6be133ca4dd8d229d39c7dcb2d659704c39e82fd7acf0d12dee
>b7e2c7cea042431f9e7e0e0decd8503d58569330e6ed6eaa13187f518102a284
 
             public keys: []
             scopes: []
             facts: [
                 query(3)
             ]
             rules: []
             checks: [
                 check if query(2), query(3) trusting ed25519/
<ecfb8ed11fd9e6be133ca4dd8d229d39c7dcb2d659704c39e82fd7acf0d12dee,
<                check if query(1) trusting ed25519/3c8aeced6363b8a862552fb2b0b4b8b0f8244e8cef3c11c3e55fd553f3a90f59
>b7e2c7cea042431f9e7e0e0decd8503d58569330e6ed6eaa13187f518102a284,
>                check if query(1) trusting ed25519/a424157b8c00c25214ea39894bf395650d88426147679a9dd43a64d65ae5bc25
 
             ]
         },
 	Block {
             symbols: []
             version: 4
             context: ""
             external key: 
             public keys: ["
<2e0118e63beb7731dab5119280ddb117234d0cdc41b7dd5dc4241bcbbb585d14
>3c1c4fa6c463ba8fb4ab60ec907d0282425d1e6c2e153df941fb917cfb877c2b
 "]
             scopes: []
             facts: [
                 query(4)
             ]
             rules: []
             checks: [
                 check if query(2) trusting ed25519/
<ecfb8ed11fd9e6be133ca4dd8d229d39c7dcb2d659704c39e82fd7acf0d12dee,
<                check if query(4) trusting ed25519/2e0118e63beb7731dab5119280ddb117234d0cdc41b7dd5dc4241bcbbb585d14
>b7e2c7cea042431f9e7e0e0decd8503d58569330e6ed6eaa13187f518102a284,
>                check if query(4) trusting ed25519/3c1c4fa6c463ba8fb4ab60ec907d0282425d1e6c2e153df941fb917cfb877c2b
 
             ]
         }
     ]
 }

Non-deterministic output of `Authorizer.dump_code()`

Facts are stored as a HashMap internally, so their order is not defined.

The Display impl for Authorizer does sort everything, so it has a stable output. It also adds more information in comments (namely the facts and rules origins, which is extremely important).

I guess we could just call .to_string() in dump_code() to keep backwards compatibility. However, the Display implementation for Authorizer only uses facts and rules from the Datalog::World value, so it may discard facts and rules added to the authorizer before calling authorize. I guess that overlaps with #192

Was BiscuitBuilder.build consuming itself intentional?

First off, thanks so much for building this library! It's been a huge boon.

I've been experimenting with writing a Python wrapper for biscuit-rust, but hit a thing I'm not sure was intentional or not. I'm definitely not as experienced in Rust as y'all are, so this is an earnest, gentle query.

Let me give some context first.

Because of Python's GC, we have to get a lil tricky with lifetime annotations. It's currently typical to have to do magic with ouroboros::self_reference like I have in biscuit-auth-python (and folks elsewhere have in crfs, etc) to get shared references to Rust objects like BiscuitBuilder that are contained in Python objects.

However, that's exposed the fact that the BiscuitBuilder.build method, as currently written, can't work on a shared reference, even a mutable one. In my Python lib, we have to throw a clone in to make things work

I expected a BiscuitBuilder to more like other builder patterned code and for it to be reused, to copy the data into a new Biscuit, and to not modify its own SymbolTable. Given the lifetime annotation work on BiscuitBuilder, I wondered: is it intentional to have it give up ownership of its fields to the Biscuit and modify the SymbolTable?

The modifying of SymbolTable was surprising on its own (split_at uses split_off which removes items from the Vec its called on). That made me wonder if perhaps there was something about BiscuitBuilder's purpose I misunderstood? Is there something about building Biscuits with partial views of a SymbolTable that's useful?

Return both policy and check errors in the error type

Currently, all the check errors are collected, but policies are tried only if there is no check error.
This makes debugging auth errors a bit harder than necessary, and does not provide meaningful perf improvements (the authorizer datalog has to be evaluated before checking block checks anyway).

I think a fine default is to run all checks and the policy list as well. A perf improvement would be to early abort evaluation after each block.

converting Rust errors to the C API

right now the C API has a crude way of representing errors, it only converts to an error code, but we might need access to the fields instead (example: display parse errors).
Can we figure out a good API for that?

`RUSTSEC-2022-0093` security advisory

RUSTSEC-2022-0093 has been issued on 2023-08-14, targeting a dependency of biscuit-rust.

tl;dr:

Upgrade biscuit-rust to v4.0.0 as soon as possible, and audit your use of biscuit creation functions (biscuit sealing and attenuation are not vulnerable). biscuit-rust itself does not present unsafe API use, but it does not prevent downstream code from providing bogus biscuit_auth::crypto::KeyPair values. Such values can only be created through safe APIs, but they can still be mutated into unsafe values. Idiomatic use of the biscuit library is safe, but it is still theorically possible to trigger unsafe use by manually mutating a safe KeyPair into an unsafe one.

Explanation

The advisory targets ed25519-dalek v1.x. The vulnerability is due to an unsafe API exposed by the library: signing operations took as input a public key and a private key that could be separately assembled, even though the public key is derived from the secret key. Providing inconsistent public and private keys can leak data about the private key.

ed25519-dalek v2.0.0 has been released on 2023-08-11 and fixes the issue by providing a safe API. Biscuit-rust has a PR migrating to ed25519-dalek v2: #136 this PR has been opened several months ago, targetting release candidates of ed25519-dalek v2. Now that v2 has been released, we'll soon merge this PR and issue a major biscuit-rust release (the major bump is required because of a coupled bump for rand, required by ed25519-dalek v2).

Now, even though the advisory was released only a few days ago, there was general awareness of the unsafe API of ed25519 libraries in several languages: https://github.com/MystenLabs/ed25519-unsafe-libs As soon as awareness of the API misuse risk spread, an informal audit of the biscuit-rust codebase was conducted, concluding that there was no API misuse in biscuit-rust (use with a keypair constructed from user-supplied public and private keys). However, several functions from the public API directly take a KeyPair argument which is ultimately used to sign content, so unsafe use is still possible by code depending on biscuit-rust. That's why we advise upgrading biscuit-rust as soon as possible.

Code audit

ed25519::KeyPair::try_sign is the dangerous function: called on a KeyPair value with a private key and an attacker-crafted public key, it can leak information about the private key. It is called in three places:

To summarize:

  • biscuit-rust itself does not use the API unsafely;
  • but biscuit-rust still allows indirect unsafe API use by downstream libraries, specifically in:
    • BiscuitBuilder::new()
    • BiscuitBuilder::new_with_symbols()
    • BiscuitBuilder::new_with_rng()
    • SerializedBiscuit::new()

All those functions take crypto::KeyPair values that can only be built through safe APIs. Such safely-built values can still be mutated by swapping the wrapped ed25519::KeyPair value with an arbitrary one with no safeness guaranteed. Doing so allows unsafe API use.

Mitigation in biscuit-rust v4.0.0

crypto::KeyPair now wraps a ed25519::SigningKey value which is always safe to use for signing operations. This is technically a breaking change on a public API, so a major update is needed, even though typical use cases won't be affected.

Detect free variables in expressions

Currently, free variables (ie variables that are not bound: not mentioned in any predicate of the body) are detected at parse time in the rules's head.

Free variables are not detected in expressions. Currently, all expressions are evaluated strictly, so a free variable in an expression always results in an error upon rule evaluation. Even with lazy evaluation for booleans, free variables don't make sense.

Can't query facts added with authorizer_merge!

Below an example showing the issue:

use biscuit_auth::{macros::*, KeyPair};

fn main() {
    let mut authorizer = biscuit!(r#"fact("foo");"#)
        .build(&KeyPair::new())
        .unwrap()
        .authorizer()
        .unwrap();

    authorizer_merge!(&mut authorizer, r#"fact("bar");"#);

    let query_facts: Vec<(String,)> = authorizer.query(rule!("x($n) <- fact($n)")).unwrap();
    let (dumped_facts, _, _, _) = authorizer.dump();
    // [Fact { predicate: Predicate { name: "fact", terms: [Str("foo")] }, parameters: None },
    // Fact { predicate: Predicate { name: "fact", terms: [Str("bar")] }, parameters: Some({}) }]
    println!("{:?}", dumped_facts);

    // panic here!
    assert!(query_facts.len() == 2);
}

It looks like query don't lookup on facts added with authorizer_merge!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.