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

     1  package dockeracquisition
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"runtime"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/crowdsecurity/go-cs-lib/cstest"
    15  
    16  	"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
    17  	"github.com/crowdsecurity/crowdsec/pkg/types"
    18  	dockerTypes "github.com/docker/docker/api/types"
    19  	dockerContainer "github.com/docker/docker/api/types/container"
    20  	"github.com/docker/docker/client"
    21  	log "github.com/sirupsen/logrus"
    22  	"gopkg.in/tomb.v2"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  )
    26  
    27  const testContainerName = "docker_test"
    28  
    29  var readLogs = false
    30  
    31  func TestConfigure(t *testing.T) {
    32  	log.Infof("Test 'TestConfigure'")
    33  
    34  	tests := []struct {
    35  		config      string
    36  		expectedErr string
    37  	}{
    38  		{
    39  			config:      `foobar: asd`,
    40  			expectedErr: "line 1: field foobar not found in type dockeracquisition.DockerConfiguration",
    41  		},
    42  		{
    43  			config: `
    44  mode: tail
    45  source: docker`,
    46  			expectedErr: "no containers names or containers ID configuration provided",
    47  		},
    48  		{
    49  			config: `
    50  mode: cat
    51  source: docker
    52  container_name:
    53   - toto`,
    54  			expectedErr: "",
    55  		},
    56  	}
    57  
    58  	subLogger := log.WithFields(log.Fields{
    59  		"type": "docker",
    60  	})
    61  
    62  	for _, test := range tests {
    63  		f := DockerSource{}
    64  		err := f.Configure([]byte(test.config), subLogger, configuration.METRICS_NONE)
    65  		cstest.AssertErrorContains(t, err, test.expectedErr)
    66  	}
    67  }
    68  
    69  func TestConfigureDSN(t *testing.T) {
    70  	log.Infof("Test 'TestConfigureDSN'")
    71  
    72  	var dockerHost string
    73  
    74  	if runtime.GOOS == "windows" {
    75  		dockerHost = "npipe:////./pipe/docker_engine"
    76  	} else {
    77  		dockerHost = "unix:///var/run/podman/podman.sock"
    78  	}
    79  
    80  	tests := []struct {
    81  		name        string
    82  		dsn         string
    83  		expectedErr string
    84  	}{
    85  		{
    86  			name:        "invalid DSN",
    87  			dsn:         "asd://",
    88  			expectedErr: "invalid DSN asd:// for docker source, must start with docker://",
    89  		},
    90  		{
    91  			name:        "empty DSN",
    92  			dsn:         "docker://",
    93  			expectedErr: "empty docker:// DSN",
    94  		},
    95  		{
    96  			name:        "DSN ok with log_level",
    97  			dsn:         "docker://test_docker?log_level=warn",
    98  			expectedErr: "",
    99  		},
   100  		{
   101  			name:        "DSN invalid log_level",
   102  			dsn:         "docker://test_docker?log_level=foobar",
   103  			expectedErr: "unknown level foobar: not a valid logrus Level:",
   104  		},
   105  		{
   106  			name:        "DSN ok with multiple parameters",
   107  			dsn:         fmt.Sprintf("docker://test_docker?since=42min&docker_host=%s", dockerHost),
   108  			expectedErr: "",
   109  		},
   110  	}
   111  	subLogger := log.WithFields(log.Fields{
   112  		"type": "docker",
   113  	})
   114  
   115  	for _, test := range tests {
   116  		f := DockerSource{}
   117  		err := f.ConfigureByDSN(test.dsn, map[string]string{"type": "testtype"}, subLogger, "")
   118  		cstest.AssertErrorContains(t, err, test.expectedErr)
   119  	}
   120  }
   121  
   122  type mockDockerCli struct {
   123  	client.Client
   124  }
   125  
   126  func TestStreamingAcquisition(t *testing.T) {
   127  	log.SetOutput(os.Stdout)
   128  	log.SetLevel(log.InfoLevel)
   129  	log.Info("Test 'TestStreamingAcquisition'")
   130  	tests := []struct {
   131  		config         string
   132  		expectedErr    string
   133  		expectedOutput string
   134  		expectedLines  int
   135  		logType        string
   136  		logLevel       log.Level
   137  	}{
   138  		{
   139  			config: `
   140  source: docker
   141  mode: cat
   142  container_name:
   143   - docker_test`,
   144  			expectedErr:    "",
   145  			expectedOutput: "",
   146  			expectedLines:  3,
   147  			logType:        "test",
   148  			logLevel:       log.InfoLevel,
   149  		},
   150  		{
   151  			config: `
   152  source: docker
   153  mode: cat
   154  container_name_regexp:
   155   - docker_*`,
   156  			expectedErr:    "",
   157  			expectedOutput: "",
   158  			expectedLines:  3,
   159  			logType:        "test",
   160  			logLevel:       log.InfoLevel,
   161  		},
   162  	}
   163  
   164  	for _, ts := range tests {
   165  		var (
   166  			logger    *log.Logger
   167  			subLogger *log.Entry
   168  		)
   169  
   170  		if ts.expectedOutput != "" {
   171  			logger.SetLevel(ts.logLevel)
   172  			subLogger = logger.WithFields(log.Fields{
   173  				"type": "docker",
   174  			})
   175  		} else {
   176  			subLogger = log.WithFields(log.Fields{
   177  				"type": "docker",
   178  			})
   179  		}
   180  
   181  		readLogs = false
   182  		dockerTomb := tomb.Tomb{}
   183  		out := make(chan types.Event)
   184  		dockerSource := DockerSource{}
   185  
   186  		err := dockerSource.Configure([]byte(ts.config), subLogger, configuration.METRICS_NONE)
   187  		if err != nil {
   188  			t.Fatalf("Unexpected error : %s", err)
   189  		}
   190  
   191  		dockerSource.Client = new(mockDockerCli)
   192  		actualLines := 0
   193  		readerTomb := &tomb.Tomb{}
   194  		streamTomb := tomb.Tomb{}
   195  		streamTomb.Go(func() error {
   196  			return dockerSource.StreamingAcquisition(out, &dockerTomb)
   197  		})
   198  		readerTomb.Go(func() error {
   199  			time.Sleep(1 * time.Second)
   200  			ticker := time.NewTicker(1 * time.Second)
   201  			for {
   202  				select {
   203  				case <-out:
   204  					actualLines++
   205  					ticker.Reset(1 * time.Second)
   206  				case <-ticker.C:
   207  					log.Infof("no more lines to read")
   208  					dockerSource.t.Kill(nil)
   209  					return nil
   210  				}
   211  			}
   212  		})
   213  		cstest.AssertErrorContains(t, err, ts.expectedErr)
   214  
   215  		if err := readerTomb.Wait(); err != nil {
   216  			t.Fatal(err)
   217  		}
   218  
   219  		if ts.expectedLines != 0 {
   220  			assert.Equal(t, ts.expectedLines, actualLines)
   221  		}
   222  
   223  		err = streamTomb.Wait()
   224  		if err != nil {
   225  			t.Fatalf("docker acquisition error: %s", err)
   226  		}
   227  	}
   228  }
   229  
   230  func (cli *mockDockerCli) ContainerList(ctx context.Context, options dockerTypes.ContainerListOptions) ([]dockerTypes.Container, error) {
   231  	if readLogs == true {
   232  		return []dockerTypes.Container{}, nil
   233  	}
   234  
   235  	containers := make([]dockerTypes.Container, 0)
   236  	container := &dockerTypes.Container{
   237  		ID:    "12456",
   238  		Names: []string{testContainerName},
   239  	}
   240  	containers = append(containers, *container)
   241  
   242  	return containers, nil
   243  }
   244  
   245  func (cli *mockDockerCli) ContainerLogs(ctx context.Context, container string, options dockerTypes.ContainerLogsOptions) (io.ReadCloser, error) {
   246  	if readLogs == true {
   247  		return io.NopCloser(strings.NewReader("")), nil
   248  	}
   249  
   250  	readLogs = true
   251  	data := []string{"docker\n", "test\n", "1234\n"}
   252  	ret := ""
   253  
   254  	for _, line := range data {
   255  		startLineByte := make([]byte, 8)
   256  		binary.LittleEndian.PutUint32(startLineByte, 1) //stdout stream
   257  		binary.BigEndian.PutUint32(startLineByte[4:], uint32(len(line)))
   258  		ret += fmt.Sprintf("%s%s", startLineByte, line)
   259  	}
   260  
   261  	r := io.NopCloser(strings.NewReader(ret)) // r type is io.ReadCloser
   262  
   263  	return r, nil
   264  }
   265  
   266  func (cli *mockDockerCli) ContainerInspect(ctx context.Context, c string) (dockerTypes.ContainerJSON, error) {
   267  	r := dockerTypes.ContainerJSON{
   268  		Config: &dockerContainer.Config{
   269  			Tty: false,
   270  		},
   271  	}
   272  
   273  	return r, nil
   274  }
   275  
   276  func TestOneShot(t *testing.T) {
   277  	log.Infof("Test 'TestOneShot'")
   278  
   279  	tests := []struct {
   280  		dsn            string
   281  		expectedErr    string
   282  		expectedOutput string
   283  		expectedLines  int
   284  		logType        string
   285  		logLevel       log.Level
   286  	}{
   287  		{
   288  			dsn:            "docker://non_exist_docker",
   289  			expectedErr:    "no container found named: non_exist_docker, can't run one shot acquisition",
   290  			expectedOutput: "",
   291  			expectedLines:  0,
   292  			logType:        "test",
   293  			logLevel:       log.InfoLevel,
   294  		},
   295  		{
   296  			dsn:            "docker://" + testContainerName,
   297  			expectedErr:    "",
   298  			expectedOutput: "",
   299  			expectedLines:  3,
   300  			logType:        "test",
   301  			logLevel:       log.InfoLevel,
   302  		},
   303  	}
   304  
   305  	for _, ts := range tests {
   306  		var (
   307  			subLogger *log.Entry
   308  			logger    *log.Logger
   309  		)
   310  
   311  		if ts.expectedOutput != "" {
   312  			logger.SetLevel(ts.logLevel)
   313  			subLogger = logger.WithFields(log.Fields{
   314  				"type": "docker",
   315  			})
   316  		} else {
   317  			log.SetLevel(ts.logLevel)
   318  			subLogger = log.WithFields(log.Fields{
   319  				"type": "docker",
   320  			})
   321  		}
   322  
   323  		readLogs = false
   324  		dockerClient := &DockerSource{}
   325  		labels := make(map[string]string)
   326  		labels["type"] = ts.logType
   327  
   328  		if err := dockerClient.ConfigureByDSN(ts.dsn, labels, subLogger, ""); err != nil {
   329  			t.Fatalf("unable to configure dsn '%s': %s", ts.dsn, err)
   330  		}
   331  
   332  		dockerClient.Client = new(mockDockerCli)
   333  		out := make(chan types.Event, 100)
   334  		tomb := tomb.Tomb{}
   335  		err := dockerClient.OneShotAcquisition(out, &tomb)
   336  		cstest.AssertErrorContains(t, err, ts.expectedErr)
   337  
   338  		// else we do the check before actualLines is incremented ...
   339  		if ts.expectedLines != 0 {
   340  			assert.Len(t, out, ts.expectedLines)
   341  		}
   342  	}
   343  }