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

ssh_key.go 26 kB

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