From c015e57a980417e5f0261049bef17e2eacc2c64d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 8 Apr 2017 21:49:39 +0800 Subject: [PATCH 1/7] add internal routes for ssh hook comands --- cmd/serv.go | 3 +- cmd/web.go | 5 ++++ models/ssh_key.go | 6 ++-- modules/httplib/httplib.go | 5 ++++ modules/private/internal.go | 50 ++++++++++++++++++++++++++++++++ modules/setting/setting.go | 57 +++++++++++++++++++++++++++++++------ routers/private/internal.go | 43 ++++++++++++++++++++++++++++ 7 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 modules/private/internal.go create mode 100644 routers/private/internal.go diff --git a/cmd/serv.go b/cmd/serv.go index e3aeb41c5679f..1a8c49f99176e 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" "github.com/Unknwon/com" "github.com/dgrijalva/jwt-go" @@ -316,7 +317,7 @@ func runServ(c *cli.Context) error { // Update user key activity. if keyID > 0 { - if err = models.UpdatePublicKeyUpdated(keyID); err != nil { + if err = private.UpdatePublicKeyUpdated(keyID); err != nil { fail("Internal error", "UpdatePublicKey: %v", err) } } diff --git a/cmd/web.go b/cmd/web.go index b2cc3959a29e6..04b2db8872329 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -28,6 +28,7 @@ import ( apiv1 "code.gitea.io/gitea/routers/api/v1" "code.gitea.io/gitea/routers/dev" "code.gitea.io/gitea/routers/org" + "code.gitea.io/gitea/routers/private" "code.gitea.io/gitea/routers/repo" "code.gitea.io/gitea/routers/user" @@ -659,6 +660,10 @@ func runWeb(ctx *cli.Context) error { apiv1.RegisterRoutes(m) }, ignSignIn) + m.Group("/internal", func() { + private.RegisterRoutes(m) + }) + // robots.txt m.Get("/robots.txt", func(ctx *context.Context) { if setting.HasRobotsTxt { diff --git a/models/ssh_key.go b/models/ssh_key.go index 75a0120c59e46..653889e48838a 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -502,8 +502,10 @@ func UpdatePublicKey(key *PublicKey) error { // UpdatePublicKeyUpdated updates public key use time. func UpdatePublicKeyUpdated(id int64) error { - cnt, err := x.ID(id).Cols("updated").Update(&PublicKey{ - Updated: time.Now(), + now := time.Now() + cnt, err := x.ID(id).Cols("updated_unix").Update(&PublicKey{ + Updated: now, + UpdatedUnix: now.Unix(), }) if err != nil { return err diff --git a/modules/httplib/httplib.go b/modules/httplib/httplib.go index 38b55e64e4f93..f2d9a2bfaa251 100644 --- a/modules/httplib/httplib.go +++ b/modules/httplib/httplib.go @@ -62,6 +62,11 @@ func newRequest(url, method string) *Request { return &Request{url, &req, map[string]string{}, map[string]string{}, defaultSetting, &resp, nil} } +// NewRequest returns *Request with specific method +func NewRequest(url, method string) *Request { + return newRequest(url, method) +} + // Get returns *Request with GET method. func Get(url string) *Request { return newRequest(url, "GET") diff --git a/modules/private/internal.go b/modules/private/internal.go new file mode 100644 index 0000000000000..f8fa2ecf29259 --- /dev/null +++ b/modules/private/internal.go @@ -0,0 +1,50 @@ +package private + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + + "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +func newRequest(url, method string) *httplib.Request { + return httplib.NewRequest(url, method).Header("Authorization", + fmt.Sprintf("Bearer %s", setting.InternalToken)) +} + +type Response struct { + Err string `json:"err"` +} + +func decodeJSONError(resp *http.Response) *Response { + var res Response + err := json.NewDecoder(resp.Body).Decode(&res) + if err != nil { + res.Err = err.Error() + } + return &res +} + +// UpdatePublicKeyUpdated update publick key updates +func UpdatePublicKeyUpdated(keyID int64) error { + // Ask for running deliver hook and test pull request tasks. + reqURL := setting.LocalURL + fmt.Sprintf("internal/ssh/%d/update", keyID) + log.GitLogger.Trace("UpdatePublicKeyUpdated: %s", reqURL) + + resp, err := newRequest(reqURL, "POST").SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }).Response() + if err != nil { + return err + } + + resp.Body.Close() + if resp.StatusCode/100 != 2 { + return fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err) + } + return nil +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 5a0666ade2347..607c104eed43e 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -27,6 +27,7 @@ import ( "code.gitea.io/gitea/modules/user" "github.com/Unknwon/com" + "github.com/dgrijalva/jwt-go" _ "github.com/go-macaron/cache/memcache" // memcache plugin for cache _ "github.com/go-macaron/cache/redis" "github.com/go-macaron/session" @@ -443,14 +444,15 @@ var ( ShowFooterTemplateLoadTime bool // Global setting objects - Cfg *ini.File - CustomPath string // Custom directory path - CustomConf string - CustomPID string - ProdMode bool - RunUser string - IsWindows bool - HasRobotsTxt bool + Cfg *ini.File + CustomPath string // Custom directory path + CustomConf string + CustomPID string + ProdMode bool + RunUser string + IsWindows bool + HasRobotsTxt bool + InternalToken string // internal access token ) // DateLang transforms standard language locale name to corresponding value in datetime plugin. @@ -765,6 +767,44 @@ please consider changing to GITEA_CUSTOM`) ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6) ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false) + InternalToken = sec.Key("INTERNAL_TOKEN").String() + if len(InternalToken) == 0 { + secretBytes := make([]byte, 32) + _, err := io.ReadFull(rand.Reader, secretBytes) + if err != nil { + log.Fatal(4, "Error reading random bytes: %v", err) + } + + secretKey := base64.RawURLEncoding.EncodeToString(secretBytes) + + now := time.Now() + InternalToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "exp": now.Add(5 * time.Minute).Unix(), + "nbf": now.Unix(), + }).SignedString([]byte(secretKey)) + + if err != nil { + log.Fatal(4, "Error generate internal token: %v", err) + } + + // Save secret + cfgSave := ini.Empty() + if com.IsFile(CustomConf) { + // Keeps custom settings if there is already something. + if err := cfgSave.Append(CustomConf); err != nil { + log.Error(4, "Failed to load custom conf '%s': %v", CustomConf, err) + } + } + + cfgSave.Section("security").Key("INTERNAL_TOKEN").SetValue(InternalToken) + + if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil { + log.Fatal(4, "Failed to create '%s': %v", CustomConf, err) + } + if err := cfgSave.SaveTo(CustomConf); err != nil { + log.Fatal(4, "Error saving generated JWT Secret to custom config: %v", err) + } + } sec = Cfg.Section("attachment") AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) @@ -935,7 +975,6 @@ var Service struct { EnableOpenIDSignUp bool OpenIDWhitelist []*regexp.Regexp OpenIDBlacklist []*regexp.Regexp - } func newService() { diff --git a/routers/private/internal.go b/routers/private/internal.go new file mode 100644 index 0000000000000..99974df79a8ca --- /dev/null +++ b/routers/private/internal.go @@ -0,0 +1,43 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package private + +import ( + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/setting" + macaron "gopkg.in/macaron.v1" +) + +// CheckInternalToken check internal token is set +func CheckInternalToken(ctx *macaron.Context) { + tokens := ctx.Req.Header.Get("Authorization") + fields := strings.Fields(tokens) + if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken { + ctx.Error(403) + } +} + +// UpdatePublicKey update publick key updates +func UpdatePublicKey(ctx *macaron.Context) { + keyID := ctx.ParamsInt64(":id") + if err := models.UpdatePublicKeyUpdated(keyID); err != nil { + ctx.JSON(500, map[string]interface{}{ + "err": err.Error(), + }) + return + } + + ctx.PlainText(200, []byte("success")) +} + +// RegisterRoutes registers all internal APIs routes to web application. +// These APIs will be invoked by internal commands for example `gitea serv` and etc. +func RegisterRoutes(m *macaron.Macaron) { + m.Group("/", func() { + m.Post("/ssh/:id/update", UpdatePublicKey) + }, CheckInternalToken) +} From af91984b492b6418fd7448b58ddf393296bc8c3a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 8 Apr 2017 22:00:01 +0800 Subject: [PATCH 2/7] fix lint --- modules/private/internal.go | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/private/internal.go b/modules/private/internal.go index f8fa2ecf29259..3ac9c1c83222f 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -16,6 +16,7 @@ func newRequest(url, method string) *httplib.Request { fmt.Sprintf("Bearer %s", setting.InternalToken)) } +// Response internal request response type Response struct { Err string `json:"err"` } From d4570aa0c5d4041b114169ea4189cbcf648ca091 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 11 Apr 2017 19:48:39 +0800 Subject: [PATCH 3/7] add comment on why package named private not internal but the route name is internal --- cmd/web.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/web.go b/cmd/web.go index 04b2db8872329..f5fdf218585d5 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -661,6 +661,7 @@ func runWeb(ctx *cli.Context) error { }, ignSignIn) m.Group("/internal", func() { + // package name internal is ideal but Golang is not allowed, so we use private as package name. private.RegisterRoutes(m) }) From dc5980e4cb58e84b1397cf4bd5d398c20aa95912 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 11 Apr 2017 19:51:11 +0800 Subject: [PATCH 4/7] add comment above package private why package named private not internal but the route name is internal --- routers/private/internal.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/private/internal.go b/routers/private/internal.go index 99974df79a8ca..d662aa2c762eb 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. +// 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. package private import ( From c76983cdfe6149a0f83809e5dafef8fb1ed35ad7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 12 Apr 2017 14:29:57 +0800 Subject: [PATCH 5/7] remove exp time on internal access --- modules/setting/setting.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 607c104eed43e..1ad8aa333a240 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -779,7 +779,6 @@ please consider changing to GITEA_CUSTOM`) now := time.Now() InternalToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "exp": now.Add(5 * time.Minute).Unix(), "nbf": now.Unix(), }).SignedString([]byte(secretKey)) From 4f2c3a7a215d8cbac49efb1d1c12a8cae5767de8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 18 Apr 2017 14:48:51 +0800 Subject: [PATCH 6/7] move routes from /internal to /api/internal --- cmd/web.go | 2 +- modules/private/internal.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/web.go b/cmd/web.go index f5fdf218585d5..ca71a14b4ac99 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -660,7 +660,7 @@ func runWeb(ctx *cli.Context) error { apiv1.RegisterRoutes(m) }, ignSignIn) - m.Group("/internal", func() { + m.Group("/api/internal", func() { // package name internal is ideal but Golang is not allowed, so we use private as package name. private.RegisterRoutes(m) }) diff --git a/modules/private/internal.go b/modules/private/internal.go index 3ac9c1c83222f..35e0689612b89 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -33,7 +33,7 @@ func decodeJSONError(resp *http.Response) *Response { // UpdatePublicKeyUpdated update publick key updates func UpdatePublicKeyUpdated(keyID int64) error { // Ask for running deliver hook and test pull request tasks. - reqURL := setting.LocalURL + fmt.Sprintf("internal/ssh/%d/update", keyID) + reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update", keyID) log.GitLogger.Trace("UpdatePublicKeyUpdated: %s", reqURL) resp, err := newRequest(reqURL, "POST").SetTLSClientConfig(&tls.Config{ From def124132b263cfbfc8e69913033d51a284a6ad3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 19 Apr 2017 10:37:48 +0800 Subject: [PATCH 7/7] add comment and defer on UpdatePublicKeyUpdated --- modules/private/internal.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/private/internal.go b/modules/private/internal.go index 35e0689612b89..017e265b7c5b1 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -43,7 +43,9 @@ func UpdatePublicKeyUpdated(keyID int64) error { return err } - resp.Body.Close() + defer resp.Body.Close() + + // All 2XX status codes are accepted and others will return an error if resp.StatusCode/100 != 2 { return fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err) }