mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	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
This commit is contained in:
		| @@ -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{ | 	now := time.Now() | ||||||
| 		Updated: 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") | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								modules/private/internal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								modules/private/internal.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | 	Cfg           *ini.File | ||||||
| 	CustomPath   string // Custom directory path | 	CustomPath    string // Custom directory path | ||||||
| 	CustomConf   string | 	CustomConf    string | ||||||
| 	CustomPID    string | 	CustomPID     string | ||||||
| 	ProdMode     bool | 	ProdMode      bool | ||||||
| 	RunUser      string | 	RunUser       string | ||||||
| 	IsWindows    bool | 	IsWindows     bool | ||||||
| 	HasRobotsTxt 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() { | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								routers/private/internal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								routers/private/internal.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Lunny Xiao
					Lunny Xiao