mirror of
https://github.com/go-gitea/gitea.git
synced 2025-06-21 21:48:00 +02:00
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)
This commit is contained in:

committed by
Lauris BH

parent
b908ac9fab
commit
cdb9478774
@ -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
|
||||
|
22
models/migrations/v66.go
Normal file
22
models/migrations/v66.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
|
134
models/user.go
134
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 {
|
||||
|
Reference in New Issue
Block a user