You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ssh_key.go 28KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  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 strings.HasPrefix(content, 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, listOptions ListOptions) ([]*PublicKey, error) {
  497. sess := x.Where("owner_id = ?", uid)
  498. if listOptions.Page != 0 {
  499. sess = listOptions.setSessionPagination(sess)
  500. keys := make([]*PublicKey, 0, listOptions.PageSize)
  501. return keys, sess.Find(&keys)
  502. }
  503. keys := make([]*PublicKey, 0, 5)
  504. return keys, sess.Find(&keys)
  505. }
  506. // ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and login source.
  507. func ListPublicLdapSSHKeys(uid int64, loginSourceID int64) ([]*PublicKey, error) {
  508. keys := make([]*PublicKey, 0, 5)
  509. return keys, x.
  510. Where("owner_id = ? AND login_source_id = ?", uid, loginSourceID).
  511. Find(&keys)
  512. }
  513. // UpdatePublicKeyUpdated updates public key use time.
  514. func UpdatePublicKeyUpdated(id int64) error {
  515. // Check if key exists before update as affected rows count is unreliable
  516. // and will return 0 affected rows if two updates are made at the same time
  517. if cnt, err := x.ID(id).Count(&PublicKey{}); err != nil {
  518. return err
  519. } else if cnt != 1 {
  520. return ErrKeyNotExist{id}
  521. }
  522. _, err := x.ID(id).Cols("updated_unix").Update(&PublicKey{
  523. UpdatedUnix: timeutil.TimeStampNow(),
  524. })
  525. if err != nil {
  526. return err
  527. }
  528. return nil
  529. }
  530. // deletePublicKeys does the actual key deletion but does not update authorized_keys file.
  531. func deletePublicKeys(e Engine, keyIDs ...int64) error {
  532. if len(keyIDs) == 0 {
  533. return nil
  534. }
  535. _, err := e.In("id", keyIDs).Delete(new(PublicKey))
  536. return err
  537. }
  538. // DeletePublicKey deletes SSH key information both in database and authorized_keys file.
  539. func DeletePublicKey(doer *User, id int64) (err error) {
  540. key, err := GetPublicKeyByID(id)
  541. if err != nil {
  542. return err
  543. }
  544. // Check if user has access to delete this key.
  545. if !doer.IsAdmin && doer.ID != key.OwnerID {
  546. return ErrKeyAccessDenied{doer.ID, key.ID, "public"}
  547. }
  548. sess := x.NewSession()
  549. defer sess.Close()
  550. if err = sess.Begin(); err != nil {
  551. return err
  552. }
  553. if err = deletePublicKeys(sess, id); err != nil {
  554. return err
  555. }
  556. if err = sess.Commit(); err != nil {
  557. return err
  558. }
  559. sess.Close()
  560. return RewriteAllPublicKeys()
  561. }
  562. // RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
  563. // Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
  564. // outside any session scope independently.
  565. func RewriteAllPublicKeys() error {
  566. return rewriteAllPublicKeys(x)
  567. }
  568. func rewriteAllPublicKeys(e Engine) error {
  569. //Don't rewrite key if internal server
  570. if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
  571. return nil
  572. }
  573. sshOpLocker.Lock()
  574. defer sshOpLocker.Unlock()
  575. if setting.SSH.RootPath != "" {
  576. // First of ensure that the RootPath is present, and if not make it with 0700 permissions
  577. // This of course doesn't guarantee that this is the right directory for authorized_keys
  578. // but at least if it's supposed to be this directory and it doesn't exist and we're the
  579. // right user it will at least be created properly.
  580. err := os.MkdirAll(setting.SSH.RootPath, 0700)
  581. if err != nil {
  582. log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
  583. return err
  584. }
  585. }
  586. fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
  587. tmpPath := fPath + ".tmp"
  588. t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  589. if err != nil {
  590. return err
  591. }
  592. defer func() {
  593. t.Close()
  594. os.Remove(tmpPath)
  595. }()
  596. if setting.SSH.AuthorizedKeysBackup && com.IsExist(fPath) {
  597. bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
  598. if err = com.Copy(fPath, bakPath); err != nil {
  599. return err
  600. }
  601. }
  602. err = e.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
  603. _, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
  604. return err
  605. })
  606. if err != nil {
  607. return err
  608. }
  609. if com.IsExist(fPath) {
  610. f, err := os.Open(fPath)
  611. if err != nil {
  612. return err
  613. }
  614. scanner := bufio.NewScanner(f)
  615. for scanner.Scan() {
  616. line := scanner.Text()
  617. if strings.HasPrefix(line, tplCommentPrefix) {
  618. scanner.Scan()
  619. continue
  620. }
  621. _, err = t.WriteString(line + "\n")
  622. if err != nil {
  623. f.Close()
  624. return err
  625. }
  626. }
  627. f.Close()
  628. }
  629. t.Close()
  630. return os.Rename(tmpPath, fPath)
  631. }
  632. // ________ .__ ____ __.
  633. // \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
  634. // | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
  635. // | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
  636. // /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
  637. // \/ \/|__| \/ \/ \/\/
  638. // DeployKey represents deploy key information and its relation with repository.
  639. type DeployKey struct {
  640. ID int64 `xorm:"pk autoincr"`
  641. KeyID int64 `xorm:"UNIQUE(s) INDEX"`
  642. RepoID int64 `xorm:"UNIQUE(s) INDEX"`
  643. Name string
  644. Fingerprint string
  645. Content string `xorm:"-"`
  646. Mode AccessMode `xorm:"NOT NULL DEFAULT 1"`
  647. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  648. UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
  649. HasRecentActivity bool `xorm:"-"`
  650. HasUsed bool `xorm:"-"`
  651. }
  652. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  653. func (key *DeployKey) AfterLoad() {
  654. key.HasUsed = key.UpdatedUnix > key.CreatedUnix
  655. key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
  656. }
  657. // GetContent gets associated public key content.
  658. func (key *DeployKey) GetContent() error {
  659. pkey, err := GetPublicKeyByID(key.KeyID)
  660. if err != nil {
  661. return err
  662. }
  663. key.Content = pkey.Content
  664. return nil
  665. }
  666. // IsReadOnly checks if the key can only be used for read operations
  667. func (key *DeployKey) IsReadOnly() bool {
  668. return key.Mode == AccessModeRead
  669. }
  670. func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
  671. // Note: We want error detail, not just true or false here.
  672. has, err := e.
  673. Where("key_id = ? AND repo_id = ?", keyID, repoID).
  674. Get(new(DeployKey))
  675. if err != nil {
  676. return err
  677. } else if has {
  678. return ErrDeployKeyAlreadyExist{keyID, repoID}
  679. }
  680. has, err = e.
  681. Where("repo_id = ? AND name = ?", repoID, name).
  682. Get(new(DeployKey))
  683. if err != nil {
  684. return err
  685. } else if has {
  686. return ErrDeployKeyNameAlreadyUsed{repoID, name}
  687. }
  688. return nil
  689. }
  690. // addDeployKey adds new key-repo relation.
  691. func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string, mode AccessMode) (*DeployKey, error) {
  692. if err := checkDeployKey(e, keyID, repoID, name); err != nil {
  693. return nil, err
  694. }
  695. key := &DeployKey{
  696. KeyID: keyID,
  697. RepoID: repoID,
  698. Name: name,
  699. Fingerprint: fingerprint,
  700. Mode: mode,
  701. }
  702. _, err := e.Insert(key)
  703. return key, err
  704. }
  705. // HasDeployKey returns true if public key is a deploy key of given repository.
  706. func HasDeployKey(keyID, repoID int64) bool {
  707. has, _ := x.
  708. Where("key_id = ? AND repo_id = ?", keyID, repoID).
  709. Get(new(DeployKey))
  710. return has
  711. }
  712. // AddDeployKey add new deploy key to database and authorized_keys file.
  713. func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey, error) {
  714. fingerprint, err := calcFingerprint(content)
  715. if err != nil {
  716. return nil, err
  717. }
  718. accessMode := AccessModeRead
  719. if !readOnly {
  720. accessMode = AccessModeWrite
  721. }
  722. sess := x.NewSession()
  723. defer sess.Close()
  724. if err = sess.Begin(); err != nil {
  725. return nil, err
  726. }
  727. pkey := &PublicKey{
  728. Fingerprint: fingerprint,
  729. }
  730. has, err := sess.Get(pkey)
  731. if err != nil {
  732. return nil, err
  733. }
  734. if has {
  735. if pkey.Type != KeyTypeDeploy {
  736. return nil, ErrKeyAlreadyExist{0, fingerprint, ""}
  737. }
  738. } else {
  739. // First time use this deploy key.
  740. pkey.Mode = accessMode
  741. pkey.Type = KeyTypeDeploy
  742. pkey.Content = content
  743. pkey.Name = name
  744. if err = addKey(sess, pkey); err != nil {
  745. return nil, fmt.Errorf("addKey: %v", err)
  746. }
  747. }
  748. key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint, accessMode)
  749. if err != nil {
  750. return nil, err
  751. }
  752. return key, sess.Commit()
  753. }
  754. // GetDeployKeyByID returns deploy key by given ID.
  755. func GetDeployKeyByID(id int64) (*DeployKey, error) {
  756. return getDeployKeyByID(x, id)
  757. }
  758. func getDeployKeyByID(e Engine, id int64) (*DeployKey, error) {
  759. key := new(DeployKey)
  760. has, err := e.ID(id).Get(key)
  761. if err != nil {
  762. return nil, err
  763. } else if !has {
  764. return nil, ErrDeployKeyNotExist{id, 0, 0}
  765. }
  766. return key, nil
  767. }
  768. // GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
  769. func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
  770. return getDeployKeyByRepo(x, keyID, repoID)
  771. }
  772. func getDeployKeyByRepo(e Engine, keyID, repoID int64) (*DeployKey, error) {
  773. key := &DeployKey{
  774. KeyID: keyID,
  775. RepoID: repoID,
  776. }
  777. has, err := e.Get(key)
  778. if err != nil {
  779. return nil, err
  780. } else if !has {
  781. return nil, ErrDeployKeyNotExist{0, keyID, repoID}
  782. }
  783. return key, nil
  784. }
  785. // UpdateDeployKeyCols updates deploy key information in the specified columns.
  786. func UpdateDeployKeyCols(key *DeployKey, cols ...string) error {
  787. _, err := x.ID(key.ID).Cols(cols...).Update(key)
  788. return err
  789. }
  790. // UpdateDeployKey updates deploy key information.
  791. func UpdateDeployKey(key *DeployKey) error {
  792. _, err := x.ID(key.ID).AllCols().Update(key)
  793. return err
  794. }
  795. // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
  796. func DeleteDeployKey(doer *User, id int64) error {
  797. sess := x.NewSession()
  798. defer sess.Close()
  799. if err := sess.Begin(); err != nil {
  800. return err
  801. }
  802. if err := deleteDeployKey(sess, doer, id); err != nil {
  803. return err
  804. }
  805. return sess.Commit()
  806. }
  807. func deleteDeployKey(sess Engine, doer *User, id int64) error {
  808. key, err := getDeployKeyByID(sess, id)
  809. if err != nil {
  810. if IsErrDeployKeyNotExist(err) {
  811. return nil
  812. }
  813. return fmt.Errorf("GetDeployKeyByID: %v", err)
  814. }
  815. // Check if user has access to delete this key.
  816. if !doer.IsAdmin {
  817. repo, err := getRepositoryByID(sess, key.RepoID)
  818. if err != nil {
  819. return fmt.Errorf("GetRepositoryByID: %v", err)
  820. }
  821. has, err := isUserRepoAdmin(sess, repo, doer)
  822. if err != nil {
  823. return fmt.Errorf("GetUserRepoPermission: %v", err)
  824. } else if !has {
  825. return ErrKeyAccessDenied{doer.ID, key.ID, "deploy"}
  826. }
  827. }
  828. if _, err = sess.ID(key.ID).Delete(new(DeployKey)); err != nil {
  829. return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err)
  830. }
  831. // Check if this is the last reference to same key content.
  832. has, err := sess.
  833. Where("key_id = ?", key.KeyID).
  834. Get(new(DeployKey))
  835. if err != nil {
  836. return err
  837. } else if !has {
  838. if err = deletePublicKeys(sess, key.KeyID); err != nil {
  839. return err
  840. }
  841. // after deleted the public keys, should rewrite the public keys file
  842. if err = rewriteAllPublicKeys(sess); err != nil {
  843. return err
  844. }
  845. }
  846. return nil
  847. }
  848. // ListDeployKeys returns all deploy keys by given repository ID.
  849. func ListDeployKeys(repoID int64, listOptions ListOptions) ([]*DeployKey, error) {
  850. return listDeployKeys(x, repoID, listOptions)
  851. }
  852. func listDeployKeys(e Engine, repoID int64, listOptions ListOptions) ([]*DeployKey, error) {
  853. sess := e.Where("repo_id = ?", repoID)
  854. if listOptions.Page != 0 {
  855. sess = listOptions.setSessionPagination(sess)
  856. keys := make([]*DeployKey, 0, listOptions.PageSize)
  857. return keys, sess.Find(&keys)
  858. }
  859. keys := make([]*DeployKey, 0, 5)
  860. return keys, sess.Find(&keys)
  861. }
  862. // SearchDeployKeys returns a list of deploy keys matching the provided arguments.
  863. func SearchDeployKeys(repoID int64, keyID int64, fingerprint string) ([]*DeployKey, error) {
  864. keys := make([]*DeployKey, 0, 5)
  865. cond := builder.NewCond()
  866. if repoID != 0 {
  867. cond = cond.And(builder.Eq{"repo_id": repoID})
  868. }
  869. if keyID != 0 {
  870. cond = cond.And(builder.Eq{"key_id": keyID})
  871. }
  872. if fingerprint != "" {
  873. cond = cond.And(builder.Eq{"fingerprint": fingerprint})
  874. }
  875. return keys, x.Where(cond).Find(&keys)
  876. }