mirror of
https://github.com/activecm/rita
synced 2026-06-08 13:02:45 +00:00
b923c39e2d
* 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>
664 lines
29 KiB
Go
664 lines
29 KiB
Go
package database_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/activecm/rita/v5/cmd"
|
|
"github.com/activecm/rita/v5/database"
|
|
"github.com/activecm/rita/v5/util"
|
|
|
|
clickhouse "github.com/ClickHouse/clickhouse-go/v2"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func (d *DatabaseTestSuite) TestConnectToDB() {
|
|
|
|
// create and connect to a new database
|
|
d.Run("Connect to Existing Database", func() {
|
|
t := d.T()
|
|
_, err := cmd.RunImportCmd(time.Now(), d.cfg, afero.NewOsFs(), "../test_data/valid_tsv", "testDB", false, false)
|
|
require.NoError(t, err, "importing data should not produce an error")
|
|
|
|
db, err := database.ConnectToDB(context.Background(), "testDB", d.cfg, nil)
|
|
require.NoError(t, err, "connecting to createddatabase should not produce an error")
|
|
require.NotNil(t, db)
|
|
})
|
|
|
|
// attempt to connect to a non-existent database
|
|
d.Run("Connect to Non-Existent Database", func() {
|
|
t := d.T()
|
|
db, err := database.ConnectToDB(context.Background(), "nonExistentDB", d.cfg, nil)
|
|
require.Error(t, err, "connecting to a non-existent database should produce an error")
|
|
require.Nil(t, db)
|
|
})
|
|
|
|
// attempt to connect with invalid configuration
|
|
d.Run("Invalid Configuration", func() {
|
|
t := d.T()
|
|
invalidCfg := *d.cfg
|
|
invalidCfg.Env.DBConnection = "invalid connection string"
|
|
|
|
db, err := database.ConnectToDB(context.Background(), "testDB", &invalidCfg, nil)
|
|
require.Error(t, err, "connecting with invalid configuration should produce an error")
|
|
require.Nil(t, db)
|
|
})
|
|
|
|
// attempt to connect with a cancelled context
|
|
d.Run("Cancel Context", func() {
|
|
t := d.T()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
// attempt to connect with a cancelled context
|
|
db, err := database.ConnectToDB(ctx, "testDB", d.cfg, nil)
|
|
require.Error(t, err, "connecting with a cancelled context should produce an error")
|
|
require.Nil(t, db)
|
|
})
|
|
}
|
|
|
|
func (d *DatabaseTestSuite) TestMinMaxTimestamps() {
|
|
d.Run("24 Hour Dataset", func() {
|
|
t := d.T()
|
|
// import a dataset with 24 hours of data
|
|
results, err := cmd.RunImportCmd(time.Now(), d.cfg, afero.NewOsFs(), "../test_data/valid_tsv", "testDB", false, false)
|
|
require.NoError(t, err, "importing data should not produce an error")
|
|
|
|
// connect to the database
|
|
db, err := database.ConnectToDB(context.Background(), "testDB", d.cfg, nil)
|
|
require.NoError(t, err, "connecting to database should not produce an error")
|
|
|
|
// get the min and max timestamps from a test function that queries the conn, openconn, and dns tables
|
|
_, max := getMinMaxTimestampsFromTables(t, db, results.ImportID[0])
|
|
require.NoError(t, err, "getting min/max timestamps should not error")
|
|
|
|
// fetch the beacon minimum and maximum timestamps
|
|
minTSBeacon, maxTSBeacon, _, err := db.GetBeaconMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching beacon min and max timestamps should not produce an error")
|
|
|
|
// fetch the true minimum and maximum timestamps
|
|
minTS, maxTS, _, useCurrentTime, err := db.GetTrueMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching min and max timestamps should not produce an error")
|
|
|
|
// validate that the current time is not used
|
|
require.False(t, useCurrentTime, "useCurrentTime should be false")
|
|
|
|
// check that the timestamps are within the expected range
|
|
require.True(t, maxTS.After(minTS), "max timestamp should be after min timestamp")
|
|
|
|
// capped min timestamp
|
|
minTSCapped := max.Add(-24 * time.Hour)
|
|
require.InDelta(t, 24.0, maxTS.Sub(minTS).Hours(), 0.1, "timestamp difference should be close to 24 hours")
|
|
|
|
fmt.Println("Max Timestamp: ", maxTS)
|
|
fmt.Println("Min Timestamp: ", minTS)
|
|
|
|
// check that the true timestamps match the min and max timestamps from the test function
|
|
require.Equal(t, minTSCapped, minTS, "min timestamp should match min timestamp from the test function")
|
|
require.Equal(t, max, maxTS, "max timestamp should match max timestamp from the test function")
|
|
|
|
// check that the beacon timestamps are within the expected range
|
|
require.True(t, maxTSBeacon.Before(max) || maxTSBeacon.Equal(max), "max beacon timestamp should be before or equal to max timestamp from the test function")
|
|
require.True(t, minTSBeacon.After(minTSCapped) || minTSBeacon.Equal(minTSCapped), "min beacon timestamp should be after or equal to min timestamp from the test function")
|
|
})
|
|
|
|
d.Run("Less Than 24 Hours of Data", func() {
|
|
t := d.T()
|
|
// import a dataset with < 24 hours of data
|
|
results, err := cmd.RunImportCmd(time.Now(), d.cfg, afero.NewOsFs(), "../test_data/proxy_rolling", "testDB", false, false)
|
|
require.NoError(t, err, "importing data should not produce an error")
|
|
|
|
// connect to the database
|
|
db, err := database.ConnectToDB(context.Background(), "testDB", d.cfg, nil)
|
|
require.NoError(t, err, "connecting to database should not produce an error")
|
|
|
|
// get the min and max timestamps from a test function that queries the conn, openconn, and dns tables
|
|
min, max := getMinMaxTimestampsFromTables(t, db, results.ImportID[0])
|
|
require.NoError(t, err, "getting min/max timestamps should not error")
|
|
|
|
// fetch the beacon minimum and maximum timestamps
|
|
minTSBeacon, maxTSBeacon, _, err := db.GetBeaconMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching beacon min and max timestamps should not produce an error")
|
|
|
|
// fetch the true minimum and maximum timestamps
|
|
minTS, maxTS, _, useCurrentTime, err := db.GetTrueMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching min and max timestamps should not produce an error")
|
|
|
|
// validate that the current time is not used
|
|
require.False(t, useCurrentTime, "useCurrentTime should be false")
|
|
|
|
// check that the timestamps are within the expected range
|
|
require.True(t, maxTS.After(minTS), "max timestamp should be after min timestamp")
|
|
require.Less(t, maxTS.Sub(minTS).Hours(), 24.0, "timestamp difference should be less than 24 hours")
|
|
|
|
// check that the max timestamp matches the max timestamps from the test function
|
|
require.Equal(t, max, maxTS, "max timestamp should match max timestamp from test function")
|
|
|
|
// since the dataset is less than 24 hours, the min timestamp should match the min timestamp from the test function
|
|
require.Equal(t, min, minTS, "min timestamp should match min timestamp from test function")
|
|
|
|
// check that the beacon timestamps are within the expected range
|
|
require.True(t, maxTSBeacon.Before(max) || maxTSBeacon.Equal(max), "max beacon timestamp should be before or equal to max timestamp from the test function")
|
|
require.True(t, minTSBeacon.After(min) || minTSBeacon.Equal(min), "min beacon timestamp should be after or equal to min timestamp from the test function")
|
|
})
|
|
|
|
d.Run("Greater Than 24 Hours of Data", func() {
|
|
t := d.T()
|
|
|
|
afs := afero.NewMemMapFs()
|
|
|
|
// create directory
|
|
directory := "/logs"
|
|
err := afs.Mkdir(directory, os.FileMode(0o775))
|
|
require.NoError(t, err, "creating directory should not produce an error")
|
|
|
|
// create mock data file
|
|
fileName := "conn.log"
|
|
path := filepath.Join(directory, fileName)
|
|
file, err := afs.Create(path)
|
|
require.NoError(t, err, "creating file should not produce an error")
|
|
|
|
// set file permissions
|
|
err = afs.Chmod(path, os.FileMode(0o775))
|
|
require.NoError(t, err, "changing file permissions should not produce an error")
|
|
|
|
// generate timestamp from current time and format to the timestamp format used in the log file
|
|
currentTime := time.Now().UTC()
|
|
formattedMaxTime := fmt.Sprintf("%d.%06d", currentTime.Unix(), currentTime.Nanosecond()/1000)
|
|
|
|
// create interval timestamps of 4 hours ago
|
|
fourHoursAgo := currentTime.Add(-4 * time.Hour)
|
|
formattedTime := fmt.Sprintf("%d.%06d", fourHoursAgo.Unix(), fourHoursAgo.Nanosecond()/1000)
|
|
|
|
// create timestamp for 48 hours ago
|
|
twoDaysAgo := currentTime.Add(-48 * time.Hour)
|
|
formattedMinTime := fmt.Sprintf("%d.%06d", twoDaysAgo.Unix(), twoDaysAgo.Nanosecond()/1000)
|
|
|
|
// create mock data
|
|
log := []byte("#separator \\x09\n" +
|
|
"#set_separator\t,\n" +
|
|
"#empty_field\t(empty)\n" +
|
|
"#unset_field\t-\n" +
|
|
"#path\tconn\n" +
|
|
"#open\t2019-02-28-12-07-01\n" +
|
|
"#fields\tts\tuid\tid.orig_h\tid.resp_h\n" +
|
|
"#types\ttime\tstring\taddr\taddr\n" +
|
|
formattedMaxTime + "\tCxT121\t10.0.0.1\t52.12.0.1\n" +
|
|
formattedTime + "\tCxT121\t10.0.0.1\t52.12.0.1\n" +
|
|
formattedTime + "\tCxT122\t10.0.0.2\t52.12.0.2\n" +
|
|
formattedTime + "\tCxT122\t10.0.0.2\t52.12.0.2\n" +
|
|
formattedTime + "\tCxT123\t10.0.0.3\t52.12.0.3\n" +
|
|
formattedTime + "\tCxT123\t10.0.0.3\t52.12.0.3\n" +
|
|
formattedTime + "\tCxT124\t10.0.0.4\t52.12.0.4\n" +
|
|
formattedTime + "\tCxT124\t10.0.0.4\t52.12.0.4\n" +
|
|
formattedTime + "\tCxT125\t10.0.0.5\t52.12.0.5\n" +
|
|
formattedMinTime + "\tCxT125\t10.0.0.5\t52.12.0.5\n",
|
|
)
|
|
bytesWritten, err := file.Write(log)
|
|
require.NoError(t, err, "writing data to file should not produce an error")
|
|
require.Equal(t, len(log), bytesWritten, "number of bytes written should be equal to the length of the log data")
|
|
|
|
err = file.Close()
|
|
require.NoError(t, err, "closing file should not produce an error")
|
|
|
|
// import the mock data
|
|
results, err := cmd.RunImportCmd(time.Now(), d.cfg, afs, directory, "testDB", false, false)
|
|
require.NoError(t, err, "importing data should not produce an error")
|
|
|
|
// connect to the database
|
|
db, err := database.ConnectToDB(context.Background(), "testDB", d.cfg, nil)
|
|
require.NoError(t, err, "connecting to database should not produce an error")
|
|
|
|
// get the min and max timestamps from a test function that queries the conn, openconn, and dns tables
|
|
min, max := getMinMaxTimestampsFromTables(t, db, results.ImportID[0])
|
|
require.NoError(t, err, "getting min/max timestamps should not error")
|
|
|
|
// fetch the beacon minimum and maximum timestamps
|
|
minTSBeacon, maxTSBeacon, _, err := db.GetBeaconMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching beacon min and max timestamps should not produce an error")
|
|
|
|
// fetch the true minimum and maximum timestamps
|
|
minTS, maxTS, _, useCurrentTime, err := db.GetTrueMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching min and max timestamps should not produce an error")
|
|
|
|
// validate that the current time is not used
|
|
require.False(t, useCurrentTime, "useCurrentTime should be false")
|
|
|
|
// check that the timestamps are within the expected range
|
|
require.True(t, maxTS.After(minTS), "max timestamp should be after min timestamp")
|
|
require.InDelta(t, 24.0, maxTS.Sub(minTS).Hours(), 0.1, "timestamp difference should be close to 24 hours")
|
|
|
|
// check that the max timestamp matches the max timestamps from the test function
|
|
require.Equal(t, max, maxTS, "max timestamp should match max timestamp from test function")
|
|
|
|
// since the dataset is > 24 hours, the min timestamp will get capped to 24 hours from the max timestamp
|
|
require.NotEqual(t, min, minTS, "min timestamp should not match min timestamp from test function")
|
|
require.Equal(t, maxTS.Add(-24*time.Hour), minTS, "min timestamp should be 24 hours from max timestamp")
|
|
|
|
// check that the beacon timestamps are within the expected range
|
|
require.True(t, maxTSBeacon.Before(max) || maxTSBeacon.Equal(max), "max beacon timestamp should be before or equal to max timestamp from the test function")
|
|
require.True(t, minTSBeacon.After(maxTS.Add(-24*time.Hour)) || minTSBeacon.Equal(maxTS.Add(-24*time.Hour)), "min beacon timestamp should be after or equal to min timestamp from the test function")
|
|
})
|
|
|
|
d.Run("Rolling, Max Timestamp < 24Hrs Ago", func() {
|
|
t := d.T()
|
|
afs := afero.NewMemMapFs()
|
|
|
|
// create directory
|
|
directory := "/logs"
|
|
err := afs.Mkdir(directory, os.FileMode(0o775))
|
|
require.NoError(t, err, "creating directory should not produce an error")
|
|
|
|
// create mock data file
|
|
fileName := "conn.log"
|
|
path := filepath.Join(directory, fileName)
|
|
file, err := afs.Create(path)
|
|
require.NoError(t, err, "creating file should not produce an error")
|
|
|
|
// set file permissions
|
|
err = afs.Chmod(path, os.FileMode(0o775))
|
|
require.NoError(t, err, "changing file permissions should not produce an error")
|
|
|
|
// generate timestamp from current time and format to the timestamp format used in the log file
|
|
maxTime := time.Now().UTC().Add(-1 * time.Hour)
|
|
formattedMaxTime := fmt.Sprintf("%d.%06d", maxTime.Unix(), maxTime.Nanosecond()/1000)
|
|
|
|
// create interval timestamps of 4 hours ago
|
|
fourHoursAgo := maxTime.Add(-4 * time.Hour)
|
|
formattedTime := fmt.Sprintf("%d.%06d", fourHoursAgo.Unix(), fourHoursAgo.Nanosecond()/1000)
|
|
|
|
// create timestamp for 24 hours ago
|
|
minTime := maxTime.Add(-24 * time.Hour)
|
|
formattedMinTime := fmt.Sprintf("%d.%06d", minTime.Unix(), minTime.Nanosecond()/1000)
|
|
|
|
// create mock data
|
|
log := []byte("#separator \\x09\n" +
|
|
"#set_separator\t,\n" +
|
|
"#empty_field\t(empty)\n" +
|
|
"#unset_field\t-\n" +
|
|
"#path\tconn\n" +
|
|
"#open\t2019-02-28-12-07-01\n" +
|
|
"#fields\tts\tuid\tid.orig_h\tid.resp_h\n" +
|
|
"#types\ttime\tstring\taddr\taddr\n" +
|
|
formattedMaxTime + "\tCxT121\t10.0.0.1\t52.12.0.1\n" +
|
|
formattedTime + "\tCxT121\t10.0.0.1\t52.12.0.1\n" +
|
|
formattedTime + "\tCxT122\t10.0.0.2\t52.12.0.2\n" +
|
|
formattedTime + "\tCxT122\t10.0.0.2\t52.12.0.2\n" +
|
|
formattedTime + "\tCxT123\t10.0.0.3\t52.12.0.3\n" +
|
|
formattedTime + "\tCxT123\t10.0.0.3\t52.12.0.3\n" +
|
|
formattedTime + "\tCxT124\t10.0.0.4\t52.12.0.4\n" +
|
|
formattedTime + "\tCxT124\t10.0.0.4\t52.12.0.4\n" +
|
|
formattedTime + "\tCxT125\t10.0.0.5\t52.12.0.5\n" +
|
|
formattedMinTime + "\tCxT125\t10.0.0.5\t52.12.0.5\n",
|
|
)
|
|
bytesWritten, err := file.Write(log)
|
|
require.NoError(t, err, "writing data to file should not produce an error")
|
|
require.Equal(t, len(log), bytesWritten, "number of bytes written should be equal to the length of the log data")
|
|
|
|
err = file.Close()
|
|
require.NoError(t, err, "closing file should not produce an error")
|
|
|
|
// import the mock data
|
|
results, err := cmd.RunImportCmd(time.Now(), d.cfg, afs, directory, "testDB", true, false)
|
|
require.NoError(t, err, "importing data should not produce an error")
|
|
|
|
// connect to the database
|
|
db, err := database.ConnectToDB(context.Background(), "testDB", d.cfg, nil)
|
|
require.NoError(t, err, "connecting to database should not produce an error")
|
|
|
|
// get the min and max timestamps from a test function that queries the conn, openconn, and dns tables
|
|
min, max := getMinMaxTimestampsFromTables(t, db, results.ImportID[0])
|
|
require.NoError(t, err, "getting min/max timestamps should not error")
|
|
|
|
// fetch the beacon minimum and maximum timestamps
|
|
minTSBeacon, maxTSBeacon, _, err := db.GetBeaconMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching beacon min and max timestamps should not produce an error")
|
|
|
|
// fetch the true minimum and maximum timestamps
|
|
minTS, maxTS, _, useCurrentTime, err := db.GetTrueMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching min and max timestamps should not produce an error")
|
|
|
|
// validate that the current time should be used since the max timestamp is <= 24 hours ago and the dataset is rolling
|
|
require.True(t, useCurrentTime, "useCurrentTime should be true")
|
|
|
|
// check that the timestamps are within the expected range
|
|
require.True(t, maxTS.After(minTS), "max timestamp should be after min timestamp")
|
|
require.InDelta(t, 24.0, maxTS.Sub(minTS).Hours(), 0.1, "timestamp difference should be close to 24 hours")
|
|
|
|
// check that the timestamps match the min and max timestamps from the test function
|
|
require.Equal(t, min, minTS, "min timestamp should match min timestamp from the test function")
|
|
require.Equal(t, max, maxTS, "max timestamp should match max timestamp from the test function")
|
|
|
|
// check that the beacon timestamps are within the expected range
|
|
require.True(t, maxTSBeacon.Before(max) || maxTSBeacon.Equal(max), "max beacon timestamp should be before or equal to max timestamp from the test function")
|
|
require.True(t, minTSBeacon.After(min) || minTSBeacon.Equal(min), "min beacon timestamp should be after or equal to min timestamp from the test function")
|
|
})
|
|
|
|
d.Run("Rolling, Max Timestamp > 24Hrs Ago", func() {
|
|
t := d.T()
|
|
afs := afero.NewMemMapFs()
|
|
|
|
// create directory
|
|
directory := "/logs"
|
|
err := afs.Mkdir(directory, os.FileMode(0o775))
|
|
require.NoError(t, err, "creating directory should not produce an error")
|
|
|
|
// create mock data file
|
|
fileName := "conn.log"
|
|
path := filepath.Join(directory, fileName)
|
|
file, err := afs.Create(path)
|
|
require.NoError(t, err, "creating file should not produce an error")
|
|
|
|
// set file permissions
|
|
err = afs.Chmod(path, os.FileMode(0o775))
|
|
require.NoError(t, err, "changing file permissions should not produce an error")
|
|
|
|
// generate timestamp from current time and format to the timestamp format used in the log file
|
|
maxTime := time.Now().UTC().Add(-25 * time.Hour)
|
|
formattedMaxTime := fmt.Sprintf("%d.%06d", maxTime.Unix(), maxTime.Nanosecond()/1000)
|
|
|
|
// create interval timestamps of 4 hours ago
|
|
fourHoursAgo := maxTime.Add(-4 * time.Hour)
|
|
formattedTime := fmt.Sprintf("%d.%06d", fourHoursAgo.Unix(), fourHoursAgo.Nanosecond()/1000)
|
|
|
|
// create timestamp for 48 hours ago
|
|
minTime := maxTime.Add(-48 * time.Hour)
|
|
formattedMinTime := fmt.Sprintf("%d.%06d", minTime.Unix(), minTime.Nanosecond()/1000)
|
|
|
|
// create mock data
|
|
log := []byte("#separator \\x09\n" +
|
|
"#set_separator\t,\n" +
|
|
"#empty_field\t(empty)\n" +
|
|
"#unset_field\t-\n" +
|
|
"#path\tconn\n" +
|
|
"#open\t2019-02-28-12-07-01\n" +
|
|
"#fields\tts\tuid\tid.orig_h\tid.resp_h\n" +
|
|
"#types\ttime\tstring\taddr\taddr\n" +
|
|
formattedMaxTime + "\tCxT121\t10.0.0.1\t52.12.0.1\n" +
|
|
formattedTime + "\tCxT121\t10.0.0.1\t52.12.0.1\n" +
|
|
formattedTime + "\tCxT122\t10.0.0.2\t52.12.0.2\n" +
|
|
formattedTime + "\tCxT122\t10.0.0.2\t52.12.0.2\n" +
|
|
formattedTime + "\tCxT123\t10.0.0.3\t52.12.0.3\n" +
|
|
formattedTime + "\tCxT123\t10.0.0.3\t52.12.0.3\n" +
|
|
formattedTime + "\tCxT124\t10.0.0.4\t52.12.0.4\n" +
|
|
formattedTime + "\tCxT124\t10.0.0.4\t52.12.0.4\n" +
|
|
formattedTime + "\tCxT125\t10.0.0.5\t52.12.0.5\n" +
|
|
formattedMinTime + "\tCxT125\t10.0.0.5\t52.12.0.5\n",
|
|
)
|
|
bytesWritten, err := file.Write(log)
|
|
require.NoError(t, err, "writing data to file should not produce an error")
|
|
require.Equal(t, len(log), bytesWritten, "number of bytes written should be equal to the length of the log data")
|
|
|
|
err = file.Close()
|
|
require.NoError(t, err, "closing file should not produce an error")
|
|
|
|
// import the mock data
|
|
results, err := cmd.RunImportCmd(time.Now(), d.cfg, afs, directory, "testDB", true, false)
|
|
require.NoError(t, err, "importing data should not produce an error")
|
|
|
|
// connect to the database
|
|
db, err := database.ConnectToDB(context.Background(), "testDB", d.cfg, nil)
|
|
require.NoError(t, err, "connecting to database should not produce an error")
|
|
|
|
// get the min and max timestamps from a test function that queries the conn, openconn, and dns tables
|
|
min, max := getMinMaxTimestampsFromTables(t, db, results.ImportID[0])
|
|
require.NoError(t, err, "getting min/max timestamps should not error")
|
|
|
|
// fetch the beacon minimum and maximum timestamps
|
|
minTSBeacon, maxTSBeacon, _, err := db.GetBeaconMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching beacon min and max timestamps should not produce an error")
|
|
|
|
// fetch the true minimum and maximum timestamps
|
|
minTS, maxTS, _, useCurrentTime, err := db.GetTrueMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching min and max timestamps should not produce an error")
|
|
|
|
// validate that the current time is not used
|
|
require.False(t, useCurrentTime, "useCurrentTime should be false")
|
|
|
|
// check that the timestamps are within the expected range
|
|
require.True(t, maxTS.After(minTS), "max timestamp should be after min timestamp")
|
|
require.InDelta(t, 24.0, maxTS.Sub(minTS).Hours(), 0.1, "timestamp difference should be close to 24 hours")
|
|
|
|
// check that the max timestamp matches the max timestamps from the test function
|
|
require.Equal(t, max, maxTS, "max timestamp should match max timestamp from test function")
|
|
|
|
// since the dataset is > 24 hours, the min timestamp will get capped to 24 hours from the max timestamp
|
|
require.NotEqual(t, min, minTS, "min timestamp should not match min timestamp from test function")
|
|
require.Equal(t, maxTS.Add(-24*time.Hour), minTS, "min timestamp should be 24 hours from max timestamp")
|
|
|
|
// check that the beacon timestamps are within the expected range
|
|
require.True(t, maxTSBeacon.Before(max) || maxTSBeacon.Equal(max), "max beacon timestamp should be before or equal to max timestamp from the test function")
|
|
require.True(t, minTSBeacon.After(maxTS.Add(-24*time.Hour)) || minTSBeacon.Equal(maxTS.Add(-24*time.Hour)), "min beacon timestamp should be after or equal to min timestamp from the test function")
|
|
})
|
|
|
|
d.Run("Non-Existent Database / Invalid Connection", func() {
|
|
t := d.T()
|
|
db := database.DB{}
|
|
|
|
// fetch the beacon minimum and maximum timestamps
|
|
minTSBeacon, maxTSBeacon, _, err := db.GetBeaconMinMaxTimestamps()
|
|
require.Error(t, err, "fetching beacon min and max timestamps should produce an error")
|
|
require.Equal(t, minTSBeacon, time.Unix(0, 0), "max timestamp should be zero unix time")
|
|
require.Equal(t, maxTSBeacon, time.Unix(0, 0), "min timestamp should be zero unix time")
|
|
require.Equal(t, err, database.ErrInvalidDatabaseConnection, "error should be invalid database connection")
|
|
|
|
// fetch the true minimum and maximum timestamps
|
|
minTS, maxTS, _, useCurrentTime, err := db.GetTrueMinMaxTimestamps()
|
|
require.Error(t, err, "fetching min and max timestamps should produce an error")
|
|
|
|
require.Equal(t, maxTS, time.Unix(0, 0), "max timestamp should be zero unix time")
|
|
require.Equal(t, minTS, time.Unix(0, 0), "min timestamp should be zero unix time")
|
|
require.False(t, useCurrentTime, "value passed back to useCurrentTime should be false")
|
|
require.Equal(t, err, database.ErrInvalidDatabaseConnection, "error should be invalid database connection")
|
|
|
|
})
|
|
}
|
|
|
|
func getMinMaxTimestampsFromTables(t *testing.T, db *database.DB, importID util.FixedString) (time.Time, time.Time) { // uses valid dataset
|
|
t.Helper()
|
|
|
|
type minMaxRes struct {
|
|
Min time.Time `ch:"min_timestamp"`
|
|
Max time.Time `ch:"max_timestamp"`
|
|
}
|
|
|
|
var result minMaxRes
|
|
|
|
// set context with importID and database parameters
|
|
ctx := clickhouse.Context(db.GetContext(), clickhouse.WithParameters(clickhouse.Parameters{
|
|
"importID": importID.Hex(),
|
|
"database": db.GetSelectedDB(),
|
|
}))
|
|
|
|
// get min and max timestamps from the conn, openconn, and udns tables
|
|
err := db.Conn.QueryRow(ctx, `
|
|
SELECT MIN(ts) AS min_timestamp, MAX(ts) AS max_timestamp
|
|
FROM (
|
|
SELECT ts FROM {database:Identifier}.conn
|
|
UNION ALL
|
|
SELECT ts FROM {database:Identifier}.openconn
|
|
UNION ALL
|
|
SELECT hour as ts FROM {database:Identifier}.udns
|
|
) AS combined_timestamps
|
|
`).ScanStruct(&result)
|
|
require.NoError(t, err)
|
|
|
|
fmt.Println("Min Timestamp: ", result.Min)
|
|
fmt.Println("Max Timestamp: ", result.Max)
|
|
|
|
return result.Min, result.Max
|
|
}
|
|
|
|
func (d *DatabaseTestSuite) TestGetNetworkSize() {
|
|
d.Run("Valid TSV Dataset", func() {
|
|
t := d.T()
|
|
// import a dataset
|
|
_, err := cmd.RunImportCmd(time.Now(), d.cfg, afero.NewOsFs(), "../test_data/valid_tsv", "testDB", false, false)
|
|
require.NoError(t, err, "importing data should not produce an error")
|
|
|
|
// connect to the database
|
|
db, err := database.ConnectToDB(context.Background(), "testDB", d.cfg, nil)
|
|
require.NoError(t, err, "connecting to database should not produce an error")
|
|
|
|
var result struct {
|
|
Count uint64 `ch:"count"`
|
|
}
|
|
|
|
// verify http linking wrote no more than 20 records with the same zeek uid
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() as count FROM conn
|
|
`).ScanStruct(&result)
|
|
require.NoError(t, err, "querying conn table should not produce an error")
|
|
|
|
fmt.Println("Count: ", result.Count)
|
|
|
|
// get the min timestamp
|
|
minTS, _, notFromConn, err := db.GetBeaconMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching min timestamp should not produce an error")
|
|
|
|
// validate which table min max is from
|
|
require.False(t, notFromConn, "min and max timestamps should be from conn table")
|
|
|
|
// get the network size (number of unique internal hosts for the past 24 hours)
|
|
networkSize, err := db.GetNetworkSize(minTS)
|
|
require.NoError(t, err, "getting network size should not produce an error")
|
|
|
|
// verify the expected network size
|
|
require.Greater(t, networkSize, uint64(0), "network size should be greater than zero")
|
|
|
|
require.Equal(t, 15, int(networkSize), "network size for valid tsv dataset should match expected value")
|
|
})
|
|
|
|
d.Run("Mocked Log File with Duplicate Internal Hosts", func() {
|
|
t := d.T()
|
|
afs := afero.NewMemMapFs()
|
|
|
|
// create directory
|
|
directory := "/logs"
|
|
err := afs.Mkdir(directory, os.FileMode(0o775))
|
|
require.NoError(t, err, "creating directory should not produce an error")
|
|
|
|
// create mock data file
|
|
fileName := "conn.log"
|
|
path := filepath.Join(directory, fileName)
|
|
file, err := afs.Create(path)
|
|
require.NoError(t, err, "creating file should not produce an error")
|
|
|
|
// set file permissions
|
|
err = afs.Chmod(path, os.FileMode(0o775))
|
|
require.NoError(t, err, "changing file permissions should not produce an error")
|
|
|
|
// create mock data with duplicates and write data to file
|
|
log := []byte("#separator \\x09\n" +
|
|
"#set_separator\t,\n" +
|
|
"#empty_field\t(empty)\n" +
|
|
"#unset_field\t-\n" +
|
|
"#path\tconn\n" +
|
|
"#open\t2019-02-28-12-07-01\n" +
|
|
"#fields\tts\tuid\tid.orig_h\tid.resp_h\n" +
|
|
"#types\ttime\tstring\taddr\taddr\n" +
|
|
"1715640994.367201\tCxT121\t10.0.0.1\t52.12.0.1\n" +
|
|
"1715640994.367201\tCxT121\t10.0.0.1\t52.12.0.1\n" +
|
|
"1715641054.367201\tCxT122\t10.0.0.2\t52.12.0.2\n" +
|
|
"1715641054.367201\tCxT122\t10.0.0.2\t52.12.0.2\n" +
|
|
"1715641114.367201\tCxT123\t10.0.0.3\t52.12.0.3\n" +
|
|
"1715641114.367201\tCxT123\t10.0.0.3\t52.12.0.3\n" +
|
|
"1715641174.367201\tCxT124\t10.0.0.4\t52.12.0.4\n" +
|
|
"1715641174.367201\tCxT124\t10.0.0.4\t52.12.0.4\n" +
|
|
"1715641234.367201\tCxT125\t10.0.0.5\t52.12.0.5\n" +
|
|
"1715641234.367201\tCxT125\t10.0.0.5\t52.12.0.5\n",
|
|
)
|
|
bytesWritten, err := file.Write(log)
|
|
require.NoError(t, err, "writing data to file should not produce an error")
|
|
require.Equal(t, len(log), bytesWritten, "number of bytes written should be equal to the length of the log data")
|
|
|
|
err = file.Close()
|
|
require.NoError(t, err, "closing file should not produce an error")
|
|
|
|
// import the mock data
|
|
_, err = cmd.RunImportCmd(time.Now(), d.cfg, afs, directory, "testDB", false, false)
|
|
require.NoError(t, err, "importing data should not produce an error")
|
|
|
|
// connect to the database
|
|
db, err := database.ConnectToDB(context.Background(), "testDB", d.cfg, nil)
|
|
require.NoError(t, err, "connecting to database should not produce an error")
|
|
|
|
var result struct {
|
|
Count uint64 `ch:"count"`
|
|
}
|
|
|
|
// create a query to count number of conn records
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() as count FROM conn
|
|
`).ScanStruct(&result)
|
|
require.NoError(t, err, "querying conn table should not produce an error")
|
|
require.Equal(t, uint64(10), result.Count, "number of records in conn table should be 10")
|
|
|
|
// get the min timestamp
|
|
minTS, _, _, err := db.GetBeaconMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching min timestamp should not produce an error")
|
|
|
|
// get the network size (number of unique internal hosts for the past 24 hours)
|
|
networkSize, err := db.GetNetworkSize(minTS)
|
|
require.NoError(t, err, "getting network size should not produce an error")
|
|
|
|
// verify the expected network size
|
|
fmt.Println("Network Size: ", networkSize)
|
|
require.Greater(t, networkSize, uint64(0), "network size should be greater than zero")
|
|
require.Equal(t, uint64(5), networkSize, "network size for test 2 should be match expected value")
|
|
|
|
// remove the mock directory
|
|
require.NoError(t, afs.RemoveAll(directory), "removing mock directory should not produce an error")
|
|
|
|
})
|
|
|
|
d.Run("Open Conn Dataset", func() {
|
|
t := d.T()
|
|
// import a dataset
|
|
_, err := cmd.RunImportCmd(time.Now(), d.cfg, afero.NewOsFs(), "../test_data/open_conns/open/open_conn.log", "testDBzzzz", false, false)
|
|
require.NoError(t, err, "importing data should not produce an error")
|
|
|
|
// connect to the database
|
|
db, err := database.ConnectToDB(context.Background(), "testDBzzzz", d.cfg, nil)
|
|
require.NoError(t, err, "connecting to database should not produce an error")
|
|
|
|
var result struct {
|
|
Count uint64 `ch:"count"`
|
|
}
|
|
|
|
// validate that number of conn records is zero
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() as count FROM conn
|
|
`).ScanStruct(&result)
|
|
require.NoError(t, err, "querying conn table should not produce an error")
|
|
require.Equal(t, uint64(0), result.Count, "number of records in conn table should be 0")
|
|
|
|
// validate that number of open conn records is > 0
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() as count FROM openconn
|
|
`).ScanStruct(&result)
|
|
require.NoError(t, err, "querying openconn table should not produce an error")
|
|
require.Greater(t, result.Count, uint64(0), "number of open conn records should be >0")
|
|
|
|
// get the min timestamp
|
|
minTS, _, _, _, err := db.GetTrueMinMaxTimestamps()
|
|
require.NoError(t, err, "fetching min timestamp should not produce an error")
|
|
|
|
// get the network size (number of unique internal hosts for the past 24 hours)
|
|
networkSize, err := db.GetNetworkSize(minTS)
|
|
require.NoError(t, err, "getting network size should not produce an error")
|
|
|
|
// verify the expected network size
|
|
// fmt.Println("Network Size: ", networkSize)
|
|
require.Greater(t, networkSize, uint64(0), "network size should be greater than zero")
|
|
require.Equal(t, uint64(12), networkSize, "network size for test should match expected value")
|
|
|
|
})
|
|
|
|
}
|