Documentation ¶
Overview ¶
Package macaroon defines Fly.io's Macaroon token format.
A Macaroon is a flexible bearer token based on the idea of "caveats". A caveat limits what a Macaroon can do. A blank Macaroon might represent an all-access credential; a caveat layered onto that Macaroon might transform it into a read-only credential; a further caveat might create a credential that can only read, and only to a particular file.
The basic laws of Macaroons:
- Anybody can add a caveat onto a Macaroon, even if they didn't originally issue it.
- A caveat can only further restrict a Macaroon's access; adding a caveat can't even increase access.
- Given a Macaroon with a set of caveats (A, B, C), it's cryptographically impossible to remove any caveat, to produce an (A, B) Macaroon or a (B, C).
An ordinary caveat is checked by looking at the request and the caveat and seeing if they match up. For instance, a Macaroon with an `Operation=read` caveat can be checked by looking to see if the request it accompanies is trying to write. Simple stuff.
A "third party (3P)" caveat works differently. 3P caveats demand that some other named system validate the request.
Users extract a little ticket from the 3P caveat and hands it to the third party, along with anything else the third party might want. That third party resolves the caveat by generating a "discharge Macaroon", which is a whole 'nother token, tied cryptographically to the original 3P caveat. The user then presents both the original Macaroon and the discharge Macaroon with their request.
For instance: most Fly.io Macaroons require a logged-in user (usually a member of a particular organization). We express that with a 3P caveat pointing to our authentication endpoint. That endpoint checks to see who you're logged in as, and produces an appropriate discharge, which accompanies the original Macaroon and (in effect) attests to you being logged in.
Cryptography ¶
All the cryptography in Macaroons is symmetric; there are no public keys.
We use SHA256 as our hash, and HMAC-SHA256 as our authenticator.
We use ChaCha20/Poly1305 as the AEAD for third-party caveats.
Fly Macaroon Format ¶
Our Macaroons are simple structs encoded with MessagePack. We use a binary encoding both for performance and to to encode deterministically, for cryptography. MessagePack is extraordinarily simple and you can reason about this code as if simply used JSON.
A typical Fly.io request from a user will require multiple tokens; the original "root" token, which says what you're allowed to do, and tokens to validate 3P caveats (usually at least an authentication token).
To represent that bundle of tokens, we define a `FlyV1` HTTP `Authorization` header scheme, which is simply a comma-separated set of Base64'd Macaroons.
Internal Deployment ¶
See the `flyio` package for more details.
Basic Library Usage ¶
Create a token with New.
Add caveats ("attenuating" it) with Macaroon.Add.
Sign and encode the token with Macaroon.Encode.
Decode a binary token with Decode.
Verify the signatures on a token with Macaroon.Verify. Note that the whole token has not been checked at this point!
Check the caveats (the result of Macaroon.Verify) with CaveatSet.Validate.
Index ¶
- Constants
- Variables
- func DischargeTicket(ka EncryptionKey, location string, ticket []byte) ([]Caveat, *Macaroon, error)
- func FindPermissionAndDischargeTokens(tokens [][]byte, location string) ([]*Macaroon, [][]byte, []*Macaroon, [][]byte, error)
- func GetCaveats[T Caveat](c *CaveatSet) (ret []T)
- func IsAttestation(c Caveat) bool
- func Parse(header string) ([][]byte, error)
- func ParsePermissionAndDischargeTokens(header string, location string) ([]byte, [][]byte, error)
- func RegisterCaveatJSONAlias(typ CaveatType, alias string)
- func RegisterCaveatType(zeroValue Caveat)
- func ThirdPartyTicket(encodedMacaroon []byte, thirdPartyLocation string) ([]byte, error)
- func ToAuthorizationHeader(toks ...[]byte) string
- func Validate[A Access](cs *CaveatSet, accesses ...A) error
- type Access
- type Attestation
- type BindToParentToken
- type Caveat
- type Caveat3P
- type CaveatSet
- func (c *CaveatSet) DecodeMsgpack(dec *msgpack.Decoder) error
- func (c CaveatSet) EncodeMsgpack(enc *msgpack.Encoder) error
- func (c CaveatSet) MarshalJSON() ([]byte, error)
- func (c CaveatSet) MarshalMsgpack() ([]byte, error)
- func (c *CaveatSet) UnmarshalJSON(b []byte) error
- func (c *CaveatSet) Validate(accesses ...Access) error
- type CaveatType
- type EncryptionKey
- type Macaroon
- func (m *Macaroon) Add(caveats ...Caveat) error
- func (m *Macaroon) Add3P(ka EncryptionKey, loc string, cs ...Caveat) error
- func (m *Macaroon) Bind(parent []byte) error
- func (m *Macaroon) BindToParentMacaroon(parent *Macaroon) error
- func (m *Macaroon) Encode() ([]byte, error)
- func (m *Macaroon) Expiration() time.Time
- func (m *Macaroon) String() (string, error)
- func (m *Macaroon) ThirdPartyTicket(location string, existingDischarges ...[]byte) ([]byte, error)
- func (m *Macaroon) ThirdPartyTickets(existingDischarges ...[]byte) (map[string][]byte, error)
- func (m *Macaroon) Verify(k SigningKey, discharges [][]byte, trusted3Ps map[string][]EncryptionKey) (*CaveatSet, error)
- type Nonce
- type SigningKey
- type UnregisteredCaveat
- func (c *UnregisteredCaveat) CaveatType() CaveatType
- func (c UnregisteredCaveat) MarshalJSON() ([]byte, error)
- func (c UnregisteredCaveat) MarshalMsgpack() ([]byte, error)
- func (c *UnregisteredCaveat) Name() string
- func (c *UnregisteredCaveat) Prohibits(f Access) error
- func (c *UnregisteredCaveat) UnmarshalJSON(data []byte) error
- func (c *UnregisteredCaveat) UnmarshalMsgpack(data []byte) error
- type ValidityWindow
- type WrapperCaveat
Constants ¶
const (
EncryptionKeySize = 32
)
Variables ¶
var ( ErrUnrecognizedToken = errors.New("bad token") ErrInvalidAccess = fmt.Errorf("%w: bad data for token verification", ErrUnauthorized) ErrBadCaveat = fmt.Errorf("%w: bad caveat", ErrUnauthorized) )
Functions ¶
func DischargeTicket ¶ added in v0.1.0
Decyrpts the ticket from the 3p caveat and prepares a discharge token. Returned caveats, if any, must be validated before issuing the discharge token to the user.
func GetCaveats ¶
GetCaveats gets any caveats of type T, including those nested within IfPresent caveats.
func IsAttestation ¶ added in v0.0.5
func ParsePermissionAndDischargeTokens ¶
Parse a string token and find the contained permission token for the given location.
func RegisterCaveatJSONAlias ¶ added in v0.0.6
func RegisterCaveatJSONAlias(typ CaveatType, alias string)
Register an alternate name for this caveat type that will be recognized when decoding JSON.
func RegisterCaveatType ¶
func RegisterCaveatType(zeroValue Caveat)
Register a caveat type for use with this library.
func ThirdPartyTicket ¶ added in v0.1.0
Checks the macaroon for a third party caveat for the specified location. Returns the caveat's encrypted ticket, if found.
func ToAuthorizationHeader ¶
ToAuthorizationHeader formats a collection of tokens as an HTTP Authorization header.
Types ¶
type Access ¶
type Access interface { // The current time Now() time.Time // Callback for validating the structure Validate() error }
Access represents the user's attempt to access some resource. Different caveats will require different contextual information.
type Attestation ¶ added in v0.0.5
type Attestation interface { Caveat // Whether or not this caveat type is an attestation. IsAttestation() bool }
Attestations make a positive assertion rather than constraining access to a resource. Most caveats are not attestations. Attestations may only be included in Proofs (macaroons whose signature is finalized and cannot have more caveats appended by the user).
type BindToParentToken ¶
type BindToParentToken []byte
BindToParentToken is used by discharge tokens to state that they may only be used to discharge 3P caveats for a specific root token or further attenuated versions of that token. This prevents a discharge token from being used with less attenuated versions of the specified token, effectively binding the discharge token to the root token. This caveat may appear multiple times to iteratively clamp down which versions of the root token the discharge token may be used with.
The parent token is identified by a prefix of the SHA256 digest of the token's signature.
func (*BindToParentToken) CaveatType ¶
func (c *BindToParentToken) CaveatType() CaveatType
func (*BindToParentToken) Name ¶ added in v0.0.5
func (c *BindToParentToken) Name() string
func (*BindToParentToken) Prohibits ¶
func (c *BindToParentToken) Prohibits(f Access) error
type Caveat ¶
type Caveat interface { // The numeric caveat type identifier. CaveatType() CaveatType // The string name of the caveat. Used for JSON encoding. Name() string // Callback for checking if the authorization check is blocked by this // caveat. Implementors must take care to return appropriate error types, // as they have bearing on the evaluation of IfPresent caveats. // Specifically, returning ErrResourceUnspecified indicates that caveat // constrains access to a resource type that isn't specified by the Access. Prohibits(f Access) error }
Caveat is the interface implemented by all caveats.
type Caveat3P ¶
type Caveat3P struct { Location string VerifierKey []byte // used by the initial issuer to verify discharge macaroon Ticket []byte // used by the 3p service to construct discharge macaroon // contains filtered or unexported fields }
Caveat3P is a requirement that the token be presented along with a 3P discharge token.
func (*Caveat3P) CaveatType ¶
func (c *Caveat3P) CaveatType() CaveatType
type CaveatSet ¶
type CaveatSet struct {
Caveats []Caveat
}
CaveatSet is how a set of caveats is serailized/encoded.
func DecodeCaveats ¶
Decodes a set of serialized caveats.
func NewCaveatSet ¶
Create a new CaveatSet comprised of the specified caveats.
func (*CaveatSet) DecodeMsgpack ¶
Implements msgpack.CustomDecoder
func (CaveatSet) EncodeMsgpack ¶
Implements msgpack.CustomEncoder
func (CaveatSet) MarshalJSON ¶
func (CaveatSet) MarshalMsgpack ¶
Implements msgpack.Marshaler
func (*CaveatSet) UnmarshalJSON ¶
type CaveatType ¶
type CaveatType uint64
A numeric identifier for caveat types. Values less than CavMinUserRegisterable (0x100000000) are reserved for use by fly.io. Users may request a globally-recognized caveat type via pull requests to this repository. Implementations that don't need to integrate with fly.io itself can pick from the user-defined range (>0x1000000000000).
const ( CavFlyioOrganization CaveatType = iota CavFlyioVolumes CavFlyioApps CavValidityWindow CavFlyioFeatureSet CavFlyioMutations CavFlyioMachines CavAuthConfineUser CavAuthConfineOrganization CavFlyioIsUser Cav3P CavBindToParentToken CavIfPresent CavFlyioMachineFeatureSet CavFlyioFromMachineSource CavFlyioClusters CavAuthConfineGoogleHD CavAuthConfineGitHubOrg CavAuthMaxValidity CavNoAdminFeatures AttestationAuthFlyioUserID AttestationAuthGitHubUserID AttestationAuthGoogleUserID CavAction CavFlyioCommands BlockPetsemMin = block255Min BlockPetsemMax = BlockPetsemMin + 0xff // Globally-recognized user-registerable caveat types may be requested via // pull requests to this repository. Add a meaningful name of the caveat // type (e.g. CavAcmeCorpWidgetID) on the line prior to // CavMaxUserRegisterable. CavMinUserRegisterable CaveatType = 1 << 32 CavMaxUserRegisterable CaveatType = 1<<48 - 1 CavMinUserDefined CaveatType = 1 << 48 CavMaxUserDefined CaveatType = 1<<64 - 2 CavUnregistered CaveatType = 1<<64 - 1 )
type EncryptionKey ¶
type EncryptionKey []byte
func NewEncryptionKey ¶
func NewEncryptionKey() EncryptionKey
type Macaroon ¶
type Macaroon struct { Nonce Nonce `json:"-"` Location string `json:"location"` // Retrieve caveats from a Macaroon you don't trust // by calling [Macaroon.Verify], not by poking into // the struct. UnsafeCaveats CaveatSet `json:"caveats"` Tail []byte `json:"-"` // contains filtered or unexported fields }
Macaroon is the fully-functioning internal representation of a token --- you've got a Macaroon either because you're constructing a new token yourself, or because you've parsed a token from the wire.
Some fields in these structures are JSON-encoded because we use a JSON representation of Macaroons in IPC with our Rails API, which doesn't have a good FFI to talk to Go.
func Decode ¶
Decode parses a token off the wire; to get usable caveats. There are two things you can do with a freshly-decoded Macaroon:
You can verify the signature and recover the caveats with Macaroon.Verify
You can add additional caveats to the Macaroon with Macaroon.Add, and then call Macaroon.Encode to re-encode it (this is called "attenuation", and it's what you'd do to take a read-write token and make it a read-only token, for instance.
Note that calling Macaroon.Verify requires a secret key, but Macaroon.Add and Macaroon.Encode does not. That's a Macaroon magic power.
func New ¶
func New(kid []byte, loc string, key SigningKey) (*Macaroon, error)
New creates a new token given a key-id string (which can be any opaque string and doesn't need to be cryptographically random or anything; the key-id is how you're going to relate the token back to a key you've saved somewhere; it's probably a database rowid somehow) and a location, which is ordinarily a URL. The key is the signing secret.
func (*Macaroon) Add ¶
Add adds a caveat to a Macaroon, adjusting the tail signature in the process. This is how you'd "attenuate" a token, taking a read-write token and turning it into a read-only token, for instance.
func (*Macaroon) Add3P ¶
func (m *Macaroon) Add3P(ka EncryptionKey, loc string, cs ...Caveat) error
Add3P adds a third-party caveat to a Macaroon. A third-party caveat is checked not by evaluating what it means, but instead by looking for a "discharge token" --- a second token sent along with the token that says "some other service verified that the claims corresponding to this caveat are true".
Add3P needs a key, which binds this token to the service that validates it. Every authentication caveat, for instance, shares an authentication key; the key connects the root service to the authentication service.
Add3P takes a location, which is used to figure out which keys to use to check which caveats. The location is normally a URL. The authentication service has an authentication location URL.
func (*Macaroon) Bind ¶
Bind cryptographically binds a discharge token to the "parent" token it's meant to accompany. This is a convenience method that takes a raw unparsed parent token as an argument.
Discharge tokens are generated by third-party services (like our authentication service, or your Slack bot) to satisfy a third-party caveat. Users present both the original and the discharge token when they make requests. Discharge tokens must be bound when they're sent; doing so prevents Discharge tokens from being replayed in some other context.
func (*Macaroon) BindToParentMacaroon ¶
See Macaroon.Bind; this is that function, but it takes a parsed Macaroon.
func (*Macaroon) Encode ¶
Encode encodes a Macaroon to bytes after creating it or decoding it and adding more caveats.
func (*Macaroon) Expiration ¶
Expiration calculates when this macaroon will expire
func (*Macaroon) ThirdPartyTicket ¶ added in v0.1.0
ThirdPartyTicket returns the ticket (see [Macaron.ThirdPartyTickets]) associated with a URL location, if possible.
func (*Macaroon) ThirdPartyTickets ¶ added in v0.1.0
ThirdPartyTickets extracts the encrypted tickets from a token's third party caveats.
The ticket of a third-party caveat is a little ticket embedded in the caveat that is readable by the third-party service for which it's intended. That service uses the ticket to generate a compatible discharge token to satisfy the caveat.
Macaroon services of all types are identified by their "location", which in our scheme is always a URL. ThirdPartyTickets returns a map of location to ticket. In a perfect world, you could iterate over this map hitting each URL and passing it the associated ticket, collecting all the discharge tokens you need for the request (it is never that simple, though).
Already-discharged caveats are excluded from the results.
func (*Macaroon) Verify ¶
func (m *Macaroon) Verify(k SigningKey, discharges [][]byte, trusted3Ps map[string][]EncryptionKey) (*CaveatSet, error)
Verify checks the signature on a [Macaroon.Decode] 'ed Macaroon and returns the the set of caveats that require validation against the user's request.
Verify is the primary way you recover caveats from a Macaroon. Note that the caveats returned are the semantically meaningful subset of caveats that might need to be checked against the request. Third-party caveats are validated implicitly by checking sgnatures, and aren't returned by Verify.
(A fun wrinkle, though: a 3P discharge token can add additional ordinary caveats to a token; you can, for instance, discharge our authentication token with a token that says "yes, this person is logged in as bob@victim.com, but only allow this request to perform reads, not writes"). Those added ordinary caveats WILL be returned from Verify.
type Nonce ¶
type Nonce struct {
// contains filtered or unexported fields
}
A Nonce in cryptography is a random number that is only used once. A Nonce on a Macaroon is a blob of data that encodes, most impotantly, the "key ID" (KID) of the token; the KID is an opaque value that you, the library caller, provide when you create a token; it's the database key you use to tie the Macaroon to your database.
func DecodeNonce ¶
DecodeNonce parses just the Nonce from an encoded Macaroon. You'd want to do this, for instance, to look metadata up by the keyid of the Macaroon, which is encoded in the Nonce.
func (*Nonce) DecodeMsgpack ¶
DecodeMsgpack implements msgpack.CustomDecoder
func (*Nonce) EncodeMsgpack ¶
EncodeMsgpack implements msgpack.CustomDecoder
func (Nonce) MustEncode ¶
type UnregisteredCaveat ¶ added in v0.2.0
type UnregisteredCaveat struct { Type CaveatType Body any RawJSON []byte RawMsgpack []byte }
func (*UnregisteredCaveat) CaveatType ¶ added in v0.2.0
func (c *UnregisteredCaveat) CaveatType() CaveatType
func (UnregisteredCaveat) MarshalJSON ¶ added in v0.2.0
func (c UnregisteredCaveat) MarshalJSON() ([]byte, error)
func (UnregisteredCaveat) MarshalMsgpack ¶ added in v0.2.0
func (c UnregisteredCaveat) MarshalMsgpack() ([]byte, error)
func (*UnregisteredCaveat) Name ¶ added in v0.2.0
func (c *UnregisteredCaveat) Name() string
func (*UnregisteredCaveat) Prohibits ¶ added in v0.2.0
func (c *UnregisteredCaveat) Prohibits(f Access) error
func (*UnregisteredCaveat) UnmarshalJSON ¶ added in v0.2.0
func (c *UnregisteredCaveat) UnmarshalJSON(data []byte) error
func (*UnregisteredCaveat) UnmarshalMsgpack ¶ added in v0.2.0
func (c *UnregisteredCaveat) UnmarshalMsgpack(data []byte) error
type ValidityWindow ¶
type ValidityWindow struct { NotBefore int64 `json:"not_before"` NotAfter int64 `json:"not_after"` }
ValidityWindow establishes the window of time the token is valid for.
func (*ValidityWindow) CaveatType ¶
func (c *ValidityWindow) CaveatType() CaveatType
func (*ValidityWindow) Name ¶ added in v0.0.5
func (c *ValidityWindow) Name() string
func (*ValidityWindow) Prohibits ¶
func (c *ValidityWindow) Prohibits(f Access) error
type WrapperCaveat ¶ added in v0.0.4
type WrapperCaveat interface {
Unwrap() *CaveatSet
}
WrapperCaveat should be implemented by caveats that wrap other caveats (eg. resset.IfPresent).