github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/kubernetesaudit/k8s_audit_test.go (about)

     1  package kubernetesauditacquisition
     2  
     3  import (
     4  	"net/http/httptest"
     5  	"strings"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
    10  	"github.com/crowdsecurity/crowdsec/pkg/types"
    11  	log "github.com/sirupsen/logrus"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	"gopkg.in/tomb.v2"
    15  )
    16  
    17  func TestBadConfiguration(t *testing.T) {
    18  	tests := []struct {
    19  		config      string
    20  		name        string
    21  		expectedErr string
    22  	}{
    23  		{
    24  			name: "unknown field",
    25  			config: `source: k8s-audit
    26  foobar: asd.log`,
    27  			expectedErr: "line 2: field foobar not found in type kubernetesauditacquisition.KubernetesAuditConfiguration",
    28  		},
    29  		{
    30  			name:        "missing listen_addr",
    31  			config:      `source: k8s-audit`,
    32  			expectedErr: "listen_addr cannot be empty",
    33  		},
    34  		{
    35  			name: "missing listen_port",
    36  			config: `source: k8s-audit
    37  listen_addr: 0.0.0.0`,
    38  			expectedErr: "listen_port cannot be empty",
    39  		},
    40  	}
    41  
    42  	for _, test := range tests {
    43  		t.Run(test.name, func(t *testing.T) {
    44  			f := KubernetesAuditSource{}
    45  
    46  			err := f.UnmarshalConfig([]byte(test.config))
    47  
    48  			assert.Contains(t, err.Error(), test.expectedErr)
    49  
    50  		})
    51  	}
    52  }
    53  
    54  func TestInvalidConfig(t *testing.T) {
    55  	tests := []struct {
    56  		name        string
    57  		config      string
    58  		expectedErr string
    59  	}{
    60  		{
    61  			name: "invalid_port",
    62  			config: `source: k8s-audit
    63  listen_addr: 127.0.0.1
    64  listen_port: 9999999
    65  webhook_path: /k8s-audit`,
    66  			expectedErr: "listen tcp: address 9999999: invalid port",
    67  		},
    68  	}
    69  
    70  	subLogger := log.WithFields(log.Fields{
    71  		"type": "k8s-audit",
    72  	})
    73  
    74  	for _, test := range tests {
    75  		t.Run(test.name, func(t *testing.T) {
    76  			out := make(chan types.Event)
    77  			tb := &tomb.Tomb{}
    78  
    79  			f := KubernetesAuditSource{}
    80  
    81  			err := f.UnmarshalConfig([]byte(test.config))
    82  
    83  			require.NoError(t, err)
    84  
    85  			err = f.Configure([]byte(test.config), subLogger, configuration.METRICS_NONE)
    86  
    87  			require.NoError(t, err)
    88  			f.StreamingAcquisition(out, tb)
    89  
    90  			time.Sleep(1 * time.Second)
    91  			tb.Kill(nil)
    92  			err = tb.Wait()
    93  			if test.expectedErr != "" {
    94  				require.ErrorContains(t, err, test.expectedErr)
    95  				return
    96  			}
    97  			require.NoError(t, err)
    98  		})
    99  	}
   100  }
   101  
   102  func TestHandler(t *testing.T) {
   103  	tests := []struct {
   104  		name               string
   105  		config             string
   106  		expectedStatusCode int
   107  		body               string
   108  		method             string
   109  		eventCount         int
   110  	}{
   111  		{
   112  			name: "valid_json",
   113  			config: `source: k8s-audit
   114  listen_addr: 127.0.0.1
   115  listen_port: 49234
   116  webhook_path: /k8s-audit`,
   117  			method:             "POST",
   118  			expectedStatusCode: 200,
   119  			body: `
   120  {
   121  	"Items": [
   122  	  {
   123  		"Level": "RequestResponse",
   124  		"AuditID": "2fca7950-03b6-41fa-95cd-08c5bcec8487",
   125  		"Stage": "ResponseComplete",
   126  		"RequestURI": "/api/v1/namespaces/default/pods?fieldManager=kubectl-client-side-apply\u0026fieldValidation=Strict",
   127  		"Verb": "create",
   128  		"User": {
   129  		  "username": "minikube-user",
   130  		  "groups": [
   131  			"system:masters",
   132  			"system:authenticated"
   133  		  ]
   134  		},
   135  		"ImpersonatedUser": null,
   136  		"SourceIPs": [
   137  		  "192.168.9.212"
   138  		],
   139  		"UserAgent": "kubectl.exe/v1.25.2 (windows/amd64) kubernetes/5835544",
   140  		"ObjectRef": {
   141  		  "Resource": "pods",
   142  		  "Namespace": "default",
   143  		  "Name": "test-pod-hostpath",
   144  		  "UID": "",
   145  		  "APIGroup": "",
   146  		  "APIVersion": "v1",
   147  		  "ResourceVersion": "",
   148  		  "Subresource": ""
   149  		},
   150  		"ResponseStatus": {
   151  		  "metadata": {},
   152  		  "code": 201
   153  		},
   154  		"RequestObject": {},
   155  		"ResponseObject": {},
   156  		"RequestReceivedTimestamp": "2022-09-26T15:24:52.316938Z",
   157  		"StageTimestamp": "2022-09-26T15:24:52.322575Z",
   158  		"Annotations": {
   159  		  "authorization.k8s.io/decision": "allow",
   160  		  "authorization.k8s.io/reason": "",
   161  		  "pod-security.kubernetes.io/enforce-policy": "privileged:latest"
   162  		}
   163  	  },
   164  	  {
   165  		"Level": "RequestResponse",
   166  		"AuditID": "2fca7950-03b6-41fa-95cd-08c5bcec8487",
   167  		"Stage": "ResponseComplete",
   168  		"RequestURI": "/api/v1/namespaces/default/pods?fieldManager=kubectl-client-side-apply\u0026fieldValidation=Strict",
   169  		"Verb": "create",
   170  		"User": {
   171  		  "username": "minikube-user",
   172  		  "groups": [
   173  			"system:masters",
   174  			"system:authenticated"
   175  		  ]
   176  		},
   177  		"ImpersonatedUser": null,
   178  		"SourceIPs": [
   179  		  "192.168.9.212"
   180  		],
   181  		"UserAgent": "kubectl.exe/v1.25.2 (windows/amd64) kubernetes/5835544",
   182  		"ObjectRef": {
   183  		  "Resource": "pods",
   184  		  "Namespace": "default",
   185  		  "Name": "test-pod-hostpath",
   186  		  "UID": "",
   187  		  "APIGroup": "",
   188  		  "APIVersion": "v1",
   189  		  "ResourceVersion": "",
   190  		  "Subresource": ""
   191  		},
   192  		"ResponseStatus": {
   193  		  "metadata": {},
   194  		  "code": 201
   195  		},
   196  		"RequestObject": {},
   197  		"ResponseObject": {},
   198  		"RequestReceivedTimestamp": "2022-09-26T15:24:52.316938Z",
   199  		"StageTimestamp": "2022-09-26T15:24:52.322575Z",
   200  		"Annotations": {
   201  		  "authorization.k8s.io/decision": "allow",
   202  		  "authorization.k8s.io/reason": "",
   203  		  "pod-security.kubernetes.io/enforce-policy": "privileged:latest"
   204  		}
   205  	  }
   206  	]
   207    }`,
   208  			eventCount: 2,
   209  		},
   210  		{
   211  			name: "invalid_json",
   212  			config: `source: k8s-audit
   213  listen_addr: 127.0.0.1
   214  listen_port: 49234
   215  webhook_path: /k8s-audit`,
   216  			expectedStatusCode: 500,
   217  			body:               "invalid json",
   218  			method:             "POST",
   219  			eventCount:         0,
   220  		},
   221  		{
   222  			name: "invalid_method",
   223  			config: `source: k8s-audit
   224  listen_addr: 127.0.0.1
   225  listen_port: 49234
   226  webhook_path: /k8s-audit`,
   227  			expectedStatusCode: 405,
   228  			method:             "GET",
   229  			eventCount:         0,
   230  		},
   231  	}
   232  
   233  	subLogger := log.WithFields(log.Fields{
   234  		"type": "k8s-audit",
   235  	})
   236  
   237  	for _, test := range tests {
   238  		t.Run(test.name, func(t *testing.T) {
   239  			out := make(chan types.Event)
   240  			tb := &tomb.Tomb{}
   241  			eventCount := 0
   242  
   243  			tb.Go(func() error {
   244  				for {
   245  					select {
   246  					case <-out:
   247  						eventCount++
   248  					case <-tb.Dying():
   249  						return nil
   250  					}
   251  				}
   252  			})
   253  
   254  			f := KubernetesAuditSource{}
   255  			err := f.UnmarshalConfig([]byte(test.config))
   256  			require.NoError(t, err)
   257  			err = f.Configure([]byte(test.config), subLogger, configuration.METRICS_NONE)
   258  
   259  			require.NoError(t, err)
   260  
   261  			req := httptest.NewRequest(test.method, "/k8s-audit", strings.NewReader(test.body))
   262  			w := httptest.NewRecorder()
   263  
   264  			f.StreamingAcquisition(out, tb)
   265  
   266  			f.webhookHandler(w, req)
   267  
   268  			res := w.Result()
   269  
   270  			assert.Equal(t, test.expectedStatusCode, res.StatusCode)
   271  			//time.Sleep(1 * time.Second)
   272  			require.NoError(t, err)
   273  
   274  			tb.Kill(nil)
   275  			err = tb.Wait()
   276  			require.NoError(t, err)
   277  
   278  			assert.Equal(t, test.eventCount, eventCount)
   279  		})
   280  	}
   281  }