github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/api/middleware/middleware.go (about)

     1  // Copyright 2022 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package middleware
    15  
    16  import (
    17  	"net/http"
    18  	"time"
    19  
    20  	"github.com/gin-gonic/gin"
    21  	"github.com/pingcap/log"
    22  	"github.com/pingcap/tiflow/cdc/api"
    23  	"github.com/pingcap/tiflow/cdc/capture"
    24  	"github.com/pingcap/tiflow/cdc/model"
    25  	"github.com/pingcap/tiflow/pkg/config"
    26  	"github.com/pingcap/tiflow/pkg/errors"
    27  	cerror "github.com/pingcap/tiflow/pkg/errors"
    28  	"github.com/pingcap/tiflow/pkg/upstream"
    29  	"go.uber.org/zap"
    30  )
    31  
    32  // ClientVersionHeader is the header name of client version
    33  const ClientVersionHeader = "X-client-version"
    34  
    35  // LogMiddleware logs the api requests
    36  func LogMiddleware() gin.HandlerFunc {
    37  	return func(c *gin.Context) {
    38  		start := time.Now()
    39  		path := c.Request.URL.Path
    40  		query := c.Request.URL.RawQuery
    41  		user, _, _ := c.Request.BasicAuth()
    42  		c.Next()
    43  
    44  		cost := time.Since(start)
    45  
    46  		err := c.Errors.Last()
    47  		var stdErr error
    48  		if err != nil {
    49  			stdErr = err.Err
    50  		}
    51  		version := c.Request.Header.Get(ClientVersionHeader)
    52  		log.Info("cdc open api request",
    53  			zap.Int("status", c.Writer.Status()),
    54  			zap.String("method", c.Request.Method),
    55  			zap.String("path", path),
    56  			zap.String("query", query),
    57  			zap.String("ip", c.ClientIP()),
    58  			zap.String("user-agent", c.Request.UserAgent()), zap.String("client-version", version),
    59  			zap.String("username", user),
    60  			zap.Error(stdErr),
    61  			zap.Duration("duration", cost),
    62  		)
    63  	}
    64  }
    65  
    66  // ErrorHandleMiddleware puts the error into response
    67  func ErrorHandleMiddleware() gin.HandlerFunc {
    68  	return func(c *gin.Context) {
    69  		c.Next()
    70  		// because we will return immediately after an error occurs in http_handler
    71  		// there wil be only one error in c.Errors
    72  		lastError := c.Errors.Last()
    73  		if lastError != nil {
    74  			err := lastError.Err
    75  			// put the error into response
    76  			if api.IsHTTPBadRequestError(err) {
    77  				c.IndentedJSON(http.StatusBadRequest, model.NewHTTPError(err))
    78  			} else {
    79  				c.IndentedJSON(http.StatusInternalServerError, model.NewHTTPError(err))
    80  			}
    81  			c.Abort()
    82  			return
    83  		}
    84  	}
    85  }
    86  
    87  // ForwardToControllerMiddleware forward a request to controller if current server
    88  // is not controller, or handle it locally.
    89  func ForwardToControllerMiddleware(p capture.Capture) gin.HandlerFunc {
    90  	return func(ctx *gin.Context) {
    91  		if !p.IsController() {
    92  			api.ForwardToController(ctx, p)
    93  
    94  			// Without calling Abort(), Gin will continue to process the next handler,
    95  			// execute code which should only be run by the owner, and cause a panic.
    96  			// See https://github.com/pingcap/tiflow/issues/5888
    97  			ctx.Abort()
    98  			return
    99  		}
   100  		ctx.Next()
   101  	}
   102  }
   103  
   104  // ForwardToChangefeedOwnerMiddleware forward a request to controller if current server
   105  // is not the changefeed owner, or handle it locally.
   106  func ForwardToChangefeedOwnerMiddleware(p capture.Capture,
   107  	changefeedIDFunc func(ctx *gin.Context) model.ChangeFeedID,
   108  ) gin.HandlerFunc {
   109  	return func(ctx *gin.Context) {
   110  		changefeedID := changefeedIDFunc(ctx)
   111  		// check if this capture is the changefeed owner
   112  		if handleRequestIfIsChnagefeedOwner(ctx, p, changefeedID) {
   113  			return
   114  		}
   115  
   116  		// forward to the controller to find the changefeed owner capture
   117  		if !p.IsController() {
   118  			api.ForwardToController(ctx, p)
   119  			// Without calling Abort(), Gin will continue to process the next handler,
   120  			// execute code which should only be run by the owner, and cause a panic.
   121  			// See https://github.com/pingcap/tiflow/issues/5888
   122  			ctx.Abort()
   123  			return
   124  		}
   125  
   126  		controller, err := p.GetController()
   127  		if err != nil {
   128  			_ = ctx.Error(err)
   129  			ctx.Abort()
   130  			return
   131  		}
   132  		// controller check if the changefeed is exists, so we don't need to forward again
   133  		ok, err := controller.IsChangefeedExists(ctx, changefeedID)
   134  		if err != nil {
   135  			_ = ctx.Error(err)
   136  			ctx.Abort()
   137  			return
   138  		}
   139  		if !ok {
   140  			_ = ctx.Error(cerror.ErrChangeFeedNotExists.GenWithStackByArgs(changefeedID))
   141  			ctx.Abort()
   142  			return
   143  		}
   144  
   145  		info, err := p.Info()
   146  		if err != nil {
   147  			_ = ctx.Error(err)
   148  			ctx.Abort()
   149  			return
   150  		}
   151  		changefeedCaptureOwner := controller.GetChangefeedOwnerCaptureInfo(changefeedID)
   152  		if changefeedCaptureOwner.ID == info.ID {
   153  			log.Warn("changefeed owner is the same as controller",
   154  				zap.String("captureID", info.ID))
   155  			return
   156  		}
   157  		api.ForwardToCapture(ctx, info.ID, changefeedCaptureOwner.AdvertiseAddr)
   158  		ctx.Abort()
   159  	}
   160  }
   161  
   162  func handleRequestIfIsChnagefeedOwner(ctx *gin.Context, p capture.Capture, changefeedID model.ChangeFeedID) bool {
   163  	// currently not only controller capture has the owner, remove this check in the future
   164  	if p.StatusProvider() != nil {
   165  		ok, err := p.StatusProvider().IsChangefeedOwner(ctx, changefeedID)
   166  		if err != nil {
   167  			_ = ctx.Error(err)
   168  			return true
   169  		}
   170  		// this capture is the changefeed owner's capture, handle this request directly
   171  		if ok {
   172  			ctx.Next()
   173  			return true
   174  		}
   175  	}
   176  	return false
   177  }
   178  
   179  // CheckServerReadyMiddleware checks if the server is ready
   180  func CheckServerReadyMiddleware(capture capture.Capture) gin.HandlerFunc {
   181  	return func(c *gin.Context) {
   182  		if capture.IsReady() {
   183  			c.Next()
   184  		} else {
   185  			c.IndentedJSON(http.StatusServiceUnavailable,
   186  				model.NewHTTPError(errors.ErrServerIsNotReady))
   187  			c.Abort()
   188  			return
   189  		}
   190  	}
   191  }
   192  
   193  // AuthenticateMiddleware authenticates the request by query upstream TiDB.
   194  func AuthenticateMiddleware(capture capture.Capture) gin.HandlerFunc {
   195  	return func(ctx *gin.Context) {
   196  		serverCfg := config.GetGlobalServerConfig()
   197  		if serverCfg.Security.ClientUserRequired {
   198  			up, err := getUpstream(capture)
   199  			if err != nil {
   200  				_ = ctx.Error(err)
   201  				ctx.Abort()
   202  				return
   203  			}
   204  
   205  			if err := verify(ctx, up); err != nil {
   206  				ctx.IndentedJSON(http.StatusUnauthorized, model.NewHTTPError(err))
   207  				ctx.Abort()
   208  				return
   209  			}
   210  		}
   211  		ctx.Next()
   212  	}
   213  }
   214  
   215  func getUpstream(capture capture.Capture) (*upstream.Upstream, error) {
   216  	m, err := capture.GetUpstreamManager()
   217  	if err != nil {
   218  		return nil, errors.Trace(err)
   219  	}
   220  	return m.GetDefaultUpstream()
   221  }
   222  
   223  func verify(ctx *gin.Context, up *upstream.Upstream) error {
   224  	// get the username and password from the authorization header
   225  	username, password, ok := ctx.Request.BasicAuth()
   226  	if !ok {
   227  		errMsg := "please specify the user and password via authorization header"
   228  		return errors.ErrCredentialNotFound.GenWithStackByArgs(errMsg)
   229  	}
   230  
   231  	allowed := false
   232  	serverCfg := config.GetGlobalServerConfig()
   233  	for _, user := range serverCfg.Security.ClientAllowedUser {
   234  		if user == username {
   235  			allowed = true
   236  			break
   237  		}
   238  	}
   239  	if !allowed {
   240  		errMsg := "The user is not allowed."
   241  		if username == "" {
   242  			errMsg = "Empty username is not allowed."
   243  		}
   244  		return errors.ErrUnauthorized.GenWithStackByArgs(username, errMsg)
   245  	}
   246  	if err := up.VerifyTiDBUser(ctx, username, password); err != nil {
   247  		return errors.ErrUnauthorized.GenWithStackByArgs(username, err.Error())
   248  	}
   249  	return nil
   250  }