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