allow user to move secret between safes
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-01-12 12:55:49 +11:00
parent a3333cebb6
commit 083fb0ebe1
3 changed files with 101 additions and 22 deletions

View File

@@ -12,7 +12,7 @@ Written by Nathan Coad (nathan.coad@dell.com)
Provide REST API to store and retrieve secrets with associated username, device name and optionally device class. Secrets are stored in sqlite database once encrypted using an AES256 block cipher wrapped in Galois Counter Mode with the standard nonce length.
All secret operations (Create, Read, Update or Delete) require successful authentication. A JWT token is returned upon login, which must be provided for all other operations.
All secret operations (Create, Read, Update or Delete) or user maangement operations require successful authentication. A JWT token is returned upon login, which must be provided for any other operation.
Users must be a member of a single group. Groups can have access to multiple safes. Groups can have read-only or read-write access to safes. Only users with an admin role can perform user-related operations such as creating users or groups, or creating/deleting safes. Users and groups can be either locally defined or sourced from LDAP lookups.
@@ -83,7 +83,7 @@ WantedBy=multi-user.target
### Login
**POST** `/api/login`
Data
Body
```
{
"username": "example_username",
@@ -95,7 +95,7 @@ This API call will return a JWT token that must be present for any other API cal
### Unlock
**POST** `/api/admin/unlock`
Data
Body
```
{
"secretKey": "Example32ByteSecretKey0123456789"
@@ -112,6 +112,7 @@ This API call can only be made once after the service has started. Subsequent ca
**POST** `/api/admin/user/add`
Create a new user record by specifying groupId
Body
```
{
@@ -122,6 +123,7 @@ Body
```
Create a new user record by specifying groupName
Body
```
{
@@ -165,6 +167,7 @@ This operation can only be performed by a user that is admin enabled. Lists curr
**POST** `/api/admin/permission/add`
Create a new read-only permission directly to a user
Body
```
{
@@ -182,6 +185,7 @@ Creates a new permission mapping user/group to safe. Currently the create permis
Delete permission by specifying description
Body
```
{
@@ -191,6 +195,7 @@ Body
```
Delete permission by specifying permission id
Body
```
{
@@ -214,6 +219,7 @@ This operation can only be performed by a user with that is admin enabled. Lists
**POST** `/api/admin/group/add`
Create a new group corresponding with an LDAP group
Body
```
{
@@ -224,6 +230,7 @@ Body
```
Create a new local admin group
Body
```
{
@@ -242,6 +249,7 @@ Ldap group must be specified via the full distinguishedName. The simplest way to
**POST** `/api/admin/group/delete`
Delete group by specifying group name
Body
```
{
@@ -251,6 +259,7 @@ Body
```
Delete group by specifying group id
Body
```
{
@@ -266,21 +275,37 @@ Deleting a group will also impact all permissions based on that group. For that
### Secrets Operations
#### Store
POST `/api/secret/store`
**POST** `/api/secret/store`
Data
Store secret if user only has access to a single safe
Body
```
{
"deviceName": "",
"deviceCategory": "",
"userName": "",
"secretValue": ""
"deviceName": "device.example.com",
"deviceCategory": "appliance",
"userName": "example-user",
"secretValue": "example-password"
}
```
Store secret if user only has access to multiple safes
Body
```
{
"safeId": 1,
"deviceName": "device.example.com",
"deviceCategory": "appliance",
"userName": "example-user",
"secretValue": "example-password"
}
```
Must be logged in to execute this command. Role of current user cannot be a ReadOnly role. Secret will be stored with the RoleId of the currently logged in user. Either deviceName or deviceCategory can be blank but not both.
Must be logged in to execute this command. Permission assigned to logged-in user cannot be a ReadOnly role. Either deviceName or deviceCategory can be blank but not both.
If a secret exists for this RoleId and matching deviceName and deviceCategory then an error will be generated.
If a secret exists with a matching deviceName and deviceCategory in a safe that the user has access to, then an error will be generated.
If the current user has access to multiple safes, then the destination safeId will also need to be specified.
#### Retrieve
POST `/api/secret/retrieve`
@@ -315,19 +340,36 @@ 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.
#### Update
POST `/api/secret/update`
**POST** `/api/secret/update`
Data
Update secret value for existing secret record
Body
```
{
"deviceName": "",
"deviceCategory": "",
"userName": "",
"secretValue": ""
"deviceName": "device.example.com",
"deviceCategory": "appliance",
"userName": "example-user",
"secretValue": "new-password"
}
```
Users with ReadOnly role will receive Forbidden error when calling this API endpoint. The values specified in deviceName and deviceCategory must match exactly one existing secret record for the RoleId of the currently logged in user. Wildcards are supported for deviceName and deviceCategory.
Move secret into different safe
Body
```
{
"safeId": 2,
"deviceName": "device.example.com",
"deviceCategory": "appliance",
"userName": "example-user",
"secretValue": "example-password"
}
```
The values specified in deviceName and deviceCategory must match exactly one existing secret record for the safes available to the currently logged in user. Wildcards are supported for deviceName and deviceCategory.
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
GET `/api/secret/list`

View File

@@ -229,7 +229,7 @@ func CheckUpdateSecretAllowed(s *models.Secret, user_id int) (int, error) {
func UpdateSecret(c *gin.Context) {
var err error
var input SecretInput
var user_id int
var UserId int
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "UpdateSecret error binding to input JSON : " + err.Error()})
@@ -260,7 +260,7 @@ func UpdateSecret(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
user_id = val.(int)
UserId = val.(int)
//log.Printf("user_id: %v\n", user_id)
}
@@ -271,10 +271,12 @@ func UpdateSecret(c *gin.Context) {
s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory
secretList, err := models.SecretsGetAllowed(&s, user_id)
secretList, err := models.SecretsGetAllowed(&s, UserId)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("error determining secret : '%s'", err)})
errString := fmt.Sprintf("error determining secret : '%s'", err)
log.Printf("UpdateSecret %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
@@ -293,6 +295,39 @@ func UpdateSecret(c *gin.Context) {
return
}
// Check for correct safe
if input.SafeId > 0 {
if input.SafeId != secretList[0].Secret.SafeId {
// Check if user has access to the new safe
allowedSafes, err := models.UserGetSafesAllowed(UserId)
if err != nil {
errString := fmt.Sprintf("error determining allowed safes : '%s'", err)
log.Printf("UpdateSecret %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
allowedFound := false
for i := range allowedSafes {
if allowedSafes[i].SafeId == input.SafeId {
allowedFound = true
break
}
}
if !allowedFound {
errString := "secret cannot be moved into inaccessible safe"
log.Printf("UpdateSecret %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
log.Printf("UpdateSecret moving secret id '%d' into safe id '%d'\n", secretList[0].SecretId, input.SafeId)
s.SafeId = input.SafeId
}
}
s.SecretId = secretList[0].SecretId
// check for empty fields in the update request and update from the existing record

View File

@@ -280,6 +280,7 @@ func UserLdapNewLoginCheck(username string, password string) (User, error) {
return u, nil
}
/*
// StoreLdapUser creates a user record in the database and returns the corresponding userId
func StoreLdapUser(u *User) error {
@@ -287,6 +288,7 @@ func StoreLdapUser(u *User) error {
return nil
}
*/
func UserGetByID(uid uint) (User, error) {