biscuit-auth / biscuit-rust Goto Github PK
View Code? Open in Web Editor NEWRust implementation of the Biscuit authorization token
Home Page: https://www.biscuitsec.org
Rust implementation of the Biscuit authorization token
Home Page: https://www.biscuitsec.org
the "block_rules" sample is not executed properly:
https://github.com/biscuit-auth/biscuit-rust/actions/runs/3357118911/jobs/5562713872#step:6:83
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.
those operations are already implemented in datalog and in the printer, but not in the parser
it might be a good idea to do that
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.
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
The following datalog code does not produce a bar("d") fact.
foo(true);
bar("d") <- foo($b),
$b == true;
For info this one does:
foo(true);
bar("d") <- foo($b),
$b;
Instead of re-implementing analog trait in middlewares (to manage public keys rotation), I propose to make token::RootKeyProvider
public.
Top level and format level errors are not very clear, and we need proper Display usage
Currently, datalog is constructed from strings that are parsed at runtime. Integrating dynamic values in those strings can be done two ways:
.set()
on the fact (or rule, or check, or policy) to replace relevant variableslet 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:
I suggest a dedicated interpolation syntax instead, which:
Incidentally, that's what is done in the haskell library (for now parameter interpolation only works for compile-time parsing)
Param(String)
alternative in datalog::Term
(we don't need string interning here){paramName}
in the datalog syntax where it makes sense (currently, everywhere a term can appear, including inside set literals)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)Map<String, impl ToParamValue>
parameter to BiscuitBuilder
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
Is there any particular reason for using a biscuit.print()
rather than implementing the Display trait ?
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
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).
we want to avoid the case where the root key check is forgotten
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.
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)
What is the process in order to query the token for facts once the authorizer has authorized the token?
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.
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
this will simplify the API and the integration in wasm and Python
Prost is unstable in versions less then 6 , (nasty stack overflow)
Link to more info:
https://rust.firosolutions.com/paste/xdpM1L63yyjXcTT66xzP1XEvHNCg8V7dPnmTMrOSA4
Plz update your Cargo.toml file
make sure the C release automation takes it into account
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;
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.
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.
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
cat verifier <<EOF
allow if true;
EOF
biscuit verify --max-facts 50 --verifier ./verifier < ./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
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.
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):
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?Let me know what you think and thanks in advance!
Regards
The docs say that they are https://docs.rs/biscuit-auth/latest/biscuit_auth/#symbols-and-symbol-tables
But the blog post claims that they are not https://www.biscuitsec.org/blog/biscuit-2-0/#removing-the-symbol-type
Probably a docs bug.
the current benchmarks require nightly
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.
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.
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
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>
some panics should be replaced with proper parser errors
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
with the following case:
The verifier caveat will not run, because currently they are only run per block.
There are 2 options to fix this:
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
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
]
}
]
}
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
waiting on a curve25519-dalek update: dalek-cryptography/curve25519-dalek#338
(also review for any API differences)
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?
The biscuit spec mandates that arithmetic over/underflows trigger errors, but this is not checked in the samples, so it's not part of the conformance suite
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.
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?
currently it is on the check's or
operator, but not n ||
in expressions: biscuit-auth/biscuit#129
we should be able to prebuild for various platforms
RUSTSEC-2022-0093
has been issued on 2023-08-14, targeting a dependency of biscuit-rust
.
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.
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.
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:
biscuit-auth/src/format/mod.rs
line 463 in pub fn seal
. It is called on a KeyPair
generated by TokenNext.keypair()
, generated solely from a PrivateKey
. This call is safe.biscuit-auth/src/token/third_party.rs
line 143 in pub fn create_block
. It is called on a KeyPair
directly generated from a PrivateKey
. This call is safe.biscuit-auth/src/crypto/mod.rs
line 239 in pub fn sign
. This call takes a crypto::KeyPair
as an argument. crypto::KeyPair
is a type from biscuit_rust
that wraps a KeyPair
from ed25519-dalek
. So it is only safe if called with a safe KeyPair
value. Note that even if this is a pub fn
, it is in a module that is not exported by the library. crypto::sign
is called in several places.
biscuit-auth/src/format/mod.rs
line 342. It is called with a KeyPair
generated by TokenNext.keypair()
which is safebiscuit-auth/src/format/mod.rs
line 375. It is called with a KeyPair
generated by TokenNext.keypair()
which is safebiscuit-auth/src/format/mod.rs
line 308. It is called with a KeyPair
provided as an argument of SerializedBiscuit::new
which is a public API function. It is called in biscuit-auth/src/token/mod.rs
line 245, which is not part of a public API function, but is called in biscuit-auth/src/token/builder.rs
line 405, which itself is called in biscuit-auth/src/token/builder.rs
line 395 and indirectly line 387, both being part of public API functions.To summarize:
biscuit-rust
itself does not use the API unsafely;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.
biscuit-rust
v4.0.0crypto::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.
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.
this is confusing
chrono has a security vulnerability (RUSTSEC-2020-0159). It is also unmaintained, as the last release was in September 2020. To resolve this issue, chrono should be replaced with the time crate.
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!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.