github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/kafka/policy.go (about) 1 // Copyright 2017 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 kafka 16 17 import ( 18 "github.com/cilium/cilium/pkg/flowdebug" 19 "github.com/cilium/cilium/pkg/policy/api" 20 21 "github.com/optiopay/kafka/proto" 22 "github.com/sirupsen/logrus" 23 ) 24 25 // isTopicAPIKey returns true if kind is apiKey message type which contains a 26 // topic in its request. 27 func isTopicAPIKey(kind int16) bool { 28 switch kind { 29 case api.ProduceKey, 30 api.FetchKey, 31 api.OffsetsKey, 32 api.MetadataKey, 33 api.LeaderAndIsr, 34 api.StopReplica, 35 api.UpdateMetadata, 36 api.OffsetCommitKey, 37 api.OffsetFetchKey, 38 api.CreateTopicsKey, 39 api.DeleteTopicsKey, 40 api.DeleteRecordsKey, 41 api.OffsetForLeaderEpochKey, 42 api.AddPartitionsToTxnKey, 43 api.WriteTxnMarkersKey, 44 api.TxnOffsetCommitKey, 45 api.AlterReplicaLogDirsKey, 46 api.DescribeLogDirsKey, 47 api.CreatePartitionsKey: 48 49 return true 50 } 51 return false 52 } 53 54 func matchNonTopicRequests(req *RequestMessage, rule api.PortRuleKafka) bool { 55 // matchNonTopicRequests() is called when 56 // the kafka parser was not able to parse beyond the generic header. 57 // This could be due to 2 sceanrios: 58 // 1. It was a non-topic request 59 // 2. The parser could not parse further even if there was a topic present. 60 // For scenario 2, if topic is present, we need to return 61 // false since topic can never be associated with this request kind. 62 if rule.Topic != "" && isTopicAPIKey(req.kind) { 63 return false 64 } 65 // TODO add functionality for parsing clientID GH-3097 66 //if rule.ClientID != "" && rule.ClientID != req.GetClientID() { 67 // return false 68 //} 69 return true 70 } 71 72 func matchProduceReq(req *proto.ProduceReq, rule api.PortRuleKafka) bool { 73 if req == nil { 74 return false 75 } 76 77 if rule.ClientID != "" && rule.ClientID != req.ClientID { 78 return false 79 } 80 81 return true 82 } 83 84 func matchFetchReq(req *proto.FetchReq, rule api.PortRuleKafka) bool { 85 if req == nil { 86 return false 87 } 88 89 if rule.ClientID != "" && rule.ClientID != req.ClientID { 90 return false 91 } 92 93 return true 94 } 95 96 func matchOffsetReq(req *proto.OffsetReq, rule api.PortRuleKafka) bool { 97 if req == nil { 98 return false 99 } 100 101 if rule.ClientID != "" && rule.ClientID != req.ClientID { 102 return false 103 } 104 105 return true 106 } 107 108 func matchMetadataReq(req *proto.MetadataReq, rule api.PortRuleKafka) bool { 109 if req == nil { 110 return false 111 } 112 113 if rule.ClientID != "" && rule.ClientID != req.ClientID { 114 return false 115 } 116 117 return true 118 } 119 120 func matchOffsetCommitReq(req *proto.OffsetCommitReq, rule api.PortRuleKafka) bool { 121 if req == nil { 122 return false 123 } 124 125 if rule.ClientID != "" && rule.ClientID != req.ClientID { 126 return false 127 } 128 129 return true 130 } 131 132 func matchOffsetFetchReq(req *proto.OffsetFetchReq, rule api.PortRuleKafka) bool { 133 if req == nil { 134 return false 135 } 136 137 if rule.ClientID != "" && rule.ClientID != req.ClientID { 138 return false 139 } 140 141 return true 142 } 143 144 func (req *RequestMessage) ruleMatches(rule api.PortRuleKafka) bool { 145 if req == nil { 146 return false 147 } 148 149 flowdebug.Log(log.WithFields(logrus.Fields{ 150 fieldRequest: req.String(), 151 fieldRule: rule, 152 }), "Matching Kafka rule") 153 154 if !rule.CheckAPIKeyRole(req.kind) { 155 return false 156 } 157 158 apiVersion, isWildcard := rule.GetAPIVersion() 159 if !isWildcard && apiVersion != req.version { 160 return false 161 } 162 163 // If the rule contains no additional conditionals, it is not required 164 // to match into the request specific fields. 165 if rule.Topic == "" && rule.ClientID == "" { 166 return true 167 } 168 169 switch val := req.request.(type) { 170 case *proto.ProduceReq: 171 return matchProduceReq(val, rule) 172 case *proto.FetchReq: 173 return matchFetchReq(val, rule) 174 case *proto.OffsetReq: 175 return matchOffsetReq(val, rule) 176 case *proto.MetadataReq: 177 return matchMetadataReq(val, rule) 178 case *proto.OffsetCommitReq: 179 return matchOffsetCommitReq(val, rule) 180 case *proto.OffsetFetchReq: 181 return matchOffsetFetchReq(val, rule) 182 case *proto.ConsumerMetadataReq: 183 return true 184 case nil: 185 // This is the case when requests like 186 // heartbeat,findcordinator, et al 187 // are specified. They are not 188 // associated with a topic, but we should 189 // still check for ClientID present in request header. 190 return matchNonTopicRequests(req, rule) 191 default: 192 // If all conditions have been met, allow the request 193 return true 194 } 195 } 196 197 // MatchesRule validates the Kafka request message against the provided list of 198 // rules. The function will return true if the policy allows the message, 199 // otherwise false is returned. 200 func (req *RequestMessage) MatchesRule(rules []api.PortRuleKafka) bool { 201 topics := req.GetTopics() 202 // Maintain a map of all topics in the request. 203 // We should allow the request only if all topics are 204 // allowed by the list of rules. 205 reqTopicsMap := make(map[string]bool, len(topics)) 206 for _, topic := range topics { 207 reqTopicsMap[topic] = true 208 } 209 210 for _, rule := range rules { 211 if rule.Topic == "" || len(topics) == 0 { 212 if req.ruleMatches(rule) { 213 return true 214 } 215 } else if reqTopicsMap[rule.Topic] { 216 if req.ruleMatches(rule) { 217 delete(reqTopicsMap, rule.Topic) 218 if len(reqTopicsMap) == 0 { 219 return true 220 } 221 } 222 } 223 } 224 return false 225 }