Skip to content

feat: add session actor middleware #897

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

Closed
wants to merge 11 commits into from
51 changes: 51 additions & 0 deletions coderd/access/session/actor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package session

import (
"github.com/coder/coder/coderd/database"
)

// ActorType is an enum of all types of Actors.
type ActorType string

// ActorTypes.
const (
ActorTypeSystem ActorType = "system"
ActorTypeAnonymous ActorType = "anonymous"
ActorTypeUser ActorType = "user"
)

// Actor represents an unauthenticated or authenticated client accessing the
// API. To check authorization, callers should call pass the Actor into the
// authz package to assert access.
type Actor interface {
Type() ActorType
// ID is the unique ID of the actor for logging purposes.
ID() string
// Name is a friendly, but consistent, name for the actor for logging
// purposes. E.g. "deansheather"
Name() string
Comment on lines +25 to +29
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't this either be system or the user's name? Seems like certain routes should never be system, so I'm confused when the logging would come into play.

Copy link
Member Author

Choose a reason for hiding this comment

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

On each request I want to log the actor name at least, so it's easy to see which user made a request in the logs. I am removing the SystemActor though.

Copy link
Member Author

Choose a reason for hiding this comment

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

is this ok @kylecarbs? The goal of this is so we can log the username without having to do a type cast because of the same reason as above (type switching just to read the username is very slow).

Copy link
Member

Choose a reason for hiding this comment

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

If we're just going to have the UserActor do we need to generalize the actor type at all? Could we just use database.User directly? That way we wouldn't have to type-cast at all.

Copy link
Member Author

Choose a reason for hiding this comment

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

We're not just going to have the UserActor type. We will also have a WorkspaceActor and eventually a SatelliteActor.

Copy link
Member

Choose a reason for hiding this comment

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

If we just have two or three though, could we just log the username / workspace / satellite name separately? If the actor type is primarily going to be used for that, I'm hesitant to say it's worth the grouping.

I'd think we'd have separate routes for users / workspaces / satellites anyways, similar to how agents can only access specific routes right now. In that pattern, there's no opportunity to have a user call an agent route, because they'll never need to do so.

Copy link
Member

Choose a reason for hiding this comment

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

That is one way to separate things. But that makes each actor type more distinct.

RBAC means an agent token is just a UserActor with a reduced scope. I see the benefit in having 1 type and letting rbac decide what they can and can't do.

I see value in the separating routes approach too.

Both options require their own care. One thing I wonder about is will a provisionerd ever need to query coderd for anything? Like is there a case some provisionerd will want a system_user with some RBAC role/perms to query some extra endpoints? I have no idea if there is, but having 1 system is nice in the sense all actors are equivalent and just defer to rbac for drawing lines.


// TODO: Steven - RBAC methods
}

// ActorTypeSystem represents the system making an authenticated request against
// itself. This should be used if a function requires an Actor but you need to
// skip authorization.
type SystemActor interface {
Actor
System()
}

// AnonymousActor represents an unauthenticated API client.
type AnonymousActor interface {
Actor
Anonymous()
}

// UserActor represents an authenticated user actor. Any consumers that wish to
// check if the actor is a user (and access user fields such as User.ID) can
// do a checked type cast from Actor to UserActor.
type UserActor interface {
Actor
User() *database.User
}
20 changes: 20 additions & 0 deletions coderd/access/session/anonymous.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package session

type anonymousActor struct{}

// Anon is a static AnonymousActor implementation.
var Anon AnonymousActor = anonymousActor{}

func (anonymousActor) Type() ActorType {
return ActorTypeAnonymous
}

func (anonymousActor) ID() string {
return "anon"
}

func (anonymousActor) Name() string {
return "anonymous"
}

func (anonymousActor) Anonymous() {}
4 changes: 4 additions & 0 deletions coderd/access/session/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package session provides session authentication via middleware for the Coder
// HTTP API. It also exposes the Actor type, which is the intermediary layer
// between identity and RBAC authorization.
package session
20 changes: 20 additions & 0 deletions coderd/access/session/system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package session

type systemActor struct{}

// System is a static SystemActor implementation.
var System SystemActor = systemActor{}

func (systemActor) Type() ActorType {
return ActorTypeSystem
}

func (systemActor) ID() string {
return "system"
}

func (systemActor) Name() string {
return "system"
}

func (systemActor) System() {}
31 changes: 31 additions & 0 deletions coderd/access/session/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package session

import "github.com/coder/coder/coderd/database"

type userActor struct {
user *database.User
}

var _ UserActor = &userActor{}

func NewUserActor(u *database.User) UserActor {
return &userActor{
user: u,
}
}

func (*userActor) Type() ActorType {
return ActorTypeUser
}

func (ua *userActor) ID() string {
return ua.user.ID.String()
}

func (ua *userActor) Name() string {
return ua.user.Username
}

func (ua *userActor) User() *database.User {
return ua.user
}