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:
		
							parent
							
								
									f42ec6120e
								
							
						
					
					
						commit
						2eeae84cbd
					
				
					 7 changed files with 161 additions and 12 deletions
				
			
		|  | @ -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") | ||||
|  |  | |||
							
								
								
									
										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" | ||||
| 
 | ||||
| 	"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" | ||||
|  | @ -450,6 +451,7 @@ var ( | |||
| 	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() { | ||||
|  |  | |||
							
								
								
									
										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) | ||||
| } | ||||
		Loading…
	
		Reference in a new issue