* 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/modules/log" | |||
"code.gitea.io/gitea/modules/private" | |||
"code.gitea.io/gitea/modules/setting" | |||
"github.com/Unknwon/com" | |||
@@ -318,7 +319,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) | |||
} | |||
} | |||
@@ -29,6 +29,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" | |||
@@ -661,6 +662,11 @@ func runWeb(ctx *cli.Context) error { | |||
apiv1.RegisterRoutes(m) | |||
}, 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 | |||
m.Get("/robots.txt", func(ctx *context.Context) { | |||
if setting.HasRobotsTxt { | |||
@@ -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 | |||
@@ -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") | |||
@@ -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" | |||
"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" | |||
@@ -442,14 +443,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. | |||
@@ -764,6 +766,43 @@ 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{ | |||
"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")) | |||
@@ -940,7 +979,6 @@ var Service struct { | |||
EnableOpenIDSignUp bool | |||
OpenIDWhitelist []*regexp.Regexp | |||
OpenIDBlacklist []*regexp.Regexp | |||
} | |||
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) | |||
} |