From f68bd9637d2215a3c7b4dfcd11d3aab36b32f38c Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Wed, 17 Jan 2024 12:20:01 +1100 Subject: [PATCH] add event log retrieval --- README.md | 9 ++++++++- controllers/auth.go | 4 ++-- controllers/retrieveAudits.go | 23 ++++++++++++++++++++++ controllers/retrieveSecrets.go | 4 ++-- controllers/storeSecrets.go | 6 +++--- main.go | 1 + models/audit.go | 36 ++++++++++++++++++++++++++++++---- models/user.go | 4 ++++ 8 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 controllers/retrieveAudits.go diff --git a/README.md b/README.md index af7ea6a..4d7f5c1 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,9 @@ Body ``` This API call will return a JWT token that must be present for any other API calls to succeed. The validity duration of this token is based on the configured TOKEN_HOUR_LIFESPAN value. JWT token is returned as value of `access_token`, and must be supplied via a HTTP header in the form `"Authorization: Bearer "` for all subsequent API calls. -### Unlock +#### Admin Only operations + +#### Unlock **POST** `/api/admin/unlock` Body @@ -106,6 +108,11 @@ If the SECRETS_KEY environment variable is not defined, this API call to unlock This API call can only be made once after the service has started. Subsequent calls will receive an error until the service is restarted. +#### Event Logs +**GET** `/api/admin/logs` + +This operation can only be performed by a user with that is admin enabled. Lists all event logs. + ### User Operations #### Register User diff --git a/controllers/auth.go b/controllers/auth.go index 6d917f1..a87616f 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -82,7 +82,7 @@ func DeleteUser(c *gin.Context) { UserId: RequestingUserId, EventText: fmt.Sprintf("Deleted User Id %d", testUser.UserId), } - a.AuditAdd() + a.AutidLogAdd() c.JSON(http.StatusOK, gin.H{"message": "user deletion success"}) } @@ -183,7 +183,7 @@ func AddUser(c *gin.Context) { UserId: RequestingUserId, EventText: fmt.Sprintf("Created User Id %d", u.UserId), } - a.AuditAdd() + a.AutidLogAdd() c.JSON(http.StatusOK, gin.H{"message": "user registration success", "data": u}) } diff --git a/controllers/retrieveAudits.go b/controllers/retrieveAudits.go new file mode 100644 index 0000000..2f61670 --- /dev/null +++ b/controllers/retrieveAudits.go @@ -0,0 +1,23 @@ +package controllers + +import ( + "fmt" + "log" + "net/http" + "smt/models" + + "github.com/gin-gonic/gin" +) + +func GetAuditLogsHandler(c *gin.Context) { + logs, err := models.AuditLogList() + + if err != nil { + errString := fmt.Sprintf("error retrieving audit logs : '%s'", err) + log.Printf("GetAuditLogsHandler %s\n", errString) + c.JSON(http.StatusBadRequest, gin.H{"error": errString}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "success", "data": logs}) +} diff --git a/controllers/retrieveSecrets.go b/controllers/retrieveSecrets.go index 546ce0a..eb9c249 100644 --- a/controllers/retrieveSecrets.go +++ b/controllers/retrieveSecrets.go @@ -164,7 +164,7 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) { UserId: UserId, EventText: fmt.Sprintf("Retrieved Secret Id %d", results[0].SecretId), } - a.AuditAdd() + a.AutidLogAdd() // output results as json c.JSON(http.StatusOK, gin.H{"message": "success", "data": results}) @@ -210,7 +210,7 @@ func ListSecrets(c *gin.Context) { UserId: UserId, EventText: fmt.Sprintf("Listed %d secrets, %+v", len(output), s), } - a.AuditAdd() + a.AutidLogAdd() // output results as json c.JSON(http.StatusOK, gin.H{"message": "success", "data": output}) diff --git a/controllers/storeSecrets.go b/controllers/storeSecrets.go index 6356fde..415d2c3 100644 --- a/controllers/storeSecrets.go +++ b/controllers/storeSecrets.go @@ -149,7 +149,7 @@ func StoreSecret(c *gin.Context) { UserId: UserId, EventText: fmt.Sprintf("Created Secret Id %d", s.SecretId), } - a.AuditAdd() + a.AutidLogAdd() c.JSON(http.StatusOK, gin.H{"message": "secret stored successfully", "data": models.SecretRestricted(s)}) } @@ -370,7 +370,7 @@ func UpdateSecret(c *gin.Context) { UserId: UserId, EventText: fmt.Sprintf("Updated Secret Id %d", s.SecretId), } - a.AuditAdd() + a.AutidLogAdd() c.JSON(http.StatusOK, gin.H{"message": "secret updated successfully", "data": models.SecretRestricted(s)}) } else { @@ -460,7 +460,7 @@ func DeleteSecret(c *gin.Context) { UserId: UserId, EventText: fmt.Sprintf("Deleted Secret Id %d", s.SecretId), } - a.AuditAdd() + a.AutidLogAdd() c.JSON(http.StatusOK, gin.H{"message": "secret deleted successfully"}) } else { diff --git a/main.go b/main.go index 752bb33..f473249 100644 --- a/main.go +++ b/main.go @@ -269,6 +269,7 @@ func main() { // Other functions for admin adminOnly.POST("/unlock", controllers.Unlock) + adminOnly.GET("/logs", controllers.GetAuditLogsHandler) // Get secrets secretRoutes := router.Group("/api/secret") diff --git a/models/audit.go b/models/audit.go index 3388059..3db0c1c 100644 --- a/models/audit.go +++ b/models/audit.go @@ -14,8 +14,8 @@ type Audit struct { EventTime time.Time `db:"EventTime" json:"eventTime"` } -// AuditAdd adds a new audit record to the database -func (a *Audit) AuditAdd() (*Audit, error) { +// AutidLogAdd adds a new audit record to the database +func (a *Audit) AutidLogAdd() (*Audit, error) { var err error // Populate timestamp field if not already set @@ -26,14 +26,42 @@ func (a *Audit) AuditAdd() (*Audit, error) { result, err := db.NamedExec(("INSERT INTO audit (UserId, SecretId, EventText, EventTime) VALUES (:UserId, :SecretId, :EventText, :EventTime);"), a) if err != nil { - log.Printf("AuditAdd error executing sql record : '%s'\n", err) + log.Printf("AutidLogAdd error executing sql record : '%s'\n", err) return &Audit{}, err } else { affected, _ := result.RowsAffected() id, _ := result.LastInsertId() a.AuditId = int(id) - log.Printf("AuditAdd insert returned result id '%d' affecting %d row(s).\n", id, affected) + log.Printf("AutidLogAdd insert returned result id '%d' affecting %d row(s).\n", id, affected) } return a, nil } + +// AuditList returns a list of all audit logs in database +func AuditLogList() ([]Audit, error) { + var results []Audit + + // Query database for groups + rows, err := db.Queryx("SELECT * FROM audit") + + if err != nil { + log.Printf("AuditLogList error executing sql record : '%s'\n", err) + return results, err + } else { + // parse all the results into a slice + for rows.Next() { + var a Audit + err = rows.StructScan(&a) + if err != nil { + log.Printf("AuditLogList error parsing sql record : '%s'\n", err) + return results, err + } + results = append(results, a) + + } + log.Printf("AuditLogList retrieved '%d' results\n", len(results)) + } + + return results, nil +} diff --git a/models/user.go b/models/user.go index c240bf9..746e4b5 100644 --- a/models/user.go +++ b/models/user.go @@ -261,6 +261,10 @@ func UserLdapNewLoginCheck(username string, password string) (User, error) { matchFound := false for _, group := range groupList { + // Skip any groups that aren't LDAP groups + if len(group.LdapDn) == 0 { + continue + } for _, lg := range ldapGroups { if group.LdapDn == lg { log.Printf("Found match with groupname '%s' and LDAP group DN '%s', user is a member of group ID '%d'\n", group.GroupName, group.LdapDn, group.GroupId)