fleet/server/server.go
Victor Vrantchan 9ab09f122a update user service (#101)
- Added all required methods for a UserService
- Added authentication handlers `/api/login` and `/api/logout`
- Added authMiddleware for authentication for `/api/v1/kolide` path
- Added authorization middleware for each endoint
- Added validation middleware for validating API inputs
- Began work on logging middleware
2016-09-01 00:51:38 -04:00

230 lines
6.6 KiB
Go

package server
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/contrib/ginrus"
"github.com/gin-gonic/contrib/static"
"github.com/gin-gonic/gin"
"github.com/kolide/kolide-ose/errors"
"github.com/kolide/kolide-ose/kolide"
"github.com/spf13/viper"
"gopkg.in/go-playground/validator.v8"
)
var validate = validator.New(&validator.Config{TagName: "validate", FieldNameTag: "json"})
// initialize the library based on configurations
func init() {
if !viper.GetBool("tool.debug") {
gin.SetMode(gin.ReleaseMode)
}
}
// Get the SMTP connection pool from the context, or panic
func GetSMTPConnectionPool(c *gin.Context) kolide.SMTPConnectionPool {
return c.MustGet("SMTPConnectionPool").(kolide.SMTPConnectionPool)
}
func SMTPConnectionPoolMiddleware(pool kolide.SMTPConnectionPool) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("SMTPConnectionPool", pool)
c.Next()
}
}
// Get the database connection from the context, or panic
func GetDB(c *gin.Context) kolide.Datastore {
return c.MustGet("DB").(kolide.Datastore)
}
func DatabaseMiddleware(db kolide.Datastore) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("DB", db)
c.Next()
}
}
// UnauthorizedError emits a response that is appropriate in the event that a
// request is received by a user which is not authorized to carry out the
// requested action
func UnauthorizedError(c *gin.Context) {
errors.ReturnError(
c,
errors.NewWithStatus(
http.StatusUnauthorized,
"Unauthorized",
"Unauthorized",
))
}
// NotFoundRequestError emits a response that is appropriate in the event that
// a request is received for a resource which is not found
func NotFoundRequestError(c *gin.Context) {
errors.ReturnError(
c,
errors.NewWithStatus(
http.StatusNotFound,
"Not Found",
"Not Found",
))
}
// Create a new server for testing purposes with no routes attached
// func createEmptyTestServer(db kolide.Datastore) *gin.Engine {
// server := gin.New()
// server.Use(DatabaseMiddleware(db))
// server.Use(SessionBackendMiddleware)
// return server
// }
func NewSessionManager(c *gin.Context) *kolide.SessionManager {
var (
cookieName = viper.GetString("session.cookie_name")
jwtKey = viper.GetString("auth.jwt_key")
)
if cookieName == "" {
cookieName = "KolideSession"
}
return &kolide.SessionManager{
Request: c.Request,
Store: GetDB(c),
Writer: c.Writer,
CookieName: cookieName,
JWTKey: jwtKey,
}
}
// Parse JSON into a struct with json.Unmarshal, followed by validation with
// the validator library.
func ParseAndValidateJSON(c *gin.Context, obj interface{}) error {
if err := json.NewDecoder(c.Request.Body).Decode(obj); err != nil {
return err
}
return validateStruct(obj)
}
func validateStruct(obj interface{}) error {
return validate.Struct(obj)
}
func ParseAndValidateUrlID(c *gin.Context, name string) (uint, error) {
id, err := strconv.ParseUint(c.Param(name), 10, 64)
if err != nil {
// TODO change to http.StatusUnprocessableEntity when it's available
return 0, errors.NewWithStatus(422, "Invalid ID", "Query ID was not a uint")
}
return uint(id), nil
}
func NotFound(c *gin.Context) {
errors.ReturnError(
c,
errors.NewWithStatus(
http.StatusNotFound,
"Not found",
fmt.Sprintf("Route not found for request: %+v", c.Request),
))
}
// CreateServer creates a gin.Engine HTTP server and configures it to be in a
// state such that it is ready to serve HTTP requests for the kolide application
func CreateServer(ds kolide.Datastore, pool kolide.SMTPConnectionPool, w io.Writer, resultHandler OsqueryResultHandler, statusHandler OsqueryStatusHandler) *gin.Engine {
server := gin.New()
server.Use(DatabaseMiddleware(ds))
server.Use(SMTPConnectionPoolMiddleware(pool))
// TODO: The following loggers are not synchronized with each other or
// logrus.StandardLogger() used through the rest of the codebase. As
// such, their output may become intermingled.
// See https://github.com/Sirupsen/logrus/issues/391
// Ginrus middleware logs details about incoming requests using the
// logrus WithFields
requestLogger := logrus.New()
requestLogger.Out = w
server.Use(ginrus.Ginrus(requestLogger, time.RFC3339, false))
// Recovery middleware recovers from panic(), returning a 500 response
// code and printing the panic information to the log
recoveryLogger := logrus.New()
recoveryLogger.WriterLevel(logrus.ErrorLevel)
recoveryLogger.Out = w
server.Use(gin.RecoveryWithWriter(recoveryLogger.Writer()))
// Set the 404 route
server.NoRoute(NotFound)
// Kolide react entrypoint
server.HTMLRender = loadTemplates("react.tmpl")
server.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "react.tmpl", gin.H{})
})
// Kolide assets
server.Use(static.Serve("/assets", NewBinaryFileSystem("/build")))
v1 := server.Group("/api/v1")
// Kolide application API endpoints
kolide := v1.Group("/kolide")
kolide.POST("/login", Login)
kolide.GET("/logout", Logout)
kolide.POST("/user", GetUser)
kolide.PUT("/user", CreateUser)
kolide.PATCH("/user", ModifyUser)
kolide.PATCH("/user/password", ChangeUserPassword)
kolide.POST("/user/password/reset", ResetUserPassword)
kolide.DELETE("/user/password/reset", DeletePasswordResetRequest)
kolide.POST("/user/password/reset/verify", VerifyPasswordResetRequest)
kolide.PATCH("/user/admin", SetUserAdminState)
kolide.PATCH("/user/enabled", SetUserEnabledState)
kolide.POST("/user/sessions", GetInfoAboutSessionsForUser)
kolide.DELETE("/user/sessions", DeleteSessionsForUser)
kolide.POST("/session", GetInfoAboutSession)
kolide.DELETE("/session", DeleteSession)
kolide.GET("/queries", GetAllQueries)
kolide.GET("/queries/:id", GetQuery)
kolide.POST("/queries", CreateQuery)
kolide.PATCH("/queries/:id", ModifyQuery)
kolide.DELETE("/queries/:id", DeleteQuery)
kolide.GET("/packs", GetAllPacks)
kolide.GET("/packs/:id", GetPack)
kolide.POST("/packs", CreatePack)
kolide.PATCH("/packs/:id", ModifyPack)
kolide.DELETE("/packs/:id", DeletePack)
// TODO this should be changed to "/packs/:pid/queries/:qid"
kolide.PUT("/pack/:pid/query/:qid", AddQueryToPack)
// TODO this should be changed to "/packs/:pid/queries/:qid"
kolide.DELETE("/pack/:pid/query/:qid", DeleteQueryFromPack)
// osquery API endpoints
osq := v1.Group("/osquery")
osqueryHandler := OsqueryHandler{
ResultHandler: resultHandler,
StatusHandler: statusHandler,
}
osq.POST("/enroll", OsqueryEnroll)
osq.POST("/config", osqueryHandler.OsqueryConfig)
osq.POST("/log", osqueryHandler.OsqueryLog)
osq.POST("/distributed/read", OsqueryDistributedRead)
osq.POST("/distributed/write", OsqueryDistributedWrite)
return server
}