Skip to content

Commit

Permalink
backport Refactor LFS SSH and internal routers (go-gitea#32473)
Browse files Browse the repository at this point in the history
  • Loading branch information
wxiaoguang committed Nov 12, 2024
1 parent b48df10 commit 5218b78
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 15 deletions.
5 changes: 4 additions & 1 deletion models/migrations/v1_21/v276.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/git"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"

"xorm.io/xorm"
)
Expand Down Expand Up @@ -163,7 +164,9 @@ func migratePushMirrors(x *xorm.Engine) error {

func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git")

if exist, _ := util.IsExist(repoPath); !exist {
return "", nil
}
remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName)
if err != nil {
return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
Expand Down
5 changes: 2 additions & 3 deletions modules/git/batch_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,8 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
}

// ReadBatchLine reads the header line from cat-file --batch
// We expect:
// <sha> SP <type> SP <size> LF
// sha is a hex encoded here
// We expect: <oid> SP <type> SP <size> LF
// then leaving the rest of the stream "<contents> LF" to be read
func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err error) {
typ, err = rd.ReadString('\n')
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion modules/private/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Ensure you are running in the correct environment or set the correct configurati
req := httplib.NewRequest(url, method).
SetContext(ctx).
Header("X-Real-IP", getClientIP()).
Header("Authorization", fmt.Sprintf("Bearer %s", setting.InternalToken)).
Header("X-Gitea-Internal-Auth", fmt.Sprintf("Bearer %s", setting.InternalToken)).
SetTLSClientConfig(&tls.Config{
InsecureSkipVerify: true,
ServerName: setting.Domain,
Expand Down
13 changes: 11 additions & 2 deletions modules/web/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package web

import (
"net/http"
"reflect"
"strings"

"code.gitea.io/gitea/modules/web/middleware"
Expand Down Expand Up @@ -80,15 +81,23 @@ func (r *Route) getPattern(pattern string) string {
return strings.TrimSuffix(newPattern, "/")
}

func isNilOrFuncNil(v any) bool {
if v == nil {
return true
}
r := reflect.ValueOf(v)
return r.Kind() == reflect.Func && r.IsNil()
}

func (r *Route) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Handler, http.HandlerFunc) {
handlerProviders := make([]func(http.Handler) http.Handler, 0, len(r.curMiddlewares)+len(h)+1)
for _, m := range r.curMiddlewares {
if m != nil {
if !isNilOrFuncNil(m) {
handlerProviders = append(handlerProviders, toHandlerProvider(m))
}
}
for _, m := range h {
if h != nil {
if !isNilOrFuncNil(m) {
handlerProviders = append(handlerProviders, toHandlerProvider(m))
}
}
Expand Down
18 changes: 10 additions & 8 deletions routers/private/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package private

import (
"crypto/subtle"
"net/http"
"strings"

Expand All @@ -18,22 +19,23 @@ import (
chi_middleware "github.com/go-chi/chi/v5/middleware"
)

// CheckInternalToken check internal token is set
func CheckInternalToken(next http.Handler) http.Handler {
func authInternal(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
tokens := req.Header.Get("Authorization")
fields := strings.SplitN(tokens, " ", 2)
if setting.InternalToken == "" {
log.Warn(`The INTERNAL_TOKEN setting is missing from the configuration file: %q, internal API can't work.`, setting.CustomConf)
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return
}
if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken {

tokens := req.Header.Get("X-Gitea-Internal-Auth") // TODO: use something like JWT or HMAC to avoid passing the token in the clear
after, found := strings.CutPrefix(tokens, "Bearer ")
authSucceeded := found && subtle.ConstantTimeCompare([]byte(after), []byte(setting.InternalToken)) == 1
if !authSucceeded {
log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens)
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
} else {
next.ServeHTTP(w, req)
return
}
next.ServeHTTP(w, req)
})
}

Expand All @@ -51,7 +53,7 @@ func bind[T any](_ T) any {
func Routes() *web.Route {
r := web.NewRoute()
r.Use(context.PrivateContexter())
r.Use(CheckInternalToken)
r.Use(authInternal)
// Log the real ip address of the request from SSH is really helpful for diagnosing sometimes.
// Since internal API will be sent only from Gitea sub commands and it's under control (checked by InternalToken), we can trust the headers.
r.Use(chi_middleware.RealIP)
Expand Down

0 comments on commit 5218b78

Please sign in to comment.