Files
Naomi Kramer b923c39e2d Update configuration (#78)
* Update on config structure, functionality, and tests

Co-Authored-By: Naomi Kramer <naomiagoddard@gmail.com>

* Extend subnet type to read/write from db, update tests

Co-Authored-By: Liza Tsibur <liza@activecountermeasures.com>

* updated read file config test and subnet tests

* fixed config and util tests, updated subnet related functions

Co-Authored-By: Naomi Kramer <naomiagoddard@gmail.com>

* Remove error return from GetDefaultConfig

Co-Authored-By: Liza Tsibur <liza@activecountermeasures.com>

* added json tags to database struct

* Updated beacon weights validation for config

* updates to score thresholds validation tags

* changes to config subnet validation and testing

* Update subnet.go

* Write missing host entries to http to populate http_proto

* Updating some fields to uint64

* WIP update some field types

* Update zeek count types and fix tests

* Add clickhouse credentials

* Misc fixes

* Update pointer

* Add ability to mark datasets as sample datasets

* fix column name

* Fix datasets exiting import if hour is empty

* Fix zeek count parsing from TSV files

* Remove storing dns conns in arrays, Fix historical first seen dns lag

* Remove unused columns

* Update config.hjson

* Update config.hjson

* updated impact category score functions to use float64

Co-Authored-By: Naomi Kramer <naomiagoddard@gmail.com>

* Update subnet.go

* Store import version in imports table

* Fix duplicated SNI/IP long connections

* Update subnet_test.go

* Cleanup output

* Rolling files updates (#39)

* Limit number of days to import for rolling datasets

* Fix breaking imports when import was interrupted

* Remove debug output

---------

Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com>

* Omit parts of env from output

* Set max for threat intel datasize

* Remove SELinux neutering for QA

* Add network size column

* Fix http_proto for missing host, update tests for missing host fixes

* Add online feeds to default config

* Update sshprep (#45)

* Update sshprep

Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com>

* Update sshprep

Add Bradley's suggestion of using head -1 to limit to a single address.

---------

Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com>
Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com>

* Installer Behavior Tweaks (#41)

* Add --yes flag to add-apt-repository command

* Add missing sudo flags, make sure we're using the SUDO variable instead

* Add ability to perform zone transfers (#48)

* Store zone transfer records

Co-Authored-By: moth <25512187+0x6d6f7468@users.noreply.github.com>

* Update config

* Add tests

* Tests, connectivity test

* Update tests

---------

Co-authored-by: moth <25512187+0x6d6f7468@users.noreply.github.com>

* Support RedHat/RHEL as a valid target (#47)

* Update sshprep

Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com>

* Supporrt RedHat/RHEL as a valid target

---------

Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com>
Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com>
Co-authored-by: moth <moth@blackhillsinfosec.com>

* Fix tests (#49)

* Fix tests

* Update WalkFiles to use UTC

* fixed issue with rolling datasets over 24hours old not getting historical first seen timestamp set (#52)

* Change values from float32 to float64 (#50)

* Switch float32 to float64

* Update threat category calculation to match CalculateBucketedScore (#51)

---------

Co-authored-by: Liza Tsibur <liza@activecountermeasures.com>

* Bump max query execution time default value

* Use string instead of error for ZoneTransferConnectivityErrors struct fields (#61)

* Upgrade Golang to version 1.24 (#59) (#60)

* Replace get_url with shell and curl (#58)

* Update sshprep

Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com>

* Replace get_url with shell and curl

* Use get_url by default, fall back to curl if it fails

---------

Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com>
Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com>
Co-authored-by: moth <moth@blackhillsinfosec.com>

* add automated log transfer, AC-Hunter issue 135 (#62)

* Update sshprep

Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com>

* add automated log transfer, PR135

* cron requires non-executable permission

* Specify suggested YAML plugin and config in VSCode workspace

* Linting and light cleanup

* Update generate_installer.sh

Download zeek_log_transport.sh to send to the sensor.

* Create cron file if remote zeek installation

* Only run zeek log import steps for remote sensor installations

---------

Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com>
Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com>
Co-authored-by: moth <moth@blackhillsinfosec.com>

* Temporarily disable RITA/Zeek log transport until installer is modular (#66)

* Uniform -y flag usage for repo management/package installation; Uniform SUDO variable usage (#68)

* Resolve Installer Side Effects and Formalize RHEL Support (#73)

* Add missing necessary wildcards for RHEL versions

* Remove Ansible task replacing python3-requests to avoid RHEL distro installation side effects

* Update supported distros in README

* Update scoring defaults

* Resolve Ansible Reboot Errors (#75)

* Clean up conditionals; Fix reboot step for Ubuntu

* Suppress erroneous error output on RPM systems, ignore errors on reboot necessity checks

* Ignore missing host rows for openhttp (#76)

* Fix integration tests due to prevalence (#77)

---------

Co-authored-by: Liza Tsibur <liza@activecountermeasures.com>
Co-authored-by: moth <moth@blackhillsinfosec.com>
Co-authored-by: William Stearns <william.l.stearns@gmail.com>
Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com>
Co-authored-by: moth <25512187+0x6d6f7468@users.noreply.github.com>
2025-09-22 16:21:05 -04:00

674 lines
24 KiB
Go

package importer
import (
"context"
"errors"
"fmt"
"log"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/activecm/rita/v5/config"
c "github.com/activecm/rita/v5/constants"
"github.com/activecm/rita/v5/database"
"github.com/activecm/rita/v5/importer/zeektypes"
zlog "github.com/activecm/rita/v5/logger"
"github.com/activecm/rita/v5/progressbar"
"github.com/activecm/rita/v5/util"
"github.com/charmbracelet/bubbles/progress"
"github.com/spf13/afero"
"github.com/vbauerster/mpb/v8"
"github.com/vbauerster/mpb/v8/decor"
"golang.org/x/sync/errgroup"
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/time/rate"
)
var ErrAllFilesPreviouslyImported = errors.New("all files were previously imported")
type zeekRecord interface {
zeektypes.Conn | zeektypes.DNS | zeektypes.HTTP | zeektypes.SSL
}
type Importer struct {
Cfg *config.Config
Database *database.DB
ImportID util.FixedString
LogDirectory string
FileMap map[string][]string
EntryChannels EntryChans
MetaDBChannel chan MetaDBFile
Paths chan string
ErrChannel chan error
TotalFileCount int
DoneChannels DoneChans
Writers writers
WriteLimiter *rate.Limiter
ProgressBar *mpb.Progress
FileProgressBar *mpb.Bar
ProgressLogger *log.Logger
HTTPLinkMutex sync.Mutex
OpenHTTPLinkMutex sync.Mutex
NumParsers int
NumDigesters int
NumWriters int
ResultCounts ResultCounts
wg WaitGroups
importStartedCallback func(util.FixedString) error
validateLogFilesCallback func(map[string][]string) (int, error)
startWritersCallback func(int)
closeWritersCallback func()
markFileImportedCallback func(util.FixedString, util.FixedString, string) error
}
type EntryChans struct {
Conn chan zeektypes.Conn
OpenConn chan zeektypes.Conn
DNS chan zeektypes.DNS
HTTP chan zeektypes.HTTP
OpenHTTP chan zeektypes.HTTP
SSL chan zeektypes.SSL
OpenSSL chan zeektypes.SSL
}
type writers struct {
ConnTmp *database.BulkWriter
OpenConnTmp *database.BulkWriter
DNS *database.BulkWriter
PDNS *database.BulkWriter
HTTPTmp *database.BulkWriter
OpenHTTPTmp *database.BulkWriter
SSLTmp *database.BulkWriter
OpenSSLTmp *database.BulkWriter
}
type DoneChans struct {
filesDone chan struct{}
conn chan struct{}
openconn chan struct{}
http chan struct{}
openhttp chan struct{}
dns chan struct{}
ssl chan struct{}
openssl chan struct{}
}
type ResultCounts struct {
ZeekUIDs uint64
OpenZeekUIDs uint64
UnfilteredConn uint64
Conn uint64
OpenConn uint64
HTTP uint64
OpenHTTP uint64
DNS uint64
UDNS int64
PDNSRaw uint64
SSL uint64
OpenSSL uint64
}
type WaitGroups struct {
Digester sync.WaitGroup
MetaDB sync.WaitGroup
OpenConn sync.WaitGroup
Conn sync.WaitGroup
DNS sync.WaitGroup
HTTP sync.WaitGroup
OpenHTTP sync.WaitGroup
SSL sync.WaitGroup
OpenSSL sync.WaitGroup
}
// NewImporter creates and returns a new Importer object
func NewImporter(db *database.DB, cfg *config.Config, importStartedAt time.Time, numDigesters int, numParsers int, numWriters int) (*Importer, error) {
logger := zlog.GetLogger()
// create channels to hold the network traffic entries
entryChannels := EntryChans{
Conn: make(chan zeektypes.Conn, 1000),
OpenConn: make(chan zeektypes.Conn, 1000),
DNS: make(chan zeektypes.DNS, 1000),
HTTP: make(chan zeektypes.HTTP, 1000),
OpenHTTP: make(chan zeektypes.HTTP, 1000),
SSL: make(chan zeektypes.SSL, 1000),
OpenSSL: make(chan zeektypes.SSL, 1000),
}
// create channels to keep track of log files being successfully imported
doneChannels := DoneChans{
filesDone: make(chan struct{}),
conn: make(chan struct{}, numDigesters),
openconn: make(chan struct{}, numDigesters),
http: make(chan struct{}, numDigesters),
openhttp: make(chan struct{}, numDigesters),
dns: make(chan struct{}, numDigesters),
ssl: make(chan struct{}, numDigesters),
openssl: make(chan struct{}, numDigesters),
}
// create a rate limiter to control the rate of writing to the database
limiter := rate.NewLimiter(5, 5)
// create writer objects to write output data to the individual log collections
logWriters := writers{
ConnTmp: database.NewBulkWriter(db, cfg, numWriters, db.GetSelectedDB(), "conn_tmp", "INSERT INTO {database:Identifier}.conn_tmp", limiter, false),
OpenConnTmp: database.NewBulkWriter(db, cfg, numWriters, db.GetSelectedDB(), "openconn_tmp", "INSERT INTO {database:Identifier}.openconn_tmp", limiter, false),
DNS: database.NewBulkWriter(db, cfg, numWriters, db.GetSelectedDB(), "dns", "INSERT INTO {database:Identifier}.dns", limiter, false),
PDNS: database.NewBulkWriter(db, cfg, numWriters, db.GetSelectedDB(), "pdns", "INSERT INTO {database:Identifier}.pdns_raw", limiter, false),
HTTPTmp: database.NewBulkWriter(db, cfg, numWriters, db.GetSelectedDB(), "http_tmp", "INSERT INTO {database:Identifier}.http_tmp", limiter, false),
OpenHTTPTmp: database.NewBulkWriter(db, cfg, numWriters, db.GetSelectedDB(), "openhttp_tmp", "INSERT INTO {database:Identifier}.openhttp_tmp", limiter, false),
SSLTmp: database.NewBulkWriter(db, cfg, numWriters, db.GetSelectedDB(), "ssl_tmp", "INSERT INTO {database:Identifier}.ssl_tmp", limiter, false),
OpenSSLTmp: database.NewBulkWriter(db, cfg, numWriters, db.GetSelectedDB(), "openssl_tmp", "INSERT INTO {database:Identifier}.openssl_tmp", limiter, false),
}
// create progressBar bar
progressBar := mpb.New(mpb.WithWidth(64))
// set the overall db import start time
db.ImportStartedAt = importStartedAt
// create a unique import id using the start time
importID, err := util.NewFixedStringHash(strconv.FormatInt(importStartedAt.UnixMicro(), 10))
if err != nil {
return nil, err
}
// log the import id
logger.Debug().Str("import_id", importID.Hex()).Send()
// return the importer object
return &Importer{
Database: db,
Cfg: cfg,
ImportID: importID,
// LogDirectory: directory,
FileMap: make(map[string][]string),
EntryChannels: entryChannels,
MetaDBChannel: make(chan MetaDBFile),
Paths: make(chan string, 10),
ErrChannel: make(chan error, 100),
DoneChannels: doneChannels,
Writers: logWriters,
WriteLimiter: rate.NewLimiter(5, 5),
ProgressBar: progressBar,
ProgressLogger: log.New(progressBar, "", 0),
NumParsers: numParsers,
NumDigesters: numDigesters,
NumWriters: numWriters,
ResultCounts: ResultCounts{},
importStartedCallback: db.AddImportStartRecordToMetaDB,
validateLogFilesCallback: db.CheckIfFilesWereAlreadyImported,
startWritersCallback: logWriters.startWriters,
closeWritersCallback: logWriters.closeWriters,
markFileImportedCallback: db.MarkFileImportedInMetaDB,
}, nil
}
func (importer *Importer) Import(afs afero.Fs, files map[string][]string) error {
logger := zlog.GetLogger()
// record the hourlyImportStart time of this import chunk
hourlyImportStart := time.Now()
// check if files have already been imported make a map of the remaining files
totalFileCount, err := importer.validateLogFilesCallback(files)
if err != nil {
return err
}
// verify that there are still files left to import and set file count
if totalFileCount < 1 {
return ErrAllFilesPreviouslyImported
}
importer.TotalFileCount = totalFileCount
// set up the file map with the remaining files
importer.FileMap = files
// add import started record to metadatabase
err = importer.importStartedCallback(importer.ImportID)
if err != nil {
return err
}
// initialize progress bar
importer.FileProgressBar = importer.ProgressBar.New(int64(importer.TotalFileCount),
mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"),
mpb.PrependDecorators(
// display our name with one space on the right
decor.Name("Log Parsing", decor.WC{C: decor.DindentRight | decor.DextraSpace}),
// replace ETA decorator with "done" message, OnComplete event
decor.OnComplete(decor.Elapsed(decor.ET_STYLE_GO), "🎉"),
),
mpb.AppendDecorators(decor.CountersNoUnit("%d / %d")),
)
// start the import
importer.process(afs)
// record import time to logger
hourlyImportEnd := time.Now()
logger.Info().Time("parsing_began", hourlyImportStart).Time("parsing_finished", hourlyImportEnd).Str("elapsed_time", time.Since(hourlyImportStart).String()).Msg("Finished Parsing Logs! 🎉")
if err := importer.season(); err != nil {
return err
}
seasoningEnd := time.Now()
logger.Info().Time("seasoning_began", hourlyImportEnd).Time("seasoning_finished", seasoningEnd).Str("elapsed_time", time.Since(hourlyImportEnd).String()).Msg("Finished Seasoning Logs! 🎉")
// create formatter for adding commas in the counts
p := message.NewPrinter(language.English)
logger.Debug().Str("count", p.Sprintf("%d", importer.ResultCounts.Conn)).Msg("Imported conn records")
logger.Debug().Str("count", p.Sprintf("%d", importer.ResultCounts.OpenConn)).Msg("Imported open conn records")
logger.Debug().Str("count", p.Sprintf("%d", importer.ResultCounts.DNS)).Msg("Imported dns records")
logger.Debug().Str("count", p.Sprintf("%d", importer.ResultCounts.PDNSRaw)).Msg("Imported pdns raw records")
logger.Debug().Str("count", p.Sprintf("%d", importer.ResultCounts.HTTP)).Msg("Imported http records")
logger.Debug().Str("count", p.Sprintf("%d", importer.ResultCounts.OpenHTTP)).Msg("Imported open http records")
logger.Debug().Str("count", p.Sprintf("%d", importer.ResultCounts.SSL)).Msg("Imported ssl records")
logger.Debug().Str("count", p.Sprintf("%d", importer.ResultCounts.OpenSSL)).Msg("Imported open ssl records")
return nil
}
// process loads the files and parses the raw log entries
func (importer *Importer) process(afs afero.Fs) {
// initialize writers
importer.startWritersCallback(importer.NumWriters)
// start goroutines to write network traffic entries to the database
importer.startParseRoutines()
// start goroutines to parse log file contents
importer.startDigesters(afs)
// start goroutine to mark files as imported in MetaDB
importer.startMetaDBFileTracker()
// start listener routine and feed initial files in to paths channel
importer.feedAndListenForFileCompletion()
// close log entry and map channels
go func() {
// wait for our bar to complete and flush
importer.ProgressBar.Wait()
// close log entry channels
close(importer.EntryChannels.Conn)
close(importer.EntryChannels.OpenConn)
close(importer.EntryChannels.DNS)
close(importer.EntryChannels.HTTP)
close(importer.EntryChannels.OpenHTTP)
close(importer.EntryChannels.SSL)
close(importer.EntryChannels.OpenSSL)
// close paths channel
close(importer.Paths)
// close metadb channel
close(importer.MetaDBChannel)
}()
// wait for log routine groups
importer.wg.Conn.Wait()
importer.wg.OpenConn.Wait()
importer.wg.DNS.Wait()
importer.wg.HTTP.Wait()
importer.wg.OpenHTTP.Wait()
importer.wg.SSL.Wait()
importer.wg.OpenSSL.Wait()
close(importer.DoneChannels.conn)
close(importer.DoneChannels.openconn)
close(importer.DoneChannels.http)
close(importer.DoneChannels.openhttp)
close(importer.DoneChannels.ssl)
close(importer.DoneChannels.openssl)
close(importer.DoneChannels.dns)
close(importer.DoneChannels.filesDone)
close(importer.ErrChannel)
// close writers
importer.closeWritersCallback()
}
// startParseRoutines starts a fixed number of goroutines to parse lines from logs into data to be written to the db.
func (importer *Importer) startParseRoutines() {
importer.wg.Conn.Add(importer.NumParsers)
importer.wg.OpenConn.Add(importer.NumParsers)
importer.wg.DNS.Add(importer.NumParsers)
importer.wg.HTTP.Add(importer.NumParsers)
importer.wg.OpenHTTP.Add(importer.NumParsers)
importer.wg.SSL.Add(importer.NumParsers)
importer.wg.OpenSSL.Add(importer.NumParsers)
for i := 0; i < importer.NumParsers; i++ {
go func(_ int) {
// parseConn(importer.EntryChannels.Conn, importer.Writers.Conn.WriteChannel, importer.UniqueMaps.Uconn, importer.UniqueMaps.ZeekUIDs, importer.ImportID, &importer.ResultCounts.Conn)
parseConn(importer.Cfg, importer.EntryChannels.Conn, importer.Writers.ConnTmp.WriteChannel, importer.ImportID, importer.Database.ImportStartedAt, &importer.ResultCounts.Conn)
importer.wg.Conn.Done()
}(i)
go func(_ int) {
// parseConn(importer.EntryChannels.OpenConn, importer.Writers.OpenConn.WriteChannel, importer.UniqueMaps.OpenConn, importer.UniqueMaps.OpenZeekUIDs, importer.ImportID, &importer.ResultCounts.OpenConn)
parseConn(importer.Cfg, importer.EntryChannels.OpenConn, importer.Writers.OpenConnTmp.WriteChannel, importer.ImportID, importer.Database.ImportStartedAt, &importer.ResultCounts.OpenConn)
importer.wg.OpenConn.Done()
}(i)
go func(_ int) {
parseDNS(importer.Cfg, importer.EntryChannels.DNS, importer.Writers.DNS.WriteChannel, importer.Writers.PDNS.WriteChannel, &importer.ResultCounts.DNS, &importer.ResultCounts.PDNSRaw, importer.Database.ImportStartedAt)
importer.wg.DNS.Done()
}(i)
go func(_ int) {
parseHTTP(importer.Cfg, importer.EntryChannels.HTTP, importer.Writers.HTTPTmp.WriteChannel, importer.Database.ImportStartedAt, &importer.ResultCounts.HTTP, &importer.ResultCounts.Conn)
importer.wg.HTTP.Done()
}(i)
go func(_ int) {
parseHTTP(importer.Cfg, importer.EntryChannels.OpenHTTP, importer.Writers.OpenHTTPTmp.WriteChannel, importer.Database.ImportStartedAt, &importer.ResultCounts.OpenHTTP, &importer.ResultCounts.OpenConn)
importer.wg.OpenHTTP.Done()
}(i)
go func(_ int) {
parseSSL(importer.Cfg, importer.EntryChannels.SSL, importer.Writers.SSLTmp.WriteChannel, importer.Database.ImportStartedAt, &importer.ResultCounts.SSL)
importer.wg.SSL.Done()
}(i)
go func(_ int) {
parseSSL(importer.Cfg, importer.EntryChannels.OpenSSL, importer.Writers.OpenSSLTmp.WriteChannel, importer.Database.ImportStartedAt, &importer.ResultCounts.OpenSSL)
importer.wg.OpenSSL.Done()
}(i)
}
}
// startDigesters starts a fixed number of goroutines to read and digest files.
func (importer *Importer) startDigesters(afs afero.Fs) {
importer.wg.Digester.Add(importer.NumDigesters)
for i := 0; i < importer.NumDigesters; i++ {
go func(_ int) {
digester(afs, importer.DoneChannels, importer.Paths, importer.ErrChannel, importer.EntryChannels, importer.MetaDBChannel, importer.Database.GetSelectedDB(), importer.ImportID, importer.ProgressLogger)
importer.wg.Digester.Done()
}(i)
}
}
// startMetaDBFileTracker starts a goroutine to mark files as imported in MetaDB
func (importer *Importer) startMetaDBFileTracker() {
importer.wg.MetaDB.Add(1)
go func() {
for metaDB := range importer.MetaDBChannel {
err := importer.markFileImportedCallback(metaDB.fileHash, metaDB.importID, metaDB.path)
if err != nil {
importer.ProgressLogger.Println("[WARNING] could not mark file as imported, path:", metaDB.path, err)
}
}
importer.wg.MetaDB.Done()
}()
}
// feedAndListenForFileCompletion feeds files to the paths channel and listens for the completion of each log type
// to orchestrate feeding other log types into the paths channel
func (importer *Importer) feedAndListenForFileCompletion() {
// listen for file completion of each log type
go func() {
for importer.FileProgressBar.Current() < int64(importer.TotalFileCount) {
select {
// read from other log types' done channels so that they don't block
case <-importer.DoneChannels.conn:
case <-importer.DoneChannels.openconn:
case <-importer.DoneChannels.http:
case <-importer.DoneChannels.openhttp:
case <-importer.DoneChannels.ssl:
case <-importer.DoneChannels.openssl:
case <-importer.DoneChannels.dns:
// increment progress bar
case <-importer.DoneChannels.filesDone:
importer.FileProgressBar.Increment()
default:
}
}
}()
if len(importer.FileMap[c.ConnPrefix]) > 0 {
for _, connLog := range importer.FileMap[c.ConnPrefix] {
importer.Paths <- connLog
}
for _, httpLog := range importer.FileMap[c.HTTPPrefix] {
importer.Paths <- httpLog
}
for _, sslLog := range importer.FileMap[c.SSLPrefix] {
importer.Paths <- sslLog
}
}
if len(importer.FileMap[c.OpenConnPrefix]) > 0 {
for _, openConnLog := range importer.FileMap[c.OpenConnPrefix] {
importer.Paths <- openConnLog
}
for _, openHTTPLog := range importer.FileMap[c.OpenHTTPPrefix] {
importer.Paths <- openHTTPLog
}
for _, openSSLLog := range importer.FileMap[c.OpenSSLPrefix] {
importer.Paths <- openSSLLog
}
}
for _, dnsLog := range importer.FileMap[c.DNSPrefix] {
importer.Paths <- dnsLog
}
}
// digester loops over the paths, checks the file prefix, and sends each path to the parser with its corresponding entryChannel until either paths or done is closed.
func digester(afs afero.Fs, done DoneChans, paths <-chan string, errc chan error, entryChannels EntryChans, metaDBChan chan<- MetaDBFile, dbName string, importID util.FixedString, progressLogger *log.Logger) {
// errc := make(chan error)
// read entries from err channel, handle specific errors if necessary
// currently, this err channel is primarily used for checking errors in tests
go func() {
for err := range errc {
_ = err
}
}()
// loop over paths and send to parseFiles with the correct corresponding entryChannels, sending a done signal for each completed file
for path := range paths {
progressLogger.Println("[-] Parsing: ", path)
switch {
case strings.HasPrefix(filepath.Base(path), c.ConnPrefix):
parseFile(afs, path, entryChannels.Conn, errc, metaDBChan, dbName, importID)
done.conn <- struct{}{}
case strings.HasPrefix(filepath.Base(path), c.OpenConnPrefix):
parseFile(afs, path, entryChannels.OpenConn, errc, metaDBChan, dbName, importID)
done.openconn <- struct{}{}
case strings.HasPrefix(filepath.Base(path), c.DNSPrefix):
parseFile(afs, path, entryChannels.DNS, errc, metaDBChan, dbName, importID)
done.dns <- struct{}{}
case strings.HasPrefix(filepath.Base(path), c.HTTPPrefix):
parseFile(afs, path, entryChannels.HTTP, errc, metaDBChan, dbName, importID)
done.http <- struct{}{}
case strings.HasPrefix(filepath.Base(path), c.OpenHTTPPrefix):
parseFile(afs, path, entryChannels.OpenHTTP, errc, metaDBChan, dbName, importID)
done.openhttp <- struct{}{}
case strings.HasPrefix(filepath.Base(path), c.SSLPrefix):
parseFile(afs, path, entryChannels.SSL, errc, metaDBChan, dbName, importID)
done.ssl <- struct{}{}
case strings.HasPrefix(filepath.Base(path), c.OpenSSLPrefix):
parseFile(afs, path, entryChannels.OpenSSL, errc, metaDBChan, dbName, importID)
done.openssl <- struct{}{}
}
done.filesDone <- struct{}{}
}
}
// startWriters start a fixed number of writer workers for each table to write to
func (writer *writers) startWriters(numWriters int) {
for i := 0; i < numWriters; i++ {
writer.ConnTmp.Start(i)
writer.OpenConnTmp.Start(i)
writer.DNS.Start(i)
writer.PDNS.Start(i)
writer.HTTPTmp.Start(i)
writer.OpenHTTPTmp.Start(i)
writer.SSLTmp.Start(i)
writer.OpenSSLTmp.Start(i)
}
}
// closeWriters close each writer
func (writer *writers) closeWriters() {
writer.ConnTmp.Close()
writer.OpenConnTmp.Close()
writer.DNS.Close()
writer.PDNS.Close()
writer.HTTPTmp.Close()
writer.OpenHTTPTmp.Close()
writer.SSLTmp.Close()
writer.OpenSSLTmp.Close()
}
// season links the http & ssl logs with the conn logs and adds data to those connections
func (importer *Importer) season() error {
logger := zlog.GetLogger()
limiter := rate.NewLimiter(5, 5)
writerWorkers := 2
sslWriter := database.NewBulkWriter(importer.Database, importer.Cfg, writerWorkers, importer.Database.GetSelectedDB(), "ssl", "INSERT INTO {database:Identifier}.ssl", limiter, false)
openSSLWriter := database.NewBulkWriter(importer.Database, importer.Cfg, writerWorkers, importer.Database.GetSelectedDB(), "openssl", "INSERT INTO {database:Identifier}.openssl", limiter, false)
httpWriter := database.NewBulkWriter(importer.Database, importer.Cfg, writerWorkers, importer.Database.GetSelectedDB(), "http", "INSERT INTO {database:Identifier}.http", limiter, false)
connWriter := database.NewBulkWriter(importer.Database, importer.Cfg, writerWorkers, importer.Database.GetSelectedDB(), "conn", "INSERT INTO {database:Identifier}.conn", limiter, false)
openHTTPWriter := database.NewBulkWriter(importer.Database, importer.Cfg, writerWorkers, importer.Database.GetSelectedDB(), "openhttp", "INSERT INTO {database:Identifier}.openhttp", limiter, false)
openConnWriter := database.NewBulkWriter(importer.Database, importer.Cfg, writerWorkers, importer.Database.GetSelectedDB(), "openconn", "INSERT INTO {database:Identifier}.openconn", limiter, false)
for i := 0; i < writerWorkers; i++ {
sslWriter.Start(i)
openSSLWriter.Start(i)
httpWriter.Start(i)
openHTTPWriter.Start(i)
connWriter.Start(i)
openConnWriter.Start(i)
}
linkingErrGroup, ctx := errgroup.WithContext(context.Background())
gradient := progress.WithGradient("#f67a70", "#ffda6d")
var barList []*progressbar.ProgressBar
var spinners []progressbar.Spinner
sslBarName := "🧂 Seasoning SSL connections "
httpBarName := "🧂 Seasoning HTTP connections"
connSpinnerID := 0
openConnSpinnerID := 0
const (
httpID = iota + 1
openHTTPID
sslID
openSSLID
)
if importer.ResultCounts.OpenConn > 0 {
spinners = append(spinners, progressbar.NewSpinner("Sifting open IP connections...", openConnSpinnerID))
connSpinnerID = 1
if importer.ResultCounts.OpenSSL > 0 {
barList = append(barList, progressbar.NewBar("🧂 Seasoning open SSL connections ", openSSLID, progress.New(gradient)))
sslBarName += " "
}
if importer.ResultCounts.OpenHTTP > 0 {
barList = append(barList, progressbar.NewBar("🧂 Seasoning open HTTP connections", openHTTPID, progress.New(gradient)))
httpBarName += " "
}
}
barList = append(barList,
progressbar.NewBar(sslBarName, sslID, progress.New(gradient)),
progressbar.NewBar(httpBarName, httpID, progress.New(gradient)))
spinners = append(spinners, progressbar.NewSpinner("Sifting IP connections...", connSpinnerID))
bars := progressbar.New(ctx, barList, spinners)
linkingErrGroup.Go(func() error {
_, err := bars.Run()
if err != nil {
logger.Error().Err(err).Msg("unable to display progress for connection correlation")
return fmt.Errorf("unable to display progress for connection correlation: %w", err)
}
return err
})
linkingErrGroup.Go(func() error {
err := importer.writeLinkedHTTP(ctx, bars, httpID, httpWriter, connWriter, false)
if err != nil {
logger.Error().Err(err).Msg("unable to link http connections")
}
return err
})
linkingErrGroup.Go(func() error {
err := importer.writeLinkedSSL(ctx, bars, sslID, sslWriter, false)
if err != nil {
logger.Error().Err(err).Msg("unable to link ssl connections")
}
return err
})
linkingErrGroup.Go(func() error {
err := importer.writeUnfilteredConns(bars, false, connSpinnerID)
if err != nil {
logger.Error().Err(err).Msg("unable to link IP connections")
}
return err
})
if importer.ResultCounts.OpenConn > 0 {
linkingErrGroup.Go(func() error {
err := importer.writeUnfilteredConns(bars, true, openConnSpinnerID)
if err != nil {
logger.Error().Err(err).Msg("unable to link open IP connections")
}
return err
})
if importer.ResultCounts.OpenSSL > 0 {
linkingErrGroup.Go(func() error {
err := importer.writeLinkedSSL(ctx, bars, openSSLID, openSSLWriter, true)
if err != nil {
logger.Error().Err(err).Msg("unable to link open ssl connections")
}
return err
})
}
if importer.ResultCounts.OpenHTTP > 0 {
linkingErrGroup.Go(func() error {
err := importer.writeLinkedHTTP(ctx, bars, openHTTPID, openHTTPWriter, openConnWriter, true)
if err != nil {
logger.Error().Err(err).Msg("unable to link open http connections")
}
return err
})
}
}
if err := linkingErrGroup.Wait(); err != nil {
return fmt.Errorf("could not perform connection linking: %w", err)
}
sslWriter.Close()
openSSLWriter.Close()
httpWriter.Close()
openHTTPWriter.Close()
connWriter.Close()
openConnWriter.Close()
// // don't truncate tmp tables in debug mode
// // these tables should be truncated before each import
if zlog.DebugMode {
return nil
}
return importer.Database.TruncateTmpLinkTables()
}