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>
260 lines
7.7 KiB
Go
260 lines
7.7 KiB
Go
package viewer
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/activecm/rita/v5/config"
|
|
|
|
"github.com/charmbracelet/bubbles/list"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/muesli/reflow/truncate"
|
|
"golang.org/x/text/language"
|
|
"golang.org/x/text/message"
|
|
)
|
|
|
|
// colors
|
|
var (
|
|
defaultTextColor = lipgloss.AdaptiveColor{Light: "#2c2b2f", Dark: "#d3cdd4"}
|
|
subduedTextColor = lipgloss.AdaptiveColor{Light: "#454545", Dark: "#A49FA5"}
|
|
helpTextColor = lipgloss.AdaptiveColor{Light: "#DDDADA", Dark: "#3C3C3C"}
|
|
separatorColor = lipgloss.AdaptiveColor{Light: "#0BA4B8", Dark: "#AD58B4"}
|
|
|
|
// catpuccin theme colors
|
|
red = lipgloss.AdaptiveColor{Light: "#D2042D", Dark: "#f38ba8"} // "#ff1f7c" "#D2042D" "#eb2654"
|
|
peach = lipgloss.AdaptiveColor{Light: "#fe640b", Dark: "#fab387"}
|
|
yellow = lipgloss.AdaptiveColor{Light: "#df8e1d", Dark: "#f9e2af"}
|
|
lavender = lipgloss.AdaptiveColor{Light: "#7287fd", Dark: "#b4befe"}
|
|
mauve = lipgloss.AdaptiveColor{Light: "#8839ef", Dark: "#cba6f7"}
|
|
sapphire = lipgloss.AdaptiveColor{Light: "#209fb5", Dark: "#74c7ec"}
|
|
green = lipgloss.AdaptiveColor{Light: "#40a02b", Dark: "#a6e3a1"}
|
|
pink = lipgloss.AdaptiveColor{Light: "#ea76cb", Dark: "#f5c2e7"}
|
|
|
|
overlay0 = lipgloss.AdaptiveColor{Light: "#9ca0b0", Dark: "#6c7086"}
|
|
surface0 = lipgloss.AdaptiveColor{Light: "#ccd0da", Dark: "#313244"}
|
|
base = lipgloss.AdaptiveColor{Light: "#eff1f5", Dark: "#1e1e2e"}
|
|
overlay2 = lipgloss.AdaptiveColor{Light: "#7c7f93", Dark: "#9399b2"}
|
|
|
|
subtext0 = lipgloss.AdaptiveColor{Light: "#6c6f85", Dark: "#a6adc8"}
|
|
)
|
|
|
|
// styles
|
|
var (
|
|
listStyle = lipgloss.NewStyle().Margin(0, 0)
|
|
listHeaderStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, false, true, false).BorderForeground(lavender).Foreground(subduedTextColor).MarginBottom(1)
|
|
)
|
|
|
|
const (
|
|
bullet = "•"
|
|
ellipsis = "…"
|
|
)
|
|
|
|
type listModel struct {
|
|
Rows list.Model
|
|
width int
|
|
totalHeight int
|
|
headerHeight int
|
|
columns []column
|
|
}
|
|
|
|
func MakeList(items []list.Item, columns []column, width int, height int) listModel {
|
|
d := listDelegate{delegate: list.NewDefaultDelegate(), columns: columns}
|
|
|
|
l := list.New(items, d, width, height)
|
|
|
|
l.SetShowStatusBar(false)
|
|
l.SetShowTitle(false)
|
|
l.SetFilteringEnabled(false)
|
|
l.SetShowHelp(false)
|
|
|
|
return listModel{
|
|
Rows: l,
|
|
columns: columns,
|
|
width: width,
|
|
}
|
|
}
|
|
|
|
func (m *listModel) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (m *listModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
|
// handle window resize
|
|
if _, ok := msg.(tea.WindowSizeMsg); ok {
|
|
_, v := listStyle.GetFrameSize()
|
|
m.Rows.SetSize(m.width, m.Rows.Height()-v)
|
|
}
|
|
|
|
var cmd tea.Cmd
|
|
|
|
m.Rows, cmd = m.Rows.Update(msg)
|
|
return m, cmd
|
|
}
|
|
|
|
func (m *listModel) SetHeight(height int) {
|
|
_, v := listStyle.GetFrameSize()
|
|
header := lipgloss.Height(renderColumnHeader(m.columns, m.width))
|
|
m.headerHeight = header
|
|
h := (height - header - v)
|
|
m.totalHeight = header + v + h
|
|
m.Rows.SetSize(m.width, h)
|
|
m.Rows.SetHeight(h)
|
|
}
|
|
|
|
func (m *listModel) View() string {
|
|
|
|
header := renderColumnHeader(m.columns, m.width)
|
|
|
|
return listStyle.
|
|
Border(lipgloss.RoundedBorder(), true, false, true, true).
|
|
BorderForeground(lavender).
|
|
Render(lipgloss.JoinVertical(lipgloss.Top, header, m.Rows.View()))
|
|
}
|
|
|
|
type listDelegate struct {
|
|
delegate list.DefaultDelegate
|
|
columns []column
|
|
}
|
|
|
|
func (d listDelegate) Height() int { return 2 } //nolint:gocritic // bubbletea requires these to not be pointer methods
|
|
func (d listDelegate) Spacing() int { return 1 } //nolint:gocritic // bubbletea requires these to not be pointer methods
|
|
func (d listDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } //nolint:gocritic // bubbletea requires these to not be pointer methods
|
|
func (d listDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { //nolint:gocritic // bubbletea requires these to not be pointer methods
|
|
|
|
var (
|
|
severity string
|
|
src, dst string
|
|
beacon string
|
|
totalDuration string
|
|
subdomains string
|
|
threatIntel string
|
|
)
|
|
|
|
// get the item
|
|
i, ok := listItem.(*Item)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
severity = i.GetSeverity(true)
|
|
src = i.GetSrc()
|
|
dst = i.GetDst()
|
|
beacon = i.GetBeacon()
|
|
totalDuration = i.GetTotalDuration()
|
|
subdomains = i.GetSubdomains()
|
|
threatIntel = i.GetThreatIntel()
|
|
|
|
if m.Width() <= 0 {
|
|
// short-circuit
|
|
return
|
|
}
|
|
|
|
// conditions
|
|
var (
|
|
isSelected = index == m.Index()
|
|
)
|
|
|
|
// build list item out of pieces and style/color them accordingly
|
|
|
|
// set up language specific printer
|
|
p := message.NewPrinter(language.English)
|
|
|
|
// set up the style for the row, giving each cell a right padding of 3 to keep them from running together
|
|
style := lipgloss.NewStyle().PaddingRight(3)
|
|
|
|
// set the background color of the row if it is selected
|
|
if isSelected {
|
|
style = style.Background(surface0).Bold(true)
|
|
}
|
|
|
|
// get severity
|
|
categoryStyle := style.PaddingLeft(2).Width(d.columns[0].width)
|
|
categoryTitle := categoryStyle.Render(Truncate(severity, &categoryStyle))
|
|
|
|
// get source
|
|
srcStyle := style.Foreground(defaultTextColor).Width(d.columns[1].width)
|
|
srcTitle := srcStyle.Render(Truncate(src, &srcStyle))
|
|
|
|
// get destination
|
|
dstStyle := style.Foreground(defaultTextColor).Width(d.columns[2].width)
|
|
dstTitle := dstStyle.Render(Truncate(dst, &dstStyle))
|
|
|
|
// get beacon
|
|
beaconStyle := style.Width(d.columns[3].width)
|
|
beaconTitle := beaconStyle.Render(beacon)
|
|
|
|
// get total duration
|
|
totalDurationStyle := style.Width(d.columns[4].width)
|
|
totalDurationTitle := totalDurationStyle.Render(totalDuration)
|
|
|
|
// get subdomains
|
|
subdomainsStyle := style.Width(d.columns[5].width)
|
|
subDomainsTitle := subdomainsStyle.Render(p.Sprint(subdomains))
|
|
|
|
// get threat intel
|
|
threatIntelStyle := style.Width(d.columns[6].width)
|
|
threatIntelTitle := threatIntelStyle.Render(p.Sprint(threatIntel))
|
|
|
|
// render the full row
|
|
row := lipgloss.NewStyle().Render(
|
|
lipgloss.JoinHorizontal(lipgloss.Left, categoryTitle, srcTitle, dstTitle, beaconTitle, totalDurationTitle, subDomainsTitle, threatIntelTitle),
|
|
)
|
|
|
|
separator := lipgloss.NewStyle().MarginLeft(1).Width(m.Width()+1).Border(lipgloss.NormalBorder(), false, false, true, false).BorderForeground(separatorColor).Render()
|
|
_ = separator
|
|
|
|
fmt.Fprintf(w, "%s", row)
|
|
}
|
|
|
|
func Truncate(str string, style *lipgloss.Style) string {
|
|
// Prevent text from exceeding list width
|
|
textwidth := uint(style.GetWidth() - style.GetPaddingLeft() - style.GetPaddingRight())
|
|
return truncate.StringWithTail(str, textwidth, ellipsis)
|
|
}
|
|
|
|
func renderIndicator(score float64, displayText string) string {
|
|
category := config.GetImpactCategoryFromScore(float64(score))
|
|
style := lipgloss.NewStyle()
|
|
|
|
switch category {
|
|
case config.CriticalThreat:
|
|
return style.Foreground(red).Render(displayText)
|
|
case config.HighThreat:
|
|
return style.Foreground(peach).Render(displayText)
|
|
case config.MediumThreat:
|
|
return style.Foreground(yellow).Render(displayText)
|
|
case config.LowThreat:
|
|
return style.Foreground(sapphire).Render(displayText)
|
|
}
|
|
|
|
return style.Foreground(defaultTextColor).Render(displayText)
|
|
|
|
}
|
|
|
|
func renderColumnHeader(columns []column, headerWidth int) string {
|
|
var header string
|
|
columnStyle := lipgloss.NewStyle().Foreground(defaultTextColor)
|
|
|
|
for i, c := range columns {
|
|
// set the width of the column, subtracting off the column border
|
|
width := c.width - 3
|
|
|
|
// the fist column must start with a margin,
|
|
if i == 0 {
|
|
width -= 2 // subtract off the margin
|
|
header += columnStyle.MarginLeft(2).Width(width).Render(c.name)
|
|
} else {
|
|
header += columnStyle.Width(width).Render(c.name)
|
|
}
|
|
|
|
// add a column border if not the last column
|
|
if i < len(columns)-1 {
|
|
header += columnStyle.Foreground(surface0).Render(" | ")
|
|
}
|
|
}
|
|
|
|
return listHeaderStyle.Width(headerWidth).Render(header)
|
|
}
|