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 }