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;