github.com/argoproj/argo-events@v1.9.1/eventsources/common/webhook/webhook.go (about) 1 /* 2 Copyright 2018 BlackRock, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package webhook 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "strings" 24 25 "github.com/gorilla/mux" 26 "go.uber.org/zap" 27 28 "github.com/argoproj/argo-events/common" 29 "github.com/argoproj/argo-events/common/logging" 30 eventsourcecommon "github.com/argoproj/argo-events/eventsources/common" 31 metrics "github.com/argoproj/argo-events/metrics" 32 "github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1" 33 ) 34 35 // NewController returns a webhook controller 36 func NewController() *Controller { 37 return &Controller{ 38 AllRoutes: make(map[string]*mux.Route), 39 ActiveServerHandlers: make(map[string]*mux.Router), 40 RouteActivateChan: make(chan Router), 41 RouteDeactivateChan: make(chan Router), 42 } 43 } 44 45 // NewRoute returns a vanilla route 46 func NewRoute(hookContext *v1alpha1.WebhookContext, logger *zap.SugaredLogger, eventSourceName, eventName string, metrics *metrics.Metrics) *Route { 47 return &Route{ 48 Context: hookContext, 49 Logger: logger, 50 EventSourceName: eventSourceName, 51 EventName: eventName, 52 Active: false, 53 DataCh: make(chan []byte), 54 StartCh: make(chan struct{}), 55 StopChan: make(chan struct{}), 56 Metrics: metrics, 57 } 58 } 59 60 // ProcessRouteStatus processes route status as active and inactive. 61 func ProcessRouteStatus(ctrl *Controller) { 62 for { 63 select { 64 case router := <-ctrl.RouteActivateChan: 65 // start server if it has not been started on this port 66 startServer(router, ctrl) 67 // to allow route process incoming requests 68 router.GetRoute().StartCh <- struct{}{} 69 70 case router := <-ctrl.RouteDeactivateChan: 71 router.GetRoute().Active = false 72 } 73 } 74 } 75 76 // starts a http server 77 func startServer(router Router, controller *Controller) { 78 // start a http server only if no other configuration previously started the server on given port 79 Lock.Lock() 80 route := router.GetRoute() 81 if _, ok := controller.ActiveServerHandlers[route.Context.Port]; !ok { 82 handler := mux.NewRouter() 83 server := &http.Server{ 84 Addr: fmt.Sprintf(":%s", route.Context.Port), 85 Handler: handler, 86 } 87 88 controller.ActiveServerHandlers[route.Context.Port] = handler 89 90 // start http server 91 go func() { 92 switch { 93 case route.Context.ServerCertSecret != nil && route.Context.ServerKeySecret != nil: 94 certPath, err := common.GetSecretVolumePath(route.Context.ServerCertSecret) 95 if err != nil { 96 route.Logger.Errorw("failed to get cert path in mounted volume", "error", err) 97 return 98 } 99 keyPath, err := common.GetSecretVolumePath(route.Context.ServerKeySecret) 100 if err != nil { 101 route.Logger.Errorw("failed to get key path in mounted volume", "error", err) 102 return 103 } 104 err = server.ListenAndServeTLS(certPath, keyPath) 105 if err != nil { 106 route.Logger.With("port", route.Context.Port).Errorw("failed to listen and serve with TLS configured", zap.Error(err)) 107 } 108 default: 109 err := server.ListenAndServe() 110 if err != nil { 111 route.Logger.With("port", route.Context.Port).Errorw("failed to listen and serve", zap.Error(err)) 112 } 113 } 114 }() 115 } 116 117 handler := controller.ActiveServerHandlers[route.Context.Port] 118 119 routeName := route.Context.Port + route.Context.Endpoint 120 r := handler.GetRoute(routeName) 121 if r == nil { 122 r = handler.NewRoute().Name(routeName) 123 r = r.Path(route.Context.Endpoint) 124 r.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { 125 if route.Context.AuthSecret != nil { 126 token, err := common.GetSecretFromVolume(route.Context.AuthSecret) 127 if err != nil { 128 route.Logger.Errorw("failed to get auth secret from volume", "error", err) 129 common.SendInternalErrorResponse(writer, "Error loading auth token") 130 return 131 } 132 authHeader := request.Header.Get("Authorization") 133 if !strings.HasPrefix(authHeader, "Bearer ") { 134 route.Logger.Error("invalid auth header") 135 common.SendResponse(writer, http.StatusUnauthorized, "Invalid Authorization Header") 136 return 137 } 138 if strings.TrimPrefix(authHeader, "Bearer ") != token { 139 route.Logger.Error("invalid auth token") 140 common.SendResponse(writer, http.StatusUnauthorized, "Invalid Auth token") 141 return 142 } 143 } 144 if request.Header.Get("Authorization") != "" { 145 // Auth secret stops here 146 request.Header.Set("Authorization", "*** Masked Auth Secret ***") 147 } 148 router.HandleRoute(writer, request) 149 }) 150 } 151 152 healthCheckRouteName := route.Context.Port + "/health" 153 healthCheckRoute := handler.GetRoute(healthCheckRouteName) 154 if healthCheckRoute == nil { 155 healthCheckRoute = handler.NewRoute().Name(healthCheckRouteName) 156 healthCheckRoute = healthCheckRoute.Path("/health") 157 healthCheckRoute.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { 158 common.SendSuccessResponse(writer, "OK") 159 }) 160 } 161 162 Lock.Unlock() 163 } 164 165 // activateRoute activates a route to process incoming requests 166 func activateRoute(router Router, controller *Controller) { 167 route := router.GetRoute() 168 // change status of route as a active route 169 controller.RouteActivateChan <- router 170 171 // wait for any route to become ready 172 // if this is the first route that is added for a server, then controller will 173 // start a http server before marking the route as ready 174 <-route.StartCh 175 176 route.Active = true 177 route.Logger.With(logging.LabelPort, route.Context.Port, logging.LabelEndpoint, route.Context.Endpoint).Info("route is activated") 178 } 179 180 // manageRouteChannels consumes data from route's data channel and stops the processing when the event source is stopped/removed 181 func manageRouteChannels(router Router, dispatch func([]byte, ...eventsourcecommon.Option) error) { 182 route := router.GetRoute() 183 logger := route.Logger 184 for { 185 select { 186 case data := <-route.DataCh: 187 logger.Info("new event received, dispatching it...") 188 if err := dispatch(data); err != nil { 189 logger.Errorw("failed to send event", zap.Error(err)) 190 route.Metrics.EventProcessingFailed(route.EventSourceName, route.EventName) 191 continue 192 } 193 194 case <-route.StopChan: 195 logger.Info("event source is stopped") 196 return 197 } 198 } 199 } 200 201 // ManagerRoute manages the lifecycle of a route 202 func ManageRoute(ctx context.Context, router Router, controller *Controller, dispatch func([]byte, ...eventsourcecommon.Option) error) error { 203 route := router.GetRoute() 204 205 logger := route.Logger 206 207 // in order to process a route, it needs to go through 208 // 1. validation - basic configuration checks 209 // 2. activation - associate http handler if not done previously 210 // 3. post start operations - operations that must be performed after route has been activated and ready to process requests 211 // 4. consume data from route's data channel 212 // 5. post stop operations - operations that must be performed after route is inactivated 213 214 logger.Info("validating the route...") 215 if err := validateRoute(router.GetRoute()); err != nil { 216 logger.Error("route is invalid, won't initialize it", zap.Error(err)) 217 return err 218 } 219 220 logger.Info("listening to payloads for the route...") 221 go manageRouteChannels(router, dispatch) 222 223 defer func() { 224 route.StopChan <- struct{}{} 225 }() 226 227 logger.Info("activating the route...") 228 activateRoute(router, controller) 229 230 logger.Info("running operations post route activation...") 231 if err := router.PostActivate(); err != nil { 232 logger.Errorw("error occurred while performing post route activation operations", zap.Error(err)) 233 return err 234 } 235 236 <-ctx.Done() 237 logger.Info("connection is closed by client") 238 239 logger.Info("marking route as inactive") 240 controller.RouteDeactivateChan <- router 241 242 logger.Info("running operations post route inactivation...") 243 if err := router.PostInactivate(); err != nil { 244 logger.Errorw("error occurred while running operations post route inactivation", zap.Error(err)) 245 } 246 247 return nil 248 }