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