biscuit

package module
v2.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 27, 2023 License: Apache-2.0 Imports: 15 Imported by: 5

README

biscuit-go

biscuit-go is an implementation of Biscuit in Go. It aims to be fully compatible with other existing implementations, so that tokens issued by, for example, the Rust version, could be validated by this library and vice versa.

Documentation and specifications

Usage

Create a biscuit
rng := rand.Reader
publicRoot, privateRoot, _ := ed25519.GenerateKey(rng)

authority, err := parser.FromStringBlockWithParams(`
	right("/a/file1.txt", {read});
	right("/a/file1.txt", {write});
	right("/a/file2.txt", {read});
	right("/a/file3.txt", {write});
`, map[string]biscuit.Term{"read": biscuit.String("read"), "write": biscuit.String("write")})

if err != nil {
	panic(fmt.Errorf("failed to parse authority block: %v", err))
}

builder := biscuit.NewBuilder(privateRoot)
builder.AddBlock(authority)

b, err := builder.Build()
if err != nil {
	panic(fmt.Errorf("failed to build biscuit: %v", err))
}

token, err := b.Serialize()
if err != nil {
	panic(fmt.Errorf("failed to serialize biscuit: %v", err))
}

// token is now a []byte, ready to be shared
// The biscuit spec mandates the use of URL-safe base64 encoding for textual representation:
fmt.Println(base64.URLEncoding.EncodeToString(token))
Attenuate a biscuit
b, err = biscuit.Unmarshal(token)
if err != nil {
    panic(fmt.Errorf("failed to deserialize biscuit: %v", err))
}

// Attenuate the biscuit by appending a new block to it
blockBuilder := b.CreateBlock()
block, err := parser.FromStringBlockWithParams(`
		check if resource($file), operation($permission), [{read}].contains($permission);`,
	map[string]biscuit.Term{"read": biscuit.String("read")})
if err != nil {
	panic(fmt.Errorf("failed to parse block: %v", err))
}
blockBuilder.AddBlock(block)

attenuatedBiscuit, err := b.Append(rng, blockBuilder.Build())
if err != nil {
    panic(fmt.Errorf("failed to append: %v", err))
}
attenuatedToken, err := b.Serialize()
if err != nil {
    panic(fmt.Errorf("failed to serialize biscuit: %v", err))
}

// attenuatedToken is a []byte, representing an attenuated token
Verify a biscuit
b, err := biscuit.Unmarshal(token)
if err != nil {
    panic(fmt.Errorf("failed to deserialize token: %v", err))
}

authorizer, err := b.Authorizer(publicRoot)
if err != nil {
    panic(fmt.Errorf("failed to verify token and create authorizer: %v", err))
}

authorizerContents, err := parser.FromStringAuthorizerWithParams(`
	resource({res});
	operation({op});
	allow if right({res}, {op});
	`, map[string]biscuit.Term{"res": biscuit.String("/a/file1.txt"), "op": biscuit.String("read")})
if err != nil {
	panic(fmt.Errorf("failed to parse authorizer: %v", err))
}
authorizer.AddAuthorizer(authorizerContents)

if err := authorizer.Authorize(); err != nil {
    fmt.Printf("failed authorizing token: %v\n", err)
} else {
    fmt.Println("success authorizing token")
}
Using biscuit-go grammar

biscuit-go provides a datalog parser, allowing to input datalog elements as plain strings, along with support for parameter substitution.

See GRAMMAR reference for the complete syntax.

The parsers supports parsing whole blocks (containing several facts, rules and checks), whole authorizers (containing several facts, rules, checks and policies), as well as individual facts, rules, checks and policies. Parsing and adding elements individually is especially useful when doing so from inside a loop.

The parser module provides convenient helpers for parsing a string into datalog elements (FromStringFact, FromStringRule, FromStringCheck, FromStringPolicy, FromStringBlock, FromStringAuthorizer, for static datalog snippets, and their counterparts allowing parameter substitution: FromStringFactWithParams, FromStringRuleWithParams, FromStringCheckWithParams, FromStringPolicyWithParams, FromStringBlockWithParams, FromStringAuthorizerWithParams).

Panic on parsing errors

In most cases, FromString* functions will let you handle errors. If you do not wish to handle errors and instead crash on errors (for instance in one-off scripts), it can be done by first creating a parser instance, and using the panic-y functions:

p := parser.New()
b := biscuit.NewBuilder(privateRoot)

b.AddBlock(p.Must().Block(`
	right("/a/file1.txt", {read});
	right("/a/file1.txt", {write});
	right("/a/file2.txt", {read});
	right("/a/file3.txt", {write});
`, map[string]biscuit.Term{"read": biscuit.String("read"), "write": biscuit.String("write")}))

b.AddFact(p.Must().Fact(`resource({res})`, map[string]biscuit.Term{"res": biscuit.String("/a/file1.txt")}))
b.AddRule(p.Must().Rule(`
    can_read($file) 
        <- resource($file)
        $file.starts_with("/a/")
`, nil))

Do note that these helpers take two arguments: a datalog snippet and a parameters map. If the datalog snippet does not contain parameters, nil can be passed as the second argument.

Examples

License

Licensed under Apache License, Version 2.0.

Documentation

Index

Examples

Constants

View Source
const (
	PolicyKindAllow = iota
	PolicyKindDeny
)
View Source
const MaxSchemaVersion uint32 = 3
View Source
const MinSchemaVersion uint32 = 3

Variables

View Source
var (
	ErrMissingSymbols   = errors.New("biscuit: missing symbols")
	ErrPolicyDenied     = errors.New("biscuit: denied by policy")
	ErrNoMatchingPolicy = errors.New("biscuit: denied by no matching policies")
)
View Source
var (
	// ErrSymbolTableOverlap is returned when multiple blocks declare the same symbols
	ErrSymbolTableOverlap = errors.New("biscuit: symbol table overlap")
	// ErrInvalidAuthorityIndex occurs when an authority block index is not 0
	ErrInvalidAuthorityIndex = errors.New("biscuit: invalid authority index")
	// ErrInvalidAuthorityFact occurs when an authority fact is an ambient fact
	ErrInvalidAuthorityFact = errors.New("biscuit: invalid authority fact")
	// ErrInvalidBlockFact occurs when a block fact provides an authority or ambient fact
	ErrInvalidBlockFact = errors.New("biscuit: invalid block fact")
	// ErrInvalidBlockRule occurs when a block rule generate an authority or ambient fact
	ErrInvalidBlockRule = errors.New("biscuit: invalid block rule")
	// ErrEmptyKeys is returned when verifying a biscuit having no keys
	ErrEmptyKeys = errors.New("biscuit: empty keys")
	// ErrUnknownPublicKey is returned when verifying a biscuit with the wrong public key
	ErrUnknownPublicKey = errors.New("biscuit: unknown public key")

	ErrInvalidSignature = errors.New("biscuit: invalid signature")

	ErrInvalidSignatureSize = errors.New("biscuit: invalid signature size")

	ErrInvalidKeySize = errors.New("biscuit: invalid key size")

	UnsupportedAlgorithm = errors.New("biscuit: unsupported signature algorithm")
)
View Source
var (
	ErrDuplicateFact     = errors.New("biscuit: fact already exists")
	ErrInvalidBlockIndex = errors.New("biscuit: invalid block index")
)
View Source
var (
	// DefaultAllowPolicy allows the biscuit to verify sucessfully as long as all its checks generate some facts.
	DefaultAllowPolicy = Policy{Kind: PolicyKindAllow, Queries: []Rule{{Head: Predicate{Name: "allow"}}}}
	// DefaultDenyPolicy makes the biscuit verification fail in all cases.
	DefaultDenyPolicy = Policy{Kind: PolicyKindDeny, Queries: []Rule{{Head: Predicate{Name: "deny"}}}}
)
View Source
var ErrFactNotFound = errors.New("biscuit: fact not found")

Functions

func WithRandom

func WithRandom(rng io.Reader) builderOption

func WithSymbols

func WithSymbols(symbols *datalog.SymbolTable) builderOption

Types

type Authorizer

type Authorizer interface {
	AddAuthorizer(a ParsedAuthorizer)
	AddBlock(b ParsedBlock)
	AddFact(fact Fact)
	AddRule(rule Rule)
	AddCheck(check Check)
	AddPolicy(policy Policy)
	Authorize() error
	Query(rule Rule) (FactSet, error)
	Biscuit() *Biscuit
	Reset()
	PrintWorld() string
	LoadPolicies([]byte) error
	SerializePolicies() ([]byte, error)
}

func NewVerifier

func NewVerifier(b *Biscuit, opts ...AuthorizerOption) (Authorizer, error)

type AuthorizerOption

type AuthorizerOption func(w *authorizer)

func WithWorldOptions

func WithWorldOptions(opts ...datalog.WorldOption) AuthorizerOption

type BinaryOp

type BinaryOp binaryOpType
const (
	BinaryUndefined BinaryOp = iota
	BinaryLessThan
	BinaryLessOrEqual
	BinaryGreaterThan
	BinaryGreaterOrEqual
	BinaryEqual
	BinaryContains
	BinaryPrefix
	BinarySuffix
	BinaryRegex
	BinaryAdd
	BinarySub
	BinaryMul
	BinaryDiv
	BinaryAnd
	BinaryOr
	BinaryIntersection
	BinaryUnion
)

func (BinaryOp) Type

func (BinaryOp) Type() OpType

type Biscuit

type Biscuit struct {
	// contains filtered or unexported fields
}

Biscuit represents a valid Biscuit token It contains multiple `Block` elements, the associated symbol table, and a serialized version of this data

Example
rng := rand.Reader
publicRoot, privateRoot, _ := ed25519.GenerateKey(rng)

authority, err := parser.FromStringBlockWithParams(`
		right("/a/file1.txt", {read});
		right("/a/file1.txt", {write});
		right("/a/file2.txt", {read});
		right("/a/file3.txt", {write});
	`, map[string]biscuit.Term{"read": biscuit.String("read"), "write": biscuit.String("write")})
if err != nil {
	panic(fmt.Errorf("failed to parse authority block: %v", err))
}

builder := biscuit.NewBuilder(privateRoot)
builder.AddBlock(authority)

b, err := builder.Build()
if err != nil {
	panic(fmt.Errorf("failed to build biscuit: %v", err))
}

token, err := b.Serialize()
if err != nil {
	panic(fmt.Errorf("failed to serialize biscuit: %v", err))
}

fmt.Printf("Token1 length: %d\n", len(token))

deser, err := biscuit.Unmarshal(token)
if err != nil {
	panic(fmt.Errorf("failed to deserialize biscuit: %v", err))
}

blockBuilder := deser.CreateBlock()

block, err := parser.FromStringBlockWithParams(`
			check if resource($file), operation($permission), [{read}].contains($permission);`,
	map[string]biscuit.Term{"read": biscuit.String("read")})

if err != nil {
	panic(fmt.Errorf("failed to parse block: %v", err))
}
blockBuilder.AddBlock(block)

b2, err := deser.Append(rng, blockBuilder.Build())
if err != nil {
	panic(fmt.Errorf("failed to append: %v", err))
}

token2, err := b2.Serialize()
if err != nil {
	panic(fmt.Errorf("failed to serialize biscuit: %v", err))
}

fmt.Printf("Token2 length: %d\n", len(token2))

// Verify
b2, err = biscuit.Unmarshal(token2)
if err != nil {
	panic(fmt.Errorf("failed to deserialize token: %v", err))
}

v1, err := b2.Authorizer(publicRoot)
if err != nil {
	panic(fmt.Errorf("failed to create verifier: %v", err))
}

authorizer, err := parser.FromStringAuthorizerWithParams(`
		resource({res});
		operation({op});
		allow if right({res}, {op});
		`, map[string]biscuit.Term{"res": biscuit.String("/a/file1.txt"), "op": biscuit.String("read")})

if err != nil {
	panic(fmt.Errorf("failed to parse authorizer: %v", err))
}
v1.AddAuthorizer(authorizer)

if err := v1.Authorize(); err != nil {
	// fmt.Println(v1.PrintWorld())

	fmt.Println("forbidden to read /a/file1.txt")
} else {
	//fmt.Println(v1.PrintWorld())

	fmt.Println("allowed to read /a/file1.txt")
}

v1, _ = b2.Authorizer(publicRoot)

authorizer, err = parser.FromStringAuthorizerWithParams(`
		resource({res});
		operation({op});
		allow if right({res}, {op});
		`, map[string]biscuit.Term{"res": biscuit.String("/a/file1.txt"), "op": biscuit.String("write")})

if err != nil {
	panic(fmt.Errorf("failed to parse authorizer: %v", err))
}
v1.AddAuthorizer(authorizer)

if err := v1.Authorize(); err != nil {
	fmt.Println("forbidden to write /a/file1.txt")
} else {
	fmt.Println("allowed to write /a/file1.txt")
}
Output:

Token1 length: 251
Token2 length: 433
allowed to read /a/file1.txt
forbidden to write /a/file1.txt

func New

func New(rng io.Reader, root ed25519.PrivateKey, baseSymbols *datalog.SymbolTable, authority *Block) (*Biscuit, error)

func Unmarshal

func Unmarshal(serialized []byte) (*Biscuit, error)

func (*Biscuit) Append

func (b *Biscuit) Append(rng io.Reader, block *Block) (*Biscuit, error)

func (*Biscuit) Authorizer

func (b *Biscuit) Authorizer(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error)

Checks the signature and creates an Authorizer The Authorizer can then test the authorizaion policies and accept or refuse the request

func (*Biscuit) BlockCount

func (b *Biscuit) BlockCount() int

func (*Biscuit) Checks

func (b *Biscuit) Checks() [][]datalog.Check

func (*Biscuit) Code added in v2.2.0

func (b *Biscuit) Code() []string

func (*Biscuit) CreateBlock

func (b *Biscuit) CreateBlock() BlockBuilder

func (*Biscuit) GetBlockID

func (b *Biscuit) GetBlockID(fact Fact) (int, error)

GetBlockID returns the first block index containing a fact starting from the authority block and then each block in the order they were added. ErrFactNotFound is returned when no block contains the fact.

func (*Biscuit) RevocationIds

func (b *Biscuit) RevocationIds() [][]byte

func (*Biscuit) Seal

func (b *Biscuit) Seal(rng io.Reader) (*Biscuit, error)

func (*Biscuit) Serialize

func (b *Biscuit) Serialize() ([]byte, error)

func (*Biscuit) String

func (b *Biscuit) String() string

type Block

type Block struct {
	// contains filtered or unexported fields
}

func (*Block) Code added in v2.2.0

func (b *Block) Code(symbols *datalog.SymbolTable) string

func (*Block) String

func (b *Block) String(symbols *datalog.SymbolTable) string

type BlockBuilder

type BlockBuilder interface {
	AddBlock(block ParsedBlock) error
	AddFact(fact Fact) error
	AddRule(rule Rule) error
	AddCheck(check Check) error
	SetContext(string)
	Build() *Block
}

func NewBlockBuilder

func NewBlockBuilder(baseSymbols *datalog.SymbolTable) BlockBuilder

type Bool

type Bool bool

func (Bool) String

func (b Bool) String() string

func (Bool) Type

func (b Bool) Type() TermType

type Builder

type Builder interface {
	AddBlock(block ParsedBlock) error
	AddAuthorityFact(fact Fact) error
	AddAuthorityRule(rule Rule) error
	AddAuthorityCheck(check Check) error
	Build() (*Biscuit, error)
}

func NewBuilder

func NewBuilder(root ed25519.PrivateKey, opts ...builderOption) Builder

type Bytes

type Bytes []byte

func (Bytes) String

func (a Bytes) String() string

func (Bytes) Type

func (a Bytes) Type() TermType

type Check

type Check struct {
	Queries []Rule
}

type Date

type Date time.Time

func (Date) String

func (a Date) String() string

func (Date) Type

func (a Date) Type() TermType

type Expression

type Expression []Op

type Fact

type Fact struct {
	Predicate
}

func (Fact) String

func (f Fact) String() string

type FactSet

type FactSet []Fact

func (FactSet) String

func (fs FactSet) String() string

type Integer

type Integer int64

func (Integer) String

func (a Integer) String() string

func (Integer) Type

func (a Integer) Type() TermType

type Op

type Op interface {
	Type() OpType
	// contains filtered or unexported methods
}

type OpType

type OpType byte
const (
	OpTypeValue OpType = iota
	OpTypeUnary
	OpTypeBinary
)

type ParsedAuthorizer added in v2.2.0

type ParsedAuthorizer struct {
	Policies []Policy
	Block    ParsedBlock
}

type ParsedBlock added in v2.2.0

type ParsedBlock struct {
	Facts  FactSet
	Rules  []Rule
	Checks []Check
}

type Policy

type Policy struct {
	Queries []Rule
	Kind    PolicyKind
}

type PolicyKind

type PolicyKind byte

type Predicate

type Predicate struct {
	Name string
	IDs  []Term
}

func (Predicate) String

func (p Predicate) String() string

type Rule

type Rule struct {
	Head        Predicate
	Body        []Predicate
	Expressions []Expression
}

type Set

type Set []Term

func (Set) String

func (a Set) String() string

func (Set) Type

func (a Set) Type() TermType

type String

type String string

func (String) String

func (a String) String() string

func (String) Type

func (a String) Type() TermType

type Term

type Term interface {
	Type() TermType
	String() string
	// contains filtered or unexported methods
}

type TermType

type TermType byte
const (
	TermTypeSymbol TermType = iota
	TermTypeVariable
	TermTypeInteger
	TermTypeString
	TermTypeDate
	TermTypeBytes
	TermTypeBool
	TermTypeSet
)

type UnaryOp

type UnaryOp unaryOpType
const (
	UnaryUndefined UnaryOp = iota
	UnaryNegate
	UnaryParens
	UnaryLength
)

func (UnaryOp) Type

func (UnaryOp) Type() OpType

type Unmarshaler

type Unmarshaler struct {
	Symbols *datalog.SymbolTable
}

func (*Unmarshaler) Unmarshal

func (u *Unmarshaler) Unmarshal(serialized []byte) (*Biscuit, error)

type Value

type Value struct {
	Term Term
}

func (Value) Type

func (v Value) Type() OpType

type Variable

type Variable string

func (Variable) String

func (a Variable) String() string

func (Variable) Type

func (a Variable) Type() TermType

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL