Crates.io | biscuit-auth |
lib.rs | biscuit-auth |
version | 5.0.0 |
source | src |
created_at | 2019-11-26 12:27:14.073089 |
updated_at | 2024-07-31 11:25:07.547357 |
description | an authorization token with decentralized verification and offline attenuation |
homepage | https://github.com/biscuit-auth/biscuit |
repository | https://github.com/biscuit-auth/biscuit-rust |
max_upload_size | |
id | 184471 |
size | 759,539 |
Biscuit is an authorization token for microservices architectures with the following properties:
Non goals:
In this example we will see how we can create a token, add some checks, serialize and deserialize a token, append more checks, and validate those checks in the context of a request:
extern crate biscuit_auth as biscuit;
use biscuit::{KeyPair, Biscuit, error};
fn main() -> Result<(), error::Token> {
// let's generate the root key pair. The root public key will be necessary
// to verify the token
let root = KeyPair::new();
let public_key = root.public();
// creating a first token
let token1 = {
// the first block of the token is the authority block. It contains global
// information like which operation types are available
let biscuit = biscuit!(r#"
right("/a/file1.txt", "read");
right("/a/file1.txt", "write");
right("/a/file2.txt", "read");
right("/b/file3.txt", "write");
"#).build(&root)?; // the first block is signed
println!("biscuit (authority): {}", biscuit);
biscuit.to_vec()?
};
// this token is only 258 bytes, holding the authority data and the signature
assert_eq!(token1.len(), 258);
// now let's add some restrictions to this token
// we want to limit access to `/a/file1.txt` and to read operations
let token2 = {
// the token is deserialized, the signature is verified
let deser = Biscuit::from(&token1, root.public())?;
// biscuits can be attenuated by appending checks
let biscuit = deser.append(block!(r#"
// checks are implemented as logic rules. If the rule produces something,
// the check is successful
// here we verify the presence of a `resource` fact with a path set to "/a/file1.txt"
// and a read operation
check if resource("/a/file1.txt"), operation("read");
"#));
println!("biscuit (authority): {}", biscuit);
biscuit.to_vec()?
};
// this new token fits in 400 bytes
assert_eq!(token2.len(), 400);
/************** VERIFICATION ****************/
// let's deserialize the token:
let biscuit2 = Biscuit::from(&token2, public_key)?;
// let's define 3 verifiers (corresponding to 3 different requests):
// - one for /a/file1.txt and a read operation
// - one for /a/file1.txt and a write operation
// - one for /a/file2.txt and a read operation
let v1 = authorizer!(r#"
resource("/a/file1.txt");
operation("read");
// a verifier can come with allow/deny policies. While checks are all tested
// and must all succeeed, allow/deny policies are tried one by one in order,
// and we stop verification on the first that matches
//
// here we will check that the token has the corresponding right
allow if right("/a/file1.txt", "read");
// explicit catch-all deny. here it is not necessary: if no policy
// matches, a default deny applies
deny if true;
"#);
let mut v2 = authorizer!(r#"
resource("/a/file1.txt");
operation("write");
allow if right("/a/file1.txt", "write");
"#);
let mut v3 = authorizer!(r#"
resource("/a/file2.txt");
operation("read");
allow if right("/a/file2.txt", "read");
"#);
// the token restricts to read operations:
assert!(biscuit.authorize(&v1).is_ok());
// the second verifier requested a read operation
assert!(biscuit.authorize(&v2).is_err());
// the third verifier requests /a/file2.txt
assert!(biscuit.authorize(&v3).is_err());
Ok(())
}
A Biscuit token is made with a list of blocks defining data and checks that must be validated upon reception with a request. Any failed check will invalidate the entire token.
If you hold a valid token, it is possible to add a new block to restrict further the token, like limiting access to one particular resource, or adding a short expiration date. This will generate a new, valid token. This can be done offline, without asking the original token creator.
On the other hand, if a block is modified or removed, the token will fail the cryptographic signature verification.
Biscuit tokens get inspiration from macaroons and JSON Web Tokens, reproducing useful features from both:
We rely on a modified version of Datalog, that can represent complex behaviours in a compact form, and add flexible constraints on data.
Here are examples of checks that can be implemented with that language:
Like Datalog, this language is based around facts and rules, but with some slight modifications:
A check rule requires the presence of one or more facts, and can have additional expressions on these facts. It is possible to create rules like these ones:
To reduce the size of tokens, the format holds a symbol table containing strings. Any string is then serialized as an index into this table.
They can be used for pretty printing of a fact or rule. As an example, with a table containing ["resource", "operation", "read", "rule1", "file.txt"], we could have the following rule: #4 <- #0(#5) & #1(#2)
that would be printed as rule1 <- resource("file.txt"), operation("read")
biscuit implementations come with a default symbol table to avoid transmitting frequent values with every token.
This project can generate C bindings with cargo-c.
compile it with:
cargo cinstall --prefix=/usr --destdir=./build
Run C integration tests with:
cargo ctest
Licensed under Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.