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

use pool_authorized() for all lookups #873

Merged
merged 1 commit into from
Apr 6, 2022
Merged

Conversation

davepacheco
Copy link
Collaborator

Recall that the DataStore has two methods for getting a database connection: pool() and pool_authorized(opctx: &OpContext). The latter checks whether the caller is even allowed to query the database. All authenticated users have this permission, so it's kind of silly. But this seemed useful as a belt-and-suspenders way to ensure that if we accidentally add some unauthenticated code path, an attacker can't use this to trivially DoS the database. We should eventually be using pool_authorized() everywhere and rip out pool() altogether. Right now, lookups use pool(), only for historical reasons. It's time to switch these to use pool_authorized(opctx).

This changes some failure modes for unauthenticated users! Previously, if an unauthenticated user tried to fetch, say, /organizations/foo, they would have gotten a 404. Same if they tried to update or delete it. That's because we'd look up the resource, find they didn't have read access, and send a 404. To be clear, I think that behavior remains reasonable. But with this change, we're kicking them out before even doing the lookup. I think that's also reasonable. From some casual research, it seems that people often advocate for using 401 when there's any authz error and no credentials were provided, so we're consistent with expectations there. This is also what our own docs/http-status-codes.adoc says.

This is especially relevant when we get to Silos. Once we do #850, it won't even be possible to do the lookup without credentials, so it makes sense to kick the request out quickly in that case -- and with a 401.

} else {
// If the user did not authenticate successfully, this will
// become a 401 rather than a 403.
Err(if !is_authn {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a small but important change.

Prior to this, if you failed an authz check, the logic went like this:

  1. Pick a "fallback" error to signify "you didn't have permissions to do this". This would be 401 if you were unauthenticated and 403 if you're authenticated.
  2. Check with the resource impl (via the call to on_unauthorized at L109). The only interesting impl is this one, which is used for most API resources. Pass the fallback error, but return whatever the resource says to return.

The intent of the on_unauthorized call is to determine if you're allowed to even know this resource exists. If so, the "fallback error" (401 or 403) is fine. If not, return a 404 instead. The on_unauthorized impl I linked above is what actually does this.

After this change, the 401 always takes precedence. If you fail the authz check and you're not authenticated, you always get a 401 -- we don't invoke on_unauthorized() at all. If you're authenticated, then we do the same as before: 403 if you're allowed to read the resource, 404 otherwise.

This change isn't strictly necessary. I did this for consistency of the public API. If we just change lookups to use pool_authorized(), then all the endpoints that do a lookup behave this way without this change here. But the endpoints that don't do lookups for whatever reason (see #845) would still return 404s in some cases for unauthenticated users that fail an authz check. I thought it better to put this logic here for consistency. If you ever add a new endpoint (whether it hits the database or not), and the user fails an authz check, and they're not authenticated, they'll get a 401, instead of having it be dependent on how the endpoint was implemented.

@davepacheco
Copy link
Collaborator Author

In case it's helpful, here's the output for the "unauthorized" test showing the new behavior:

SUMMARY OF REQUESTS MADE

KEY, USING HEADER AND EXAMPLE ROW:

          +----------------------------> privileged GET (expects 200)
          |                              (digit = last digit of 200-level
          |                              response)
          |
          |                          +-> privileged GET (expects same as above)
          |                          |   (digit = last digit of 200-level
          |                          |    response)
          |                          |   ('-' => skipped (N/A))
          ^                          ^
HEADER:   G GET  PUT  POST DEL  TRCE G  URL
EXAMPLE:  0 3111 5555 3111 5555 5555 0  /organizations
    ROW     ^^^^
            ||||                      TEST CASES FOR EACH HTTP METHOD:
            +|||----------------------< authenticated, unauthorized request
             +||----------------------< unauthenticated request
              +|----------------------< bad authentication: no such user
               +----------------------< bad authentication: invalid syntax

            \__/ \__/ \__/ \__/ \__/
            GET  PUT  etc.  The test cases are repeated for each HTTP method.

            The number in each cell is the last digit of the 400-level response
            that was expected for this test case.

    In this case, an unauthenthicated request to "GET /organizations" returned
    401.  All requests to "PUT /organizations" returned 405.

G GET  PUT  POST DEL  TRCE G  URL
0 3111 5555 3111 5555 5555 0  /organizations
0 4111 4111 5555 4111 5555 0  /organizations/demo-org
0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects
0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project
0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs
0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc
0 4111 4111 5555 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/firewall/rules
0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/subnets
0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/subnets/demo-vpc-subnet
0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers
0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers/demo-vpc-router
0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers/demo-vpc-router/routes
0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers/demo-vpc-router/routes/demo-router-route
0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/disks
0 4111 5555 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/disks/demo-disk
- 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/disks/attach
- 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/disks/detach
0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/instances
0 4111 5555 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/instances/demo-instance
- 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/start
- 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/stop
- 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/reboot
- 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/migrate
0 3111 5555 5555 5555 5555 0  /roles
0 4111 5555 5555 5555 5555 0  /roles/fleet.admin
0 3111 5555 5555 5555 5555 0  /users
0 4111 5555 5555 5555 5555 0  /users/db-init
0 3111 5555 5555 5555 5555 0  /hardware/racks
0 4111 5555 5555 5555 5555 0  /hardware/racks/c19a698f-c6f9-4a17-ae30-20d711b8f7dc
0 3111 5555 5555 5555 5555 0  /hardware/sleds
0 4111 5555 5555 5555 5555 0  /hardware/sleds/b6d65341-167c-41df-9b5c-41cded99c229
0 3111 5555 5555 5555 5555 0  /sagas
- 3111 5555 5555 5555 5555 -  /sagas/48a1b8c8-fc1c-6fea-9de9-fdeb8dda7823
0 3111 5555 5555 5555 5555 0  /timeseries/schema
- 5555 5555 3111 5555 5555 -  /updates/refresh

and here's the diff -- unfortunately it's a column that changed, so nearly every line changed, and I'm not sure how to most usefully show that:

$ diff {old,new}
3,24c3,24
< 0 4411 4411 5555 4411 5555 0  /organizations/demo-org
< 0 4411 5555 4411 5555 5555 0  /organizations/demo-org/projects
< 0 4411 4411 5555 4411 5555 0  /organizations/demo-org/projects/demo-project
< 0 4411 5555 4411 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs
< 0 4411 4411 5555 4411 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc
< 0 4411 4411 5555 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/firewall/rules
< 0 4411 5555 4411 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/subnets
< 0 4411 4411 5555 4411 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/subnets/demo-vpc-subnet
< 0 4411 5555 4411 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers
< 0 4411 4411 5555 4411 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers/demo-vpc-router
< 0 4411 5555 4411 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers/demo-vpc-router/routes
< 0 4411 4411 5555 4411 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers/demo-vpc-router/routes/demo-router-route
< 0 4411 5555 4411 5555 5555 0  /organizations/demo-org/projects/demo-project/disks
< 0 4411 5555 5555 4411 5555 0  /organizations/demo-org/projects/demo-project/disks/demo-disk
< - 5555 5555 4411 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/disks/attach
< - 5555 5555 4411 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/disks/detach
< 0 4411 5555 4411 5555 5555 0  /organizations/demo-org/projects/demo-project/instances
< 0 4411 5555 5555 4411 5555 0  /organizations/demo-org/projects/demo-project/instances/demo-instance
< - 5555 5555 4411 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/start
< - 5555 5555 4411 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/stop
< - 5555 5555 4411 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/reboot
< - 5555 5555 4411 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/migrate
---
> 0 4111 4111 5555 4111 5555 0  /organizations/demo-org
> 0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects
> 0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project
> 0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs
> 0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc
> 0 4111 4111 5555 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/firewall/rules
> 0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/subnets
> 0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/subnets/demo-vpc-subnet
> 0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers
> 0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers/demo-vpc-router
> 0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers/demo-vpc-router/routes
> 0 4111 4111 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/vpcs/demo-vpc/routers/demo-vpc-router/routes/demo-router-route
> 0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/disks
> 0 4111 5555 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/disks/demo-disk
> - 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/disks/attach
> - 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/disks/detach
> 0 4111 5555 4111 5555 5555 0  /organizations/demo-org/projects/demo-project/instances
> 0 4111 5555 5555 4111 5555 0  /organizations/demo-org/projects/demo-project/instances/demo-instance
> - 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/start
> - 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/stop
> - 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/reboot
> - 5555 5555 4111 5555 5555 -  /organizations/demo-org/projects/demo-project/instances/demo-instance/migrate
26c26
< 0 4411 5555 5555 5555 5555 0  /roles/fleet.admin
---
> 0 4111 5555 5555 5555 5555 0  /roles/fleet.admin
28c28
< 0 4411 5555 5555 5555 5555 0  /users/db-init
---
> 0 4111 5555 5555 5555 5555 0  /users/db-init
30c30
< 0 4411 5555 5555 5555 5555 0  /hardware/racks/c19a698f-c6f9-4a17-ae30-20d711b8f7dc
---
> 0 4111 5555 5555 5555 5555 0  /hardware/racks/c19a698f-c6f9-4a17-ae30-20d711b8f7dc
32c32
< 0 4411 5555 5555 5555 5555 0  /hardware/sleds/b6d65341-167c-41df-9b5c-41cded99c229
---
> 0 4111 5555 5555 5555 5555 0  /hardware/sleds/b6d65341-167c-41df-9b5c-41cded99c229

The best I know how to do is show vimdiff's colored output (which makes it look like more changed than really did):

Screen Shot 2022-04-04 at 2 44 07 PM

@davepacheco davepacheco merged commit 7912eb2 into main Apr 6, 2022
@davepacheco davepacheco deleted the lookup-authorized-try-2 branch April 6, 2022 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants