mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2026-01-07 14:53:17 +01:00
refactor: remove pocketbase (#1138)
This commit is contained in:
441
app/data/demo.go
Normal file
441
app/data/demo.go
Normal file
@@ -0,0 +1,441 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v7"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/app/auth"
|
||||
"github.com/SecurityBrewery/catalyst/app/auth/password"
|
||||
"github.com/SecurityBrewery/catalyst/app/database"
|
||||
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
|
||||
"github.com/SecurityBrewery/catalyst/app/pointer"
|
||||
)
|
||||
|
||||
const (
|
||||
minimumUserCount = 1
|
||||
minimumTicketCount = 1
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed scripts/createticket.py
|
||||
createTicketPy string
|
||||
//go:embed scripts/alertingest.py
|
||||
alertIngestPy string
|
||||
//go:embed scripts/assigntickets.py
|
||||
assignTicketsPy string
|
||||
)
|
||||
|
||||
func GenerateDemoData(ctx context.Context, queries *sqlc.Queries, userCount, ticketCount int) error {
|
||||
if userCount < minimumUserCount {
|
||||
userCount = minimumUserCount
|
||||
}
|
||||
|
||||
if ticketCount < minimumTicketCount {
|
||||
ticketCount = minimumTicketCount
|
||||
}
|
||||
|
||||
types, err := database.PaginateItems(ctx, func(ctx context.Context, offset, limit int64) ([]sqlc.ListTypesRow, error) {
|
||||
return queries.ListTypes(ctx, sqlc.ListTypesParams{Limit: limit, Offset: offset})
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list types: %w", err)
|
||||
}
|
||||
|
||||
users, err := generateDemoUsers(ctx, queries, userCount, ticketCount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create user records: %w", err)
|
||||
}
|
||||
|
||||
if len(types) == 0 {
|
||||
return fmt.Errorf("no types found")
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return fmt.Errorf("no users found")
|
||||
}
|
||||
|
||||
if err := generateDemoTickets(ctx, queries, users, types, ticketCount); err != nil {
|
||||
return fmt.Errorf("failed to create ticket records: %w", err)
|
||||
}
|
||||
|
||||
if err := generateDemoReactions(ctx, queries, ticketCount); err != nil {
|
||||
return fmt.Errorf("failed to create reaction records: %w", err)
|
||||
}
|
||||
|
||||
if err := generateDemoGroups(ctx, queries, users, ticketCount); err != nil {
|
||||
return fmt.Errorf("failed to create group records: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateDemoUsers(ctx context.Context, queries *sqlc.Queries, count, ticketCount int) ([]sqlc.User, error) {
|
||||
users := make([]sqlc.User, 0, count)
|
||||
|
||||
// create the test user
|
||||
user, err := queries.GetUser(ctx, "u_test")
|
||||
if err != nil {
|
||||
newUser, err := createTestUser(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users = append(users, newUser)
|
||||
} else {
|
||||
users = append(users, user)
|
||||
}
|
||||
|
||||
for range count - 1 {
|
||||
newUser, err := createDemoUser(ctx, queries, ticketCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users = append(users, newUser)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func createDemoUser(ctx context.Context, queries *sqlc.Queries, ticketCount int) (sqlc.User, error) {
|
||||
username := gofakeit.Username()
|
||||
|
||||
passwordHash, tokenKey, err := password.Hash(gofakeit.Password(true, true, true, true, false, 16))
|
||||
if err != nil {
|
||||
return sqlc.User{}, fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
created, updated := dates(ticketCount)
|
||||
|
||||
return queries.InsertUser(ctx, sqlc.InsertUserParams{
|
||||
ID: database.GenerateID("u"),
|
||||
Name: pointer.Pointer(gofakeit.Name()),
|
||||
Email: pointer.Pointer(username + "@catalyst-soar.com"),
|
||||
Username: username,
|
||||
PasswordHash: passwordHash,
|
||||
TokenKey: tokenKey,
|
||||
Active: gofakeit.Bool(),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
}
|
||||
|
||||
var ticketCreated = time.Date(2025, 2, 1, 11, 29, 35, 0, time.UTC)
|
||||
|
||||
func generateDemoTickets(ctx context.Context, queries *sqlc.Queries, users []sqlc.User, types []sqlc.ListTypesRow, count int) error { //nolint:cyclop
|
||||
for range count {
|
||||
newTicket, err := createDemoTicket(ctx, queries, random(types), random(users).ID, fakeTicketTitle(), fakeTicketDescription(), count)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create ticket: %w", err)
|
||||
}
|
||||
|
||||
for range gofakeit.IntRange(1, 5) {
|
||||
_, err := createDemoComment(ctx, queries, newTicket.ID, random(users).ID, fakeTicketComment(), count)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create comment for ticket %s: %w", newTicket.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
for range gofakeit.IntRange(1, 5) {
|
||||
_, err := createDemoTimeline(ctx, queries, newTicket.ID, fakeTicketTimelineMessage(), count)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create timeline for ticket %s: %w", newTicket.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
for range gofakeit.IntRange(1, 5) {
|
||||
_, err := createDemoTask(ctx, queries, newTicket.ID, random(users).ID, fakeTicketTask(), count)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create task for ticket %s: %w", newTicket.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
for range gofakeit.IntRange(1, 5) {
|
||||
_, err := createDemoLink(ctx, queries, newTicket.ID, random([]string{"Blog", "Forum", "Wiki", "Documentation"}), gofakeit.URL(), count)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create link for ticket %s: %w", newTicket.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createDemoTicket(ctx context.Context, queries *sqlc.Queries, ticketType sqlc.ListTypesRow, userID, name, description string, ticketCount int) (sqlc.Ticket, error) {
|
||||
created, updated := dates(ticketCount)
|
||||
|
||||
ticket, err := queries.InsertTicket(
|
||||
ctx,
|
||||
sqlc.InsertTicketParams{
|
||||
ID: database.GenerateID(ticketType.Singular),
|
||||
Name: name,
|
||||
Description: description,
|
||||
Open: gofakeit.Bool(),
|
||||
Owner: &userID,
|
||||
Schema: marshal(map[string]any{"type": "object", "properties": map[string]any{"tlp": map[string]any{"title": "TLP", "type": "string"}}}),
|
||||
State: marshal(map[string]any{"severity": "Medium"}),
|
||||
Type: ticketType.ID,
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return sqlc.Ticket{}, fmt.Errorf("failed to create ticket for user %s: %w", userID, err)
|
||||
}
|
||||
|
||||
return ticket, nil
|
||||
}
|
||||
|
||||
func createDemoComment(ctx context.Context, queries *sqlc.Queries, ticketID, userID, message string, ticketCount int) (*sqlc.Comment, error) {
|
||||
created, updated := dates(ticketCount)
|
||||
|
||||
comment, err := queries.InsertComment(ctx, sqlc.InsertCommentParams{
|
||||
ID: database.GenerateID("c"),
|
||||
Ticket: ticketID,
|
||||
Author: userID,
|
||||
Message: message,
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create comment for ticket %s: %w", ticketID, err)
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
func createDemoTimeline(ctx context.Context, queries *sqlc.Queries, ticketID, message string, ticketCount int) (*sqlc.Timeline, error) {
|
||||
created, updated := dates(ticketCount)
|
||||
|
||||
timeline, err := queries.InsertTimeline(ctx, sqlc.InsertTimelineParams{
|
||||
ID: database.GenerateID("tl"),
|
||||
Ticket: ticketID,
|
||||
Message: message,
|
||||
Time: ticketCreated,
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create timeline for ticket %s: %w", ticketID, err)
|
||||
}
|
||||
|
||||
return &timeline, nil
|
||||
}
|
||||
|
||||
func createDemoTask(ctx context.Context, queries *sqlc.Queries, ticketID, userID, name string, ticketCount int) (*sqlc.Task, error) {
|
||||
created, updated := dates(ticketCount)
|
||||
|
||||
task, err := queries.InsertTask(ctx, sqlc.InsertTaskParams{
|
||||
ID: database.GenerateID("t"),
|
||||
Ticket: ticketID,
|
||||
Owner: &userID,
|
||||
Name: name,
|
||||
Open: gofakeit.Bool(),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create task for ticket %s: %w", ticketID, err)
|
||||
}
|
||||
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
func createDemoLink(ctx context.Context, queries *sqlc.Queries, ticketID, name, url string, ticketCount int) (*sqlc.Link, error) {
|
||||
created, updated := dates(ticketCount)
|
||||
|
||||
link, err := queries.InsertLink(ctx, sqlc.InsertLinkParams{
|
||||
ID: database.GenerateID("l"),
|
||||
Ticket: ticketID,
|
||||
Name: name,
|
||||
Url: url,
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create link for ticket %s: %w", ticketID, err)
|
||||
}
|
||||
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func generateDemoReactions(ctx context.Context, queries *sqlc.Queries, ticketCount int) error {
|
||||
created, updated := dates(ticketCount)
|
||||
|
||||
_, err := queries.InsertReaction(ctx, sqlc.InsertReactionParams{
|
||||
ID: "r-schedule",
|
||||
Name: "Create New Ticket",
|
||||
Trigger: "schedule",
|
||||
Triggerdata: marshal(map[string]any{"expression": "12 * * * *"}),
|
||||
Action: "python",
|
||||
Actiondata: marshal(map[string]any{
|
||||
"requirements": "requests",
|
||||
"script": createTicketPy,
|
||||
}),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create reaction for schedule trigger: %w", err)
|
||||
}
|
||||
|
||||
created, updated = dates(ticketCount)
|
||||
|
||||
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
|
||||
ID: "r-webhook",
|
||||
Name: "Alert Ingest Webhook",
|
||||
Trigger: "webhook",
|
||||
Triggerdata: marshal(map[string]any{"token": "1234567890", "path": "webhook"}),
|
||||
Action: "python",
|
||||
Actiondata: marshal(map[string]any{
|
||||
"requirements": "requests",
|
||||
"script": alertIngestPy,
|
||||
}),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create reaction for webhook trigger: %w", err)
|
||||
}
|
||||
|
||||
created, updated = dates(ticketCount)
|
||||
|
||||
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
|
||||
ID: "r-hook",
|
||||
Name: "Assign new Tickets",
|
||||
Trigger: "hook",
|
||||
Triggerdata: marshal(map[string]any{"collections": []any{"tickets"}, "events": []any{"create"}}),
|
||||
Action: "python",
|
||||
Actiondata: marshal(map[string]any{
|
||||
"requirements": "requests",
|
||||
"script": assignTicketsPy,
|
||||
}),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create reaction for hook trigger: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateDemoGroups(ctx context.Context, queries *sqlc.Queries, users []sqlc.User, ticketCount int) error { //nolint:cyclop
|
||||
created, updated := dates(ticketCount)
|
||||
|
||||
_, err := queries.InsertGroup(ctx, sqlc.InsertGroupParams{
|
||||
ID: "team-ir",
|
||||
Name: "IR Team",
|
||||
Permissions: auth.ToJSONArray(ctx, []string{}),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create IR team group: %w", err)
|
||||
}
|
||||
|
||||
created, updated = dates(ticketCount)
|
||||
|
||||
_, err = queries.InsertGroup(ctx, sqlc.InsertGroupParams{
|
||||
ID: "team-seceng",
|
||||
Name: "Security Engineering Team",
|
||||
Permissions: auth.ToJSONArray(ctx, []string{}),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create IR team group: %w", err)
|
||||
}
|
||||
|
||||
created, updated = dates(ticketCount)
|
||||
|
||||
_, err = queries.InsertGroup(ctx, sqlc.InsertGroupParams{
|
||||
ID: "team-security",
|
||||
Name: "Security Team",
|
||||
Permissions: auth.ToJSONArray(ctx, []string{}),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create security team group: %w", err)
|
||||
}
|
||||
|
||||
created, updated = dates(ticketCount)
|
||||
|
||||
_, err = queries.InsertGroup(ctx, sqlc.InsertGroupParams{
|
||||
ID: "g-engineer",
|
||||
Name: "Engineer",
|
||||
Permissions: auth.ToJSONArray(ctx, []string{"reaction:read", "reaction:write"}),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create analyst group: %w", err)
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
group := gofakeit.RandomString([]string{"team-seceng", "team-ir"})
|
||||
if user.ID == "u_test" {
|
||||
group = "admin"
|
||||
}
|
||||
|
||||
if err := queries.AssignGroupToUser(ctx, sqlc.AssignGroupToUserParams{
|
||||
UserID: user.ID,
|
||||
GroupID: group,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to assign group %s to user %s: %w", group, user.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = queries.AssignParentGroup(ctx, sqlc.AssignParentGroupParams{
|
||||
ParentGroupID: "team-ir",
|
||||
ChildGroupID: "analyst",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to assign parent group: %w", err)
|
||||
}
|
||||
|
||||
err = queries.AssignParentGroup(ctx, sqlc.AssignParentGroupParams{
|
||||
ParentGroupID: "team-seceng",
|
||||
ChildGroupID: "g-engineer",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to assign parent group: %w", err)
|
||||
}
|
||||
|
||||
err = queries.AssignParentGroup(ctx, sqlc.AssignParentGroupParams{
|
||||
ParentGroupID: "team-ir",
|
||||
ChildGroupID: "team-security",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to assign parent group: %w", err)
|
||||
}
|
||||
|
||||
err = queries.AssignParentGroup(ctx, sqlc.AssignParentGroupParams{
|
||||
ParentGroupID: "team-seceng",
|
||||
ChildGroupID: "team-security",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to assign parent group: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func weeksAgo(c int) time.Time {
|
||||
return time.Now().UTC().AddDate(0, 0, -7*c)
|
||||
}
|
||||
|
||||
func dates(ticketCount int) (time.Time, time.Time) {
|
||||
const ticketsPerWeek = 10
|
||||
weeks := ticketCount / ticketsPerWeek
|
||||
|
||||
created := gofakeit.DateRange(weeksAgo(1), weeksAgo(weeks+1)).UTC()
|
||||
updated := gofakeit.DateRange(created, time.Now()).UTC()
|
||||
|
||||
return created, updated
|
||||
}
|
||||
26
app/data/demo_test.go
Normal file
26
app/data/demo_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package data_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/app/data"
|
||||
catalystTesting "github.com/SecurityBrewery/catalyst/testing"
|
||||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, cleanup, _ := catalystTesting.App(t)
|
||||
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
_ = app.Queries.DeleteUser(t.Context(), "u_admin")
|
||||
_ = app.Queries.DeleteUser(t.Context(), "u_bob_analyst")
|
||||
_ = app.Queries.DeleteGroup(t.Context(), "g_admin")
|
||||
_ = app.Queries.DeleteGroup(t.Context(), "g_analyst")
|
||||
|
||||
err := data.GenerateDemoData(t.Context(), app.Queries, 4, 4)
|
||||
require.NoError(t, err, "failed to generate fake data")
|
||||
}
|
||||
20
app/data/scripts/alertingest.py
Normal file
20
app/data/scripts/alertingest.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import sys
|
||||
import json
|
||||
import random
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
# Parse the event from the webhook payload
|
||||
event = json.loads(sys.argv[1])
|
||||
body = json.loads(event["body"])
|
||||
|
||||
url = os.environ["CATALYST_APP_URL"]
|
||||
header = {"Authorization": "Bearer " + os.environ["CATALYST_TOKEN"]}
|
||||
|
||||
# Create a new ticket
|
||||
requests.post(url + "/api/tickets", headers=header, json={
|
||||
"name": body["name"],
|
||||
"type": "alert",
|
||||
"open": True,
|
||||
})
|
||||
21
app/data/scripts/assigntickets.py
Normal file
21
app/data/scripts/assigntickets.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import sys
|
||||
import json
|
||||
import random
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
# Parse the ticket from the input
|
||||
ticket = json.loads(sys.argv[1])
|
||||
|
||||
url = os.environ["CATALYST_APP_URL"]
|
||||
header = {"Authorization": "Bearer " + os.environ["CATALYST_TOKEN"]}
|
||||
|
||||
# Get a random user
|
||||
users = requests.get(url + "/api/users", headers=header).json()
|
||||
random_user = random.choice(users)
|
||||
|
||||
# Assign the ticket to the random user
|
||||
requests.patch(url + "/api/tickets/" + ticket["record"]["id"], headers=header, json={
|
||||
"owner": random_user["id"]
|
||||
})
|
||||
20
app/data/scripts/createticket.py
Normal file
20
app/data/scripts/createticket.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import sys
|
||||
import json
|
||||
import random
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
url = os.environ["CATALYST_APP_URL"]
|
||||
header = {"Authorization": "Bearer " + os.environ["CATALYST_TOKEN"]}
|
||||
|
||||
newtickets = requests.get(url + "/api/tickets?limit=3", headers=header).json()
|
||||
for ticket in newtickets:
|
||||
requests.delete(url + "/api/tickets/" + ticket["id"], headers=header)
|
||||
|
||||
# Create a new ticket
|
||||
requests.post(url + "/api/tickets", headers=header, json={
|
||||
"name": "New Ticket",
|
||||
"type": "alert",
|
||||
"open": True,
|
||||
})
|
||||
21
app/data/scripts/upgradetest.py
Normal file
21
app/data/scripts/upgradetest.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import sys
|
||||
import json
|
||||
import random
|
||||
import os
|
||||
|
||||
from pocketbase import PocketBase
|
||||
|
||||
# Connect to the PocketBase server
|
||||
client = PocketBase(os.environ["CATALYST_APP_URL"])
|
||||
client.auth_store.save(token=os.environ["CATALYST_TOKEN"])
|
||||
|
||||
newtickets = client.collection("tickets").get_list(1, 200, {"filter": 'name = "New Ticket"'})
|
||||
for ticket in newtickets.items:
|
||||
client.collection("tickets").delete(ticket.id)
|
||||
|
||||
# Create a new ticket
|
||||
client.collection("tickets").create({
|
||||
"name": "New Ticket",
|
||||
"type": "alert",
|
||||
"open": True,
|
||||
})
|
||||
227
app/data/testdata.go
Normal file
227
app/data/testdata.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/app/database"
|
||||
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
|
||||
"github.com/SecurityBrewery/catalyst/app/pointer"
|
||||
)
|
||||
|
||||
const (
|
||||
AdminEmail = "admin@catalyst-soar.com"
|
||||
AnalystEmail = "analyst@catalyst-soar.com"
|
||||
)
|
||||
|
||||
func DefaultTestData(t *testing.T, dir string, queries *sqlc.Queries) {
|
||||
t.Helper()
|
||||
|
||||
parseTime := func(s string) time.Time {
|
||||
t, _ := time.Parse(time.RFC3339Nano, s)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
// Insert users
|
||||
_, err := queries.InsertUser(ctx, sqlc.InsertUserParams{
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Email: pointer.Pointer("analyst@catalyst-soar.com"),
|
||||
Username: "u_bob_analyst",
|
||||
Name: pointer.Pointer("Bob Analyst"),
|
||||
PasswordHash: "$2a$10$ZEHNh9ZKJ81N717wovDnMuLwZOLa6.g22IRzRr4goG6zGN.57UzJG",
|
||||
TokenKey: "z3Jj8bbzcq_cSZs07XKoGlB0UtvmQiphHgwNkE4akoY=",
|
||||
Active: true,
|
||||
ID: "u_bob_analyst",
|
||||
})
|
||||
require.NoError(t, err, "failed to insert analyst user")
|
||||
|
||||
_, err = queries.InsertUser(ctx, sqlc.InsertUserParams{
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Email: pointer.Pointer("admin@catalyst-soar.com"),
|
||||
Username: "u_admin",
|
||||
Name: pointer.Pointer("Admin User"),
|
||||
PasswordHash: "$2a$10$Z3/0HHWau6oi1t1aRPiI0uiVOWI.IosTAYEL0DJ2XJaalP9kesgBa",
|
||||
TokenKey: "5BWDKLIAn3SQkpQlBUGrS_XEbFf91DsDpuh_Xmt4Nwg=",
|
||||
Active: true,
|
||||
ID: "u_admin",
|
||||
})
|
||||
require.NoError(t, err, "failed to insert admin user")
|
||||
|
||||
// Insert webhooks
|
||||
_, err = queries.InsertWebhook(ctx, sqlc.InsertWebhookParams{
|
||||
ID: "w_test_webhook",
|
||||
Name: "Test Webhook",
|
||||
Collection: "tickets",
|
||||
Destination: "https://example.com",
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
})
|
||||
require.NoError(t, err, "failed to insert webhook")
|
||||
|
||||
// Insert types
|
||||
_, err = queries.InsertType(ctx, sqlc.InsertTypeParams{
|
||||
ID: "test-type",
|
||||
Singular: "Test",
|
||||
Plural: "Tests",
|
||||
Schema: []byte(`{}`),
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
})
|
||||
require.NoError(t, err, "failed to insert type")
|
||||
|
||||
// Insert tickets
|
||||
_, err = queries.InsertTicket(ctx, sqlc.InsertTicketParams{
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Description: "This is a test ticket.",
|
||||
ID: "test-ticket",
|
||||
Name: "Test Ticket",
|
||||
Open: true,
|
||||
Owner: pointer.Pointer("u_bob_analyst"),
|
||||
Schema: json.RawMessage(`{"type":"object","properties":{"tlp":{"title":"TLP","type":"string"}}}`),
|
||||
State: json.RawMessage(`{"tlp":"AMBER"}`),
|
||||
Type: "incident",
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
})
|
||||
require.NoError(t, err, "failed to insert ticket")
|
||||
|
||||
// Insert tasks
|
||||
_, err = queries.InsertTask(ctx, sqlc.InsertTaskParams{
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
ID: "k_test_task",
|
||||
Name: "Test Task",
|
||||
Open: true,
|
||||
Owner: pointer.Pointer("u_bob_analyst"),
|
||||
Ticket: "test-ticket",
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
})
|
||||
require.NoError(t, err, "failed to insert task")
|
||||
|
||||
// Insert comments
|
||||
_, err = queries.InsertComment(ctx, sqlc.InsertCommentParams{
|
||||
Author: "u_bob_analyst",
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
ID: "c_test_comment",
|
||||
Message: "Initial comment on the test ticket.",
|
||||
Ticket: "test-ticket",
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
})
|
||||
require.NoError(t, err, "failed to insert comment")
|
||||
|
||||
// Insert timeline
|
||||
_, err = queries.InsertTimeline(ctx, sqlc.InsertTimelineParams{
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
ID: "h_test_timeline",
|
||||
Message: "Initial timeline entry.",
|
||||
Ticket: "test-ticket",
|
||||
Time: parseTime("2023-01-01T00:00:00Z"),
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
})
|
||||
require.NoError(t, err, "failed to insert timeline entry")
|
||||
|
||||
// Insert links
|
||||
_, err = queries.InsertLink(ctx, sqlc.InsertLinkParams{
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
ID: "l_test_link",
|
||||
Name: "Catalyst",
|
||||
Ticket: "test-ticket",
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Url: "https://example.com",
|
||||
})
|
||||
require.NoError(t, err, "failed to insert link")
|
||||
|
||||
// Insert files
|
||||
_, err = queries.InsertFile(ctx, sqlc.InsertFileParams{
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
ID: "b_test_file",
|
||||
Name: "hello.txt",
|
||||
Size: 5,
|
||||
Ticket: "test-ticket",
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Blob: "hello_a20DUE9c77rj.txt",
|
||||
})
|
||||
require.NoError(t, err, "failed to insert file")
|
||||
|
||||
// Insert features
|
||||
_, err = queries.CreateFeature(ctx, "dev")
|
||||
require.NoError(t, err, "failed to insert feature 'dev'")
|
||||
|
||||
// Insert reactions
|
||||
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
|
||||
ID: "r-test-webhook",
|
||||
Name: "Reaction",
|
||||
Action: "python",
|
||||
Actiondata: []byte(`{"requirements":"requests","script":"print('Hello, World!')"}`),
|
||||
Trigger: "webhook",
|
||||
Triggerdata: []byte(`{"token":"1234567890","path":"test"}`),
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
})
|
||||
require.NoError(t, err, "failed to insert reaction")
|
||||
|
||||
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
|
||||
ID: "r-test-proxy",
|
||||
Action: "webhook",
|
||||
Name: "Reaction",
|
||||
Actiondata: []byte(`{"headers":{"Content-Type":"application/json"},"url":"http://127.0.0.1:12345/webhook"}`),
|
||||
Trigger: "webhook",
|
||||
Triggerdata: []byte(`{"path":"test2"}`),
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
})
|
||||
require.NoError(t, err, "failed to insert reaction")
|
||||
|
||||
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
|
||||
ID: "r-test-hook",
|
||||
Name: "Hook",
|
||||
Action: "python",
|
||||
Actiondata: []byte(`{"requirements":"requests","script":"import requests\nrequests.post('http://127.0.0.1:12346/test', json={'test':True})"}`),
|
||||
Trigger: "hook",
|
||||
Triggerdata: json.RawMessage(`{"collections":["tickets"],"events":["create"]}`),
|
||||
Created: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
Updated: parseTime("2025-06-21T22:21:26.271Z"),
|
||||
})
|
||||
require.NoError(t, err, "failed to insert reaction")
|
||||
|
||||
// Insert user_groups
|
||||
err = queries.AssignGroupToUser(ctx, sqlc.AssignGroupToUserParams{
|
||||
UserID: "u_bob_analyst",
|
||||
GroupID: "analyst",
|
||||
})
|
||||
require.NoError(t, err, "failed to assign analyst group to user")
|
||||
|
||||
err = queries.AssignGroupToUser(ctx, sqlc.AssignGroupToUserParams{
|
||||
UserID: "u_admin",
|
||||
GroupID: "admin",
|
||||
})
|
||||
require.NoError(t, err, "failed to assign admin group to user")
|
||||
|
||||
files, err := database.PaginateItems(ctx, func(ctx context.Context, offset, limit int64) ([]sqlc.ListFilesRow, error) {
|
||||
return queries.ListFiles(ctx, sqlc.ListFilesParams{Limit: limit, Offset: offset})
|
||||
})
|
||||
require.NoError(t, err, "failed to list files")
|
||||
|
||||
for _, file := range files {
|
||||
_ = os.MkdirAll(path.Join(dir, "uploads", file.ID), 0o755)
|
||||
|
||||
infoFilePath := path.Join(dir, "uploads", file.ID+".info")
|
||||
slog.InfoContext(t.Context(), "Creating file info", "path", infoFilePath)
|
||||
|
||||
err = os.WriteFile(infoFilePath, []byte(`{"MetaData":{"filetype":"text/plain"}}`), 0o600)
|
||||
require.NoError(t, err, "failed to write file info")
|
||||
|
||||
err = os.WriteFile(path.Join(dir, "uploads", file.ID, file.Blob), []byte("hello"), 0o600)
|
||||
require.NoError(t, err, "failed to write file blob")
|
||||
}
|
||||
}
|
||||
16
app/data/testdata.sql
Normal file
16
app/data/testdata.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
INSERT INTO users VALUES('2025-06-21 22:21:26.271Z','2025-06-21 22:21:26.271Z','analyst@catalyst-soar.com','u_bob_analyst','','','Bob Analyst','$2a$10$ZEHNh9ZKJ81N717wovDnMuLwZOLa6.g22IRzRr4goG6zGN.57UzJG','z3Jj8bbzcq_cSZs07XKoGlB0UtvmQiphHgwNkE4akoY=','2025-06-21 22:21:26.271Z','u_bob_analyst',1);
|
||||
INSERT INTO users VALUES('2025-06-21 22:21:26.271Z','2025-06-21 22:21:26.271Z','admin@catalyst-soar.com','u_admin','','','Admin User','$2a$10$Z3/0HHWau6oi1t1aRPiI0uiVOWI.IosTAYEL0DJ2XJaalP9kesgBa','5BWDKLIAn3SQkpQlBUGrS_XEbFf91DsDpuh_Xmt4Nwg=','2025-06-21 22:21:26.271Z','u_admin',1);
|
||||
INSERT INTO webhooks VALUES('tickets','2025-06-21 22:21:26.271Z','https://example.com','w_test_webhook','Test Webhook','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO types VALUES('2025-06-21 22:21:26.271Z','Bug','test-type','Tests','{}','Test','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO tickets VALUES('2025-06-21 22:21:26.271Z','This is a test ticket.','test-ticket','Test Ticket',1,'u_bob_analyst','','{"type":"object","properties":{"tlp":{"title":"TLP","type":"string"}}}','{"tlp":"AMBER"}','incident','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO tasks VALUES('2025-06-21 22:21:26.271Z','k_test_task','Test Task',1,'u_bob_analyst','test-ticket','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO comments VALUES('u_bob_analyst','2025-06-21 22:21:26.271Z','c_test_comment','Initial comment on the test ticket.','test-ticket','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO timeline VALUES('2025-06-21 22:21:26.271Z','h_test_timeline','Initial timeline entry.','test-ticket','2023-01-01T00:00:00Z','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO links VALUES('2025-06-21 22:21:26.271Z','l_test_link','Catalyst','test-ticket','2025-06-21 22:21:26.271Z','https://example.com');
|
||||
INSERT INTO files VALUES('hello_a20DUE9c77rj.txt','2025-06-21 22:21:26.271Z','b_test_file','hello.txt',5,'test-ticket','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO features VALUES('2025-06-21 22:21:26.271Z','rce91818107f46a','dev','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO reactions VALUES('python','{"requirements":"requests","script":"print(''Hello, World!'')"}','','r-test-webhook','Reaction','webhook','{"token":"1234567890","path":"test"}','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO reactions VALUES('webhook','{"headers":{"Content-Type":"application/json"},"url":"http://127.0.0.1:12345/webhook"}','','r-test-proxy','Reaction','webhook','{"path":"test2"}','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO reactions VALUES('python','{"requirements":"requests","script":"import requests\nrequests.post(''http://127.0.0.1:12346/test'', json={''test'':True})"}','','r-test-hook','Hook','hook','{"collections":["tickets"],"events":["create"]}','2025-06-21 22:21:26.271Z');
|
||||
INSERT INTO user_groups VALUES('u_bob_analyst','analyst');
|
||||
INSERT INTO user_groups VALUES('u_admin','admin');
|
||||
84
app/data/testdata_test.go
Normal file
84
app/data/testdata_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
|
||||
"github.com/SecurityBrewery/catalyst/app/pointer"
|
||||
)
|
||||
|
||||
func TestDBInitialization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
queries := NewTestDB(t, t.TempDir())
|
||||
|
||||
user, err := queries.SystemUser(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "system", user.ID)
|
||||
|
||||
types, err := queries.ListTypes(t.Context(), sqlc.ListTypesParams{Offset: 0, Limit: 10})
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, len(types), 1)
|
||||
}
|
||||
|
||||
func TestNewTestDBDefaultData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
queries := NewTestDB(t, t.TempDir())
|
||||
|
||||
user, err := queries.UserByEmail(t.Context(), pointer.Pointer(AdminEmail))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, AdminEmail, *user.Email)
|
||||
|
||||
ticket, err := queries.Ticket(t.Context(), "test-ticket")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "test-ticket", ticket.ID)
|
||||
|
||||
comment, err := queries.GetComment(t.Context(), "c_test_comment")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "c_test_comment", comment.ID)
|
||||
|
||||
timeline, err := queries.GetTimeline(t.Context(), "h_test_timeline")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "h_test_timeline", timeline.ID)
|
||||
}
|
||||
|
||||
func TestReadWrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
queries := NewTestDB(t, t.TempDir())
|
||||
|
||||
for range 3 {
|
||||
y, err := queries.CreateType(t.Context(), sqlc.CreateTypeParams{
|
||||
Singular: "Foo",
|
||||
Plural: "Foos",
|
||||
Icon: pointer.Pointer("Bug"),
|
||||
Schema: json.RawMessage("{}"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = queries.GetType(t.Context(), y.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = queries.DeleteType(t.Context(), y.ID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
queries := NewTestDB(t, t.TempDir())
|
||||
|
||||
// read from a table
|
||||
_, err := queries.GetUser(t.Context(), "u_bob_analyst")
|
||||
require.NoError(t, err)
|
||||
|
||||
// read from a view
|
||||
_, err = queries.GetSidebar(t.Context())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
27
app/data/testing.go
Normal file
27
app/data/testing.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/app/database"
|
||||
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
|
||||
"github.com/SecurityBrewery/catalyst/app/migration"
|
||||
"github.com/SecurityBrewery/catalyst/app/upload"
|
||||
)
|
||||
|
||||
func NewTestDB(t *testing.T, dir string) *sqlc.Queries {
|
||||
t.Helper()
|
||||
|
||||
queries := database.TestDB(t, dir)
|
||||
uploader, err := upload.New(dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = migration.Apply(t.Context(), queries, dir, uploader)
|
||||
require.NoError(t, err)
|
||||
|
||||
DefaultTestData(t, dir, queries)
|
||||
|
||||
return queries
|
||||
}
|
||||
40
app/data/testing_test.go
Normal file
40
app/data/testing_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
|
||||
"github.com/SecurityBrewery/catalyst/app/pointer"
|
||||
)
|
||||
|
||||
func TestNewTestDB(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
queries := NewTestDB(t, dir)
|
||||
|
||||
user, err := queries.GetUser(t.Context(), "u_bob_analyst")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "u_bob_analyst", user.ID)
|
||||
assert.Equal(t, "Bob Analyst", *user.Name)
|
||||
assert.Equal(t, time.Date(2025, time.June, 21, 22, 21, 26, 271000000, time.UTC), user.Created)
|
||||
|
||||
alice, err := queries.InsertUser(t.Context(), sqlc.InsertUserParams{
|
||||
ID: "u_alice_admin",
|
||||
Name: pointer.Pointer("Alice Admin"),
|
||||
Username: "alice_admin",
|
||||
PasswordHash: "",
|
||||
TokenKey: "",
|
||||
Created: time.Date(2025, time.June, 21, 22, 21, 26, 0, time.UTC),
|
||||
Updated: time.Date(2025, time.June, 21, 22, 21, 26, 0, time.UTC),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, time.Date(2025, time.June, 21, 22, 21, 26, 0, time.UTC), alice.Created)
|
||||
}
|
||||
127
app/data/text.go
Normal file
127
app/data/text.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package data
|
||||
|
||||
import "github.com/brianvoe/gofakeit/v7"
|
||||
|
||||
func fakeTicketTitle() string {
|
||||
return random([]string{
|
||||
"Unauthorized Access Attempt",
|
||||
"Multiple Failed Login Attempts",
|
||||
"Suspicious File Download",
|
||||
"User Account Locked",
|
||||
"Unusual Network Activity",
|
||||
"Phishing Email Reported",
|
||||
"Sensitive Data Transfer Detected",
|
||||
"Malware Infection Found",
|
||||
"Unauthorized Device Connected",
|
||||
"Brute-Force Attack Attempt",
|
||||
"Security Patch Required",
|
||||
"External IP Address Probing Network",
|
||||
"Suspicious Behavior Detected",
|
||||
"Unauthorized Software Installation",
|
||||
"Access Control System Malfunction",
|
||||
"DDoS Attack Detected",
|
||||
})
|
||||
}
|
||||
|
||||
func fakeTicketDescription() string {
|
||||
return random([]string{
|
||||
"Unauthorized access attempt detected in the main server room.",
|
||||
"Multiple failed login attempts from an unknown IP address.",
|
||||
"Suspicious file download flagged by antivirus software.",
|
||||
"User account locked due to repeated incorrect password entries.",
|
||||
"Unusual network activity observed on the internal firewall.",
|
||||
"Phishing email reported by several employees.",
|
||||
"Sensitive data transfer detected outside the approved hours.",
|
||||
"Malware infection found on a workstation in the finance department.",
|
||||
"Unauthorized device connected to the company network.",
|
||||
"Brute-force attack attempt on the admin account detected.",
|
||||
"Security patch required for vulnerability in outdated software.",
|
||||
"External IP address attempting to probe network ports.",
|
||||
"Suspicious behavior detected by user in HR department.",
|
||||
"Unauthorized software installation on company laptop.",
|
||||
"Access control system malfunction at the main entrance.",
|
||||
"DDoS attack detected on company web server.",
|
||||
"Unusual outbound traffic to a known malicious domain.",
|
||||
"Potential insider threat flagged by behavior analysis tool.",
|
||||
"Compromised credentials detected on dark web.",
|
||||
"Encryption key rotation required for compliance with security policy.",
|
||||
})
|
||||
}
|
||||
|
||||
func fakeTicketComment() string {
|
||||
return random([]string{
|
||||
"Ticket opened by user.",
|
||||
"Initial investigation started.",
|
||||
"Further analysis required.",
|
||||
"Escalated to security team.",
|
||||
"Action taken to mitigate risk.",
|
||||
"Resolution in progress.",
|
||||
"User notified of incident.",
|
||||
"Security incident confirmed.",
|
||||
"Containment measures implemented.",
|
||||
"Root cause analysis underway.",
|
||||
"Forensic investigation initiated.",
|
||||
"Data breach confirmed.",
|
||||
"Incident response team activated.",
|
||||
"Legal counsel consulted.",
|
||||
"Public relations notified.",
|
||||
"Regulatory authorities informed.",
|
||||
"Compensation plan developed.",
|
||||
"Press release drafted.",
|
||||
"Media monitoring in progress.",
|
||||
"Post-incident review scheduled.",
|
||||
})
|
||||
}
|
||||
|
||||
func fakeTicketTimelineMessage() string {
|
||||
return random([]string{
|
||||
"Initial investigation started.",
|
||||
"Further analysis required.",
|
||||
"Escalated to security team.",
|
||||
"Action taken to mitigate risk.",
|
||||
"Resolution in progress.",
|
||||
"User notified of incident.",
|
||||
"Security incident confirmed.",
|
||||
"Containment measures implemented.",
|
||||
"Root cause analysis underway.",
|
||||
"Forensic investigation initiated.",
|
||||
"Data breach confirmed.",
|
||||
"Incident response team activated.",
|
||||
"Legal counsel consulted.",
|
||||
"Public relations notified.",
|
||||
"Regulatory authorities informed.",
|
||||
"Compensation plan developed.",
|
||||
"Press release drafted.",
|
||||
"Media monitoring in progress.",
|
||||
"Post-incident review scheduled.",
|
||||
})
|
||||
}
|
||||
|
||||
func fakeTicketTask() string {
|
||||
return random([]string{
|
||||
"Interview witnesses.",
|
||||
"Review security camera footage.",
|
||||
"Analyze network traffic logs.",
|
||||
"Scan for malware on affected systems.",
|
||||
"Check for unauthorized software installations.",
|
||||
"Conduct vulnerability assessment.",
|
||||
"Implement security patch.",
|
||||
"Change firewall rules.",
|
||||
"Reset compromised credentials.",
|
||||
"Isolate infected systems.",
|
||||
"Monitor for further suspicious activity.",
|
||||
"Coordinate with law enforcement.",
|
||||
"Notify affected customers.",
|
||||
"Prepare incident report.",
|
||||
"Update security policies.",
|
||||
"Train employees on security best practices.",
|
||||
"Conduct post-incident review.",
|
||||
"Implement lessons learned.",
|
||||
"Improve incident response procedures.",
|
||||
"Enhance security awareness program.",
|
||||
})
|
||||
}
|
||||
|
||||
func random[T any](e []T) T {
|
||||
return e[gofakeit.IntN(len(e))]
|
||||
}
|
||||
60
app/data/text_test.go
Normal file
60
app/data/text_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_fakeTicketComment(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.NotEmpty(t, fakeTicketComment())
|
||||
}
|
||||
|
||||
func Test_fakeTicketDescription(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.NotEmpty(t, fakeTicketDescription())
|
||||
}
|
||||
|
||||
func Test_fakeTicketTask(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.NotEmpty(t, fakeTicketTask())
|
||||
}
|
||||
|
||||
func Test_fakeTicketTimelineMessage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.NotEmpty(t, fakeTicketTimelineMessage())
|
||||
}
|
||||
|
||||
func Test_random(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args[T any] struct {
|
||||
e []T
|
||||
}
|
||||
|
||||
type testCase[T any] struct {
|
||||
name string
|
||||
args args[T]
|
||||
}
|
||||
|
||||
tests := []testCase[int]{
|
||||
{
|
||||
name: "Test random",
|
||||
args: args[int]{e: []int{1, 2, 3, 4, 5}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := random(tt.args.e)
|
||||
|
||||
assert.Contains(t, tt.args.e, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
216
app/data/upgradetest.go
Normal file
216
app/data/upgradetest.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
|
||||
"github.com/SecurityBrewery/catalyst/app/pointer"
|
||||
)
|
||||
|
||||
//go:embed scripts/upgradetest.py
|
||||
var Script string
|
||||
|
||||
func GenerateUpgradeTestData(ctx context.Context, queries *sqlc.Queries) error { //nolint:cyclop
|
||||
if _, err := createTestUser(ctx, queries); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ticket := range CreateUpgradeTestDataTickets() {
|
||||
_, err := queries.InsertTicket(ctx, sqlc.InsertTicketParams{
|
||||
ID: ticket.ID,
|
||||
Name: ticket.Name,
|
||||
Type: ticket.Type,
|
||||
Description: ticket.Description,
|
||||
Open: ticket.Open,
|
||||
Schema: ticket.Schema,
|
||||
State: ticket.State,
|
||||
Owner: ticket.Owner,
|
||||
Resolution: ticket.Resolution,
|
||||
Created: ticket.Created,
|
||||
Updated: ticket.Updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create ticket: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, comment := range CreateUpgradeTestDataComments() {
|
||||
_, err := queries.InsertComment(ctx, sqlc.InsertCommentParams{
|
||||
ID: comment.ID,
|
||||
Ticket: comment.Ticket,
|
||||
Author: comment.Author,
|
||||
Message: comment.Message,
|
||||
Created: comment.Created,
|
||||
Updated: comment.Updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create comment: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, timeline := range CreateUpgradeTestDataTimeline() {
|
||||
_, err := queries.InsertTimeline(ctx, sqlc.InsertTimelineParams{
|
||||
ID: timeline.ID,
|
||||
Ticket: timeline.Ticket,
|
||||
Time: timeline.Time,
|
||||
Message: timeline.Message,
|
||||
Created: timeline.Created,
|
||||
Updated: timeline.Updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create timeline: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, task := range CreateUpgradeTestDataTasks() {
|
||||
_, err := queries.InsertTask(ctx, sqlc.InsertTaskParams{
|
||||
ID: task.ID,
|
||||
Ticket: task.Ticket,
|
||||
Name: task.Name,
|
||||
Open: task.Open,
|
||||
Owner: task.Owner,
|
||||
Created: task.Created,
|
||||
Updated: task.Updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create task: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, link := range CreateUpgradeTestDataLinks() {
|
||||
_, err := queries.InsertLink(ctx, sqlc.InsertLinkParams{
|
||||
ID: link.ID,
|
||||
Ticket: link.Ticket,
|
||||
Url: link.Url,
|
||||
Name: link.Name,
|
||||
Created: link.Created,
|
||||
Updated: link.Updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create link: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, reaction := range CreateUpgradeTestDataReaction() {
|
||||
_, err := queries.InsertReaction(ctx, sqlc.InsertReactionParams{ //nolint: staticcheck
|
||||
ID: reaction.ID,
|
||||
Name: reaction.Name,
|
||||
Trigger: reaction.Trigger,
|
||||
Triggerdata: reaction.Triggerdata,
|
||||
Action: reaction.Action,
|
||||
Actiondata: reaction.Actiondata,
|
||||
Created: reaction.Created,
|
||||
Updated: reaction.Updated,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create reaction: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateUpgradeTestDataTickets() map[string]sqlc.Ticket {
|
||||
return map[string]sqlc.Ticket{
|
||||
"t_0": {
|
||||
ID: "t_0",
|
||||
Created: ticketCreated,
|
||||
Updated: ticketCreated.Add(time.Minute * 5),
|
||||
Name: "phishing-123",
|
||||
Type: "alert",
|
||||
Description: "Phishing email reported by several employees.",
|
||||
Open: true,
|
||||
Schema: json.RawMessage(`{"type":"object","properties":{"tlp":{"title":"TLP","type":"string"}}}`),
|
||||
State: json.RawMessage(`{"severity":"Medium"}`),
|
||||
Owner: pointer.Pointer("u_test"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUpgradeTestDataComments() map[string]sqlc.Comment {
|
||||
return map[string]sqlc.Comment{
|
||||
"c_0": {
|
||||
ID: "c_0",
|
||||
Created: ticketCreated.Add(time.Minute * 10),
|
||||
Updated: ticketCreated.Add(time.Minute * 15),
|
||||
Ticket: "t_0",
|
||||
Author: "u_test",
|
||||
Message: "This is a test comment.",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUpgradeTestDataTimeline() map[string]sqlc.Timeline {
|
||||
return map[string]sqlc.Timeline{
|
||||
"tl_0": {
|
||||
ID: "tl_0",
|
||||
Created: ticketCreated.Add(time.Minute * 15),
|
||||
Updated: ticketCreated.Add(time.Minute * 20),
|
||||
Ticket: "t_0",
|
||||
Time: ticketCreated.Add(time.Minute * 15),
|
||||
Message: "This is a test timeline message.",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUpgradeTestDataTasks() map[string]sqlc.Task {
|
||||
return map[string]sqlc.Task{
|
||||
"ts_0": {
|
||||
ID: "ts_0",
|
||||
Created: ticketCreated.Add(time.Minute * 20),
|
||||
Updated: ticketCreated.Add(time.Minute * 25),
|
||||
Ticket: "t_0",
|
||||
Name: "This is a test task.",
|
||||
Open: true,
|
||||
Owner: pointer.Pointer("u_test"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUpgradeTestDataLinks() map[string]sqlc.Link {
|
||||
return map[string]sqlc.Link{
|
||||
"l_0": {
|
||||
ID: "l_0",
|
||||
Created: ticketCreated.Add(time.Minute * 25),
|
||||
Updated: ticketCreated.Add(time.Minute * 30),
|
||||
Ticket: "t_0",
|
||||
Url: "https://www.example.com",
|
||||
Name: "This is a test link.",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUpgradeTestDataReaction() map[string]sqlc.Reaction {
|
||||
var (
|
||||
reactionCreated = time.Date(2025, 2, 1, 11, 30, 0, 0, time.UTC)
|
||||
reactionUpdated = reactionCreated.Add(time.Minute * 5)
|
||||
)
|
||||
|
||||
createTicketActionData := marshal(map[string]any{
|
||||
"requirements": "pocketbase",
|
||||
"script": Script,
|
||||
})
|
||||
|
||||
return map[string]sqlc.Reaction{
|
||||
"w_0": {
|
||||
ID: "w_0",
|
||||
Created: reactionCreated,
|
||||
Updated: reactionUpdated,
|
||||
Name: "Create New Ticket",
|
||||
Trigger: "schedule",
|
||||
Triggerdata: json.RawMessage(`{"expression":"12 * * * *"}`),
|
||||
Action: "python",
|
||||
Actiondata: createTicketActionData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func marshal(m map[string]any) json.RawMessage {
|
||||
b, _ := json.Marshal(m) //nolint:errchkjson
|
||||
|
||||
return b
|
||||
}
|
||||
30
app/data/user.go
Normal file
30
app/data/user.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/app/auth/password"
|
||||
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
|
||||
"github.com/SecurityBrewery/catalyst/app/pointer"
|
||||
)
|
||||
|
||||
func createTestUser(ctx context.Context, queries *sqlc.Queries) (sqlc.User, error) {
|
||||
passwordHash, tokenKey, err := password.Hash("1234567890")
|
||||
if err != nil {
|
||||
return sqlc.User{}, fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
return queries.InsertUser(ctx, sqlc.InsertUserParams{
|
||||
ID: "u_test",
|
||||
Username: "u_test",
|
||||
Name: pointer.Pointer("Test User"),
|
||||
Email: pointer.Pointer("user@catalyst-soar.com"),
|
||||
Active: true,
|
||||
PasswordHash: passwordHash,
|
||||
TokenKey: tokenKey,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user