本站源代码
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.

999 lines
27KB

  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 models
  6. import (
  7. "bufio"
  8. "crypto/rsa"
  9. "crypto/x509"
  10. "encoding/asn1"
  11. "encoding/base64"
  12. "encoding/binary"
  13. "encoding/pem"
  14. "errors"
  15. "fmt"
  16. "io/ioutil"
  17. "math/big"
  18. "os"
  19. "path/filepath"
  20. "strings"
  21. "sync"
  22. "time"
  23. "code.gitea.io/gitea/modules/log"
  24. "code.gitea.io/gitea/modules/process"
  25. "code.gitea.io/gitea/modules/setting"
  26. "code.gitea.io/gitea/modules/timeutil"
  27. "github.com/unknwon/com"
  28. "golang.org/x/crypto/ssh"
  29. "xorm.io/builder"
  30. "xorm.io/xorm"
  31. )
  32. const (
  33. tplCommentPrefix = `# gitea public key`
  34. tplPublicKey = tplCommentPrefix + "\n" + `command="%s --config='%s' serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
  35. )
  36. var sshOpLocker sync.Mutex
  37. // KeyType specifies the key type
  38. type KeyType int
  39. const (
  40. // KeyTypeUser specifies the user key
  41. KeyTypeUser = iota + 1
  42. // KeyTypeDeploy specifies the deploy key
  43. KeyTypeDeploy
  44. )
  45. // PublicKey represents a user or deploy SSH public key.
  46. type PublicKey struct {
  47. ID int64 `xorm:"pk autoincr"`
  48. OwnerID int64 `xorm:"INDEX NOT NULL"`
  49. Name string `xorm:"NOT NULL"`
  50. Fingerprint string `xorm:"INDEX NOT NULL"`
  51. Content string `xorm:"TEXT NOT NULL"`
  52. Mode AccessMode `xorm:"NOT NULL DEFAULT 2"`
  53. Type KeyType `xorm:"NOT NULL DEFAULT 1"`
  54. LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"`
  55. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  56. UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
  57. HasRecentActivity bool `xorm:"-"`
  58. HasUsed bool `xorm:"-"`
  59. }
  60. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  61. func (key *PublicKey) AfterLoad() {
  62. key.HasUsed = key.UpdatedUnix > key.CreatedUnix
  63. key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
  64. }
  65. // OmitEmail returns content of public key without email address.
  66. func (key *PublicKey) OmitEmail() string {
  67. return strings.Join(strings.Split(key.Content, " ")[:2], " ")
  68. }
  69. // AuthorizedString returns formatted public key string for authorized_keys file.
  70. func (key *PublicKey) AuthorizedString() string {
  71. return fmt.Sprintf(tplPublicKey, setting.AppPath, setting.CustomConf, key.ID, key.Content)
  72. }
  73. func extractTypeFromBase64Key(key string) (string, error) {
  74. b, err := base64.StdEncoding.DecodeString(key)
  75. if err != nil || len(b) < 4 {
  76. return "", fmt.Errorf("invalid key format: %v", err)
  77. }
  78. keyLength := int(binary.BigEndian.Uint32(b))
  79. if len(b) < 4+keyLength {
  80. return "", fmt.Errorf("invalid key format: not enough length %d", keyLength)
  81. }
  82. return string(b[4 : 4+keyLength]), nil
  83. }
  84. const ssh2keyStart = "---- BEGIN SSH2 PUBLIC KEY ----"
  85. // parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253).
  86. func parseKeyString(content string) (string, error) {
  87. // remove whitespace at start and end
  88. content = strings.TrimSpace(content)
  89. var keyType, keyContent, keyComment string
  90. if content[:len(ssh2keyStart)] == ssh2keyStart {
  91. // Parse SSH2 file format.
  92. // Transform all legal line endings to a single "\n".
  93. content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content)
  94. lines := strings.Split(content, "\n")
  95. continuationLine := false
  96. for _, line := range lines {
  97. // Skip lines that:
  98. // 1) are a continuation of the previous line,
  99. // 2) contain ":" as that are comment lines
  100. // 3) contain "-" as that are begin and end tags
  101. if continuationLine || strings.ContainsAny(line, ":-") {
  102. continuationLine = strings.HasSuffix(line, "\\")
  103. } else {
  104. keyContent += line
  105. }
  106. }
  107. t, err := extractTypeFromBase64Key(keyContent)
  108. if err != nil {
  109. return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
  110. }
  111. keyType = t
  112. } else {
  113. if strings.Contains(content, "-----BEGIN") {
  114. // Convert PEM Keys to OpenSSH format
  115. // Transform all legal line endings to a single "\n".
  116. content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content)
  117. block, _ := pem.Decode([]byte(content))
  118. if block == nil {
  119. return "", fmt.Errorf("failed to parse PEM block containing the public key")
  120. }
  121. pub, err := x509.ParsePKIXPublicKey(block.Bytes)
  122. if err != nil {
  123. var pk rsa.PublicKey
  124. _, err2 := asn1.Unmarshal(block.Bytes, &pk)
  125. if err2 != nil {
  126. return "", fmt.Errorf("failed to parse DER encoded public key as either PKIX or PEM RSA Key: %v %v", err, err2)
  127. }
  128. pub = &pk
  129. }
  130. sshKey, err := ssh.NewPublicKey(pub)
  131. if err != nil {
  132. return "", fmt.Errorf("unable to convert to ssh public key: %v", err)
  133. }
  134. content = string(ssh.MarshalAuthorizedKey(sshKey))
  135. }
  136. // Parse OpenSSH format.
  137. // Remove all newlines
  138. content = strings.NewReplacer("\r\n", "", "\n", "").Replace(content)
  139. parts := strings.SplitN(content, " ", 3)
  140. switch len(parts) {
  141. case 0:
  142. return "", errors.New("empty key")
  143. case 1:
  144. keyContent = parts[0]
  145. case 2:
  146. keyType = parts[0]
  147. keyContent = parts[1]
  148. default:
  149. keyType = parts[0]
  150. keyContent = parts[1]
  151. keyComment = parts[2]
  152. }
  153. // If keyType is not given, extract it from content. If given, validate it.
  154. t, err := extractTypeFromBase64Key(keyContent)
  155. if err != nil {
  156. return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
  157. }
  158. if len(keyType) == 0 {
  159. keyType = t
  160. } else if keyType != t {
  161. return "", fmt.Errorf("key type and content does not match: %s - %s", keyType, t)
  162. }
  163. }
  164. // Finally we need to check whether we can actually read the proposed key:
  165. _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyType + " " + keyContent + " " + keyComment))
  166. if err != nil {
  167. return "", fmt.Errorf("invalid ssh public key: %v", err)
  168. }
  169. return keyType + " " + keyContent + " " + keyComment, nil
  170. }
  171. // writeTmpKeyFile writes key content to a temporary file
  172. // and returns the name of that file, along with any possible errors.
  173. func writeTmpKeyFile(content string) (string, error) {
  174. tmpFile, err := ioutil.TempFile(setting.SSH.KeyTestPath, "gitea_keytest")
  175. if err != nil {
  176. return "", fmt.Errorf("TempFile: %v", err)
  177. }
  178. defer tmpFile.Close()
  179. if _, err = tmpFile.WriteString(content); err != nil {
  180. return "", fmt.Errorf("WriteString: %v", err)
  181. }
  182. return tmpFile.Name(), nil
  183. }
  184. // SSHKeyGenParsePublicKey extracts key type and length using ssh-keygen.
  185. func SSHKeyGenParsePublicKey(key string) (string, int, error) {
  186. // The ssh-keygen in Windows does not print key type, so no need go further.
  187. if setting.IsWindows {
  188. return "", 0, nil
  189. }
  190. tmpName, err := writeTmpKeyFile(key)
  191. if err != nil {
  192. return "", 0, fmt.Errorf("writeTmpKeyFile: %v", err)
  193. }
  194. defer os.Remove(tmpName)
  195. stdout, stderr, err := process.GetManager().Exec("SSHKeyGenParsePublicKey", setting.SSH.KeygenPath, "-lf", tmpName)
  196. if err != nil {
  197. return "", 0, fmt.Errorf("fail to parse public key: %s - %s", err, stderr)
  198. }
  199. if strings.Contains(stdout, "is not a public key file") {
  200. return "", 0, ErrKeyUnableVerify{stdout}
  201. }
  202. fields := strings.Split(stdout, " ")
  203. if len(fields) < 4 {
  204. return "", 0, fmt.Errorf("invalid public key line: %s", stdout)
  205. }
  206. keyType := strings.Trim(fields[len(fields)-1], "()\r\n")
  207. return strings.ToLower(keyType), com.StrTo(fields[0]).MustInt(), nil
  208. }
  209. // SSHNativeParsePublicKey extracts the key type and length using the golang SSH library.
  210. func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
  211. fields := strings.Fields(keyLine)
  212. if len(fields) < 2 {
  213. return "", 0, fmt.Errorf("not enough fields in public key line: %s", keyLine)
  214. }
  215. raw, err := base64.StdEncoding.DecodeString(fields[1])
  216. if err != nil {
  217. return "", 0, err
  218. }
  219. pkey, err := ssh.ParsePublicKey(raw)
  220. if err != nil {
  221. if strings.Contains(err.Error(), "ssh: unknown key algorithm") {
  222. return "", 0, ErrKeyUnableVerify{err.Error()}
  223. }
  224. return "", 0, fmt.Errorf("ParsePublicKey: %v", err)
  225. }
  226. // The ssh library can parse the key, so next we find out what key exactly we have.
  227. switch pkey.Type() {
  228. case ssh.KeyAlgoDSA:
  229. rawPub := struct {
  230. Name string
  231. P, Q, G, Y *big.Int
  232. }{}
  233. if err := ssh.Unmarshal(pkey.Marshal(), &rawPub); err != nil {
  234. return "", 0, err
  235. }
  236. // as per https://bugzilla.mindrot.org/show_bug.cgi?id=1647 we should never
  237. // see dsa keys != 1024 bit, but as it seems to work, we will not check here
  238. return "dsa", rawPub.P.BitLen(), nil // use P as per crypto/dsa/dsa.go (is L)
  239. case ssh.KeyAlgoRSA:
  240. rawPub := struct {
  241. Name string
  242. E *big.Int
  243. N *big.Int
  244. }{}
  245. if err := ssh.Unmarshal(pkey.Marshal(), &rawPub); err != nil {
  246. return "", 0, err
  247. }
  248. return "rsa", rawPub.N.BitLen(), nil // use N as per crypto/rsa/rsa.go (is bits)
  249. case ssh.KeyAlgoECDSA256:
  250. return "ecdsa", 256, nil
  251. case ssh.KeyAlgoECDSA384:
  252. return "ecdsa", 384, nil
  253. case ssh.KeyAlgoECDSA521:
  254. return "ecdsa", 521, nil
  255. case ssh.KeyAlgoED25519:
  256. return "ed25519", 256, nil
  257. }
  258. return "", 0, fmt.Errorf("unsupported key length detection for type: %s", pkey.Type())
  259. }
  260. // CheckPublicKeyString checks if the given public key string is recognized by SSH.
  261. // It returns the actual public key line on success.
  262. func CheckPublicKeyString(content string) (_ string, err error) {
  263. if setting.SSH.Disabled {
  264. return "", ErrSSHDisabled{}
  265. }
  266. content, err = parseKeyString(content)
  267. if err != nil {
  268. return "", err
  269. }
  270. content = strings.TrimRight(content, "\n\r")
  271. if strings.ContainsAny(content, "\n\r") {
  272. return "", errors.New("only a single line with a single key please")
  273. }
  274. // remove any unnecessary whitespace now
  275. content = strings.TrimSpace(content)
  276. if !setting.SSH.MinimumKeySizeCheck {
  277. return content, nil
  278. }
  279. var (
  280. fnName string
  281. keyType string
  282. length int
  283. )
  284. if setting.SSH.StartBuiltinServer {
  285. fnName = "SSHNativeParsePublicKey"
  286. keyType, length, err = SSHNativeParsePublicKey(content)
  287. } else {
  288. fnName = "SSHKeyGenParsePublicKey"
  289. keyType, length, err = SSHKeyGenParsePublicKey(content)
  290. }
  291. if err != nil {
  292. return "", fmt.Errorf("%s: %v", fnName, err)
  293. }
  294. log.Trace("Key info [native: %v]: %s-%d", setting.SSH.StartBuiltinServer, keyType, length)
  295. if minLen, found := setting.SSH.MinimumKeySizes[keyType]; found && length >= minLen {
  296. return content, nil
  297. } else if found && length < minLen {
  298. return "", fmt.Errorf("key length is not enough: got %d, needs %d", length, minLen)
  299. }
  300. return "", fmt.Errorf("key type is not allowed: %s", keyType)
  301. }
  302. // appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
  303. func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
  304. // Don't need to rewrite this file if builtin SSH server is enabled.
  305. if setting.SSH.StartBuiltinServer {
  306. return nil
  307. }
  308. sshOpLocker.Lock()
  309. defer sshOpLocker.Unlock()
  310. if setting.SSH.RootPath != "" {
  311. // First of ensure that the RootPath is present, and if not make it with 0700 permissions
  312. // This of course doesn't guarantee that this is the right directory for authorized_keys
  313. // but at least if it's supposed to be this directory and it doesn't exist and we're the
  314. // right user it will at least be created properly.
  315. err := os.MkdirAll(setting.SSH.RootPath, 0700)
  316. if err != nil {
  317. log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
  318. return err
  319. }
  320. }
  321. fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
  322. f, err := os.OpenFile(fPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
  323. if err != nil {
  324. return err
  325. }
  326. defer f.Close()
  327. // Note: chmod command does not support in Windows.
  328. if !setting.IsWindows {
  329. fi, err := f.Stat()
  330. if err != nil {
  331. return err
  332. }
  333. // .ssh directory should have mode 700, and authorized_keys file should have mode 600.
  334. if fi.Mode().Perm() > 0600 {
  335. log.Error("authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String())
  336. if err = f.Chmod(0600); err != nil {
  337. return err
  338. }
  339. }
  340. }
  341. for _, key := range keys {
  342. if _, err = f.WriteString(key.AuthorizedString()); err != nil {
  343. return err
  344. }
  345. }
  346. return nil
  347. }
  348. // checkKeyFingerprint only checks if key fingerprint has been used as public key,
  349. // it is OK to use same key as deploy key for multiple repositories/users.
  350. func checkKeyFingerprint(e Engine, fingerprint string) error {
  351. has, err := e.Get(&PublicKey{
  352. Fingerprint: fingerprint,
  353. })
  354. if err != nil {
  355. return err
  356. } else if has {
  357. return ErrKeyAlreadyExist{0, fingerprint, ""}
  358. }
  359. return nil
  360. }
  361. func calcFingerprintSSHKeygen(publicKeyContent string) (string, error) {
  362. // Calculate fingerprint.
  363. tmpPath, err := writeTmpKeyFile(publicKeyContent)
  364. if err != nil {
  365. return "", err
  366. }
  367. defer os.Remove(tmpPath)
  368. stdout, stderr, err := process.GetManager().Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath)
  369. if err != nil {
  370. return "", fmt.Errorf("'ssh-keygen -lf %s' failed with error '%s': %s", tmpPath, err, stderr)
  371. } else if len(stdout) < 2 {
  372. return "", errors.New("not enough output for calculating fingerprint: " + stdout)
  373. }
  374. return strings.Split(stdout, " ")[1], nil
  375. }
  376. func calcFingerprintNative(publicKeyContent string) (string, error) {
  377. // Calculate fingerprint.
  378. pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeyContent))
  379. if err != nil {
  380. return "", err
  381. }
  382. return ssh.FingerprintSHA256(pk), nil
  383. }
  384. func calcFingerprint(publicKeyContent string) (string, error) {
  385. //Call the method based on configuration
  386. var (
  387. fnName, fp string
  388. err error
  389. )
  390. if setting.SSH.StartBuiltinServer {
  391. fnName = "calcFingerprintNative"
  392. fp, err = calcFingerprintNative(publicKeyContent)
  393. } else {
  394. fnName = "calcFingerprintSSHKeygen"
  395. fp, err = calcFingerprintSSHKeygen(publicKeyContent)
  396. }
  397. if err != nil {
  398. return "", fmt.Errorf("%s: %v", fnName, err)
  399. }
  400. return fp, nil
  401. }
  402. func addKey(e Engine, key *PublicKey) (err error) {
  403. if len(key.Fingerprint) == 0 {
  404. key.Fingerprint, err = calcFingerprint(key.Content)
  405. if err != nil {
  406. return err
  407. }
  408. }
  409. // Save SSH key.
  410. if _, err = e.Insert(key); err != nil {
  411. return err
  412. }
  413. return appendAuthorizedKeysToFile(key)
  414. }
  415. // AddPublicKey adds new public key to database and authorized_keys file.
  416. func AddPublicKey(ownerID int64, name, content string, loginSourceID int64) (*PublicKey, error) {
  417. log.Trace(content)
  418. fingerprint, err := calcFingerprint(content)
  419. if err != nil {
  420. return nil, err
  421. }
  422. sess := x.NewSession()
  423. defer sess.Close()
  424. if err = sess.Begin(); err != nil {
  425. return nil, err
  426. }
  427. if err := checkKeyFingerprint(sess, fingerprint); err != nil {
  428. return nil, err
  429. }
  430. // Key name of same user cannot be duplicated.
  431. has, err := sess.
  432. Where("owner_id = ? AND name = ?", ownerID, name).
  433. Get(new(PublicKey))
  434. if err != nil {
  435. return nil, err
  436. } else if has {
  437. return nil, ErrKeyNameAlreadyUsed{ownerID, name}
  438. }
  439. key := &PublicKey{
  440. OwnerID: ownerID,
  441. Name: name,
  442. Fingerprint: fingerprint,
  443. Content: content,
  444. Mode: AccessModeWrite,
  445. Type: KeyTypeUser,
  446. LoginSourceID: loginSourceID,
  447. }
  448. if err = addKey(sess, key); err != nil {
  449. return nil, fmt.Errorf("addKey: %v", err)
  450. }
  451. return key, sess.Commit()
  452. }
  453. // GetPublicKeyByID returns public key by given ID.
  454. func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
  455. key := new(PublicKey)
  456. has, err := x.
  457. Id(keyID).
  458. Get(key)
  459. if err != nil {
  460. return nil, err
  461. } else if !has {
  462. return nil, ErrKeyNotExist{keyID}
  463. }
  464. return key, nil
  465. }
  466. func searchPublicKeyByContentWithEngine(e Engine, content string) (*PublicKey, error) {
  467. key := new(PublicKey)
  468. has, err := e.
  469. Where("content like ?", content+"%").
  470. Get(key)
  471. if err != nil {
  472. return nil, err
  473. } else if !has {
  474. return nil, ErrKeyNotExist{}
  475. }
  476. return key, nil
  477. }
  478. // SearchPublicKeyByContent searches content as prefix (leak e-mail part)
  479. // and returns public key found.
  480. func SearchPublicKeyByContent(content string) (*PublicKey, error) {
  481. return searchPublicKeyByContentWithEngine(x, content)
  482. }
  483. // SearchPublicKey returns a list of public keys matching the provided arguments.
  484. func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) {
  485. keys := make([]*PublicKey, 0, 5)
  486. cond := builder.NewCond()
  487. if uid != 0 {
  488. cond = cond.And(builder.Eq{"owner_id": uid})
  489. }
  490. if fingerprint != "" {
  491. cond = cond.And(builder.Eq{"fingerprint": fingerprint})
  492. }
  493. return keys, x.Where(cond).Find(&keys)
  494. }
  495. // ListPublicKeys returns a list of public keys belongs to given user.
  496. func ListPublicKeys(uid int64) ([]*PublicKey, error) {
  497. keys := make([]*PublicKey, 0, 5)
  498. return keys, x.
  499. Where("owner_id = ?", uid).
  500. Find(&keys)
  501. }
  502. // ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and login source.
  503. func ListPublicLdapSSHKeys(uid int64, loginSourceID int64) ([]*PublicKey, error) {
  504. keys := make([]*PublicKey, 0, 5)
  505. return keys, x.
  506. Where("owner_id = ? AND login_source_id = ?", uid, loginSourceID).
  507. Find(&keys)
  508. }
  509. // UpdatePublicKeyUpdated updates public key use time.
  510. func UpdatePublicKeyUpdated(id int64) error {
  511. // Check if key exists before update as affected rows count is unreliable
  512. // and will return 0 affected rows if two updates are made at the same time
  513. if cnt, err := x.ID(id).Count(&PublicKey{}); err != nil {
  514. return err
  515. } else if cnt != 1 {
  516. return ErrKeyNotExist{id}
  517. }
  518. _, err := x.ID(id).Cols("updated_unix").Update(&PublicKey{
  519. UpdatedUnix: timeutil.TimeStampNow(),
  520. })
  521. if err != nil {
  522. return err
  523. }
  524. return nil
  525. }
  526. // deletePublicKeys does the actual key deletion but does not update authorized_keys file.
  527. func deletePublicKeys(e Engine, keyIDs ...int64) error {
  528. if len(keyIDs) == 0 {
  529. return nil
  530. }
  531. _, err := e.In("id", keyIDs).Delete(new(PublicKey))
  532. return err
  533. }
  534. // DeletePublicKey deletes SSH key information both in database and authorized_keys file.
  535. func DeletePublicKey(doer *User, id int64) (err error) {
  536. key, err := GetPublicKeyByID(id)
  537. if err != nil {
  538. return err
  539. }
  540. // Check if user has access to delete this key.
  541. if !doer.IsAdmin && doer.ID != key.OwnerID {
  542. return ErrKeyAccessDenied{doer.ID, key.ID, "public"}
  543. }
  544. sess := x.NewSession()
  545. defer sess.Close()
  546. if err = sess.Begin(); err != nil {
  547. return err
  548. }
  549. if err = deletePublicKeys(sess, id); err != nil {
  550. return err
  551. }
  552. if err = sess.Commit(); err != nil {
  553. return err
  554. }
  555. sess.Close()
  556. return RewriteAllPublicKeys()
  557. }
  558. // RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
  559. // Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
  560. // outside any session scope independently.
  561. func RewriteAllPublicKeys() error {
  562. return rewriteAllPublicKeys(x)
  563. }
  564. func rewriteAllPublicKeys(e Engine) error {
  565. //Don't rewrite key if internal server
  566. if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
  567. return nil
  568. }
  569. sshOpLocker.Lock()
  570. defer sshOpLocker.Unlock()
  571. if setting.SSH.RootPath != "" {
  572. // First of ensure that the RootPath is present, and if not make it with 0700 permissions
  573. // This of course doesn't guarantee that this is the right directory for authorized_keys
  574. // but at least if it's supposed to be this directory and it doesn't exist and we're the
  575. // right user it will at least be created properly.
  576. err := os.MkdirAll(setting.SSH.RootPath, 0700)
  577. if err != nil {
  578. log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
  579. return err
  580. }
  581. }
  582. fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
  583. tmpPath := fPath + ".tmp"
  584. t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  585. if err != nil {
  586. return err
  587. }
  588. defer func() {
  589. t.Close()
  590. os.Remove(tmpPath)
  591. }()
  592. if setting.SSH.AuthorizedKeysBackup && com.IsExist(fPath) {
  593. bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
  594. if err = com.Copy(fPath, bakPath); err != nil {
  595. return err
  596. }
  597. }
  598. err = e.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
  599. _, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
  600. return err
  601. })
  602. if err != nil {
  603. return err
  604. }
  605. if com.IsExist(fPath) {
  606. f, err := os.Open(fPath)
  607. if err != nil {
  608. return err
  609. }
  610. scanner := bufio.NewScanner(f)
  611. for scanner.Scan() {
  612. line := scanner.Text()
  613. if strings.HasPrefix(line, tplCommentPrefix) {
  614. scanner.Scan()
  615. continue
  616. }
  617. _, err = t.WriteString(line + "\n")
  618. if err != nil {
  619. f.Close()
  620. return err
  621. }
  622. }
  623. f.Close()
  624. }
  625. t.Close()
  626. return os.Rename(tmpPath, fPath)
  627. }
  628. // ________ .__ ____ __.
  629. // \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
  630. // | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
  631. // | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
  632. // /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
  633. // \/ \/|__| \/ \/ \/\/
  634. // DeployKey represents deploy key information and its relation with repository.
  635. type DeployKey struct {
  636. ID int64 `xorm:"pk autoincr"`
  637. KeyID int64 `xorm:"UNIQUE(s) INDEX"`
  638. RepoID int64 `xorm:"UNIQUE(s) INDEX"`
  639. Name string
  640. Fingerprint string
  641. Content string `xorm:"-"`
  642. Mode AccessMode `xorm:"NOT NULL DEFAULT 1"`
  643. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  644. UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
  645. HasRecentActivity bool `xorm:"-"`
  646. HasUsed bool `xorm:"-"`
  647. }
  648. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  649. func (key *DeployKey) AfterLoad() {
  650. key.HasUsed = key.UpdatedUnix > key.CreatedUnix
  651. key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
  652. }
  653. // GetContent gets associated public key content.
  654. func (key *DeployKey) GetContent() error {
  655. pkey, err := GetPublicKeyByID(key.KeyID)
  656. if err != nil {
  657. return err
  658. }
  659. key.Content = pkey.Content
  660. return nil
  661. }
  662. // IsReadOnly checks if the key can only be used for read operations
  663. func (key *DeployKey) IsReadOnly() bool {
  664. return key.Mode == AccessModeRead
  665. }
  666. func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
  667. // Note: We want error detail, not just true or false here.
  668. has, err := e.
  669. Where("key_id = ? AND repo_id = ?", keyID, repoID).
  670. Get(new(DeployKey))
  671. if err != nil {
  672. return err
  673. } else if has {
  674. return ErrDeployKeyAlreadyExist{keyID, repoID}
  675. }
  676. has, err = e.
  677. Where("repo_id = ? AND name = ?", repoID, name).
  678. Get(new(DeployKey))
  679. if err != nil {
  680. return err
  681. } else if has {
  682. return ErrDeployKeyNameAlreadyUsed{repoID, name}
  683. }
  684. return nil
  685. }
  686. // addDeployKey adds new key-repo relation.
  687. func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string, mode AccessMode) (*DeployKey, error) {
  688. if err := checkDeployKey(e, keyID, repoID, name); err != nil {
  689. return nil, err
  690. }
  691. key := &DeployKey{
  692. KeyID: keyID,
  693. RepoID: repoID,
  694. Name: name,
  695. Fingerprint: fingerprint,
  696. Mode: mode,
  697. }
  698. _, err := e.Insert(key)
  699. return key, err
  700. }
  701. // HasDeployKey returns true if public key is a deploy key of given repository.
  702. func HasDeployKey(keyID, repoID int64) bool {
  703. has, _ := x.
  704. Where("key_id = ? AND repo_id = ?", keyID, repoID).
  705. Get(new(DeployKey))
  706. return has
  707. }
  708. // AddDeployKey add new deploy key to database and authorized_keys file.
  709. func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey, error) {
  710. fingerprint, err := calcFingerprint(content)
  711. if err != nil {
  712. return nil, err
  713. }
  714. accessMode := AccessModeRead
  715. if !readOnly {
  716. accessMode = AccessModeWrite
  717. }
  718. sess := x.NewSession()
  719. defer sess.Close()
  720. if err = sess.Begin(); err != nil {
  721. return nil, err
  722. }
  723. pkey := &PublicKey{
  724. Fingerprint: fingerprint,
  725. }
  726. has, err := sess.Get(pkey)
  727. if err != nil {
  728. return nil, err
  729. }
  730. if has {
  731. if pkey.Type != KeyTypeDeploy {
  732. return nil, ErrKeyAlreadyExist{0, fingerprint, ""}
  733. }
  734. } else {
  735. // First time use this deploy key.
  736. pkey.Mode = accessMode
  737. pkey.Type = KeyTypeDeploy
  738. pkey.Content = content
  739. pkey.Name = name
  740. if err = addKey(sess, pkey); err != nil {
  741. return nil, fmt.Errorf("addKey: %v", err)
  742. }
  743. }
  744. key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint, accessMode)
  745. if err != nil {
  746. return nil, err
  747. }
  748. return key, sess.Commit()
  749. }
  750. // GetDeployKeyByID returns deploy key by given ID.
  751. func GetDeployKeyByID(id int64) (*DeployKey, error) {
  752. return getDeployKeyByID(x, id)
  753. }
  754. func getDeployKeyByID(e Engine, id int64) (*DeployKey, error) {
  755. key := new(DeployKey)
  756. has, err := e.ID(id).Get(key)
  757. if err != nil {
  758. return nil, err
  759. } else if !has {
  760. return nil, ErrDeployKeyNotExist{id, 0, 0}
  761. }
  762. return key, nil
  763. }
  764. // GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
  765. func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
  766. return getDeployKeyByRepo(x, keyID, repoID)
  767. }
  768. func getDeployKeyByRepo(e Engine, keyID, repoID int64) (*DeployKey, error) {
  769. key := &DeployKey{
  770. KeyID: keyID,
  771. RepoID: repoID,
  772. }
  773. has, err := e.Get(key)
  774. if err != nil {
  775. return nil, err
  776. } else if !has {
  777. return nil, ErrDeployKeyNotExist{0, keyID, repoID}
  778. }
  779. return key, nil
  780. }
  781. // UpdateDeployKeyCols updates deploy key information in the specified columns.
  782. func UpdateDeployKeyCols(key *DeployKey, cols ...string) error {
  783. _, err := x.ID(key.ID).Cols(cols...).Update(key)
  784. return err
  785. }
  786. // UpdateDeployKey updates deploy key information.
  787. func UpdateDeployKey(key *DeployKey) error {
  788. _, err := x.ID(key.ID).AllCols().Update(key)
  789. return err
  790. }
  791. // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
  792. func DeleteDeployKey(doer *User, id int64) error {
  793. sess := x.NewSession()
  794. defer sess.Close()
  795. if err := sess.Begin(); err != nil {
  796. return err
  797. }
  798. if err := deleteDeployKey(sess, doer, id); err != nil {
  799. return err
  800. }
  801. return sess.Commit()
  802. }
  803. func deleteDeployKey(sess Engine, doer *User, id int64) error {
  804. key, err := getDeployKeyByID(sess, id)
  805. if err != nil {
  806. if IsErrDeployKeyNotExist(err) {
  807. return nil
  808. }
  809. return fmt.Errorf("GetDeployKeyByID: %v", err)
  810. }
  811. // Check if user has access to delete this key.
  812. if !doer.IsAdmin {
  813. repo, err := getRepositoryByID(sess, key.RepoID)
  814. if err != nil {
  815. return fmt.Errorf("GetRepositoryByID: %v", err)
  816. }
  817. has, err := isUserRepoAdmin(sess, repo, doer)
  818. if err != nil {
  819. return fmt.Errorf("GetUserRepoPermission: %v", err)
  820. } else if !has {
  821. return ErrKeyAccessDenied{doer.ID, key.ID, "deploy"}
  822. }
  823. }
  824. if _, err = sess.ID(key.ID).Delete(new(DeployKey)); err != nil {
  825. return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err)
  826. }
  827. // Check if this is the last reference to same key content.
  828. has, err := sess.
  829. Where("key_id = ?", key.KeyID).
  830. Get(new(DeployKey))
  831. if err != nil {
  832. return err
  833. } else if !has {
  834. if err = deletePublicKeys(sess, key.KeyID); err != nil {
  835. return err
  836. }
  837. // after deleted the public keys, should rewrite the public keys file
  838. if err = rewriteAllPublicKeys(sess); err != nil {
  839. return err
  840. }
  841. }
  842. return nil
  843. }
  844. // ListDeployKeys returns all deploy keys by given repository ID.
  845. func ListDeployKeys(repoID int64) ([]*DeployKey, error) {
  846. return listDeployKeys(x, repoID)
  847. }
  848. func listDeployKeys(e Engine, repoID int64) ([]*DeployKey, error) {
  849. keys := make([]*DeployKey, 0, 5)
  850. return keys, e.
  851. Where("repo_id = ?", repoID).
  852. Find(&keys)
  853. }
  854. // SearchDeployKeys returns a list of deploy keys matching the provided arguments.
  855. func SearchDeployKeys(repoID int64, keyID int64, fingerprint string) ([]*DeployKey, error) {
  856. keys := make([]*DeployKey, 0, 5)
  857. cond := builder.NewCond()
  858. if repoID != 0 {
  859. cond = cond.And(builder.Eq{"repo_id": repoID})
  860. }
  861. if keyID != 0 {
  862. cond = cond.And(builder.Eq{"key_id": keyID})
  863. }
  864. if fingerprint != "" {
  865. cond = cond.And(builder.Eq{"fingerprint": fingerprint})
  866. }
  867. return keys, x.Where(cond).Find(&keys)
  868. }
上海开阖软件有限公司 沪ICP备12045867号-1