* 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 UpdatePublicKeyUpdatedtags/v1.21.12.1
| @@ -16,6 +16,7 @@ import ( | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
| "code.gitea.io/gitea/modules/private" | |||||
| "code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
| "github.com/Unknwon/com" | "github.com/Unknwon/com" | ||||
| @@ -318,7 +319,7 @@ func runServ(c *cli.Context) error { | |||||
| // Update user key activity. | // Update user key activity. | ||||
| if keyID > 0 { | if keyID > 0 { | ||||
| if err = models.UpdatePublicKeyUpdated(keyID); err != nil { | |||||
| if err = private.UpdatePublicKeyUpdated(keyID); err != nil { | |||||
| fail("Internal error", "UpdatePublicKey: %v", err) | fail("Internal error", "UpdatePublicKey: %v", err) | ||||
| } | } | ||||
| } | } | ||||
| @@ -29,6 +29,7 @@ import ( | |||||
| apiv1 "code.gitea.io/gitea/routers/api/v1" | apiv1 "code.gitea.io/gitea/routers/api/v1" | ||||
| "code.gitea.io/gitea/routers/dev" | "code.gitea.io/gitea/routers/dev" | ||||
| "code.gitea.io/gitea/routers/org" | "code.gitea.io/gitea/routers/org" | ||||
| "code.gitea.io/gitea/routers/private" | |||||
| "code.gitea.io/gitea/routers/repo" | "code.gitea.io/gitea/routers/repo" | ||||
| "code.gitea.io/gitea/routers/user" | "code.gitea.io/gitea/routers/user" | ||||
| @@ -661,6 +662,11 @@ func runWeb(ctx *cli.Context) error { | |||||
| apiv1.RegisterRoutes(m) | apiv1.RegisterRoutes(m) | ||||
| }, ignSignIn) | }, ignSignIn) | ||||
| 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) | |||||
| }) | |||||
| // robots.txt | // robots.txt | ||||
| m.Get("/robots.txt", func(ctx *context.Context) { | m.Get("/robots.txt", func(ctx *context.Context) { | ||||
| if setting.HasRobotsTxt { | if setting.HasRobotsTxt { | ||||
| @@ -502,8 +502,10 @@ func UpdatePublicKey(key *PublicKey) error { | |||||
| // UpdatePublicKeyUpdated updates public key use time. | // UpdatePublicKeyUpdated updates public key use time. | ||||
| func UpdatePublicKeyUpdated(id int64) error { | 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 { | if err != nil { | ||||
| return err | return err | ||||
| @@ -62,6 +62,11 @@ func newRequest(url, method string) *Request { | |||||
| return &Request{url, &req, map[string]string{}, map[string]string{}, defaultSetting, &resp, nil} | 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. | // Get returns *Request with GET method. | ||||
| func Get(url string) *Request { | func Get(url string) *Request { | ||||
| return newRequest(url, "GET") | return newRequest(url, "GET") | ||||
| @@ -0,0 +1,53 @@ | |||||
| 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)) | |||||
| } | |||||
| // Response internal request response | |||||
| 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("api/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 | |||||
| } | |||||
| 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) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| @@ -27,6 +27,7 @@ import ( | |||||
| "code.gitea.io/gitea/modules/user" | "code.gitea.io/gitea/modules/user" | ||||
| "github.com/Unknwon/com" | "github.com/Unknwon/com" | ||||
| "github.com/dgrijalva/jwt-go" | |||||
| _ "github.com/go-macaron/cache/memcache" // memcache plugin for cache | _ "github.com/go-macaron/cache/memcache" // memcache plugin for cache | ||||
| _ "github.com/go-macaron/cache/redis" | _ "github.com/go-macaron/cache/redis" | ||||
| "github.com/go-macaron/session" | "github.com/go-macaron/session" | ||||
| @@ -442,14 +443,15 @@ var ( | |||||
| ShowFooterTemplateLoadTime bool | ShowFooterTemplateLoadTime bool | ||||
| // Global setting objects | // 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. | // DateLang transforms standard language locale name to corresponding value in datetime plugin. | ||||
| @@ -764,6 +766,43 @@ please consider changing to GITEA_CUSTOM`) | |||||
| ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") | ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") | ||||
| MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6) | MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6) | ||||
| ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false) | 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{ | |||||
| "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") | sec = Cfg.Section("attachment") | ||||
| AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) | AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) | ||||
| @@ -940,7 +979,6 @@ var Service struct { | |||||
| EnableOpenIDSignUp bool | EnableOpenIDSignUp bool | ||||
| OpenIDWhitelist []*regexp.Regexp | OpenIDWhitelist []*regexp.Regexp | ||||
| OpenIDBlacklist []*regexp.Regexp | OpenIDBlacklist []*regexp.Regexp | ||||
| } | } | ||||
| func newService() { | func newService() { | ||||
| @@ -0,0 +1,44 @@ | |||||
| // 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 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 ( | |||||
| "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) | |||||
| } | |||||