github.com/zhyoulun/cilium@v1.6.12/proxylib/cassandra/cassandraparser_test.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  // +build !privileged_tests
    16  
    17  package cassandra
    18  
    19  import (
    20  	"encoding/hex"
    21  	"testing"
    22  
    23  	// "github.com/cilium/cilium/pkg/logging"
    24  	"github.com/cilium/cilium/proxylib/accesslog"
    25  	"github.com/cilium/cilium/proxylib/proxylib"
    26  	"github.com/cilium/cilium/proxylib/test"
    27  
    28  	// log "github.com/sirupsen/logrus"
    29  	. "gopkg.in/check.v1"
    30  )
    31  
    32  // Hook up gocheck into the "go test" runner.
    33  func Test(t *testing.T) {
    34  	// logging.ToggleDebugLogs(true)
    35  	// log.SetLevel(log.DebugLevel)
    36  
    37  	TestingT(t)
    38  }
    39  
    40  type CassandraSuite struct {
    41  	logServer *test.AccessLogServer
    42  	ins       *proxylib.Instance
    43  }
    44  
    45  var _ = Suite(&CassandraSuite{})
    46  
    47  // Set up access log server and Library instance for all the test cases
    48  func (s *CassandraSuite) SetUpSuite(c *C) {
    49  	s.logServer = test.StartAccessLogServer("access_log.sock", 10)
    50  	c.Assert(s.logServer, Not(IsNil))
    51  	s.ins = proxylib.NewInstance("node1", accesslog.NewClient(s.logServer.Path))
    52  	c.Assert(s.ins, Not(IsNil))
    53  }
    54  
    55  func (s *CassandraSuite) checkAccessLogs(c *C, expPasses, expDrops int) {
    56  	passes, drops := s.logServer.Clear()
    57  	c.Check(passes, Equals, expPasses, Commentf("Unxpected number of passed access log messages"))
    58  	c.Check(drops, Equals, expDrops, Commentf("Unxpected number of passed access log messages"))
    59  }
    60  
    61  func (s *CassandraSuite) TearDownTest(c *C) {
    62  	s.logServer.Clear()
    63  }
    64  
    65  func (s *CassandraSuite) TearDownSuite(c *C) {
    66  	s.logServer.Close()
    67  }
    68  
    69  // util function used for Cassandra tests, as we have cassandra requests
    70  // as hex strings
    71  func hexData(c *C, dataHex ...string) [][]byte {
    72  	data := make([][]byte, 0, len(dataHex))
    73  	for i := range dataHex {
    74  		dataRaw, err := hex.DecodeString(dataHex[i])
    75  		c.Assert(err, IsNil)
    76  		data = append(data, dataRaw)
    77  	}
    78  	return data
    79  }
    80  
    81  func (s *CassandraSuite) TestCassandraOnDataNoHeader(c *C) {
    82  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "no-policy")
    83  	data := hexData(c, "0400")
    84  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
    85  		proxylib.MORE, 9-len(data[0]))
    86  }
    87  
    88  func (s *CassandraSuite) TestCassandraOnDataOptionsReq(c *C) {
    89  	s.ins.CheckInsertPolicyText(c, "1", []string{`
    90  		name: "cp6"
    91  		policy: 2
    92  		ingress_per_port_policies: <
    93  		  port: 80
    94  		  rules: <
    95  		    remote_policies: 1
    96  		    remote_policies: 3
    97  		    remote_policies: 4
    98  		    l7_proto: "cassandra"
    99  		    l7_rules: <
   100  		      l7_rules: <
   101  			rule: <
   102  			  key: "query_action"
   103  			  value: "select"
   104  			>
   105  		      >
   106  		    >
   107  		  >
   108  		>
   109  		`})
   110  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp6")
   111  
   112  	data := hexData(c, "040000000500000000")
   113  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   114  		proxylib.PASS, len(data[0]),
   115  		proxylib.MORE, 9)
   116  }
   117  
   118  // this passes a large query request that is missing just the last byte
   119  func (s *CassandraSuite) TestCassandraOnDataPartialReq(c *C) {
   120  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   121  		name: "cp5"
   122  		policy: 2
   123  		ingress_per_port_policies: <
   124  		  port: 80
   125  		  rules: <
   126  		    remote_policies: 1
   127  		    remote_policies: 3
   128  		    remote_policies: 4
   129  		    l7_proto: "cassandra"
   130  		    l7_rules: <
   131  		      l7_rules: <
   132  			rule: <
   133  			  key: "query_table"
   134  			  value: ".*"
   135  			>
   136  		      >
   137  		    >
   138  		  >
   139  		>
   140  		`})
   141  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp5")
   142  	data := hexData(c, "0400000407000000760000006f53454c45435420636c75737465725f6e616d652c20646174615f63656e7465722c207261636b2c20746f6b656e732c20706172746974696f6e65722c20736368656d615f76657273696f6e2046524f4d2073797374656d2e6c6f63616c205748455245206b65793d276c6f63616c270001")
   143  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   144  		proxylib.MORE, 1)
   145  }
   146  
   147  func (s *CassandraSuite) TestCassandraOnDataQueryReq(c *C) {
   148  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   149  		name: "cp4"
   150  		policy: 2
   151  		ingress_per_port_policies: <
   152  		  port: 80
   153  		  rules: <
   154  		    remote_policies: 1
   155  		    remote_policies: 3
   156  		    remote_policies: 4
   157  		    l7_proto: "cassandra"
   158  		    l7_rules: <
   159  		      l7_rules: <
   160  			rule: <
   161  			  key: "query_table"
   162  			  value: ".*"
   163  			>
   164  		      >
   165  		    >
   166  		  >
   167  		>
   168  		`})
   169  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp4")
   170  	data := hexData(c, "0400000407000000760000006f53454c45435420636c75737465725f6e616d652c20646174615f63656e7465722c207261636b2c20746f6b656e732c20706172746974696f6e65722c20736368656d615f76657273696f6e2046524f4d2073797374656d2e6c6f63616c205748455245206b65793d276c6f63616c27000100")
   171  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   172  		proxylib.PASS, len(data[0]),
   173  		proxylib.MORE, 9)
   174  }
   175  
   176  func (s *CassandraSuite) TestCassandraOnDataSplitQueryReq(c *C) {
   177  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   178  		name: "cp3"
   179  		policy: 2
   180  		ingress_per_port_policies: <
   181  		  port: 80
   182  		  rules: <
   183  		    remote_policies: 1
   184  		    remote_policies: 3
   185  		    remote_policies: 4
   186  		    l7_proto: "cassandra"
   187  		    l7_rules: <
   188  		      l7_rules: <
   189  			rule: <
   190  			  key: "query_table"
   191  			  value: ".*"
   192  			>
   193  		      >
   194  		    >
   195  		  >
   196  		>
   197  		`})
   198  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp3")
   199  	data := hexData(c, "04000004070000007600", "00006f53454c45435420636c75737465725f6e616d652c20646174615f63656e7465722c207261636b2c20746f6b656e732c20706172746974696f6e65722c20736368656d615f76657273696f6e2046524f4d2073797374656d2e6c6f63616c205748455245206b65793d276c6f63616c27000100")
   200  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   201  		proxylib.PASS, len(data[0])+len(data[1]),
   202  		proxylib.MORE, 9)
   203  }
   204  
   205  func (s *CassandraSuite) TestCassandraOnDataMultiReq(c *C) {
   206  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   207  		name: "cp2"
   208  		policy: 2
   209  		ingress_per_port_policies: <
   210  		  port: 80
   211  		  rules: <
   212  		    remote_policies: 1
   213  		    remote_policies: 3
   214  		    remote_policies: 4
   215  		    l7_proto: "cassandra"
   216  		    l7_rules: <
   217  		      l7_rules: <
   218  			rule: <
   219  			  key: "query_table"
   220  			  value: ".*"
   221  			>
   222  		      >
   223  		    >
   224  		  >
   225  		>
   226  		`})
   227  
   228  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp2")
   229  
   230  	data := hexData(c, "040000000500000000",
   231  		"0400000407000000760000006f53454c45435420636c75737465725f6e616d652c20646174615f63656e7465722c207261636b2c20746f6b656e732c20706172746974696f6e65722c20736368656d615f76657273696f6e2046524f4d2073797374656d2e6c6f63616c205748455245206b65793d276c6f63616c27000100")
   232  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   233  		proxylib.PASS, len(data[0]),
   234  		proxylib.PASS, len(data[1]),
   235  		proxylib.MORE, 9)
   236  }
   237  
   238  func (s *CassandraSuite) TestSimpleCassandraPolicy(c *C) {
   239  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   240  		name: "cp1"
   241  		policy: 2
   242  		ingress_per_port_policies: <
   243  		  port: 80
   244  		  rules: <
   245  		    remote_policies: 1
   246  		    remote_policies: 3
   247  		    remote_policies: 4
   248  		    l7_proto: "cassandra"
   249  		    l7_rules: <
   250  		      l7_rules: <
   251  			rule: <
   252  			  key: "query_table"
   253  			  value: "no-match"
   254  			>
   255  		      >
   256  		    >
   257  		  >
   258  		>
   259  		`})
   260  
   261  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   262  
   263  	unauthMsg := createUnauthMsg(0x4)
   264  	data := hexData(c, "040000000500000000",
   265  		"0400000407000000760000006f53454c45435420636c75737465725f6e616d652c20646174615f63656e7465722c207261636b2c20746f6b656e732c20706172746974696f6e65722c20736368656d615f76657273696f6e2046524f4d2073797374656d2e6c6f63616c205748455245206b65793d276c6f63616c27000100")
   266  	conn.CheckOnDataOK(c, false, false, &data, unauthMsg,
   267  		proxylib.PASS, len(data[0]),
   268  		proxylib.DROP, len(data[1]),
   269  		proxylib.MORE, 9)
   270  
   271  	// All passes are not access-logged
   272  	s.checkAccessLogs(c, 0, 1)
   273  }
   274  
   275  func createUnauthMsg(streamID byte) []byte {
   276  	unauthMsg := make([]byte, len(unauthMsgBase))
   277  	copy(unauthMsg, unauthMsgBase)
   278  	unauthMsg[0] = 0x84
   279  	unauthMsg[2] = 0x0
   280  	unauthMsg[3] = streamID
   281  	return unauthMsg
   282  }
   283  
   284  // this test confirms that we correctly parse and allow a valid batch requests
   285  func (s *CassandraSuite) TestCassandraBatchRequestPolicy(c *C) {
   286  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   287  		name: "cp1"
   288  		policy: 2
   289  		ingress_per_port_policies: <
   290  		  port: 80
   291  		  rules: <
   292  		    remote_policies: 1
   293  		    remote_policies: 3
   294  		    remote_policies: 4
   295  		    l7_proto: "cassandra"
   296  		    l7_rules: <
   297  		      l7_rules: <
   298  			rule: <
   299  			  key: "query_table"
   300  			  value: "db1.*"
   301  			>
   302  		      >
   303  		    >
   304  		  >
   305  		>
   306  		`})
   307  
   308  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   309  
   310  	batchMsg := []byte{
   311  		0x04,     // version
   312  		0x0,      // flags, (uint8)
   313  		0x0, 0x4, // stream-id (uint16) (test request uses 0x0004 as stream ID)
   314  		0x0d,                // opcode batch (uint8)
   315  		0x0, 0x0, 0x0, 0x3c, // request length of 60 (uint32) - update if body changes
   316  		0x0,      // batch type == logged
   317  		0x0, 0x2, // two batch messages
   318  
   319  		// first batch message
   320  		0x0,                 // type: non-prepared query
   321  		0x0, 0x0, 0x0, 0x14, // [long string] length (20)
   322  		'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'd', 'b', '1', '.', 't', '1',
   323  		0x0, 0x0, // # of bound values
   324  
   325  		// second batch message
   326  		0x0,                 // type: non-prepared query
   327  		0x0, 0x0, 0x0, 0x14, // [long string] length (20)
   328  		'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'd', 'b', '1', '.', 't', '2',
   329  		0x0, 0x0, // # of bound values
   330  
   331  		0x0, 0x0, // consistency level [short]
   332  		0x0, // batch flags
   333  	}
   334  	data := [][]byte{batchMsg}
   335  
   336  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   337  		proxylib.PASS, len(data[0]),
   338  		proxylib.MORE, 9)
   339  
   340  	// batch requests are access-logged individually
   341  	s.checkAccessLogs(c, 2, 0)
   342  }
   343  
   344  // this test confirms that we correctly parse and deny a batch request
   345  // if any of the requests are denied.
   346  func (s *CassandraSuite) TestCassandraBatchRequestPolicyDenied(c *C) {
   347  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   348  		name: "cp1"
   349  		policy: 2
   350  		ingress_per_port_policies: <
   351  		  port: 80
   352  		  rules: <
   353  		    remote_policies: 1
   354  		    remote_policies: 3
   355  		    remote_policies: 4
   356  		    l7_proto: "cassandra"
   357  		    l7_rules: <
   358  		      l7_rules: <
   359  			rule: <
   360  			  key: "query_table"
   361  			  value: "db1.*"
   362  			>
   363  		      >
   364  		    >
   365  		  >
   366  		>
   367  		`})
   368  
   369  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   370  
   371  	batchMsg := []byte{
   372  		0x04,     // version
   373  		0x0,      // flags, (uint8)
   374  		0x0, 0x4, // stream-id (uint16) (test request uses 0x0004 as stream ID)
   375  		0x0d,                // opcode batch (uint8)
   376  		0x0, 0x0, 0x0, 0x3c, // request length of 60 (uint32) - update if body changes
   377  		0x0,      // batch type == logged
   378  		0x0, 0x2, // two batch messages
   379  
   380  		// first batch message
   381  		0x0,                 // type: non-prepared query
   382  		0x0, 0x0, 0x0, 0x14, // [long string] length (20)
   383  		'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'd', 'b', '1', '.', 't', '1',
   384  		0x0, 0x0, // # of bound values
   385  
   386  		// second batch message (accesses db2.t2, which should be denied)
   387  		0x0,                 // type: non-prepared query
   388  		0x0, 0x0, 0x0, 0x14, // [long string] length (20)
   389  		'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'd', 'b', '2', '.', 't', '2',
   390  		0x0, 0x0, // # of bound values
   391  
   392  		0x0, 0x0, // consistency level [short]
   393  		0x0, // batch flags
   394  	}
   395  	data := [][]byte{batchMsg}
   396  
   397  	unauthMsg := createUnauthMsg(0x4)
   398  	conn.CheckOnDataOK(c, false, false, &data, unauthMsg,
   399  		proxylib.DROP, len(data[0]),
   400  		proxylib.MORE, 9)
   401  
   402  	// batch requests are access-logged individually
   403  	// Note: in this case, both accesses are denied, as a batch
   404  	// request is either entirely allowed or denied
   405  	s.checkAccessLogs(c, 0, 2)
   406  }
   407  
   408  // test batch requests with prepared statements
   409  func (s *CassandraSuite) TestCassandraBatchRequestPreparedStatement(c *C) {
   410  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   411  		name: "cp1"
   412  		policy: 2
   413  		ingress_per_port_policies: <
   414  		  port: 80
   415  		  rules: <
   416  		    remote_policies: 1
   417  		    remote_policies: 3
   418  		    remote_policies: 4
   419  		    l7_proto: "cassandra"
   420  		    l7_rules: <
   421  		      l7_rules: <
   422  			rule: <
   423  			  key: "query_table"
   424  			  value: "db3.*"
   425  			>
   426  		      >
   427  		    >
   428  		  >
   429  		>
   430  		`})
   431  
   432  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   433  
   434  	cassParser, ok := (conn.Parser).(*CassandraParser)
   435  	if !ok {
   436  		panic("failed to cast conn.Parser to *CassandraParser\n")
   437  	}
   438  	preparedQueryID1 := "aaaa"
   439  	cassParser.preparedQueryPathByPreparedID[preparedQueryID1] = "/batch/select/db3.t1"
   440  	preparedQueryID2 := "bbbb"
   441  	cassParser.preparedQueryPathByPreparedID[preparedQueryID2] = "/batch/select/db3.t2"
   442  
   443  	batchMsg := []byte{
   444  		0x04,     // version
   445  		0x0,      // flags, (uint8)
   446  		0x0, 0x4, // stream-id (uint16) (test request uses 0x0004 as stream ID)
   447  		0x0d,                // opcode batch (uint8)
   448  		0x0, 0x0, 0x0, 0x18, // request length of 60 (uint32) - update if body changes
   449  		0x0,      // batch type == logged
   450  		0x0, 0x2, // two batch messages
   451  
   452  		// first batch message
   453  		0x1,      // type: prepared query
   454  		0x0, 0x4, // [short] length (4)
   455  		'a', 'a', 'a', 'a',
   456  		0x0, 0x0, // # of bound values
   457  
   458  		// second batch message
   459  		0x1,      // type: non-prepared query
   460  		0x0, 0x4, // [short] length (4)
   461  		'b', 'b', 'b', 'b',
   462  		0x0, 0x0, // # of bound values
   463  
   464  		0x0, 0x0, // consistency level [short]
   465  		0x0, // batch flags
   466  	}
   467  	data := [][]byte{batchMsg}
   468  
   469  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   470  		proxylib.PASS, len(data[0]),
   471  		proxylib.MORE, 9)
   472  
   473  	// batch requests are access-logged individually
   474  	s.checkAccessLogs(c, 2, 0)
   475  }
   476  
   477  // test batch requests with prepared statements, including a deny
   478  func (s *CassandraSuite) TestCassandraBatchRequestPreparedStatementDenied(c *C) {
   479  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   480  		name: "cp1"
   481  		policy: 2
   482  		ingress_per_port_policies: <
   483  		  port: 80
   484  		  rules: <
   485  		    remote_policies: 1
   486  		    remote_policies: 3
   487  		    remote_policies: 4
   488  		    l7_proto: "cassandra"
   489  		    l7_rules: <
   490  		      l7_rules: <
   491  			rule: <
   492  			  key: "query_table"
   493  			  value: "db3.*"
   494  			>
   495  		      >
   496  		    >
   497  		  >
   498  		>
   499  		`})
   500  
   501  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   502  
   503  	cassParser, ok := (conn.Parser).(*CassandraParser)
   504  	if !ok {
   505  		panic("failed to cast conn.Parser to *CassandraParser\n")
   506  	}
   507  	preparedQueryID1 := "aaaa"
   508  	cassParser.preparedQueryPathByPreparedID[preparedQueryID1] = "/batch/select/db3.t1"
   509  	preparedQueryID2 := "bbbb"
   510  	cassParser.preparedQueryPathByPreparedID[preparedQueryID2] = "/batch/select/db4.t2"
   511  
   512  	batchMsg := []byte{
   513  		0x04,     // version
   514  		0x0,      // flags, (uint8)
   515  		0x0, 0x4, // stream-id (uint16) (test request uses 0x0004 as stream ID)
   516  		0x0d,                // opcode batch (uint8)
   517  		0x0, 0x0, 0x0, 0x18, // request length of 60 (uint32) - update if body changes
   518  		0x0,      // batch type == logged
   519  		0x0, 0x2, // two batch messages
   520  
   521  		// first batch message
   522  		0x1,      // type: prepared query
   523  		0x0, 0x4, // [short] length (4)
   524  		'a', 'a', 'a', 'a',
   525  		0x0, 0x0, // # of bound values
   526  
   527  		// second batch message (accesses table db4, which should be denied)
   528  		0x1,      // type: non-prepared query
   529  		0x0, 0x4, // [short] length (4)
   530  		'b', 'b', 'b', 'b',
   531  		0x0, 0x0, // # of bound values
   532  
   533  		0x0, 0x0, // consistency level [short]
   534  		0x0, // batch flags
   535  	}
   536  	data := [][]byte{batchMsg}
   537  
   538  	unauthMsg := createUnauthMsg(0x4)
   539  	conn.CheckOnDataOK(c, false, false, &data, unauthMsg,
   540  		proxylib.DROP, len(data[0]),
   541  		proxylib.MORE, 9)
   542  
   543  	// batch requests are access-logged individually
   544  	s.checkAccessLogs(c, 0, 2)
   545  }
   546  
   547  // test execute statement, allow request
   548  func (s *CassandraSuite) TestCassandraExecutePreparedStatement(c *C) {
   549  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   550  		name: "cp1"
   551  		policy: 2
   552  		ingress_per_port_policies: <
   553  		  port: 80
   554  		  rules: <
   555  		    remote_policies: 1
   556  		    remote_policies: 3
   557  		    remote_policies: 4
   558  		    l7_proto: "cassandra"
   559  		    l7_rules: <
   560  		      l7_rules: <
   561  			rule: <
   562  			  key: "query_table"
   563  			  value: "db3.*"
   564  			>
   565  		      >
   566  		    >
   567  		  >
   568  		>
   569  		`})
   570  
   571  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   572  
   573  	cassParser, ok := (conn.Parser).(*CassandraParser)
   574  	if !ok {
   575  		panic("failed to cast conn.Parser to *CassandraParser\n")
   576  	}
   577  	preparedQueryID1 := "aaaa"
   578  	cassParser.preparedQueryPathByPreparedID[preparedQueryID1] = "/query/select/db3.t1"
   579  
   580  	executeMsg := []byte{
   581  		0x04,     // version
   582  		0x0,      // flags, (uint8)
   583  		0x0, 0x4, // stream-id (uint16) (test request uses 0x0004 as stream ID)
   584  		0x0a,                // opcode execute (uint8)
   585  		0x0, 0x0, 0x0, 0x09, // request length  (uint32) - update if body changes
   586  
   587  		// Execute request
   588  		0x0, 0x4, // short bytes len (4)
   589  		'a', 'a', 'a', 'a',
   590  
   591  		// the rest of this is values that can be ignored by our parser,
   592  		// but we add some here to make sure that we're properly passing
   593  		// based on total request length.
   594  		'x', 'y', 'z',
   595  	}
   596  	data := [][]byte{executeMsg}
   597  
   598  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   599  		proxylib.PASS, len(data[0]),
   600  		proxylib.MORE, 9)
   601  
   602  	s.checkAccessLogs(c, 1, 0)
   603  }
   604  
   605  // test execute statement with unknown prepared-id
   606  func (s *CassandraSuite) TestCassandraExecutePreparedStatementUnknownID(c *C) {
   607  
   608  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   609  
   610  	executeMsg := []byte{
   611  		0x04,     // version
   612  		0x0,      // flags, (uint8)
   613  		0x0, 0x4, // stream-id (uint16) (test request uses 0x0004 as stream ID)
   614  		0x0a,                // opcode execute (uint8)
   615  		0x0, 0x0, 0x0, 0x06, // request length  (uint32) - update if body changes
   616  
   617  		// Execute request
   618  		0x0, 0x4, // short bytes len (4)
   619  		'a', 'a', 'a', 'a',
   620  	}
   621  	data := [][]byte{executeMsg}
   622  
   623  	unpreparedMsg := createUnpreparedMsg(0x04, []byte{0x0, 0x4}, "aaaa")
   624  
   625  	conn.CheckOnDataOK(c, false, false, &data, unpreparedMsg,
   626  		proxylib.DROP, len(data[0]),
   627  		proxylib.MORE, 9)
   628  
   629  	s.checkAccessLogs(c, 0, 1)
   630  }
   631  
   632  // test parsing of a prepared query reply
   633  func (s *CassandraSuite) TestCassandraPreparedResultReply(c *C) {
   634  
   635  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   636  
   637  	cassParser, ok := (conn.Parser).(*CassandraParser)
   638  	if !ok {
   639  		panic("failed to cast conn.Parser to *CassandraParser\n")
   640  	}
   641  
   642  	// make sure there is a stream-id (4) that matches the request below
   643  	// this would have been populated by a "prepare" request
   644  	cassParser.preparedQueryPathByStreamID[uint16(4)] = "/query/select/db3.t1"
   645  
   646  	preparedResultMsg := []byte{
   647  		0x84,     // reply + version
   648  		0x0,      // flags, (uint8)
   649  		0x0, 0x4, // stream-id (uint16) (test request uses 0x0004 as stream ID)
   650  		0x08,                // opcode result (uint8)
   651  		0x0, 0x0, 0x0, 0x16, // request length 22 (uint32) - update if body changes
   652  
   653  		// Prepared Result request
   654  		0x0, 0x0, 0x0, 0x4, // [int] result type
   655  		0x0, 0x4, // prepared-id len (short)
   656  		'a', 'a', 'a', 'a', // prepared-id
   657  		0x0, 0x0, 0x0, 0x0, // prepared results flags
   658  		0x0, 0x0, 0x0, 0x0, // column-count
   659  		0x0, 0x0, 0x0, 0x0, // pk-count
   660  	}
   661  	data := [][]byte{preparedResultMsg}
   662  
   663  	conn.CheckOnDataOK(c, true, false, &data, []byte{},
   664  		proxylib.PASS, len(data[0]),
   665  		proxylib.MORE, 9)
   666  
   667  	// these replies are not access logged
   668  	s.checkAccessLogs(c, 0, 0)
   669  }
   670  
   671  // test additional queries
   672  func (s *CassandraSuite) TestCassandraAdditionalQueries(c *C) {
   673  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   674  		name: "cp1"
   675  		policy: 2
   676  		ingress_per_port_policies: <
   677  		  port: 80
   678  		  rules: <
   679  		    remote_policies: 1
   680  		    remote_policies: 3
   681  		    remote_policies: 4
   682  		    l7_proto: "cassandra"
   683  		    l7_rules: <
   684  		      l7_rules: <
   685  			rule: <
   686  			  key: "query_table"
   687  			  value: "db4.t1"
   688  			>
   689  		      >
   690  		    >
   691  		  >
   692  		>
   693  		`})
   694  
   695  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   696  
   697  	queries := []string{"CREATE TABLE db4.t1 (f1 varchar, f2 timeuuid, PRIMARY KEY ((f1), f2))",
   698  		"INSERT INTO db4.t1 (f1, f2, f3) values ('dan', now(), 'Cilium!')",
   699  		"UPDATE db4.t1 SET f1 = 'donald' where f2 in (1,2,3)",
   700  		"DROP TABLE db4.t1",
   701  		"TRUNCATE db4.t1",
   702  		"CREATE TABLE IF NOT EXISTS db4.t1 (f1 varchar, PRIMARY KEY(f1))",
   703  	}
   704  
   705  	queryMsgBase := []byte{
   706  		0x04,     // version
   707  		0x0,      // flags, (uint8)
   708  		0x0, 0x5, // stream-id (uint16) (test request uses 0x0005 as stream ID)
   709  		0x07,               // opcode query (uint8)
   710  		0x0, 0x0, 0x0, 0x0, // length of request - must be set
   711  
   712  		// Query Req
   713  		0x0, 0x0, 0x0, 0x0, // length of query (int) - must be set
   714  		// query string goes here
   715  	}
   716  
   717  	data := make([][]byte, len(queries))
   718  	for i := 0; i < len(queries); i++ {
   719  		queryLen := len(queries[i])
   720  
   721  		queryMsg := append(queryMsgBase, []byte(queries[i])...)
   722  
   723  		// this works as long as query is less than 251 bytes
   724  		queryMsg[8] = byte(4 + queryLen)
   725  		queryMsg[12] = byte(queryLen)
   726  
   727  		data[i] = queryMsg
   728  	}
   729  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   730  		proxylib.PASS, len(data[0]),
   731  		proxylib.PASS, len(data[1]),
   732  		proxylib.PASS, len(data[2]),
   733  		proxylib.PASS, len(data[3]),
   734  		proxylib.PASS, len(data[4]),
   735  		proxylib.PASS, len(data[5]),
   736  		proxylib.MORE, 9)
   737  
   738  	s.checkAccessLogs(c, 6, 0)
   739  }
   740  
   741  // test use query, following by query that does not include the keyspace
   742  func (s *CassandraSuite) TestCassandraUseQuery(c *C) {
   743  	s.ins.CheckInsertPolicyText(c, "1", []string{`
   744  		name: "cp1"
   745  		policy: 2
   746  		ingress_per_port_policies: <
   747  		  port: 80
   748  		  rules: <
   749  		    remote_policies: 1
   750  		    remote_policies: 3
   751  		    remote_policies: 4
   752  		    l7_proto: "cassandra"
   753  		    l7_rules: <
   754  		      l7_rules: <
   755  			rule: <
   756  			  key: "query_table"
   757  			  value: "db5.t1"
   758  			>
   759  		      >
   760  		    >
   761  		  >
   762  		>
   763  		`})
   764  
   765  	conn := s.ins.CheckNewConnectionOK(c, "cassandra", true, 1, 2, "1.1.1.1:34567", "2.2.2.2:80", "cp1")
   766  
   767  	// note: the second insert command intentionally does not include a keyspace, so that it will only
   768  	// be allowed if we properly propagate the keyspace from the previous use command
   769  	queries := []string{"USE db5", "INSERT INTO t1 (f1, f2, f3) values ('dan', now(), 'Cilium!')"}
   770  
   771  	queryMsgBase := []byte{
   772  		0x04,     // version
   773  		0x0,      // flags, (uint8)
   774  		0x0, 0x5, // stream-id (uint16) (test request uses 0x0005 as stream ID)
   775  		0x07,               // opcode query (uint8)
   776  		0x0, 0x0, 0x0, 0x0, // length of request - must be set
   777  
   778  		// Query Req
   779  		0x0, 0x0, 0x0, 0x0, // length of query (int) - must be set
   780  		// query string goes here
   781  	}
   782  
   783  	data := make([][]byte, len(queries))
   784  	for i := 0; i < len(queries); i++ {
   785  		queryLen := len(queries[i])
   786  
   787  		queryMsg := append(queryMsgBase, []byte(queries[i])...)
   788  
   789  		// this works as long as query is less than 251 bytes
   790  		queryMsg[8] = byte(4 + queryLen)
   791  		queryMsg[12] = byte(queryLen)
   792  
   793  		data[i] = queryMsg
   794  	}
   795  	conn.CheckOnDataOK(c, false, false, &data, []byte{},
   796  		proxylib.PASS, len(data[0]),
   797  		proxylib.PASS, len(data[1]),
   798  		proxylib.MORE, 9)
   799  
   800  	// use command will not show up in access log, so only expect one msg
   801  	s.checkAccessLogs(c, 1, 0)
   802  }