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.

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