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 }