code.gitea.io/gitea@v1.21.7/models/asymkey/ssh_key.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package asymkey
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"strings"
    11  	"time"
    12  
    13  	"code.gitea.io/gitea/models/auth"
    14  	"code.gitea.io/gitea/models/db"
    15  	"code.gitea.io/gitea/models/perm"
    16  	user_model "code.gitea.io/gitea/models/user"
    17  	"code.gitea.io/gitea/modules/log"
    18  	"code.gitea.io/gitea/modules/timeutil"
    19  	"code.gitea.io/gitea/modules/util"
    20  
    21  	"golang.org/x/crypto/ssh"
    22  	"xorm.io/builder"
    23  )
    24  
    25  // KeyType specifies the key type
    26  type KeyType int
    27  
    28  const (
    29  	// KeyTypeUser specifies the user key
    30  	KeyTypeUser = iota + 1
    31  	// KeyTypeDeploy specifies the deploy key
    32  	KeyTypeDeploy
    33  	// KeyTypePrincipal specifies the authorized principal key
    34  	KeyTypePrincipal
    35  )
    36  
    37  // PublicKey represents a user or deploy SSH public key.
    38  type PublicKey struct {
    39  	ID            int64           `xorm:"pk autoincr"`
    40  	OwnerID       int64           `xorm:"INDEX NOT NULL"`
    41  	Name          string          `xorm:"NOT NULL"`
    42  	Fingerprint   string          `xorm:"INDEX NOT NULL"`
    43  	Content       string          `xorm:"MEDIUMTEXT NOT NULL"`
    44  	Mode          perm.AccessMode `xorm:"NOT NULL DEFAULT 2"`
    45  	Type          KeyType         `xorm:"NOT NULL DEFAULT 1"`
    46  	LoginSourceID int64           `xorm:"NOT NULL DEFAULT 0"`
    47  
    48  	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
    49  	UpdatedUnix       timeutil.TimeStamp `xorm:"updated"`
    50  	HasRecentActivity bool               `xorm:"-"`
    51  	HasUsed           bool               `xorm:"-"`
    52  	Verified          bool               `xorm:"NOT NULL DEFAULT false"`
    53  }
    54  
    55  func init() {
    56  	db.RegisterModel(new(PublicKey))
    57  }
    58  
    59  // AfterLoad is invoked from XORM after setting the values of all fields of this object.
    60  func (key *PublicKey) AfterLoad() {
    61  	key.HasUsed = key.UpdatedUnix > key.CreatedUnix
    62  	key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
    63  }
    64  
    65  // OmitEmail returns content of public key without email address.
    66  func (key *PublicKey) OmitEmail() string {
    67  	return strings.Join(strings.Split(key.Content, " ")[:2], " ")
    68  }
    69  
    70  // AuthorizedString returns formatted public key string for authorized_keys file.
    71  //
    72  // TODO: Consider dropping this function
    73  func (key *PublicKey) AuthorizedString() string {
    74  	return AuthorizedStringForKey(key)
    75  }
    76  
    77  func addKey(ctx context.Context, key *PublicKey) (err error) {
    78  	if len(key.Fingerprint) == 0 {
    79  		key.Fingerprint, err = CalcFingerprint(key.Content)
    80  		if err != nil {
    81  			return err
    82  		}
    83  	}
    84  
    85  	// Save SSH key.
    86  	if err = db.Insert(ctx, key); err != nil {
    87  		return err
    88  	}
    89  
    90  	return appendAuthorizedKeysToFile(key)
    91  }
    92  
    93  // AddPublicKey adds new public key to database and authorized_keys file.
    94  func AddPublicKey(ownerID int64, name, content string, authSourceID int64) (*PublicKey, error) {
    95  	log.Trace(content)
    96  
    97  	fingerprint, err := CalcFingerprint(content)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	ctx, committer, err := db.TxContext(db.DefaultContext)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	defer committer.Close()
   107  
   108  	if err := checkKeyFingerprint(ctx, fingerprint); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	// Key name of same user cannot be duplicated.
   113  	has, err := db.GetEngine(ctx).
   114  		Where("owner_id = ? AND name = ?", ownerID, name).
   115  		Get(new(PublicKey))
   116  	if err != nil {
   117  		return nil, err
   118  	} else if has {
   119  		return nil, ErrKeyNameAlreadyUsed{ownerID, name}
   120  	}
   121  
   122  	key := &PublicKey{
   123  		OwnerID:       ownerID,
   124  		Name:          name,
   125  		Fingerprint:   fingerprint,
   126  		Content:       content,
   127  		Mode:          perm.AccessModeWrite,
   128  		Type:          KeyTypeUser,
   129  		LoginSourceID: authSourceID,
   130  	}
   131  	if err = addKey(ctx, key); err != nil {
   132  		return nil, fmt.Errorf("addKey: %w", err)
   133  	}
   134  
   135  	return key, committer.Commit()
   136  }
   137  
   138  // GetPublicKeyByID returns public key by given ID.
   139  func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
   140  	key := new(PublicKey)
   141  	has, err := db.GetEngine(db.DefaultContext).
   142  		ID(keyID).
   143  		Get(key)
   144  	if err != nil {
   145  		return nil, err
   146  	} else if !has {
   147  		return nil, ErrKeyNotExist{keyID}
   148  	}
   149  	return key, nil
   150  }
   151  
   152  // SearchPublicKeyByContent searches content as prefix (leak e-mail part)
   153  // and returns public key found.
   154  func SearchPublicKeyByContent(ctx context.Context, content string) (*PublicKey, error) {
   155  	key := new(PublicKey)
   156  	has, err := db.GetEngine(ctx).
   157  		Where("content like ?", content+"%").
   158  		Get(key)
   159  	if err != nil {
   160  		return nil, err
   161  	} else if !has {
   162  		return nil, ErrKeyNotExist{}
   163  	}
   164  	return key, nil
   165  }
   166  
   167  // SearchPublicKeyByContentExact searches content
   168  // and returns public key found.
   169  func SearchPublicKeyByContentExact(ctx context.Context, content string) (*PublicKey, error) {
   170  	key := new(PublicKey)
   171  	has, err := db.GetEngine(ctx).
   172  		Where("content = ?", content).
   173  		Get(key)
   174  	if err != nil {
   175  		return nil, err
   176  	} else if !has {
   177  		return nil, ErrKeyNotExist{}
   178  	}
   179  	return key, nil
   180  }
   181  
   182  // SearchPublicKey returns a list of public keys matching the provided arguments.
   183  func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) {
   184  	keys := make([]*PublicKey, 0, 5)
   185  	cond := builder.NewCond()
   186  	if uid != 0 {
   187  		cond = cond.And(builder.Eq{"owner_id": uid})
   188  	}
   189  	if fingerprint != "" {
   190  		cond = cond.And(builder.Eq{"fingerprint": fingerprint})
   191  	}
   192  	return keys, db.GetEngine(db.DefaultContext).Where(cond).Find(&keys)
   193  }
   194  
   195  // ListPublicKeys returns a list of public keys belongs to given user.
   196  func ListPublicKeys(uid int64, listOptions db.ListOptions) ([]*PublicKey, error) {
   197  	sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal)
   198  	if listOptions.Page != 0 {
   199  		sess = db.SetSessionPagination(sess, &listOptions)
   200  
   201  		keys := make([]*PublicKey, 0, listOptions.PageSize)
   202  		return keys, sess.Find(&keys)
   203  	}
   204  
   205  	keys := make([]*PublicKey, 0, 5)
   206  	return keys, sess.Find(&keys)
   207  }
   208  
   209  // CountPublicKeys count public keys a user has
   210  func CountPublicKeys(userID int64) (int64, error) {
   211  	sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", userID, KeyTypePrincipal)
   212  	return sess.Count(&PublicKey{})
   213  }
   214  
   215  // ListPublicKeysBySource returns a list of synchronized public keys for a given user and login source.
   216  func ListPublicKeysBySource(uid, authSourceID int64) ([]*PublicKey, error) {
   217  	keys := make([]*PublicKey, 0, 5)
   218  	return keys, db.GetEngine(db.DefaultContext).
   219  		Where("owner_id = ? AND login_source_id = ?", uid, authSourceID).
   220  		Find(&keys)
   221  }
   222  
   223  // UpdatePublicKeyUpdated updates public key use time.
   224  func UpdatePublicKeyUpdated(id int64) error {
   225  	// Check if key exists before update as affected rows count is unreliable
   226  	//    and will return 0 affected rows if two updates are made at the same time
   227  	if cnt, err := db.GetEngine(db.DefaultContext).ID(id).Count(&PublicKey{}); err != nil {
   228  		return err
   229  	} else if cnt != 1 {
   230  		return ErrKeyNotExist{id}
   231  	}
   232  
   233  	_, err := db.GetEngine(db.DefaultContext).ID(id).Cols("updated_unix").Update(&PublicKey{
   234  		UpdatedUnix: timeutil.TimeStampNow(),
   235  	})
   236  	if err != nil {
   237  		return err
   238  	}
   239  	return nil
   240  }
   241  
   242  // DeletePublicKeys does the actual key deletion but does not update authorized_keys file.
   243  func DeletePublicKeys(ctx context.Context, keyIDs ...int64) error {
   244  	if len(keyIDs) == 0 {
   245  		return nil
   246  	}
   247  
   248  	_, err := db.GetEngine(ctx).In("id", keyIDs).Delete(new(PublicKey))
   249  	return err
   250  }
   251  
   252  // PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key
   253  func PublicKeysAreExternallyManaged(keys []*PublicKey) ([]bool, error) {
   254  	sources := make([]*auth.Source, 0, 5)
   255  	externals := make([]bool, len(keys))
   256  keyloop:
   257  	for i, key := range keys {
   258  		if key.LoginSourceID == 0 {
   259  			externals[i] = false
   260  			continue keyloop
   261  		}
   262  
   263  		var source *auth.Source
   264  
   265  	sourceloop:
   266  		for _, s := range sources {
   267  			if s.ID == key.LoginSourceID {
   268  				source = s
   269  				break sourceloop
   270  			}
   271  		}
   272  
   273  		if source == nil {
   274  			var err error
   275  			source, err = auth.GetSourceByID(key.LoginSourceID)
   276  			if err != nil {
   277  				if auth.IsErrSourceNotExist(err) {
   278  					externals[i] = false
   279  					sources[i] = &auth.Source{
   280  						ID: key.LoginSourceID,
   281  					}
   282  					continue keyloop
   283  				}
   284  				return nil, err
   285  			}
   286  		}
   287  
   288  		if sshKeyProvider, ok := source.Cfg.(auth.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() {
   289  			// Disable setting SSH keys for this user
   290  			externals[i] = true
   291  		}
   292  	}
   293  
   294  	return externals, nil
   295  }
   296  
   297  // PublicKeyIsExternallyManaged returns whether the provided KeyID represents an externally managed Key
   298  func PublicKeyIsExternallyManaged(id int64) (bool, error) {
   299  	key, err := GetPublicKeyByID(id)
   300  	if err != nil {
   301  		return false, err
   302  	}
   303  	if key.LoginSourceID == 0 {
   304  		return false, nil
   305  	}
   306  	source, err := auth.GetSourceByID(key.LoginSourceID)
   307  	if err != nil {
   308  		if auth.IsErrSourceNotExist(err) {
   309  			return false, nil
   310  		}
   311  		return false, err
   312  	}
   313  	if sshKeyProvider, ok := source.Cfg.(auth.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() {
   314  		// Disable setting SSH keys for this user
   315  		return true, nil
   316  	}
   317  	return false, nil
   318  }
   319  
   320  // deleteKeysMarkedForDeletion returns true if ssh keys needs update
   321  func deleteKeysMarkedForDeletion(keys []string) (bool, error) {
   322  	// Start session
   323  	ctx, committer, err := db.TxContext(db.DefaultContext)
   324  	if err != nil {
   325  		return false, err
   326  	}
   327  	defer committer.Close()
   328  
   329  	// Delete keys marked for deletion
   330  	var sshKeysNeedUpdate bool
   331  	for _, KeyToDelete := range keys {
   332  		key, err := SearchPublicKeyByContent(ctx, KeyToDelete)
   333  		if err != nil {
   334  			log.Error("SearchPublicKeyByContent: %v", err)
   335  			continue
   336  		}
   337  		if err = DeletePublicKeys(ctx, key.ID); err != nil {
   338  			log.Error("deletePublicKeys: %v", err)
   339  			continue
   340  		}
   341  		sshKeysNeedUpdate = true
   342  	}
   343  
   344  	if err := committer.Commit(); err != nil {
   345  		return false, err
   346  	}
   347  
   348  	return sshKeysNeedUpdate, nil
   349  }
   350  
   351  // AddPublicKeysBySource add a users public keys. Returns true if there are changes.
   352  func AddPublicKeysBySource(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool {
   353  	var sshKeysNeedUpdate bool
   354  	for _, sshKey := range sshPublicKeys {
   355  		var err error
   356  		found := false
   357  		keys := []byte(sshKey)
   358  	loop:
   359  		for len(keys) > 0 && err == nil {
   360  			var out ssh.PublicKey
   361  			// We ignore options as they are not relevant to Gitea
   362  			out, _, _, keys, err = ssh.ParseAuthorizedKey(keys)
   363  			if err != nil {
   364  				break loop
   365  			}
   366  			found = true
   367  			marshalled := string(ssh.MarshalAuthorizedKey(out))
   368  			marshalled = marshalled[:len(marshalled)-1]
   369  			sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out))
   370  
   371  			if _, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil {
   372  				if IsErrKeyAlreadyExist(err) {
   373  					log.Trace("AddPublicKeysBySource[%s]: Public SSH Key %s already exists for user", sshKeyName, usr.Name)
   374  				} else {
   375  					log.Error("AddPublicKeysBySource[%s]: Error adding Public SSH Key for user %s: %v", sshKeyName, usr.Name, err)
   376  				}
   377  			} else {
   378  				log.Trace("AddPublicKeysBySource[%s]: Added Public SSH Key for user %s", sshKeyName, usr.Name)
   379  				sshKeysNeedUpdate = true
   380  			}
   381  		}
   382  		if !found && err != nil {
   383  			log.Warn("AddPublicKeysBySource[%s]: Skipping invalid Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey)
   384  		}
   385  	}
   386  	return sshKeysNeedUpdate
   387  }
   388  
   389  // SynchronizePublicKeys updates a users public keys. Returns true if there are changes.
   390  func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool {
   391  	var sshKeysNeedUpdate bool
   392  
   393  	log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name)
   394  
   395  	// Get Public Keys from DB with current LDAP source
   396  	var giteaKeys []string
   397  	keys, err := ListPublicKeysBySource(usr.ID, s.ID)
   398  	if err != nil {
   399  		log.Error("synchronizePublicKeys[%s]: Error listing Public SSH Keys for user %s: %v", s.Name, usr.Name, err)
   400  	}
   401  
   402  	for _, v := range keys {
   403  		giteaKeys = append(giteaKeys, v.OmitEmail())
   404  	}
   405  
   406  	// Process the provided keys to remove duplicates and name part
   407  	var providedKeys []string
   408  	for _, v := range sshPublicKeys {
   409  		sshKeySplit := strings.Split(v, " ")
   410  		if len(sshKeySplit) > 1 {
   411  			key := strings.Join(sshKeySplit[:2], " ")
   412  			if !util.SliceContainsString(providedKeys, key) {
   413  				providedKeys = append(providedKeys, key)
   414  			}
   415  		}
   416  	}
   417  
   418  	// Check if Public Key sync is needed
   419  	if util.SliceSortedEqual(giteaKeys, providedKeys) {
   420  		log.Trace("synchronizePublicKeys[%s]: Public Keys are already in sync for %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys))
   421  		return false
   422  	}
   423  	log.Trace("synchronizePublicKeys[%s]: Public Key needs update for user %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys))
   424  
   425  	// Add new Public SSH Keys that doesn't already exist in DB
   426  	var newKeys []string
   427  	for _, key := range providedKeys {
   428  		if !util.SliceContainsString(giteaKeys, key) {
   429  			newKeys = append(newKeys, key)
   430  		}
   431  	}
   432  	if AddPublicKeysBySource(usr, s, newKeys) {
   433  		sshKeysNeedUpdate = true
   434  	}
   435  
   436  	// Mark keys from DB that no longer exist in the source for deletion
   437  	var giteaKeysToDelete []string
   438  	for _, giteaKey := range giteaKeys {
   439  		if !util.SliceContainsString(providedKeys, giteaKey) {
   440  			log.Trace("synchronizePublicKeys[%s]: Marking Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey)
   441  			giteaKeysToDelete = append(giteaKeysToDelete, giteaKey)
   442  		}
   443  	}
   444  
   445  	// Delete keys from DB that no longer exist in the source
   446  	needUpd, err := deleteKeysMarkedForDeletion(giteaKeysToDelete)
   447  	if err != nil {
   448  		log.Error("synchronizePublicKeys[%s]: Error deleting Public Keys marked for deletion for user %s: %v", s.Name, usr.Name, err)
   449  	}
   450  	if needUpd {
   451  		sshKeysNeedUpdate = true
   452  	}
   453  
   454  	return sshKeysNeedUpdate
   455  }