Skip to content

Commit 2eeae84

Browse files
authored
Add internal routes for ssh hook comands (#1471)
* add internal routes for ssh hook comands * fix lint * add comment on why package named private not internal but the route name is internal * add comment above package private why package named private not internal but the route name is internal * remove exp time on internal access * move routes from /internal to /api/internal * add comment and defer on UpdatePublicKeyUpdated
1 parent f42ec61 commit 2eeae84

File tree

7 files changed

+161
-12
lines changed

7 files changed

+161
-12
lines changed

cmd/serv.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"code.gitea.io/gitea/models"
1818
"code.gitea.io/gitea/modules/log"
19+
"code.gitea.io/gitea/modules/private"
1920
"code.gitea.io/gitea/modules/setting"
2021

2122
"github.com/Unknwon/com"
@@ -318,7 +319,7 @@ func runServ(c *cli.Context) error {
318319

319320
// Update user key activity.
320321
if keyID > 0 {
321-
if err = models.UpdatePublicKeyUpdated(keyID); err != nil {
322+
if err = private.UpdatePublicKeyUpdated(keyID); err != nil {
322323
fail("Internal error", "UpdatePublicKey: %v", err)
323324
}
324325
}

cmd/web.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
apiv1 "code.gitea.io/gitea/routers/api/v1"
3030
"code.gitea.io/gitea/routers/dev"
3131
"code.gitea.io/gitea/routers/org"
32+
"code.gitea.io/gitea/routers/private"
3233
"code.gitea.io/gitea/routers/repo"
3334
"code.gitea.io/gitea/routers/user"
3435

@@ -661,6 +662,11 @@ func runWeb(ctx *cli.Context) error {
661662
apiv1.RegisterRoutes(m)
662663
}, ignSignIn)
663664

665+
m.Group("/api/internal", func() {
666+
// package name internal is ideal but Golang is not allowed, so we use private as package name.
667+
private.RegisterRoutes(m)
668+
})
669+
664670
// robots.txt
665671
m.Get("/robots.txt", func(ctx *context.Context) {
666672
if setting.HasRobotsTxt {

models/ssh_key.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,8 +502,10 @@ func UpdatePublicKey(key *PublicKey) error {
502502

503503
// UpdatePublicKeyUpdated updates public key use time.
504504
func UpdatePublicKeyUpdated(id int64) error {
505-
cnt, err := x.ID(id).Cols("updated").Update(&PublicKey{
506-
Updated: time.Now(),
505+
now := time.Now()
506+
cnt, err := x.ID(id).Cols("updated_unix").Update(&PublicKey{
507+
Updated: now,
508+
UpdatedUnix: now.Unix(),
507509
})
508510
if err != nil {
509511
return err

modules/httplib/httplib.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ func newRequest(url, method string) *Request {
6262
return &Request{url, &req, map[string]string{}, map[string]string{}, defaultSetting, &resp, nil}
6363
}
6464

65+
// NewRequest returns *Request with specific method
66+
func NewRequest(url, method string) *Request {
67+
return newRequest(url, method)
68+
}
69+
6570
// Get returns *Request with GET method.
6671
func Get(url string) *Request {
6772
return newRequest(url, "GET")

modules/private/internal.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package private
2+
3+
import (
4+
"crypto/tls"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
9+
"code.gitea.io/gitea/modules/httplib"
10+
"code.gitea.io/gitea/modules/log"
11+
"code.gitea.io/gitea/modules/setting"
12+
)
13+
14+
func newRequest(url, method string) *httplib.Request {
15+
return httplib.NewRequest(url, method).Header("Authorization",
16+
fmt.Sprintf("Bearer %s", setting.InternalToken))
17+
}
18+
19+
// Response internal request response
20+
type Response struct {
21+
Err string `json:"err"`
22+
}
23+
24+
func decodeJSONError(resp *http.Response) *Response {
25+
var res Response
26+
err := json.NewDecoder(resp.Body).Decode(&res)
27+
if err != nil {
28+
res.Err = err.Error()
29+
}
30+
return &res
31+
}
32+
33+
// UpdatePublicKeyUpdated update publick key updates
34+
func UpdatePublicKeyUpdated(keyID int64) error {
35+
// Ask for running deliver hook and test pull request tasks.
36+
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update", keyID)
37+
log.GitLogger.Trace("UpdatePublicKeyUpdated: %s", reqURL)
38+
39+
resp, err := newRequest(reqURL, "POST").SetTLSClientConfig(&tls.Config{
40+
InsecureSkipVerify: true,
41+
}).Response()
42+
if err != nil {
43+
return err
44+
}
45+
46+
defer resp.Body.Close()
47+
48+
// All 2XX status codes are accepted and others will return an error
49+
if resp.StatusCode/100 != 2 {
50+
return fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err)
51+
}
52+
return nil
53+
}

modules/setting/setting.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"code.gitea.io/gitea/modules/user"
2828

2929
"github.com/Unknwon/com"
30+
"github.com/dgrijalva/jwt-go"
3031
_ "github.com/go-macaron/cache/memcache" // memcache plugin for cache
3132
_ "github.com/go-macaron/cache/redis"
3233
"github.com/go-macaron/session"
@@ -442,14 +443,15 @@ var (
442443
ShowFooterTemplateLoadTime bool
443444

444445
// Global setting objects
445-
Cfg *ini.File
446-
CustomPath string // Custom directory path
447-
CustomConf string
448-
CustomPID string
449-
ProdMode bool
450-
RunUser string
451-
IsWindows bool
452-
HasRobotsTxt bool
446+
Cfg *ini.File
447+
CustomPath string // Custom directory path
448+
CustomConf string
449+
CustomPID string
450+
ProdMode bool
451+
RunUser string
452+
IsWindows bool
453+
HasRobotsTxt bool
454+
InternalToken string // internal access token
453455
)
454456

455457
// DateLang transforms standard language locale name to corresponding value in datetime plugin.
@@ -764,6 +766,43 @@ please consider changing to GITEA_CUSTOM`)
764766
ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER")
765767
MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6)
766768
ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false)
769+
InternalToken = sec.Key("INTERNAL_TOKEN").String()
770+
if len(InternalToken) == 0 {
771+
secretBytes := make([]byte, 32)
772+
_, err := io.ReadFull(rand.Reader, secretBytes)
773+
if err != nil {
774+
log.Fatal(4, "Error reading random bytes: %v", err)
775+
}
776+
777+
secretKey := base64.RawURLEncoding.EncodeToString(secretBytes)
778+
779+
now := time.Now()
780+
InternalToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
781+
"nbf": now.Unix(),
782+
}).SignedString([]byte(secretKey))
783+
784+
if err != nil {
785+
log.Fatal(4, "Error generate internal token: %v", err)
786+
}
787+
788+
// Save secret
789+
cfgSave := ini.Empty()
790+
if com.IsFile(CustomConf) {
791+
// Keeps custom settings if there is already something.
792+
if err := cfgSave.Append(CustomConf); err != nil {
793+
log.Error(4, "Failed to load custom conf '%s': %v", CustomConf, err)
794+
}
795+
}
796+
797+
cfgSave.Section("security").Key("INTERNAL_TOKEN").SetValue(InternalToken)
798+
799+
if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
800+
log.Fatal(4, "Failed to create '%s': %v", CustomConf, err)
801+
}
802+
if err := cfgSave.SaveTo(CustomConf); err != nil {
803+
log.Fatal(4, "Error saving generated JWT Secret to custom config: %v", err)
804+
}
805+
}
767806

768807
sec = Cfg.Section("attachment")
769808
AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments"))
@@ -940,7 +979,6 @@ var Service struct {
940979
EnableOpenIDSignUp bool
941980
OpenIDWhitelist []*regexp.Regexp
942981
OpenIDBlacklist []*regexp.Regexp
943-
944982
}
945983

946984
func newService() {

routers/private/internal.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2017 The Gitea 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+
5+
// Package private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead.
6+
package private
7+
8+
import (
9+
"strings"
10+
11+
"code.gitea.io/gitea/models"
12+
"code.gitea.io/gitea/modules/setting"
13+
macaron "gopkg.in/macaron.v1"
14+
)
15+
16+
// CheckInternalToken check internal token is set
17+
func CheckInternalToken(ctx *macaron.Context) {
18+
tokens := ctx.Req.Header.Get("Authorization")
19+
fields := strings.Fields(tokens)
20+
if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken {
21+
ctx.Error(403)
22+
}
23+
}
24+
25+
// UpdatePublicKey update publick key updates
26+
func UpdatePublicKey(ctx *macaron.Context) {
27+
keyID := ctx.ParamsInt64(":id")
28+
if err := models.UpdatePublicKeyUpdated(keyID); err != nil {
29+
ctx.JSON(500, map[string]interface{}{
30+
"err": err.Error(),
31+
})
32+
return
33+
}
34+
35+
ctx.PlainText(200, []byte("success"))
36+
}
37+
38+
// RegisterRoutes registers all internal APIs routes to web application.
39+
// These APIs will be invoked by internal commands for example `gitea serv` and etc.
40+
func RegisterRoutes(m *macaron.Macaron) {
41+
m.Group("/", func() {
42+
m.Post("/ssh/:id/update", UpdatePublicKey)
43+
}, CheckInternalToken)
44+
}

0 commit comments

Comments
 (0)