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.

669 lines
17 KiB

10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "bufio"
  7. "encoding/base64"
  8. "encoding/binary"
  9. "errors"
  10. "fmt"
  11. "io"
  12. "io/ioutil"
  13. "os"
  14. "path"
  15. "path/filepath"
  16. "strings"
  17. "sync"
  18. "time"
  19. "github.com/Unknwon/com"
  20. "github.com/go-xorm/xorm"
  21. "github.com/gogits/gogs/modules/log"
  22. "github.com/gogits/gogs/modules/process"
  23. "github.com/gogits/gogs/modules/setting"
  24. )
  25. const (
  26. // "### autogenerated by gitgos, DO NOT EDIT\n"
  27. _TPL_PUBLICK_KEY = `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
  28. )
  29. var (
  30. ErrKeyUnableVerify = errors.New("Unable to verify public key")
  31. )
  32. var sshOpLocker = sync.Mutex{}
  33. var SSHPath string // SSH directory.
  34. // homeDir returns the home directory of current user.
  35. func homeDir() string {
  36. home, err := com.HomeDir()
  37. if err != nil {
  38. log.Fatal(4, "Fail to get home directory: %v", err)
  39. }
  40. return home
  41. }
  42. func init() {
  43. // Determine and create .ssh path.
  44. SSHPath = filepath.Join(homeDir(), ".ssh")
  45. if err := os.MkdirAll(SSHPath, 0700); err != nil {
  46. log.Fatal(4, "fail to create '%s': %v", SSHPath, err)
  47. }
  48. }
  49. type KeyType int
  50. const (
  51. KEY_TYPE_USER = iota + 1
  52. KEY_TYPE_DEPLOY
  53. )
  54. // PublicKey represents a SSH or deploy key.
  55. type PublicKey struct {
  56. ID int64 `xorm:"pk autoincr"`
  57. OwnerID int64 `xorm:"INDEX NOT NULL"`
  58. Name string `xorm:"NOT NULL"`
  59. Fingerprint string `xorm:"NOT NULL"`
  60. Content string `xorm:"TEXT NOT NULL"`
  61. Mode AccessMode `xorm:"NOT NULL DEFAULT 2"`
  62. Type KeyType `xorm:"NOT NULL DEFAULT 1"`
  63. Created time.Time `xorm:"CREATED"`
  64. Updated time.Time // Note: Updated must below Created for AfterSet.
  65. HasRecentActivity bool `xorm:"-"`
  66. HasUsed bool `xorm:"-"`
  67. }
  68. func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) {
  69. switch colName {
  70. case "created":
  71. k.HasUsed = k.Updated.After(k.Created)
  72. k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
  73. }
  74. }
  75. // OmitEmail returns content of public key but without e-mail address.
  76. func (k *PublicKey) OmitEmail() string {
  77. return strings.Join(strings.Split(k.Content, " ")[:2], " ")
  78. }
  79. // GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
  80. func (key *PublicKey) GetAuthorizedString() string {
  81. return fmt.Sprintf(_TPL_PUBLICK_KEY, setting.AppPath, key.ID, setting.CustomConf, key.Content)
  82. }
  83. func extractTypeFromBase64Key(key string) (string, error) {
  84. b, err := base64.StdEncoding.DecodeString(key)
  85. if err != nil || len(b) < 4 {
  86. return "", errors.New("Invalid key format")
  87. }
  88. keyLength := int(binary.BigEndian.Uint32(b))
  89. if len(b) < 4+keyLength {
  90. return "", errors.New("Invalid key format")
  91. }
  92. return string(b[4 : 4+keyLength]), nil
  93. }
  94. // parseKeyString parses any key string in openssh or ssh2 format to clean openssh string (rfc4253)
  95. func parseKeyString(content string) (string, error) {
  96. // Transform all legal line endings to a single "\n"
  97. s := strings.Replace(strings.Replace(strings.TrimSpace(content), "\r\n", "\n", -1), "\r", "\n", -1)
  98. lines := strings.Split(s, "\n")
  99. var keyType, keyContent, keyComment string
  100. if len(lines) == 1 {
  101. // Parse openssh format
  102. parts := strings.SplitN(lines[0], " ", 3)
  103. switch len(parts) {
  104. case 0:
  105. return "", errors.New("Empty key")
  106. case 1:
  107. keyContent = parts[0]
  108. case 2:
  109. keyType = parts[0]
  110. keyContent = parts[1]
  111. default:
  112. keyType = parts[0]
  113. keyContent = parts[1]
  114. keyComment = parts[2]
  115. }
  116. // If keyType is not given, extract it from content. If given, validate it
  117. if len(keyType) == 0 {
  118. if t, err := extractTypeFromBase64Key(keyContent); err == nil {
  119. keyType = t
  120. } else {
  121. return "", err
  122. }
  123. } else {
  124. if t, err := extractTypeFromBase64Key(keyContent); err != nil || keyType != t {
  125. return "", err
  126. }
  127. }
  128. } else {
  129. // Parse SSH2 file format.
  130. continuationLine := false
  131. for _, line := range lines {
  132. // Skip lines that:
  133. // 1) are a continuation of the previous line,
  134. // 2) contain ":" as that are comment lines
  135. // 3) contain "-" as that are begin and end tags
  136. if continuationLine || strings.ContainsAny(line, ":-") {
  137. continuationLine = strings.HasSuffix(line, "\\")
  138. } else {
  139. keyContent = keyContent + line
  140. }
  141. }
  142. if t, err := extractTypeFromBase64Key(keyContent); err == nil {
  143. keyType = t
  144. } else {
  145. return "", err
  146. }
  147. }
  148. return keyType + " " + keyContent + " " + keyComment, nil
  149. }
  150. // CheckPublicKeyString checks if the given public key string is recognized by SSH.
  151. func CheckPublicKeyString(content string) (_ string, err error) {
  152. content, err = parseKeyString(content)
  153. if err != nil {
  154. return "", err
  155. }
  156. content = strings.TrimRight(content, "\n\r")
  157. if strings.ContainsAny(content, "\n\r") {
  158. return "", errors.New("only a single line with a single key please")
  159. }
  160. // write the key to a file…
  161. tmpFile, err := ioutil.TempFile(os.TempDir(), "keytest")
  162. if err != nil {
  163. return "", err
  164. }
  165. tmpPath := tmpFile.Name()
  166. defer os.Remove(tmpPath)
  167. tmpFile.WriteString(content)
  168. tmpFile.Close()
  169. // Check if ssh-keygen recognizes its contents.
  170. stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-lf", tmpPath)
  171. if err != nil {
  172. return "", errors.New("ssh-keygen -lf: " + stderr)
  173. } else if len(stdout) < 2 {
  174. return "", errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
  175. }
  176. // The ssh-keygen in Windows does not print key type, so no need go further.
  177. if setting.IsWindows {
  178. return content, nil
  179. }
  180. sshKeygenOutput := strings.Split(stdout, " ")
  181. if len(sshKeygenOutput) < 4 {
  182. return content, ErrKeyUnableVerify
  183. }
  184. // Check if key type and key size match.
  185. if !setting.Service.DisableMinimumKeySizeCheck {
  186. keySize := com.StrTo(sshKeygenOutput[0]).MustInt()
  187. if keySize == 0 {
  188. return "", errors.New("cannot get key size of the given key")
  189. }
  190. keyType := strings.Trim(sshKeygenOutput[len(sshKeygenOutput)-1], " ()\n")
  191. if minimumKeySize := setting.Service.MinimumKeySizes[keyType]; minimumKeySize == 0 {
  192. return "", fmt.Errorf("unrecognized public key type: %s", keyType)
  193. } else if keySize < minimumKeySize {
  194. return "", fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
  195. }
  196. }
  197. return content, nil
  198. }
  199. // saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
  200. func saveAuthorizedKeyFile(keys ...*PublicKey) error {
  201. sshOpLocker.Lock()
  202. defer sshOpLocker.Unlock()
  203. fpath := filepath.Join(SSHPath, "authorized_keys")
  204. f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
  205. if err != nil {
  206. return err
  207. }
  208. defer f.Close()
  209. fi, err := f.Stat()
  210. if err != nil {
  211. return err
  212. }
  213. // FIXME: following command does not support in Windows.
  214. if !setting.IsWindows {
  215. // .ssh directory should have mode 700, and authorized_keys file should have mode 600.
  216. if fi.Mode().Perm() > 0600 {
  217. log.Error(4, "authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String())
  218. if err = f.Chmod(0600); err != nil {
  219. return err
  220. }
  221. }
  222. }
  223. for _, key := range keys {
  224. if _, err = f.WriteString(key.GetAuthorizedString()); err != nil {
  225. return err
  226. }
  227. }
  228. return nil
  229. }
  230. // checkKeyContent onlys checks if key content has been used as public key,
  231. // it is OK to use same key as deploy key for multiple repositories/users.
  232. func checkKeyContent(content string) error {
  233. has, err := x.Get(&PublicKey{
  234. Content: content,
  235. Type: KEY_TYPE_USER,
  236. })
  237. if err != nil {
  238. return err
  239. } else if has {
  240. return ErrKeyAlreadyExist{0, content}
  241. }
  242. return nil
  243. }
  244. func addKey(e Engine, key *PublicKey) (err error) {
  245. // Calculate fingerprint.
  246. tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
  247. "id_rsa.pub"), "\\", "/", -1)
  248. os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
  249. if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
  250. return err
  251. }
  252. stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath)
  253. if err != nil {
  254. return errors.New("ssh-keygen -lf: " + stderr)
  255. } else if len(stdout) < 2 {
  256. return errors.New("not enough output for calculating fingerprint: " + stdout)
  257. }
  258. key.Fingerprint = strings.Split(stdout, " ")[1]
  259. // Save SSH key.
  260. if _, err = e.Insert(key); err != nil {
  261. return err
  262. }
  263. return saveAuthorizedKeyFile(key)
  264. }
  265. // AddPublicKey adds new public key to database and authorized_keys file.
  266. func AddPublicKey(ownerID int64, name, content string) (err error) {
  267. if err = checkKeyContent(content); err != nil {
  268. return err
  269. }
  270. // Key name of same user cannot be duplicated.
  271. has, err := x.Where("owner_id=? AND name=?", ownerID, name).Get(new(PublicKey))
  272. if err != nil {
  273. return err
  274. } else if has {
  275. return ErrKeyNameAlreadyUsed{ownerID, name}
  276. }
  277. sess := x.NewSession()
  278. defer sessionRelease(sess)
  279. if err = sess.Begin(); err != nil {
  280. return err
  281. }
  282. key := &PublicKey{
  283. OwnerID: ownerID,
  284. Name: name,
  285. Content: content,
  286. Mode: ACCESS_MODE_WRITE,
  287. Type: KEY_TYPE_USER,
  288. }
  289. if err = addKey(sess, key); err != nil {
  290. return fmt.Errorf("addKey: %v", err)
  291. }
  292. return sess.Commit()
  293. }
  294. // GetPublicKeyByID returns public key by given ID.
  295. func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
  296. key := new(PublicKey)
  297. has, err := x.Id(keyID).Get(key)
  298. if err != nil {
  299. return nil, err
  300. } else if !has {
  301. return nil, ErrKeyNotExist{keyID}
  302. }
  303. return key, nil
  304. }
  305. // SearchPublicKeyByContent searches content as prefix (leak e-mail part)
  306. // and returns public key found.
  307. func SearchPublicKeyByContent(content string) (*PublicKey, error) {
  308. key := new(PublicKey)
  309. has, err := x.Where("content like ?", content+"%").Get(key)
  310. if err != nil {
  311. return nil, err
  312. } else if !has {
  313. return nil, ErrKeyNotExist{}
  314. }
  315. return key, nil
  316. }
  317. // ListPublicKeys returns a list of public keys belongs to given user.
  318. func ListPublicKeys(uid int64) ([]*PublicKey, error) {
  319. keys := make([]*PublicKey, 0, 5)
  320. return keys, x.Where("owner_id=?", uid).Find(&keys)
  321. }
  322. // rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
  323. func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
  324. fr, err := os.Open(p)
  325. if err != nil {
  326. return err
  327. }
  328. defer fr.Close()
  329. fw, err := os.OpenFile(tmpP, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
  330. if err != nil {
  331. return err
  332. }
  333. defer fw.Close()
  334. isFound := false
  335. keyword := fmt.Sprintf("key-%d", key.ID)
  336. buf := bufio.NewReader(fr)
  337. for {
  338. line, errRead := buf.ReadString('\n')
  339. line = strings.TrimSpace(line)
  340. if errRead != nil {
  341. if errRead != io.EOF {
  342. return errRead
  343. }
  344. // Reached end of file, if nothing to read then break,
  345. // otherwise handle the last line.
  346. if len(line) == 0 {
  347. break
  348. }
  349. }
  350. // Found the line and copy rest of file.
  351. if !isFound && strings.Contains(line, keyword) && strings.Contains(line, key.Content) {
  352. isFound = true
  353. continue
  354. }
  355. // Still finding the line, copy the line that currently read.
  356. if _, err = fw.WriteString(line + "\n"); err != nil {
  357. return err
  358. }
  359. if errRead == io.EOF {
  360. break
  361. }
  362. }
  363. return nil
  364. }
  365. // UpdatePublicKey updates given public key.
  366. func UpdatePublicKey(key *PublicKey) error {
  367. _, err := x.Id(key.ID).AllCols().Update(key)
  368. return err
  369. }
  370. func deletePublicKey(e *xorm.Session, keyID int64) error {
  371. sshOpLocker.Lock()
  372. defer sshOpLocker.Unlock()
  373. key := &PublicKey{ID: keyID}
  374. has, err := e.Get(key)
  375. if err != nil {
  376. return err
  377. } else if !has {
  378. return nil
  379. }
  380. if _, err = e.Id(key.ID).Delete(new(PublicKey)); err != nil {
  381. return err
  382. }
  383. fpath := filepath.Join(SSHPath, "authorized_keys")
  384. tmpPath := filepath.Join(SSHPath, "authorized_keys.tmp")
  385. if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
  386. return err
  387. } else if err = os.Remove(fpath); err != nil {
  388. return err
  389. }
  390. return os.Rename(tmpPath, fpath)
  391. }
  392. // DeletePublicKey deletes SSH key information both in database and authorized_keys file.
  393. func DeletePublicKey(id int64) (err error) {
  394. has, err := x.Id(id).Get(new(PublicKey))
  395. if err != nil {
  396. return err
  397. } else if !has {
  398. return nil
  399. }
  400. sess := x.NewSession()
  401. defer sessionRelease(sess)
  402. if err = sess.Begin(); err != nil {
  403. return err
  404. }
  405. if err = deletePublicKey(sess, id); err != nil {
  406. return err
  407. }
  408. return sess.Commit()
  409. }
  410. // RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
  411. func RewriteAllPublicKeys() error {
  412. sshOpLocker.Lock()
  413. defer sshOpLocker.Unlock()
  414. tmpPath := filepath.Join(SSHPath, "authorized_keys.tmp")
  415. f, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  416. if err != nil {
  417. return err
  418. }
  419. defer os.Remove(tmpPath)
  420. err = x.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
  421. _, err = f.WriteString((bean.(*PublicKey)).GetAuthorizedString())
  422. return err
  423. })
  424. f.Close()
  425. if err != nil {
  426. return err
  427. }
  428. fpath := filepath.Join(SSHPath, "authorized_keys")
  429. if com.IsExist(fpath) {
  430. if err = os.Remove(fpath); err != nil {
  431. return err
  432. }
  433. }
  434. if err = os.Rename(tmpPath, fpath); err != nil {
  435. return err
  436. }
  437. return nil
  438. }
  439. // ________ .__ ____ __.
  440. // \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
  441. // | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
  442. // | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
  443. // /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
  444. // \/ \/|__| \/ \/ \/\/
  445. // DeployKey represents deploy key information and its relation with repository.
  446. type DeployKey struct {
  447. ID int64 `xorm:"pk autoincr"`
  448. KeyID int64 `xorm:"UNIQUE(s) INDEX"`
  449. RepoID int64 `xorm:"UNIQUE(s) INDEX"`
  450. Name string
  451. Fingerprint string
  452. Created time.Time `xorm:"CREATED"`
  453. Updated time.Time // Note: Updated must below Created for AfterSet.
  454. HasRecentActivity bool `xorm:"-"`
  455. HasUsed bool `xorm:"-"`
  456. }
  457. func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) {
  458. switch colName {
  459. case "created":
  460. k.HasUsed = k.Updated.After(k.Created)
  461. k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
  462. }
  463. }
  464. func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
  465. // Note: We want error detail, not just true or false here.
  466. has, err := e.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
  467. if err != nil {
  468. return err
  469. } else if has {
  470. return ErrDeployKeyAlreadyExist{keyID, repoID}
  471. }
  472. has, err = e.Where("repo_id=? AND name=?", repoID, name).Get(new(DeployKey))
  473. if err != nil {
  474. return err
  475. } else if has {
  476. return ErrDeployKeyNameAlreadyUsed{repoID, name}
  477. }
  478. return nil
  479. }
  480. // addDeployKey adds new key-repo relation.
  481. func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (err error) {
  482. if err = checkDeployKey(e, keyID, repoID, name); err != nil {
  483. return err
  484. }
  485. _, err = e.Insert(&DeployKey{
  486. KeyID: keyID,
  487. RepoID: repoID,
  488. Name: name,
  489. Fingerprint: fingerprint,
  490. })
  491. return err
  492. }
  493. // HasDeployKey returns true if public key is a deploy key of given repository.
  494. func HasDeployKey(keyID, repoID int64) bool {
  495. has, _ := x.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
  496. return has
  497. }
  498. // AddDeployKey add new deploy key to database and authorized_keys file.
  499. func AddDeployKey(repoID int64, name, content string) (err error) {
  500. if err = checkKeyContent(content); err != nil {
  501. return err
  502. }
  503. key := &PublicKey{
  504. Content: content,
  505. Mode: ACCESS_MODE_READ,
  506. Type: KEY_TYPE_DEPLOY,
  507. }
  508. has, err := x.Get(key)
  509. if err != nil {
  510. return err
  511. }
  512. sess := x.NewSession()
  513. defer sessionRelease(sess)
  514. if err = sess.Begin(); err != nil {
  515. return err
  516. }
  517. // First time use this deploy key.
  518. if !has {
  519. if err = addKey(sess, key); err != nil {
  520. return nil
  521. }
  522. }
  523. if err = addDeployKey(sess, key.ID, repoID, name, key.Fingerprint); err != nil {
  524. return err
  525. }
  526. return sess.Commit()
  527. }
  528. // GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
  529. func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
  530. key := &DeployKey{
  531. KeyID: keyID,
  532. RepoID: repoID,
  533. }
  534. _, err := x.Get(key)
  535. return key, err
  536. }
  537. // UpdateDeployKey updates deploy key information.
  538. func UpdateDeployKey(key *DeployKey) error {
  539. _, err := x.Id(key.ID).AllCols().Update(key)
  540. return err
  541. }
  542. // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
  543. func DeleteDeployKey(id int64) error {
  544. key := &DeployKey{ID: id}
  545. has, err := x.Id(key.ID).Get(key)
  546. if err != nil {
  547. return err
  548. } else if !has {
  549. return nil
  550. }
  551. sess := x.NewSession()
  552. defer sessionRelease(sess)
  553. if err = sess.Begin(); err != nil {
  554. return err
  555. }
  556. if _, err = sess.Id(key.ID).Delete(new(DeployKey)); err != nil {
  557. return fmt.Errorf("delete deploy key[%d]: %v", key.ID, err)
  558. }
  559. // Check if this is the last reference to same key content.
  560. has, err = sess.Where("key_id=?", key.KeyID).Get(new(DeployKey))
  561. if err != nil {
  562. return err
  563. } else if !has {
  564. if err = deletePublicKey(sess, key.KeyID); err != nil {
  565. return err
  566. }
  567. }
  568. return sess.Commit()
  569. }
  570. // ListDeployKeys returns all deploy keys by given repository ID.
  571. func ListDeployKeys(repoID int64) ([]*DeployKey, error) {
  572. keys := make([]*DeployKey, 0, 5)
  573. return keys, x.Where("repo_id=?", repoID).Find(&keys)
  574. }