feat: allow owner to deploy system master key from deploy page

This commit is contained in:
2026-04-08 21:37:27 +02:00
parent 1cf7f50bfb
commit dd4af5b25c
3 changed files with 76 additions and 12 deletions
+5 -2
View File
@@ -59,9 +59,12 @@ Server groups are used as targets for:
1. Navigate to **Deploy**
2. Select an **SSH key** from the dropdown (shows all keys from all users)
3. Select a **target server**
4. Click **Deploy**
4. Choose an authentication method (password or existing key)
5. Click **Deploy**
Keywarden connects to the target server using the system master key and appends the selected public key to the server user's `~/.ssh/authorized_keys`.
Keywarden connects to the target server and appends the selected public key to the server user's `~/.ssh/authorized_keys`.
> **Owner only:** The SSH key dropdown includes the **[MASTER] System Master Key** as the first option. This allows the owner to deploy the system master key directly to servers from the Deploy page — useful for initial server setup or re-deployment after master key regeneration.
### Group Deployment
+2
View File
@@ -35,6 +35,7 @@ Owner → Admin → User
| Test server connectivity | ❌ | ✅ | ✅ |
| **Deployments** | | | |
| Manual key deployment | ❌ | ✅ | ✅ |
| Deploy system master key | ❌ | ❌ | ✅ |
| Group deployment | ❌ | ✅ | ✅ |
| **Access Assignments** | | | |
| Create/edit/delete assignments | ❌ | ✅ | ✅ |
@@ -88,6 +89,7 @@ Admins **cannot** access the Admin Settings page, regenerate the master key, man
The **Owner** role has unrestricted access. In addition to all Admin permissions, the owner can:
- Deploy the system master key to servers (via the Deploy page)
- Access the Admin Settings page
- Configure application settings (app name, session timeout, default key type)
- Configure security settings (password policy, account lockout, MFA enforcement)
+69 -10
View File
@@ -1434,6 +1434,36 @@ func (h *Handler) handleServerTestAuth(w http.ResponseWriter, r *http.Request) {
}
}
// masterKeyForDeploy returns the system master key as a virtual SSHKey entry for deployment.
// Returns nil if the master key is not available.
func (h *Handler) masterKeyForDeploy() *models.SSHKey {
pub, err := h.keys.GetSystemMasterKeyPublic()
if err != nil || pub == "" {
return nil
}
fp, _ := h.keys.GetSystemMasterKeyFingerprint()
return &models.SSHKey{
ID: -1,
UserID: 0,
Name: "[MASTER] System Master Key",
KeyType: "ed25519",
PublicKey: pub,
Fingerprint: fp,
}
}
// prependMasterKey adds the system master key to the key list if the user is an owner.
func (h *Handler) prependMasterKey(keyList []models.SSHKey, role string) []models.SSHKey {
if !isOwner(role) {
return keyList
}
mk := h.masterKeyForDeploy()
if mk == nil {
return keyList
}
return append([]models.SSHKey{*mk}, keyList...)
}
func (h *Handler) handleDeploy(w http.ResponseWriter, r *http.Request) {
userID := h.getUserID(r)
user, _ := h.auth.GetUserByID(userID)
@@ -1442,6 +1472,9 @@ func (h *Handler) handleDeploy(w http.ResponseWriter, r *http.Request) {
groups, _ := h.servers.GetAllGroups()
deployments, _ := h.deploy.GetDeployments(userID)
// Owner can deploy the system master key
keyList = h.prependMasterKey(keyList, user.Role)
if r.Method == http.MethodGet {
data := &PageData{
Title: "Deploy Keys",
@@ -1461,14 +1494,26 @@ func (h *Handler) handleDeploy(w http.ResponseWriter, r *http.Request) {
serverID, _ := strconv.ParseInt(r.FormValue("server_id"), 10, 64)
authMethod := r.FormValue("auth_method")
key, err := h.keys.GetKeyByID(keyID, userID)
if err != nil {
// Try global access for admin/owner deploying other users' keys
key, err = h.keys.GetKeyByIDGlobal(keyID)
if err != nil {
var key *models.SSHKey
var err error
if keyID == -1 && isOwner(user.Role) {
// Owner deploying the system master key
key = h.masterKeyForDeploy()
if key == nil {
http.Redirect(w, r, "/deploy", http.StatusSeeOther)
return
}
} else {
key, err = h.keys.GetKeyByID(keyID, userID)
if err != nil {
// Try global access for admin/owner deploying other users' keys
key, err = h.keys.GetKeyByIDGlobal(keyID)
if err != nil {
http.Redirect(w, r, "/deploy", http.StatusSeeOther)
return
}
}
}
server, err := h.servers.GetByIDGlobal(serverID)
@@ -1527,14 +1572,26 @@ func (h *Handler) handleDeployGroup(w http.ResponseWriter, r *http.Request) {
groupID, _ := strconv.ParseInt(r.FormValue("group_id"), 10, 64)
authMethod := r.FormValue("auth_method")
key, err := h.keys.GetKeyByID(keyID, userID)
if err != nil {
// Try global access for admin/owner deploying other users' keys
key, err = h.keys.GetKeyByIDGlobal(keyID)
if err != nil {
var key *models.SSHKey
var keyErr error
if keyID == -1 && isOwner(user.Role) {
// Owner deploying the system master key
key = h.masterKeyForDeploy()
if key == nil {
http.Redirect(w, r, "/deploy", http.StatusSeeOther)
return
}
} else {
key, keyErr = h.keys.GetKeyByID(keyID, userID)
if keyErr != nil {
// Try global access for admin/owner deploying other users' keys
key, keyErr = h.keys.GetKeyByIDGlobal(keyID)
if keyErr != nil {
http.Redirect(w, r, "/deploy", http.StatusSeeOther)
return
}
}
}
group, err := h.servers.GetGroupByIDGlobal(groupID)
@@ -1546,6 +1603,7 @@ func (h *Handler) handleDeployGroup(w http.ResponseWriter, r *http.Request) {
members, err := h.servers.GetGroupMembersGlobal(groupID)
if err != nil || len(members) == 0 {
keyList, _ := h.keys.GetAllKeys()
keyList = h.prependMasterKey(keyList, user.Role)
serverList, _ := h.servers.GetAllServers()
groups, _ := h.servers.GetAllGroups()
deployments, _ := h.deploy.GetDeployments(userID)
@@ -1601,6 +1659,7 @@ func (h *Handler) handleDeployGroup(w http.ResponseWriter, r *http.Request) {
}
keyList, _ := h.keys.GetAllKeys()
keyList = h.prependMasterKey(keyList, user.Role)
serverList, _ := h.servers.GetAllServers()
groups, _ := h.servers.GetAllGroups()
deployments, _ := h.deploy.GetDeployments(userID)