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  }