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  }