github.com/elfadel/cilium@v1.6.12/pkg/proxy/kafka.go (about)

     1  // Copyright 2017-2018 Authors of Cilium
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package proxy
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"github.com/cilium/cilium/pkg/revert"
    21  	"io"
    22  	"net"
    23  	"strconv"
    24  
    25  	"github.com/cilium/cilium/pkg/completion"
    26  	"github.com/cilium/cilium/pkg/datapath/linux/linux_defaults"
    27  	"github.com/cilium/cilium/pkg/flowdebug"
    28  	"github.com/cilium/cilium/pkg/identity"
    29  	"github.com/cilium/cilium/pkg/kafka"
    30  	"github.com/cilium/cilium/pkg/lock"
    31  	"github.com/cilium/cilium/pkg/logging/logfields"
    32  	"github.com/cilium/cilium/pkg/policy"
    33  	"github.com/cilium/cilium/pkg/policy/api"
    34  	"github.com/cilium/cilium/pkg/proxy/accesslog"
    35  	"github.com/cilium/cilium/pkg/proxy/logger"
    36  
    37  	"github.com/optiopay/kafka/proto"
    38  	"github.com/sirupsen/logrus"
    39  )
    40  
    41  const (
    42  	fieldID = "id"
    43  )
    44  
    45  // The maps holding kafkaListeners (and kafkaRedirects), as well as
    46  // the reference `count` field are protected by `mutex`. `socket` is safe
    47  // to be used from multiple goroutines and the other fields below are
    48  // immutable after initialization.
    49  type kafkaListener struct {
    50  	socket               *proxySocket
    51  	proxyPort            uint16
    52  	endpointInfoRegistry logger.EndpointInfoRegistry
    53  	ingress              bool
    54  	transparent          bool
    55  	count                int
    56  }
    57  
    58  var (
    59  	mutex          lock.RWMutex                      // mutex protects accesses to the configuration resources.
    60  	kafkaListeners = make(map[uint16]*kafkaListener) // key: proxy port
    61  	kafkaRedirects = make(map[uint64]*kafkaRedirect) // key: dst port | dir << 16 | endpoint ID << 32
    62  )
    63  
    64  func mapKey(dstPort uint16, ingress bool, eID uint16) uint64 {
    65  	var dir uint64
    66  	if ingress {
    67  		dir = 1
    68  	}
    69  	return uint64(dstPort) | dir<<16 | uint64(eID)<<32
    70  }
    71  
    72  // kafkaRedirect implements the RedirectImplementation interface
    73  // This extends the Redirect with Kafka specific state.
    74  // 'listener' is shared across multiple kafkaRedirects
    75  // 'redirect' is unique for this kafkaRedirect
    76  type kafkaRedirect struct {
    77  	listener             *kafkaListener
    78  	redirect             *Redirect
    79  	endpointInfoRegistry logger.EndpointInfoRegistry
    80  	conf                 kafkaConfiguration
    81  }
    82  
    83  type srcIDLookupFunc func(mapname, remoteAddr, localAddr string, ingress bool) (uint32, error)
    84  
    85  type kafkaConfiguration struct {
    86  	testMode    bool
    87  	lookupSrcID srcIDLookupFunc
    88  }
    89  
    90  func (l *kafkaListener) Listen() {
    91  	for {
    92  		pair, err := l.socket.Accept(true)
    93  		select {
    94  		case <-l.socket.closing:
    95  			// Don't report errors while the socket is being closed
    96  			return
    97  		default:
    98  		}
    99  
   100  		if err != nil {
   101  			log.WithField(logfields.Port, l.proxyPort).WithError(err).Error("Unable to accept connection on port")
   102  			continue
   103  		}
   104  		// Locate the redirect for this connection
   105  		endpointIPStr, dstPortStr, err := net.SplitHostPort(pair.Rx.conn.LocalAddr().String())
   106  		if err != nil {
   107  			log.WithField(logfields.Port, l.proxyPort).WithError(err).Error("No destination address")
   108  			continue
   109  		}
   110  		if !l.ingress {
   111  			// for egress EP is the source
   112  			endpointIPStr, _, err = net.SplitHostPort(pair.Rx.conn.RemoteAddr().String())
   113  			if err != nil {
   114  				log.WithField(logfields.Port, l.proxyPort).WithError(err).Error("No source address")
   115  				continue
   116  			}
   117  		}
   118  		var epinfo accesslog.EndpointInfo
   119  		if !l.endpointInfoRegistry.FillEndpointIdentityByIP(net.ParseIP(endpointIPStr), &epinfo) {
   120  			log.WithField(logfields.Port, l.proxyPort).Errorf("Can't find endpoint with IP %s", endpointIPStr)
   121  			continue
   122  		}
   123  		portInt, _ := strconv.Atoi(dstPortStr)
   124  		key := mapKey(uint16(portInt), l.ingress, uint16(epinfo.ID))
   125  		log.WithField(logfields.EndpointID, epinfo.ID).Debugf("Looking up Kafka redirect with port: %d, ingress: %v", uint16(portInt), l.ingress)
   126  
   127  		mutex.Lock()
   128  		redir, ok := kafkaRedirects[key]
   129  		mutex.Unlock()
   130  		if ok && redir != nil {
   131  			go redir.handleRequestConnection(pair)
   132  		} else {
   133  			log.WithField(logfields.Port, l.proxyPort).Error("No redirect found for accepted connection")
   134  		}
   135  	}
   136  }
   137  
   138  // createKafkaRedirect creates a redirect to the kafka proxy. The redirect structure passed
   139  // in is safe to access for reading and writing.
   140  func createKafkaRedirect(r *Redirect, conf kafkaConfiguration, endpointInfoRegistry logger.EndpointInfoRegistry) (RedirectImplementation, error) {
   141  	redir := &kafkaRedirect{
   142  		redirect:             r,
   143  		conf:                 conf,
   144  		endpointInfoRegistry: endpointInfoRegistry,
   145  	}
   146  
   147  	if redir.conf.lookupSrcID == nil {
   148  		redir.conf.lookupSrcID = lookupSrcID
   149  	}
   150  
   151  	// must register with the proxy port for unit tests (no IP_TRANSPARENT)
   152  	dstPort := r.dstPort
   153  	if conf.testMode {
   154  		dstPort = r.listener.proxyPort
   155  	}
   156  	key := mapKey(dstPort, r.listener.ingress, uint16(r.endpointID))
   157  	log.WithField(logfields.EndpointID, r.endpointID).Debugf(
   158  		"Registering %s with port: %d, ingress: %v",
   159  		r.listener.name, dstPort, r.listener.ingress)
   160  	mutex.Lock()
   161  	if _, ok := kafkaRedirects[key]; ok {
   162  		mutex.Unlock()
   163  		panic("Kafka redirect already exists for the given dst port and endpoint ID")
   164  	}
   165  	kafkaRedirects[key] = redir
   166  
   167  	// Start a listener if not already running
   168  	listener := kafkaListeners[r.listener.proxyPort]
   169  	if listener == nil {
   170  		marker := 0
   171  		if !conf.testMode {
   172  			marker = linux_defaults.GetMagicProxyMark(r.listener.ingress, 0)
   173  		}
   174  
   175  		// Listen needs to be in the synchronous part of this function to ensure that
   176  		// the proxy port is never refusing connections.
   177  		socket, err := listenSocket(fmt.Sprintf(":%d", r.listener.proxyPort), marker, !conf.testMode)
   178  		if err != nil {
   179  			delete(kafkaRedirects, key)
   180  			mutex.Unlock()
   181  			return nil, err
   182  		}
   183  		listener = &kafkaListener{
   184  			socket:               socket,
   185  			proxyPort:            r.listener.proxyPort,
   186  			endpointInfoRegistry: endpointInfoRegistry,
   187  			ingress:              r.listener.ingress,
   188  			transparent:          !conf.testMode,
   189  			count:                0,
   190  		}
   191  
   192  		go listener.Listen()
   193  
   194  		kafkaListeners[r.listener.proxyPort] = listener
   195  	}
   196  	listener.count++
   197  	redir.listener = listener
   198  	mutex.Unlock()
   199  
   200  	return redir, nil
   201  }
   202  
   203  // canAccess determines if the kafka message req sent by identity is allowed to
   204  // be forwarded according to the rules configured on kafkaRedirect
   205  func (k *kafkaRedirect) canAccess(req *kafka.RequestMessage, srcIdentity identity.NumericIdentity) bool {
   206  	scopedLog := log.WithFields(logrus.Fields{
   207  		logfields.Request: req.String(),
   208  		"NumericIdentity": srcIdentity,
   209  	})
   210  
   211  	k.redirect.mutex.RLock()
   212  	rules := k.redirect.rules.GetRelevantRulesForKafka(srcIdentity)
   213  	k.redirect.mutex.RUnlock()
   214  
   215  	if len(rules) == 0 {
   216  		flowdebug.Log(scopedLog, "No Kafka rules matching identity, rejecting")
   217  		return false
   218  	}
   219  
   220  	if flowdebug.Enabled() {
   221  		b, err := json.Marshal(rules)
   222  		if err != nil {
   223  			flowdebug.Log(scopedLog, "Error marshalling kafka rules to apply")
   224  			return false
   225  		} else {
   226  			flowdebug.Log(scopedLog.WithField("rule", string(b)), "Applying rule")
   227  		}
   228  	}
   229  
   230  	return req.MatchesRule(rules)
   231  }
   232  
   233  // kafkaLogRecord wraps an accesslog.LogRecord so that we can define methods with a receiver
   234  type kafkaLogRecord struct {
   235  	*logger.LogRecord
   236  	localEndpoint logger.EndpointUpdater
   237  	topics        []string
   238  }
   239  
   240  func apiKeyToString(apiKey int16) string {
   241  	if key, ok := api.KafkaReverseAPIKeyMap[apiKey]; ok {
   242  		return key
   243  	}
   244  	return fmt.Sprintf("%d", apiKey)
   245  }
   246  
   247  func (k *kafkaRedirect) newLogRecordFromRequest(req *kafka.RequestMessage) kafkaLogRecord {
   248  	return kafkaLogRecord{
   249  		LogRecord: logger.NewLogRecord(k.endpointInfoRegistry, k.redirect.localEndpoint,
   250  			accesslog.TypeRequest, k.redirect.listener.ingress,
   251  			logger.LogTags.Kafka(&accesslog.LogRecordKafka{
   252  				APIVersion:    req.GetVersion(),
   253  				APIKey:        apiKeyToString(req.GetAPIKey()),
   254  				CorrelationID: int32(req.GetCorrelationID()),
   255  			})),
   256  		localEndpoint: k.redirect.localEndpoint,
   257  		topics:        req.GetTopics(),
   258  	}
   259  }
   260  
   261  func (k *kafkaRedirect) newLogRecordFromResponse(res *kafka.ResponseMessage, req *kafka.RequestMessage) kafkaLogRecord {
   262  	lr := kafkaLogRecord{
   263  		LogRecord: logger.NewLogRecord(k.endpointInfoRegistry, k.redirect.localEndpoint,
   264  			accesslog.TypeResponse, k.redirect.listener.ingress, logger.LogTags.Kafka(&accesslog.LogRecordKafka{})),
   265  		localEndpoint: k.redirect.localEndpoint,
   266  	}
   267  
   268  	if res != nil {
   269  		lr.Kafka.CorrelationID = int32(res.GetCorrelationID())
   270  	}
   271  
   272  	if req != nil {
   273  		lr.Kafka.APIVersion = req.GetVersion()
   274  		lr.Kafka.APIKey = apiKeyToString(req.GetAPIKey())
   275  		lr.topics = req.GetTopics()
   276  	}
   277  
   278  	return lr
   279  }
   280  
   281  // log Kafka log records
   282  func (l *kafkaLogRecord) log(verdict accesslog.FlowVerdict, code int, info string) {
   283  	l.ApplyTags(logger.LogTags.Verdict(verdict, info))
   284  	l.Kafka.ErrorCode = code
   285  
   286  	// Log multiple entries for multiple Kafka topics in a single request.
   287  	for _, t := range l.topics {
   288  		l.Kafka.Topic.Topic = t
   289  		l.Log()
   290  	}
   291  
   292  	// Update stats for the endpoint.
   293  	// Count only one request.
   294  	ingress := l.ObservationPoint == accesslog.Ingress
   295  	var port uint16
   296  	if ingress {
   297  		port = l.DestinationEndpoint.Port
   298  	} else {
   299  		port = l.SourceEndpoint.Port
   300  	}
   301  	if port == 0 {
   302  		// Something went wrong when identifying the endpoints.
   303  		// Ignore in order to avoid polluting the stats.
   304  		return
   305  	}
   306  	request := l.Type == accesslog.TypeRequest
   307  	l.localEndpoint.UpdateProxyStatistics("TCP", port, ingress, request, l.Verdict)
   308  }
   309  
   310  func (k *kafkaRedirect) handleRequest(pair *connectionPair, req *kafka.RequestMessage, correlationCache *kafka.CorrelationCache,
   311  	remoteAddr net.Addr, remoteIdentity uint32, origDstAddr string) {
   312  	scopedLog := log.WithField(fieldID, pair.String())
   313  	flowdebug.Log(scopedLog.WithField(logfields.Request, req.String()), "Handling Kafka request")
   314  
   315  	record := k.newLogRecordFromRequest(req)
   316  
   317  	record.ApplyTags(logger.LogTags.Addressing(logger.AddressingInfo{
   318  		SrcIPPort:   remoteAddr.String(),
   319  		DstIPPort:   origDstAddr,
   320  		SrcIdentity: remoteIdentity,
   321  	}))
   322  
   323  	if !k.canAccess(req, identity.NumericIdentity(remoteIdentity)) {
   324  		flowdebug.Log(scopedLog, "Kafka request is denied by policy")
   325  
   326  		resp, err := req.CreateResponse(proto.ErrTopicAuthorizationFailed)
   327  		if err != nil {
   328  			record.log(accesslog.VerdictError,
   329  				kafka.ErrInvalidMessage, fmt.Sprintf("Unable to create response: %s", err))
   330  			scopedLog.WithError(err).Error("Unable to create Kafka response")
   331  			return
   332  		}
   333  
   334  		record.log(accesslog.VerdictDenied,
   335  			kafka.ErrTopicAuthorizationFailed, fmt.Sprint("Kafka request is denied by policy"))
   336  
   337  		pair.Rx.Enqueue(resp.GetRaw())
   338  		return
   339  	}
   340  
   341  	if pair.Tx.Closed() {
   342  		marker := 0
   343  		if !k.conf.testMode {
   344  			marker = linux_defaults.GetMagicProxyMark(k.redirect.listener.ingress, int(remoteIdentity))
   345  		}
   346  
   347  		flowdebug.Log(scopedLog.WithFields(logrus.Fields{
   348  			"marker":      marker,
   349  			"destination": origDstAddr,
   350  		}), "Dialing original destination")
   351  
   352  		txConn, err := ciliumDialer(marker, remoteAddr.Network(), origDstAddr)
   353  		if err != nil {
   354  			scopedLog.WithError(err).WithFields(logrus.Fields{
   355  				"origNetwork": remoteAddr.Network(),
   356  				"origDest":    origDstAddr,
   357  			}).Error("Unable to dial original destination")
   358  
   359  			record.log(accesslog.VerdictError,
   360  				kafka.ErrNetwork, fmt.Sprintf("Unable to dial original destination: %s", err))
   361  
   362  			return
   363  		}
   364  
   365  		pair.Tx.SetConnection(txConn)
   366  
   367  		// Start go routine to handle responses and pass in a copy of
   368  		// the request record as template for all responses
   369  		go k.handleResponseConnection(pair, correlationCache, remoteAddr, remoteIdentity, origDstAddr)
   370  	}
   371  
   372  	// The request is allowed so we will forward it:
   373  	// 1. Rewrite the correlation ID to a unique ID, it will be restored in
   374  	//    the response direction
   375  	// 2. Store the request in the correlation cache
   376  	correlationCache.HandleRequest(req, nil)
   377  
   378  	flowdebug.Log(scopedLog, "Forwarding Kafka request")
   379  	// log valid request
   380  	record.log(accesslog.VerdictForwarded, kafka.ErrNone, "")
   381  
   382  	// Write the entire raw request onto the outgoing connection
   383  	pair.Tx.Enqueue(req.GetRaw())
   384  }
   385  
   386  type kafkaReqMessageHander func(pair *connectionPair, req *kafka.RequestMessage, correlationCache *kafka.CorrelationCache,
   387  	remoteAddr net.Addr, remoteIdentity uint32, origDstAddr string)
   388  type kafkaRespMessageHander func(pair *connectionPair, req *kafka.ResponseMessage)
   389  
   390  func (k *kafkaRedirect) handleRequests(done <-chan struct{}, pair *connectionPair, c *proxyConnection,
   391  	handler kafkaReqMessageHander) {
   392  	defer c.Close()
   393  
   394  	scopedLog := log.WithField(fieldID, pair.String())
   395  
   396  	remoteAddr := pair.Rx.conn.RemoteAddr()
   397  	if remoteAddr == nil {
   398  		scopedLog.Error("Kafka request connection has no remote address")
   399  		return
   400  	}
   401  
   402  	localAddr := pair.Rx.conn.LocalAddr()
   403  	if localAddr == nil {
   404  		scopedLog.Error("Kafka request connection has no local address")
   405  		return
   406  	}
   407  
   408  	// retrieve identity of source
   409  	k.redirect.localEndpoint.UnconditionalRLock()
   410  	mapname := k.redirect.localEndpoint.ConntrackName()
   411  	k.redirect.localEndpoint.RUnlock()
   412  	srcIdentity, err := k.conf.lookupSrcID(mapname, remoteAddr.String(), localAddr.String(), k.redirect.listener.ingress)
   413  	if err != nil {
   414  		scopedLog.WithField("source",
   415  			remoteAddr.String()).WithError(err).Error("Unable to lookup source security ID")
   416  		return
   417  	}
   418  
   419  	// create a correlation cache
   420  	correlationCache := kafka.NewCorrelationCache()
   421  	defer correlationCache.DeleteCache()
   422  
   423  	for {
   424  		req, err := kafka.ReadRequest(c.conn)
   425  
   426  		// Ignore any error if the listen socket has been closed, i.e. the
   427  		// port redirect has been removed.
   428  		select {
   429  		case <-done:
   430  			scopedLog.Debug("Redirect removed; closing Kafka request connection")
   431  			return
   432  		default:
   433  		}
   434  
   435  		if err != nil {
   436  			if err != io.ErrUnexpectedEOF && err != io.EOF {
   437  				scopedLog.WithError(err).Error("Unable to parse Kafka request; closing Kafka request connection")
   438  			}
   439  			return
   440  		}
   441  		origDstAddr := localAddr.String()
   442  		if k.conf.testMode {
   443  			origDstAddr = fmt.Sprintf("127.0.0.1:%d", k.redirect.dstPort)
   444  		}
   445  		scopedLog.Debugf("Forwarding request to %s", origDstAddr)
   446  		handler(pair, req, correlationCache, remoteAddr, srcIdentity, origDstAddr)
   447  	}
   448  }
   449  
   450  func (k *kafkaRedirect) handleResponses(done <-chan struct{}, pair *connectionPair, c *proxyConnection,
   451  	correlationCache *kafka.CorrelationCache, handler kafkaRespMessageHander,
   452  	remoteAddr net.Addr, remoteIdentity uint32, origDstAddr string) {
   453  	defer c.Close()
   454  	scopedLog := log.WithField(fieldID, pair.String())
   455  	for {
   456  		rsp, err := kafka.ReadResponse(c.conn)
   457  
   458  		// Ignore any error if the listen socket has been closed, i.e. the
   459  		// port redirect has been removed.
   460  		select {
   461  		case <-done:
   462  			scopedLog.Debug("Redirect removed; closing Kafka response connection")
   463  			return
   464  		default:
   465  		}
   466  
   467  		if err != nil {
   468  			record := k.newLogRecordFromResponse(nil, nil)
   469  			record.log(accesslog.VerdictError,
   470  				kafka.ErrInvalidMessage,
   471  				fmt.Sprintf("Unable to parse Kafka response: %s", err))
   472  			scopedLog.WithError(err).Error("Unable to parse Kafka response; closing Kafka response connection")
   473  			return
   474  		}
   475  
   476  		// 1. Find the request that correlates with this response based
   477  		//    on the correlation ID
   478  		// 2. Restore the original correlation id that was overwritten
   479  		//    by the proxy so the client is guaranteed to see the
   480  		//    correlation id as expected
   481  		req := correlationCache.CorrelateResponse(rsp)
   482  
   483  		record := k.newLogRecordFromResponse(rsp, req)
   484  		record.ApplyTags(logger.LogTags.Addressing(logger.AddressingInfo{
   485  			SrcIPPort:   remoteAddr.String(),
   486  			DstIPPort:   origDstAddr,
   487  			SrcIdentity: remoteIdentity,
   488  		}))
   489  		record.log(accesslog.VerdictForwarded, kafka.ErrNone, "")
   490  
   491  		handler(pair, rsp)
   492  	}
   493  }
   494  
   495  func (k *kafkaRedirect) handleRequestConnection(pair *connectionPair) {
   496  	flowdebug.Log(log.WithFields(logrus.Fields{
   497  		"from": pair.Rx,
   498  		"to":   pair.Tx,
   499  	}), "Proxying request Kafka connection")
   500  
   501  	k.handleRequests(k.listener.socket.closing, pair, pair.Rx, k.handleRequest)
   502  }
   503  
   504  func (k *kafkaRedirect) handleResponseConnection(pair *connectionPair, correlationCache *kafka.CorrelationCache,
   505  	remoteAddr net.Addr, remoteIdentity uint32, origDstAddr string) {
   506  	flowdebug.Log(log.WithFields(logrus.Fields{
   507  		"from": pair.Tx,
   508  		"to":   pair.Rx,
   509  	}), "Proxying response Kafka connection")
   510  
   511  	k.handleResponses(k.listener.socket.closing, pair, pair.Tx, correlationCache,
   512  		func(pair *connectionPair, rsp *kafka.ResponseMessage) {
   513  			pair.Rx.Enqueue(rsp.GetRaw())
   514  		}, remoteAddr, remoteIdentity, origDstAddr)
   515  }
   516  
   517  // UpdateRules is a no-op for kafka redirects, as rules are read directly
   518  // during request processing.
   519  func (k *kafkaRedirect) UpdateRules(wg *completion.WaitGroup, l4 *policy.L4Filter) (revert.RevertFunc, error) {
   520  	return func() error { return nil }, nil
   521  }
   522  
   523  // Close the redirect.
   524  func (k *kafkaRedirect) Close(wg *completion.WaitGroup) (revert.FinalizeFunc, revert.RevertFunc) {
   525  	return func() {
   526  		r := k.redirect
   527  		log.WithField(logfields.EndpointID, r.endpointID).Debugf("Un-Registering %s port: %d",
   528  			r.listener.name, r.dstPort)
   529  		key := mapKey(r.dstPort, r.listener.ingress, uint16(r.endpointID))
   530  
   531  		mutex.Lock()
   532  		delete(kafkaRedirects, key)
   533  		k.listener.count--
   534  		log.Debugf("Close: Listener count: %d", k.listener.count)
   535  		if k.listener.count == 0 {
   536  			k.listener.socket.Close()
   537  			delete(kafkaListeners, r.listener.proxyPort)
   538  		}
   539  		mutex.Unlock()
   540  	}, nil
   541  }
   542  
   543  func init() {
   544  	if err := proto.ConfigureParser(proto.ParserConfig{
   545  		SimplifiedMessageSetParsing: false,
   546  	}); err != nil {
   547  		log.WithError(err).Fatal("Unable to configure kafka parser")
   548  	}
   549  }