Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Named variables support #37

Merged
merged 2 commits into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 31 additions & 31 deletions biscuit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ func TestBiscuit(t *testing.T) {
block2.AddCaveat(Caveat{
Queries: []Rule{
{
Head: Predicate{Name: "caveat", IDs: []Atom{Variable(0)}},
Head: Predicate{Name: "caveat", IDs: []Atom{Variable("0")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(0)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("0")}},
{Name: "operation", IDs: []Atom{Symbol("ambient"), Symbol("read")}},
{Name: "right", IDs: []Atom{Symbol("authority"), Variable(0), Symbol("read")}},
{Name: "right", IDs: []Atom{Symbol("authority"), Variable("0"), Symbol("read")}},
},
},
},
Expand Down Expand Up @@ -109,27 +109,27 @@ func TestBiscuitRules(t *testing.T) {
builder := NewBuilder(root)

builder.AddAuthorityRule(Rule{
Head: Predicate{Name: "right", IDs: []Atom{Variable(1), Symbol("read")}},
Head: Predicate{Name: "right", IDs: []Atom{Variable("1"), Symbol("read")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
})
builder.AddAuthorityRule(Rule{
Head: Predicate{Name: "right", IDs: []Atom{Variable(1), Symbol("write")}},
Head: Predicate{Name: "right", IDs: []Atom{Variable("1"), Symbol("write")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
})
builder.AddAuthorityCaveat(Caveat{Queries: []Rule{
{
Head: Predicate{Name: "allowed_users", IDs: []Atom{Variable(0)}},
Head: Predicate{Name: "allowed_users", IDs: []Atom{Variable("0")}},
Body: []Predicate{
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
Constraints: []Constraint{{
Name: Variable(0),
Name: Variable("0"),
Checker: SymbolInChecker{
Set: map[Symbol]struct{}{Symbol("alice"): {}, Symbol("bob"): {}},
Not: false,
Expand All @@ -150,22 +150,22 @@ func TestBiscuitRules(t *testing.T) {
block.AddCaveat(Caveat{
Queries: []Rule{
{
Head: Predicate{Name: "caveat1", IDs: []Atom{Variable(0), Variable(1)}},
Head: Predicate{Name: "caveat1", IDs: []Atom{Variable("0"), Variable("1")}},
Body: []Predicate{
{Name: "right", IDs: []Atom{Symbol("authority"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(0)}},
{Name: "operation", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "right", IDs: []Atom{Symbol("authority"), Variable("0"), Variable("1")}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("0")}},
{Name: "operation", IDs: []Atom{Symbol("ambient"), Variable("1")}},
},
},
},
})
block.AddCaveat(Caveat{
Queries: []Rule{
{
Head: Predicate{Name: "caveat2", IDs: []Atom{Variable(0)}},
Head: Predicate{Name: "caveat2", IDs: []Atom{Variable("0")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(0)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Symbol("alice"), Variable(0)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("0")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Symbol("alice"), Variable("0")}},
},
},
},
Expand Down Expand Up @@ -234,17 +234,17 @@ func TestGenerateWorld(t *testing.T) {
authorityFact2 := Fact{Predicate: Predicate{Name: "fact2", IDs: []Atom{Symbol("authority"), String("file2")}}}

authorityRule1 := Rule{
Head: Predicate{Name: "right", IDs: []Atom{Symbol("authority"), Variable(1), Symbol("read")}},
Head: Predicate{Name: "right", IDs: []Atom{Symbol("authority"), Variable("1"), Symbol("read")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
}
authorityRule2 := Rule{
Head: Predicate{Name: "right", IDs: []Atom{Symbol("authority"), Variable(1), Symbol("write")}},
Head: Predicate{Name: "right", IDs: []Atom{Symbol("authority"), Variable("1"), Symbol("write")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
}

Expand All @@ -269,10 +269,10 @@ func TestGenerateWorld(t *testing.T) {

blockBuild := b.CreateBlock()
blockRule := Rule{
Head: Predicate{Name: "blockRule", IDs: []Atom{Variable(1)}},
Head: Predicate{Name: "blockRule", IDs: []Atom{Variable("1")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Symbol("alice"), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Symbol("alice"), Variable("1")}},
},
}
blockBuild.AddRule(blockRule)
Expand Down Expand Up @@ -318,7 +318,7 @@ func TestGenerateWorldErrors(t *testing.T) {
Desc: "invalid ambient authority fact",
Symbols: &datalog.SymbolTable{"authority", "ambient"},
Facts: []Fact{
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("ambient"), Variable(0)}}},
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("ambient"), Variable("0")}}},
},
},
{
Expand All @@ -332,14 +332,14 @@ func TestGenerateWorldErrors(t *testing.T) {
Desc: "invalid block fact authority",
Symbols: &datalog.SymbolTable{"authority", "ambient"},
BlockFacts: []Fact{
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("authority"), Variable(0)}}},
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("authority"), Variable("0")}}},
},
},
{
Desc: "invalid block fact ambient",
Symbols: &datalog.SymbolTable{"authority", "ambient"},
BlockFacts: []Fact{
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("ambient"), Variable(0)}}},
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("ambient"), Variable("0")}}},
},
},
{
Expand Down
75 changes: 32 additions & 43 deletions experiments/pop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,20 @@ import (
// Signature flow overview:
//
// ---- server generates a token to be signed ----
// $data = staticCtx | randomChallenge[0:16]
//
// server adds:
// then server adds:
// - facts:
// - should_sign(#authority, dataID, alg, pubkey)
// - data(#authority, dataID, staticCtx | challenge[16])
// - should_sign(#authority, $dataID, $alg, $pubkey)
// - data(#authority, $dataID, $data)
// - caveat:
// - *valid(0?)<- should_sign(#authority, $0, $1, $2), valid_signature(#ambient, $0, $1, $2)
// - *valid($dataID)<- should_sign(#authority, $dataID, $alg, $pubkey, valid_signature(#ambient, $dataID, $alg, $pubkey)
//
// ---- server sends the token to the client ----
//
// client queries for:
// - *to_sign(dataID, $0, $1) <- should_sign(#authority, $0, $1, pubkey), data(#authority, $0, $2)
// with:
// $0: dataID
// $1: alg
// $2: data
//
// - *to_sign(dataID, $alg, $pubkey) <- should_sign(#authority, $dataID, $alg, $pubkey), data(#authority, $dataID, $data)

// foreach to_sign facts:
// - verify data starts with staticCtx
// - let tokenHash = Sha256(authorityBlock | all blocks | all keys)
Expand All @@ -46,18 +43,10 @@ import (
// ---- client sends the token to the server ----
//
// server queries for:
// - *to_validate($0, $1, $2, $3, $4, $5, $6) <-
// should_sign(#authority, $0, $1, $2),
// data(#authority, $0, $3),
// signature($0, $2, $4, $5, $6)
// with:
// $0: dataID
// $1: alg
// $2: pubkey
// $3: data
// $4: signature
// $5: signerNonce
// $6: signerTimestamp
// - *to_validate($dataID, $alg, $pubkey, $data, $signature, $signerNonce, $signerTimestamp) <-
// should_sign(#authority, $dataID, $alg, $pubkey),
// data(#authority, $dataID, $data),
// signature($0, $pubkey, $signature, $signerNonce, $signerTimestamp)
//
// foreach to_validate facts:
// - let tokenHash = Sha256(authorityBlock | all blocks expect the last one | all keys except the last one)
Expand Down Expand Up @@ -128,13 +117,13 @@ func getServerToken(t *testing.T, pubkey ed25519.PublicKey) ([]byte, sig.PublicK

// This caveat requires every "should_sign" fact to have a matching "valid_signature" fact,
// that can only provided by the verifier (due to the ambient tag)
// *valid(0?)<- should_sign(#authority, $0, $1, $2), valid_signature(#ambient, $0, $1, $2)
// *valid($dataID)<- should_sign(#authority, $dataID, $alg, $pubkey), valid_signature(#ambient, $dataID, $alg, $pubkey)
builder.AddAuthorityCaveat(biscuit.Caveat{Queries: []biscuit.Rule{
{
Head: biscuit.Predicate{Name: "valid", IDs: []biscuit.Atom{biscuit.Variable(0)}},
Head: biscuit.Predicate{Name: "valid", IDs: []biscuit.Atom{biscuit.Variable("dataID")}},
Body: []biscuit.Predicate{
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}},
{Name: "valid_signature", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}},
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Variable("pubkey")}},
{Name: "valid_signature", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Variable("pubkey")}},
},
},
}})
Expand All @@ -158,13 +147,13 @@ func clientSign(t *testing.T, rootPubkey sig.PublicKey, pubkey ed25519.PublicKey

t.Logf("clientSign world:\n%s", verifier.PrintWorld())

// This query returns to_sign(dataID, alg, data) facts which require a signature with a private key matching pubkey.
// This query returns to_sign($dataID, $alg, $data) facts which require a signature with a private key matching pubkey.
// in this example: [to_sign(0, "ed25519", "hex:7369676e2074686973")]
toSign, err := verifier.Query(biscuit.Rule{
Head: biscuit.Predicate{Name: "to_sign", IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}},
Head: biscuit.Predicate{Name: "to_sign", IDs: []biscuit.Atom{biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Variable("pubkey")}},
Body: []biscuit.Predicate{
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Bytes(pubkey)}},
{Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(2)}},
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Bytes(pubkey)}},
{Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("pubkey")}},
},
})
require.NoError(t, err)
Expand All @@ -185,9 +174,9 @@ func clientSign(t *testing.T, rootPubkey sig.PublicKey, pubkey ed25519.PublicKey

// We have a "to_sign" fact, so we check if the token doesn't already hold a signature:
alreadySigned, err := verifier.Query(biscuit.Rule{
Head: biscuit.Predicate{Name: "already_signed", IDs: []biscuit.Atom{biscuit.Variable(0)}},
Head: biscuit.Predicate{Name: "already_signed", IDs: []biscuit.Atom{biscuit.Variable("dataID")}},
Body: []biscuit.Predicate{
{Name: "signature", IDs: []biscuit.Atom{dataID, biscuit.Bytes(pubkey), biscuit.Variable(0)}},
{Name: "signature", IDs: []biscuit.Atom{dataID, biscuit.Bytes(pubkey), biscuit.Variable("dataID")}},
},
})
require.NoError(t, err)
Expand Down Expand Up @@ -248,23 +237,23 @@ func verifySignature(t *testing.T, rootPubKey sig.PublicKey, b []byte) {

t.Logf("verifySignature world before:\n%s", verifier.PrintWorld())

// Generate "to_validate(dataID, alg, pubkey, data, signerNonce, signerTimestamp, signature)" facts from existing signatures
// Generate "to_validate($dataID, $alg, $pubkey, $data, $signerNonce, $signerTimestamp, $signature)" facts from existing signatures
toValidate, err := verifier.Query(biscuit.Rule{
Head: biscuit.Predicate{
Name: "to_validate",
IDs: []biscuit.Atom{
biscuit.Variable(0), // dataID
biscuit.Variable(1), // alg
biscuit.Variable(2), // pubkey
biscuit.Variable(3), // data
biscuit.Variable(4), // signature
biscuit.Variable(5), // signerNonce
biscuit.Variable(6), // signerTimestamp
biscuit.Variable("dataID"),
biscuit.Variable("alg"),
biscuit.Variable("pubkey"),
biscuit.Variable("data"),
biscuit.Variable("signature"),
biscuit.Variable("signerNonce"),
biscuit.Variable("signerTimestamp"),
}},
Body: []biscuit.Predicate{
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}},
{Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(3)}},
{Name: "signature", IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(2), biscuit.Variable(4), biscuit.Variable(5), biscuit.Variable(6)}},
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Variable("pubkey")}},
{Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("data")}},
{Name: "signature", IDs: []biscuit.Atom{biscuit.Variable("dataID"), biscuit.Variable("pubkey"), biscuit.Variable("signature"), biscuit.Variable("signerNonce"), biscuit.Variable("signerTimestamp")}},
},
})
require.NoError(t, err)
Expand Down
48 changes: 24 additions & 24 deletions parser/GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This document describes the currently supported Datalog grammar.
Represents a Datalog type, can be one of: symbol, variable, integer, string, date, bytes, or set.

- symbol is prefixed with a `#` sign followed by text, e.g. `#read`
- variable is prefixed with a `$` sign followed by an unsigned 32bit base-10 integer, e.g. `$0`
- variable is prefixed with a `$` sign followed by a string or an unsigned 32bit base-10 integer, e.g. `$0` or `$variable1`
- integer is any base-10 int64
- string is any utf8 character sequence, between double quotes, e.g. `"/path/to/file.txt"`
- date is RFC3339 encoded, e.g. `2006-01-02T15:04:05Z07:00`
Expand All @@ -24,43 +24,43 @@ Constraints allows performing checks on a variable, below is the list of availab

### Integer:

- Equal: `$0 == 1`
- Greater than: `$0 > 1`
- Greater than or equal: `$0 >= 1`
- Less than: `$0 < 1`
- Less than or equal: `$0 <= 1`
- In: `$0 in [1, 2, 3]`
- Not in: `$0 not in [1, 2, 3]`
- Equal: `$i == 1`
- Greater than: `$i > 1`
- Greater than or equal: `$i >= 1`
- Less than: `$i < 1`
- Less than or equal: `$i <= 1`
- In: `$i in [1, 2, 3]`
- Not in: `$i not in [1, 2, 3]`

### String

- Equal: `$0 == "abc"`
- Starts with: `prefix($0, "abc")`
- Ends with: `suffix($0, "abc")`
- Regular expression: `match($0, "^abc\s+def$") `
- In: `$0 in ["abc", "def"]`
- Not in: `$0 not in ["abc", "def"]`
- Equal: `$s == "abc"`
- Starts with: `prefix($s, "abc")`
- Ends with: `suffix($s, "abc")`
- Regular expression: `match($s, "^abc\s+def$") `
- In: `$s in ["abc", "def"]`
- Not in: `$s not in ["abc", "def"]`

### Date

- Before: `$0 < "2006-01-02T15:04:05Z07:00"`
- After: `$0 > "2006-01-02T15:04:05Z07:00"`
- Before: `$date < "2006-01-02T15:04:05Z07:00"`
- After: `$date > "2006-01-02T15:04:05Z07:00"`

### Symbols

- In:`$0 in [#a, #b, #c]`
- Not in:`$0 not in [#a, #b, #c]`
- In:`$sym in [#a, #b, #c]`
- Not in:`$sym not in [#a, #b, #c]`

### Bytes

- Equal: `$0 == "hex:3df97fb5"`
- In: `$0 in ["hex:3df97fb5", "hex:4a8feed1"]`
- Not in: `$0 not in ["hex:3df97fb5", "hex:4a8feed1"]`
- Equal: `$b == "hex:3df97fb5"`
- In: `$b in ["hex:3df97fb5", "hex:4a8feed1"]`
- Not in: `$b not in ["hex:3df97fb5", "hex:4a8feed1"]`

### Set

- Any: `$0 in [#read, #write]`
- None: `$0 not in [#read, #write]`
- Any: `$set in [#read, #write]`
- None: `$set not in [#read, #write]`

## Fact

Expand All @@ -73,7 +73,7 @@ The head is a single predicate, the body is a list of predicates, and followed b

It has the format: `*Head <- Body @ Constraints`

e.g. `*right(#authority, $1, #read) <- resource(#ambient, $1), owner(#ambient, $0, $1) @ $0 == "username", prefix($1, "/home/username")`
e.g. `*right(#authority, $file, #read) <- resource(#ambient, $file), owner(#ambient, $user, $file) @ $user == "username", prefix($file, "/home/username")`

# Caveat

Expand Down
6 changes: 3 additions & 3 deletions parser/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type Caveat struct {

type Atom struct {
Symbol *string `"#" @Ident`
Variable *uint32 `| "$" @Int`
Variable *string `| "$" @(Int|Ident)`
Bytes *HexString `| @@`
String *string `| @String`
Integer *int64 `| @Int`
Expand All @@ -44,7 +44,7 @@ type Constraint struct {
}

type VariableConstraint struct {
Variable *uint32 `"$" @Int`
Variable *string `"$" @(Int|Ident)`
Date *DateComparison `((@@`
Bytes *BytesComparison `| @@`
String *StringComparison `| @@`
Expand All @@ -54,7 +54,7 @@ type VariableConstraint struct {

type FunctionConstraint struct {
Function *string `@( "prefix" | "suffix" | "match" ) "("`
Variable *uint32 `"$" @Int ","`
Variable *string `"$" @(Int|Ident) ","`
Argument *string `@String ")"`
}

Expand Down
Loading