63 Commits
0.3.3 ... main

Author SHA1 Message Date
2754fb8144 revert css to customised but older
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-04 15:02:21 +10:00
4650a971a3 css update
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-04 14:54:05 +10:00
8cd1292a41 update packages
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-04 14:46:39 +10:00
ea5198a5b9 bugfix
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-11 21:01:06 +10:00
d000469836 golang updates
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-18 11:08:55 +10:00
02061f5b26 reduce logging
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-19 13:52:08 +10:00
ea3e8ddfbc re-add cache rebuild
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-05 09:47:54 +10:00
8182b899cf upgrade dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-05 09:45:12 +10:00
ae5b864feb update for new dell site
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-15 08:24:05 +10:00
4a6a7270f9 improve input checking when retrieving secret
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-08 08:36:20 +10:00
a7beb94341 ftps doesnt seem t owork
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-08 14:06:37 +10:00
4a0e98bab8 test secure ftp
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-08 13:59:00 +10:00
66a1917e6f return updated permission details in UpdatePermissionHandler
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-04 08:56:14 +11:00
526161f6b4 bugfix permission delete when only permissionId specified
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-03 11:10:36 +11:00
ff16acc816 add support to update permissions
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-03 10:42:11 +11:00
ee822b5c9d if username in UPN format for login try searching both user and full UPN string
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 16:55:11 +11:00
e427184310 test logic fix for ldap users not in an ldap group
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 15:11:24 +11:00
5719ce8f5d creating ldap user was not setting ldapuser flag
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 14:41:15 +11:00
a78f2b7c88 improve adding ldap user
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 14:13:21 +11:00
1171a7bbaa update readme
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-13 11:26:55 +11:00
8ff92e206e include count in listsecrets
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:33:08 +11:00
19ffc9e683 log schema
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:23:23 +11:00
968bcf1b7a fix sql statement
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:22:14 +11:00
77d063867a set schema version better
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:20:52 +11:00
c82bffe421 try using schema version better
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:17:38 +11:00
b801563074 cleanup
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:11:08 +11:00
4bc430633e fix audit message
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:09:02 +11:00
69a25fbb09 add username to secret retrieve audit log
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 11:48:11 +11:00
840b9f4863 include go version
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 10:06:14 +11:00
5b87ef0d30 whoops
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 10:04:08 +11:00
bfc734a6d1 update golang version
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 10:03:33 +11:00
b5c9b5ce19 more audit logs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-22 19:05:26 +11:00
9f0dafd4fd remove artificial restriction on multiple secret retrieval
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-22 16:27:06 +11:00
abaa291a14 add search by username
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-22 16:13:35 +11:00
de1a076d64 update docs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 11:22:57 +11:00
1b5a2e89dd db fix
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 11:01:38 +11:00
8799f0f796 add client IP to audit logs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 10:56:37 +11:00
317e0ab83d bugfix
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 10:40:27 +11:00
116a9e827b fix extra comma
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 09:21:44 +11:00
2ab6240a24 add lastlogin for user
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 09:17:31 +11:00
bb3bf3093d add missing secretId to audit logs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-17 14:40:36 +11:00
1d1aa098a9 change eventlog message
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-17 14:38:24 +11:00
f68bd9637d add event log retrieval
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-17 12:20:01 +11:00
5f63ee235b remove some logging containing hashes
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:26:04 +11:00
092fe32baf missed one
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:21:38 +11:00
44d3bc71ed remove extra debug statements
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:20:46 +11:00
dc3c5d1068 test new PrintStructContents
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:18:46 +11:00
d834a5c362 blah
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:16:37 +11:00
9e0c1e7cd7 blah
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:12:36 +11:00
9ac729b684 more testing
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:10:26 +11:00
7f43662cbc try debugging the debugging
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:05:48 +11:00
b35d365467 test
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:02:17 +11:00
77d487c1ce blah
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:00:53 +11:00
f5827ef432 debug
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:58:37 +11:00
6e1a28d2df try this
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:55:28 +11:00
5c3b2e19cf another fix
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:49:37 +11:00
e109cd084d try again to fix lastupdated
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:40:51 +11:00
f29c733080 fix LastUpdated default value
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:35:28 +11:00
97cd75b0d7 fix lastupdated definition
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:22:45 +11:00
b278a3c7d8 add last updated tracking for secrets
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:21:41 +11:00
498dd9a8c3 allow use of secretId when performing operations on secrets
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:08:40 +11:00
c99ffa8368 resume dell deploy
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 15:19:30 +11:00
8fec84c118 another fix
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 15:18:42 +11:00
20 changed files with 1099 additions and 510 deletions

View File

@@ -81,31 +81,29 @@ steps:
- sudo bash -c 'mv /home/l075239/smt/test.env /home/l075239/smt/.env' - sudo bash -c 'mv /home/l075239/smt/test.env /home/l075239/smt/.env'
- sudo bash -c '/etc/init.d/smt restart' - sudo bash -c '/etc/init.d/smt restart'
- name: dell-deploy
# # https://github.com/cschlosser/drone-ftps/blob/master/README.md - name: dell-sftp-deploy
image: cschlosser/drone-ftps image: hypervtechnics/drone-sftp
when: settings:
event: host: deft.dell.com
- tag username:
environment: from_secret: DELLFTP_USER
FTP_USERNAME: password:
from_secret: FTP_USERNAME from_secret: DELLFTP_PASS
FTP_PASSWORD: port: 22
from_secret: FTP_PASSWORD source: ./
PLUGIN_HOSTNAME: ftp.emc.com:21 filter: smt*
PLUGIN_SECURE: false clean: false
PLUGIN_VERIFY: false target: /
PLUGIN_CHMOD: false overwrite: true
#PLUGIN_DEBUG: false verbose: true
PLUGIN_INCLUDE: ^smt$,^smt_checksum.txt$
PLUGIN_EXCLUDE: ^\.git/$,^\controllers/$,^\middlewares/$,^\models/$,^\utils/$,^\pkg.build/$,^\pkg.mod/$,^\www/$
- name: rebuild-cache-with-filesystem - name: rebuild-cache-with-filesystem
image: meltwater/drone-cache image: meltwater/drone-cache
pull: true pull: true
when: #when:
event: # event:
- tag # - tag
settings: settings:
backend: "filesystem" backend: "filesystem"
#debug: true #debug: true

117
README.md
View File

@@ -6,6 +6,8 @@ Build Date: `{BUILDTIME}`
Build Hash: `{SHA1VER}` Build Hash: `{SHA1VER}`
Go version: `{RUNTIME}`
Written by Nathan Coad (nathan.coad@dell.com) Written by Nathan Coad (nathan.coad@dell.com)
## Overview ## Overview
@@ -80,6 +82,10 @@ WantedBy=multi-user.target
``` ```
## API Usage ## API Usage
API calls return http status code of **200** if successful, or **4xx** if unsuccessful. API calls that are unsuccessful will also include a JSON response with the key `error` and a value of the reason for the failure. Successful API calls will include a `message` key with a value of either success or something more detailed such as "user deletion success"
API calls that create or modify a record will include the created/updated record in the JSON response.
### Login ### Login
**POST** `/api/login` **POST** `/api/login`
@@ -92,7 +98,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 <JWT_TOKEN>"` for all subsequent API calls. 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 <JWT_TOKEN>"` for all subsequent API calls.
### Unlock #### Admin Only operations
#### Unlock
**POST** `/api/admin/unlock` **POST** `/api/admin/unlock`
Body Body
@@ -106,6 +114,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. 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 ### User Operations
#### Register User #### Register User
@@ -133,6 +146,17 @@ Body
} }
``` ```
Add an ldap user
Body
```
{
"userName": "Ldap User",
"groupName": "Users",
"ldapUser": true
}
```
Registering a user requires specifying the group to which the user will belong. There are 2 built-in groups, with groupName of 'Administrators' or 'Users' and corresponding groupId of 1 and 2 respectively. Available groups can be retrieved via the `/api/admin/groups/list` Registering a user requires specifying the group to which the user will belong. There are 2 built-in groups, with groupName of 'Administrators' or 'Users' and corresponding groupId of 1 and 2 respectively. Available groups can be retrieved via the `/api/admin/groups/list`
This operation can only be performed by a user that is a member of a group with the admin flag enabled, or a user who has the admin flag enabled individually on their database record. This operation can only be performed by a user that is a member of a group with the admin flag enabled, or a user who has the admin flag enabled individually on their database record.
@@ -178,6 +202,17 @@ Body
} }
``` ```
Create a new permission for a group
Body
```
{
"Description": "Group access to default safe",
"safeId": 1,
"groupId": 1
}
```
Creates a new permission mapping user/group to safe. Currently the create permission operation requires knowing the correct user Id or group Id, as well as the safe Id onto which permissions will be granted. This operation can only be performed by a user that is admin enabled. Creates a new permission mapping user/group to safe. Currently the create permission operation requires knowing the correct user Id or group Id, as well as the safe Id onto which permissions will be granted. This operation can only be performed by a user that is admin enabled.
#### Delete Permission #### Delete Permission
@@ -189,7 +224,7 @@ Delete permission by specifying description
Body Body
``` ```
{ {
"Description":"Readonly access to default safe" "Description": "Readonly access to default safe"
} }
``` ```
@@ -199,7 +234,7 @@ Delete permission by specifying permission id
Body Body
``` ```
{ {
"permissionId":2 "permissionId": 2
} }
``` ```
@@ -208,6 +243,22 @@ Deletes a permission mapping either a user or a group to a safe. Either the perm
Deleting a permission should be performed prior to deleting any groups specified in that permission. Deleting a permission should be performed prior to deleting any groups specified in that permission.
#### Update Permission
**POST** `/api/admin/permission/update`
Change description of permission
Body
```
{
"permissionId": 2
"Description": "New Permission Description"
}
```
Updates an existing permission. The permissionId must be specified. This operation can only be performed by a user that is admin enabled.
### Group Operations ### Group Operations
#### List Groups #### List Groups
@@ -318,7 +369,7 @@ This operation can only be performed by a user that is admin enabled, or that is
### Secrets Operations ### Secrets Operations
#### Store #### Store Secret
**POST** `/api/secret/add` **POST** `/api/secret/add`
Store secret if user only has access to a single safe Store secret if user only has access to a single safe
@@ -351,39 +402,55 @@ If a secret exists with a matching deviceName and deviceCategory in a safe that
If the current user has access to multiple safes, then the destination safeId will also need to be specified. If the current user has access to multiple safes, then the destination safeId will also need to be specified.
#### Retrieve #### Get Secret
**POST** `/api/secret/get` **POST** `/api/secret/get`
Body Body
``` ```
{ {
"deviceName": "", "deviceName": "device.example.com",
"deviceCategory": "", "deviceCategory": "",
"userName": "" "userName": "example-user"
} }
``` ```
Must be logged in to execute this command. Only secrets registered with the current user's RoleId can be retrieved. Body
```
{
"secretId": 29
}
```
Must be logged in to execute this command. Only secrets that the logged in user has access to can be retrieved.
Either deviceName or deviceCategory can be specified (or both). Wildcards are supported for both deviceName and deviceCategory fields. userName can also be specified in conjunction with deviceName or deviceCategory. Either deviceName or deviceCategory can be specified (or both). Wildcards are supported for both deviceName and deviceCategory fields. userName can also be specified in conjunction with deviceName or deviceCategory.
1. The percent sign % wildcard matches any sequence of zero or more characters. 1. The percent sign % wildcard matches any sequence of zero or more characters.
2. The underscore _ wildcard matches any single character. 2. The underscore _ wildcard matches any single character.
If the secretId is known, that can also be used to query for the secret. In this case the secretId uniquely identifies the secret so no other parameters are necessary.
#### Search by device name #### Search by device name
**GET** `/api/secret/retrieve/name/<searchname>` **GET** `/api/secret/retrieve/name/<searchname>`
Search for a secret specified by deviceName using a GET request. Search for a secret specified by deviceName using a GET request.
Must be logged in to execute this command. Only secrets registered with the current user's RoleId can be retrieved. Must be logged in to execute this command. Only secrets in safes that the current user can access can be retrieved.
#### Search by device category #### Search by device category
**GET** `/api/secret/retrieve/category/<searchname>` **GET** `/api/secret/retrieve/category/<searchname>`
Search for a secret specified by deviceCategory using a GET request. Search for a secret specified by deviceCategory using a GET request.
Must be logged in to execute this command. Only secrets registered with the current user's RoleId can be retrieved. Must be logged in to execute this command. Only secrets in safes that the current user can access can be retrieved.
#### Update #### Search by username
**GET** `/api/secret/retrieve/user/<searchname>`
Search for a secret specified by userName using a GET request.
Must be logged in to execute this command. Only secrets in safes that the current user can access can be retrieved.
#### Update Secret
**POST** `/api/secret/update` **POST** `/api/secret/update`
Update secret value for existing secret record Update secret value for existing secret record
@@ -415,10 +482,36 @@ The values specified in deviceName and deviceCategory must match exactly one exi
If a user has read-write access to multiple safes, then specifying a different safeId to the one currently holding the secret will allow the secret to be moved into the other safe. If a user has read-write access to multiple safes, then specifying a different safeId to the one currently holding the secret will allow the secret to be moved into the other safe.
#### List #### List Secrets
**GET** `/api/secret/list` **GET** `/api/secret/list`
Will generate a list of secrets with their secretId, userName, deviceCategory and deviceName fields, but not secret data. Only secrets belonging to safes that are accessible by the currently logged in user will be returned Will generate a list of secrets with their secretId, userName, deviceCategory and deviceName fields, but not secret data. Only secrets belonging to safes that are accessible by the currently logged in user will be returned
#### Delete Secret
**POST** `/api/secret/delete`
Body
```
{
"deviceName": "device.example.com",
"deviceCategory": "",
"userName": "example-user"
}
```
Body
```
{
"secretId": 29
}
```
Deletes specified secret. User must have read-write access to the safe the secret is stored in.
Secret can be specified either by the secretId, or a unique combination of deviceName, deviceCategory and userName.
## Database Schema ## Database Schema
![Diagram](www/database.png) ![Diagram](www/database.png)

View File

@@ -17,9 +17,10 @@ import (
type AddUserInput struct { type AddUserInput struct {
UserName string `json:"userName" binding:"required"` UserName string `json:"userName" binding:"required"`
Password string `json:"password" binding:"required"` Password string `json:"password"`
GroupId int `json:"groupId"` GroupId int `json:"groupId"`
GroupName string `json:"groupName"` GroupName string `json:"groupName"`
LdapUser bool `json:"ldapUser"`
//RoleId int `json:"roleid"` //RoleId int `json:"roleid"`
} }
@@ -80,9 +81,10 @@ func DeleteUser(c *gin.Context) {
// Create audit record // Create audit record
a := models.Audit{ a := models.Audit{
UserId: RequestingUserId, UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Deleted User Id %d", testUser.UserId), EventText: fmt.Sprintf("Deleted User Id %d", testUser.UserId),
} }
a.AuditAdd() a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "user deletion success"}) c.JSON(http.StatusOK, gin.H{"message": "user deletion success"})
} }
@@ -98,12 +100,17 @@ func AddUser(c *gin.Context) {
} }
if len(input.UserName) == 0 { if len(input.UserName) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified"}) c.JSON(http.StatusBadRequest, gin.H{"error": "username must be specified"})
return return
} }
if len(input.Password) == 0 { if len(input.Password) == 0 && !input.LdapUser {
c.JSON(http.StatusBadRequest, gin.H{"error": "no password specified"}) c.JSON(http.StatusBadRequest, gin.H{"error": "password must be specified for non-ldap user"})
return
}
if input.LdapUser && len(input.Password) > 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "password should not be specified for ldap user"})
return return
} }
@@ -117,6 +124,7 @@ func AddUser(c *gin.Context) {
u := models.User{} u := models.User{}
u.UserName = input.UserName u.UserName = input.UserName
u.Password = input.Password u.Password = input.Password
u.LdapUser = input.LdapUser
// Determine which GroupId to save // Determine which GroupId to save
// Can be specified either by GroupName or GroupId in the request // Can be specified either by GroupName or GroupId in the request
@@ -160,18 +168,20 @@ func AddUser(c *gin.Context) {
return return
} }
//turn password into hash //turn password into hash if defined
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) if len(input.Password) > 0 {
if err != nil { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
c.JSON(http.StatusBadRequest, gin.H{"Error hashing password": err.Error()}) if err != nil {
return c.JSON(http.StatusBadRequest, gin.H{"Error hashing password": err.Error()})
} else { return
//log.Printf("Register generated hashed password value '%s' from '%s'\n", string(hashedPassword), input.Password) } else {
log.Printf("Register generated hashed password value '%s'\n", string(hashedPassword)) //log.Printf("Register generated hashed password value '%s' from '%s'\n", string(hashedPassword), input.Password)
log.Printf("Register generated hashed password value '%s'\n", string(hashedPassword))
}
u.Password = string(hashedPassword)
} }
u.Password = string(hashedPassword)
_, err = u.SaveUser() _, err := u.SaveUser()
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"Error saving user": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"Error saving user": err.Error()})
@@ -181,9 +191,10 @@ func AddUser(c *gin.Context) {
// Create audit record // Create audit record
a := models.Audit{ a := models.Audit{
UserId: RequestingUserId, UserId: RequestingUserId,
EventText: fmt.Sprintf("Created User Id %d", u.UserId), IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Created User '%s' with id %d", u.UserName, u.UserId),
} }
a.AuditAdd() a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "user registration success", "data": u}) c.JSON(http.StatusOK, gin.H{"message": "user registration success", "data": u})
} }
@@ -210,7 +221,8 @@ func Login(c *gin.Context) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "username or password is incorrect."}) c.JSON(http.StatusUnauthorized, gin.H{"error": "username or password is incorrect."})
return return
} else { } else {
log.Printf("Login verified, returning token '%s'\n", token) //log.Printf("Login verified, returning token '%s'\n", token)
log.Printf("Login verified, returning token\n")
} }
c.JSON(http.StatusOK, gin.H{"access_token": token}) c.JSON(http.StatusOK, gin.H{"access_token": token})

View File

@@ -34,6 +34,7 @@ func GetGroupsHandler(c *gin.Context) {
func AddGroupHandler(c *gin.Context) { func AddGroupHandler(c *gin.Context) {
var input GroupInput var input GroupInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil { if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -50,6 +51,13 @@ func AddGroupHandler(c *gin.Context) {
return return
} }
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
g := models.Group{} g := models.Group{}
g.GroupName = input.GroupName g.GroupName = input.GroupName
g.LdapGroup = input.LdapGroup g.LdapGroup = input.LdapGroup
@@ -90,6 +98,14 @@ func AddGroupHandler(c *gin.Context) {
// Verification checks passed, return group // Verification checks passed, return group
group, err := g.GroupAdd() group, err := g.GroupAdd()
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Created Group '%s' with id %d", g.GroupName, g.GroupId),
}
a.AuditLogAdd()
if err != nil { if err != nil {
errString := fmt.Sprintf("error creating group : '%s'", err) errString := fmt.Sprintf("error creating group : '%s'", err)
log.Printf("AddGroupHandler %s\n", errString) log.Printf("AddGroupHandler %s\n", errString)
@@ -102,6 +118,7 @@ func AddGroupHandler(c *gin.Context) {
func DeleteGroupHandler(c *gin.Context) { func DeleteGroupHandler(c *gin.Context) {
var input GroupInput var input GroupInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil { if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -114,6 +131,13 @@ func DeleteGroupHandler(c *gin.Context) {
return return
} }
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
g := models.Group{} g := models.Group{}
g.GroupId = input.GroupId g.GroupId = input.GroupId
g.GroupName = input.GroupName g.GroupName = input.GroupName
@@ -152,6 +176,14 @@ func DeleteGroupHandler(c *gin.Context) {
err := g.GroupDelete() err := g.GroupDelete()
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Deleted Group '%s' with id %d", g.GroupName, g.GroupId),
}
a.AuditLogAdd()
if err != nil { if err != nil {
errString := fmt.Sprintf("error deleting group : '%s'", err) errString := fmt.Sprintf("error deleting group : '%s'", err)
log.Printf("DeleteGroupHandler %s\n", errString) log.Printf("DeleteGroupHandler %s\n", errString)

View File

@@ -6,6 +6,7 @@ import (
"log" "log"
"net/http" "net/http"
"smt/models" "smt/models"
"smt/utils"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -35,6 +36,7 @@ func GetPermissionsHandler(c *gin.Context) {
func AddPermissionHandler(c *gin.Context) { func AddPermissionHandler(c *gin.Context) {
var input PermissionInput var input PermissionInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil { if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -55,6 +57,13 @@ func AddPermissionHandler(c *gin.Context) {
return return
} }
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
p := models.Permission{ p := models.Permission{
PermissionId: input.PermissionId, PermissionId: input.PermissionId,
Description: input.Description, Description: input.Description,
@@ -82,6 +91,14 @@ func AddPermissionHandler(c *gin.Context) {
_, err := p.PermissionAdd() _, err := p.PermissionAdd()
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Created Permission '%s' with id %d on safe id %d for group id %d or user id %d", p.Description, p.PermissionId, p.SafeId, p.GroupId, p.UserId),
}
a.AuditLogAdd()
if err != nil { if err != nil {
errString := fmt.Sprintf("error creating permission : '%s'", err) errString := fmt.Sprintf("error creating permission : '%s'", err)
log.Printf("AddPermissionHandler %s\n", errString) log.Printf("AddPermissionHandler %s\n", errString)
@@ -94,6 +111,7 @@ func AddPermissionHandler(c *gin.Context) {
func DeletePermissionHandler(c *gin.Context) { func DeletePermissionHandler(c *gin.Context) {
var input PermissionInput var input PermissionInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil { if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -108,6 +126,13 @@ func DeletePermissionHandler(c *gin.Context) {
return return
} }
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
p := models.Permission{ p := models.Permission{
PermissionId: input.PermissionId, PermissionId: input.PermissionId,
Description: input.Description, Description: input.Description,
@@ -121,23 +146,122 @@ func DeletePermissionHandler(c *gin.Context) {
p.Description = html.EscapeString(strings.TrimSpace(p.Description)) p.Description = html.EscapeString(strings.TrimSpace(p.Description))
// Check if permission definition already exists // Check if permission definition already exists
testPermission, _ := models.PermissionGetByDesc(p.Description) if len(p.Description) > 0 {
log.Printf("DeletePermissionHandler confirming permission with description '%s' exists\n", p.Description) log.Printf("DeletePermissionHandler confirming permission with description '%s' exists\n", p.Description)
if (models.Permission{} == testPermission) { testPermission, _ := models.PermissionGetByDesc(p.Description)
errString := fmt.Sprintf("attempt to delete non-existing permission with description '%s'", p.Description)
log.Printf("DeletePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
} else {
err := p.PermissionDelete()
if err != nil { if (models.Permission{} == testPermission) {
errString := fmt.Sprintf("error deleting permission : '%s'", err) errString := fmt.Sprintf("attempt to delete non-existing permission with description '%s'", p.Description)
log.Printf("DeletePermissionHandler %s\n", errString) log.Printf("DeletePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString}) c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return return
} }
} else {
log.Printf("DeletePermissionHandler confirming permission with id '%d' exists\n", p.PermissionId)
testPermission, _ := models.PermissionGetById(p.PermissionId)
c.JSON(http.StatusOK, gin.H{"message": "permission deletion success"}) if (models.Permission{} == testPermission) {
errString := fmt.Sprintf("attempt to delete non-existing permission with id '%d'", p.PermissionId)
log.Printf("DeletePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
} }
err := p.PermissionDelete()
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Deleted Permission '%s' with id %d", p.Description, p.PermissionId),
}
a.AuditLogAdd()
if err != nil {
errString := fmt.Sprintf("error deleting permission : '%s'", err)
log.Printf("DeletePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
c.JSON(http.StatusOK, gin.H{"message": "permission deletion success"})
}
func UpdatePermissionHandler(c *gin.Context) {
var input PermissionInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Input validation
if input.PermissionId == 0 {
errString := "must specify permission id"
log.Printf("UpdatePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
// Check specified permission currently exists
currentPermission, err := models.PermissionGetById(input.PermissionId)
if err != nil {
errString := fmt.Sprintf("error querying existing permission : '%s'", err)
log.Printf("UpdatePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
if (models.Permission{} == currentPermission) {
errString := fmt.Sprintf("no permission id '%d' found", input.PermissionId)
log.Printf("UpdatePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
// create new struct with values supplied by user
newPermission := models.Permission{
PermissionId: input.PermissionId,
Description: input.Description,
ReadOnly: input.ReadOnly,
SafeId: input.SafeId,
UserId: input.UserId,
GroupId: input.GroupId,
}
//remove leading/trailing spaces in permission description
newPermission.Description = html.EscapeString(strings.TrimSpace(newPermission.Description))
// Copy newPermission into currentPermission
utils.UpdateStruct(&currentPermission, &newPermission)
// run the database update
_, err = currentPermission.PermissionUpdate()
if err != nil {
errString := fmt.Sprintf("error updating permission : '%s'", err)
log.Printf("UpdatePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
//create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Updated Permission '%s' with id %d", currentPermission.Description, currentPermission.PermissionId),
}
a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "permission update success", "data": currentPermission})
} }

View File

@@ -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})
}

View File

@@ -5,11 +5,13 @@ import (
"log" "log"
"net/http" "net/http"
"smt/models" "smt/models"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type RetrieveInput struct { type RetrieveInput struct {
SecretId int `json:"secretId"`
DeviceName string `json:"deviceName"` DeviceName string `json:"deviceName"`
DeviceCategory string `json:"deviceCategory"` DeviceCategory string `json:"deviceCategory"`
UserName string `json:"userName"` UserName string `json:"userName"`
@@ -18,42 +20,43 @@ type RetrieveInput struct {
} }
type ListSecret struct { type ListSecret struct {
SecretId int `db:"SecretId" json:"secretId"` SecretId int `db:"SecretId" json:"secretId"`
SafeId int `db:"SafeId" json:"safeId"` SafeId int `db:"SafeId" json:"safeId"`
DeviceName string `db:"DeviceName" json:"deviceName"` DeviceName string `db:"DeviceName" json:"deviceName"`
DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"` DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"`
UserName string `db:"UserName" json:"userName"` UserName string `db:"UserName" json:"userName"`
Secret string `db:"Secret" json:"-"` Secret string `db:"Secret" json:"-"`
LastUpdated time.Time `db:"LastUpdated" json:"lastUpdated"`
} }
func RetrieveSecret(c *gin.Context) { func RetrieveSecret(c *gin.Context) {
var input RetrieveInput var input RetrieveInput
//var results []models.Secret
//var userIsAdmin bool = false
// Validate the input matches our struct // Validate the input matches our struct
if err := c.ShouldBindJSON(&input); err != nil { if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
log.Printf("RetrieveSecret received JSON input '%v'\n", input) //log.Printf("RetrieveSecret received JSON input '%v'\n", input)
/*
// Get the user and role id of the requestor
u, err := models.UserGetRoleFromToken(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
*/
// Populate fields // Populate fields
s := models.Secret{} s := models.Secret{}
//s.RoleId = u.RoleId
s.DeviceName = input.DeviceName s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory s.DeviceCategory = input.DeviceCategory
s.UserName = input.UserName s.UserName = input.UserName
if input.SecretId > 0 {
s.SecretId = input.SecretId
}
if input.DeviceName == "" && input.DeviceCategory == "" && input.UserName == "" && input.SecretId == 0 {
errString := "no values provided to select secret"
log.Printf("RetrieveSecret %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
retrieveSpecifiedSecret(&s, c) retrieveSpecifiedSecret(&s, c)
} }
@@ -61,7 +64,9 @@ func RetrieveSecretByDevicename(c *gin.Context) {
DeviceName := c.Param("devicename") DeviceName := c.Param("devicename")
if DeviceName == "" { if DeviceName == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "no devicename value specified"}) errString := "no devicename value specified"
log.Printf("RetrieveSecretByDevicename %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return return
} }
@@ -76,7 +81,9 @@ func RetrieveSecretByDevicecategory(c *gin.Context) {
DeviceCategory := c.Param("devicecategory") DeviceCategory := c.Param("devicecategory")
if DeviceCategory == "" { if DeviceCategory == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "no devicecategory value specified"}) errString := "no devicecategory value specified"
log.Printf("RetrieveSecretByDevicecategory %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return return
} }
@@ -86,34 +93,31 @@ func RetrieveSecretByDevicecategory(c *gin.Context) {
retrieveSpecifiedSecret(&s, c) retrieveSpecifiedSecret(&s, c)
} }
func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) { func RetrieveSecretByUsername(c *gin.Context) {
/* userName := c.Param("username")
// Get the user and role id of the requestor
u, err := models.UserGetRoleFromToken(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
s.RoleId = u.RoleId
results, err := models.GetSecrets(s, false) if userName == "" {
if err != nil { errString := "no username value specified"
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) log.Printf("RetrieveSecretByUsername %s\n", errString)
return c.JSON(http.StatusBadRequest, gin.H{"error": errString})
} return
*/ }
// Create object based on specified data
s := models.Secret{UserName: userName}
retrieveSpecifiedSecret(&s, c)
}
func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) {
var UserId int var UserId int
var results []models.Secret var results []models.Secret
/*
user_id, err := token.ExtractTokenID(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
}
*/
// Get userId that we stored in the context earlier // Get userId that we stored in the context earlier
if val, ok := c.Get("user-id"); !ok { if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) errString := "error determining user"
log.Printf("retrieveSpecifiedSecret %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return return
} else { } else {
UserId = val.(int) UserId = val.(int)
@@ -123,7 +127,9 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) {
safeList, err := models.UserGetSafesAllowed(int(UserId)) safeList, err := models.UserGetSafesAllowed(int(UserId))
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user safes"}) errString := "error determining user safes"
log.Printf("retrieveSpecifiedSecret %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return return
} }
@@ -151,29 +157,29 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) {
return return
} }
if len(results) == 1 { if len(results) == 0 {
// Create audit record
a := models.Audit{
UserId: UserId,
EventText: fmt.Sprintf("Retrieved Secret Id %d", results[0].SecretId),
}
a.AuditAdd()
// output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": results})
} else if len(results) > 1 {
c.JSON(http.StatusBadRequest, gin.H{"error": "found multiple matching secrets"})
return
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": "found no matching secrets"}) c.JSON(http.StatusBadRequest, gin.H{"error": "found no matching secrets"})
return return
} }
// Create audit record for results
for i := range results {
a := models.Audit{
UserId: UserId,
SecretId: results[i].SecretId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("User '%s' retrieved SecretId %d", safeList[0].User.UserName, results[i].SecretId),
}
a.AuditLogAdd()
}
// output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": results, "count": len(results)})
} }
func ListSecrets(c *gin.Context) { func ListSecrets(c *gin.Context) {
var UserId int var UserId int
var output []ListSecret var results []ListSecret
//var results []models.Secret //var results []models.Secret
s := models.Secret{} s := models.Secret{}
@@ -196,61 +202,18 @@ func ListSecrets(c *gin.Context) {
// Extract the normal secret fields from the allowed list // Extract the normal secret fields from the allowed list
for _, secret := range secretList { for _, secret := range secretList {
output = append(output, ListSecret(secret.Secret)) results = append(results, ListSecret(secret.Secret))
} }
// Create audit record // Create audit record
a := models.Audit{ a := models.Audit{
UserId: UserId, UserId: UserId,
EventText: fmt.Sprintf("Listed %d secrets, %+v", len(output), s), IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Listed %d secrets accessible to user", len(results)),
} }
a.AuditAdd() a.AuditLogAdd()
// output results as json // output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": output}) c.JSON(http.StatusOK, gin.H{"message": "success", "data": results, "count": len(results)})
} }
func RetrieveMultpleSecrets(c *gin.Context) {
// TODO implement with new schema
/*
var input RetrieveInput
// Validate the input matches our struct
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
log.Printf("StoreSecret received JSON input '%v'\n", input)
// Get the user and role id of the requestor
user_id, err := token.ExtractTokenID(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
u, err := models.GetUserRoleByID(user_id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Populate fields
s := models.Secret{}
s.RoleId = u.RoleId
s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory
results, err := models.GetSecrets(&s, false)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": results})
*/
}

View File

@@ -14,6 +14,7 @@ import (
type SecretInput struct { type SecretInput struct {
SafeId int `json:"safeId"` SafeId int `json:"safeId"`
SafeName string `json:"safeName"` SafeName string `json:"safeName"`
SecretId int `json:"secretId"`
DeviceName string `json:"deviceName"` DeviceName string `json:"deviceName"`
DeviceCategory string `json:"deviceCategory"` DeviceCategory string `json:"deviceCategory"`
UserName string `json:"userName"` UserName string `json:"userName"`
@@ -146,9 +147,11 @@ func StoreSecret(c *gin.Context) {
// Create audit record // Create audit record
a := models.Audit{ a := models.Audit{
UserId: UserId, UserId: UserId,
SecretId: s.SecretId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Created Secret Id %d", s.SecretId), EventText: fmt.Sprintf("Created Secret Id %d", s.SecretId),
} }
a.AuditAdd() a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "secret stored successfully", "data": models.SecretRestricted(s)}) c.JSON(http.StatusOK, gin.H{"message": "secret stored successfully", "data": models.SecretRestricted(s)})
} }
@@ -254,16 +257,6 @@ func UpdateSecret(c *gin.Context) {
return return
} }
// Temporarily disable because we should be able to figure it out without user specifying
/*
if input.SafeId == 0 && len(input.SafeName) == 0 {
errString := "UpdateSecret no safe specified\n"
log.Print(errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
*/
// Get userId that we stored in the context earlier // Get userId that we stored in the context earlier
if val, ok := c.Get("user-id"); !ok { if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
@@ -367,9 +360,11 @@ func UpdateSecret(c *gin.Context) {
// Create audit record // Create audit record
a := models.Audit{ a := models.Audit{
UserId: UserId, UserId: UserId,
SecretId: s.SecretId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Updated Secret Id %d", s.SecretId), EventText: fmt.Sprintf("Updated Secret Id %d", s.SecretId),
} }
a.AuditAdd() a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "secret updated successfully", "data": models.SecretRestricted(s)}) c.JSON(http.StatusOK, gin.H{"message": "secret updated successfully", "data": models.SecretRestricted(s)})
} else { } else {
@@ -402,6 +397,10 @@ func DeleteSecret(c *gin.Context) {
// Populate fields // Populate fields
s := models.Secret{} s := models.Secret{}
if input.SecretId > 0 {
s.SecretId = input.SecretId
}
s.UserName = input.UserName s.UserName = input.UserName
s.DeviceName = input.DeviceName s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory s.DeviceCategory = input.DeviceCategory
@@ -453,9 +452,11 @@ func DeleteSecret(c *gin.Context) {
// Create audit record // Create audit record
a := models.Audit{ a := models.Audit{
UserId: UserId, UserId: UserId,
SecretId: s.SecretId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Deleted Secret Id %d", s.SecretId), EventText: fmt.Sprintf("Deleted Secret Id %d", s.SecretId),
} }
a.AuditAdd() a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "secret deleted successfully"}) c.JSON(http.StatusOK, gin.H{"message": "secret deleted successfully"})
} else { } else {

77
go.mod
View File

@@ -1,54 +1,57 @@
module smt module smt
go 1.19 go 1.24.4
require ( require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-gonic/gin v1.10.1
github.com/gin-gonic/gin v1.9.0 github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-ldap/ldap v3.0.3+incompatible github.com/golang-jwt/jwt/v5 v5.2.2
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.4.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.13.0 golang.org/x/crypto v0.39.0
modernc.org/sqlite v1.21.0 modernc.org/sqlite v1.38.0
) )
require ( require (
github.com/bytedance/sonic v1.8.6 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/bytedance/sonic v1.13.3 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.12.0 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/google/uuid v1.3.1 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/leodido/go-urn v1.2.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.3.0 // indirect
golang.org/x/arch v0.3.0 // indirect golang.org/x/arch v0.18.0 // indirect
golang.org/x/mod v0.8.0 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/net v0.10.0 // indirect golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.12.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.6.0 // indirect golang.org/x/tools v0.34.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect modernc.org/gc/v3 v3.1.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/libc v1.66.2 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/libc v1.22.3 // indirect modernc.org/memory v1.11.0 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/strutil v1.2.1 // indirect
modernc.org/memory v1.5.0 // indirect modernc.org/token v1.1.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
) )

265
go.sum
View File

@@ -1,139 +1,216 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/bytedance/sonic v1.8.6 h1:aUgO9S8gvdN6SyW2EhIpAw5E4ChworywIEndZCkCVXk= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/bytedance/sonic v1.8.6/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/ccgo/v4 v4.20.7 h1:skrinQsjxWfvj6nbC3ztZPJy+NuwmB3hV9zX/pthNYQ=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/ccgo/v4 v4.20.7/go.mod h1:UOkI3JSG2zT4E2ioHlncSOZsXbuDCZLvPi3uMlZT5GY=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a h1:CfbpOLEo2IwNzJdMvE8aiRbPMxoTpgAJeyePh0SmO8M=
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/gc/v3 v3.1.0 h1:CiObI+9ROz7pjjH3iAgMPaFCN5zE3sN5KF4jet8BWdc=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/gc/v3 v3.1.0/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/libc v1.59.9 h1:k+nNDDakwipimgmJ1D9H466LhFeSkaPPycAs1OZiDmY=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/libc v1.59.9/go.mod h1:EY/egGEU7Ju66eU6SBqCNYaFUDuc4npICkMWnU5EE3A=
modernc.org/libc v1.66.2 h1:JCBxlJzZOIwZY54fzjHN3Wsn8Ty5PUTPr/xioRkmecI=
modernc.org/libc v1.66.2/go.mod h1:ceIGzvXxP+JV3pgVjP9avPZo6Chlsfof2egXBH3YT5Q=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

18
main.go
View File

@@ -11,6 +11,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"runtime"
"smt/controllers" "smt/controllers"
"smt/middlewares" "smt/middlewares"
"smt/models" "smt/models"
@@ -100,6 +101,7 @@ func main() {
// These replacements are for the embedded html generated from README.md // These replacements are for the embedded html generated from README.md
replacements["{SHA1VER}"] = sha1ver replacements["{SHA1VER}"] = sha1ver
replacements["{BUILDTIME}"] = buildTime replacements["{BUILDTIME}"] = buildTime
replacements["{RUNTIME}"] = runtime.Version()
// Load data from environment file // Load data from environment file
envFilename := utils.GetFilePath(".env") envFilename := utils.GetFilePath(".env")
@@ -121,7 +123,7 @@ func main() {
} }
log.SetOutput(logfileWriter) log.SetOutput(logfileWriter)
log.Printf("SMT starting execution. Built on %s from sha1 %s\n", buildTime, sha1ver) log.Printf("SMT starting execution. Built on %s from sha1 %s. Runtime %s\n", buildTime, sha1ver, runtime.Version())
/* /*
// for debugging, list all the files that we embedded at compile time // for debugging, list all the files that we embedded at compile time
@@ -199,7 +201,7 @@ func main() {
// Determine bind port // Determine bind port
bindPort := os.Getenv("BIND_PORT") bindPort := os.Getenv("BIND_PORT")
if bindPort == "" { if bindPort == "" {
bindIP = "8443" bindPort = "8443"
} }
bindAddress := fmt.Sprint(bindIP, ":", bindPort) bindAddress := fmt.Sprint(bindIP, ":", bindPort)
log.Printf("Will listen on address 'https://%s'\n", bindAddress) log.Printf("Will listen on address 'https://%s'\n", bindAddress)
@@ -248,19 +250,20 @@ func main() {
adminOnly.POST("/user/add", controllers.AddUser) adminOnly.POST("/user/add", controllers.AddUser)
adminOnly.GET("/users", controllers.GetUsers) adminOnly.GET("/users", controllers.GetUsers)
// TODO // TODO
//adminOnly.POST("/user/update", controllers.UpdateUser) //adminOnly.POST("/user/update", controllers.UpdateUserHandler)
// Group functions for admin // Group functions for admin
adminOnly.GET("/groups", controllers.GetGroupsHandler) adminOnly.GET("/groups", controllers.GetGroupsHandler)
adminOnly.POST("/group/add", controllers.AddGroupHandler) adminOnly.POST("/group/add", controllers.AddGroupHandler)
// TODO // TODO
//adminOnly.POST("/group/update", controllers.UpdateGroup) //adminOnly.POST("/group/update", controllers.UpdateGroupHandler)
adminOnly.POST("/group/delete", controllers.DeleteGroupHandler) adminOnly.POST("/group/delete", controllers.DeleteGroupHandler)
// Permission functions for admin // Permission functions for admin
adminOnly.GET("/permissions", controllers.GetPermissionsHandler) adminOnly.GET("/permissions", controllers.GetPermissionsHandler)
adminOnly.POST("/permission/add", controllers.AddPermissionHandler) adminOnly.POST("/permission/add", controllers.AddPermissionHandler)
adminOnly.POST("/permission/delete", controllers.DeletePermissionHandler) adminOnly.POST("/permission/delete", controllers.DeletePermissionHandler)
adminOnly.POST("/permission/update", controllers.UpdatePermissionHandler)
// Safe functions for admin // Safe functions for admin
adminOnly.GET("/safe/listall", controllers.GetAllSafesHandler) adminOnly.GET("/safe/listall", controllers.GetAllSafesHandler)
@@ -269,6 +272,10 @@ func main() {
// Other functions for admin // Other functions for admin
adminOnly.POST("/unlock", controllers.Unlock) adminOnly.POST("/unlock", controllers.Unlock)
adminOnly.GET("/logs", controllers.GetAuditLogsHandler)
// TODO
//adminOnly.GET("/logs/secret/:id", controllers.GetAuditLogsBySecretHandler)
//adminOnly.GET("/logs/user/:id", controllers.GetAuditLogsByUserHandler)
// Get secrets // Get secrets
secretRoutes := router.Group("/api/secret") secretRoutes := router.Group("/api/secret")
@@ -276,12 +283,10 @@ func main() {
secretRoutes.POST("/retrieve", controllers.RetrieveSecret) // TODO deprecate, replace retrieve with get secretRoutes.POST("/retrieve", controllers.RetrieveSecret) // TODO deprecate, replace retrieve with get
secretRoutes.POST("/get", controllers.RetrieveSecret) secretRoutes.POST("/get", controllers.RetrieveSecret)
secretRoutes.GET("/list", controllers.ListSecrets) secretRoutes.GET("/list", controllers.ListSecrets)
//secretRoutes.POST("/retrieveMultiple", controllers.RetrieveMultpleSecrets) // TODO is this still required?
secretRoutes.POST("/store", controllers.StoreSecret) // TODO deprecate, replace store with add secretRoutes.POST("/store", controllers.StoreSecret) // TODO deprecate, replace store with add
secretRoutes.POST("/add", controllers.StoreSecret) secretRoutes.POST("/add", controllers.StoreSecret)
secretRoutes.POST("/update", controllers.UpdateSecret) secretRoutes.POST("/update", controllers.UpdateSecret)
// TODO
secretRoutes.POST("/delete", controllers.DeleteSecret) secretRoutes.POST("/delete", controllers.DeleteSecret)
// Get Safes (only those user allowed to access) // Get Safes (only those user allowed to access)
@@ -293,6 +298,7 @@ func main() {
// See https://gin-gonic.com/docs/examples/param-in-path/ // See https://gin-gonic.com/docs/examples/param-in-path/
secretRoutes.GET("/retrieve/name/:devicename", controllers.RetrieveSecretByDevicename) secretRoutes.GET("/retrieve/name/:devicename", controllers.RetrieveSecretByDevicename)
secretRoutes.GET("/retrieve/category/:devicecategory", controllers.RetrieveSecretByDevicecategory) secretRoutes.GET("/retrieve/category/:devicecategory", controllers.RetrieveSecretByDevicecategory)
secretRoutes.GET("/retrieve/user/:username", controllers.RetrieveSecretByUsername)
// Initializing the server in a goroutine so that // Initializing the server in a goroutine so that
// it won't block the graceful shutdown handling below // it won't block the graceful shutdown handling below

View File

@@ -12,10 +12,11 @@ type Audit struct {
SecretId int `db:"SecretId" json:"secretId"` SecretId int `db:"SecretId" json:"secretId"`
EventText string `db:"EventText" json:"eventText"` EventText string `db:"EventText" json:"eventText"`
EventTime time.Time `db:"EventTime" json:"eventTime"` EventTime time.Time `db:"EventTime" json:"eventTime"`
IpAddress string `db:"IpAddress" json:"ipAddress"`
} }
// AuditAdd adds a new audit record to the database // AuditLogAdd adds a new audit record to the database
func (a *Audit) AuditAdd() (*Audit, error) { func (a *Audit) AuditLogAdd() (*Audit, error) {
var err error var err error
// Populate timestamp field if not already set // Populate timestamp field if not already set
@@ -23,17 +24,45 @@ func (a *Audit) AuditAdd() (*Audit, error) {
a.EventTime = time.Now().UTC() a.EventTime = time.Now().UTC()
} }
result, err := db.NamedExec(("INSERT INTO audit (UserId, SecretId, EventText, EventTime) VALUES (:UserId, :SecretId, :EventText, :EventTime);"), a) result, err := db.NamedExec(("INSERT INTO audit (UserId, SecretId, EventText, EventTime, IpAddress) VALUES (:UserId, :SecretId, :EventText, :EventTime, :IpAddress);"), a)
if err != nil { if err != nil {
log.Printf("AuditAdd error executing sql record : '%s'\n", err) log.Printf("AuditLogAdd error executing sql record : '%s'\n", err)
return &Audit{}, err return &Audit{}, err
} else { } else {
affected, _ := result.RowsAffected() affected, _ := result.RowsAffected()
id, _ := result.LastInsertId() id, _ := result.LastInsertId()
a.AuditId = int(id) a.AuditId = int(id)
log.Printf("AuditAdd insert returned result id '%d' affecting %d row(s).\n", id, affected) log.Printf("AuditLogAdd insert returned result id '%d' affecting %d row(s).\n", id, affected)
} }
return a, nil 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
}

View File

@@ -27,7 +27,8 @@ const createUsers string = `
UserName VARCHAR, UserName VARCHAR,
Password VARCHAR, Password VARCHAR,
Admin BOOLEAN DEFAULT 0, Admin BOOLEAN DEFAULT 0,
LdapUser BOOLEAN DEFAULT 0 LdapUser BOOLEAN DEFAULT 0,
LastLogin datetime DEFAULT (datetime('1970-01-01 00:00:00'))
); );
` `
@@ -68,6 +69,7 @@ const createSecrets string = `
DeviceCategory VARCHAR, DeviceCategory VARCHAR,
UserName VARCHAR, UserName VARCHAR,
Secret VARCHAR, Secret VARCHAR,
LastUpdated datetime DEFAULT (datetime('1970-01-01 00:00:00')),
FOREIGN KEY (SafeId) REFERENCES safes(SafeId) FOREIGN KEY (SafeId) REFERENCES safes(SafeId)
); );
` `
@@ -83,8 +85,9 @@ const createAudit string = `
AuditId INTEGER PRIMARY KEY AUTOINCREMENT, AuditId INTEGER PRIMARY KEY AUTOINCREMENT,
UserId INTEGER DEFAULT 0, UserId INTEGER DEFAULT 0,
SecretId INTEGER DEFAULT 0, SecretId INTEGER DEFAULT 0,
EventText VARCHAR, EventText VARCHAR DEFAULT '',
EventTime datetime IpAddress VARCHAR DEFAULT '',
EventTime datetime DEFAULT (datetime('1970-01-01 00:00:00'))
); );
` `
@@ -119,29 +122,8 @@ func DisconnectDatabase() {
func CreateTables() { func CreateTables() {
var err error var err error
var rowCount int var rowCount int
// Create database tables if it doesn't exist // Create database tables if it doesn't exist
/*
// Roles table should go first since other tables refer to it
if _, err = db.Exec(createRoles); err != nil {
log.Printf("Error checking roles table : '%s'", err)
os.Exit(1)
}
rowCount, _ = CheckCount("roles")
if rowCount == 0 {
if _, err = db.Exec("INSERT INTO roles VALUES(1, 'Admin', false);"); err != nil {
log.Printf("Error adding initial admin role : '%s'", err)
os.Exit(1)
}
if _, err = db.Exec("INSERT INTO roles VALUES(2, 'UserRole', false);"); err != nil {
log.Printf("Error adding initial user role : '%s'", err)
os.Exit(1)
}
if _, err = db.Exec("INSERT INTO roles VALUES(3, 'GuestRole', true);"); err != nil {
log.Printf("Error adding initial guest role : '%s'", err)
os.Exit(1)
}
}
*/
// groups table // groups table
if _, err = db.Exec(createGroups); err != nil { if _, err = db.Exec(createGroups); err != nil {
@@ -247,11 +229,16 @@ func CreateTables() {
os.Exit(1) os.Exit(1)
} }
// Remove users RoleId column // Check the database schema version
userRoleIdCheck, _ := CheckColumnExists("users", "RoleId") version, _ := GetSchemaVersion()
if userRoleIdCheck { if version >= 3 {
//_, err := db.Exec("ALTER TABLE users DROP COLUMN RoleId;") log.Printf("Database schema up to date\n")
_, err := db.Exec(` } else {
// Remove users RoleId column
userRoleIdCheck, _ := CheckColumnExists("users", "RoleId")
if userRoleIdCheck {
//_, err := db.Exec("ALTER TABLE users DROP COLUMN RoleId;")
_, err := db.Exec(`
PRAGMA foreign_keys=off; PRAGMA foreign_keys=off;
BEGIN TRANSACTION; BEGIN TRANSACTION;
ALTER TABLE users RENAME TO _users_old; ALTER TABLE users RENAME TO _users_old;
@@ -269,43 +256,49 @@ func CreateTables() {
PRAGMA foreign_keys=on; PRAGMA foreign_keys=on;
DROP TABLE _users_old; DROP TABLE _users_old;
`) `)
if err != nil { if err != nil {
log.Printf("Error altering users table to drop RoleId column : '%s'\n", err) log.Printf("Error altering users table to drop RoleId column : '%s'\n", err)
os.Exit(1)
}
}
// Set any unassigned secrets to the default safe id
if _, err = db.Exec("UPDATE users SET LdapUser = 0 WHERE LdapUser is null;"); err != nil {
log.Printf("Error setting LdapUser flag to false for existing users : '%s'", err)
os.Exit(1) os.Exit(1)
} }
}
// Remove LdapGroup column from roles table // Remove LdapGroup column from roles table
ldapCheck, _ := CheckColumnExists("roles", "LdapGroup") ldapCheck, _ := CheckColumnExists("roles", "LdapGroup")
if ldapCheck { if ldapCheck {
_, err := db.Exec("ALTER TABLE roles DROP COLUMN LdapGroup;") _, err := db.Exec("ALTER TABLE roles DROP COLUMN LdapGroup;")
if err != nil { if err != nil {
log.Printf("Error altering roles table to renmove LdapGroup column : '%s'\n", err) log.Printf("Error altering roles table to renmove LdapGroup column : '%s'\n", err)
os.Exit(1)
}
}
// Add SafeId column to secrets table
safeIdCheck, _ := CheckColumnExists("secrets", "SafeId")
if !safeIdCheck {
// Add the column for LdapGroup in the roles table
_, err := db.Exec("ALTER TABLE secrets ADD COLUMN SafeId INTEGER REFERENCES safes(SafeId);")
if err != nil {
log.Printf("Error altering secrets table to add SafeId column : '%s'\n", err)
os.Exit(1)
}
}
// Set any unassigned secrets to the default safe id
if _, err = db.Exec("UPDATE secrets SET SafeId = 1 WHERE SafeId is null;"); err != nil {
log.Printf("Error setting safe ID of existing secrets : '%s'", err)
os.Exit(1) os.Exit(1)
} }
}
// Add SafeId column to secrets table // Remove RoleId column from secrets table
safeIdCheck, _ := CheckColumnExists("secrets", "SafeId") secretsRoleIdCheck, _ := CheckColumnExists("secrets", "RoleId")
if !safeIdCheck { if secretsRoleIdCheck {
// Add the column for LdapGroup in the roles table _, err := db.Exec(`
_, err := db.Exec("ALTER TABLE secrets ADD COLUMN SafeId INTEGER REFERENCES safes(SafeId);")
if err != nil {
log.Printf("Error altering secrets table to add SafeId column : '%s'\n", err)
os.Exit(1)
}
}
// Set any unassigned secrets to the default safe id
if _, err = db.Exec("UPDATE secrets SET SafeId = 1 WHERE SafeId is null;"); err != nil {
log.Printf("Error setting safe ID of existing secrets : '%s'", err)
os.Exit(1)
}
// Remove RoleId column from secrets table
secretsRoleIdCheck, _ := CheckColumnExists("secrets", "RoleId")
if secretsRoleIdCheck {
_, err := db.Exec(`
PRAGMA foreign_keys=off; PRAGMA foreign_keys=off;
BEGIN TRANSACTION; BEGIN TRANSACTION;
ALTER TABLE secrets RENAME TO _secrets_old; ALTER TABLE secrets RENAME TO _secrets_old;
@@ -322,30 +315,32 @@ func CreateTables() {
); );
INSERT INTO secrets SELECT SecretId, RoleId, SafeId, DeviceName, DeviceCategory, UserName, Secret FROM _secrets_old; INSERT INTO secrets SELECT SecretId, RoleId, SafeId, DeviceName, DeviceCategory, UserName, Secret FROM _secrets_old;
ALTER TABLE secrets DROP COLUMN RoleId; ALTER TABLE secrets DROP COLUMN RoleId;
ALTER TABLE secrets ADD COLUMN LastUpdated datetime;
UPDATE secrets SET LastUpdated = (datetime('1970-01-01 00:00:00')) WHERE LastUpdated is null;
COMMIT; COMMIT;
PRAGMA foreign_keys=on; PRAGMA foreign_keys=on;
DROP TABLE _secrets_old; DROP TABLE _secrets_old;
`) `)
if err != nil { if err != nil {
log.Printf("Error altering secrets table to remove RoleId column : '%s'\n", err) log.Printf("Error altering secrets table to remove RoleId column : '%s'\n", err)
os.Exit(1) os.Exit(1)
}
} }
}
// Remove the Admin column from roles table // Remove the Admin column from roles table
rolesAdminCheck, _ := CheckColumnExists("roles", "Admin") rolesAdminCheck, _ := CheckColumnExists("roles", "Admin")
if rolesAdminCheck { if rolesAdminCheck {
_, err := db.Exec("ALTER TABLE roles DROP COLUMN Admin;") _, err := db.Exec("ALTER TABLE roles DROP COLUMN Admin;")
if err != nil { if err != nil {
log.Printf("Error altering roles table to remove Admin column : '%s'\n", err) log.Printf("Error altering roles table to remove Admin column : '%s'\n", err)
os.Exit(1) os.Exit(1)
}
} }
}
// Remove the RoleId from permissiosn table // Remove the RoleId from permissiosn table
permissionsRoleIdCheck, _ := CheckColumnExists("permissions", "RoleId") permissionsRoleIdCheck, _ := CheckColumnExists("permissions", "RoleId")
if permissionsRoleIdCheck { if permissionsRoleIdCheck {
_, err := db.Exec(` _, err := db.Exec(`
PRAGMA foreign_keys=off; PRAGMA foreign_keys=off;
BEGIN TRANSACTION; BEGIN TRANSACTION;
ALTER TABLE permissions RENAME TO _permissions_old; ALTER TABLE permissions RENAME TO _permissions_old;
@@ -368,43 +363,75 @@ func CreateTables() {
PRAGMA foreign_keys=on; PRAGMA foreign_keys=on;
DROP TABLE _permissions_old; DROP TABLE _permissions_old;
`) `)
if err != nil { if err != nil {
log.Printf("Error altering permissions table to remove RoleId column : '%s'\n", err) log.Printf("Error altering permissions table to remove RoleId column : '%s'\n", err)
os.Exit(1) os.Exit(1)
}
}
secretsLastUpdatedCheck, _ := CheckColumnExists("secrets", "LastUpdated")
if !secretsLastUpdatedCheck {
// Add the column for LastUpdated in the secrets table
_, err := db.Exec("ALTER TABLE secrets ADD COLUMN LastUpdated datetime;")
if err != nil {
log.Printf("Error altering secrets table to add LastUpdated column : '%s'\n", err)
os.Exit(1)
}
// Set the default value
if _, err = db.Exec("UPDATE secrets SET LastUpdated = (datetime('1970-01-01 00:00:00')) WHERE LastUpdated is null;"); err != nil {
log.Printf("Error setting LastUpdated of existing secrets : '%s'", err)
os.Exit(1)
}
}
lastLoginCheck, _ := CheckColumnExists("users", "LastLogin")
if !lastLoginCheck {
// Add the column for LastUpdated in the secrets table
_, err := db.Exec("ALTER TABLE users ADD COLUMN LastLogin datetime;")
if err != nil {
log.Printf("Error altering users table to add LastLogin column : '%s'\n", err)
os.Exit(1)
}
// Set the default value
if _, err = db.Exec("UPDATE users SET LastLogin = (datetime('1970-01-01 00:00:00')) WHERE LastLogin is null;"); err != nil {
log.Printf("Error setting LastLogin of existing users : '%s'", err)
os.Exit(1)
}
}
// Add IpAddress column to audit table
auditIPCheck, _ := CheckColumnExists("audit", "IpAddress")
if !auditIPCheck {
// Add the column for LdapGroup in the roles table
_, err := db.Exec("ALTER TABLE audit ADD COLUMN IpAddress VARCHAR;")
if err != nil {
log.Printf("Error altering audit table to add IpAddress column : '%s'\n", err)
os.Exit(1)
}
if _, err = db.Exec("UPDATE audit SET IpAddress = '' WHERE IpAddress is null;"); err != nil {
log.Printf("Error setting IpAddress of existing audit records : '%s'", err)
os.Exit(1)
}
}
// Set the schema version
rowCount, _ = CheckCount("schema")
if rowCount > 0 {
if _, err = db.Exec("UPDATE schema SET Version = 3;"); err != nil {
log.Printf("Error setting schema to version 3 : '%s'", err)
os.Exit(1)
}
} else {
if _, err = db.Exec("INSERT INTO schema (Version) VALUES (3);"); err != nil {
log.Printf("Error setting schema to version 3 : '%s'", err)
os.Exit(1)
}
} }
} }
/*
// Database updates added after initial version released
ldapCheck, _ := CheckColumnExists("roles", "LdapGroup")
if !ldapCheck {
// Add the column for LdapGroup in the roles table
_, err := db.Exec("ALTER TABLE roles ADD COLUMN LdapGroup VARCHAR DEFAULT '';")
if err != nil {
log.Printf("Error altering roles table to add LdapGroup column : '%s'\n", err)
os.Exit(1)
}
}
// Add the two LDAP columns to the users table if they weren't there
ldapUserCheck, _ := CheckColumnExists("users", "LdapUser")
if !ldapUserCheck {
log.Printf("CreateTables creating ldap columns in user table")
_, err := db.Exec("ALTER TABLE users ADD COLUMN LdapUser BOOLEAN DEFAULT 0;")
if err != nil {
log.Printf("Error altering users table to add LdapUser column : '%s'\n", err)
os.Exit(1)
}
_, err = db.Exec("ALTER TABLE users ADD COLUMN LdapDn VARCHAR DEFAULT '';")
if err != nil {
log.Printf("Error altering users table to add LdapDn column : '%s'\n", err)
os.Exit(1)
}
}
*/
} }
// Count the number of records in the sqlite database // Count the number of records in the sqlite database
@@ -425,6 +452,23 @@ func CheckCount(tablename string) (int, error) {
return count, nil return count, nil
} }
func GetSchemaVersion() (int, error) {
var version int
stmt, err := db.Prepare("SELECT Version FROM schema;")
if err != nil {
log.Printf("GetSchemaVersion error preparing sqlite statement : '%s'\n", err)
return 0, err
}
err = stmt.QueryRow().Scan(&version)
if err != nil {
log.Printf("GetSchemaVersion error querying database record count : '%s'\n", err)
return 0, err
}
stmt.Close() // or use defer rows.Close(), idc
return version, nil
}
// From https://stackoverflow.com/a/60100045 // From https://stackoverflow.com/a/60100045
func GenerateInsertMethod(q interface{}) (string, error) { func GenerateInsertMethod(q interface{}) (string, error) {
if reflect.ValueOf(q).Kind() == reflect.Struct { if reflect.ValueOf(q).Kind() == reflect.Struct {

View File

@@ -12,7 +12,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/go-ldap/ldap" "github.com/go-ldap/ldap/v3"
) )
// Code relating to AD integration // Code relating to AD integration
@@ -236,7 +236,7 @@ func LdapGetGroupMembership(username string, password string) ([]string, error)
defer ldaps.Close() defer ldaps.Close()
// try an authenticated bind to AD to verify credentials // try an authenticated bind to AD to verify credentials
log.Printf("GetLdapGroupMembership Attempting LDAP bind with user '%s' and password length '%d'\n", username, len(password)) log.Printf("LdapGetGroupMembership Attempting LDAP bind with user '%s' and password length '%d'\n", username, len(password))
err = ldaps.Bind(username, password) err = ldaps.Bind(username, password)
if err != nil { if err != nil {
if ldapErr, ok := err.(*ldap.Error); ok && ldapErr.ResultCode == ldap.LDAPResultInvalidCredentials { if ldapErr, ok := err.(*ldap.Error); ok && ldapErr.ResultCode == ldap.LDAPResultInvalidCredentials {
@@ -244,17 +244,17 @@ func LdapGetGroupMembership(username string, password string) ([]string, error)
log.Print(errString) log.Print(errString)
return nil, errors.New(errString) return nil, errors.New(errString)
} else { } else {
errString := fmt.Sprintf("GetLdapGroupMembership error binding to LDAP with supplied credentials : '%s'\n", err) errString := fmt.Sprintf("LdapGetGroupMembership error binding to LDAP with supplied credentials : '%s'\n", err)
log.Print(errString) log.Print(errString)
return nil, errors.New(errString) return nil, errors.New(errString)
} }
} else { } else {
log.Printf("GetLdapGroupMembership successfully bound to LDAP\n") log.Printf("LdapGetGroupMembership successfully bound to LDAP\n")
} }
groups, err := GetGroupsOfUser(username, LdapBaseDn, ldaps) groups, err := GetGroupsOfUser(username, LdapBaseDn, ldaps)
if err != nil { if err != nil {
errString := fmt.Sprintf("GetLdapGroupMembership group search error : '%s'\n", err) errString := fmt.Sprintf("LdapGetGroupMembership group search error : '%s'\n", err)
log.Print(errString) log.Print(errString)
return nil, errors.New(errString) return nil, errors.New(errString)
} }
@@ -373,3 +373,12 @@ func GetLdapUserDn(username string, baseDN string, conn *ldap.Conn) (string, err
} }
} }
} }
// Returns the user portion of a UPN formatted username
func GetUserFromUPN(email string) string {
parts := strings.Split(email, "@")
if len(parts) > 0 {
return parts[0]
}
return ""
}

View File

@@ -142,3 +142,29 @@ func (p *Permission) PermissionDelete() error {
return nil return nil
} }
// PermissionUpdate updates an existing permission definition in the database
func (p *Permission) PermissionUpdate() (*Permission, error) {
var err error
log.Printf("PermissionUpdate storing values '%v'\n", p)
if p.PermissionId == 0 {
err = errors.New("PermissionUpdate unable to update permission with empty PermissionId field")
log.Printf("PermissionUpdate error in pre-check : '%s'\n", err)
return p, err
}
result, err := db.NamedExec((`UPDATE permissions SET Description = :Description, ReadOnly = :ReadOnly, SafeId = :SafeId, UserId = :UserId, GroupId = :GroupId WHERE PermissionId = :PermissionId`), p)
if err != nil {
log.Printf("PermissionUpdate error executing sql record : '%s'\n", err)
return &Permission{}, err
} else {
affected, _ := result.RowsAffected()
id, _ := result.LastInsertId()
log.Printf("PermissionUpdate returned result id '%d' affecting %d row(s).\n", id, affected)
}
return p, nil
}

View File

@@ -11,35 +11,39 @@ import (
"log" "log"
"smt/utils" "smt/utils"
"strings" "strings"
"time"
) )
const nonceSize = 12 const nonceSize = 12
// We use the json:"-" field tag to prevent showing these details to the user // We use the json:"-" field tag to prevent showing these details to the user
type Secret struct { type Secret struct {
SecretId int `db:"SecretId" json:"secretId"` SecretId int `db:"SecretId" json:"secretId"`
SafeId int `db:"SafeId" json:"safeId"` SafeId int `db:"SafeId" json:"safeId"`
DeviceName string `db:"DeviceName" json:"deviceName"` DeviceName string `db:"DeviceName" json:"deviceName"`
DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"` DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"`
UserName string `db:"UserName" json:"userName"` UserName string `db:"UserName" json:"userName"`
Secret string `db:"Secret" json:"secret"` Secret string `db:"Secret" json:"secret"`
LastUpdated time.Time `db:"LastUpdated" json:"lastUpdated"`
} }
// SecretRestricted is for when we want to output a Secret but not the protected information // SecretRestricted is for when we want to output a Secret but not the protected information
type SecretRestricted struct { type SecretRestricted struct {
SecretId int `db:"SecretId" json:"secretId"` SecretId int `db:"SecretId" json:"secretId"`
SafeId int `db:"SafeId" json:"safeId"` SafeId int `db:"SafeId" json:"safeId"`
DeviceName string `db:"DeviceName" json:"deviceName"` DeviceName string `db:"DeviceName" json:"deviceName"`
DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"` DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"`
UserName string `db:"UserName" json:"userName"` UserName string `db:"UserName" json:"userName"`
Secret string `db:"Secret" json:"-"` Secret string `db:"Secret" json:"-"`
LastUpdated time.Time `db:"LastUpdated" json:"lastUpdated"`
} }
// Used for querying all secrets the user has access to // Used for querying all secrets the user has access to
// Since there are some ambiguous column names (eg UserName is present in both users and secrets table), the order of fields in this struct matters // Since there are some ambiguous column names (eg UserName is present in both users and secrets table), the order of fields in this struct matters
type UserSecret struct { type UserSecret struct {
Secret Secret
UserUserId int `db:"UserUserId"` UserUserId int `db:"UserUserId"`
UserUserName string `db:"UserUserName"`
User User
//Group //Group
Permission Permission
@@ -51,11 +55,15 @@ func (s Secret) GetId() int {
} }
func (s *Secret) SaveSecret() (*Secret, error) { func (s *Secret) SaveSecret() (*Secret, error) {
var err error var err error
// Populate timestamp field if not already set
if s.LastUpdated.IsZero() {
s.LastUpdated = time.Now().UTC()
}
log.Printf("SaveSecret storing values '%v'\n", s) log.Printf("SaveSecret storing values '%v'\n", s)
result, err := db.NamedExec((`INSERT INTO secrets (SafeId, DeviceName, DeviceCategory, UserName, Secret) VALUES (:SafeId, :DeviceName, :DeviceCategory, :UserName, :Secret)`), s) result, err := db.NamedExec((`INSERT INTO secrets (SafeId, DeviceName, DeviceCategory, UserName, Secret, LastUpdated) VALUES (:SafeId, :DeviceName, :DeviceCategory, :UserName, :Secret, :LastUpdated)`), s)
if err != nil { if err != nil {
log.Printf("StoreSecret error executing sql record : '%s'\n", err) log.Printf("StoreSecret error executing sql record : '%s'\n", err)
@@ -78,7 +86,7 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
// Query for group access // Query for group access
queryArgs := []interface{}{} queryArgs := []interface{}{}
query := ` query := `
SELECT users.UserId AS UserUserId, permissions.*, SELECT users.UserId AS UserUserId, users.UserName AS UserUserName, permissions.*,
secrets.SecretId, secrets.SafeId, secrets.DeviceName, secrets.DeviceCategory, secrets.UserName secrets.SecretId, secrets.SafeId, secrets.DeviceName, secrets.DeviceCategory, secrets.UserName
FROM users FROM users
INNER JOIN groups ON users.GroupId = groups.GroupId INNER JOIN groups ON users.GroupId = groups.GroupId
@@ -88,6 +96,11 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
queryArgs = append(queryArgs, userId) queryArgs = append(queryArgs, userId)
// Add any other arguments to the query if they were specified // Add any other arguments to the query if they were specified
if s.SecretId > 0 {
query += " AND SecretId = ? "
queryArgs = append(queryArgs, s.SecretId)
}
if s.DeviceName != "" { if s.DeviceName != "" {
query += " AND DeviceName LIKE ? " query += " AND DeviceName LIKE ? "
queryArgs = append(queryArgs, s.DeviceName) queryArgs = append(queryArgs, s.DeviceName)
@@ -106,7 +119,7 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
// Query for user access // Query for user access
query += ` query += `
UNION UNION
SELECT users.UserId AS UserUserId, permissions.*, SELECT users.UserId AS UserUserId, users.UserName AS UserUserName, permissions.*,
secrets.SecretId, secrets.SafeId, secrets.DeviceName, secrets.DeviceCategory, secrets.UserName secrets.SecretId, secrets.SafeId, secrets.DeviceName, secrets.DeviceCategory, secrets.UserName
FROM users FROM users
INNER JOIN permissions ON users.UserId = permissions.UserId INNER JOIN permissions ON users.UserId = permissions.UserId
@@ -116,6 +129,10 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
queryArgs = append(queryArgs, userId) queryArgs = append(queryArgs, userId)
// Add any other arguments to the query if they were specified // Add any other arguments to the query if they were specified
if s.SecretId > 0 {
query += " AND SecretId = ? "
queryArgs = append(queryArgs, s.SecretId)
}
if s.DeviceName != "" { if s.DeviceName != "" {
query += " AND DeviceName LIKE ? " query += " AND DeviceName LIKE ? "
queryArgs = append(queryArgs, s.DeviceName) queryArgs = append(queryArgs, s.DeviceName)
@@ -132,37 +149,42 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
} }
// Execute the query // Execute the query
log.Printf("SecretsGetAllowed query string : '%s'\nArguments:%+v\n", query, queryArgs) //log.Printf("SecretsGetAllowed query string : '%s'\nArguments:%+v\n", query, queryArgs)
rows, err := db.Queryx(query, queryArgs...) rows, err := db.Queryx(query, queryArgs...)
if err != nil { if err != nil {
log.Printf("SecretsGetAllowed error executing sql record : '%s'\n", err) log.Printf("SecretsGetAllowed error executing sql record : '%s'\n", err)
return secretResults, err return secretResults, err
} else { } else {
//log.Printf("SecretsGetAllowed any error '%s'\n", rows.Err())
// parse all the results into a slice // parse all the results into a slice
for rows.Next() { for rows.Next() {
//log.Printf("SecretsGetAllowed processing row\n")
var r UserSecret var r UserSecret
err = rows.StructScan(&r) err = rows.StructScan(&r)
//log.Printf("SecretsGetAllowed performed struct scan\n")
if err != nil { if err != nil {
log.Printf("SecretsGetAllowed error parsing sql record : '%s'\n", err) log.Printf("SecretsGetAllowed error parsing sql record : '%s'\n", err)
return secretResults, err return secretResults, err
} }
//log.Printf("r: %v\n", r) //log.Printf("r: %v\n", r)
//log.Printf("SecretsGetAllowed performed err check\n")
// work around to get the UserId populated in the User field of the struct // work around to get the UserId populated in the User field of the struct
r.User.UserId = r.UserUserId r.User.UserId = r.UserUserId
r.User.UserName = r.UserUserName
// For debugging purposes // For debugging purposes
debugPrint := utils.PrintStructContents(&r, 0) //debugPrint := utils.PrintStructContents(&r, 0)
log.Println(debugPrint) //log.Println(debugPrint)
// Append the secrets to the query output, don't decrypt the secrets (we didn't SELECT them anyway)
//secretResults = append(secretResults, r)
// Use generics and the GetID() method on the UserSecret struct // Use generics and the GetID() method on the UserSecret struct
// to avoid adding this element to the results // to avoid adding this element to the results
// if there is already a secret with the same ID present // if there is already a secret with the same ID present
secretResults = utils.AppendIfNotExists(secretResults, r) secretResults = utils.AppendIfNotExists(secretResults, r)
//log.Printf("SecretsGetAllowed added secret results\n")
} }
log.Printf("SecretsGetAllowed retrieved '%d' results\n", len(secretResults)) log.Printf("SecretsGetAllowed retrieved '%d' results\n", len(secretResults))
} }
@@ -175,7 +197,7 @@ func SecretsGetFromMultipleSafes(s *Secret, safeIds []int) ([]Secret, error) {
var err error var err error
var secretResults []Secret var secretResults []Secret
args := []interface{}{} queryArgs := []interface{}{}
var query string var query string
// Generate placeholders for the IN clause to match multiple SafeId values // Generate placeholders for the IN clause to match multiple SafeId values
placeholders := make([]string, len(safeIds)) placeholders := make([]string, len(safeIds))
@@ -189,28 +211,33 @@ func SecretsGetFromMultipleSafes(s *Secret, safeIds []int) ([]Secret, error) {
// Add the Safe Ids to the arguments list // Add the Safe Ids to the arguments list
for _, g := range safeIds { for _, g := range safeIds {
args = append(args, g) queryArgs = append(queryArgs, g)
} }
// Add any other arguments to the query if they were specified // Add any other arguments to the query if they were specified
if s.SecretId > 0 {
query += " AND SecretId = ? "
queryArgs = append(queryArgs, s.SecretId)
}
if s.DeviceName != "" { if s.DeviceName != "" {
query += " AND DeviceName LIKE ? " query += " AND DeviceName LIKE ? "
args = append(args, s.DeviceName) queryArgs = append(queryArgs, s.DeviceName)
} }
if s.DeviceCategory != "" { if s.DeviceCategory != "" {
query += " AND DeviceCategory LIKE ? " query += " AND DeviceCategory LIKE ? "
args = append(args, s.DeviceCategory) queryArgs = append(queryArgs, s.DeviceCategory)
} }
if s.UserName != "" { if s.UserName != "" {
query += " AND UserName LIKE ? " query += " AND UserName LIKE ? "
args = append(args, s.UserName) queryArgs = append(queryArgs, s.UserName)
} }
// Execute the query // Execute the query
log.Printf("SecretsGetMultipleSafes query string :\n'%s'\nQuery Args : %+v\n", query, args) //log.Printf("SecretsGetMultipleSafes query string :\n'%s'\nQuery Args : %+v\n", query, queryArgs)
rows, err := db.Queryx(query, args...) rows, err := db.Queryx(query, queryArgs...)
if err != nil { if err != nil {
log.Printf("SecretsGetMultipleSafes error executing sql record : '%s'\n", err) log.Printf("SecretsGetMultipleSafes error executing sql record : '%s'\n", err)
@@ -246,6 +273,11 @@ func (s *Secret) UpdateSecret() (*Secret, error) {
var err error var err error
// Populate timestamp field if not already set
if s.LastUpdated.IsZero() {
s.LastUpdated = time.Now().UTC()
}
log.Printf("UpdateSecret storing values '%v'\n", s) log.Printf("UpdateSecret storing values '%v'\n", s)
if s.SecretId == 0 { if s.SecretId == 0 {
@@ -254,7 +286,7 @@ func (s *Secret) UpdateSecret() (*Secret, error) {
return s, err return s, err
} }
result, err := db.NamedExec((`UPDATE secrets SET DeviceName = :DeviceName, DeviceCategory = :DeviceCategory, UserName = :UserName, Secret = :Secret WHERE SecretId = :SecretId`), s) result, err := db.NamedExec((`UPDATE secrets SET DeviceName = :DeviceName, DeviceCategory = :DeviceCategory, UserName = :UserName, Secret = :Secret, LastUpdated = :LastUpdated WHERE SecretId = :SecretId`), s)
if err != nil { if err != nil {
log.Printf("UpdateSecret error executing sql record : '%s'\n", err) log.Printf("UpdateSecret error executing sql record : '%s'\n", err)
return &Secret{}, err return &Secret{}, err

View File

@@ -5,20 +5,22 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"smt/utils"
"smt/utils/token" "smt/utils/token"
"strings"
"time"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
type User struct { type User struct {
UserId int `db:"UserId" json:"userId"` UserId int `db:"UserId" json:"userId"`
GroupId int `db:"GroupId" json:"groupId"` GroupId int `db:"GroupId" json:"groupId"`
UserName string `db:"UserName" json:"userName"` UserName string `db:"UserName" json:"userName"`
Password string `db:"Password" json:"-"` Password string `db:"Password" json:"-"`
LdapUser bool `db:"LdapUser" json:"ldapUser"` LdapUser bool `db:"LdapUser" json:"ldapUser"`
Admin bool `db:"Admin"` Admin bool `db:"Admin"`
//LdapDn string `db:"LdapDn" json:"ldapDn"` LastLogin time.Time `db:"LastLogin" json:"lastLogin"`
LdapGroup bool `db:"LdapGroup"`
} }
type UserRole struct { type UserRole struct {
@@ -49,12 +51,16 @@ func (u *User) SaveUser() (*User, error) {
var err error var err error
if u.LastLogin.IsZero() {
u.LastLogin = time.Time{}
}
// Validate username not already in use // Validate username not already in use
_, err = UserGetByName(u.UserName) _, err = UserGetByName(u.UserName)
if err != nil && err.Error() == "user not found" { if err != nil && err.Error() == "user not found" {
log.Printf("SaveUser confirmed no existing user, continuing with creation of user '%s'\n", u.UserName) log.Printf("SaveUser confirmed no existing user, continuing with creation of user '%s'\n", u.UserName)
//log.Printf("u: %v\n", u) //log.Printf("u: %v\n", u)
result, err := db.NamedExec((`INSERT INTO users (GroupId, UserName, Password, LdapUser, Admin) VALUES (:GroupId, :UserName, :Password, :LdapUser, :Admin)`), u) result, err := db.NamedExec((`INSERT INTO users (GroupId, UserName, Password, LdapUser, Admin, LastLogin) VALUES (:GroupId, :UserName, :Password, :LdapUser, :Admin, :LastLogin)`), u)
if err != nil { if err != nil {
log.Printf("SaveUser error executing sql record : '%s'\n", err) log.Printf("SaveUser error executing sql record : '%s'\n", err)
@@ -107,7 +113,7 @@ func VerifyPassword(password, hashedPassword string) error {
return errors.New("unable to compare password with empty hash") return errors.New("unable to compare password with empty hash")
} }
log.Printf("VerifyPassword comparing input against hashed value '%s'\n", hashedPassword) //log.Printf("VerifyPassword comparing input against hashed value '%s'\n", hashedPassword)
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
} }
@@ -118,12 +124,32 @@ func LoginCheck(username string, password string) (string, error) {
// Query database for matching user object // Query database for matching user object
// Use IFNULL to handle situation where a user might not be a member of a group // Use IFNULL to handle situation where a user might not be a member of a group
err = db.QueryRowx("SELECT UserId, IFNULL(GroupId, 0) GroupId, UserName, Password, LdapUser, Admin FROM Users WHERE Username=?", username).StructScan(&u) // Join on groups table so we can get the value in LdapGroup column
// if username is UPN format then get just the user portion
if strings.Contains(username, "@") {
plainUser := GetUserFromUPN(username)
// check for original username or plainUser
err = db.QueryRowx(`
SELECT users.UserId, IFNULL(users.GroupId, 0) GroupId, UserName, Password, LdapUser, users.Admin, groups.LdapGroup FROM Users
INNER JOIN groups ON users.GroupId = groups.GroupId
WHERE Username=? OR Username=?`, username, plainUser).StructScan(&u)
} else {
err = db.QueryRowx(`
SELECT users.UserId, IFNULL(users.GroupId, 0) GroupId, UserName, Password, LdapUser, users.Admin, groups.LdapGroup FROM Users
INNER JOIN groups ON users.GroupId = groups.GroupId
WHERE Username=?`, username).StructScan(&u)
}
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
log.Printf("LoginCheck found no users matching username '%s'\n", username)
// TODO - if username contains UPN style login then try extracting just the username and doing a query on that
// check LDAP if enabled // check LDAP if enabled
if LdapEnabled { if LdapEnabled {
log.Printf("LoginCheck initiating ldap lookup for username '%s'\n", username)
ldapUser, err := UserLdapNewLoginCheck(username, password) ldapUser, err := UserLdapNewLoginCheck(username, password)
if err != nil { if err != nil {
errString := fmt.Sprintf("LoginCheck error checking LDAP for user : '%s'\n", err) errString := fmt.Sprintf("LoginCheck error checking LDAP for user : '%s'\n", err)
@@ -154,10 +180,11 @@ func LoginCheck(username string, password string) (string, error) {
return "", errors.New(errString) return "", errors.New(errString)
} }
} else { } else {
log.Printf("LoginCheck retrieved user '%v' from database\n", u) //log.Printf("LoginCheck retrieved user '%v' from database\n", u)
log.Printf("LoginCheck retrieved user id '%d' from database\n", u.UserId)
} }
log.Printf("u: %v\n", u) //log.Printf("u: %v\n", u)
if !u.LdapUser { if !u.LdapUser {
// Locally defined user, perform password verification // Locally defined user, perform password verification
@@ -181,14 +208,22 @@ func LoginCheck(username string, password string) (string, error) {
} else { } else {
log.Printf("LoginCheck successfully verified LDAP user\n") log.Printf("LoginCheck successfully verified LDAP user\n")
// confirm that current LDAP group membership matches a group // check if user's group membership is an ldap group or not
err := UserLdapGroupVerify(username, password) log.Printf("User id '%d' is a member of group '%d' which has ldapGroup status '%v'\n", u.UserId, u.GroupId, u.LdapGroup)
if err != nil { // If user's group membership is an ldap group, then run UserLdapGroupVerify as we were doing before
// No valid group membership if u.LdapGroup {
errString := fmt.Sprintf("ldap group membership check unsuccessful : '%s'\n", err) // confirm that current LDAP group membership matches a group
log.Printf("LoginCheck %s\n", errString) err := UserLdapGroupVerify(username, password)
return "", errors.New(errString)
if err != nil {
// No valid group membership
errString := fmt.Sprintf("ldap group membership check unsuccessful : '%s'\n", err)
log.Printf("LoginCheck %s\n", errString)
return "", errors.New(errString)
}
} else { // If user's group membership is not an ldap group, then we are fine and the login attempt was successful
log.Printf("No need to check ldap group membership since user is not a member of an ldap group\n")
} }
} }
} else { } else {
@@ -206,6 +241,8 @@ func LoginCheck(username string, password string) (string, error) {
return "", err return "", err
} }
u.UserSetLastLogin()
return token, nil return token, nil
} }
@@ -260,6 +297,10 @@ func UserLdapNewLoginCheck(username string, password string) (User, error) {
matchFound := false matchFound := false
for _, group := range groupList { for _, group := range groupList {
// Skip any groups that aren't LDAP groups
if len(group.LdapDn) == 0 {
continue
}
for _, lg := range ldapGroups { for _, lg := range ldapGroups {
if group.LdapDn == lg { 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) 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)
@@ -281,15 +322,23 @@ func UserLdapNewLoginCheck(username string, password string) (User, error) {
return u, nil return u, nil
} }
/* func (u *User) UserSetLastLogin() error {
// StoreLdapUser creates a user record in the database and returns the corresponding userId
func StoreLdapUser(u *User) error {
// TODO u.LastLogin = time.Now().UTC()
result, err := db.NamedExec((`UPDATE users SET LastLogin = :LastLogin WHERE UserId = :UserId`), u)
if err != nil {
log.Printf("UserSetLastLogin error executing sql update : '%s'\n", err)
return err
} else {
affected, _ := result.RowsAffected()
id, _ := result.LastInsertId()
log.Printf("UserSetLastLogin returned result id '%d' affecting %d row(s).\n", id, affected)
}
return nil return nil
} }
*/
func UserGetByID(uid uint) (User, error) { func UserGetByID(uid uint) (User, error) {
@@ -389,7 +438,7 @@ func UserGetSafesAllowed(userId int) ([]UserSafe, error) {
// join users, groups and permissions // join users, groups and permissions
rows, err := db.Queryx(` rows, err := db.Queryx(`
SELECT users.UserId, users.GroupId, SELECT users.UserId, users.GroupId, users.UserName,
permissions.SafeId, permissions.ReadOnly, safes.SafeName FROM users permissions.SafeId, permissions.ReadOnly, safes.SafeName FROM users
INNER JOIN groups ON users.GroupId = groups.GroupId INNER JOIN groups ON users.GroupId = groups.GroupId
INNER JOIN permissions ON groups.GroupId = permissions.GroupId INNER JOIN permissions ON groups.GroupId = permissions.GroupId
@@ -421,12 +470,13 @@ func UserGetSafesAllowed(userId int) ([]UserSafe, error) {
return results, err return results, err
} }
//log.Printf("UserGetSafesAllowed adding record : '%+v'\n", us) //log.Printf("UserGetSafesAllowed adding record : '%+v'\n", us)
debugPrint := utils.PrintStructContents(&us, 0) //debugPrint := utils.PrintStructContents(&us, 0)
log.Printf("UserGetSafesAllowed adding record :\n%s\n", debugPrint) //log.Printf("UserGetSafesAllowed adding record :\n%s\n", debugPrint)
results = append(results, us) results = append(results, us)
/* /*
// For intense debugging
// Create a map to store column names and values // Create a map to store column names and values
rowValues := make(map[string]interface{}) rowValues := make(map[string]interface{})
@@ -445,7 +495,7 @@ func UserGetSafesAllowed(userId int) ([]UserSafe, error) {
log.Println("-----------") log.Println("-----------")
*/ */
} }
log.Printf("UserGetSafesAllowed retrieved '%d' results\n", len(results)) //log.Printf("UserGetSafesAllowed retrieved '%d' results\n", len(results))
} }
return results, nil return results, nil
@@ -458,7 +508,7 @@ func UserList() ([]User, error) {
rows, err := db.Queryx("SELECT * FROM users") rows, err := db.Queryx("SELECT * FROM users")
if err != nil { if err != nil {
log.Printf("QueryUsers error executing sql record : '%s'\n", err) log.Printf("UserList error executing sql record : '%s'\n", err)
return results, err return results, err
} else { } else {
// parse all the results into a slice // parse all the results into a slice
@@ -466,13 +516,13 @@ func UserList() ([]User, error) {
var u User var u User
err = rows.StructScan(&u) err = rows.StructScan(&u)
if err != nil { if err != nil {
log.Printf("QueryUsers error parsing sql record : '%s'\n", err) log.Printf("UserList error parsing sql record : '%s'\n", err)
return results, err return results, err
} }
results = append(results, u) results = append(results, u)
} }
log.Printf("QueryUsers retrieved '%d' results\n", len(results)) log.Printf("UserList retrieved '%d' results\n", len(results))
} }
return results, nil return results, nil

View File

@@ -4,8 +4,52 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
"time"
) )
/*
func PrintStructContents(s interface{}, indentLevel int) string {
var result strings.Builder
val := reflect.ValueOf(s)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldType := typ.Field(i)
log.Printf("PrintStructContents [%d] field '%s' (%T)\n", i, field, fieldType)
indent := strings.Repeat("\t", indentLevel)
result.WriteString(fmt.Sprintf("%s%s: ", indent, fieldType.Name))
log.Printf("%s%s: \n", indent, fieldType.Name)
switch field.Kind() {
case reflect.Struct:
result.WriteString("\n")
foo := PrintStructContents(field.Interface(), indentLevel+1)
log.Printf("foo: %s\n", foo)
result.WriteString(foo)
case reflect.Uint64:
log.Printf("uint64 %v\n", field.Interface())
result.WriteString(fmt.Sprintf("%v\n", field.Interface()))
default:
log.Printf("default %v\n", field.Interface())
result.WriteString(fmt.Sprintf("%v\n", field.Interface()))
}
}
log.Printf("PrintStructContents completed\n")
return result.String()
}
*/
func PrintStructContents(s interface{}, indentLevel int) string { func PrintStructContents(s interface{}, indentLevel int) string {
var result strings.Builder var result strings.Builder
@@ -26,8 +70,13 @@ func PrintStructContents(s interface{}, indentLevel int) string {
switch field.Kind() { switch field.Kind() {
case reflect.Struct: case reflect.Struct:
result.WriteString("\n") if fieldType.Type == reflect.TypeOf(time.Time{}) {
result.WriteString(PrintStructContents(field.Interface(), indentLevel+1)) // Handle time.Time field
result.WriteString(fmt.Sprintf("%v\n", field.Interface().(time.Time).Format("2006-12-24 15:04:05")))
} else {
result.WriteString("\n")
result.WriteString(PrintStructContents(field.Interface(), indentLevel+1))
}
default: default:
result.WriteString(fmt.Sprintf("%v\n", field.Interface())) result.WriteString(fmt.Sprintf("%v\n", field.Interface()))
} }
@@ -53,3 +102,20 @@ func AppendIfNotExists[T Identifiable](slice []T, element T) []T {
// Element with the same Id does not exist, append the new element // Element with the same Id does not exist, append the new element
return append(slice, element) return append(slice, element)
} }
// UpdateStruct updates the values in the destination struct with values from the source struct
func UpdateStruct(dest interface{}, src interface{}) {
destValue := reflect.ValueOf(dest).Elem()
srcValue := reflect.ValueOf(src).Elem()
destType := destValue.Type()
for i := 0; i < srcValue.NumField(); i++ {
srcField := srcValue.Field(i)
destField := destValue.FieldByName(destType.Field(i).Name)
if destField.IsValid() && destField.Type() == srcField.Type() && destField.CanSet() {
destField.Set(srcField)
}
}
}

View File

@@ -8,8 +8,9 @@ import (
"strings" "strings"
"time" "time"
jwt "github.com/dgrijalva/jwt-go" //jwt "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
jwt "github.com/golang-jwt/jwt/v5"
) )
func GenerateToken(user_id uint) (string, error) { func GenerateToken(user_id uint) (string, error) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 86 KiB