All checks were successful
continuous-integration/drone/push Build is passing
424 lines
14 KiB
Markdown
424 lines
14 KiB
Markdown
---
|
|
title: Secrets Management Tool (SMT)
|
|
---
|
|
|
|
Build Date: `{BUILDTIME}`
|
|
|
|
Build Hash: `{SHA1VER}`
|
|
|
|
Written by Nathan Coad (nathan.coad@dell.com)
|
|
|
|
## Overview
|
|
|
|
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) 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.
|
|
|
|
## Installation
|
|
|
|
Only tested on x64 Linux, but code should compile on other platforms.
|
|
|
|
1. Copy binary to chosen location, eg /srv/smt/smt
|
|
2. Create .env file in same directory as binary, populate as per Configuration section below
|
|
3. Create systemd service definition
|
|
4. Start service
|
|
|
|
## Configuration
|
|
|Environment Variable Name| Description | Example | Default |
|
|
|--|--|--|--|
|
|
| LOG_FILE | Specify the name/path of file to write log messages to | /var/log/smt.log | ./smt.log
|
|
| BIND_IP | Specify the local IP address to bind to. | 127.0.0.1 | Primary IPv4 address |
|
|
| BIND_PORT | Specify the TCP/IP port to bind to. | 443 | 8443 |
|
|
| TLS_KEY_FILE | Specify the filename of the TLS certificate private key (must be unencrypted) in PEM format | key.pem | privkey.pem |
|
|
| TLS_CERT_FILE | Specify the filename of the TLS certificate file in PEM format | cert.pem | cert.pem |
|
|
| TOKEN_HOUR_LIFESPAN | Number of hours that the JWT token returned at login is valid | 12 | No default specified, must define this value |
|
|
| API_SECRET | Secret to use when generating JWT token | 3c55990bd479322e2053db3a8 | No default specified, must define this value |
|
|
| INITIAL_PASSWORD | Password to set for builtin Administrator account created when first started, can remove this value after first start. Can specify in plaintext or bcrypt hash | $2a$10$s39a82wrRAdOJVZEkkrSReVnXprz5mxU30ZBO.dHPYTncQCsUD9ce | password |
|
|
| SECRETS_KEY | Key to use for AES256 GCM encryption. Must be exactly 32 bytes | AES256Key-32Characters1234567890 | No default specified, must define this value or use /api/unlock at runtime |
|
|
|
|
If the TLS certificate and key files cannot be located in the specified location, a self signed certificate will be generated with a 1 year validity period.
|
|
|
|
Example for generating API_SECRET and SECRETS_KEY is the following command on linux: `head /dev/urandom | tr -dc A-Za-z0-9 | head -c32`
|
|
|
|
### LDAP specific configuration
|
|
|
|
Several optional environment variables are available to configure LDAP integration if required. If these parameters are not specifed, LDAP integration will not be used.
|
|
|
|
If the LDAP_BIND_ADDRESS is specified, SMT will attempt to perform an LDAP search for the provided username if no matches to the locally configured users are found in the database. This search will utilise the provided credentials to perform the LDAP bind.
|
|
|
|
This lookup will utilise the sAMAccountName property of the user object in Active Directory. No other LDAP providers have been tested.
|
|
|
|
Upon successfully verifying the LDAP credentials, SMT will verify if any of the group memberships matches a role defined in the SMT database. If no match is found, the authentication will not succeed.
|
|
|
|
|Environment Variable Name| Description | Example | Default |
|
|
|--|--|--|--|
|
|
| LDAP_BIND_ADDRESS | Specify the LDAP Bind address. Only LDAPS on port 636 is supported. Do not specify port 636 in the bind address | dc.example.com | No default specified |
|
|
| LDAP_BASE_DN | Specify the base DN to use when binding to AD | "CN=Users,DC=example,DC=com" | No default specified |
|
|
| LDAP_TRUST_CERT_FILE | Specify filepath to PEM format public certificate of Certificate Authority signing LDAPS communications | caroot.pem | No default specified, must define this value |
|
|
| LDAP_INSECURE_VALIDATION | Specify whether to skip certificate validation when connecting to LDAPS. Do not enable this in production | true | false |
|
|
|
|
## Systemd script
|
|
|
|
Create/update the systemd service definition at /etc/systemd/system/smt.service and then run systemctl daemon-reload
|
|
```
|
|
[Unit]
|
|
Description=Secrets Management Tool
|
|
After=network.target
|
|
#StartLimitIntervalSec=0
|
|
|
|
[Service]
|
|
Type=simple
|
|
Restart=always
|
|
RestartSec=1
|
|
User=root
|
|
ExecStart=/srv/smt/smt
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
## API Usage
|
|
|
|
### Login
|
|
**POST** `/api/login`
|
|
|
|
Body
|
|
```
|
|
{
|
|
"username": "example_username",
|
|
"password": "example_password"
|
|
}
|
|
```
|
|
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
|
|
**POST** `/api/admin/unlock`
|
|
|
|
Body
|
|
```
|
|
{
|
|
"secretKey": "Example32ByteSecretKey0123456789"
|
|
}
|
|
```
|
|
|
|
If the SECRETS_KEY environment variable is not defined, this API call to unlock stored secrets must be performed after initial startup of SMT. Storing/retrieval of secrets will not succeed until this API call has been made.
|
|
|
|
This API call can only be made once after the service has started. Subsequent calls will receive an error until the service is restarted.
|
|
|
|
### User Operations
|
|
|
|
#### Register User
|
|
**POST** `/api/admin/user/add`
|
|
|
|
Create a new user record by specifying groupId
|
|
|
|
Body
|
|
```
|
|
{
|
|
"userName": "Test User",
|
|
"password": "Example Password",
|
|
"groupId": "1",
|
|
}
|
|
```
|
|
|
|
Create a new user record by specifying groupName
|
|
|
|
Body
|
|
```
|
|
{
|
|
"userName": "Test User",
|
|
"password": "Example Password",
|
|
"groupName": "Users",
|
|
}
|
|
```
|
|
|
|
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.
|
|
|
|
#### Remove User
|
|
**POST** `/api/admin/user/delete`
|
|
|
|
Body
|
|
```
|
|
{
|
|
"userName": "example_username"
|
|
}
|
|
```
|
|
|
|
This operation can only be performed by a user that is admin enabled. Removes user account corresponding to specified userName.
|
|
|
|
#### List Users
|
|
**GET** `/api/admin/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
|
|
|
|
#### List Groups
|
|
**GET** `/api/admin/groups`
|
|
|
|
This operation can only be performed by a user with that is admin enabled. Lists currently defined groups.
|
|
|
|
#### Create Group
|
|
**POST** `/api/admin/group/add`
|
|
|
|
Create a new group corresponding with an LDAP group
|
|
|
|
Body
|
|
```
|
|
{
|
|
"groupName":"ldap access for smt_users",
|
|
"ldapGroup":true,
|
|
"LdapDn":"CN=smt_users,OU=Groups,DC=example,DC=com"
|
|
}
|
|
```
|
|
|
|
Create a new local admin group
|
|
|
|
Body
|
|
```
|
|
{
|
|
"groupName":"admin group",
|
|
"Admin":true,
|
|
|
|
}
|
|
```
|
|
|
|
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.
|
|
|
|
|
|
#### 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.
|
|
|
|
### Safes Operations
|
|
|
|
#### List
|
|
**GET** `/api/safe/list`
|
|
|
|
This operation lists all the safes that the currently logged in user has access to.
|
|
|
|
#### List All
|
|
**GET** `/api/admin/safe/listall`
|
|
|
|
This operation lists all the safes defined in the database. 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.
|
|
|
|
#### Create Safe
|
|
|
|
**POST** `/api/admin/safe/add`
|
|
|
|
Create a new safe
|
|
|
|
Body
|
|
```
|
|
{
|
|
"safeName":"Example second safe"
|
|
}
|
|
```
|
|
|
|
This operation creates a new safe in the database. The operation returns details of the created safe, including the allocated safeId. 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.
|
|
|
|
#### Delete Safe
|
|
|
|
**POST** `/api/admin/safe/delete`
|
|
|
|
Delete an existing safe
|
|
|
|
Body
|
|
```
|
|
{
|
|
"safeName":"Example second safe"
|
|
}
|
|
```
|
|
|
|
This operation deletes a safe defined in the database. If the safe contained any secrets, they are now inaccessible except to a user that is admin enabled. It is recommended that any secrets stored within the safe are moved to a different safe via the `/api/secret/update` API endpoint.
|
|
|
|
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.
|
|
|
|
### Secrets Operations
|
|
|
|
#### Store
|
|
**POST** `/api/secret/add`
|
|
|
|
Store secret if user only has access to a single safe
|
|
|
|
Body
|
|
```
|
|
{
|
|
"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. 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 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/get`
|
|
|
|
Body
|
|
```
|
|
{
|
|
"deviceName": "",
|
|
"deviceCategory": "",
|
|
"userName": ""
|
|
}
|
|
```
|
|
|
|
Must be logged in to execute this command. Only secrets registered with the current user's RoleId 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.
|
|
1. The percent sign % wildcard matches any sequence of zero or more characters.
|
|
2. The underscore _ wildcard matches any single character.
|
|
|
|
#### Search by device name
|
|
|
|
**GET** `/api/secret/retrieve/name/<searchname>`
|
|
|
|
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.
|
|
|
|
#### Search by device category
|
|
|
|
**GET** `/api/secret/retrieve/category/<searchname>`
|
|
|
|
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`
|
|
|
|
Update secret value for existing secret record
|
|
|
|
Body
|
|
```
|
|
{
|
|
"deviceName": "device.example.com",
|
|
"deviceCategory": "appliance",
|
|
"userName": "example-user",
|
|
"secretValue": "new-password"
|
|
}
|
|
```
|
|
|
|
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`
|
|
|
|
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
|
|
|
|
## Database Schema
|
|
 |