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 }