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.

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