code.gitea.io/gitea@v1.21.7/routers/api/actions/runner/interceptor.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package runner
     5  
     6  import (
     7  	"context"
     8  	"crypto/subtle"
     9  	"errors"
    10  	"strings"
    11  
    12  	actions_model "code.gitea.io/gitea/models/actions"
    13  	auth_model "code.gitea.io/gitea/models/auth"
    14  	"code.gitea.io/gitea/modules/log"
    15  	"code.gitea.io/gitea/modules/timeutil"
    16  	"code.gitea.io/gitea/modules/util"
    17  
    18  	"github.com/bufbuild/connect-go"
    19  	"google.golang.org/grpc/codes"
    20  	"google.golang.org/grpc/status"
    21  )
    22  
    23  const (
    24  	uuidHeaderKey  = "x-runner-uuid"
    25  	tokenHeaderKey = "x-runner-token"
    26  	// Deprecated: will be removed after Gitea 1.20 released.
    27  	versionHeaderKey = "x-runner-version"
    28  )
    29  
    30  var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unaryFunc connect.UnaryFunc) connect.UnaryFunc {
    31  	return func(ctx context.Context, request connect.AnyRequest) (connect.AnyResponse, error) {
    32  		methodName := getMethodName(request)
    33  		if methodName == "Register" {
    34  			return unaryFunc(ctx, request)
    35  		}
    36  		uuid := request.Header().Get(uuidHeaderKey)
    37  		token := request.Header().Get(tokenHeaderKey)
    38  		// TODO: version will be removed from request header after Gitea 1.20 released.
    39  		// And Gitea will not try to read version from reuqest header
    40  		version := request.Header().Get(versionHeaderKey)
    41  
    42  		runner, err := actions_model.GetRunnerByUUID(ctx, uuid)
    43  		if err != nil {
    44  			if errors.Is(err, util.ErrNotExist) {
    45  				return nil, status.Error(codes.Unauthenticated, "unregistered runner")
    46  			}
    47  			return nil, status.Error(codes.Internal, err.Error())
    48  		}
    49  		if subtle.ConstantTimeCompare([]byte(runner.TokenHash), []byte(auth_model.HashToken(token, runner.TokenSalt))) != 1 {
    50  			return nil, status.Error(codes.Unauthenticated, "unregistered runner")
    51  		}
    52  
    53  		cols := []string{"last_online"}
    54  
    55  		// TODO: version will be removed from request header after Gitea 1.20 released.
    56  		// And Gitea will not try to read version from reuqest header
    57  		version, _ = util.SplitStringAtByteN(version, 64)
    58  		if !util.IsEmptyString(version) && runner.Version != version {
    59  			runner.Version = version
    60  			cols = append(cols, "version")
    61  		}
    62  		runner.LastOnline = timeutil.TimeStampNow()
    63  		if methodName == "UpdateTask" || methodName == "UpdateLog" {
    64  			runner.LastActive = timeutil.TimeStampNow()
    65  			cols = append(cols, "last_active")
    66  		}
    67  		if err := actions_model.UpdateRunner(ctx, runner, cols...); err != nil {
    68  			log.Error("can't update runner status: %v", err)
    69  		}
    70  
    71  		ctx = context.WithValue(ctx, runnerCtxKey{}, runner)
    72  		return unaryFunc(ctx, request)
    73  	}
    74  }))
    75  
    76  func getMethodName(req connect.AnyRequest) string {
    77  	splits := strings.Split(req.Spec().Procedure, "/")
    78  	if len(splits) > 0 {
    79  		return splits[len(splits)-1]
    80  	}
    81  	return ""
    82  }
    83  
    84  type runnerCtxKey struct{}
    85  
    86  func GetRunner(ctx context.Context) *actions_model.ActionRunner {
    87  	if v := ctx.Value(runnerCtxKey{}); v != nil {
    88  		if r, ok := v.(*actions_model.ActionRunner); ok {
    89  			return r
    90  		}
    91  	}
    92  	return nil
    93  }