fleet/server/http_auth.go
Zachary Wasserman 8f16bd8bcc Send configuration + label queries in distributed reads (#215)
This PR is the beginning of distributed query work. For now we are focusing on using the distributed query subsystem to retrieve the basic configuration information (currently just platform), and run the label queries.

A mockable clock interface is also added to the service struct, allowing us to inject a clock as a dependency, and write unit tests that can control the time.
2016-09-20 20:08:11 -07:00

215 lines
6.2 KiB
Go

package server
import (
"fmt"
"net/http"
"strings"
jwt "github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request"
kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/kolide/kolide-ose/kolide"
"golang.org/x/net/context"
)
// authentication error
type authError struct {
reason string
// client reason is used to provide
// a different error message to the client
// when security is a concern
clientReason string
}
func (e authError) Error() string {
return e.reason
}
func (e authError) AuthError() string {
if e.clientReason != "" {
return e.clientReason
}
return "authorization error"
}
// forbidden, set when user is authenticated, but not allowd to perform action
type forbiddenError struct {
message string
fields []string
}
func (e forbiddenError) Error() string {
if e.message == "" {
return "unauthorized"
}
return fmt.Sprintf("unauthorized: %s", e.message)
}
// ViewerContext is a struct which represents the ability for an execution
// context to participate in certain actions. Most often, a ViewerContext is
// associated with an application user, but a ViewerContext can represent a
// variety of other execution contexts as well (script, test, etc). The main
// purpose of a ViewerContext is to assist in the authorization of sensitive
// actions.
type viewerContext struct {
user *kolide.User
session *kolide.Session
}
// IsAdmin indicates whether or not the current user can perform administrative
// actions.
func (vc *viewerContext) IsAdmin() bool {
if vc.user != nil {
return vc.user.Admin && vc.user.Enabled
}
return false
}
// UserID is a helper that enables quick access to the user ID of the current
// user.
func (vc *viewerContext) UserID() uint {
if vc.user != nil {
return vc.user.ID
}
return 0
}
func (vc *viewerContext) SessionID() uint {
if vc.session != nil {
return vc.session.ID
}
return 0
}
// IsLoggedIn determines whether or not the current VC is attached to a user
// account
func (vc *viewerContext) IsLoggedIn() bool {
return vc.user != nil && vc.user.Enabled
}
// CanPerformActions returns a bool indicating the current user's ability to
// perform the most basic actions on the site
func (vc *viewerContext) CanPerformActions() bool {
return vc.IsLoggedIn() && !vc.user.AdminForcedPasswordReset
}
// CanPerformReadActionsOnUser returns a bool indicating the current user's
// ability to perform read actions on the given user
func (vc *viewerContext) CanPerformReadActionOnUser(uid uint) bool {
return vc.CanPerformActions() || (vc.IsLoggedIn() && vc.IsUserID(uid))
}
// CanPerformWriteActionOnUser returns a bool indicating the current user's
// ability to perform write actions on the given user
func (vc *viewerContext) CanPerformWriteActionOnUser(uid uint) bool {
return vc.CanPerformActions() && (vc.IsUserID(uid) || vc.IsAdmin())
}
// IsUserID returns true if the given user id the same as the user which is
// represented by this ViewerContext
func (vc *viewerContext) IsUserID(id uint) bool {
if vc.UserID() == id {
return true
}
return false
}
// newViewerContext generates a ViewerContext given a session
func newViewerContext(user *kolide.User, session *kolide.Session) *viewerContext {
return &viewerContext{
user: user,
session: session,
}
}
// emptyVC is a utility which generates an empty ViewerContext. This is often
// used to represent users which are not logged in.
func emptyVC() *viewerContext {
return &viewerContext{}
}
func osqueryHostFromContext(ctx context.Context) (*kolide.Host, error) {
host, ok := ctx.Value("osqueryHost").(*kolide.Host)
if !ok {
return nil, errNoContext
}
return host, nil
}
func viewerContextFromContext(ctx context.Context) (*viewerContext, error) {
vc, ok := ctx.Value("viewerContext").(*viewerContext)
if !ok {
return nil, errNoContext
}
return vc, nil
}
// setViewerContext updates the context with a viewerContext,
// which holds the currently logged in user
func setViewerContext(svc kolide.Service, ds kolide.Datastore, jwtKey string, logger kitlog.Logger) kithttp.RequestFunc {
return func(ctx context.Context, r *http.Request) context.Context {
token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(jwtKey), nil
})
if err != nil {
if err != request.ErrNoTokenInRequest {
// all unauthenticated requests (login,logout,passwordreset) result in the
// request.ErrNoTokenInRequest error. we can ignore logging it
logger.Log("err", err, "error-source", "setViewerContext")
}
return context.WithValue(ctx, "viewerContext", emptyVC())
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return context.WithValue(ctx, "viewerContext", emptyVC())
}
sessionKeyClaim, ok := claims["session_key"]
if !ok {
return context.WithValue(ctx, "viewerContext", emptyVC())
}
sessionKey, ok := sessionKeyClaim.(string)
if !ok {
return context.WithValue(ctx, "viewerContext", emptyVC())
}
session, err := svc.GetSessionByKey(ctx, sessionKey)
if err != nil {
switch err {
case kolide.ErrNoActiveSession:
// If the code path got this far, it's likely that the user was logged
// in some time in the past, but their session has been expired since
// their last usage of the application
return context.WithValue(ctx, "viewerContext", emptyVC())
default:
return context.WithValue(ctx, "viewerContext", emptyVC())
}
}
user, err := svc.User(ctx, session.UserID)
if err != nil {
logger.Log("err", err, "error-source", "setViewerContext")
return context.WithValue(ctx, "viewerContext", emptyVC())
}
ctx = context.WithValue(ctx, "viewerContext", newViewerContext(user, session))
logger.Log("msg", "viewer context set", "user", user.ID)
// get the user-id for request
if strings.Contains(r.URL.Path, "users/") {
ctx = withUserIDFromRequest(r, ctx)
}
return ctx
}
}
func withUserIDFromRequest(r *http.Request, ctx context.Context) context.Context {
id, _ := idFromRequest(r, "id")
return context.WithValue(ctx, "request-id", id)
}