Security patterns
Authorization
Authorization is the gatekeeper for securing private resources by preventing unauthorized access. Examples of private resources are:
- Contents of a private repository
- Endpoints that can only be called by certain users (e.g. site admins)
- User settings or organization settings
Current forms of authorization in Sourcegraph include:
- Site admin roles
- Organization memberships
- Batch changes permissions
- Repository permissions
- Same-user validation
As a standard practice, users who do not have access to a given private resource should not be aware of the existence of that private resource. When only part of a resource is restricted by authorization limitations, it is reasonable to prompt the user that some resources are not available due to insufficient permissions. However, we must not provide anything that indicates what those restricted resources might be.
Enforce authorization
In Sourcegraph, there are two places to enforce authorization, both equally important:
- At the GraphQL layer:
- Some endpoints are restricted to certain users (e.g. site admins or the same user).
- Be aware that any backend failure has the potential to indicate unauthorized information about private resources. Therefore, halt the process as soon as we identify an unauthorized request, and behave as if the resource does not exist at all.
- At the database layer:
- The database is our source of truth for authorization, especially for repository and batch changes permissions. Enforcing authorization at this layer is absolutely necessary.
Authorization implementation
Within our Go code, such as the frontend and repo-updater services, metadata representing the current user or actor is kept within the current context. It can be accessed using actor.FromContext(ctx)
.
Actors
The Actor
type represents the current actor. In most cases, this is the user who issued the request.
More specifically, an actor will be one of these three cases:
- An authenticated user, in which case the
UID
field will be set to a non-zero value. - A guest user, in which case the
UID
field will be zero. - An internal actor, which indicates that the current operation was started by an internal Sourcegraph process.
Checking authorization
Care must be taken when checking an actor: it's possible to have a context that doesn't have an actor at all, in which case actor.FromContext()
will return nil
.
Code in cmd/frontend
and enterprise/cmd/frontend
can take advantage of the helper functions available in cmd/frontend/backend
such as CheckCurrentUserIsSiteAdmin
and CheckSiteAdminOrSameUser
. These functions already take the details of internal and user actors into account, and are the safest way to perform authorization checks at the frontend level (for example, in GraphQL resolvers).
Below the frontend level, or in other commands, you'll need to implement more of your own authorization logic. Two nil
-safe methods are provided on the Actor
type: IsAuthenticated()
and IsInternal()
, which are always safe to call. The UID
field must only be accessed after calling IsAuthenticated()
.
As a general rule, internal actors should always be considered authorized, so your checks will often take this general form:
func isAuthorized(ctx context.Context, uid int32) bool { actor := actor.FromContext(ctx) if actor.IsInternal() { return true } if !actor.IsAuthenticated() { return false } if actor.UID == uid { return true } // Get the user by ID and check admin status if necessary. }
Secret management
Secrets used by our applications are stored in GCP Secret Manager, never in source code unless it is mock/test data. Internal documentation on how to store/consume secrets for development can be found in this document.