update README
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-01-12 09:41:26 +11:00
parent d087492c31
commit a3333cebb6
6 changed files with 170 additions and 57 deletions

124
README.md
View File

@@ -138,55 +138,131 @@ This operation can only be performed by a user that is a member of a group with
#### Remove User #### Remove User
**POST** `/api/admin/user/delete` **POST** `/api/admin/user/delete`
Data Body
``` ```
{ {
"userName": "example_username" "userName": "example_username"
} }
``` ```
This operation can only be performed by a user with a role that is admin enabled. Removes user account corresponding to specified userName. This operation can only be performed by a user that is admin enabled. Removes user account corresponding to specified userName.
#### List Users #### List Users
GET `/api/admin/users` **GET** `/api/admin/users`
This operation can only be performed by a user with a role that is admin enabled. Lists currently defined users. This operation can only be performed by a user that is admin enabled. Lists currently defined users.
### Permission Operations
Permissions can be assigned either via a group or directly to a user Id. Permissions map the user to the safe(s) they are allowed to access. By default a permission grants read-write access to a safe, although that can be set to read-only if required.
#### List Permissions
**GET** `/api/admin/permissions`
This operation can only be performed by a user that is admin enabled. Lists currently defined permissions.
#### Create Permission
**POST** `/api/admin/permission/add`
Create a new read-only permission directly to a user
Body
```
{
"Description": "Readonly access to default safe",
"safeId": 1,
"userId": 2,
"readOnly": true
}
```
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
**POST** `/api/admin/permission/delete`
Delete permission by specifying description
Body
```
{
"Description":"Readonly access to default safe"
}
```
Delete permission by specifying permission id
Body
```
{
"permissionId":2
}
```
Deletes a permission mapping either a user or a group to a safe. Either the permission description or permission Id can be specified. This operation can only be performed by a user that is admin enabled.
Deleting a permission should be performed prior to deleting any groups specified in that permission.
### Group Operations ### Group Operations
#### List Groups #### List Groups
GET `/api/admin/groups/list` **GET** `/api/admin/groups`
This operations has not yet been implemented. This operation can only be performed by a user with that is admin enabled. Lists currently defined groups.
This operation can only be performed by a user with a role that is admin enabled. Lists currently defined groups. #### Create Group
**POST** `/api/admin/group/add`
### Role Operations - Deprecated Create a new group corresponding with an LDAP group
Body
#### List Roles
GET `/api/admin/roles`
This operation can only be performed by a user with a role that is admin enabled. Lists currently defined roles.
#### Create Role
POST `/api/admin/role/add`
Data
``` ```
{ {
"roleName":"example role", "groupName":"ldap access for smt_users",
"readOnly":true, "ldapGroup":true,
"Admin":false, "LdapDn":"CN=smt_users,OU=Groups,DC=example,DC=com"
"LdapGroup":"CN=smt_users,OU=Groups,DC=example,DC=com"
} }
``` ```
This operation can only be performed by a user with a role that is admin enabled. Creates a new role. Can be read only, or admin enabled, or map to an LDAP group if LDAP integration is being used. Create a new local admin group
Body
```
{
"groupName":"admin group",
"Admin":true,
Users allocated to this role will only be able to access secrets that are part of this role. The exception being users in a role with admin enabled; any user in an admin enabled role can access all secrets. }
```
Creates a new group, which can be entirely local or mapped to an LDAP security group if LDAP integration is enabled. This operation can only be performed by a user that is admin enabled, or that is a member of a group that is admin enabled.
Ldap group must be specified via the full distinguishedName. The simplest way to get this information is to run the command `dsquery group -name <known group name>` from a windows machine. Ldap group must be specified via the full distinguishedName. The simplest way to get this information is to run the command `dsquery group -name <known group name>` from a windows machine.
#### Delete Group
**POST** `/api/admin/group/delete`
Delete group by specifying group name
Body
```
{
"groupName":"admin group"
}
```
Delete group by specifying group id
Body
```
{
"groupId":2
}
```
Deletes an existing group. Either group name or group Id can be specified. If both are specified, group Id takes precedence. This operation can only be performed by a user that is admin enabled, or that is a member of a group that is admin enabled.
Deleting a group will also impact all permissions based on that group. For that reason, permissions should be removed before a group is deleted.
### Secrets Operations ### Secrets Operations
#### Store #### Store

View File

@@ -155,7 +155,7 @@ func AddUser(c *gin.Context) {
return return
} }
c.JSON(http.StatusOK, gin.H{"message": "user registration success"}) c.JSON(http.StatusOK, gin.H{"message": "user registration success", "data": u})
} }
func Login(c *gin.Context) { func Login(c *gin.Context) {

View File

@@ -118,27 +118,47 @@ func DeleteGroupHandler(c *gin.Context) {
g.GroupId = input.GroupId g.GroupId = input.GroupId
g.GroupName = input.GroupName g.GroupName = input.GroupName
//remove leading/trailing spaces in groupname if g.GroupId > 0 { // Group Id was specified
g.GroupName = html.EscapeString(strings.TrimSpace(g.GroupName)) // Confirm group exists
testGroup, _ := models.GroupGetById(g.GroupId)
log.Printf("DeleteGroupHandler confirming group id '%d' exists\n", g.GroupId)
// Confirm group exists if (models.Group{} == testGroup) {
testGroup, _ := models.GroupGetByName(g.GroupName) errString := fmt.Sprintf("attempt to delete non-existing group id '%d'", g.GroupId)
log.Printf("DeleteGroupHandler confirming group '%s' exists\n", g.GroupName) log.Printf("DeleteGroupHandler %s\n", errString)
if (models.Group{} == testGroup) { c.JSON(http.StatusBadRequest, gin.H{"error": errString})
errString := fmt.Sprintf("attempt to delete non-existing group '%s'", g.GroupName) return
log.Printf("DeleteGroupHandler %s\n", errString) }
c.JSON(http.StatusBadRequest, gin.H{"error": errString}) g.GroupName = testGroup.GroupName
return } else if len(g.GroupName) > 0 { // Group name was specified
} else { //remove leading/trailing spaces in groupname
err := g.GroupDelete() g.GroupName = html.EscapeString(strings.TrimSpace(g.GroupName))
if err != nil { // Confirm group exists
errString := fmt.Sprintf("error deleting group : '%s'", err) testGroup, _ := models.GroupGetByName(g.GroupName)
log.Printf("DeleteGroupHandler confirming group '%s' exists\n", g.GroupName)
if (models.Group{} == testGroup) {
errString := fmt.Sprintf("attempt to delete non-existing group '%s'", g.GroupName)
log.Printf("DeleteGroupHandler %s\n", errString) log.Printf("DeleteGroupHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString}) c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return return
} }
c.JSON(http.StatusOK, gin.H{"message": "group deletion success"}) g.GroupId = testGroup.GroupId
} }
// TODO verify no permissions refer to this group still
err := g.GroupDelete()
if err != nil {
errString := fmt.Sprintf("error deleting group : '%s'", err)
log.Printf("DeleteGroupHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
c.JSON(http.StatusOK, gin.H{"message": "group deletion success"})
} }

View File

@@ -320,7 +320,7 @@ func UpdateSecret(c *gin.Context) {
return return
} }
c.JSON(http.StatusOK, gin.H{"message": "secret updated successfully"}) c.JSON(http.StatusOK, gin.H{"message": "secret updated successfully", "data": models.SecretRestricted(s)})
} else { } else {
c.JSON(http.StatusBadRequest, gin.H{"error": "multiple secrets matched search parameters, be more specific"}) c.JSON(http.StatusBadRequest, gin.H{"error": "multiple secrets matched search parameters, be more specific"})
return return

View File

@@ -26,6 +26,19 @@ func GroupGetByName(groupname string) (Group, error) {
return g, nil return g, nil
} }
// GroupGetById queries the database for the specified group name
func GroupGetById(groupId int) (Group, error) {
var g Group
// Query database for matching group object
err := db.QueryRowx("SELECT * FROM groups WHERE GroupId=?", groupId).StructScan(&g)
if err != nil {
return g, errors.New("group not found")
}
return g, nil
}
// GroupGetByName queries the database for a group with the specified LDAP distinguishedName // GroupGetByName queries the database for a group with the specified LDAP distinguishedName
func GroupGetByLdapDn(ldapDn string) (Group, error) { func GroupGetByLdapDn(ldapDn string) (Group, error) {
var g Group var g Group

View File

@@ -71,26 +71,30 @@ func ReceiveKey(key string) error {
return errors.New("secret key provided is not exactly 32 bytes long") return errors.New("secret key provided is not exactly 32 bytes long")
} }
// TODO hash the secret key and store it on disk so we can verify if correct secret key is received if os.Getenv("SECRETS_KEY") == "" {
filePath, _ := getHashFilePath() // Hash the secret key and store it on disk so we can verify if correct secret key is received
filePath, _ := getHashFilePath()
if filePath != "" && utils.FileExists(filePath) { if filePath != "" && utils.FileExists(filePath) {
log.Printf("ReceiveKey detected hash file at '%s'\n", filePath) log.Printf("ReceiveKey detected hash file at '%s'\n", filePath)
// File already exists, compare received key with hash in file // File already exists, compare received key with hash in file
compare, err := compareHashWithPlaintext(key, filePath) compare, err := compareHashWithPlaintext(key, filePath)
if err != nil { if err != nil {
return fmt.Errorf("unable to verify secret key: '%s'", err.Error()) return fmt.Errorf("unable to verify secret key: '%s'", err.Error())
} }
if !compare { if !compare {
return errors.New("secret key is not correct") return errors.New("secret key is not correct")
} else {
log.Printf("ReceiveKey successfully verified supplied key\n")
}
} else if filePath != "" {
log.Printf("ReceiveKey storing key into file '%s'\n", filePath)
storeKeyHash(key, filePath)
} else { } else {
log.Printf("ReceiveKey successfully verified supplied key\n") return fmt.Errorf("unable to determine path to key hash file '%s'", hashFileName)
} }
} else if filePath != "" {
log.Printf("ReceiveKey storing key into file '%s'\n", filePath)
storeKeyHash(key, filePath)
} else { } else {
return fmt.Errorf("unable to determine path to key hash file '%s'", hashFileName) log.Printf("ReceiveKey not storing hash on disk since we read key from environment variable")
} }
// Store the secret key in memory so that we can access it when encrypting/decrypting // Store the secret key in memory so that we can access it when encrypting/decrypting