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.

676 lines
17 KiB

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