diff options
author | Magnus Lindvall <magnus@dnmgns.com> | 2018-05-24 06:59:02 +0200 |
---|---|---|
committer | Lauris BH <lauris@nix.lv> | 2018-05-24 07:59:02 +0300 |
commit | cdb9478774e6c5cebf5a75ff35bfa6d8a37bdbdb (patch) | |
tree | a3f8a487c45d43b15a9aaf7518e0b342880b3361 /models | |
parent | b908ac9fab141b72f38db3d40a9f6054bb701982 (diff) | |
download | gitea-cdb9478774e6c5cebf5a75ff35bfa6d8a37bdbdb.tar.gz gitea-cdb9478774e6c5cebf5a75ff35bfa6d8a37bdbdb.zip |
LDAP Public SSH Keys synchronization (#1844)
* Add LDAP Key Synchronization feature
Signed-off-by: Magnus Lindvall <magnus@dnmgns.com>
* Add migration: add login source id column for public_key table
* Only update keys if needed
* Add function to only list pubkey synchronized from ldap
* Only list pub ssh keys synchronized from ldap. Do not sort strings as ExistsInSlice does it.
* Only get keys belonging to current login source id
* Set default login source id to 0
* Some minor cleanup. Add integration tests (updete dep testify)
Diffstat (limited to 'models')
-rw-r--r-- | models/migrations/migrations.go | 2 | ||||
-rw-r--r-- | models/migrations/v66.go | 22 | ||||
-rw-r--r-- | models/ssh_key.go | 38 | ||||
-rw-r--r-- | models/user.go | 134 |
4 files changed, 181 insertions, 15 deletions
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 7c90f1eb1f..1300065ab4 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -184,6 +184,8 @@ var migrations = []Migration{ NewMigration("add multiple assignees", addMultipleAssignees), // v65 -> v66 NewMigration("add u2f", addU2FReg), + // v66 -> v67 + NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable), } // Migrate database to current version diff --git a/models/migrations/v66.go b/models/migrations/v66.go new file mode 100644 index 0000000000..43acfb4ea5 --- /dev/null +++ b/models/migrations/v66.go @@ -0,0 +1,22 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + + "github.com/go-xorm/xorm" +) + +func addLoginSourceIDToPublicKeyTable(x *xorm.Engine) error { + type PublicKey struct { + LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"` + } + + if err := x.Sync2(new(PublicKey)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/ssh_key.go b/models/ssh_key.go index 97a2d2dee4..997e8ee997 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -47,13 +47,14 @@ const ( // PublicKey represents a user or deploy SSH public key. type PublicKey struct { - ID int64 `xorm:"pk autoincr"` - OwnerID int64 `xorm:"INDEX NOT NULL"` - Name string `xorm:"NOT NULL"` - Fingerprint string `xorm:"NOT NULL"` - Content string `xorm:"TEXT NOT NULL"` - Mode AccessMode `xorm:"NOT NULL DEFAULT 2"` - Type KeyType `xorm:"NOT NULL DEFAULT 1"` + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"INDEX NOT NULL"` + Name string `xorm:"NOT NULL"` + Fingerprint string `xorm:"NOT NULL"` + Content string `xorm:"TEXT NOT NULL"` + Mode AccessMode `xorm:"NOT NULL DEFAULT 2"` + Type KeyType `xorm:"NOT NULL DEFAULT 1"` + LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"` CreatedUnix util.TimeStamp `xorm:"created"` UpdatedUnix util.TimeStamp `xorm:"updated"` @@ -391,7 +392,7 @@ func addKey(e Engine, key *PublicKey) (err error) { } // AddPublicKey adds new public key to database and authorized_keys file. -func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) { +func AddPublicKey(ownerID int64, name, content string, LoginSourceID int64) (*PublicKey, error) { log.Trace(content) fingerprint, err := calcFingerprint(content) @@ -420,12 +421,13 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) { } key := &PublicKey{ - OwnerID: ownerID, - Name: name, - Fingerprint: fingerprint, - Content: content, - Mode: AccessModeWrite, - Type: KeyTypeUser, + OwnerID: ownerID, + Name: name, + Fingerprint: fingerprint, + Content: content, + Mode: AccessModeWrite, + Type: KeyTypeUser, + LoginSourceID: LoginSourceID, } if err = addKey(sess, key); err != nil { return nil, fmt.Errorf("addKey: %v", err) @@ -471,6 +473,14 @@ func ListPublicKeys(uid int64) ([]*PublicKey, error) { Find(&keys) } +// ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and login source. +func ListPublicLdapSSHKeys(uid int64, LoginSourceID int64) ([]*PublicKey, error) { + keys := make([]*PublicKey, 0, 5) + return keys, x. + Where("owner_id = ? AND login_source_id = ?", uid, LoginSourceID). + Find(&keys) +} + // UpdatePublicKeyUpdated updates public key use time. func UpdatePublicKeyUpdated(id int64) error { // Check if key exists before update as affected rows count is unreliable diff --git a/models/user.go b/models/user.go index d642054979..1497eef44d 100644 --- a/models/user.go +++ b/models/user.go @@ -1356,6 +1356,119 @@ func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) { return repos, nil } +// deleteKeysMarkedForDeletion returns true if ssh keys needs update +func deleteKeysMarkedForDeletion(keys []string) (bool, error) { + // Start session + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return false, err + } + + // Delete keys marked for deletion + var sshKeysNeedUpdate bool + for _, KeyToDelete := range keys { + key, err := SearchPublicKeyByContent(KeyToDelete) + if err != nil { + log.Error(4, "SearchPublicKeyByContent: %v", err) + continue + } + if err = deletePublicKeys(sess, key.ID); err != nil { + log.Error(4, "deletePublicKeys: %v", err) + continue + } + sshKeysNeedUpdate = true + } + + if err := sess.Commit(); err != nil { + return false, err + } + + return sshKeysNeedUpdate, nil +} + +func addLdapSSHPublicKeys(s *LoginSource, usr *User, SSHPublicKeys []string) bool { + var sshKeysNeedUpdate bool + for _, sshKey := range SSHPublicKeys { + if strings.HasPrefix(strings.ToLower(sshKey), "ssh") { + sshKeyName := fmt.Sprintf("%s-%s", s.Name, sshKey[0:40]) + if _, err := AddPublicKey(usr.ID, sshKeyName, sshKey, s.ID); err != nil { + log.Error(4, "addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, err) + } else { + log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s", s.Name, usr.Name) + sshKeysNeedUpdate = true + } + } else { + log.Warn("addLdapSSHPublicKeys[%s]: Skipping invalid LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey) + } + } + return sshKeysNeedUpdate +} + +func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *User) bool { + var sshKeysNeedUpdate bool + + log.Trace("synchronizeLdapSSHPublicKeys[%s]: Handling LDAP Public SSH Key synchronization for user %s", s.Name, usr.Name) + + // Get Public Keys from DB with current LDAP source + var giteaKeys []string + keys, err := ListPublicLdapSSHKeys(usr.ID, s.ID) + if err != nil { + log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error listing LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) + } + + for _, v := range keys { + giteaKeys = append(giteaKeys, v.OmitEmail()) + } + + // Get Public Keys from LDAP and skip duplicate keys + var ldapKeys []string + for _, v := range SSHPublicKeys { + ldapKey := strings.Join(strings.Split(v, " ")[:2], " ") + if !util.ExistsInSlice(ldapKey, ldapKeys) { + ldapKeys = append(ldapKeys, ldapKey) + } + } + + // Check if Public Key sync is needed + if util.IsEqualSlice(giteaKeys, ldapKeys) { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Keys are already in sync for %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) + return false + } + log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Key needs update for user %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) + + // Add LDAP Public SSH Keys that doesn't already exist in DB + var newLdapSSHKeys []string + for _, LDAPPublicSSHKey := range ldapKeys { + if !util.ExistsInSlice(LDAPPublicSSHKey, giteaKeys) { + newLdapSSHKeys = append(newLdapSSHKeys, LDAPPublicSSHKey) + } + } + if addLdapSSHPublicKeys(s, usr, newLdapSSHKeys) { + sshKeysNeedUpdate = true + } + + // Mark LDAP keys from DB that doesn't exist in LDAP for deletion + var giteaKeysToDelete []string + for _, giteaKey := range giteaKeys { + if !util.ExistsInSlice(giteaKey, ldapKeys) { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: Marking LDAP Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey) + giteaKeysToDelete = append(giteaKeysToDelete, giteaKey) + } + } + + // Delete LDAP keys from DB that doesn't exist in LDAP + needUpd, err := deleteKeysMarkedForDeletion(giteaKeysToDelete) + if err != nil { + log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error deleting LDAP Public SSH Keys marked for deletion for user %s: %v", s.Name, usr.Name, err) + } + if needUpd { + sshKeysNeedUpdate = true + } + + return sshKeysNeedUpdate +} + // SyncExternalUsers is used to synchronize users with external authorization source func SyncExternalUsers() { if !taskStatusTable.StartIfNotRunning(syncExternalUsers) { @@ -1377,10 +1490,13 @@ func SyncExternalUsers() { if !s.IsActived || !s.IsSyncEnabled { continue } + if s.IsLDAP() { log.Trace("Doing: SyncExternalUsers[%s]", s.Name) var existingUsers []int64 + var isAttributeSSHPublicKeySet = len(strings.TrimSpace(s.LDAP().AttributeSSHPublicKey)) > 0 + var sshKeysNeedUpdate bool // Find all users with this login type var users []User @@ -1389,7 +1505,6 @@ func SyncExternalUsers() { Find(&users) sr := s.LDAP().SearchEntries() - for _, su := range sr { if len(su.Username) == 0 { continue @@ -1426,11 +1541,23 @@ func SyncExternalUsers() { } err = CreateUser(usr) + if err != nil { log.Error(4, "SyncExternalUsers[%s]: Error creating user %s: %v", s.Name, su.Username, err) + } else if isAttributeSSHPublicKeySet { + log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", s.Name, usr.Name) + if addLdapSSHPublicKeys(s, usr, su.SSHPublicKey) { + sshKeysNeedUpdate = true + } } } else if updateExisting { existingUsers = append(existingUsers, usr.ID) + + // Synchronize SSH Public Key if that attribute is set + if isAttributeSSHPublicKeySet && synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) { + sshKeysNeedUpdate = true + } + // Check if user data has changed if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || strings.ToLower(usr.Email) != strings.ToLower(su.Mail) || @@ -1455,6 +1582,11 @@ func SyncExternalUsers() { } } + // Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed + if sshKeysNeedUpdate { + RewriteAllPublicKeys() + } + // Deactivate users not present in LDAP if updateExisting { for _, usr := range users { |