diff --git a/cmd/admin/handlers/get.go b/cmd/admin/handlers/get.go
index 965996d7..34d8c882 100644
--- a/cmd/admin/handlers/get.go
+++ b/cmd/admin/handlers/get.go
@@ -4,6 +4,7 @@ import (
"io"
"net/http"
"os"
+ "strings"
"github.com/jmpsec/osctrl/cmd/admin/sessions"
"github.com/jmpsec/osctrl/pkg/carves"
@@ -150,4 +151,6 @@ func (h *HandlersAdmin) CarvesDownloadHandler(w http.ResponseWriter, r *http.Req
fileReader, _ = os.Open(archived.File)
_, _ = io.Copy(w, fileReader)
}
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
diff --git a/cmd/admin/handlers/handlers.go b/cmd/admin/handlers/handlers.go
index 771743b4..827bd0d5 100644
--- a/cmd/admin/handlers/handlers.go
+++ b/cmd/admin/handlers/handlers.go
@@ -2,6 +2,7 @@ package handlers
import (
"github.com/jmpsec/osctrl/cmd/admin/sessions"
+ "github.com/jmpsec/osctrl/pkg/auditlog"
"github.com/jmpsec/osctrl/pkg/backend"
"github.com/jmpsec/osctrl/pkg/cache"
"github.com/jmpsec/osctrl/pkg/carves"
@@ -43,6 +44,7 @@ type HandlersAdmin struct {
OptimizedUI bool
OsqueryTables []types.OsqueryTable
AdminConfig *config.JSONConfigurationService
+ AuditLog *auditlog.AuditLogManager
DBLogger *logging.LoggerDB
DebugHTTP *zerolog.Logger
DebugHTTPConfig *config.DebugHTTPConfiguration
@@ -161,6 +163,12 @@ func WithAdminConfig(config *config.JSONConfigurationService) HandlersOption {
}
}
+func WithAuditLog(auditLog *auditlog.AuditLogManager) HandlersOption {
+ return func(h *HandlersAdmin) {
+ h.AuditLog = auditLog
+ }
+}
+
func WithDBLogger(dbfile string, config *backend.JSONConfigurationDB) HandlersOption {
return func(h *HandlersAdmin) {
if dbfile == "" {
diff --git a/cmd/admin/handlers/json-audit.go b/cmd/admin/handlers/json-audit.go
new file mode 100644
index 00000000..ec51a64b
--- /dev/null
+++ b/cmd/admin/handlers/json-audit.go
@@ -0,0 +1,79 @@
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/jmpsec/osctrl/cmd/admin/sessions"
+ "github.com/jmpsec/osctrl/pkg/auditlog"
+ "github.com/jmpsec/osctrl/pkg/environments"
+ "github.com/jmpsec/osctrl/pkg/users"
+ "github.com/jmpsec/osctrl/pkg/utils"
+ "github.com/rs/zerolog/log"
+)
+
+// AuditLogJSON to be used to populate JSON data for audit logs
+type AuditLogJSON struct {
+ Service string `json:"service"`
+ Username string `json:"username"`
+ Line string `json:"line"`
+ SourceIP string `json:"sourceip"`
+ LogType string `json:"logtype"`
+ Severity string `json:"severity"`
+ Env string `json:"environment"`
+ When CreationTimes `json:"when"`
+}
+
+// ReturnedAudit to return a JSON with audit logs
+type ReturnedAudit struct {
+ Data []AuditLogJSON `json:"data"`
+}
+
+// JSONAuditLogHandler for audit logs in JSON
+func (h *HandlersAdmin) JSONAuditLogHandler(w http.ResponseWriter, r *http.Request) {
+ if h.DebugHTTPConfig.Enabled {
+ utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody)
+ }
+ // Get context data
+ ctx := r.Context().Value(sessions.ContextKey(sessions.CtxSession)).(sessions.ContextValue)
+ // Check permissions
+ if !h.Users.CheckPermissions(ctx[sessions.CtxUser], users.AdminLevel, users.NoEnvironment) {
+ adminErrorResponse(w, fmt.Sprintf("%s has insufficient permissions", ctx[sessions.CtxUser]), http.StatusForbidden, nil)
+ return
+ }
+ // Get all environments
+ envs, err := h.Envs.All()
+ if err != nil {
+ log.Err(err).Msg("error getting environments")
+ return
+ }
+ // Get audit logs
+ auditLogs, err := h.AuditLog.GetAll()
+ if err != nil {
+ log.Err(err).Msg("error getting audit logs")
+ return
+ }
+ // Prepare data to be returned
+ var auditLogsJSON []AuditLogJSON
+ for _, logEntry := range auditLogs {
+ auditLogsJSON = append(auditLogsJSON, AuditLogJSON{
+ Service: logEntry.Service,
+ Username: logEntry.Username,
+ Line: logEntry.Line,
+ SourceIP: logEntry.SourceIP,
+ LogType: auditlog.LogTypeToString(logEntry.LogType),
+ Severity: auditlog.SeverityToString(logEntry.Severity),
+ Env: environments.EnvironmentFinderID(logEntry.EnvironmentID, envs, false),
+ When: CreationTimes{
+ Display: utils.PastFutureTimes(logEntry.CreatedAt),
+ // Use Unix timestamp in seconds
+ Timestamp: utils.TimeTimestamp(logEntry.CreatedAt),
+ },
+ })
+ }
+ returned := ReturnedAudit{
+ Data: auditLogsJSON,
+ }
+ // Serve JSON
+ utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, returned)
+}
diff --git a/cmd/admin/handlers/post.go b/cmd/admin/handlers/post.go
index f4a67a02..95a5595a 100644
--- a/cmd/admin/handlers/post.go
+++ b/cmd/admin/handlers/post.go
@@ -10,6 +10,7 @@ import (
"net/http"
"github.com/jmpsec/osctrl/cmd/admin/sessions"
+ "github.com/jmpsec/osctrl/pkg/auditlog"
"github.com/jmpsec/osctrl/pkg/handlers"
"github.com/jmpsec/osctrl/pkg/nodes"
"github.com/jmpsec/osctrl/pkg/queries"
@@ -45,7 +46,7 @@ func (h *HandlersAdmin) LoginPOSTHandler(w http.ResponseWriter, r *http.Request)
return
}
// Serialize and send response
- log.Debug().Msg("Login response sent")
+ h.AuditLog.NewLogin(user.Username, strings.Split(r.RemoteAddr, ":")[0])
adminOKResponse(w, "/dashboard")
}
@@ -74,7 +75,7 @@ func (h *HandlersAdmin) LogoutPOSTHandler(w http.ResponseWriter, r *http.Request
return
}
// Serialize and send response
- log.Debug().Msg("Logout response sent")
+ h.AuditLog.NewLogout(ctx[sessions.CtxUser], strings.Split(r.RemoteAddr, ":")[0])
adminOKResponse(w, "OK")
}
@@ -172,7 +173,7 @@ func (h *HandlersAdmin) QueryRunPOSTHandler(w http.ResponseWriter, r *http.Reque
}
}
// Serialize and send response
- log.Debug().Msg("Query run response sent")
+ h.AuditLog.NewQuery(ctx[sessions.CtxUser], q.Query, strings.Split(r.RemoteAddr, ":")[0], env.ID)
adminOKResponse(w, "OK")
}
@@ -262,7 +263,7 @@ func (h *HandlersAdmin) CarvesRunPOSTHandler(w http.ResponseWriter, r *http.Requ
return
}
// Serialize and send response
- log.Debug().Msg("Carve run response sent")
+ h.AuditLog.NewCarve(ctx[sessions.CtxUser], c.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
adminOKResponse(w, "OK")
}
@@ -345,8 +346,7 @@ func (h *HandlersAdmin) QueryActionsPOSTHandler(w http.ResponseWriter, r *http.R
}
adminOKResponse(w, "queries delete successfully")
}
- // Serialize and send response
- log.Debug().Msg("Query run response sent")
+ h.AuditLog.QueryAction(ctx[sessions.CtxUser], q.Action, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// CarvesActionsPOSTHandler - Handler for POST requests to carves
@@ -402,8 +402,7 @@ func (h *HandlersAdmin) CarvesActionsPOSTHandler(w http.ResponseWriter, r *http.
log.Debug().Msg("testing action")
adminOKResponse(w, "test successful")
}
- // Serialize and send response
- log.Debug().Msg("Carves action response sent")
+ h.AuditLog.CarveAction(ctx[sessions.CtxUser], q.Action, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// ConfPOSTHandler for POST requests for saving configuration
@@ -466,8 +465,8 @@ func (h *HandlersAdmin) ConfPOSTHandler(w http.ResponseWriter, r *http.Request)
adminErrorResponse(w, "error saving configuration parts", http.StatusInternalServerError, err)
return
}
+ h.AuditLog.ConfAction(ctx[sessions.CtxUser], "update configuration", strings.Split(r.RemoteAddr, ":")[0], env.ID)
// Send response
- log.Debug().Msg("Configuration response sent")
adminOKResponse(w, "configuration saved successfully")
return
}
@@ -489,8 +488,8 @@ func (h *HandlersAdmin) ConfPOSTHandler(w http.ResponseWriter, r *http.Request)
adminErrorResponse(w, "error updating configuration", http.StatusInternalServerError, err)
return
}
+ h.AuditLog.ConfAction(ctx[sessions.CtxUser], "update options", strings.Split(r.RemoteAddr, ":")[0], env.ID)
// Send response
- log.Debug().Msg("Options response sent")
adminOKResponse(w, "options saved successfully")
return
}
@@ -512,8 +511,8 @@ func (h *HandlersAdmin) ConfPOSTHandler(w http.ResponseWriter, r *http.Request)
adminErrorResponse(w, "error updating configuration", http.StatusInternalServerError, err)
return
}
+ h.AuditLog.ConfAction(ctx[sessions.CtxUser], "update schedule", strings.Split(r.RemoteAddr, ":")[0], env.ID)
// Send response
- log.Debug().Msg("Schedule response sent")
adminOKResponse(w, "schedule saved successfully")
return
}
@@ -535,8 +534,8 @@ func (h *HandlersAdmin) ConfPOSTHandler(w http.ResponseWriter, r *http.Request)
adminErrorResponse(w, "error updating configuration", http.StatusInternalServerError, err)
return
}
+ h.AuditLog.ConfAction(ctx[sessions.CtxUser], "update packs", strings.Split(r.RemoteAddr, ":")[0], env.ID)
// Send response
- log.Debug().Msg("Packs response sent")
adminOKResponse(w, "packs saved successfully")
return
}
@@ -558,8 +557,8 @@ func (h *HandlersAdmin) ConfPOSTHandler(w http.ResponseWriter, r *http.Request)
adminErrorResponse(w, "error updating configuration", http.StatusInternalServerError, err)
return
}
+ h.AuditLog.ConfAction(ctx[sessions.CtxUser], "update decorators", strings.Split(r.RemoteAddr, ":")[0], env.ID)
// Send response
- log.Debug().Msg("Decorators response sent")
adminOKResponse(w, "decorators saved successfully")
return
}
@@ -581,8 +580,8 @@ func (h *HandlersAdmin) ConfPOSTHandler(w http.ResponseWriter, r *http.Request)
adminErrorResponse(w, "error updating configuration", http.StatusInternalServerError, err)
return
}
+ h.AuditLog.ConfAction(ctx[sessions.CtxUser], "update ATC", strings.Split(r.RemoteAddr, ":")[0], env.ID)
// Send response
- log.Debug().Msg("ATC response sent")
adminOKResponse(w, "ATC saved successfully")
return
}
@@ -644,8 +643,8 @@ func (h *HandlersAdmin) IntervalsPOSTHandler(w http.ResponseWriter, r *http.Requ
adminErrorResponse(w, "error updating flags", http.StatusInternalServerError, err)
return
}
+ h.AuditLog.ConfAction(ctx[sessions.CtxUser], "update intervals", strings.Split(r.RemoteAddr, ":")[0], env.ID)
// Serialize and send response
- log.Debug().Msg("Intervals response sent")
adminOKResponse(w, "intervals saved successfully")
}
@@ -741,8 +740,7 @@ func (h *HandlersAdmin) ExpirationPOSTHandler(w http.ResponseWriter, r *http.Req
adminOKResponse(w, "link set to not expire successfully")
}
}
- // Serialize and send response
- log.Debug().Msg("Expiration response sent")
+ h.AuditLog.ConfAction(ctx[sessions.CtxUser], fmt.Sprintf("%s:%s", e.Type, e.Action), strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// NodeActionsPOSTHandler for POST requests for multi node action
@@ -787,8 +785,7 @@ func (h *HandlersAdmin) NodeActionsPOSTHandler(w http.ResponseWriter, r *http.Re
return
}
}
- // Serialize and send response
- log.Debug().Msg("Multi-node action response sent")
+ h.AuditLog.NodeAction(ctx[sessions.CtxUser], m.Action, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
}
// EnvsPOSTHandler for POST request for /environments
@@ -878,8 +875,7 @@ func (h *HandlersAdmin) EnvsPOSTHandler(w http.ResponseWriter, r *http.Request)
}
adminOKResponse(w, "debug changed successfully")
}
- // Serialize and send response
- log.Debug().Msg("Environments response sent")
+ h.AuditLog.EnvAction(ctx[sessions.CtxUser], fmt.Sprintf("%s - %s", c.Action, c.Name), strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
}
// SettingsPOSTHandler for POST request for /settings
@@ -963,8 +959,7 @@ func (h *HandlersAdmin) SettingsPOSTHandler(w http.ResponseWriter, r *http.Reque
}
adminOKResponse(w, "setting deleted successfully")
}
- // Serialize and send response
- log.Debug().Msg("Settings response sent")
+ h.AuditLog.SettingsAction(ctx[sessions.CtxUser], fmt.Sprintf("%s - %s", s.Action, s.Name), strings.Split(r.RemoteAddr, ":")[0])
}
// UsersPOSTHandler for POST request for /users
@@ -1112,8 +1107,7 @@ func (h *HandlersAdmin) UsersPOSTHandler(w http.ResponseWriter, r *http.Request)
adminOKResponse(w, "service changed successfully")
}
}
- // Serialize and send response
- log.Debug().Msg("Users response sent")
+ h.AuditLog.UserAction(ctx[sessions.CtxUser], fmt.Sprintf("%s - %s", u.Action, u.Username), strings.Split(r.RemoteAddr, ":")[0])
}
// TagsPOSTHandler for POST request for /tags
@@ -1205,8 +1199,7 @@ func (h *HandlersAdmin) TagsPOSTHandler(w http.ResponseWriter, r *http.Request)
}
adminOKResponse(w, "tag removed successfully")
}
- // Serialize and send response
- log.Debug().Msg("Tags response sent")
+ h.AuditLog.TagAction(ctx[sessions.CtxUser], fmt.Sprintf("%s - %s", t.Action, t.Name), strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// TagNodesPOSTHandler for POST request for /tags/nodes
@@ -1263,8 +1256,9 @@ func (h *HandlersAdmin) TagNodesPOSTHandler(w http.ResponseWriter, r *http.Reque
return
}
}
+ aMsg := fmt.Sprintf("tags processed: add %d, remove %d", len(t.TagsAdd), len(t.TagsRemove))
+ h.AuditLog.TagAction(ctx[sessions.CtxUser], aMsg, strings.Split(r.RemoteAddr, ":")[0], toBeProcessed[0].EnvironmentID)
// Serialize and send response
- log.Debug().Msg("Tags response sent")
adminOKResponse(w, "tags processed successfully")
}
@@ -1322,8 +1316,8 @@ func (h *HandlersAdmin) PermissionsPOSTHandler(w http.ResponseWriter, r *http.Re
return
}
}
+ h.AuditLog.UserAction(ctx[sessions.CtxUser], fmt.Sprintf("permissions - %s", usernameVar), strings.Split(r.RemoteAddr, ":")[0])
// Serialize and send response
- log.Debug().Msg("Users response sent")
adminOKResponse(w, "permissions updated successfully")
}
@@ -1423,8 +1417,8 @@ func (h *HandlersAdmin) EnrollPOSTHandler(w http.ResponseWriter, r *http.Request
}
}
}
+ h.AuditLog.EnvAction(ctx[sessions.CtxUser], fmt.Sprintf("%s - %s", e.Action, env.Name), strings.Split(r.RemoteAddr, ":")[0], env.ID)
// Serialize and send response
- log.Debug().Msg("Configuration response sent")
adminOKResponse(w, "enroll data saved")
}
@@ -1491,8 +1485,7 @@ func (h *HandlersAdmin) EditProfilePOSTHandler(w http.ResponseWriter, r *http.Re
}
adminOKResponse(w, "profiled updated successfully")
}
- // Serialize and send response
- log.Debug().Msg("Edit profile response sent")
+ h.AuditLog.UserAction(ctx[sessions.CtxUser], fmt.Sprintf("%s - %s", u.Action, u.Username), strings.Split(r.RemoteAddr, ":")[0])
}
// SavedQueriesPOSTHandler for POST requests to save queries
@@ -1520,6 +1513,4 @@ func (h *HandlersAdmin) SavedQueriesPOSTHandler(w http.ResponseWriter, r *http.R
case "edit":
adminOKResponse(w, "query saved successfully")
}
- // Serialize and send response
- log.Debug().Msg("Saved query response sent")
}
diff --git a/cmd/admin/handlers/templates.go b/cmd/admin/handlers/templates.go
index 54fd0bf9..32ac875b 100644
--- a/cmd/admin/handlers/templates.go
+++ b/cmd/admin/handlers/templates.go
@@ -9,6 +9,7 @@ import (
"strings"
"github.com/jmpsec/osctrl/cmd/admin/sessions"
+ "github.com/jmpsec/osctrl/pkg/auditlog"
"github.com/jmpsec/osctrl/pkg/carves"
"github.com/jmpsec/osctrl/pkg/environments"
"github.com/jmpsec/osctrl/pkg/nodes"
@@ -37,7 +38,6 @@ var validTarget = map[string]bool{
}
// TemplateMetadata - Helper to prepare template metadata
-// TODO until a better implementation, all users are admin
func (h *HandlersAdmin) TemplateMetadata(ctx sessions.ContextValue, metadata types.BuildMetadata, admin bool) TemplateMetadata {
return TemplateMetadata{
Username: ctx[sessions.CtxUser],
@@ -88,7 +88,7 @@ func (h *HandlersAdmin) LoginHandler(w http.ResponseWriter, r *http.Request) {
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Login template served")
+ h.AuditLog.Visit("anonymous", r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], settings.NoEnvironmentID)
}
// EnvironmentHandler for environment view of the table
@@ -169,7 +169,8 @@ func (h *HandlersAdmin) EnvironmentHandler(w http.ResponseWriter, r *http.Reques
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Environment table template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// QueryRunGETHandler for GET requests to run queries
@@ -269,7 +270,8 @@ func (h *HandlersAdmin) QueryRunGETHandler(w http.ResponseWriter, r *http.Reques
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Query run template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// QueryListGETHandler for GET requests to queries
@@ -339,7 +341,8 @@ func (h *HandlersAdmin) QueryListGETHandler(w http.ResponseWriter, r *http.Reque
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Query list template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// SavedQueriesGETHandler for GET requests to queries
@@ -403,7 +406,8 @@ func (h *HandlersAdmin) SavedQueriesGETHandler(w http.ResponseWriter, r *http.Re
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Query list template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// CarvesRunGETHandler for GET requests to run file carves
@@ -497,7 +501,8 @@ func (h *HandlersAdmin) CarvesRunGETHandler(w http.ResponseWriter, r *http.Reque
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Query run template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// CarvesListGETHandler for GET requests to carves
@@ -568,7 +573,8 @@ func (h *HandlersAdmin) CarvesListGETHandler(w http.ResponseWriter, r *http.Requ
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Carve list template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// QueryLogsHandler for GET requests to see query results by name
@@ -658,7 +664,8 @@ func (h *HandlersAdmin) QueryLogsHandler(w http.ResponseWriter, r *http.Request)
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Query logs template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// CarvesDetailsHandler for GET requests to see carves details by name
@@ -763,7 +770,8 @@ func (h *HandlersAdmin) CarvesDetailsHandler(w http.ResponseWriter, r *http.Requ
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Carve details template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// ConfGETHandler for GET requests for /conf
@@ -834,7 +842,8 @@ func (h *HandlersAdmin) ConfGETHandler(w http.ResponseWriter, r *http.Request) {
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Conf template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// EnrollGETHandler for GET requests for /enroll
@@ -929,7 +938,8 @@ func (h *HandlersAdmin) EnrollGETHandler(w http.ResponseWriter, r *http.Request)
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Enroll template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// EnrollGETHandler for GET requests for /enroll
@@ -1007,6 +1017,8 @@ func (h *HandlersAdmin) EnrollDownloadHandler(w http.ResponseWriter, r *http.Req
utils.HTTPDownload(w, description, fName, int64(len(toDownload)))
w.WriteHeader(http.StatusOK)
_, _ = io.Copy(w, bytes.NewReader(toDownload))
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// NodeHandler for node view
@@ -1126,7 +1138,8 @@ func (h *HandlersAdmin) NodeHandler(w http.ResponseWriter, r *http.Request) {
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Node template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
}
// EnvsGETHandler for GET requests for /env
@@ -1170,7 +1183,8 @@ func (h *HandlersAdmin) EnvsGETHandler(w http.ResponseWriter, r *http.Request) {
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Environments template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
}
// SettingsGETHandler for GET requests for /settings
@@ -1244,7 +1258,8 @@ func (h *HandlersAdmin) SettingsGETHandler(w http.ResponseWriter, r *http.Reques
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Settings template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
}
// UsersGETHandler for GET requests for /users
@@ -1307,7 +1322,8 @@ func (h *HandlersAdmin) UsersGETHandler(w http.ResponseWriter, r *http.Request)
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Users template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
}
// UsersTokensGETHandler for GET requests for /users/tokens
@@ -1362,7 +1378,8 @@ func (h *HandlersAdmin) UsersTokensGETHandler(w http.ResponseWriter, r *http.Req
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Users template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
}
// TagsGETHandler for GET requests for /tags
@@ -1420,7 +1437,8 @@ func (h *HandlersAdmin) TagsGETHandler(w http.ResponseWriter, r *http.Request) {
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Tags template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
}
// EditProfileGETHandler for user profile edit
@@ -1476,7 +1494,8 @@ func (h *HandlersAdmin) EditProfileGETHandler(w http.ResponseWriter, r *http.Req
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Profile template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
}
// DashboardGETHandler for dashboard page
@@ -1537,5 +1556,56 @@ func (h *HandlersAdmin) DashboardGETHandler(w http.ResponseWriter, r *http.Reque
log.Err(err).Msg("template error")
return
}
- log.Debug().Msg("Dashboard template served")
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
+}
+
+// AuditLogsGETHandler for GET requests for /audit-logs
+func (h *HandlersAdmin) AuditLogsGETHandler(w http.ResponseWriter, r *http.Request) {
+ if h.DebugHTTPConfig.Enabled {
+ utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody)
+ }
+ // Get context data
+ ctx := r.Context().Value(sessions.ContextKey(sessions.CtxSession)).(sessions.ContextValue)
+ // Check permissions
+ if !h.Users.CheckPermissions(ctx[sessions.CtxUser], users.AdminLevel, users.NoEnvironment) {
+ log.Info().Msgf("%s has insufficient permissions", ctx[sessions.CtxUser])
+ return
+ }
+ // Custom functions to handle formatting
+ funcMap := template.FuncMap{
+ "pastFutureTimes": utils.PastFutureTimes,
+ "inFutureTime": utils.InFutureTime,
+ }
+ // Prepare template
+ tempateFiles := h.NewTemplateFiles(h.TemplatesFolder, "audit.html").filepaths
+ t, err := template.New("audit.html").Funcs(funcMap).ParseFiles(tempateFiles...)
+ if err != nil {
+ log.Err(err).Msg("error getting audit log template")
+ return
+ }
+ // Get stats for all environments
+ envAll, err := h.Envs.All()
+ if err != nil {
+ log.Err(err).Msg("error getting environments")
+ return
+ }
+ // Get if the user is admin
+ user, err := h.Users.Get(ctx[sessions.CtxUser])
+ if err != nil {
+ log.Err(err).Msg("error getting user")
+ return
+ }
+ // Prepare template data
+ templateData := AuditLogTemplateData{
+ Title: "Audit logs for all environments",
+ Metadata: h.TemplateMetadata(ctx, h.ServiceMetadata, user.Admin),
+ Environments: h.allowedEnvironments(ctx[sessions.CtxUser], envAll),
+ }
+ if err := t.Execute(w, templateData); err != nil {
+ log.Err(err).Msg("template error")
+ return
+ }
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
}
diff --git a/cmd/admin/handlers/tokens.go b/cmd/admin/handlers/tokens.go
index 4d2e06b4..494d0121 100644
--- a/cmd/admin/handlers/tokens.go
+++ b/cmd/admin/handlers/tokens.go
@@ -4,8 +4,10 @@ import (
"encoding/json"
"fmt"
"net/http"
+ "strings"
"github.com/jmpsec/osctrl/cmd/admin/sessions"
+ "github.com/jmpsec/osctrl/pkg/auditlog"
"github.com/jmpsec/osctrl/pkg/users"
"github.com/jmpsec/osctrl/pkg/utils"
"github.com/rs/zerolog/log"
@@ -50,6 +52,8 @@ func (h *HandlersAdmin) TokensGETHandler(w http.ResponseWriter, r *http.Request)
ExpiresTS: utils.TimeTimestamp(user.TokenExpire),
}
}
+ // Audit log visit
+ h.AuditLog.Visit(ctx[sessions.CtxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
// Serve JSON
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, returned)
}
@@ -105,6 +109,8 @@ func (h *HandlersAdmin) TokensPOSTHandler(w http.ResponseWriter, r *http.Request
ExpirationTS: utils.TimeTimestamp(exp),
Expiration: utils.PastFutureTimes(exp),
}
+ // Audit log token creation
+ h.AuditLog.NewToken(ctx[sessions.CtxUser], strings.Split(r.RemoteAddr, ":")[0])
// Serialize and serve JSON
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, response)
}
diff --git a/cmd/admin/handlers/types-templates.go b/cmd/admin/handlers/types-templates.go
index e3f71581..ec037197 100644
--- a/cmd/admin/handlers/types-templates.go
+++ b/cmd/admin/handlers/types-templates.go
@@ -229,6 +229,14 @@ type TagsTemplateData struct {
LeftMetadata AsideLeftMetadata
}
+// AuditLogTemplateData for passing data to the audit log template
+type AuditLogTemplateData struct {
+ Title string
+ Environments []environments.TLSEnvironment
+ Metadata TemplateMetadata
+ LeftMetadata AsideLeftMetadata
+}
+
// NodeTemplateData for passing data to the query template
type NodeTemplateData struct {
Title string
diff --git a/cmd/admin/main.go b/cmd/admin/main.go
index 0e1750f9..ba5dd1d0 100644
--- a/cmd/admin/main.go
+++ b/cmd/admin/main.go
@@ -14,6 +14,7 @@ import (
"github.com/crewjam/saml/samlsp"
"github.com/jmpsec/osctrl/cmd/admin/handlers"
"github.com/jmpsec/osctrl/cmd/admin/sessions"
+ "github.com/jmpsec/osctrl/pkg/auditlog"
"github.com/jmpsec/osctrl/pkg/backend"
"github.com/jmpsec/osctrl/pkg/cache"
"github.com/jmpsec/osctrl/pkg/carves"
@@ -98,6 +99,7 @@ var (
// FIXME this is nasty and should not be a global but here we are
osqueryTables []types.OsqueryTable
handlersAdmin *handlers.HandlersAdmin
+ auditLog *auditlog.AuditLogManager
)
// SAML variables
@@ -302,6 +304,12 @@ func osctrlAdminService() {
}
}
+ // Initialize audit log manager
+ auditLog, err = auditlog.CreateAuditLogManager(db.Conn, serviceName, flagParams.AuditLog)
+ if err != nil {
+ log.Fatal().Msgf("Error initializing audit log manager - %v", err)
+ }
+
// Initialize Admin handlers before router
log.Info().Msg("Initializing handlers")
handlersAdmin = handlers.CreateHandlersAdmin(
@@ -327,6 +335,7 @@ func osctrlAdminService() {
handlers.WithCarvesFolder(flagParams.CarvedDir),
handlers.WithOptimizedUI(flagParams.OptimizeUI),
handlers.WithAdminConfig(&flagParams.ConfigValues),
+ handlers.WithAuditLog(auditLog),
handlers.WithDBLogger(flagParams.LoggerFile, loggerDBConfig),
handlers.WithDebugHTTP(&flagParams.DebugHTTPValues),
)
@@ -397,6 +406,12 @@ func osctrlAdminService() {
adminMux.Handle(
"GET /json/tags",
handlerAuthCheck(http.HandlerFunc(handlersAdmin.JSONTagsHandler), flagParams.ConfigValues.Auth))
+ // Admin: JSON data for audit logs
+ if flagParams.AuditLog {
+ adminMux.Handle(
+ "GET /json/audit-logs",
+ handlerAuthCheck(http.HandlerFunc(handlersAdmin.JSONAuditLogHandler), flagParams.ConfigValues.Auth))
+ }
// Admin: table for environments
adminMux.Handle(
"GET /environment/{env}/{target}",
@@ -541,6 +556,12 @@ func osctrlAdminService() {
adminMux.Handle(
"POST /profile",
handlerAuthCheck(http.HandlerFunc(handlersAdmin.EditProfilePOSTHandler), flagParams.ConfigValues.Auth))
+ // Admin: audit logs
+ if flagParams.AuditLog {
+ adminMux.Handle(
+ "GET /audit-logs",
+ handlerAuthCheck(http.HandlerFunc(handlersAdmin.AuditLogsGETHandler), flagParams.ConfigValues.Auth))
+ }
// Admin: dashboard and search bar
adminMux.Handle(
"GET /dashboard",
diff --git a/cmd/admin/templates/audit.html b/cmd/admin/templates/audit.html
new file mode 100644
index 00000000..15291dd4
--- /dev/null
+++ b/cmd/admin/templates/audit.html
@@ -0,0 +1,179 @@
+
+
+
+ {{ $metadata := .Metadata }}
+ {{ $leftmeta := .LeftMetadata }}
+
+ {{ template "page-head" . }}
+
+
+
+ {{ template "page-header" . }}
+
+
+
+ {{ template "page-aside-left" . }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Service |
+ User |
+ Log |
+ IP Address |
+ LogType |
+ Severity |
+ Environment |
+ When |
+
+
+
+
+
+
+ {{ template "page-modals" . }}
+
+
+
+
+
+ {{ if $metadata.Admin }}
+ {{ template "page-aside-right" . }}
+ {{ end }}
+
+
+
+ {{ template "page-js" . }}
+
+
+
+
+
+
diff --git a/cmd/admin/templates/components/page-aside-right.html b/cmd/admin/templates/components/page-aside-right.html
index 25247736..d187e104 100644
--- a/cmd/admin/templates/components/page-aside-right.html
+++ b/cmd/admin/templates/components/page-aside-right.html
@@ -17,6 +17,16 @@ Admin Server Settings
Settings for the Admin service
+
+
+
+
+
+
+ View the Audit Logs
+