github.com/fafucoder/cilium@v1.6.11/proxylib/r2d2/r2d2parser.go (about) 1 // Copyright 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 r2d2 16 17 import ( 18 "bytes" 19 "fmt" 20 "regexp" 21 "strings" 22 23 "github.com/cilium/cilium/proxylib/proxylib" 24 25 "github.com/cilium/proxy/go/cilium/api" 26 log "github.com/sirupsen/logrus" 27 ) 28 29 // 30 // R2D2 Parser 31 // 32 // This is a toy protocol to teach people how to build a Cilium golang proxy parser. 33 // 34 35 // Current R2D2 parser supports filtering on a basic text protocol with 4 request-types: 36 // "READ <filename>\r\n" - Read a file from the Droid 37 // "WRITE <filename>\r\n" - Write a file to the Droid 38 // "HALT\r\n" - Shutdown the Droid 39 // "RESET\r\n" - Reset the Droid to factory settings 40 // 41 // Replies include a status of either "OK\r\n", "ERROR\r\n" for "WRITE", "HALT", or "RESET". 42 // Replies for "READ" are either "OK <filedata>\r\n" or "ERROR\r\n". 43 // 44 // 45 // Policy Examples: 46 // {cmd : "READ"} - Allow all reads, no other commands. 47 // {cmd : "READ", file : "/public/.*" } - Allow reads that are in the public directory 48 // {file : "/public/.*" } - Allow read/write on the public directory. 49 // {cmd : "HALT"} - Allow shutdown, but no other actions. 50 51 type r2d2Rule struct { 52 cmdExact string 53 fileRegexCompiled *regexp.Regexp 54 } 55 56 type r2d2RequestData struct { 57 cmd string 58 file string 59 } 60 61 func (rule *r2d2Rule) Matches(data interface{}) bool { 62 // Cast 'data' to the type we give to 'Matches()' 63 64 reqData, ok := data.(r2d2RequestData) 65 regexStr := "" 66 if rule.fileRegexCompiled != nil { 67 regexStr = rule.fileRegexCompiled.String() 68 } 69 70 if !ok { 71 log.Warning("Matches() called with type other than R2d2RequestData") 72 return false 73 } 74 if len(rule.cmdExact) > 0 && rule.cmdExact != reqData.cmd { 75 log.Debugf("R2d2Rule: cmd mismatch %s, %s", rule.cmdExact, reqData.cmd) 76 return false 77 } 78 if rule.fileRegexCompiled != nil && 79 !rule.fileRegexCompiled.MatchString(reqData.file) { 80 log.Debugf("R2d2Rule: file mismatch %s, %s", rule.fileRegexCompiled.String(), reqData.file) 81 return false 82 } 83 log.Debugf("policy match for rule: '%s' '%s'", rule.cmdExact, regexStr) 84 return true 85 } 86 87 // ruleParser parses protobuf L7 rules to enforcement objects 88 // May panic 89 func ruleParser(rule *cilium.PortNetworkPolicyRule) []proxylib.L7NetworkPolicyRule { 90 l7Rules := rule.GetL7Rules() 91 var rules []proxylib.L7NetworkPolicyRule 92 if l7Rules == nil { 93 return rules 94 } 95 for _, l7Rule := range l7Rules.GetL7Rules() { 96 var rr r2d2Rule 97 for k, v := range l7Rule.Rule { 98 switch k { 99 case "cmd": 100 rr.cmdExact = v 101 case "file": 102 if v != "" { 103 rr.fileRegexCompiled = regexp.MustCompile(v) 104 } 105 default: 106 proxylib.ParseError(fmt.Sprintf("Unsupported key: %s", k), rule) 107 } 108 } 109 if rr.cmdExact != "" && 110 rr.cmdExact != "READ" && 111 rr.cmdExact != "WRITE" && 112 rr.cmdExact != "HALT" && 113 rr.cmdExact != "RESET" { 114 proxylib.ParseError(fmt.Sprintf("Unable to parse L7 r2d2 rule with invalid cmd: '%s'", rr.cmdExact), rule) 115 } 116 if (rr.fileRegexCompiled != nil) && !(rr.cmdExact == "" || rr.cmdExact == "READ" || rr.cmdExact == "WRITE") { 117 proxylib.ParseError(fmt.Sprintf("Unable to parse L7 r2d2 rule, cmd '%s' is not compatible with 'file'", rr.cmdExact), rule) 118 } 119 regexStr := "" 120 if rr.fileRegexCompiled != nil { 121 regexStr = rr.fileRegexCompiled.String() 122 } 123 log.Debugf("Parsed rule '%s' '%s'", rr.cmdExact, regexStr) 124 rules = append(rules, &rr) 125 } 126 return rules 127 } 128 129 type factory struct{} 130 131 func init() { 132 log.Debug("init(): Registering r2d2ParserFactory") 133 proxylib.RegisterParserFactory("r2d2", &factory{}) 134 proxylib.RegisterL7RuleParser("r2d2", ruleParser) 135 } 136 137 type parser struct { 138 connection *proxylib.Connection 139 inserted bool 140 } 141 142 func (f *factory) Create(connection *proxylib.Connection) proxylib.Parser { 143 log.Debugf("R2d2ParserFactory: Create: %v", connection) 144 145 return &parser{connection: connection} 146 } 147 148 func (p *parser) OnData(reply, endStream bool, dataArray [][]byte) (proxylib.OpType, int) { 149 150 // inefficient, but simple 151 data := string(bytes.Join(dataArray, []byte{})) 152 153 log.Debugf("OnData: '%s'", data) 154 msgLen := strings.Index(data, "\r\n") 155 if msgLen < 0 { 156 // No delimiter, request more data 157 log.Debugf("No delimiter found, requesting more bytes") 158 return proxylib.MORE, 1 159 } 160 161 msgStr := data[:msgLen] // read single request 162 msgLen += 2 // include "\r\n" 163 log.Debugf("Request = '%s'", msgStr) 164 165 // we don't process reply traffic for now 166 if reply { 167 log.Debugf("reply, passing %d bytes", msgLen) 168 return proxylib.PASS, msgLen 169 } 170 171 fields := strings.Split(msgStr, " ") 172 if len(fields) < 1 { 173 return proxylib.ERROR, int(proxylib.ERROR_INVALID_FRAME_TYPE) 174 } 175 reqData := r2d2RequestData{cmd: fields[0]} 176 if len(fields) == 2 { 177 reqData.file = fields[1] 178 } 179 180 matches := true 181 access_log_entry_type := cilium.EntryType_Request 182 183 if !p.connection.Matches(reqData) { 184 matches = false 185 access_log_entry_type = cilium.EntryType_Denied 186 } 187 188 p.connection.Log(access_log_entry_type, 189 &cilium.LogEntry_GenericL7{ 190 GenericL7: &cilium.L7LogEntry{ 191 Proto: "r2d2", 192 Fields: map[string]string{ 193 "cmd": reqData.cmd, 194 "file": reqData.file, 195 }, 196 }, 197 }) 198 199 if !matches { 200 p.connection.Inject(true, []byte("ERROR\r\n")) 201 log.Debugf("Policy mismatch, dropping %d bytes", msgLen) 202 return proxylib.DROP, msgLen 203 } 204 205 return proxylib.PASS, msgLen 206 }