biscuit_auth/lib.rs
1//! Biscuit authentication and authorization token
2//!
3//! Biscuit is an authorization token for microservices architectures with the following properties:
4//!
5//! * decentralized validation: any node could validate the token only with public information;
6//! * offline delegation: a new, valid token can be created from another one by attenuating its rights, by its holder, without communicating with anyone;
7//! * capabilities based: authorization in microservices should be tied to rights related to the request, instead of relying to an identity that might not make sense to the authorizer;
8//! * flexible rights managements: the token uses a logic language to specify attenuation and add bounds on ambient data;
9//! * small enough to fit anywhere (cookies, etc).
10//!
11//! Non goals:
12//!
13//! * This is not a new authentication protocol. Biscuit tokens can be used as opaque tokens delivered by other systems such as OAuth.
14//! * Revocation: while tokens come with expiration dates, revocation requires external state management.
15//!
16//! # Usage
17//!
18//! Most of the interaction with this library is done through the
19//! [Biscuit](`crate::token::Biscuit`) structure, that represents a valid
20//! token, and the [Authorizer](`crate::token::authorizer::Authorizer`), used to
21//! check authorization policies on a token.
22//!
23//! In this example we will see how we can create a token, add some checks,
24//! serialize and deserialize a token, append more checks, and validate
25//! those checks in the context of a request:
26//!
27//! ```rust
28//! extern crate biscuit_auth as biscuit;
29//!
30//! use biscuit::{KeyPair, Biscuit, Authorizer, builder::*, error, macros::*};
31//!
32//! fn main() -> Result<(), error::Token> {
33//! // let's generate the root key pair. The root public key will be necessary
34//! // to verify the token
35//! let root = KeyPair::new();
36//! let public_key = root.public();
37//!
38//! // creating a first token
39//! let token1 = {
40//! // the first block of the token is the authority block. It contains global
41//! // information like which operation types are available
42//! let biscuit = biscuit!(r#"
43//! right("/a/file1.txt", "read");
44//! right("/a/file1.txt", "write");
45//! right("/a/file2.txt", "read");
46//! right("/b/file3.txt", "write");
47//! "#)
48//! .build(&root)?; // the first block is signed
49//!
50//! println!("biscuit (authority): {}", biscuit);
51//!
52//! biscuit.to_vec()?
53//! };
54//!
55//! // this token is only 249 bytes, holding the authority data and the signature
56//! assert_eq!(token1.len(), 249);
57//!
58//! // now let's add some restrictions to this token
59//! // we want to limit access to `/a/file1.txt` and to read operations
60//! let token2 = {
61//! // the token is deserialized, the signature is verified
62//! let deser = Biscuit::from(&token1, root.public())?;
63//!
64//! // biscuits can be attenuated by appending checks
65//! let biscuit = deser.append(block!(r#"
66//! // checks are implemented as logic rules. If the rule produces something,
67//! // the check is successful
68//! // here we verify the presence of a `resource` fact with a path set to "/a/file1.txt"
69//! // and a read operation
70//! check if resource("/a/file1.txt"), operation("read");
71//! "#))?;
72//!
73//! println!("biscuit (authority): {}", biscuit);
74//!
75//! biscuit.to_vec()?
76//! };
77//!
78//! // this new token fits in 385 bytes
79//! assert_eq!(token2.len(), 385);
80//!
81//! /************** VERIFICATION ****************/
82//!
83//! // let's deserialize the token:
84//! let biscuit2 = Biscuit::from(&token2, root.public())?;
85//!
86//! // let's define 3 authorizers (corresponding to 3 different requests):
87//! // - one for /a/file1.txt and a read operation
88//! // - one for /a/file1.txt and a write operation
89//! // - one for /a/file2.txt and a read operation
90//!
91//! let v1 = authorizer!(r#"
92//! resource("/a/file1.txt");
93//! operation("read");
94//!
95//! // a verifier can come with allow/deny policies. While checks are all tested
96//! // and must all succeeed, allow/deny policies are tried one by one in order,
97//! // and we stop verification on the first that matches
98//! //
99//! // here we will check that the token has the corresponding right
100//! allow if right("/a/file1.txt", "read");
101//! // explicit catch-all deny. here it is not necessary: if no policy
102//! // matches, a default deny applies
103//! deny if true;
104//! "#);
105//!
106//! let mut v2 = authorizer!(r#"
107//! resource("/a/file1.txt");
108//! operation("write");
109//! allow if right("/a/file1.txt", "write");
110//! "#);
111//!
112//! let mut v3 = authorizer!(r#"
113//! resource("/a/file2.txt");
114//! operation("read");
115//! allow if right("/a/file2.txt", "read");
116//! "#);
117//!
118//! // the token restricts to read operations:
119//! assert!(biscuit2.authorize(&v1).is_ok());
120//! // the second verifier requested a read operation
121//! assert!(biscuit2.authorize(&v2).is_err());
122//! // the third verifier requests /a/file2.txt
123//! assert!(biscuit2.authorize(&v3).is_err());
124//!
125//! Ok(())
126//! }
127//! ```
128//!
129//! # Concepts
130//!
131//! ## blocks
132//!
133//! A Biscuit token is made with a list of blocks defining data and checks that
134//! must be validated upon reception with a request. Any failed check will invalidate
135//! the entire token.
136//!
137//! If you hold a valid token, it is possible to add a new block to restrict further
138//! the token, like limiting access to one particular resource, or adding a short
139//! expiration date. This will generate a new, valid token. This can be done offline,
140//! without asking the original token creator.
141//!
142//! On the other hand, if a block is modified or removed, the token will fail the
143//! cryptographic signature verification.
144//!
145//! ## Cryptography
146//!
147//! Biscuit tokens get inspiration from macaroons and JSON Web Tokens, reproducing
148//! useful features from both:
149//!
150//! - offline delegation like macaroons
151//! - based on public key cryptography like JWT, so any application holding the root public key can verify a token (while macaroons are based on a root shared secret)
152//!
153//! ## A logic language for authorization policies: Datalog with constraints
154//!
155//! We rely on a modified version of Datalog, that can represent complex behaviours
156//! in a compact form, and add flexible constraints on data.
157//!
158//! Here are examples of checks that can be implemented with that language:
159//!
160//! - valid if the requested resource is "file.txt" and the operation is "read"
161//! - valid if current time is before January 1st 2030, 00h00mn00s UTC
162//! - source IP is in set [1.2.3.4, 5.6.7.8]
163//! - resource matches prefix "/home/biscuit/data/"
164//!
165//! But it can also combine into more complex patterns, like: right is read *if*
166//! user has read *or* user is member of organisation and organisation has read right
167//! *or* other user with read right has delegated to user.
168//!
169//! Like Datalog, this language is based around facts and rules, but with some
170//! slight modifications: a block's rules and checks can only apply to facts
171//! from the current or previous blocks. The authorizer executes its checks and
172//! policies in the context of the first block. This allows Biscuit to carry
173//! basic rights in the first block while preventing later blocks from
174//! inreasing the token's rights.
175//!
176//! ### Checks
177//!
178//! A check requires the presence of one or more facts, and can have additional
179//! constraints on these facts (the constraints are implemented separately to simplify
180//! the language implementation: among other things, it avoids implementing negation).
181//! It is possible to create rules like these ones:
182//!
183//! - `check if resource("file1")`
184//! - `check if resource($0), owner("user1", $0)` the $0 represents a "hole" that must be filled with the correct value
185//! - `check if time($0), $0 < 2019-02-05T23:00:00Z` expiration date
186//! - `check if application($0), operation($1), user($2), right(#app, $0, $1), owner($2, $0), credit($2, $3), $3 > 0` verifies that the user owns the applications, the application has the right on the operation, there's a credit information for the operation, and the credit is larger than 0
187//!
188//! ### Allow/deny policies
189//!
190//! On the verification side, we can define *allow/deny policies*, which are tested
191//! after all checks passed, one by one in order until one of them matches.
192//!
193//! * if an *allow* matches, verification succeeds
194//! * if a *deny* matches, verification fails
195//! * if there's no *allow* or *deny*, verification fails
196//!
197//! They can be written as follows:
198//!
199//! ```ignore
200//! // verify that we have the right for this request
201//! allow if
202//! resource($res),
203//! operation($op),
204//! right($res, $op);
205//!
206//! deny if true;
207//! ```
208//!
209//! ## Symbol table
210//!
211//! To reduce the size of tokens, the language uses string interning: strings are
212//! serialized as an index in a list of strings. Any repetition of the string will
213//! then use reduced space.
214//!
215//! They can be used for pretty printing of a fact or rule. As an example, with a table
216//! containing `["resource", "operation", "read", "rule1", "file1.txt"]`, we could have the following rule:
217//! `#3() <- #0(#4), #1(#2)` that would be printed as `rule1() <- resource("file.txt"), operation("read")`
218//!
219//! biscuit implementations come with a default symbol table to avoid transmitting
220//! frequent values with every token.
221
222mod crypto;
223pub mod datalog;
224pub mod error;
225pub mod format;
226pub mod parser;
227mod token;
228
229pub use crypto::{KeyPair, PrivateKey, PublicKey};
230pub use token::authorizer::{Authorizer, AuthorizerLimits};
231pub use token::builder;
232pub use token::builder_ext;
233pub use token::unverified::UnverifiedBiscuit;
234pub use token::Biscuit;
235pub use token::RootKeyProvider;
236pub use token::{ThirdPartyBlock, ThirdPartyRequest};
237
238#[cfg(cargo_c)]
239mod capi;
240
241#[cfg(cargo_c)]
242pub use capi::*;
243
244#[cfg(bwk)]
245mod bwk;
246#[cfg(bwk)]
247pub use bwk::*;
248
249mod time;
250
251/// Procedural macros to construct Datalog policies
252#[cfg(feature = "datalog-macro")]
253#[cfg_attr(feature = "docsrs", doc(cfg(feature = "datalog-macro")))]
254pub mod macros;