Expectation for verifying first party caveats for attenuated macaroons

66 views
Skip to first unread message

Mike Dodson

unread,
Jun 1, 2020, 6:52:22 AM6/1/20
to Macaroons
I am trying to understand expectations with respect to verifying first party caveats for attenuated macaroons.

My simple example here would be a target service creating a macaroon allowing read/write access to a resource.  An intermediate party adds a read-only caveat to the macaroon.  The end user then provides this macaroon to the target service and requests to read the resource.  How should the macaroon be verified to ensure the user is only given read-access?

The original paper says during verification the target service "scan[s] all the provided macaroons for first-party caveats and checks the associated predicates in the context of the request."

The README for libmacaroons (which is excellent, btw) says  "One (very flawed) way we could try to verify this macaroon would be to manually parse it and authorize the request if its caveats are true.  But handling things this way completely sidesteps all the crypto-goodness that macaroons are built upon."

I assume I'm misunderstanding one or the other, because they seem to be in tension.

Going back to my original example:  If I have a macaroon that originally supports read/write access, and then attenuate it to be read-only, how am I expected to verify that and ensure that I only provide read access to the bearer?

Naively, I can think of two ways:

1. Create a verifier that is initialised with every possible, allowable caveat (including things like read-only, write-only, read-this-bit, read-that-bit).  Verify the macaroon.  Parse the macaroon, find the most restrictive set of caveats, and provide access to the resource based on that most restrictive set.  For my example, the verifier would verify the macaroon, then I would scan the first party caveats, find the 'read-only' caveat, see that this matches the request from the user, and allow the user to read the resource.

2. Create separate verifiers for every valid conjunction of caveats and attempt to verify an incoming macaroon against each.  In this case, the *least restrictive* verifier would tell you the access supported by the token.  For my example, I would have a read-only verifier, a write-only verifier, a read-this-bit verifier, etc.  I would attempt to verify the macaroon against each of them.  The write-only verifier would fail, but both the read verifiers would pass.  I would compare the requested access against each verifier and select the least restrictive, passing verifier (i.e., read-only).

libmacaroons doesn't seem to provide direct access to first party caveats (though you can pull them out of the inspect() function) and the README implies parsing the caveats is the wrong way to go about this, so option 1 appears to be incorrect.  Option 2, however, seems wrong; it takes all the lovely expressiveness of macaroons and turns it into complexity at the target service.

Can someone please straighten me out?

William Casarin

unread,
Jun 2, 2020, 5:38:20 PM6/2/20
to Mike Dodson, Macaroons

Hey Mike,

Mike Dodson <mike.je...@gmail.com> writes:

> I am trying to understand expectations with respect to verifying first
> party caveats for attenuated macaroons.
>
> My simple example here would be a target service creating a macaroon
> allowing read/write access to a resource. An intermediate party adds a
> read-only caveat to the macaroon. The end user then provides this macaroon
> to the target service and requests to read the resource. How should the
> macaroon be verified to ensure the user is only given read-access?
>
> The original paper says during verification the target service "scan[s] all
> the provided macaroons for first-party caveats and checks the associated
> predicates in the context of the request."
>
> The README for libmacaroons (which is excellent, btw) says "One (very
> flawed) way we could try to verify this macaroon would be to manually parse
> it and authorize the request if its caveats are true. But handling things
> this way completely sidesteps all the crypto-goodness that macaroons are
> built upon."
>
> I assume I'm misunderstanding one or the other, because they seem to be in
> tension.
>
> Going back to my original example: If I have a macaroon that originally
> supports read/write access, and then attenuate it to be read-only, how am I
> expected to verify that and ensure that I only provide read access to the
> bearer?

You shouldn't think of caveats as "supporting", "giving" or "amplifying"
rights, they should only ever restrict rights. In this case you should
determine if the request is trying to write to the resource, and then
check to see if there are any caveats that prevent this action.

The way I do this in my code is to generate a "scope" from a request.
This describes what they are trying to access (as a read/write bitmask).
In my case I'm using graphql, so I set the scope to write if it's a
mutation query, etc.

Thinking in terms of restricting rights, a read/write caveat should
prevent reading AND writing, since a macaroon without this caveat should
already be authorized to read/write. So the caveat should be called
read-only and write-only.

If there was a read-only caveat, this should restrict actions to read,
and should fail any requests with write scope.

If there was another caveat with write-only set, this would prevent
reading or writing, since it would fail either the read-only or
write-only caveat check (since these checks are independent)

Another way of thinking about this: a caveat that is read-only AND
write-only should always fail, as these constraints are impossible to
satisfy.

This is the safest, if not only way to verify caveats, otherwise you run
into issues where users can add caveats to grant more rights, and the
logic to prevent that is super easy to get wrong.

> Naively, I can think of two ways:
>
> 1. Create a verifier that is initialised with every possible, allowable
> caveat (including things like read-only, write-only, read-this-bit,
> read-that-bit). Verify the macaroon. Parse the macaroon, find the most
> restrictive set of caveats, and provide access to the resource based on
> that most restrictive set. For my example, the verifier would verify the
> macaroon, then I would scan the first party caveats, find the 'read-only'
> caveat, see that this matches the request from the user, and allow the user
> to read the resource.

I see you are thinking in terms of caveats that amplify rights, this is
the wrong way to go.

If some party adds a first-party caveat with all permission bits set,
this is affectively a no-op since macaroon without this caveat is
already authorized to access everything.

In my code I have a "perm" caveat that looks like this:

perm 1fff,19a7

describePermission(0x19a7) => ["session", "note", "task", ...]

Where 0x1fff is the read permissions the macaroon is restricted to, and
0x19a7 is the write permissions that the macaroon is restricted to.

Adding more perm caveats only further restricts access. Adding perm
ffff,ffff is a no-op since the previous perm caveat would have failed on
the bits not set in ffff.

You don't need to find the "most restricted set" of caveats, because the
only thing that matters is the individual caveats and if they restrict
the access or not. If any individual caveats fails the auth, the whole
macaroon should fail.

Caveats should be mostly stateless and not depend on any other caveats.
Sometimes you can get fancy and amplify rights in caveats, but you would
need to add a check to make sure that caveat only exists once, and you
would need to ensure that caveat is always set by the server. This is
super error prone so it's almost always not recommended.

> 2. Create separate verifiers for every valid conjunction of caveats and
> attempt to verify an incoming macaroon against each. In this case, the
> *least restrictive* verifier would tell you the access supported by the
> token. For my example, I would have a read-only verifier, a write-only
> verifier, a read-this-bit verifier, etc. I would attempt to verify the
> macaroon against each of them. The write-only verifier would fail, but
> both the read verifiers would pass. I would compare the requested access
> against each verifier and select the least restrictive, passing verifier
> (i.e., read-only).

Looks like this is again thinking in terms of amplifying rights, once
you change your thinking here things will make a lot more sense ;)

> libmacaroons doesn't seem to provide direct access to first party caveats
> (though you can pull them out of the inspect() function) and the README
> implies parsing the caveats is the wrong way to go about this, so option 1
> appears to be incorrect. Option 2, however, seems wrong; it takes all the
> lovely expressiveness of macaroons and turns it into complexity at the
> target service.
>
> Can someone please straighten me out?


Cheers,
Will


--
https://jb55.com

roger peppe

unread,
Jun 4, 2020, 12:55:47 PM6/4/20
to maca...@googlegroups.com
I'd second what William Casarin said.

FWIW, in my own macaroon package, first party caveats are checked with a simple function with a string argument and an error return. If the macaroon has a valid signature and the check function returns without error for all of the caveats, the macaroon is treated as valid. It's up to the caller to decide on their own caveat language (although I also implemented an extensible checkers framework to make that a bit easier).

This approach seems a little simpler to me than the verifier approach used by libmacaroons, but I'm sure it has its own issues :)

  cheers,
    rog.


--
You received this message because you are subscribed to the Google Groups "Macaroons" group.
To unsubscribe from this group and stop receiving emails from it, send an email to macaroons+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/macaroons/8cfa73d0-2192-4f9c-879c-317a5839aeb7%40googlegroups.com.

Mike Dodson

unread,
Jun 12, 2020, 8:31:22 AM6/12/20
to Macaroons
William and Roger,

Thanks for the responses.  I've inlined some questions and comments in William's response and refer to Roger's.
 
You shouldn't think of caveats as "supporting", "giving" or "amplifying"
rights, they should only ever restrict rights. In this case you should
determine if the request is trying to write to the resource, and then
check to see if there are any caveats that prevent this action.

I see where the terminology I used is incorrect, and I see where it could get me into trouble.  In this particular case, when I referred to a macaroon cavet that 'supports' read/write, I meant that the caveat was restricting operations to read/write only.  This comes right out of the progressive example from the 2014 paper:  a base macaroon is created, then a read/write-only caveat is added (presumably prohibiting execute, but actually we don't know what the full set of operations might have been), and then a separate read-only caveat is added.

So in this case, verification would first check overall validity of the macaroon (using the key, set of caveats, and signature), then compare the requested operation against all the caveats to check that it isn't a prohibited avtion.  I like the idea of using a bitfield for this (as you mention below), as you can simply use each caveat as a mask against the requested operation, but it still requires you to extract all the first-party caveats from the received macaroon.  Based on your answer, though, along with Roger Peppe's, perhaps extracting first-party caveats isn't actually taboo?
 
Thinking in terms of restricting rights, a read/write caveat should
prevent reading AND writing, since a macaroon without this caveat should
already be authorized to read/write. So the caveat should be called
read-only and write-only.

If there was a read-only caveat, this should restrict actions to read,
and should fail any requests with write scope.

If there was another caveat with write-only set, this would prevent
reading or writing, since it would fail either the read-only or
write-only caveat check (since these checks are independent)

Another way of thinking about this: a caveat that is read-only AND
write-only should always fail, as these constraints are impossible to
satisfy.

The idea that a macaroon with a read-only and write-only caveat should reject makes sense to me, because they're mutually exclusive; however, it seems like I should be able to specify a read/write-only caveat.  Otherwise I have a choice of creating a comprehensive blacklist of disallowed operations ("no execute", "no reading this memory range", "no writing that memory range", etc.) or only whitelisting a single operation (e.g., "read-only").
 
This is the safest, if not only way to verify caveats, otherwise you run
into issues where users can add caveats to grant more rights, and the
logic to prevent that is super easy to get wrong.

Yes, I can see the logic getting complex and error prone, though (as you point out), provided the overall set of permissible operations is well defined, a bitfield for permissions would be fairly straightforward.
 
In my code I have a "perm" caveat that looks like this:

    perm 1fff,19a7

    describePermission(0x19a7) => ["session", "note", "task", ...]

Where 0x1fff is the read permissions the macaroon is restricted to, and
0x19a7 is the write permissions that the macaroon is restricted to.

Adding more perm caveats only further restricts access. Adding perm
ffff,ffff is a no-op since the previous perm caveat would have failed on
the bits not set in ffff.

This is a very helpful example, and as I've stated above, implementing permission bits in this way seems a great way to implement what I'm trying to do.
 
You don't need to find the "most restricted set" of caveats, because the
only thing that matters is the individual caveats and if they restrict
the access or not. If any individual caveats fails the auth, the whole
macaroon should fail.

So if someone added multiple perm caveats of the form you stated above, you would simply bounce the request against each of the caveats, and if any of them fail, the operation is not permitted?  That makes sense.
 
Caveats should be mostly stateless and not depend on any other caveats.
Sometimes you can get fancy and amplify rights in caveats, but you would
need to add a check to make sure that caveat only exists once, and you
would need to ensure that caveat is always set by the server. This is
super error prone so it's almost always not recommended.

I don't think I'm interested in amplifying permissions using caveats.  My unfortunate choice of wording earlier probably gave the wrong impression.  That said, I appreciate the warning that thinking about caveats in any terms except restricting operations opens the door to bad implementation that would semantically allow adding a caveat that increases permissions.

Thanks again,

Mike
Reply all
Reply to author
Forward
0 new messages