Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ glide.lock
bin
build
vendor
dist

# Plugins
*.so
Expand Down
41 changes: 16 additions & 25 deletions admin/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"strings"

"github.com/jmpsec/osctrl/admin/sessions"
"github.com/jmpsec/osctrl/settings"
"github.com/jmpsec/osctrl/users"
)
Expand All @@ -25,15 +26,10 @@ const (
)

// Helper to convert permissions for a user to a level for context
func levelPermissions(user users.AdminUser) string {
func levelPermissions(user users.AdminUser, perms users.UserPermissions) string {
if user.Admin {
return adminLevel
}
perms, err := adminUsers.ConvertPermissions(user.Permissions.RawMessage)
if err != nil {
log.Printf("error converting permissions %v", err)
return userLevel
}
// Check for query access
if perms.Query {
return queryLevel
Expand All @@ -58,14 +54,13 @@ func handlerAuthCheck(h http.Handler) http.Handler {
return
}
// Set middleware values
s := make(contextValue)
s := make(sessions.ContextValue)
s[ctxUser] = session.Username
s[ctxCSRF] = session.Values[ctxCSRF].(string)
s[ctxLevel] = session.Values[ctxLevel].(string)
ctx := context.WithValue(r.Context(), contextKey("session"), s)
ctx := context.WithValue(r.Context(), sessions.ContextKey("session"), s)
// Update metadata for the user
err := adminUsers.UpdateMetadata(session.IPAddress, session.UserAgent, session.Username, s["csrftoken"])
if err != nil {
if err := adminUsers.UpdateMetadata(session.IPAddress, session.UserAgent, session.Username, s["csrftoken"]); err != nil {
log.Printf("error updating metadata for user %s: %v", session.Username, err)
}
// Access granted
Expand Down Expand Up @@ -99,20 +94,26 @@ func handlerAuthCheck(h http.Handler) http.Handler {
http.Redirect(w, r, forbiddenPath, http.StatusFound)
return
}
permissions, err := adminUsers.ConvertPermissions(u.Permissions.RawMessage)
if err != nil {
log.Printf("error getting permissions for %s: %v", jwtdata.Username, err)
http.Redirect(w, r, forbiddenPath, http.StatusFound)
return
}
// Create new session
session, err = sessionsmgr.Save(r, w, u)
session, err = sessionsmgr.Save(r, w, u, permissions)
if err != nil {
log.Printf("session error: %v", err)
http.Redirect(w, r, samlConfig.LoginURL, http.StatusFound)
return
}
}
// Set middleware values
s := make(contextValue)
s := make(sessions.ContextValue)
s[ctxUser] = session.Username
s[ctxCSRF] = session.Values[ctxCSRF].(string)
s[ctxLevel] = session.Values[ctxLevel].(string)
ctx := context.WithValue(r.Context(), contextKey("session"), s)
ctx := context.WithValue(r.Context(), sessions.ContextKey("session"), s)
// Update metadata for the user
err = adminUsers.UpdateMetadata(session.IPAddress, session.UserAgent, session.Username, s["csrftoken"])
if err != nil {
Expand All @@ -134,7 +135,7 @@ func handlerAuthCheck(h http.Handler) http.Handler {
return
}
// Set middleware values
s := make(contextValue)
s := make(sessions.ContextValue)
s[ctxUser] = username
s[ctxCSRF] = generateCSRF()
for _, group := range groups {
Expand Down Expand Up @@ -165,19 +166,9 @@ func handlerAuthCheck(h http.Handler) http.Handler {
}
// _, session := sessionsmgr.CheckAuth(r)
// s["csrftoken"] = session.Values["csrftoken"].(string)
ctx := context.WithValue(r.Context(), contextKey("session"), s)
ctx := context.WithValue(r.Context(), sessions.ContextKey("session"), s)
// Access granted
h.ServeHTTP(w, r.WithContext(ctx))
}
})
}

// Helper to prepare context based on the user
func prepareContext(user users.AdminUser) contextValue {
s := make(contextValue)
s[ctxUser] = user.Username
s[ctxEmail] = user.Email
s[ctxCSRF] = user.CSRFToken
s[ctxLevel] = levelPermissions(user)
return s
}
220 changes: 220 additions & 0 deletions admin/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package auth

import (
"context"
"log"
"net/http"
"strings"

"github.com/jmpsec/osctrl/admin/sessions"
"github.com/jmpsec/osctrl/settings"
"github.com/jmpsec/osctrl/users"
)

// AdminAuth to handle authentication for admin
type AdminAuth struct {
Users *users.UserManager
Sessions *sessions.SessionManager
}

type AuthOption func(*AdminAuth)

func WithSessions(sessions *sessions.SessionManager) AuthOption {
return func(a *AdminAuth) {
a.Sessions = sessions
}
}

func WithUsers(users *users.UserManager) AuthOption {
return func(a *AdminAuth) {
a.Users = users
}
}

// CreateAdminAuth to initialize the Admin handlers struct
func CreateAdminAuth(opts ...AuthOption) *AdminAuth {
a := &AdminAuth{}
for _, opt := range opts {
opt(a)
}
return a
}

// LevelPermissions to convert permissions for a user to a level for context
func (a *AdminAuth) LevelPermissions(user users.AdminUser) string {
if user.Admin {
return sessions.AdminLevel
}
perms, err := a.Users.ConvertPermissions(user.Permissions.RawMessage)
if err != nil {
log.Printf("error converting permissions %v", err)
return sessions.UserLevel
}
// Check for query access
if perms.Query {
return sessions.QueryLevel
}
// Check for carve access
if perms.Carve {
return sessions.CarveLevel
}
// At this point, no access granted
return sessions.UserLevel
}

// Handler to check access to a resource based on the authentication enabled
func (a *AdminAuth) HandlerAuthCheck(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch adminConfig.Auth {
case settings.AuthDB:
// Check if user is already authenticated
authenticated, session := a.Sessions.CheckAuth(r)
if !authenticated {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
// Set middleware values
s := make(sessions.ContextValue)
s[sessions.CtxUser] = session.Username
s[sessions.CtxCSRF] = session.Values[sessions.CtxCSRF].(string)
s[sessions.CtxLevel] = session.Values[sessions.CtxLevel].(string)
ctx := context.WithValue(r.Context(), sessions.ContextKey("session"), s)
// Update metadata for the user
if err := a.Users.UpdateMetadata(session.IPAddress, session.UserAgent, session.Username, s["csrftoken"]); err != nil {
log.Printf("error updating metadata for user %s: %v", session.Username, err)
}
// Access granted
h.ServeHTTP(w, r.WithContext(ctx))
case settings.AuthSAML:
if samlMiddleware.IsAuthorized(r) {
cookiev, err := r.Cookie(samlConfig.TokenName)
if err != nil {
log.Printf("error extracting JWT data: %v", err)
http.Redirect(w, r, samlConfig.LoginURL, http.StatusFound)
return
}
jwtdata, err := parseJWTFromCookie(samlData.KeyPair, cookiev.Value)
if err != nil {
log.Printf("error parsing JWT: %v", err)
http.Redirect(w, r, samlConfig.LoginURL, http.StatusFound)
return
}
// Check if user is already authenticated
authenticated, session := a.Sessions.CheckAuth(r)
if !authenticated {
// Create user if it does not exist
if !a.Users.Exists(jwtdata.Username) {
log.Printf("user not found: %s", jwtdata.Username)
http.Redirect(w, r, forbiddenPath, http.StatusFound)
return
}
u, err := a.Users.Get(jwtdata.Username)
if err != nil {
log.Printf("error getting user %s: %v", jwtdata.Username, err)
http.Redirect(w, r, forbiddenPath, http.StatusFound)
return
}
// Create new session
session, err = a.Sessions.Save(r, w, u)
if err != nil {
log.Printf("session error: %v", err)
http.Redirect(w, r, samlConfig.LoginURL, http.StatusFound)
return
}
}
// Set middleware values
s := make(sessions.ContextValue)
s[sessions.CtxUser] = session.Username
s[sessions.CtxCSRF] = session.Values[sessions.CtxCSRF].(string)
s[sessions.CtxLevel] = session.Values[sessions.CtxLevel].(string)
ctx := context.WithValue(r.Context(), sessions.ContextKey("session"), s)
// Update metadata for the user
err = a.Users.UpdateMetadata(session.IPAddress, session.UserAgent, session.Username, s["csrftoken"])
if err != nil {
log.Printf("error updating metadata for user %s: %v", session.Username, err)
}
// Access granted
samlMiddleware.RequireAccount(h).ServeHTTP(w, r.WithContext(ctx))
} else {
samlMiddleware.RequireAccount(h).ServeHTTP(w, r)
}
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)
// A username is required to use this system
if username == "" {
http.Redirect(w, r, forbiddenPath, http.StatusBadRequest)
return
}
// Set middleware values
s := make(sessions.ContextValue)
s[sessions.CtxUser] = username
s[sessions.CtxCSRF] = generateCSRF()
for _, group := range groups {
if group == headersConfig.AdminGroup {
s[sessions.CtxLevel] = sessions.AdminLevel
// We can break because there is no greater permission level
break
} else if group == headersConfig.UserGroup {
s[sessions.CtxLevel] = sessions.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[sessions.CtxLevel]; !ok {
http.Redirect(w, r, forbiddenPath, http.StatusForbidden)
return
}
newUser, err := a.Users.New(username, "", email, fullname, (s[sessions.CtxLevel] == sessions.AdminLevel))
if err != nil {
log.Printf("Error with new user %s: %v", username, err)
http.Redirect(w, r, forbiddenPath, http.StatusFound)
return
}
if err := a.Users.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(), sessions.ContextKey("session"), s)
// Access granted
h.ServeHTTP(w, r.WithContext(ctx))
}
})
}

// Helper to prepare context based on the user
func prepareContext(user users.AdminUser) sessions.ContextValue {
s := make(sessions.ContextValue)
s[sessions.CtxUser] = user.Username
s[sessions.CtxEmail] = user.Email
s[sessions.CtxCSRF] = user.CSRFToken
s[sessions.CtxLevel] = levelPermissions(user)
return s
}

// Helper to parse JWT tokens because the SAML library is total garbage
func parseJWTFromCookie(keypair tls.Certificate, cookie string) (JWTData, error) {
type TokenClaims struct {
jwt.StandardClaims
Attributes map[string][]string `json:"attr"`
}
tokenClaims := TokenClaims{}
token, err := jwt.ParseWithClaims(cookie, &tokenClaims, func(t *jwt.Token) (interface{}, error) {
secretBlock := x509.MarshalPKCS1PrivateKey(keypair.PrivateKey.(*rsa.PrivateKey))
return secretBlock, nil
})
if err != nil || !token.Valid {
return JWTData{}, err
}
return JWTData{
Subject: tokenClaims.Subject,
Email: tokenClaims.Attributes["mail"][0],
Display: tokenClaims.Attributes["displayName"][0],
Username: tokenClaims.Attributes["sAMAccountName"][0],
}, nil
}
3 changes: 3 additions & 0 deletions admin/auth/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/jmpsec/osctrl/admin/auth

go 1.12
28 changes: 0 additions & 28 deletions admin/db.go

This file was deleted.

Loading