Files
Atay-Makhzan/models/ssh_key.go
T

495 lines
14 KiB
Go
Raw Normal View History

2014-03-16 05:24:13 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
2014-03-16 05:24:13 -04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2014-02-17 23:57:23 +08:00
package models
import (
"fmt"
2014-03-16 05:24:13 -04:00
"strings"
2014-02-17 23:57:23 +08:00
"time"
2014-03-02 15:25:09 -05:00
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"golang.org/x/crypto/ssh"
2014-02-17 23:57:23 +08:00
2021-07-24 11:16:34 +01:00
"xorm.io/builder"
)
2016-11-26 01:36:03 +01:00
// KeyType specifies the key type
2015-08-06 22:48:11 +08:00
type KeyType int
const (
2016-11-26 01:36:03 +01:00
// KeyTypeUser specifies the user key
2016-11-07 17:53:22 +01:00
KeyTypeUser = iota + 1
2016-11-26 01:36:03 +01:00
// KeyTypeDeploy specifies the deploy key
2016-11-07 17:53:22 +01:00
KeyTypeDeploy
2020-10-11 02:38:09 +02:00
// KeyTypePrincipal specifies the authorized principal key
KeyTypePrincipal
2015-08-06 22:48:11 +08:00
)
2016-07-26 10:47:25 +08:00
// PublicKey represents a user or deploy SSH public key.
2014-02-17 23:57:23 +08:00
type PublicKey struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"INDEX NOT NULL"`
Name string `xorm:"NOT NULL"`
Fingerprint string `xorm:"INDEX 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 timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
HasRecentActivity bool `xorm:"-"`
HasUsed bool `xorm:"-"`
2015-08-06 22:48:11 +08:00
}
func init() {
db.RegisterModel(new(PublicKey))
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (key *PublicKey) AfterLoad() {
key.HasUsed = key.UpdatedUnix > key.CreatedUnix
key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
2014-02-17 23:57:23 +08:00
}
2016-07-26 10:47:25 +08:00
// OmitEmail returns content of public key without email address.
2016-11-26 01:36:03 +01:00
func (key *PublicKey) OmitEmail() string {
return strings.Join(strings.Split(key.Content, " ")[:2], " ")
2014-11-23 02:33:47 -05:00
}
2016-07-26 10:47:25 +08:00
// AuthorizedString returns formatted public key string for authorized_keys file.
2021-07-24 11:16:34 +01:00
//
// TODO: Consider dropping this function
2016-07-26 10:47:25 +08:00
func (key *PublicKey) AuthorizedString() string {
2021-07-24 11:16:34 +01:00
return AuthorizedStringForKey(key)
}
func addKey(e db.Engine, key *PublicKey) (err error) {
2018-10-20 23:25:14 +02:00
if len(key.Fingerprint) == 0 {
key.Fingerprint, err = calcFingerprint(key.Content)
if err != nil {
return err
}
2014-03-16 06:16:03 -04:00
}
// Save SSH key.
2015-08-06 22:48:11 +08:00
if _, err = e.Insert(key); err != nil {
2014-03-16 06:16:03 -04:00
return err
2015-08-06 22:48:11 +08:00
}
2016-07-26 10:47:25 +08:00
return appendAuthorizedKeysToFile(key)
2015-08-06 22:48:11 +08:00
}
// AddPublicKey adds new public key to database and authorized_keys file.
2019-06-12 21:41:28 +02:00
func AddPublicKey(ownerID int64, name, content string, loginSourceID int64) (*PublicKey, error) {
log.Trace(content)
fingerprint, err := calcFingerprint(content)
if err != nil {
return nil, err
}
2021-09-23 16:45:36 +01:00
sess := db.NewSession(db.DefaultContext)
defer sess.Close()
if err = sess.Begin(); err != nil {
return nil, err
}
if err := checkKeyFingerprint(sess, fingerprint); err != nil {
2015-12-03 00:24:37 -05:00
return nil, err
2014-02-17 23:57:23 +08:00
}
2015-08-06 22:48:11 +08:00
// Key name of same user cannot be duplicated.
has, err := sess.
2016-11-10 16:16:32 +01:00
Where("owner_id = ? AND name = ?", ownerID, name).
Get(new(PublicKey))
2015-08-06 22:48:11 +08:00
if err != nil {
2015-12-03 00:24:37 -05:00
return nil, err
2015-08-06 22:48:11 +08:00
} else if has {
2015-12-03 00:24:37 -05:00
return nil, ErrKeyNameAlreadyUsed{ownerID, name}
2015-08-06 22:48:11 +08:00
}
key := &PublicKey{
OwnerID: ownerID,
Name: name,
Fingerprint: fingerprint,
Content: content,
Mode: AccessModeWrite,
Type: KeyTypeUser,
2019-06-12 21:41:28 +02:00
LoginSourceID: loginSourceID,
2015-08-06 22:48:11 +08:00
}
if err = addKey(sess, key); err != nil {
2015-12-03 00:24:37 -05:00
return nil, fmt.Errorf("addKey: %v", err)
2015-08-06 22:48:11 +08:00
}
2015-12-03 00:24:37 -05:00
return key, sess.Commit()
2014-02-17 23:57:23 +08:00
}
2015-08-06 22:48:11 +08:00
// GetPublicKeyByID returns public key by given ID.
func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
2014-08-09 15:40:10 -07:00
key := new(PublicKey)
2021-09-23 16:45:36 +01:00
has, err := db.GetEngine(db.DefaultContext).
2020-03-22 23:12:55 +08:00
ID(keyID).
2016-11-10 16:16:32 +01:00
Get(key)
2014-08-09 15:40:10 -07:00
if err != nil {
return nil, err
} else if !has {
2015-08-06 22:48:11 +08:00
return nil, ErrKeyNotExist{keyID}
2014-08-09 15:40:10 -07:00
}
return key, nil
}
func searchPublicKeyByContentWithEngine(e db.Engine, content string) (*PublicKey, error) {
key := new(PublicKey)
has, err := e.
2016-11-10 16:16:32 +01:00
Where("content like ?", content+"%").
Get(key)
if err != nil {
return nil, err
} else if !has {
return nil, ErrKeyNotExist{}
}
return key, nil
}
// SearchPublicKeyByContent searches content as prefix (leak e-mail part)
// and returns public key found.
func SearchPublicKeyByContent(content string) (*PublicKey, error) {
2021-09-23 16:45:36 +01:00
return searchPublicKeyByContentWithEngine(db.GetEngine(db.DefaultContext), content)
}
func searchPublicKeyByContentExactWithEngine(e db.Engine, content string) (*PublicKey, error) {
2020-10-11 02:38:09 +02:00
key := new(PublicKey)
has, err := e.
Where("content = ?", content).
Get(key)
if err != nil {
return nil, err
} else if !has {
return nil, ErrKeyNotExist{}
}
return key, nil
}
// SearchPublicKeyByContentExact searches content
// and returns public key found.
func SearchPublicKeyByContentExact(content string) (*PublicKey, error) {
2021-09-23 16:45:36 +01:00
return searchPublicKeyByContentExactWithEngine(db.GetEngine(db.DefaultContext), content)
2020-10-11 02:38:09 +02:00
}
2018-11-01 03:40:49 +00:00
// SearchPublicKey returns a list of public keys matching the provided arguments.
func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) {
keys := make([]*PublicKey, 0, 5)
cond := builder.NewCond()
if uid != 0 {
cond = cond.And(builder.Eq{"owner_id": uid})
}
if fingerprint != "" {
cond = cond.And(builder.Eq{"fingerprint": fingerprint})
}
2021-09-23 16:45:36 +01:00
return keys, db.GetEngine(db.DefaultContext).Where(cond).Find(&keys)
2018-11-01 03:40:49 +00:00
}
2014-11-12 06:48:50 -05:00
// ListPublicKeys returns a list of public keys belongs to given user.
func ListPublicKeys(uid int64, listOptions db.ListOptions) ([]*PublicKey, error) {
2021-09-23 16:45:36 +01:00
sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal)
2020-01-24 19:00:29 +00:00
if listOptions.Page != 0 {
sess = db.SetSessionPagination(sess, &listOptions)
2020-01-24 19:00:29 +00:00
keys := make([]*PublicKey, 0, listOptions.PageSize)
return keys, sess.Find(&keys)
}
2014-07-26 00:24:27 -04:00
keys := make([]*PublicKey, 0, 5)
2020-01-24 19:00:29 +00:00
return keys, sess.Find(&keys)
2014-05-07 12:09:30 -04:00
}
2021-08-12 14:43:08 +02:00
// CountPublicKeys count public keys a user has
func CountPublicKeys(userID int64) (int64, error) {
2021-09-23 16:45:36 +01:00
sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", userID, KeyTypePrincipal)
2021-08-12 14:43:08 +02:00
return sess.Count(&PublicKey{})
}
2021-07-24 11:16:34 +01:00
// ListPublicKeysBySource returns a list of synchronized public keys for a given user and login source.
func ListPublicKeysBySource(uid, loginSourceID int64) ([]*PublicKey, error) {
keys := make([]*PublicKey, 0, 5)
2021-09-23 16:45:36 +01:00
return keys, db.GetEngine(db.DefaultContext).
2019-06-12 21:41:28 +02:00
Where("owner_id = ? AND login_source_id = ?", uid, loginSourceID).
Find(&keys)
}
2017-04-07 17:40:38 -07:00
// UpdatePublicKeyUpdated updates public key use time.
func UpdatePublicKeyUpdated(id int64) error {
// Check if key exists before update as affected rows count is unreliable
// and will return 0 affected rows if two updates are made at the same time
2021-09-23 16:45:36 +01:00
if cnt, err := db.GetEngine(db.DefaultContext).ID(id).Count(&PublicKey{}); err != nil {
return err
} else if cnt != 1 {
return ErrKeyNotExist{id}
}
2021-09-23 16:45:36 +01:00
_, err := db.GetEngine(db.DefaultContext).ID(id).Cols("updated_unix").Update(&PublicKey{
UpdatedUnix: timeutil.TimeStampNow(),
2017-04-07 17:40:38 -07:00
})
if err != nil {
return err
}
return nil
}
// deletePublicKeys does the actual key deletion but does not update authorized_keys file.
func deletePublicKeys(e db.Engine, keyIDs ...int64) error {
if len(keyIDs) == 0 {
2015-08-06 22:48:11 +08:00
return nil
2014-03-22 14:27:03 -04:00
}
2014-05-06 16:28:52 -04:00
2016-11-12 16:29:18 +08:00
_, err := e.In("id", keyIDs).Delete(new(PublicKey))
return err
2014-02-17 23:57:23 +08:00
}
// PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key
func PublicKeysAreExternallyManaged(keys []*PublicKey) ([]bool, error) {
sources := make([]*login.Source, 0, 5)
externals := make([]bool, len(keys))
keyloop:
for i, key := range keys {
if key.LoginSourceID == 0 {
externals[i] = false
continue keyloop
}
var source *login.Source
sourceloop:
for _, s := range sources {
if s.ID == key.LoginSourceID {
source = s
break sourceloop
}
}
if source == nil {
var err error
source, err = login.GetSourceByID(key.LoginSourceID)
if err != nil {
if login.IsErrSourceNotExist(err) {
externals[i] = false
sources[i] = &login.Source{
ID: key.LoginSourceID,
}
continue keyloop
}
return nil, err
}
}
if sshKeyProvider, ok := source.Cfg.(login.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() {
// Disable setting SSH keys for this user
externals[i] = true
}
}
return externals, nil
}
// PublicKeyIsExternallyManaged returns whether the provided KeyID represents an externally managed Key
func PublicKeyIsExternallyManaged(id int64) (bool, error) {
key, err := GetPublicKeyByID(id)
if err != nil {
return false, err
}
if key.LoginSourceID == 0 {
return false, nil
}
source, err := login.GetSourceByID(key.LoginSourceID)
if err != nil {
if login.IsErrSourceNotExist(err) {
return false, nil
}
return false, err
}
if sshKeyProvider, ok := source.Cfg.(login.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() {
// Disable setting SSH keys for this user
return true, nil
}
return false, nil
}
2015-08-06 22:48:11 +08:00
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
2015-12-03 00:24:37 -05:00
func DeletePublicKey(doer *User, id int64) (err error) {
key, err := GetPublicKeyByID(id)
2015-08-20 17:11:29 +08:00
if err != nil {
return err
2015-12-03 00:24:37 -05:00
}
// Check if user has access to delete this key.
2016-07-24 01:08:22 +08:00
if !doer.IsAdmin && doer.ID != key.OwnerID {
return ErrKeyAccessDenied{doer.ID, key.ID, "public"}
2015-08-20 17:11:29 +08:00
}
2021-09-23 16:45:36 +01:00
sess := db.NewSession(db.DefaultContext)
defer sess.Close()
2015-08-06 22:48:11 +08:00
if err = sess.Begin(); err != nil {
return err
}
if err = deletePublicKeys(sess, id); err != nil {
2015-08-06 22:48:11 +08:00
return err
}
if err = sess.Commit(); err != nil {
return err
}
sess.Close()
2020-10-11 02:38:09 +02:00
if key.Type == KeyTypePrincipal {
return RewriteAllPrincipalKeys()
}
return RewriteAllPublicKeys()
2015-08-06 22:48:11 +08:00
}
2021-07-24 11:16:34 +01:00
// deleteKeysMarkedForDeletion returns true if ssh keys needs update
func deleteKeysMarkedForDeletion(keys []string) (bool, error) {
// Start session
2021-09-23 16:45:36 +01:00
sess := db.NewSession(db.DefaultContext)
defer sess.Close()
if err := sess.Begin(); err != nil {
2021-07-24 11:16:34 +01:00
return false, err
2015-12-03 00:24:37 -05:00
}
2021-07-24 11:16:34 +01:00
// Delete keys marked for deletion
var sshKeysNeedUpdate bool
for _, KeyToDelete := range keys {
key, err := searchPublicKeyByContentWithEngine(sess, KeyToDelete)
2015-12-05 17:13:13 -05:00
if err != nil {
2021-07-24 11:16:34 +01:00
log.Error("SearchPublicKeyByContent: %v", err)
continue
2015-08-06 22:48:11 +08:00
}
2021-07-24 11:16:34 +01:00
if err = deletePublicKeys(sess, key.ID); err != nil {
log.Error("deletePublicKeys: %v", err)
continue
}
2021-07-24 11:16:34 +01:00
sshKeysNeedUpdate = true
2015-08-06 22:48:11 +08:00
}
2021-07-24 11:16:34 +01:00
if err := sess.Commit(); err != nil {
return false, err
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
return sshKeysNeedUpdate, nil
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
// AddPublicKeysBySource add a users public keys. Returns true if there are changes.
func AddPublicKeysBySource(usr *User, s *login.Source, sshPublicKeys []string) bool {
2021-07-24 11:16:34 +01:00
var sshKeysNeedUpdate bool
for _, sshKey := range sshPublicKeys {
var err error
found := false
keys := []byte(sshKey)
loop:
for len(keys) > 0 && err == nil {
var out ssh.PublicKey
// We ignore options as they are not relevant to Gitea
out, _, _, keys, err = ssh.ParseAuthorizedKey(keys)
2020-10-11 02:38:09 +02:00
if err != nil {
2021-07-24 11:16:34 +01:00
break loop
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
found = true
marshalled := string(ssh.MarshalAuthorizedKey(out))
marshalled = marshalled[:len(marshalled)-1]
sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out))
if _, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil {
if IsErrKeyAlreadyExist(err) {
log.Trace("AddPublicKeysBySource[%s]: Public SSH Key %s already exists for user", sshKeyName, usr.Name)
} else {
log.Error("AddPublicKeysBySource[%s]: Error adding Public SSH Key for user %s: %v", sshKeyName, usr.Name, err)
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
} else {
log.Trace("AddPublicKeysBySource[%s]: Added Public SSH Key for user %s", sshKeyName, usr.Name)
sshKeysNeedUpdate = true
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
}
if !found && err != nil {
log.Warn("AddPublicKeysBySource[%s]: Skipping invalid Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey)
2020-10-11 02:38:09 +02:00
}
}
2021-07-24 11:16:34 +01:00
return sshKeysNeedUpdate
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
// SynchronizePublicKeys updates a users public keys. Returns true if there are changes.
func SynchronizePublicKeys(usr *User, s *login.Source, sshPublicKeys []string) bool {
2021-07-24 11:16:34 +01:00
var sshKeysNeedUpdate bool
2020-10-11 02:38:09 +02:00
2021-07-24 11:16:34 +01:00
log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name)
2020-10-11 02:38:09 +02:00
2021-07-24 11:16:34 +01:00
// Get Public Keys from DB with current LDAP source
var giteaKeys []string
keys, err := ListPublicKeysBySource(usr.ID, s.ID)
if err != nil {
log.Error("synchronizePublicKeys[%s]: Error listing Public SSH Keys for user %s: %v", s.Name, usr.Name, err)
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
for _, v := range keys {
giteaKeys = append(giteaKeys, v.OmitEmail())
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
// Process the provided keys to remove duplicates and name part
var providedKeys []string
for _, v := range sshPublicKeys {
sshKeySplit := strings.Split(v, " ")
if len(sshKeySplit) > 1 {
key := strings.Join(sshKeySplit[:2], " ")
if !util.ExistsInSlice(key, providedKeys) {
providedKeys = append(providedKeys, key)
}
}
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
// Check if Public Key sync is needed
if util.IsEqualSlice(giteaKeys, providedKeys) {
log.Trace("synchronizePublicKeys[%s]: Public Keys are already in sync for %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys))
return false
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
log.Trace("synchronizePublicKeys[%s]: Public Key needs update for user %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys))
2020-10-11 02:38:09 +02:00
2021-07-24 11:16:34 +01:00
// Add new Public SSH Keys that doesn't already exist in DB
var newKeys []string
for _, key := range providedKeys {
if !util.ExistsInSlice(key, giteaKeys) {
newKeys = append(newKeys, key)
}
}
if AddPublicKeysBySource(usr, s, newKeys) {
sshKeysNeedUpdate = true
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
// Mark keys from DB that no longer exist in the source for deletion
var giteaKeysToDelete []string
for _, giteaKey := range giteaKeys {
if !util.ExistsInSlice(giteaKey, providedKeys) {
log.Trace("synchronizePublicKeys[%s]: Marking Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey)
giteaKeysToDelete = append(giteaKeysToDelete, giteaKey)
}
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
// Delete keys from DB that no longer exist in the source
needUpd, err := deleteKeysMarkedForDeletion(giteaKeysToDelete)
if err != nil {
2021-07-24 11:16:34 +01:00
log.Error("synchronizePublicKeys[%s]: Error deleting Public Keys marked for deletion for user %s: %v", s.Name, usr.Name, err)
}
2021-07-24 11:16:34 +01:00
if needUpd {
sshKeysNeedUpdate = true
2020-10-11 02:38:09 +02:00
}
2021-07-24 11:16:34 +01:00
return sshKeysNeedUpdate
2020-10-11 02:38:09 +02:00
}