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

feat(daemon): use identities to auth API requests #434

Merged
merged 60 commits into from
Jul 16, 2024

Conversation

benhoyt
Copy link
Contributor

@benhoyt benhoyt commented Jun 17, 2024

This adds the code to actually use the new state-based identities for API authorization and authentication (part of spec OP043).

The PR updates the daemon to actually use UserState (which now has access-level and UID feilds). The daemon now fetches the request's named identity if one exists, otherwise it populates UserState with default user based on the ucred UID. So UserState is always set -- this makes the access checkers simpler and the notices API code simpler.

If there is a named identity (a non-nil UserState), that identity is used, even if the "default UID-based auth" would let them in. So named identities trump the default ("uid==0 || uid==daemon_uid" means admin; any uid means read). For example, if you have a user bob: {access: untrusted, local: {user-id: 42}} and are making a request with UID 42, it'll only work for untrusted/open endpoints (/v1/health and /v1/system-info), not the usual of any read API.

@hpidcock
Copy link
Member

Let's do this one last.

@hpidcock
Copy link
Member

@benhoyt I think I want to review this one in isolation after the rest have been merged.

@nishitm
Copy link

nishitm commented Jul 8, 2024

the changes look good from the security point of view

@benhoyt benhoyt changed the title feat(daemon): use new identities system to authorize API requests feat(daemon): use identities to auth API requests Jul 9, 2024
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.

Still reviewing, just some early comments.

// avoids holding the state lock for /v1/health in particular, which is
// not good: https://github.com/canonical/pebble/pull/369
var user *UserState
if _, isOpen := access.(OpenAccess); !isOpen {
Copy link
Member

Choose a reason for hiding this comment

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

I'm OK with this special case because it doesn't actually bypass the access checker. As long as we still actually call the access checker, we can have this optimisation.

Copy link
Contributor

@thp-canonical thp-canonical Jul 11, 2024

Choose a reason for hiding this comment

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

This should be fine. In case there's another implementation of AccessChecker in the future that is not of concrete type OpenAccess, but behaves the same, we will only "lose" the optimization (meaning we unnecessarily call userFromRequest and populate user properly), but it will still do the right thing.

@hpidcock
Copy link
Member

Unrelated flaky test in internals/daemon

----------------------------------------------------------------------
FAIL: api_exec_test.go:91: execSuite.TestStderr

api_exec_test.go:92:
    stdout, stderr, waitErr := s.exec(c, "", &client.ExecOptions{
        Command: []string{"/bin/sh", "-c", "echo some stderr! >&2"},
    })
api_exec_test.go:324:
    c.Assert(err, IsNil)
... value *fmt.wrapError = &fmt.wrapError{msg:"cannot connect to \"control\" websocket: read unix @->/tmp/check-2962214203/23.pebble.socket: i/o timeout", err:(*net.OpError)(0xc000326370)} ("cannot connect to \"control\" websocket: read unix @->/tmp/check-2962214203/23.pebble.socket: i/o timeout")

2024-07-11T05:20:52.589Z [test] Started daemon.
2024-07-11T05:20:52.602Z [test] POST /v1/exec 6.517775ms 202
2024-07-11T05:20:52.607Z [test] GET /v1/tasks/1/websocket/control 3.803267ms 200
2024-07-11T05:20:52.607Z [test] GET /v1/tasks/1/websocket/stdio 101.523µs 200
2024-07-11T05:20:52.608Z [test] GET /v1/tasks/1/websocket/stderr 137.962µs 200
2024-07-11T05:20:52.626Z [test] GET /v1/changes/1/wait 17.87651ms 200

----------------------------------------------------------------------

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.

@benhoyt, your work on this is much appreciated.

I've performed the QA that I think was suitable for this (integrating with my Juju PR) + testing the login directly + a series of stress tests on the unit tests (with and without -race).

I am happy to approve this now. I only had one thought around possible security violations that this change could bring and that is to do with revocation of privileges.
If you have a long running request, with an identity that had access to do an action (such as exec, logs, push, pull, etc.), then that action will go uninterrupted, despite changes to the access level for that identity.

We can address that in a future PR.

Copy link
Contributor

@flotter flotter left a comment

Choose a reason for hiding this comment

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

Really nice!

@benhoyt
Copy link
Contributor Author

benhoyt commented Jul 12, 2024

If you have a long running request, with an identity that had access to do an action (such as exec, logs, push, pull, etc.), then that action will go uninterrupted, despite changes to the access level for that identity.

@hpidcock That doesn't really concern me. If you were able to start a long running request, you had access at the time.

@benhoyt
Copy link
Contributor Author

benhoyt commented Jul 12, 2024

@thp-canonical Actually, after discussion with Harry, he was a bit concerned with that latest commit to always use Untrusted and a non-nil UserState. It may be good (in future) to distinguish between no auth and an untrusted identity. In addition, both of us had done manual testing on the previous iteration of the code, so any changes now are risky. We can always iterate further in future without user-facing changes.

@benhoyt benhoyt merged commit ec19d79 into canonical:master Jul 16, 2024
16 checks passed
@benhoyt benhoyt deleted the identities-access branch July 16, 2024 02:18
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.

6 participants