From 227036d5daf9fcbfc2d5ff7da67655bab314b5d4 Mon Sep 17 00:00:00 2001 From: Mitchell Grenier Date: Fri, 27 Sep 2019 11:35:36 -0700 Subject: [PATCH 1/2] Improve the Header Authentication Method This adds more of the code needed to properly authenticate a user based on headers present added by a reverse proxy. --- cmd/admin/auth.go | 54 ++++++++++++++++++++++++++++++++++++++++++-- cmd/admin/headers.go | 30 ++++++++++++++++++++++++ cmd/admin/main.go | 33 +++++++++++++++++++++++---- pkg/types/types.go | 14 ++++++++++++ 4 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 cmd/admin/headers.go diff --git a/cmd/admin/auth.go b/cmd/admin/auth.go index 792dec02..3193f95c 100644 --- a/cmd/admin/auth.go +++ b/cmd/admin/auth.go @@ -4,6 +4,7 @@ import ( "context" "log" "net/http" + "strings" "github.com/jmpsec/osctrl/pkg/settings" ) @@ -102,8 +103,57 @@ func handlerAuthCheck(h http.Handler) http.Handler { samlMiddleware.RequireAccount(h).ServeHTTP(w, r) } case settings.AuthHeaders: - // Access always granted - h.ServeHTTP(w, r) + username := r.Header.Get(headersConfig.TrustedPrefix + headersConfig.UserName) + groups := strings.Split(r.Header.Get(headersConfig.TrustedPrefix+headersConfig.Groups), ",") + fullname := r.Header.Get(headersConfig.TrustedPrefix + headersConfig.DisplayName) + + // A username is required to use this system + if username == "" { + http.Redirect(w, r, forbiddenPath, http.StatusBadRequest) + return + } + + s := make(contextValue) + s["user"] = username + + for _, group := range groups { + if group == headersConfig.AdminGroup { + s["level"] = adminLevel + // We can break because there is no greater permission level + break + } else if group == headersConfig.UserGroup { + s["level"] = userLevel + // We can't break because we might still find a higher permission level + } + } + + // This user didn't present a group that has permission to use the service + if _, ok := s["level"]; !ok { + http.Redirect(w, r, forbiddenPath, http.StatusForbidden) + return + } + + if !adminUsers.Exists(username) { + log.Printf("User not found, creating: %s", username) + _, err := adminUsers.New(username, "", fullname, (s["level"] == adminLevel)) + + if err != nil { + log.Printf("Error creating user %s: %v", username, err) + http.Redirect(w, r, forbiddenPath, http.StatusFound) + return + } + } else { + if err := adminUsers.UpdateMetadata(r.RemoteAddr, r.Header.Get("User-Agent"), username); err != nil { + log.Printf("error updating metadata for user %s: %v", username, err) + } + } + + // _, session := sessionsmgr.CheckAuth(r) + // s["csrftoken"] = session.Values["csrftoken"].(string) + ctx := context.WithValue(r.Context(), contextKey("session"), s) + + // Access granted + h.ServeHTTP(w, r.WithContext(ctx)) } }) } diff --git a/cmd/admin/headers.go b/cmd/admin/headers.go new file mode 100644 index 00000000..de6f116d --- /dev/null +++ b/cmd/admin/headers.go @@ -0,0 +1,30 @@ +package main + +import ( + "log" + + "github.com/jmpsec/osctrl/pkg/settings" + "github.com/jmpsec/osctrl/pkg/types" + "github.com/spf13/viper" +) + +// Function to load the configuration file +func loadHeaders(file string) (types.JSONConfigurationHeaders, error) { + var cfg types.JSONConfigurationHeaders + log.Printf("Loading %s", file) + // Load file and read config + viper.SetConfigFile(file) + err := viper.ReadInConfig() + if err != nil { + return cfg, err + } + // Header values + headersRaw := viper.Sub(settings.AuthHeaders) + err = headersRaw.Unmarshal(&cfg) + if err != nil { + return cfg, err + } + + // No errors! + return cfg, nil +} diff --git a/cmd/admin/main.go b/cmd/admin/main.go index c4079a7f..6263fa06 100644 --- a/cmd/admin/main.go +++ b/cmd/admin/main.go @@ -50,6 +50,8 @@ const ( dbConfigurationFile string = "config/db.json" // Default SAML configuration file samlConfigurationFile string = "config/saml.json" + // Default Headers configuration file + headersConfigurationFile string = "config/headers.json" // osquery version to display tables osqueryTablesVersion string = "4.0.1" // JSON file with osquery tables data @@ -87,6 +89,7 @@ var ( configFlag *string dbFlag *string samlFlag *string + headersFlag *string ) // SAML variables @@ -96,13 +99,20 @@ var ( samlData samlThings ) -// Valid values for auth and logging in configuration +// Headers variables +var ( + headersConfig types.JSONConfigurationHeaders +) + +// Valid values for auth in configuration var validAuth = map[string]bool{ settings.AuthDB: true, settings.AuthSAML: true, settings.AuthHeaders: true, settings.AuthJSON: true, } + +// Valid values for logging in configuration var validLogging = map[string]bool{ settings.LoggingDB: true, settings.LoggingSplunk: true, @@ -146,6 +156,7 @@ func init() { configFlag = flag.String("c", configurationFile, "Service configuration JSON file to use.") dbFlag = flag.String("D", dbConfigurationFile, "DB configuration JSON file to use.") samlFlag = flag.String("S", samlConfigurationFile, "SAML configuration JSON file to use.") + headersFlag = flag.String("H", headersConfigurationFile, "Headers configuration JSON file to use.") // Parse all flags flag.Parse() if *versionFlag { @@ -158,17 +169,29 @@ func init() { if err != nil { log.Fatalf("Error loading %s - %s", *configFlag, err) } + + // Load osquery tables JSON + osqueryTables, err = loadOsqueryTables(osqueryTablesFile) + if err != nil { + log.Fatalf("Error loading osquery tables %s", err) + } + // Load configuration for SAML if enabled if adminConfig.Auth == settings.AuthSAML { samlConfig, err = loadSAML(*samlFlag) if err != nil { log.Fatalf("Error loading %s - %s", *samlFlag, err) } + return } - // Load osquery tables JSON - osqueryTables, err = loadOsqueryTables(osqueryTablesFile) - if err != nil { - log.Fatalf("Error loading osquery tables %s", err) + + // Load configuration for Headers if enabled + if adminConfig.Auth == settings.AuthHeaders { + headersConfig, err = loadHeaders(*headersFlag) + if err != nil { + log.Fatalf("Error loading %s - %s", *headersFlag, err) + } + return } } diff --git a/pkg/types/types.go b/pkg/types/types.go index e42f0e84..33136cbf 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -20,3 +20,17 @@ type JSONConfigurationService struct { Auth string `json:"auth"` Logging string `json:"logging"` } + +// JSONConfigurationHeaders to keep all SAML details for auth +type JSONConfigurationHeaders struct { + TrustedPrefix string `json:"trustedPrefix"` + AdminGroup string `json:"adminGroup"` + UserGroup string `json:"userGroup"` + Email string `json:"email"` + UserName string `json:"userName"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + DisplayName string `json:"displayName"` + DistinguishedName string `json:"distinguishedName"` + Groups string `json:"groups"` +} From a09fbb2154233e2d0019600fa21cc44c3648ea85 Mon Sep 17 00:00:00 2001 From: Mitchell Grenier Date: Wed, 2 Oct 2019 10:14:08 -0700 Subject: [PATCH 2/2] Fix call to new user --- cmd/admin/auth.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/cmd/admin/auth.go b/cmd/admin/auth.go index 62374b5a..ca05d4fe 100644 --- a/cmd/admin/auth.go +++ b/cmd/admin/auth.go @@ -106,6 +106,7 @@ func handlerAuthCheck(h http.Handler) http.Handler { } case settings.AuthHeaders: username := r.Header.Get(headersConfig.TrustedPrefix + headersConfig.UserName) + email := r.Header.Get(headersConfig.TrustedPrefix + headersConfig.Email) groups := strings.Split(r.Header.Get(headersConfig.TrustedPrefix+headersConfig.Groups), ",") fullname := r.Header.Get(headersConfig.TrustedPrefix + headersConfig.DisplayName) @@ -135,21 +136,18 @@ func handlerAuthCheck(h http.Handler) http.Handler { return } - if !adminUsers.Exists(username) { - log.Printf("User not found, creating: %s", username) - _, err := adminUsers.New(username, "", fullname, (s["level"] == adminLevel)) - - if err != nil { - log.Printf("Error creating user %s: %v", username, err) - http.Redirect(w, r, forbiddenPath, http.StatusFound) - return - } - } else { - if err := adminUsers.UpdateMetadata(r.RemoteAddr, r.Header.Get("User-Agent"), username); err != nil { - log.Printf("error updating metadata for user %s: %v", username, err) - } + newUser, err := adminUsers.New(username, "", email, fullname, (s["level"] == adminLevel)) + if err != nil { + log.Printf("Error with new user %s: %v", username, err) + http.Redirect(w, r, forbiddenPath, http.StatusFound) + return } + if err := adminUsers.Create(newUser); err != nil { + log.Printf("Error creating user %s: %v", username, err) + http.Redirect(w, r, forbiddenPath, http.StatusFound) + return + } // _, session := sessionsmgr.CheckAuth(r) // s["csrftoken"] = session.Values["csrftoken"].(string) ctx := context.WithValue(r.Context(), contextKey("session"), s)