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

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