You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ssh_key.go 13KB

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