mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-11 17:52:50 +01:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88cc02b350 | ||
|
|
46f7815699 | ||
|
|
ea03a3ed23 | ||
|
|
6346140de5 | ||
|
|
d7bdf1d276 | ||
|
|
1e1022ab15 |
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@@ -7,6 +7,8 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
goreleaser:
|
goreleaser:
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ builds:
|
|||||||
- linux
|
- linux
|
||||||
- darwin
|
- darwin
|
||||||
|
|
||||||
|
dockers:
|
||||||
|
- ids: [ catalyst ]
|
||||||
|
dockerfile: docker/goreleaser.Dockerfile
|
||||||
|
image_templates:
|
||||||
|
- "ghcr.io/securitybrewery/catalyst:latest"
|
||||||
|
- "ghcr.io/securitybrewery/catalyst:{{.Tag}}"
|
||||||
|
- "ghcr.io/securitybrewery/catalyst:v{{.Major}}"
|
||||||
|
- "ghcr.io/securitybrewery/catalyst:v{{.Major}}.{{.Minor}}"
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- format: tar.gz
|
- format: tar.gz
|
||||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -57,6 +57,6 @@ dev-10000:
|
|||||||
go run . fake-data --users 100 --tickets 10000
|
go run . fake-data --users 100 --tickets 10000
|
||||||
go run . serve
|
go run . serve
|
||||||
|
|
||||||
.PHONY: dev-ui
|
.PHONY: serve-ui
|
||||||
serve-ui:
|
serve-ui:
|
||||||
cd ui && bun dev --port 3000
|
cd ui && bun dev --port 3000
|
||||||
|
|||||||
24
docker/Dockerfile
Normal file
24
docker/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM oven/bun:debian
|
||||||
|
RUN apt-get update && apt-get install -y make
|
||||||
|
COPY .. /tmp/catalyst
|
||||||
|
|
||||||
|
WORKDIR /tmp/catalyst
|
||||||
|
|
||||||
|
RUN make build-ui
|
||||||
|
|
||||||
|
FROM golang:1.23
|
||||||
|
COPY --from=0 /tmp/catalyst /tmp/catalyst
|
||||||
|
|
||||||
|
WORKDIR /tmp/catalyst
|
||||||
|
|
||||||
|
RUN go build -o /usr/local/bin/catalyst
|
||||||
|
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
COPY --from=1 /usr/local/bin/catalyst /usr/local/bin/catalyst
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
VOLUME /usr/local/bin/catalyst_data
|
||||||
|
|
||||||
|
CMD ["/usr/local/bin/catalyst", "serve", "--http", "0.0.0.0:8080"]
|
||||||
9
docker/goreleaser.Dockerfile
Normal file
9
docker/goreleaser.Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
COPY catalyst /usr/local/bin/catalyst
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
VOLUME /usr/local/bin/catalyst_data
|
||||||
|
|
||||||
|
CMD ["/usr/local/bin/catalyst", "serve", "--http", "0.0.0.0:8080"]
|
||||||
@@ -316,7 +316,7 @@ func reactionRecords(dao *daos.Dao) []*models.Record {
|
|||||||
|
|
||||||
record := models.NewRecord(collection)
|
record := models.NewRecord(collection)
|
||||||
record.SetId("w_" + security.PseudorandomString(10))
|
record.SetId("w_" + security.PseudorandomString(10))
|
||||||
record.Set("name", "Test Reaction")
|
record.Set("name", "Alert Ingest Webhook")
|
||||||
record.Set("trigger", "webhook")
|
record.Set("trigger", "webhook")
|
||||||
record.Set("triggerdata", triggerWebhook)
|
record.Set("triggerdata", triggerWebhook)
|
||||||
record.Set("action", "python")
|
record.Set("action", "python")
|
||||||
@@ -334,7 +334,7 @@ func reactionRecords(dao *daos.Dao) []*models.Record {
|
|||||||
|
|
||||||
record = models.NewRecord(collection)
|
record = models.NewRecord(collection)
|
||||||
record.SetId("w_" + security.PseudorandomString(10))
|
record.SetId("w_" + security.PseudorandomString(10))
|
||||||
record.Set("name", "Test Reaction 2")
|
record.Set("name", "Assign new Tickets")
|
||||||
record.Set("trigger", "hook")
|
record.Set("trigger", "hook")
|
||||||
record.Set("triggerdata", triggerHook)
|
record.Set("triggerdata", triggerHook)
|
||||||
record.Set("action", "python")
|
record.Set("action", "python")
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -11,6 +11,7 @@ require (
|
|||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/tidwall/sjson v1.2.5
|
github.com/tidwall/sjson v1.2.5
|
||||||
|
go.uber.org/multierr v1.11.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -229,6 +229,8 @@ go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D
|
|||||||
go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s=
|
go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s=
|
||||||
go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM=
|
go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM=
|
||||||
go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I=
|
go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro=
|
gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro=
|
||||||
gocloud.dev v0.37.0/go.mod h1:7/O4kqdInCNsc6LqgmuFnS0GRew4XNNYWpA44yQnwco=
|
gocloud.dev v0.37.0/go.mod h1:7/O4kqdInCNsc6LqgmuFnS0GRew4XNNYWpA44yQnwco=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
"github.com/SecurityBrewery/catalyst/migrations"
|
"github.com/SecurityBrewery/catalyst/migrations"
|
||||||
"github.com/SecurityBrewery/catalyst/reaction/action"
|
"github.com/SecurityBrewery/catalyst/reaction/action"
|
||||||
@@ -70,43 +71,49 @@ func runHook(ctx context.Context, app core.App, collection, event string, record
|
|||||||
return fmt.Errorf("failed to marshal webhook payload: %w", err)
|
return fmt.Errorf("failed to marshal webhook payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hook, found, err := findByHookTrigger(app.Dao(), collection, event)
|
hooks, err := findByHookTrigger(app.Dao(), collection, event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to find hook by trigger: %w", err)
|
return fmt.Errorf("failed to find hook by trigger: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if len(hooks) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = action.Run(ctx, app, hook.GetString("action"), hook.GetString("actiondata"), string(payload))
|
var errs error
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to run hook reaction: %w", err)
|
for _, hook := range hooks {
|
||||||
|
_, err = action.Run(ctx, app, hook.GetString("action"), hook.GetString("actiondata"), string(payload))
|
||||||
|
if err != nil {
|
||||||
|
errs = multierr.Append(errs, fmt.Errorf("failed to run hook reaction: %w", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func findByHookTrigger(dao *daos.Dao, collection, event string) (*models.Record, bool, error) {
|
func findByHookTrigger(dao *daos.Dao, collection, event string) ([]*models.Record, error) {
|
||||||
records, err := dao.FindRecordsByExpr(migrations.ReactionCollectionName, dbx.HashExp{"trigger": "hook"})
|
records, err := dao.FindRecordsByExpr(migrations.ReactionCollectionName, dbx.HashExp{"trigger": "hook"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("failed to find hook reaction: %w", err)
|
return nil, fmt.Errorf("failed to find hook reaction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(records) == 0 {
|
if len(records) == 0 {
|
||||||
return nil, false, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var matchedRecords []*models.Record
|
||||||
|
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
var hook Hook
|
var hook Hook
|
||||||
if err := json.Unmarshal([]byte(record.GetString("triggerdata")), &hook); err != nil {
|
if err := json.Unmarshal([]byte(record.GetString("triggerdata")), &hook); err != nil {
|
||||||
return nil, false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(hook.Collections, collection) && slices.Contains(hook.Events, event) {
|
if slices.Contains(hook.Collections, collection) && slices.Contains(hook.Events, event) {
|
||||||
return record, true, nil
|
matchedRecords = append(matchedRecords, record)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, false, nil
|
return matchedRecords, nil
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
ui/bun.lockb
BIN
ui/bun.lockb
Binary file not shown.
@@ -28,6 +28,7 @@
|
|||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"easymde": "^2.18.0",
|
"easymde": "^2.18.0",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
"lucide-vue-next": "^0.365.0",
|
"lucide-vue-next": "^0.365.0",
|
||||||
"marked": "^12.0.2",
|
"marked": "^12.0.2",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
@@ -48,6 +49,7 @@
|
|||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@tsconfig/node20": "^20.1.2",
|
"@tsconfig/node20": "^20.1.2",
|
||||||
"@types/lodash.debounce": "^4.0.9",
|
"@types/lodash.debounce": "^4.0.9",
|
||||||
|
"@types/lodash.isequal": "^4.5.8",
|
||||||
"@types/node": "^20.11.28",
|
"@types/node": "^20.11.28",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue/eslint-config-prettier": "^8.0.0",
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
SelectValue
|
SelectValue
|
||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
|
|
||||||
|
import isEqual from 'lodash.isequal'
|
||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
import type { JSONSchema } from '@/lib/types'
|
import type { JSONSchema } from '@/lib/types'
|
||||||
@@ -34,6 +35,11 @@ onMounted(() => {
|
|||||||
watch(
|
watch(
|
||||||
() => formdata.value,
|
() => formdata.value,
|
||||||
() => {
|
() => {
|
||||||
|
const normFormdata = JSON.parse(JSON.stringify(formdata.value))
|
||||||
|
const normModel = JSON.parse(JSON.stringify(model.value))
|
||||||
|
|
||||||
|
if (isEqual(normFormdata, normModel)) return
|
||||||
|
|
||||||
model.value = { ...formdata.value }
|
model.value = { ...formdata.value }
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ const curlExample = computed(() => {
|
|||||||
let cmd = `curl`
|
let cmd = `curl`
|
||||||
|
|
||||||
if (values.triggerdata.token) {
|
if (values.triggerdata.token) {
|
||||||
cmd += ` -H "Auth: Bearer ${values.triggerdata.token}"`
|
cmd += ` -H "Authorization: Bearer ${values.triggerdata.token}"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.triggerdata.path) {
|
if (values.triggerdata.path) {
|
||||||
|
|||||||
Reference in New Issue
Block a user