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 }