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

chore(daemon): port AccessChecker interface from snapd #358

Merged
merged 26 commits into from
Feb 22, 2024

Conversation

thp-canonical
Copy link
Contributor

@thp-canonical thp-canonical commented Feb 13, 2024

Port the accessChecker API from snapd for easier customization and more fine-grained access control.

UserState is currently empty, but it might sense to drag it along (to be in line with snapd's function signature) and allow user-specific state data to be extracted from the HTTP request in a central place (userFromRequest() in daemon.go).

benhoyt pushed a commit that referenced this pull request Feb 14, 2024
This ports canonical/snapd#10126 from snapd,
so that ucrednetGet() has the same return type.

This change is in preparation for porting the accessChecker API
from snapd (canonical/snapd#10127):
#358
Copy link
Contributor

@benhoyt benhoyt left a comment

Choose a reason for hiding this comment

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

Looking good! Let's just update from the master branch and resolve conflicts, add those tests, and I've commented on some minor issues.

I'd also like to get @flotter's review on this.

internals/daemon/access.go Outdated Show resolved Hide resolved
internals/daemon/access.go Outdated Show resolved Hide resolved
internals/daemon/access.go Outdated Show resolved Hide resolved
internals/daemon/access_test.go Outdated Show resolved Hide resolved
internals/daemon/access.go Outdated Show resolved Hide resolved
internals/daemon/api.go Outdated Show resolved Hide resolved
internals/daemon/daemon.go Outdated Show resolved Hide resolved
internals/daemon/daemon.go Show resolved Hide resolved
internals/daemon/daemon_test.go Outdated Show resolved Hide resolved
internals/daemon/export_test.go Outdated Show resolved Hide resolved
@benhoyt benhoyt requested a review from flotter February 14, 2024 03:46
@thp-canonical thp-canonical changed the title Port accessChecker interface from snapd Port AccessChecker interface from snapd Feb 14, 2024

func (s *daemonSuite) TestLoggedInUserAccess(c *C) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The TestLoggedInUserAccess test was removed, as daemon.userFromRequest(...) doesn't do anything yet, and will always return a nil user state. We can re-add those test cases when an AccessChecker is implemented that accesses the user parameter.

I've left the user parameter in AccessChecker.CheckAccess(...), as we do have a UserState struct, and this might be useful for @hpidcock when implementing token support.

@thp-canonical thp-canonical marked this pull request as ready for review February 14, 2024 14:07
@thp-canonical
Copy link
Contributor Author

thp-canonical commented Feb 14, 2024

@benhoyt This is now ready for review. It ended up spilling into many areas.

Let me know if you want me to split off any or all of those topics into separate PRs for easier review:

Split off already:

benhoyt pushed a commit that referenced this pull request Feb 19, 2024
As part of working on #358, we found out that the untrusted socket is not used, and so can be removed (in preparation for porting the `AccessChecker` changes from snapd in #358).

**Indicators that it's not used:**

If we look at how `canAccess` works, if we match on `untrustedSocketPath` (`isUntrusted`), the only way for `canAccess` to allow the request is when `c.UntrustedOK` is `true` (otherwise it unconditionally returns `accessUnauthorized` immediately):

```golang
if isUntrusted {
	if c.UntrustedOK {
		return accessOK
	}
	return accessUnauthorized
}
```

So in order for any API calls to be allowed with the untrusted socket (assuming all API calls go through `canAccess`), we would need to have a `Command` defined with `UntrustedOK: true`. Checking the Pebble codebase, no such `Command` definition exists, which means that even if any application would use the untrusted socket currently, all API calls would return `accessUnauthorized` unconditionally for this socket.

The untrusted socket as well as `UntrustedOK` in `Command` were already part of the initial import commit (50466ba), so seem to be an inheritance from snapd that haven't seen use in Pebble since then. The corresponding [snapd sources from around November 10th, 2020](https://github.com/snapcore/snapd/tree/e2581af241a941856a755035d816047ff9aa15d8) seem to call these [`SnapOK`](https://github.com/snapcore/snapd/blob/e2581af241a941856a755035d816047ff9aa15d8/daemon/daemon.go#L139) (`UntrustedOK`), [`dirs.SnapSocket`](https://github.com/snapcore/snapd/blob/e2581af241a941856a755035d816047ff9aa15d8/daemon/daemon.go#L160C23-L160C38) (`untrustedSocketPath`) and [`snapListener`](https://github.com/snapcore/snapd/blob/489358223f0bd03da01e62a4062174eb7e9e0ffa/daemon/daemon.go#L72) (`untrustedListener`).

Due to `gofmt` and removal of struct members with the longest names, this PR is best reviewed with the "hide whitespace" option.
benhoyt pushed a commit that referenced this pull request Feb 19, 2024
This brings the error response namings in line with snapd: [`daemon/errors.go` in snapd](https://github.com/snapcore/snapd/blob/489358223f0bd03da01e62a4062174eb7e9e0ffa/daemon/errors.go#L112). Split off from #358. While currently the error responses are used exclusively in the `daemon` package, once `AccessChecker` is available, it should be possible for other packages to create error responders to implement their `AccessChecker` variants.
@benhoyt
Copy link
Contributor

benhoyt commented Feb 19, 2024

@thp-canonical Can you please bring this branch up to date and resolve conflicts, now that #360 and #361 are merged?

@thp-canonical
Copy link
Contributor Author

@thp-canonical Can you please bring this branch up to date and resolve conflicts, now that #360 and #361 are merged?

@benhoyt PR is now updated against the latest master branch, with some further cleanups after looking at the diff again. Ready for re-review from my side.

Copy link
Contributor

@benhoyt benhoyt left a comment

Choose a reason for hiding this comment

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

This looks good -- just a couple of minor changes requested in the tests.

internals/daemon/daemon_test.go Outdated Show resolved Hide resolved
internals/daemon/daemon_test.go Outdated Show resolved Hide resolved
internals/daemon/daemon_test.go Outdated Show resolved Hide resolved
@benhoyt benhoyt changed the title Port AccessChecker interface from snapd chore(api): port AccessChecker interface from snapd Feb 20, 2024
@benhoyt benhoyt changed the title chore(api): port AccessChecker interface from snapd chore(api): port AccessChecker interface from snapd Feb 20, 2024
Copy link
Contributor

@benhoyt benhoyt left a comment

Choose a reason for hiding this comment

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

Thanks for the tweaks. Looks good now -- let's just fix the bracing style.

internals/daemon/daemon_test.go Outdated Show resolved Hide resolved
@benhoyt
Copy link
Contributor

benhoyt commented Feb 20, 2024

I've also asked for @hpidcock's review, as he's pretty close to Pebble and has been part of these discussions.

@benhoyt benhoyt requested review from hpidcock and removed request for flotter February 20, 2024 22:03
Copy link
Member

@hpidcock hpidcock left a comment

Choose a reason for hiding this comment

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

I'd retain the delete method as a write only method, but it should be allowed as it is a useful concept, even if we don't use it.

LGTM thank-you.

case "POST":
rspf = c.POST
case "DELETE":
Copy link
Member

Choose a reason for hiding this comment

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

What happened to DELETE here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Earlier I had suggested removing it as snapd removed it some time ago (and Pebble wasn't using it either). We can always add it back if we need it, but this was to be more in line with snapd.

Copy link
Member

Choose a reason for hiding this comment

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

OK, happy for it to go, since it isn't used and inline with snapd.

@benhoyt benhoyt changed the title chore(api): port AccessChecker interface from snapd chore(daemon): port AccessChecker interface from snapd Feb 22, 2024
@benhoyt benhoyt merged commit 150cb9f into canonical:master Feb 22, 2024
15 checks passed
@thp-canonical thp-canonical deleted the access-checker branch February 22, 2024 07:24
benhoyt added a commit to benhoyt/pebble that referenced this pull request Apr 3, 2024
Most of this was introduced in PR canonical#358, when we ported the
AccessChecker changes from snapd, but accidentally set all the
WriteAccess fields to UserAccess{} instead of AdminAccess{}.
Previously there was a r.Method=="GET" check in Command.canAccess that
handled this case.

Additionally:

- We lock down the files "pull" API to require admin. Even though it's
  a read (GET), this meant any user could potentially read sensitive
  files.
- We lock down the task-websocket endpoint to admin. This is a GET
  endpoint, but these websockets are used by exec to send stdin/out/err
  and commands to the exec'd process, so they should require admin too.

I've added some tests for these to ensure we don't accidentally change
them in future, without noticing. How valuable these tests are I'm not
sure, as they only cover a subset of the API endpoints, but it seems
better than nothing.
benhoyt added a commit that referenced this pull request Apr 3, 2024
Most of this was introduced in PR #358, when we ported the AccessChecker
changes from snapd, but accidentally set all the WriteAccess fields to
UserAccess{} instead of AdminAccess{}. Previously there was a
r.Method=="GET" check in Command.canAccess that handled this case.

Additionally:

- We lock down the files "pull" API to require admin. Even though it's a
read (GET), this meant any user could potentially read sensitive files.
- We lock down the task-websocket endpoint to admin. This is a GET
endpoint, but these websockets are used by exec to send stdin/out/err
and commands to the exec'd process, so they should require admin too.

I've added some tests for these to ensure we don't accidentally change
them in future, without noticing. How valuable these tests are I'm not
sure, as they only cover a subset of the API endpoints, but it seems
better than nothing.
benhoyt added a commit that referenced this pull request Apr 3, 2024
Most of this was introduced in PR #358, when we ported the AccessChecker
changes from snapd, but accidentally set all the WriteAccess fields to
UserAccess{} instead of AdminAccess{}. Previously there was a
r.Method=="GET" check in Command.canAccess that handled this case.

Additionally:

- We lock down the files "pull" API to require admin. Even though it's a
read (GET), this meant any user could potentially read sensitive files.
- We lock down the task-websocket endpoint to admin. This is a GET
endpoint, but these websockets are used by exec to send stdin/out/err
and commands to the exec'd process, so they should require admin too.

I've added some tests for these to ensure we don't accidentally change
them in future, without noticing. How valuable these tests are I'm not
sure, as they only cover a subset of the API endpoints, but it seems
better than nothing.
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.

3 participants