diff --git a/.github/workflows/v11-deployment-pipeline.yml b/.github/workflows/v11-deployment-pipeline.yml index 65d09e245..538de44a3 100644 --- a/.github/workflows/v11-deployment-pipeline.yml +++ b/.github/workflows/v11-deployment-pipeline.yml @@ -130,16 +130,27 @@ jobs: - name: Check out code into the right branch uses: actions/checkout@v4 - - name: Build Linux Binaries + - name: Build Linux Binaries (amd64) env: GOOS: linux GOARCH: amd64 run: | cd ${{ github.workspace }}/agent - go build -o utmstack_agent_service -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + go build -o utmstack_agent_service_linux_amd64 -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . cd ${{ github.workspace }}/agent/updater - go build -o utmstack_updater_service . + go build -o utmstack_updater_service_linux_amd64 . + + - name: Build Linux Binaries (arm64) + env: + GOOS: linux + GOARCH: arm64 + run: | + cd ${{ github.workspace }}/agent + go build -o utmstack_agent_service_linux_arm64 -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + + cd ${{ github.workspace }}/agent/updater + go build -o utmstack_updater_service_linux_arm64 . - name: Build Windows Binaries (amd64) env: @@ -147,10 +158,10 @@ jobs: GOARCH: amd64 run: | cd ${{ github.workspace }}/agent - go build -o utmstack_agent_service.exe -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + go build -o utmstack_agent_service_windows_amd64.exe -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . cd ${{ github.workspace }}/agent/updater - go build -o utmstack_updater_service.exe . + go build -o utmstack_updater_service_windows_amd64.exe . - name: Build Windows Binaries (arm64) env: @@ -158,34 +169,138 @@ jobs: GOARCH: arm64 run: | cd ${{ github.workspace }}/agent - go build -o utmstack_agent_service_arm64.exe -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + go build -o utmstack_agent_service_windows_arm64.exe -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . cd ${{ github.workspace }}/agent/updater - go build -o utmstack_updater_service_arm64.exe . + go build -o utmstack_updater_service_windows_arm64.exe . - name: Sign Windows Agents run: | cd ${{ github.workspace }}/agent - signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_agent_service.exe" - signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_agent_service_arm64.exe" + signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_agent_service_windows_amd64.exe" + signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_agent_service_windows_arm64.exe" cd ${{ github.workspace }}/agent/updater - signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_updater_service.exe" - signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_updater_service_arm64.exe" + signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_updater_service_windows_amd64.exe" + signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_updater_service_windows_arm64.exe" - name: Upload signed binaries as artifacts uses: actions/upload-artifact@v4 with: name: signed-agents path: | - ${{ github.workspace }}/agent/utmstack_agent_service - ${{ github.workspace }}/agent/utmstack_agent_service.exe - ${{ github.workspace }}/agent/utmstack_agent_service_arm64.exe - ${{ github.workspace }}/agent/updater/utmstack_updater_service - ${{ github.workspace }}/agent/updater/utmstack_updater_service.exe - ${{ github.workspace }}/agent/updater/utmstack_updater_service_arm64.exe + ${{ github.workspace }}/agent/utmstack_agent_service_linux_amd64 + ${{ github.workspace }}/agent/utmstack_agent_service_linux_arm64 + ${{ github.workspace }}/agent/utmstack_agent_service_windows_amd64.exe + ${{ github.workspace }}/agent/utmstack_agent_service_windows_arm64.exe + ${{ github.workspace }}/agent/updater/utmstack_updater_service_linux_amd64 + ${{ github.workspace }}/agent/updater/utmstack_updater_service_linux_arm64 + ${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_amd64.exe + ${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_arm64.exe retention-days: 1 - + + build_agent_darwin: + name: Build and Sign Agent (macOS) + needs: [setup_deployment] + if: ${{ needs.setup_deployment.outputs.tag != '' }} + runs-on: macos-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Install Apple Certificate + env: + CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} + CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + run: | + # Create temporary keychain + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + KEYCHAIN_PASSWORD=$(openssl rand -base64 32) + + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # Import certificate + echo "$CERTIFICATE_BASE64" | base64 --decode > $RUNNER_TEMP/certificate.p12 + security import $RUNNER_TEMP/certificate.p12 -P "$CERTIFICATE_PASSWORD" \ + -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + # Allow codesign without prompt + security set-key-partition-list -S apple-tool:,apple:,codesign: \ + -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + - name: Build macOS Agent (arm64) + env: + GOOS: darwin + GOARCH: arm64 + CGO_ENABLED: 0 + run: | + cd ${{ github.workspace }}/agent + go build -o utmstack_agent_service_darwin_arm64 -v \ + -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + + cd ${{ github.workspace }}/agent/updater + go build -o utmstack_updater_service_darwin_arm64 . + + - name: Sign macOS Binaries + env: + SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + run: | + # Sign agent + codesign --force --options runtime \ + --sign "$SIGNING_IDENTITY" \ + --timestamp \ + ${{ github.workspace }}/agent/utmstack_agent_service_darwin_arm64 + + # Sign updater + codesign --force --options runtime \ + --sign "$SIGNING_IDENTITY" \ + --timestamp \ + ${{ github.workspace }}/agent/updater/utmstack_updater_service_darwin_arm64 + + - name: Notarize macOS Binaries + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + # Create zip for notarization (notarytool requires zip/pkg/dmg) + cd ${{ github.workspace }}/agent + zip utmstack_agent_darwin_arm64.zip utmstack_agent_service_darwin_arm64 + + cd ${{ github.workspace }}/agent/updater + zip utmstack_updater_darwin_arm64.zip utmstack_updater_service_darwin_arm64 + + # Notarize agent + xcrun notarytool submit ${{ github.workspace }}/agent/utmstack_agent_darwin_arm64.zip \ + --apple-id "$APPLE_ID" \ + --password "$APPLE_APP_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" \ + --wait + + # Notarize updater + xcrun notarytool submit ${{ github.workspace }}/agent/updater/utmstack_updater_darwin_arm64.zip \ + --apple-id "$APPLE_ID" \ + --password "$APPLE_APP_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" \ + --wait + + - name: Upload macOS binaries as artifacts + uses: actions/upload-artifact@v4 + with: + name: signed-agents-darwin + path: | + ${{ github.workspace }}/agent/utmstack_agent_service_darwin_arm64 + ${{ github.workspace }}/agent/updater/utmstack_updater_service_darwin_arm64 + retention-days: 1 + build_utmstack_collector: name: Build UTMStack Collector needs: [setup_deployment] @@ -211,8 +326,8 @@ jobs: build_agent_manager: name: Build Agent Manager Microservice - needs: [build_agent, build_utmstack_collector, setup_deployment] - if: ${{ always() && needs.build_agent.result == 'success' && needs.build_utmstack_collector.result == 'success' && needs.setup_deployment.outputs.tag != '' }} + needs: [build_agent, build_agent_darwin, build_utmstack_collector, setup_deployment] + if: ${{ always() && needs.build_agent.result == 'success' && needs.build_agent_darwin.result == 'success' && needs.build_utmstack_collector.result == 'success' && needs.setup_deployment.outputs.tag != '' }} runs-on: ubuntu-24.04 steps: - name: Check out code into the right branch @@ -230,6 +345,12 @@ jobs: name: utmstack-collector path: ${{ github.workspace }}/utmstack-collector + - name: Download signed macOS agents from artifacts + uses: actions/download-artifact@v4 + with: + name: signed-agents-darwin + path: ${{ github.workspace }}/agent-darwin + - name: Prepare dependencies for Agent Manager Image run: | cd ${{ github.workspace }}/agent-manager @@ -243,17 +364,33 @@ jobs: cp "${{ github.workspace }}/utmstack-collector/version.json" ./dependencies/collector/ mkdir -p ./dependencies/agent/ - curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/agent/utmstack_agent_dependencies_linux.zip" -o ./dependencies/agent/utmstack_agent_dependencies_linux.zip - curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/agent/utmstack_agent_dependencies_windows.zip" -o ./dependencies/agent/utmstack_agent_dependencies_windows.zip - curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/agent/utmstack_agent_dependencies_windows_arm64.zip" -o ./dependencies/agent/utmstack_agent_dependencies_windows_arm64.zip - curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/agent/utmstack-macos-agent-v10.pkg" -o ./dependencies/agent/utmstack-macos-agent.pkg - - cp "${{ github.workspace }}/agent/utmstack_agent_service" ./dependencies/agent/ - cp "${{ github.workspace }}/agent/utmstack_agent_service.exe" ./dependencies/agent/ - cp "${{ github.workspace }}/agent/utmstack_agent_service_arm64.exe" ./dependencies/agent/ - cp "${{ github.workspace }}/agent/updater/utmstack_updater_service" ./dependencies/agent/ - cp "${{ github.workspace }}/agent/updater/utmstack_updater_service.exe" ./dependencies/agent/ - cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_arm64.exe" ./dependencies/agent/ + + # Linux agents + cp "${{ github.workspace }}/agent/utmstack_agent_service_linux_amd64" ./dependencies/agent/ + cp "${{ github.workspace }}/agent/utmstack_agent_service_linux_arm64" ./dependencies/agent/ + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_linux_amd64" ./dependencies/agent/ + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_linux_arm64" ./dependencies/agent/ + + # Windows agents + cp "${{ github.workspace }}/agent/utmstack_agent_service_windows_amd64.exe" ./dependencies/agent/ + cp "${{ github.workspace }}/agent/utmstack_agent_service_windows_arm64.exe" ./dependencies/agent/ + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_amd64.exe" ./dependencies/agent/ + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_arm64.exe" ./dependencies/agent/ + + # macOS agents (signed and notarized) + cp "${{ github.workspace }}/agent-darwin/utmstack_agent_service_darwin_arm64" ./dependencies/agent/ + cp "${{ github.workspace }}/agent-darwin/updater/utmstack_updater_service_darwin_arm64" ./dependencies/agent/ + curl -sSL "https://storage.googleapis.com/utmstack-updates/agent_updates/release/macos-agent/latest/utmstack-collector-mac" -o ./dependencies/agent/utmstack-collector-mac + + # TODO: Remove legacy binary names after all agents have migrated to new naming convention + # Legacy names for backwards compatibility with existing agents + cp "${{ github.workspace }}/agent/utmstack_agent_service_linux_amd64" ./dependencies/agent/utmstack_agent_service + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_linux_amd64" ./dependencies/agent/utmstack_updater_service + cp "${{ github.workspace }}/agent/utmstack_agent_service_windows_amd64.exe" ./dependencies/agent/utmstack_agent_service.exe + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_amd64.exe" ./dependencies/agent/utmstack_updater_service.exe + cp "${{ github.workspace }}/agent/utmstack_agent_service_windows_arm64.exe" ./dependencies/agent/utmstack_agent_service_arm64.exe + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_arm64.exe" ./dependencies/agent/utmstack_updater_service_arm64.exe + cp "${{ github.workspace }}/agent/version.json" ./dependencies/agent/ - name: Login to GitHub Container Registry diff --git a/agent-manager/agent/agent.pb.go b/agent-manager/agent/agent.pb.go index 7c73da984..d5b52edb9 100644 --- a/agent-manager/agent/agent.pb.go +++ b/agent-manager/agent/agent.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: agent.proto package agent @@ -12,6 +12,7 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -77,30 +78,27 @@ func (AgentCommandStatus) EnumDescriptor() ([]byte, []int) { } type AgentRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` - Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` - Platform string `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"` - Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` - RegisterBy string `protobuf:"bytes,6,opt,name=register_by,json=registerBy,proto3" json:"register_by,omitempty"` - Mac string `protobuf:"bytes,7,opt,name=mac,proto3" json:"mac,omitempty"` - OsMajorVersion string `protobuf:"bytes,8,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` - OsMinorVersion string `protobuf:"bytes,9,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` - Aliases string `protobuf:"bytes,10,opt,name=aliases,proto3" json:"aliases,omitempty"` - Addresses string `protobuf:"bytes,11,opt,name=addresses,proto3" json:"addresses,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + Platform string `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"` + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + RegisterBy string `protobuf:"bytes,6,opt,name=register_by,json=registerBy,proto3" json:"register_by,omitempty"` + Mac string `protobuf:"bytes,7,opt,name=mac,proto3" json:"mac,omitempty"` + OsMajorVersion string `protobuf:"bytes,8,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` + OsMinorVersion string `protobuf:"bytes,9,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` + Aliases string `protobuf:"bytes,10,opt,name=aliases,proto3" json:"aliases,omitempty"` + Addresses string `protobuf:"bytes,11,opt,name=addresses,proto3" json:"addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentRequest) Reset() { *x = AgentRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentRequest) String() string { @@ -111,7 +109,7 @@ func (*AgentRequest) ProtoMessage() {} func (x *AgentRequest) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -204,21 +202,18 @@ func (x *AgentRequest) GetAddresses() string { } type ListAgentsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Rows []*Agent `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` unknownFields protoimpl.UnknownFields - - Rows []*Agent `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` - Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListAgentsResponse) Reset() { *x = ListAgentsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListAgentsResponse) String() string { @@ -229,7 +224,7 @@ func (*ListAgentsResponse) ProtoMessage() {} func (x *ListAgentsResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -259,33 +254,30 @@ func (x *ListAgentsResponse) GetTotal() int32 { } type Agent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` - Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` - Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=agent.Status" json:"status,omitempty"` - Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` - Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` - AgentKey string `protobuf:"bytes,7,opt,name=agent_key,json=agentKey,proto3" json:"agent_key,omitempty"` - Id uint32 `protobuf:"varint,8,opt,name=id,proto3" json:"id,omitempty"` - LastSeen string `protobuf:"bytes,9,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` - Mac string `protobuf:"bytes,10,opt,name=mac,proto3" json:"mac,omitempty"` - OsMajorVersion string `protobuf:"bytes,11,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` - OsMinorVersion string `protobuf:"bytes,12,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` - Aliases string `protobuf:"bytes,13,opt,name=aliases,proto3" json:"aliases,omitempty"` - Addresses string `protobuf:"bytes,14,opt,name=addresses,proto3" json:"addresses,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=agent.Status" json:"status,omitempty"` + Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` + Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` + AgentKey string `protobuf:"bytes,7,opt,name=agent_key,json=agentKey,proto3" json:"agent_key,omitempty"` + Id uint32 `protobuf:"varint,8,opt,name=id,proto3" json:"id,omitempty"` + LastSeen string `protobuf:"bytes,9,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + Mac string `protobuf:"bytes,10,opt,name=mac,proto3" json:"mac,omitempty"` + OsMajorVersion string `protobuf:"bytes,11,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` + OsMinorVersion string `protobuf:"bytes,12,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` + Aliases string `protobuf:"bytes,13,opt,name=aliases,proto3" json:"aliases,omitempty"` + Addresses string `protobuf:"bytes,14,opt,name=addresses,proto3" json:"addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Agent) Reset() { *x = Agent{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Agent) String() string { @@ -296,7 +288,7 @@ func (*Agent) ProtoMessage() {} func (x *Agent) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -410,24 +402,21 @@ func (x *Agent) GetAddresses() string { } type BidirectionalStream struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to StreamMessage: + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to StreamMessage: // // *BidirectionalStream_Command // *BidirectionalStream_Result StreamMessage isBidirectionalStream_StreamMessage `protobuf_oneof:"stream_message"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *BidirectionalStream) Reset() { *x = BidirectionalStream{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BidirectionalStream) String() string { @@ -438,7 +427,7 @@ func (*BidirectionalStream) ProtoMessage() {} func (x *BidirectionalStream) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -453,23 +442,27 @@ func (*BidirectionalStream) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{3} } -func (m *BidirectionalStream) GetStreamMessage() isBidirectionalStream_StreamMessage { - if m != nil { - return m.StreamMessage +func (x *BidirectionalStream) GetStreamMessage() isBidirectionalStream_StreamMessage { + if x != nil { + return x.StreamMessage } return nil } func (x *BidirectionalStream) GetCommand() *UtmCommand { - if x, ok := x.GetStreamMessage().(*BidirectionalStream_Command); ok { - return x.Command + if x != nil { + if x, ok := x.StreamMessage.(*BidirectionalStream_Command); ok { + return x.Command + } } return nil } func (x *BidirectionalStream) GetResult() *CommandResult { - if x, ok := x.GetStreamMessage().(*BidirectionalStream_Result); ok { - return x.Result + if x != nil { + if x, ok := x.StreamMessage.(*BidirectionalStream_Result); ok { + return x.Result + } } return nil } @@ -491,26 +484,24 @@ func (*BidirectionalStream_Command) isBidirectionalStream_StreamMessage() {} func (*BidirectionalStream_Result) isBidirectionalStream_StreamMessage() {} type UtmCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` + ExecutedBy string `protobuf:"bytes,3,opt,name=executed_by,json=executedBy,proto3" json:"executed_by,omitempty"` + CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` + OriginType string `protobuf:"bytes,5,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` + OriginId string `protobuf:"bytes,6,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` + Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + Shell string `protobuf:"bytes,8,opt,name=shell,proto3" json:"shell,omitempty"` // Shell to execute command: "cmd", "powershell" (Windows), "sh", "bash" (Linux/macOS). Empty = default unknownFields protoimpl.UnknownFields - - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` - ExecutedBy string `protobuf:"bytes,3,opt,name=executed_by,json=executedBy,proto3" json:"executed_by,omitempty"` - CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` - OriginType string `protobuf:"bytes,5,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` - OriginId string `protobuf:"bytes,6,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` - Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + sizeCache protoimpl.SizeCache } func (x *UtmCommand) Reset() { *x = UtmCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UtmCommand) String() string { @@ -521,7 +512,7 @@ func (*UtmCommand) ProtoMessage() {} func (x *UtmCommand) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -585,24 +576,28 @@ func (x *UtmCommand) GetReason() string { return "" } +func (x *UtmCommand) GetShell() string { + if x != nil { + return x.Shell + } + return "" +} + type CommandResult struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` + ExecutedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=executed_at,json=executedAt,proto3" json:"executed_at,omitempty"` + CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` unknownFields protoimpl.UnknownFields - - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` - ExecutedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=executed_at,json=executedAt,proto3" json:"executed_at,omitempty"` - CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *CommandResult) Reset() { *x = CommandResult{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CommandResult) String() string { @@ -613,7 +608,7 @@ func (*CommandResult) ProtoMessage() {} func (x *CommandResult) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -657,21 +652,18 @@ func (x *CommandResult) GetCmdId() string { } type ListAgentsCommandsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Rows []*AgentCommand `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` unknownFields protoimpl.UnknownFields - - Rows []*AgentCommand `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` - Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListAgentsCommandsResponse) Reset() { *x = ListAgentsCommandsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListAgentsCommandsResponse) String() string { @@ -682,7 +674,7 @@ func (*ListAgentsCommandsResponse) ProtoMessage() {} func (x *ListAgentsCommandsResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -712,10 +704,7 @@ func (x *ListAgentsCommandsResponse) GetTotal() int32 { } type AgentCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` CreatedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` AgentId uint32 `protobuf:"varint,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` @@ -727,15 +716,15 @@ type AgentCommand struct { Reason string `protobuf:"bytes,9,opt,name=reason,proto3" json:"reason,omitempty"` OriginType string `protobuf:"bytes,10,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` OriginId string `protobuf:"bytes,11,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentCommand) Reset() { *x = AgentCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentCommand) String() string { @@ -746,7 +735,7 @@ func (*AgentCommand) ProtoMessage() {} func (x *AgentCommand) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -840,182 +829,116 @@ func (x *AgentCommand) GetOriginId() string { var File_agent_proto protoreflect.FileDescriptor -var file_agent_proto_rawDesc = []byte{ - 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x02, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x42, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, - 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, - 0x73, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, - 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x4c, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x04, 0x72, - 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x22, 0x88, 0x03, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1a, 0x0a, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, - 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, - 0x61, 0x63, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, - 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, - 0x6f, 0x73, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, - 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, - 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x86, - 0x01, 0x0a, 0x13, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x55, 0x74, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2e, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x10, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x0a, 0x55, 0x74, 0x6d, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x15, 0x0a, 0x06, - 0x63, 0x6d, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, - 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x96, 0x01, 0x0a, 0x0d, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x3b, - 0x0a, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x63, - 0x6d, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, 0x64, - 0x49, 0x64, 0x22, 0x5b, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x27, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, - 0xa1, 0x03, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x40, 0x0a, 0x0e, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0d, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, - 0x64, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x63, 0x6d, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, - 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x69, 0x67, 0x69, - 0x6e, 0x49, 0x64, 0x2a, 0x57, 0x0a, 0x12, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x4f, 0x54, - 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x51, - 0x55, 0x45, 0x55, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, - 0x47, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x10, - 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x32, 0x9c, 0x03, 0x0a, - 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, - 0x0d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0b, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x3d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x4b, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, - 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x1a, 0x1a, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, - 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4c, 0x0a, - 0x11, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x73, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x4f, 0x0a, 0x0c, 0x50, - 0x61, 0x6e, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x50, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x11, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x74, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x1a, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x32, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_agent_proto_rawDesc = "" + + "\n" + + "\vagent.proto\x12\x05agent\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\fcommon.proto\"\xbf\x02\n" + + "\fAgentRequest\x12\x0e\n" + + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x0e\n" + + "\x02os\x18\x03 \x01(\tR\x02os\x12\x1a\n" + + "\bplatform\x18\x04 \x01(\tR\bplatform\x12\x18\n" + + "\aversion\x18\x05 \x01(\tR\aversion\x12\x1f\n" + + "\vregister_by\x18\x06 \x01(\tR\n" + + "registerBy\x12\x10\n" + + "\x03mac\x18\a \x01(\tR\x03mac\x12(\n" + + "\x10os_major_version\x18\b \x01(\tR\x0eosMajorVersion\x12(\n" + + "\x10os_minor_version\x18\t \x01(\tR\x0eosMinorVersion\x12\x18\n" + + "\aaliases\x18\n" + + " \x01(\tR\aaliases\x12\x1c\n" + + "\taddresses\x18\v \x01(\tR\taddresses\"L\n" + + "\x12ListAgentsResponse\x12 \n" + + "\x04rows\x18\x01 \x03(\v2\f.agent.AgentR\x04rows\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\x88\x03\n" + + "\x05Agent\x12\x0e\n" + + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x0e\n" + + "\x02os\x18\x03 \x01(\tR\x02os\x12%\n" + + "\x06status\x18\x04 \x01(\x0e2\r.agent.StatusR\x06status\x12\x1a\n" + + "\bplatform\x18\x05 \x01(\tR\bplatform\x12\x18\n" + + "\aversion\x18\x06 \x01(\tR\aversion\x12\x1b\n" + + "\tagent_key\x18\a \x01(\tR\bagentKey\x12\x0e\n" + + "\x02id\x18\b \x01(\rR\x02id\x12\x1b\n" + + "\tlast_seen\x18\t \x01(\tR\blastSeen\x12\x10\n" + + "\x03mac\x18\n" + + " \x01(\tR\x03mac\x12(\n" + + "\x10os_major_version\x18\v \x01(\tR\x0eosMajorVersion\x12(\n" + + "\x10os_minor_version\x18\f \x01(\tR\x0eosMinorVersion\x12\x18\n" + + "\aaliases\x18\r \x01(\tR\aaliases\x12\x1c\n" + + "\taddresses\x18\x0e \x01(\tR\taddresses\"\x86\x01\n" + + "\x13BidirectionalStream\x12-\n" + + "\acommand\x18\x01 \x01(\v2\x11.agent.UtmCommandH\x00R\acommand\x12.\n" + + "\x06result\x18\x02 \x01(\v2\x14.agent.CommandResultH\x00R\x06resultB\x10\n" + + "\x0estream_message\"\xe5\x01\n" + + "\n" + + "UtmCommand\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x18\n" + + "\acommand\x18\x02 \x01(\tR\acommand\x12\x1f\n" + + "\vexecuted_by\x18\x03 \x01(\tR\n" + + "executedBy\x12\x15\n" + + "\x06cmd_id\x18\x04 \x01(\tR\x05cmdId\x12\x1f\n" + + "\vorigin_type\x18\x05 \x01(\tR\n" + + "originType\x12\x1b\n" + + "\torigin_id\x18\x06 \x01(\tR\boriginId\x12\x16\n" + + "\x06reason\x18\a \x01(\tR\x06reason\x12\x14\n" + + "\x05shell\x18\b \x01(\tR\x05shell\"\x96\x01\n" + + "\rCommandResult\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x16\n" + + "\x06result\x18\x02 \x01(\tR\x06result\x12;\n" + + "\vexecuted_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "executedAt\x12\x15\n" + + "\x06cmd_id\x18\x04 \x01(\tR\x05cmdId\"[\n" + + "\x1aListAgentsCommandsResponse\x12'\n" + + "\x04rows\x18\x01 \x03(\v2\x13.agent.AgentCommandR\x04rows\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\xa1\x03\n" + + "\fAgentCommand\x129\n" + + "\n" + + "created_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x19\n" + + "\bagent_id\x18\x03 \x01(\rR\aagentId\x12\x18\n" + + "\acommand\x18\x04 \x01(\tR\acommand\x12@\n" + + "\x0ecommand_status\x18\x05 \x01(\x0e2\x19.agent.AgentCommandStatusR\rcommandStatus\x12\x16\n" + + "\x06result\x18\x06 \x01(\tR\x06result\x12\x1f\n" + + "\vexecuted_by\x18\a \x01(\tR\n" + + "executedBy\x12\x15\n" + + "\x06cmd_id\x18\b \x01(\tR\x05cmdId\x12\x16\n" + + "\x06reason\x18\t \x01(\tR\x06reason\x12\x1f\n" + + "\vorigin_type\x18\n" + + " \x01(\tR\n" + + "originType\x12\x1b\n" + + "\torigin_id\x18\v \x01(\tR\boriginId*W\n" + + "\x12AgentCommandStatus\x12\x10\n" + + "\fNOT_EXECUTED\x10\x00\x12\t\n" + + "\x05QUEUE\x10\x01\x12\v\n" + + "\aPENDING\x10\x02\x12\f\n" + + "\bEXECUTED\x10\x03\x12\t\n" + + "\x05ERROR\x10\x042\x9c\x03\n" + + "\fAgentService\x12;\n" + + "\rRegisterAgent\x12\x13.agent.AgentRequest\x1a\x13.agent.AuthResponse\"\x00\x129\n" + + "\vUpdateAgent\x12\x13.agent.AgentRequest\x1a\x13.agent.AuthResponse\"\x00\x12:\n" + + "\vDeleteAgent\x12\x14.agent.DeleteRequest\x1a\x13.agent.AuthResponse\"\x00\x12=\n" + + "\n" + + "ListAgents\x12\x12.agent.ListRequest\x1a\x19.agent.ListAgentsResponse\"\x00\x12K\n" + + "\vAgentStream\x12\x1a.agent.BidirectionalStream\x1a\x1a.agent.BidirectionalStream\"\x00(\x010\x01\x12L\n" + + "\x11ListAgentCommands\x12\x12.agent.ListRequest\x1a!.agent.ListAgentsCommandsResponse\"\x002O\n" + + "\fPanelService\x12?\n" + + "\x0eProcessCommand\x12\x11.agent.UtmCommand\x1a\x14.agent.CommandResult\"\x00(\x010\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_agent_proto_rawDescOnce sync.Once - file_agent_proto_rawDescData = file_agent_proto_rawDesc + file_agent_proto_rawDescData []byte ) func file_agent_proto_rawDescGZIP() []byte { file_agent_proto_rawDescOnce.Do(func() { - file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_proto_rawDescData) + file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc))) }) return file_agent_proto_rawDescData } var file_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 8) -var file_agent_proto_goTypes = []interface{}{ +var file_agent_proto_goTypes = []any{ (AgentCommandStatus)(0), // 0: agent.AgentCommandStatus (*AgentRequest)(nil), // 1: agent.AgentRequest (*ListAgentsResponse)(nil), // 2: agent.ListAgentsResponse @@ -1068,105 +991,7 @@ func file_agent_proto_init() { return } file_common_proto_init() - if !protoimpl.UnsafeEnabled { - file_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAgentsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Agent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BidirectionalStream); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UtmCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CommandResult); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAgentsCommandsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_agent_proto_msgTypes[3].OneofWrappers = []interface{}{ + file_agent_proto_msgTypes[3].OneofWrappers = []any{ (*BidirectionalStream_Command)(nil), (*BidirectionalStream_Result)(nil), } @@ -1174,7 +999,7 @@ func file_agent_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_agent_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc)), NumEnums: 1, NumMessages: 8, NumExtensions: 0, @@ -1186,7 +1011,6 @@ func file_agent_proto_init() { MessageInfos: file_agent_proto_msgTypes, }.Build() File_agent_proto = out.File - file_agent_proto_rawDesc = nil file_agent_proto_goTypes = nil file_agent_proto_depIdxs = nil } diff --git a/agent-manager/agent/agent_grpc.pb.go b/agent-manager/agent/agent_grpc.pb.go index f75ba57b2..400673e17 100644 --- a/agent-manager/agent/agent_grpc.pb.go +++ b/agent-manager/agent/agent_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: agent.proto package agent diff --git a/agent-manager/agent/agent_imp.go b/agent-manager/agent/agent_imp.go index d9e09b870..fafa7d97d 100644 --- a/agent-manager/agent/agent_imp.go +++ b/agent-manager/agent/agent_imp.go @@ -75,17 +75,13 @@ func (s *AgentService) RegisterAgent(ctx context.Context, req *AgentRequest) (*A } oldAgent := &models.Agent{} - err := s.DBConnection.GetFirst(oldAgent, "hostname = ?", agent.Hostname) + err := s.DBConnection.GetFirst(oldAgent, "hostname = ? AND mac = ?", agent.Hostname, agent.Mac) if err == nil { - if oldAgent.Ip == agent.Ip { - return &AuthResponse{ - Id: uint32(oldAgent.ID), - Key: oldAgent.AgentKey, - }, nil - } else { - catcher.Error("agent already exists", err, map[string]any{"hostname": agent.Hostname, "process": "agent-manager"}) - return nil, status.Errorf(codes.AlreadyExists, "hostname has already been registered") - } + // Same machine re-registering, return existing agent + return &AuthResponse{ + Id: uint32(oldAgent.ID), + Key: oldAgent.AgentKey, + }, nil } key := uuid.New().String() @@ -334,6 +330,7 @@ func (s *AgentService) ProcessCommand(stream PanelService_ProcessCommandServer) AgentId: cmd.AgentId, Command: replaceSecretValues(cmd.Command), CmdId: cmdID, + Shell: cmd.Shell, }, }, }) diff --git a/agent-manager/agent/collector.pb.go b/agent-manager/agent/collector.pb.go index bcf8d63b7..be2fe7fa6 100644 --- a/agent-manager/agent/collector.pb.go +++ b/agent-manager/agent/collector.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: collector.proto package agent @@ -766,7 +766,7 @@ const file_collector_proto_rawDesc = "" + "\x0fCollectorStream\x12\x18.agent.CollectorMessages\x1a\x18.agent.CollectorMessages\"\x00(\x010\x01\x12D\n" + "\x12GetCollectorConfig\x12\x14.agent.ConfigRequest\x1a\x16.agent.CollectorConfig\"\x002d\n" + "\x15PanelCollectorService\x12K\n" + - "\x17RegisterCollectorConfig\x12\x16.agent.CollectorConfig\x1a\x16.agent.ConfigKnowledge\"\x00B5Z3github.com/utmstack/UTMStack/docker-collector/agentb\x06proto3" + "\x17RegisterCollectorConfig\x12\x16.agent.CollectorConfig\x1a\x16.agent.ConfigKnowledge\"\x00B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_collector_proto_rawDescOnce sync.Once diff --git a/agent-manager/agent/collector_grpc.pb.go b/agent-manager/agent/collector_grpc.pb.go index a924c7361..307c97b51 100644 --- a/agent-manager/agent/collector_grpc.pb.go +++ b/agent-manager/agent/collector_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: collector.proto package agent diff --git a/agent-manager/agent/common.pb.go b/agent-manager/agent/common.pb.go index 0d4ccf71d..4407bccf4 100644 --- a/agent-manager/agent/common.pb.go +++ b/agent-manager/agent/common.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: common.proto package agent @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -116,23 +117,20 @@ func (ConnectorType) EnumDescriptor() ([]byte, []int) { } type ListRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + PageNumber int32 `protobuf:"varint,1,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + SearchQuery string `protobuf:"bytes,3,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` + SortBy string `protobuf:"bytes,4,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` unknownFields protoimpl.UnknownFields - - PageNumber int32 `protobuf:"varint,1,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` - PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - SearchQuery string `protobuf:"bytes,3,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` - SortBy string `protobuf:"bytes,4,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListRequest) Reset() { *x = ListRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListRequest) String() string { @@ -143,7 +141,7 @@ func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -187,21 +185,18 @@ func (x *ListRequest) GetSortBy() string { } type AuthResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` unknownFields protoimpl.UnknownFields - - Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + sizeCache protoimpl.SizeCache } func (x *AuthResponse) Reset() { *x = AuthResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AuthResponse) String() string { @@ -212,7 +207,7 @@ func (*AuthResponse) ProtoMessage() {} func (x *AuthResponse) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -242,20 +237,17 @@ func (x *AuthResponse) GetKey() string { } type DeleteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + DeletedBy string `protobuf:"bytes,1,opt,name=deleted_by,json=deletedBy,proto3" json:"deleted_by,omitempty"` unknownFields protoimpl.UnknownFields - - DeletedBy string `protobuf:"bytes,1,opt,name=deleted_by,json=deletedBy,proto3" json:"deleted_by,omitempty"` + sizeCache protoimpl.SizeCache } func (x *DeleteRequest) Reset() { *x = DeleteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DeleteRequest) String() string { @@ -266,7 +258,7 @@ func (*DeleteRequest) ProtoMessage() {} func (x *DeleteRequest) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -290,50 +282,45 @@ func (x *DeleteRequest) GetDeletedBy() string { var File_common_proto protoreflect.FileDescriptor -var file_common_proto_rawDesc = []byte{ - 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x75, - 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x65, - 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, - 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, - 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x22, - 0x30, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x22, 0x2e, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x42, - 0x79, 0x2a, 0x2e, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x4f, - 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x46, 0x46, 0x4c, 0x49, - 0x4e, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x02, 0x2a, 0x29, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, - 0x09, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x10, 0x01, 0x42, 0x32, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_common_proto_rawDesc = "" + + "\n" + + "\fcommon.proto\x12\x05agent\"\x87\x01\n" + + "\vListRequest\x12\x1f\n" + + "\vpage_number\x18\x01 \x01(\x05R\n" + + "pageNumber\x12\x1b\n" + + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12!\n" + + "\fsearch_query\x18\x03 \x01(\tR\vsearchQuery\x12\x17\n" + + "\asort_by\x18\x04 \x01(\tR\x06sortBy\"0\n" + + "\fAuthResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\".\n" + + "\rDeleteRequest\x12\x1d\n" + + "\n" + + "deleted_by\x18\x01 \x01(\tR\tdeletedBy*.\n" + + "\x06Status\x12\n" + + "\n" + + "\x06ONLINE\x10\x00\x12\v\n" + + "\aOFFLINE\x10\x01\x12\v\n" + + "\aUNKNOWN\x10\x02*)\n" + + "\rConnectorType\x12\t\n" + + "\x05AGENT\x10\x00\x12\r\n" + + "\tCOLLECTOR\x10\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_common_proto_rawDescOnce sync.Once - file_common_proto_rawDescData = file_common_proto_rawDesc + file_common_proto_rawDescData []byte ) func file_common_proto_rawDescGZIP() []byte { file_common_proto_rawDescOnce.Do(func() { - file_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_proto_rawDescData) + file_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc))) }) return file_common_proto_rawDescData } var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_common_proto_goTypes = []interface{}{ +var file_common_proto_goTypes = []any{ (Status)(0), // 0: agent.Status (ConnectorType)(0), // 1: agent.ConnectorType (*ListRequest)(nil), // 2: agent.ListRequest @@ -353,49 +340,11 @@ func file_common_proto_init() { if File_common_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_common_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)), NumEnums: 2, NumMessages: 3, NumExtensions: 0, @@ -407,7 +356,6 @@ func file_common_proto_init() { MessageInfos: file_common_proto_msgTypes, }.Build() File_common_proto = out.File - file_common_proto_rawDesc = nil file_common_proto_goTypes = nil file_common_proto_depIdxs = nil } diff --git a/agent-manager/agent/ping.pb.go b/agent-manager/agent/ping.pb.go index 21ddaf763..b6f9787b9 100644 --- a/agent-manager/agent/ping.pb.go +++ b/agent-manager/agent/ping.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: ping.proto package agent @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -21,20 +22,17 @@ const ( ) type PingRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Type ConnectorType `protobuf:"varint,1,opt,name=type,proto3,enum=agent.ConnectorType" json:"type,omitempty"` unknownFields protoimpl.UnknownFields - - Type ConnectorType `protobuf:"varint,1,opt,name=type,proto3,enum=agent.ConnectorType" json:"type,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingRequest) Reset() { *x = PingRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_ping_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_ping_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingRequest) String() string { @@ -45,7 +43,7 @@ func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { mi := &file_ping_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -68,20 +66,17 @@ func (x *PingRequest) GetType() ConnectorType { } type PingResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Received string `protobuf:"bytes,1,opt,name=received,proto3" json:"received,omitempty"` unknownFields protoimpl.UnknownFields - - Received string `protobuf:"bytes,1,opt,name=received,proto3" json:"received,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingResponse) Reset() { *x = PingResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_ping_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_ping_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingResponse) String() string { @@ -92,7 +87,7 @@ func (*PingResponse) ProtoMessage() {} func (x *PingResponse) ProtoReflect() protoreflect.Message { mi := &file_ping_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -116,40 +111,31 @@ func (x *PingResponse) GetReceived() string { var File_ping_proto protoreflect.FileDescriptor -var file_ping_proto_rawDesc = []byte{ - 0x0a, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x1a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0x37, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2a, 0x0a, 0x0c, 0x50, 0x69, - 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x32, 0x42, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_ping_proto_rawDesc = "" + + "\n" + + "\n" + + "ping.proto\x12\x05agent\x1a\fcommon.proto\"7\n" + + "\vPingRequest\x12(\n" + + "\x04type\x18\x01 \x01(\x0e2\x14.agent.ConnectorTypeR\x04type\"*\n" + + "\fPingResponse\x12\x1a\n" + + "\breceived\x18\x01 \x01(\tR\breceived2B\n" + + "\vPingService\x123\n" + + "\x04Ping\x12\x12.agent.PingRequest\x1a\x13.agent.PingResponse\"\x00(\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_ping_proto_rawDescOnce sync.Once - file_ping_proto_rawDescData = file_ping_proto_rawDesc + file_ping_proto_rawDescData []byte ) func file_ping_proto_rawDescGZIP() []byte { file_ping_proto_rawDescOnce.Do(func() { - file_ping_proto_rawDescData = protoimpl.X.CompressGZIP(file_ping_proto_rawDescData) + file_ping_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_ping_proto_rawDesc), len(file_ping_proto_rawDesc))) }) return file_ping_proto_rawDescData } var file_ping_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_ping_proto_goTypes = []interface{}{ +var file_ping_proto_goTypes = []any{ (*PingRequest)(nil), // 0: agent.PingRequest (*PingResponse)(nil), // 1: agent.PingResponse (ConnectorType)(0), // 2: agent.ConnectorType @@ -171,37 +157,11 @@ func file_ping_proto_init() { return } file_common_proto_init() - if !protoimpl.UnsafeEnabled { - file_ping_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_ping_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_ping_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_ping_proto_rawDesc), len(file_ping_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, @@ -212,7 +172,6 @@ func file_ping_proto_init() { MessageInfos: file_ping_proto_msgTypes, }.Build() File_ping_proto = out.File - file_ping_proto_rawDesc = nil file_ping_proto_goTypes = nil file_ping_proto_depIdxs = nil } diff --git a/agent-manager/agent/ping_grpc.pb.go b/agent-manager/agent/ping_grpc.pb.go index f283dc80a..f3213500c 100644 --- a/agent-manager/agent/ping_grpc.pb.go +++ b/agent-manager/agent/ping_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: ping.proto package agent diff --git a/agent-manager/go.mod b/agent-manager/go.mod index 08a1bc007..78140e2d6 100644 --- a/agent-manager/go.mod +++ b/agent-manager/go.mod @@ -7,7 +7,7 @@ require ( github.com/gin-gonic/gin v1.11.0 github.com/google/uuid v1.6.0 github.com/utmstack/config-client-go v1.2.7 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.31.1 @@ -18,7 +18,7 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -40,7 +40,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect - github.com/threatwinds/go-sdk v1.1.8 + github.com/threatwinds/go-sdk v1.1.14 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect golang.org/x/arch v0.23.0 // indirect @@ -49,5 +49,5 @@ require ( golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect ) diff --git a/agent-manager/go.sum b/agent-manager/go.sum index 189507d1b..bb5bde9b0 100644 --- a/agent-manager/go.sum +++ b/agent-manager/go.sum @@ -6,14 +6,16 @@ github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uS github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -87,8 +89,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.8 h1:jDd6HH4GZVRswv9ToaVU+xcyzNlKnA7f1lf/e1Xyt3A= -github.com/threatwinds/go-sdk v1.1.8/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= +github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= @@ -97,16 +99,16 @@ github.com/utmstack/config-client-go v1.2.7 h1:JeRdI5JjH1liNzMW3LmyevjuPd67J/yt9 github.com/utmstack/config-client-go v1.2.7/go.mod h1:kM0KoUizM9ZlcQp0qKviGTWn/+anT5Rfjx3zfZk79nM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= @@ -124,10 +126,10 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d h1:xXzuihhT3gL/ntduUZwHECzAn57E8dA6l8SOtYWdD8Q= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/agent-manager/protos/agent.proto b/agent-manager/protos/agent.proto index 3f69e9a2b..c1db23819 100644 --- a/agent-manager/protos/agent.proto +++ b/agent-manager/protos/agent.proto @@ -79,6 +79,7 @@ message UtmCommand { string origin_type = 5; string origin_id = 6; string reason = 7; + string shell = 8; // Shell to execute command: "cmd", "powershell" (Windows), "sh", "bash" (Linux/macOS). Empty = default } message CommandResult { diff --git a/agent-manager/utils/filter.go b/agent-manager/utils/filter.go index c0b403fb0..dbb7f960a 100644 --- a/agent-manager/utils/filter.go +++ b/agent-manager/utils/filter.go @@ -75,17 +75,17 @@ func FilterScope(filters []Filter) func(db *gorm.DB) *gorm.DB { for _, filter := range filters { switch filter.Op { case Is: - db.Where(filter.Field+" = ?", filter.Value) + db = db.Where(filter.Field+" = ?", filter.Value) case IsNot: - db.Where(filter.Field+" <> ?", filter.Value) + db = db.Where(filter.Field+" <> ?", filter.Value) case Contain: - db.Where(filter.Field+" like %?%", filter.Value) + db = db.Where(filter.Field+" LIKE ?", "%"+fmt.Sprintf("%v", filter.Value)+"%") case NotContain: - db.Where(filter.Field+" not like %?%", filter.Value) + db = db.Where(filter.Field+" NOT LIKE ?", "%"+fmt.Sprintf("%v", filter.Value)+"%") case In: - db.Where(filter.Field+" in (?)", filter.Value) + db = db.Where(filter.Field+" IN (?)", filter.Value) case NotIn: - db.Where(filter.Field+" not in (?)", filter.Value) + db = db.Where(filter.Field+" NOT IN (?)", filter.Value) } } return db diff --git a/agent/agent/agent.pb.go b/agent/agent/agent.pb.go index 7c73da984..d5b52edb9 100644 --- a/agent/agent/agent.pb.go +++ b/agent/agent/agent.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: agent.proto package agent @@ -12,6 +12,7 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -77,30 +78,27 @@ func (AgentCommandStatus) EnumDescriptor() ([]byte, []int) { } type AgentRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` - Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` - Platform string `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"` - Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` - RegisterBy string `protobuf:"bytes,6,opt,name=register_by,json=registerBy,proto3" json:"register_by,omitempty"` - Mac string `protobuf:"bytes,7,opt,name=mac,proto3" json:"mac,omitempty"` - OsMajorVersion string `protobuf:"bytes,8,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` - OsMinorVersion string `protobuf:"bytes,9,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` - Aliases string `protobuf:"bytes,10,opt,name=aliases,proto3" json:"aliases,omitempty"` - Addresses string `protobuf:"bytes,11,opt,name=addresses,proto3" json:"addresses,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + Platform string `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"` + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + RegisterBy string `protobuf:"bytes,6,opt,name=register_by,json=registerBy,proto3" json:"register_by,omitempty"` + Mac string `protobuf:"bytes,7,opt,name=mac,proto3" json:"mac,omitempty"` + OsMajorVersion string `protobuf:"bytes,8,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` + OsMinorVersion string `protobuf:"bytes,9,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` + Aliases string `protobuf:"bytes,10,opt,name=aliases,proto3" json:"aliases,omitempty"` + Addresses string `protobuf:"bytes,11,opt,name=addresses,proto3" json:"addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentRequest) Reset() { *x = AgentRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentRequest) String() string { @@ -111,7 +109,7 @@ func (*AgentRequest) ProtoMessage() {} func (x *AgentRequest) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -204,21 +202,18 @@ func (x *AgentRequest) GetAddresses() string { } type ListAgentsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Rows []*Agent `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` unknownFields protoimpl.UnknownFields - - Rows []*Agent `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` - Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListAgentsResponse) Reset() { *x = ListAgentsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListAgentsResponse) String() string { @@ -229,7 +224,7 @@ func (*ListAgentsResponse) ProtoMessage() {} func (x *ListAgentsResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -259,33 +254,30 @@ func (x *ListAgentsResponse) GetTotal() int32 { } type Agent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` - Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` - Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=agent.Status" json:"status,omitempty"` - Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` - Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` - AgentKey string `protobuf:"bytes,7,opt,name=agent_key,json=agentKey,proto3" json:"agent_key,omitempty"` - Id uint32 `protobuf:"varint,8,opt,name=id,proto3" json:"id,omitempty"` - LastSeen string `protobuf:"bytes,9,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` - Mac string `protobuf:"bytes,10,opt,name=mac,proto3" json:"mac,omitempty"` - OsMajorVersion string `protobuf:"bytes,11,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` - OsMinorVersion string `protobuf:"bytes,12,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` - Aliases string `protobuf:"bytes,13,opt,name=aliases,proto3" json:"aliases,omitempty"` - Addresses string `protobuf:"bytes,14,opt,name=addresses,proto3" json:"addresses,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=agent.Status" json:"status,omitempty"` + Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` + Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` + AgentKey string `protobuf:"bytes,7,opt,name=agent_key,json=agentKey,proto3" json:"agent_key,omitempty"` + Id uint32 `protobuf:"varint,8,opt,name=id,proto3" json:"id,omitempty"` + LastSeen string `protobuf:"bytes,9,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + Mac string `protobuf:"bytes,10,opt,name=mac,proto3" json:"mac,omitempty"` + OsMajorVersion string `protobuf:"bytes,11,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` + OsMinorVersion string `protobuf:"bytes,12,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` + Aliases string `protobuf:"bytes,13,opt,name=aliases,proto3" json:"aliases,omitempty"` + Addresses string `protobuf:"bytes,14,opt,name=addresses,proto3" json:"addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Agent) Reset() { *x = Agent{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Agent) String() string { @@ -296,7 +288,7 @@ func (*Agent) ProtoMessage() {} func (x *Agent) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -410,24 +402,21 @@ func (x *Agent) GetAddresses() string { } type BidirectionalStream struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to StreamMessage: + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to StreamMessage: // // *BidirectionalStream_Command // *BidirectionalStream_Result StreamMessage isBidirectionalStream_StreamMessage `protobuf_oneof:"stream_message"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *BidirectionalStream) Reset() { *x = BidirectionalStream{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BidirectionalStream) String() string { @@ -438,7 +427,7 @@ func (*BidirectionalStream) ProtoMessage() {} func (x *BidirectionalStream) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -453,23 +442,27 @@ func (*BidirectionalStream) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{3} } -func (m *BidirectionalStream) GetStreamMessage() isBidirectionalStream_StreamMessage { - if m != nil { - return m.StreamMessage +func (x *BidirectionalStream) GetStreamMessage() isBidirectionalStream_StreamMessage { + if x != nil { + return x.StreamMessage } return nil } func (x *BidirectionalStream) GetCommand() *UtmCommand { - if x, ok := x.GetStreamMessage().(*BidirectionalStream_Command); ok { - return x.Command + if x != nil { + if x, ok := x.StreamMessage.(*BidirectionalStream_Command); ok { + return x.Command + } } return nil } func (x *BidirectionalStream) GetResult() *CommandResult { - if x, ok := x.GetStreamMessage().(*BidirectionalStream_Result); ok { - return x.Result + if x != nil { + if x, ok := x.StreamMessage.(*BidirectionalStream_Result); ok { + return x.Result + } } return nil } @@ -491,26 +484,24 @@ func (*BidirectionalStream_Command) isBidirectionalStream_StreamMessage() {} func (*BidirectionalStream_Result) isBidirectionalStream_StreamMessage() {} type UtmCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` + ExecutedBy string `protobuf:"bytes,3,opt,name=executed_by,json=executedBy,proto3" json:"executed_by,omitempty"` + CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` + OriginType string `protobuf:"bytes,5,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` + OriginId string `protobuf:"bytes,6,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` + Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + Shell string `protobuf:"bytes,8,opt,name=shell,proto3" json:"shell,omitempty"` // Shell to execute command: "cmd", "powershell" (Windows), "sh", "bash" (Linux/macOS). Empty = default unknownFields protoimpl.UnknownFields - - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` - ExecutedBy string `protobuf:"bytes,3,opt,name=executed_by,json=executedBy,proto3" json:"executed_by,omitempty"` - CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` - OriginType string `protobuf:"bytes,5,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` - OriginId string `protobuf:"bytes,6,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` - Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + sizeCache protoimpl.SizeCache } func (x *UtmCommand) Reset() { *x = UtmCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UtmCommand) String() string { @@ -521,7 +512,7 @@ func (*UtmCommand) ProtoMessage() {} func (x *UtmCommand) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -585,24 +576,28 @@ func (x *UtmCommand) GetReason() string { return "" } +func (x *UtmCommand) GetShell() string { + if x != nil { + return x.Shell + } + return "" +} + type CommandResult struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` + ExecutedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=executed_at,json=executedAt,proto3" json:"executed_at,omitempty"` + CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` unknownFields protoimpl.UnknownFields - - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` - ExecutedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=executed_at,json=executedAt,proto3" json:"executed_at,omitempty"` - CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *CommandResult) Reset() { *x = CommandResult{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CommandResult) String() string { @@ -613,7 +608,7 @@ func (*CommandResult) ProtoMessage() {} func (x *CommandResult) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -657,21 +652,18 @@ func (x *CommandResult) GetCmdId() string { } type ListAgentsCommandsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Rows []*AgentCommand `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` unknownFields protoimpl.UnknownFields - - Rows []*AgentCommand `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` - Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListAgentsCommandsResponse) Reset() { *x = ListAgentsCommandsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListAgentsCommandsResponse) String() string { @@ -682,7 +674,7 @@ func (*ListAgentsCommandsResponse) ProtoMessage() {} func (x *ListAgentsCommandsResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -712,10 +704,7 @@ func (x *ListAgentsCommandsResponse) GetTotal() int32 { } type AgentCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` CreatedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` AgentId uint32 `protobuf:"varint,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` @@ -727,15 +716,15 @@ type AgentCommand struct { Reason string `protobuf:"bytes,9,opt,name=reason,proto3" json:"reason,omitempty"` OriginType string `protobuf:"bytes,10,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` OriginId string `protobuf:"bytes,11,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentCommand) Reset() { *x = AgentCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentCommand) String() string { @@ -746,7 +735,7 @@ func (*AgentCommand) ProtoMessage() {} func (x *AgentCommand) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -840,182 +829,116 @@ func (x *AgentCommand) GetOriginId() string { var File_agent_proto protoreflect.FileDescriptor -var file_agent_proto_rawDesc = []byte{ - 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x02, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x42, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, - 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, - 0x73, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, - 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x4c, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x04, 0x72, - 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x22, 0x88, 0x03, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1a, 0x0a, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, - 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, - 0x61, 0x63, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, - 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, - 0x6f, 0x73, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, - 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, - 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x86, - 0x01, 0x0a, 0x13, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x55, 0x74, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2e, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x10, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x0a, 0x55, 0x74, 0x6d, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x15, 0x0a, 0x06, - 0x63, 0x6d, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, - 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x96, 0x01, 0x0a, 0x0d, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x3b, - 0x0a, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x63, - 0x6d, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, 0x64, - 0x49, 0x64, 0x22, 0x5b, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x27, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, - 0xa1, 0x03, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x40, 0x0a, 0x0e, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0d, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, - 0x64, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x63, 0x6d, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, - 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x69, 0x67, 0x69, - 0x6e, 0x49, 0x64, 0x2a, 0x57, 0x0a, 0x12, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x4f, 0x54, - 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x51, - 0x55, 0x45, 0x55, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, - 0x47, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x10, - 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x32, 0x9c, 0x03, 0x0a, - 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, - 0x0d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0b, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x3d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x4b, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, - 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x1a, 0x1a, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, - 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4c, 0x0a, - 0x11, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x73, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x4f, 0x0a, 0x0c, 0x50, - 0x61, 0x6e, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x50, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x11, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x74, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x1a, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x32, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_agent_proto_rawDesc = "" + + "\n" + + "\vagent.proto\x12\x05agent\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\fcommon.proto\"\xbf\x02\n" + + "\fAgentRequest\x12\x0e\n" + + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x0e\n" + + "\x02os\x18\x03 \x01(\tR\x02os\x12\x1a\n" + + "\bplatform\x18\x04 \x01(\tR\bplatform\x12\x18\n" + + "\aversion\x18\x05 \x01(\tR\aversion\x12\x1f\n" + + "\vregister_by\x18\x06 \x01(\tR\n" + + "registerBy\x12\x10\n" + + "\x03mac\x18\a \x01(\tR\x03mac\x12(\n" + + "\x10os_major_version\x18\b \x01(\tR\x0eosMajorVersion\x12(\n" + + "\x10os_minor_version\x18\t \x01(\tR\x0eosMinorVersion\x12\x18\n" + + "\aaliases\x18\n" + + " \x01(\tR\aaliases\x12\x1c\n" + + "\taddresses\x18\v \x01(\tR\taddresses\"L\n" + + "\x12ListAgentsResponse\x12 \n" + + "\x04rows\x18\x01 \x03(\v2\f.agent.AgentR\x04rows\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\x88\x03\n" + + "\x05Agent\x12\x0e\n" + + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x0e\n" + + "\x02os\x18\x03 \x01(\tR\x02os\x12%\n" + + "\x06status\x18\x04 \x01(\x0e2\r.agent.StatusR\x06status\x12\x1a\n" + + "\bplatform\x18\x05 \x01(\tR\bplatform\x12\x18\n" + + "\aversion\x18\x06 \x01(\tR\aversion\x12\x1b\n" + + "\tagent_key\x18\a \x01(\tR\bagentKey\x12\x0e\n" + + "\x02id\x18\b \x01(\rR\x02id\x12\x1b\n" + + "\tlast_seen\x18\t \x01(\tR\blastSeen\x12\x10\n" + + "\x03mac\x18\n" + + " \x01(\tR\x03mac\x12(\n" + + "\x10os_major_version\x18\v \x01(\tR\x0eosMajorVersion\x12(\n" + + "\x10os_minor_version\x18\f \x01(\tR\x0eosMinorVersion\x12\x18\n" + + "\aaliases\x18\r \x01(\tR\aaliases\x12\x1c\n" + + "\taddresses\x18\x0e \x01(\tR\taddresses\"\x86\x01\n" + + "\x13BidirectionalStream\x12-\n" + + "\acommand\x18\x01 \x01(\v2\x11.agent.UtmCommandH\x00R\acommand\x12.\n" + + "\x06result\x18\x02 \x01(\v2\x14.agent.CommandResultH\x00R\x06resultB\x10\n" + + "\x0estream_message\"\xe5\x01\n" + + "\n" + + "UtmCommand\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x18\n" + + "\acommand\x18\x02 \x01(\tR\acommand\x12\x1f\n" + + "\vexecuted_by\x18\x03 \x01(\tR\n" + + "executedBy\x12\x15\n" + + "\x06cmd_id\x18\x04 \x01(\tR\x05cmdId\x12\x1f\n" + + "\vorigin_type\x18\x05 \x01(\tR\n" + + "originType\x12\x1b\n" + + "\torigin_id\x18\x06 \x01(\tR\boriginId\x12\x16\n" + + "\x06reason\x18\a \x01(\tR\x06reason\x12\x14\n" + + "\x05shell\x18\b \x01(\tR\x05shell\"\x96\x01\n" + + "\rCommandResult\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x16\n" + + "\x06result\x18\x02 \x01(\tR\x06result\x12;\n" + + "\vexecuted_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "executedAt\x12\x15\n" + + "\x06cmd_id\x18\x04 \x01(\tR\x05cmdId\"[\n" + + "\x1aListAgentsCommandsResponse\x12'\n" + + "\x04rows\x18\x01 \x03(\v2\x13.agent.AgentCommandR\x04rows\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\xa1\x03\n" + + "\fAgentCommand\x129\n" + + "\n" + + "created_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x19\n" + + "\bagent_id\x18\x03 \x01(\rR\aagentId\x12\x18\n" + + "\acommand\x18\x04 \x01(\tR\acommand\x12@\n" + + "\x0ecommand_status\x18\x05 \x01(\x0e2\x19.agent.AgentCommandStatusR\rcommandStatus\x12\x16\n" + + "\x06result\x18\x06 \x01(\tR\x06result\x12\x1f\n" + + "\vexecuted_by\x18\a \x01(\tR\n" + + "executedBy\x12\x15\n" + + "\x06cmd_id\x18\b \x01(\tR\x05cmdId\x12\x16\n" + + "\x06reason\x18\t \x01(\tR\x06reason\x12\x1f\n" + + "\vorigin_type\x18\n" + + " \x01(\tR\n" + + "originType\x12\x1b\n" + + "\torigin_id\x18\v \x01(\tR\boriginId*W\n" + + "\x12AgentCommandStatus\x12\x10\n" + + "\fNOT_EXECUTED\x10\x00\x12\t\n" + + "\x05QUEUE\x10\x01\x12\v\n" + + "\aPENDING\x10\x02\x12\f\n" + + "\bEXECUTED\x10\x03\x12\t\n" + + "\x05ERROR\x10\x042\x9c\x03\n" + + "\fAgentService\x12;\n" + + "\rRegisterAgent\x12\x13.agent.AgentRequest\x1a\x13.agent.AuthResponse\"\x00\x129\n" + + "\vUpdateAgent\x12\x13.agent.AgentRequest\x1a\x13.agent.AuthResponse\"\x00\x12:\n" + + "\vDeleteAgent\x12\x14.agent.DeleteRequest\x1a\x13.agent.AuthResponse\"\x00\x12=\n" + + "\n" + + "ListAgents\x12\x12.agent.ListRequest\x1a\x19.agent.ListAgentsResponse\"\x00\x12K\n" + + "\vAgentStream\x12\x1a.agent.BidirectionalStream\x1a\x1a.agent.BidirectionalStream\"\x00(\x010\x01\x12L\n" + + "\x11ListAgentCommands\x12\x12.agent.ListRequest\x1a!.agent.ListAgentsCommandsResponse\"\x002O\n" + + "\fPanelService\x12?\n" + + "\x0eProcessCommand\x12\x11.agent.UtmCommand\x1a\x14.agent.CommandResult\"\x00(\x010\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_agent_proto_rawDescOnce sync.Once - file_agent_proto_rawDescData = file_agent_proto_rawDesc + file_agent_proto_rawDescData []byte ) func file_agent_proto_rawDescGZIP() []byte { file_agent_proto_rawDescOnce.Do(func() { - file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_proto_rawDescData) + file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc))) }) return file_agent_proto_rawDescData } var file_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 8) -var file_agent_proto_goTypes = []interface{}{ +var file_agent_proto_goTypes = []any{ (AgentCommandStatus)(0), // 0: agent.AgentCommandStatus (*AgentRequest)(nil), // 1: agent.AgentRequest (*ListAgentsResponse)(nil), // 2: agent.ListAgentsResponse @@ -1068,105 +991,7 @@ func file_agent_proto_init() { return } file_common_proto_init() - if !protoimpl.UnsafeEnabled { - file_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAgentsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Agent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BidirectionalStream); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UtmCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CommandResult); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAgentsCommandsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_agent_proto_msgTypes[3].OneofWrappers = []interface{}{ + file_agent_proto_msgTypes[3].OneofWrappers = []any{ (*BidirectionalStream_Command)(nil), (*BidirectionalStream_Result)(nil), } @@ -1174,7 +999,7 @@ func file_agent_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_agent_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc)), NumEnums: 1, NumMessages: 8, NumExtensions: 0, @@ -1186,7 +1011,6 @@ func file_agent_proto_init() { MessageInfos: file_agent_proto_msgTypes, }.Build() File_agent_proto = out.File - file_agent_proto_rawDesc = nil file_agent_proto_goTypes = nil file_agent_proto_depIdxs = nil } diff --git a/agent/agent/agent_grpc.pb.go b/agent/agent/agent_grpc.pb.go index f75ba57b2..400673e17 100644 --- a/agent/agent/agent_grpc.pb.go +++ b/agent/agent/agent_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: agent.proto package agent diff --git a/agent/agent/common.pb.go b/agent/agent/common.pb.go index 0d4ccf71d..4407bccf4 100644 --- a/agent/agent/common.pb.go +++ b/agent/agent/common.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: common.proto package agent @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -116,23 +117,20 @@ func (ConnectorType) EnumDescriptor() ([]byte, []int) { } type ListRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + PageNumber int32 `protobuf:"varint,1,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + SearchQuery string `protobuf:"bytes,3,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` + SortBy string `protobuf:"bytes,4,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` unknownFields protoimpl.UnknownFields - - PageNumber int32 `protobuf:"varint,1,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` - PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - SearchQuery string `protobuf:"bytes,3,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` - SortBy string `protobuf:"bytes,4,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListRequest) Reset() { *x = ListRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListRequest) String() string { @@ -143,7 +141,7 @@ func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -187,21 +185,18 @@ func (x *ListRequest) GetSortBy() string { } type AuthResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` unknownFields protoimpl.UnknownFields - - Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + sizeCache protoimpl.SizeCache } func (x *AuthResponse) Reset() { *x = AuthResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AuthResponse) String() string { @@ -212,7 +207,7 @@ func (*AuthResponse) ProtoMessage() {} func (x *AuthResponse) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -242,20 +237,17 @@ func (x *AuthResponse) GetKey() string { } type DeleteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + DeletedBy string `protobuf:"bytes,1,opt,name=deleted_by,json=deletedBy,proto3" json:"deleted_by,omitempty"` unknownFields protoimpl.UnknownFields - - DeletedBy string `protobuf:"bytes,1,opt,name=deleted_by,json=deletedBy,proto3" json:"deleted_by,omitempty"` + sizeCache protoimpl.SizeCache } func (x *DeleteRequest) Reset() { *x = DeleteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DeleteRequest) String() string { @@ -266,7 +258,7 @@ func (*DeleteRequest) ProtoMessage() {} func (x *DeleteRequest) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -290,50 +282,45 @@ func (x *DeleteRequest) GetDeletedBy() string { var File_common_proto protoreflect.FileDescriptor -var file_common_proto_rawDesc = []byte{ - 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x75, - 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x65, - 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, - 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, - 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x22, - 0x30, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x22, 0x2e, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x42, - 0x79, 0x2a, 0x2e, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x4f, - 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x46, 0x46, 0x4c, 0x49, - 0x4e, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x02, 0x2a, 0x29, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, - 0x09, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x10, 0x01, 0x42, 0x32, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_common_proto_rawDesc = "" + + "\n" + + "\fcommon.proto\x12\x05agent\"\x87\x01\n" + + "\vListRequest\x12\x1f\n" + + "\vpage_number\x18\x01 \x01(\x05R\n" + + "pageNumber\x12\x1b\n" + + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12!\n" + + "\fsearch_query\x18\x03 \x01(\tR\vsearchQuery\x12\x17\n" + + "\asort_by\x18\x04 \x01(\tR\x06sortBy\"0\n" + + "\fAuthResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\".\n" + + "\rDeleteRequest\x12\x1d\n" + + "\n" + + "deleted_by\x18\x01 \x01(\tR\tdeletedBy*.\n" + + "\x06Status\x12\n" + + "\n" + + "\x06ONLINE\x10\x00\x12\v\n" + + "\aOFFLINE\x10\x01\x12\v\n" + + "\aUNKNOWN\x10\x02*)\n" + + "\rConnectorType\x12\t\n" + + "\x05AGENT\x10\x00\x12\r\n" + + "\tCOLLECTOR\x10\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_common_proto_rawDescOnce sync.Once - file_common_proto_rawDescData = file_common_proto_rawDesc + file_common_proto_rawDescData []byte ) func file_common_proto_rawDescGZIP() []byte { file_common_proto_rawDescOnce.Do(func() { - file_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_proto_rawDescData) + file_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc))) }) return file_common_proto_rawDescData } var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_common_proto_goTypes = []interface{}{ +var file_common_proto_goTypes = []any{ (Status)(0), // 0: agent.Status (ConnectorType)(0), // 1: agent.ConnectorType (*ListRequest)(nil), // 2: agent.ListRequest @@ -353,49 +340,11 @@ func file_common_proto_init() { if File_common_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_common_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)), NumEnums: 2, NumMessages: 3, NumExtensions: 0, @@ -407,7 +356,6 @@ func file_common_proto_init() { MessageInfos: file_common_proto_msgTypes, }.Build() File_common_proto = out.File - file_common_proto_rawDesc = nil file_common_proto_goTypes = nil file_common_proto_depIdxs = nil } diff --git a/agent/agent/conn.go b/agent/agent/conn.go new file mode 100644 index 000000000..ded43381c --- /dev/null +++ b/agent/agent/conn.go @@ -0,0 +1,90 @@ +package agent + +import ( + "crypto/tls" + "fmt" + "sync" + "time" + + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials" +) + +const ( + maxMessageSize = 1024 * 1024 * 1024 + maxConnectionAttempts = 3 + initialReconnectDelay = 10 * time.Second + maxReconnectDelay = 60 * time.Second +) + +type connEntry struct { + mu sync.Mutex + conn *grpc.ClientConn +} + +func (e *connEntry) getOrCreate(cnf *config.Config, port string, name string) (*grpc.ClientConn, error) { + e.mu.Lock() + defer e.mu.Unlock() + + if e.conn != nil { + state := e.conn.GetState() + if state != connectivity.Shutdown && state != connectivity.TransientFailure { + return e.conn, nil + } + e.conn.Close() + } + + conn, err := connectToServer(cnf.Server, port, cnf.SkipCertValidation) + if err != nil { + return nil, fmt.Errorf("error connecting to %s: %v", name, err) + } + e.conn = conn + return e.conn, nil +} + +var ( + agentManagerEntry connEntry + correlationEntry connEntry +) + +func GetAgentManagerConnection(cnf *config.Config) (*grpc.ClientConn, error) { + return agentManagerEntry.getOrCreate(cnf, config.AgentManagerPort, "Agent Manager") +} + +func GetCorrelationConnection(cnf *config.Config) (*grpc.ClientConn, error) { + return correlationEntry.getOrCreate(cnf, config.LogAuthProxyPort, "Correlation") +} + +func connectToServer(addrs, port string, skip bool) (*grpc.ClientConn, error) { + connectionAttemps := 0 + reconnectDelay := initialReconnectDelay + + serverAddress := addrs + ":" + port + var conn *grpc.ClientConn + var err error + + for { + if connectionAttemps >= maxConnectionAttempts { + return nil, fmt.Errorf("failed to connect to Server: %v", err) + } + + conn, err = grpc.NewClient( + serverAddress, + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMessageSize)), + grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: skip}))) + if err != nil { + connectionAttemps++ + utils.Logger.ErrorF("error connecting to Server, trying again in %.0f seconds", reconnectDelay.Seconds()) + time.Sleep(reconnectDelay) + reconnectDelay = utils.IncrementReconnectDelay(reconnectDelay, maxReconnectDelay) + continue + } + + break + } + + return conn, nil +} diff --git a/agent/agent/delete.go b/agent/agent/delete.go index d700b30c0..c1c359aca 100644 --- a/agent/agent/delete.go +++ b/agent/agent/delete.go @@ -7,13 +7,12 @@ import ( "strconv" "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/conn" "github.com/utmstack/UTMStack/agent/utils" "google.golang.org/grpc/metadata" ) func DeleteAgent(cnf *config.Config) error { - connection, err := conn.GetAgentManagerConnection(cnf) + connection, err := GetAgentManagerConnection(cnf) if err != nil { return fmt.Errorf("error connecting to Agent Manager: %v", err) } diff --git a/agent/agent/grpc_errors.go b/agent/agent/grpc_errors.go new file mode 100644 index 000000000..5a71dbf03 --- /dev/null +++ b/agent/agent/grpc_errors.go @@ -0,0 +1,78 @@ +package agent + +import ( + "strings" + "time" + + "github.com/utmstack/UTMStack/agent/utils" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// StreamAction indicates what the caller should do after handling a gRPC error. +type StreamAction int + +const ( + // ActionContinue means retry the operation (non-fatal error). + ActionContinue StreamAction = iota + // ActionReconnect means break the inner loop and reconnect the stream. + ActionReconnect +) + +// HandleGRPCStreamError processes a gRPC stream error and returns the appropriate action. +// It handles EOF, Unavailable, and Canceled errors with deduplication of log messages. +// The errorLogged pointer tracks whether an error has already been logged to avoid spam. +func HandleGRPCStreamError(err error, msg string, errorLogged *bool) StreamAction { + // EOF means the stream was closed by the server - reconnect + if strings.Contains(err.Error(), "EOF") { + utils.Logger.LogF(100, "%s: %v", msg, err) + time.Sleep(timeToSleep) + return ActionReconnect + } + + st, ok := status.FromError(err) + isTransient := ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) + + // Log error only once to avoid spam + logError(err, msg, errorLogged) + time.Sleep(timeToSleep) + + if isTransient { + // Transient errors (Unavailable, Canceled) require reconnection + return ActionReconnect + } + + // Other errors - retry the operation + return ActionContinue +} + +// logError logs an error message with deduplication. +// After the first error, subsequent errors are logged at debug level. +func logError(err error, msg string, errorLogged *bool) { + if !*errorLogged { + utils.Logger.ErrorF("%s: %v", msg, err) + *errorLogged = true + } else { + utils.Logger.LogF(100, "%s: %v", msg, err) + } +} + +// LogConnectionError logs a connection error with deduplication. +func LogConnectionError(err error, target string, errorLogged *bool) { + if !*errorLogged { + utils.Logger.ErrorF("error connecting to %s: %v", target, err) + *errorLogged = true + } else { + utils.Logger.LogF(100, "error connecting to %s: %v", target, err) + } +} + +// LogStreamError logs a stream creation error with deduplication. +func LogStreamError(err error, streamName string, errorLogged *bool) { + if !*errorLogged { + utils.Logger.ErrorF("failed to start %s: %v", streamName, err) + *errorLogged = true + } else { + utils.Logger.LogF(100, "failed to start %s: %v", streamName, err) + } +} diff --git a/agent/agent/incident_response.go b/agent/agent/incident_response.go index 7a5d9e8f3..a7b87eec2 100644 --- a/agent/agent/incident_response.go +++ b/agent/agent/incident_response.go @@ -2,30 +2,25 @@ package agent import ( "context" + "fmt" "runtime" "strconv" - "strings" "time" "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/conn" "github.com/utmstack/UTMStack/agent/utils" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "github.com/utmstack/UTMStack/shared/fs" "google.golang.org/protobuf/types/known/timestamppb" ) func IncidentResponseStream(cnf *config.Config, ctx context.Context) { - path := utils.GetMyPath() - var connErrMsgWritten, errorLogged bool + path := fs.GetExecutablePath() + var connErrLogged, streamErrLogged bool for { - connection, err := conn.GetAgentManagerConnection(cnf) + connection, err := GetAgentManagerConnection(cnf) if err != nil { - if !connErrMsgWritten { - utils.Logger.ErrorF("error connecting to Agent Manager: %v", err) - connErrMsgWritten = true - } + LogConnectionError(err, "Agent Manager", &connErrLogged) time.Sleep(timeToSleep) continue } @@ -33,113 +28,80 @@ func IncidentResponseStream(cnf *config.Config, ctx context.Context) { client := NewAgentServiceClient(connection) stream, err := client.AgentStream(ctx) if err != nil { - if !connErrMsgWritten { - utils.Logger.ErrorF("failed to start AgentStream: %v", err) - connErrMsgWritten = true - } else { - utils.Logger.LogF(100, "failed to start AgentStream: %v", err) - } + LogStreamError(err, "AgentStream", &connErrLogged) time.Sleep(timeToSleep) continue } - connErrMsgWritten = false + connErrLogged = false + recvLoop: for { in, err := stream.Recv() if err != nil { - if strings.Contains(err.Error(), "EOF") { - utils.Logger.LogF(100, "error receiving command from server: %v", err) - time.Sleep(timeToSleep) - break - } - st, ok := status.FromError(err) - if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { - if !errorLogged { - utils.Logger.ErrorF("error receiving command from server: %v", err) - errorLogged = true - } else { - utils.Logger.LogF(100, "error receiving command from server: %v", err) - } - time.Sleep(timeToSleep) - break - } else { - if !errorLogged { - utils.Logger.ErrorF("error receiving command from server: %v", err) - errorLogged = true - } else { - utils.Logger.LogF(100, "error receiving command from server: %v", err) - } - time.Sleep(timeToSleep) - continue + action := HandleGRPCStreamError(err, "error receiving command from server", &streamErrLogged) + if action == ActionReconnect { + break recvLoop } + continue } switch msg := in.StreamMessage.(type) { case *BidirectionalStream_Command: - err = commandProcessor(path, stream, cnf, []string{msg.Command.Command, in.GetCommand().CmdId}) + err = commandProcessor(path, stream, cnf, msg.Command.Command, msg.Command.CmdId, msg.Command.Shell) if err != nil { - if strings.Contains(err.Error(), "EOF") { - utils.Logger.LogF(100, "error sending result to server: %v", err) - time.Sleep(timeToSleep) - break - } - st, ok := status.FromError(err) - if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { - if !errorLogged { - utils.Logger.ErrorF("error sending result to server: %v", err) - errorLogged = true - } else { - utils.Logger.LogF(100, "error sending result to server: %v", err) - } - time.Sleep(timeToSleep) - break - } else { - if !errorLogged { - utils.Logger.ErrorF("error sending result to server: %v", err) - errorLogged = true - } else { - utils.Logger.LogF(100, "error sending result to server: %v", err) - } - time.Sleep(timeToSleep) - continue + action := HandleGRPCStreamError(err, "error sending result to server", &streamErrLogged) + if action == ActionReconnect { + break recvLoop } + continue } } - errorLogged = false + streamErrLogged = false } } } -func commandProcessor(path string, stream AgentService_AgentStreamClient, cnf *config.Config, commandPair []string) error { +func commandProcessor(path string, stream AgentService_AgentStreamClient, cnf *config.Config, command, cmdId, shell string) error { var result string var errB bool - utils.Logger.LogF(100, "Received command: %s", commandPair[0]) + utils.Logger.LogF(100, "Received command: %s (shell: %s)", command, shell) switch runtime.GOOS { case "windows": - result, errB = utils.ExecuteWithResult("cmd.exe", path, "/C", commandPair[0]) + if shell == "powershell" { + result, errB = utils.ExecuteWithResult("powershell.exe", path, "-Command", command) + } else { + // Default to cmd.exe (also handles shell == "" or shell == "cmd") + result, errB = utils.ExecuteWithResult("cmd.exe", path, "/C", command) + } case "linux", "darwin": - result, errB = utils.ExecuteWithResult("sh", path, "-c", commandPair[0]) + if shell == "bash" { + result, errB = utils.ExecuteWithResult("bash", path, "-c", command) + } else { + // Default to sh (also handles shell == "" or shell == "sh") + result, errB = utils.ExecuteWithResult("sh", path, "-c", command) + } default: - utils.Logger.Fatal("unsupported operating system: %s", runtime.GOOS) + utils.Logger.ErrorF("unsupported operating system: %s", runtime.GOOS) + return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) } if errB { - utils.Logger.ErrorF("error executing command %s: %s", commandPair[0], result) + utils.Logger.ErrorF("error executing command %s: %s", command, result) } else { - utils.Logger.LogF(100, "Result when executing the command %s: %s", commandPair[0], result) + utils.Logger.LogF(100, "Result when executing the command %s: %s", command, result) } if err := stream.Send(&BidirectionalStream{ StreamMessage: &BidirectionalStream_Result{ - Result: &CommandResult{Result: result, AgentId: strconv.Itoa(int(cnf.AgentID)), ExecutedAt: timestamppb.Now(), CmdId: commandPair[1]}, + Result: &CommandResult{Result: result, AgentId: strconv.Itoa(int(cnf.AgentID)), ExecutedAt: timestamppb.Now(), CmdId: cmdId}, }, }); err != nil { return err - } else { - utils.Logger.LogF(100, "Result sent to server successfully!!!") } + + utils.Logger.LogF(100, "Result sent to server successfully") return nil } diff --git a/agent/logservice/processor.go b/agent/agent/logprocessor.go similarity index 58% rename from agent/logservice/processor.go rename to agent/agent/logprocessor.go index 537e8f922..b67fff502 100644 --- a/agent/logservice/processor.go +++ b/agent/agent/logprocessor.go @@ -1,9 +1,8 @@ -package logservice +package agent import ( "context" "errors" - "os" "strconv" "strings" "sync" @@ -12,14 +11,11 @@ import ( "github.com/google/uuid" "github.com/threatwinds/go-sdk/plugins" - "github.com/utmstack/UTMStack/agent/agent" "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/conn" "github.com/utmstack/UTMStack/agent/database" "github.com/utmstack/UTMStack/agent/models" "github.com/utmstack/UTMStack/agent/utils" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "github.com/utmstack/UTMStack/shared/fs" ) type LogProcessor struct { @@ -30,31 +26,48 @@ type LogProcessor struct { } var ( - processor LogProcessor - processorOnce sync.Once - LogQueue = make(chan *plugins.Log) - timeToSleep = 10 * time.Second - timeCLeanLogs = 10 * time.Minute + processor LogProcessor + processorOnce sync.Once + processorInitErr error + LogQueue = make(chan *plugins.Log, 10000) + timeCLeanLogs = 10 * time.Minute + + // ErrAgentUninstalled is returned when the agent uninstalls itself due to invalid key + ErrAgentUninstalled = errors.New("agent uninstalled due to invalid key") ) -func GetLogProcessor() LogProcessor { +func GetLogProcessor() (*LogProcessor, error) { processorOnce.Do(func() { + db, err := database.GetDB() + if err != nil { + processorInitErr = err + return + } processor = LogProcessor{ - db: database.GetDB(), + db: db, connErrWritten: false, ackErrWritten: false, sendErrWritten: false, } }) - return processor + if processorInitErr != nil { + return nil, processorInitErr + } + return &processor, nil } func (l *LogProcessor) ProcessLogs(cnf *config.Config, ctx context.Context) { go l.CleanCountedLogs() for { - ctxEof, cancelEof := context.WithCancel(context.Background()) - connection, err := conn.GetCorrelationConnection(cnf) + select { + case <-ctx.Done(): + utils.Logger.Info("ProcessLogs stopping due to context cancellation") + return + default: + } + + connection, err := GetCorrelationConnection(cnf) if err != nil { if !l.connErrWritten { utils.Logger.ErrorF("error connecting to Correlation: %v", err) @@ -65,9 +78,23 @@ func (l *LogProcessor) ProcessLogs(cnf *config.Config, ctx context.Context) { } client := plugins.NewIntegrationClient(connection) - plClient := createClient(client, ctx) + plClient, err := createClient(client, ctx) + if err != nil { + if errors.Is(err, ErrAgentUninstalled) { + utils.Logger.Info("Agent uninstalled, stopping log processor") + return + } + if errors.Is(err, context.Canceled) { + utils.Logger.Info("ProcessLogs stopping due to context cancellation") + return + } + utils.Logger.ErrorF("error creating client: %v", err) + continue + } l.connErrWritten = false + // Create context only after successful client creation to avoid leaks + ctxEof, cancelEof := context.WithCancel(context.Background()) go l.handleAcknowledgements(plClient, ctxEof, cancelEof) l.processLogs(plClient, ctxEof, cancelEof) } @@ -81,38 +108,20 @@ func (l *LogProcessor) handleAcknowledgements(plClient plugins.Integration_Proce default: ack, err := plClient.Recv() if err != nil { - if strings.Contains(err.Error(), "EOF") { - time.Sleep(timeToSleep) + action := HandleGRPCStreamError(err, "failed to receive ack", &l.ackErrWritten) + if action == ActionReconnect { cancel() return } - st, ok := status.FromError(err) - if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { - if !l.ackErrWritten { - utils.Logger.ErrorF("failed to receive ack: %v", err) - l.ackErrWritten = true - } - time.Sleep(timeToSleep) - cancel() - return - } else { - if !l.ackErrWritten { - utils.Logger.ErrorF("failed to receive ack: %v", err) - l.ackErrWritten = true - } - time.Sleep(timeToSleep) - continue - } + continue } l.ackErrWritten = false - l.db.Lock() err = l.db.Update(&models.Log{}, "id", ack.LastId, "processed", true) if err != nil { utils.Logger.ErrorF("failed to update log: %v", err) } - l.db.Unlock() } } } @@ -124,44 +133,28 @@ func (l *LogProcessor) processLogs(plClient plugins.Integration_ProcessLogClient utils.Logger.Info("context done, exiting processLogs") return case newLog := <-LogQueue: - id, err := uuid.NewRandom() - if err != nil { - utils.Logger.ErrorF("failed to generate uuid: %v", err) - continue - } + if newLog.Id == "" { + id, err := uuid.NewRandom() + if err != nil { + utils.Logger.ErrorF("failed to generate uuid: %v", err) + continue + } - newLog.Id = id.String() - l.db.Lock() - err = l.db.Create(&models.Log{ID: newLog.Id, Log: newLog.Raw, Type: newLog.DataType, CreatedAt: time.Now(), DataSource: newLog.DataSource, Processed: false}) - if err != nil { - utils.Logger.ErrorF("failed to save log: %v :log: %s", err, newLog.Raw) + newLog.Id = id.String() + err = l.db.Create(&models.Log{ID: newLog.Id, Log: newLog.Raw, Type: newLog.DataType, CreatedAt: time.Now(), DataSource: newLog.DataSource, Processed: false}) + if err != nil { + utils.Logger.ErrorF("failed to save log: %v :log: %s", err, newLog.Raw) + } } - l.db.Unlock() - err = plClient.Send(newLog) + err := plClient.Send(newLog) if err != nil { - if strings.Contains(err.Error(), "EOF") { - time.Sleep(timeToSleep) - cancel() - return - } - st, ok := status.FromError(err) - if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { - if !l.sendErrWritten { - utils.Logger.ErrorF("failed to send log: %v :log: %s", err, newLog.Raw) - l.sendErrWritten = true - } - time.Sleep(timeToSleep) + action := HandleGRPCStreamError(err, "failed to send log", &l.sendErrWritten) + if action == ActionReconnect { cancel() return - } else { - if !l.sendErrWritten { - utils.Logger.ErrorF("failed to send log: %v :log: %s", err, newLog.Raw) - l.sendErrWritten = true - } - time.Sleep(timeToSleep) - continue } + continue } l.sendErrWritten = false } @@ -185,17 +178,13 @@ func (l *LogProcessor) CleanCountedLogs() { continue } } - l.db.Lock() _, err = l.db.DeleteOld(&models.Log{}, dataRetention) if err != nil { utils.Logger.ErrorF("error deleting old logs: %s", err) } - l.db.Unlock() unprocessed := make([]models.Log, 0, 10) - l.db.Lock() found, err := l.db.Find(&unprocessed, "processed", false) - l.db.Unlock() if err != nil { utils.Logger.ErrorF("error finding unprocessed logs: %s", err) continue @@ -215,21 +204,35 @@ func (l *LogProcessor) CleanCountedLogs() { } } -func createClient(client plugins.IntegrationClient, ctx context.Context) plugins.Integration_ProcessLogClient { +func createClient(client plugins.IntegrationClient, ctx context.Context) (plugins.Integration_ProcessLogClient, error) { var connErrMsgWritten bool invalidKeyCounter := 0 + invalidKeyDelay := timeToSleep + maxInvalidKeyDelay := 5 * time.Minute + maxInvalidKeyAttempts := 100 // ~8+ hours with backoff before uninstall for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + plClient, err := client.ProcessLog(ctx) if err != nil { if strings.Contains(err.Error(), "invalid agent key") { invalidKeyCounter++ - if invalidKeyCounter >= 20 { - utils.Logger.Info("Uninstalling agent: reason: agent has been removed from the panel...") - _ = agent.UninstallAll() - os.Exit(1) + utils.Logger.ErrorF("invalid agent key (attempt %d/%d), retrying in %v", invalidKeyCounter, maxInvalidKeyAttempts, invalidKeyDelay) + if invalidKeyCounter >= maxInvalidKeyAttempts { + utils.Logger.ErrorF("uninstalling agent after %d consecutive invalid key errors", maxInvalidKeyAttempts) + _ = UninstallAll() + return nil, ErrAgentUninstalled } + time.Sleep(invalidKeyDelay) + invalidKeyDelay = utils.IncrementReconnectDelay(invalidKeyDelay, maxInvalidKeyDelay) + continue } else { invalidKeyCounter = 0 + invalidKeyDelay = timeToSleep } if !connErrMsgWritten { utils.Logger.ErrorF("failed to create input client: %v", err) @@ -238,7 +241,7 @@ func createClient(client plugins.IntegrationClient, ctx context.Context) plugins time.Sleep(timeToSleep) continue } - return plClient + return plClient, nil } } @@ -256,12 +259,12 @@ func SetDataRetention(retention string) error { return errors.New("retention must be greater than 0") } - return utils.WriteJSON(config.RetentionConfigFile, models.DataRetention{Retention: retentionInt}) + return fs.WriteJSON(config.RetentionConfigFile, models.DataRetention{Retention: retentionInt}) } func GetDataRetention() (int, error) { retention := models.DataRetention{} - err := utils.ReadJson(config.RetentionConfigFile, &retention) + err := fs.ReadJSON(config.RetentionConfigFile, &retention) if err != nil { return 0, err } diff --git a/agent/agent/ping.pb.go b/agent/agent/ping.pb.go index 21ddaf763..b6f9787b9 100644 --- a/agent/agent/ping.pb.go +++ b/agent/agent/ping.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: ping.proto package agent @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -21,20 +22,17 @@ const ( ) type PingRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Type ConnectorType `protobuf:"varint,1,opt,name=type,proto3,enum=agent.ConnectorType" json:"type,omitempty"` unknownFields protoimpl.UnknownFields - - Type ConnectorType `protobuf:"varint,1,opt,name=type,proto3,enum=agent.ConnectorType" json:"type,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingRequest) Reset() { *x = PingRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_ping_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_ping_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingRequest) String() string { @@ -45,7 +43,7 @@ func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { mi := &file_ping_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -68,20 +66,17 @@ func (x *PingRequest) GetType() ConnectorType { } type PingResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Received string `protobuf:"bytes,1,opt,name=received,proto3" json:"received,omitempty"` unknownFields protoimpl.UnknownFields - - Received string `protobuf:"bytes,1,opt,name=received,proto3" json:"received,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingResponse) Reset() { *x = PingResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_ping_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_ping_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingResponse) String() string { @@ -92,7 +87,7 @@ func (*PingResponse) ProtoMessage() {} func (x *PingResponse) ProtoReflect() protoreflect.Message { mi := &file_ping_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -116,40 +111,31 @@ func (x *PingResponse) GetReceived() string { var File_ping_proto protoreflect.FileDescriptor -var file_ping_proto_rawDesc = []byte{ - 0x0a, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x1a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0x37, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2a, 0x0a, 0x0c, 0x50, 0x69, - 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x32, 0x42, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_ping_proto_rawDesc = "" + + "\n" + + "\n" + + "ping.proto\x12\x05agent\x1a\fcommon.proto\"7\n" + + "\vPingRequest\x12(\n" + + "\x04type\x18\x01 \x01(\x0e2\x14.agent.ConnectorTypeR\x04type\"*\n" + + "\fPingResponse\x12\x1a\n" + + "\breceived\x18\x01 \x01(\tR\breceived2B\n" + + "\vPingService\x123\n" + + "\x04Ping\x12\x12.agent.PingRequest\x1a\x13.agent.PingResponse\"\x00(\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_ping_proto_rawDescOnce sync.Once - file_ping_proto_rawDescData = file_ping_proto_rawDesc + file_ping_proto_rawDescData []byte ) func file_ping_proto_rawDescGZIP() []byte { file_ping_proto_rawDescOnce.Do(func() { - file_ping_proto_rawDescData = protoimpl.X.CompressGZIP(file_ping_proto_rawDescData) + file_ping_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_ping_proto_rawDesc), len(file_ping_proto_rawDesc))) }) return file_ping_proto_rawDescData } var file_ping_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_ping_proto_goTypes = []interface{}{ +var file_ping_proto_goTypes = []any{ (*PingRequest)(nil), // 0: agent.PingRequest (*PingResponse)(nil), // 1: agent.PingResponse (ConnectorType)(0), // 2: agent.ConnectorType @@ -171,37 +157,11 @@ func file_ping_proto_init() { return } file_common_proto_init() - if !protoimpl.UnsafeEnabled { - file_ping_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_ping_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_ping_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_ping_proto_rawDesc), len(file_ping_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, @@ -212,7 +172,6 @@ func file_ping_proto_init() { MessageInfos: file_ping_proto_msgTypes, }.Build() File_ping_proto = out.File - file_ping_proto_rawDesc = nil file_ping_proto_goTypes = nil file_ping_proto_depIdxs = nil } diff --git a/agent/agent/ping_grpc.pb.go b/agent/agent/ping_grpc.pb.go index f283dc80a..f3213500c 100644 --- a/agent/agent/ping_grpc.pb.go +++ b/agent/agent/ping_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: ping.proto package agent diff --git a/agent/agent/ping_imp.go b/agent/agent/ping_imp.go index c8660740a..c4cc93f6e 100644 --- a/agent/agent/ping_imp.go +++ b/agent/agent/ping_imp.go @@ -2,14 +2,10 @@ package agent import ( "context" - "strings" "time" "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/conn" "github.com/utmstack/UTMStack/agent/utils" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) var ( @@ -18,17 +14,12 @@ var ( ) func StartPing(cnf *config.Config, ctx context.Context) { - var connErrMsgWritten, errorLogged bool + var connErrLogged, streamErrLogged bool for { - connection, err := conn.GetAgentManagerConnection(cnf) + connection, err := GetAgentManagerConnection(cnf) if err != nil { - if !connErrMsgWritten { - utils.Logger.ErrorF("error connecting to Agent Manager: %v", err) - connErrMsgWritten = true - } else { - utils.Logger.LogF(100, "error connecting to Agent Manager: %v", err) - } + LogConnectionError(err, "Agent Manager", &connErrLogged) time.Sleep(timeToSleep) continue } @@ -36,52 +27,28 @@ func StartPing(cnf *config.Config, ctx context.Context) { client := NewPingServiceClient(connection) stream, err := client.Ping(ctx) if err != nil { - if !connErrMsgWritten { - utils.Logger.ErrorF("failed to start Ping Stream: %v", err) - connErrMsgWritten = true - } else { - utils.Logger.LogF(100, "failed to start Ping Stream: %v", err) - } + LogStreamError(err, "Ping Stream", &connErrLogged) time.Sleep(timeToSleep) continue } utils.Logger.LogF(100, "Ping Stream started") - connErrMsgWritten = false + connErrLogged = false ticker := time.NewTicker(pingInterval) + pingLoop: for range ticker.C { err := stream.Send(&PingRequest{Type: ConnectorType_AGENT}) if err != nil { - if strings.Contains(err.Error(), "EOF") { - utils.Logger.LogF(100, "error sending Ping request: %v", err) - time.Sleep(timeToSleep) - break - } - st, ok := status.FromError(err) - if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { - if !errorLogged { - utils.Logger.ErrorF("error sending Ping request: %v", err) - errorLogged = true - } else { - utils.Logger.LogF(100, "error sending Ping request: %v", err) - } - time.Sleep(timeToSleep) - break - } else { - if !errorLogged { - utils.Logger.ErrorF("error sending Ping request: %v", err) - errorLogged = true - } else { - utils.Logger.LogF(100, "error sending Ping request: %v", err) - } - time.Sleep(timeToSleep) - continue + action := HandleGRPCStreamError(err, "error sending Ping request", &streamErrLogged) + if action == ActionReconnect { + break pingLoop } + continue } - errorLogged = false + streamErrLogged = false utils.Logger.LogF(100, "Ping request sent") } diff --git a/agent/agent/register.go b/agent/agent/register.go index b90dd0489..59615ab98 100644 --- a/agent/agent/register.go +++ b/agent/agent/register.go @@ -5,14 +5,14 @@ import ( "fmt" "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/conn" "github.com/utmstack/UTMStack/agent/models" "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" "google.golang.org/grpc/metadata" ) func RegisterAgent(cnf *config.Config, UTMKey string) error { - connection, err := conn.GetAgentManagerConnection(cnf) + connection, err := GetAgentManagerConnection(cnf) if err != nil { return fmt.Errorf("error connecting to Agent Manager: %v", err) } @@ -33,7 +33,7 @@ func RegisterAgent(cnf *config.Config, UTMKey string) error { } version := models.Version{} - err = utils.ReadJson(config.VersionPath, &version) + err = fs.ReadJSON(config.VersionPath, &version) if err != nil { return fmt.Errorf("error reading version file: %v", err) } diff --git a/agent/agent/uninstall.go b/agent/agent/uninstall.go index 3d0bbf291..fe931fccc 100644 --- a/agent/agent/uninstall.go +++ b/agent/agent/uninstall.go @@ -2,15 +2,15 @@ package agent import ( "fmt" - "path/filepath" + "os" - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" ) func UninstallAll() error { - err := utils.Execute(filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceFile, "")), utils.GetMyPath(), "uninstall") - if err != nil { + // Use the current executable path - the agent uninstalls itself + if err := exec.Run(os.Args[0], fs.GetExecutablePath(), "uninstall"); err != nil { return fmt.Errorf("%v", err) } return nil diff --git a/agent/agent/update.go b/agent/agent/update.go index 7fca7450e..a9616454f 100644 --- a/agent/agent/update.go +++ b/agent/agent/update.go @@ -2,31 +2,54 @@ package agent import ( context "context" - "fmt" + "time" "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/conn" "github.com/utmstack/UTMStack/agent/models" "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" ) -func UpdateAgent(cnf *config.Config, ctx context.Context) error { - connection, err := conn.GetAgentManagerConnection(cnf) +const updateInterval = 5 * time.Minute + +func UpdateAgent(cnf *config.Config, ctx context.Context) { + var errLogged bool + + for { + err := updateAgentOnce(cnf, ctx) + if err != nil { + if !errLogged { + utils.Logger.ErrorF("error updating agent: %v", err) + errLogged = true + } + } else { + errLogged = false + } + + select { + case <-ctx.Done(): + return + case <-time.After(updateInterval): + } + } +} + +func updateAgentOnce(cnf *config.Config, ctx context.Context) error { + connection, err := GetAgentManagerConnection(cnf) if err != nil { - return fmt.Errorf("error connecting to Agent Manager: %v", err) + return err } client := NewAgentServiceClient(connection) osInfo, err := utils.GetOsInfo() if err != nil { - return fmt.Errorf("error getting os info: %v", err) + return err } version := models.Version{} - err = utils.ReadJson(config.VersionPath, &version) - if err != nil { - utils.Logger.Fatal("error reading version file: %v", err) + if err = fs.ReadJSON(config.VersionPath, &version); err != nil { + return err } request := &AgentRequest{ @@ -40,9 +63,5 @@ func UpdateAgent(cnf *config.Config, ctx context.Context) error { } _, err = client.UpdateAgent(ctx, request) - if err != nil { - return fmt.Errorf("error updating agent: %v", err) - } - - return nil + return err } diff --git a/agent/cmd/change_paths.go b/agent/cmd/change_paths.go new file mode 100644 index 000000000..639baa6e5 --- /dev/null +++ b/agent/cmd/change_paths.go @@ -0,0 +1,92 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/collector" + "github.com/utmstack/UTMStack/agent/config" +) + +var changePathsCmd = &cobra.Command{ + Use: "change-paths [path2] [path3] ...", + Short: "Change the file paths for a file-based integration", + Long: `Change the file paths for a file-based integration. + +Glob patterns are supported (e.g., /var/log/nginx/*.log). + +Examples: + utmstack_agent change-paths nginx /var/log/nginx/access.log /var/log/nginx/error.log + utmstack_agent change-paths postgresql "/var/log/postgresql/*.log"`, + Args: cobra.MinimumNArgs(2), + PreRunE: requireInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + integration := args[0] + paths := args[1:] + + // Validate this is a file-type integration + if config.ValidateModuleType(integration) != "file" { + fmt.Printf("Error: %s is not a file-based integration\n", integration) + fmt.Println("Valid file integrations:", strings.Join(getFileIntegrations(), ", ")) + os.Exit(1) + } + + fmt.Printf("Changing paths for %s...\n", integration) + + result, err := collector.ChangeFilePaths(integration, paths) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + fmt.Println("Old paths:") + for _, p := range result.OldPaths { + fmt.Printf(" - %s\n", p) + } + fmt.Println("New paths:") + for _, p := range paths { + fmt.Printf(" - %s\n", p) + } + time.Sleep(5 * time.Second) + + return nil + }, +} + +var showPathsCmd = &cobra.Command{ + Use: "show-paths ", + Short: "Show the configured paths for a file-based integration", + Args: cobra.ExactArgs(1), + PreRunE: requireInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + integration := args[0] + + // Validate this is a file-type integration + if config.ValidateModuleType(integration) != "file" { + fmt.Printf("Error: %s is not a file-based integration\n", integration) + fmt.Println("Valid file integrations:", strings.Join(getFileIntegrations(), ", ")) + os.Exit(1) + } + + paths, err := collector.GetFileIntegrationPaths(integration) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + fmt.Printf("Configured paths for %s:\n", integration) + for _, p := range paths { + fmt.Printf(" - %s\n", p) + } + + return nil + }, +} + +func init() { + rootCmd.AddCommand(changePathsCmd) + rootCmd.AddCommand(showPathsCmd) +} diff --git a/agent/cmd/change_port.go b/agent/cmd/change_port.go new file mode 100644 index 000000000..d50be7b20 --- /dev/null +++ b/agent/cmd/change_port.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/collector" +) + +var changePortCmd = &cobra.Command{ + Use: "change-port ", + Short: "Change the port for a specific integration and protocol", + Args: cobra.ExactArgs(3), + PreRunE: requireInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("Changing integration port ...") + integration := args[0] + proto := args[1] + port := args[2] + + result, err := collector.ChangePort(integration, proto, port) + if err != nil { + fmt.Println("Error trying to change integration port: ", err) + os.Exit(1) + } + fmt.Printf("Port changed correctly from %s to %s\n", result.OldPort, port) + if result.Warning != "" { + fmt.Printf("Warning: %s\n", result.Warning) + } + time.Sleep(5 * time.Second) + + return nil + }, +} + +func init() { + rootCmd.AddCommand(changePortCmd) +} diff --git a/agent/cmd/change_retention.go b/agent/cmd/change_retention.go new file mode 100644 index 000000000..55def4a4d --- /dev/null +++ b/agent/cmd/change_retention.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/agent" +) + +var changeRetentionCmd = &cobra.Command{ + Use: "change-retention ", + Short: "Change the log retention", + Long: "Change the log retention to . Retention must be a number of megabytes. Example: 20", + Args: cobra.ExactArgs(1), + PreRunE: requireInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("Changing log retention ...") + retention := args[0] + + if err := agent.SetDataRetention(retention); err != nil { + fmt.Println("Error trying to change retention: ", err) + os.Exit(1) + } + + fmt.Printf("Retention changed correctly to %s\n", retention) + time.Sleep(5 * time.Second) + + return nil + }, +} + +func init() { + rootCmd.AddCommand(changeRetentionCmd) +} diff --git a/agent/cmd/clean_logs.go b/agent/cmd/clean_logs.go new file mode 100644 index 000000000..56ce119a9 --- /dev/null +++ b/agent/cmd/clean_logs.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/agent" + "github.com/utmstack/UTMStack/agent/database" + "github.com/utmstack/UTMStack/agent/models" +) + +var cleanLogsCmd = &cobra.Command{ + Use: "clean-logs", + Short: "Clean old logs from the database", + Args: cobra.NoArgs, + PreRunE: requireInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("Cleaning old logs ...") + db, err := database.GetDB() + if err != nil { + fmt.Println("Error initializing database: ", err) + os.Exit(1) + } + datR, err := agent.GetDataRetention() + if err != nil { + fmt.Println("Error getting retention: ", err) + os.Exit(1) + } + _, err = db.DeleteOld(models.Log{}, datR) + if err != nil { + fmt.Println("Error cleaning logs: ", err) + os.Exit(1) + } + fmt.Println("Logs cleaned correctly") + time.Sleep(5 * time.Second) + + return nil + }, +} + +func init() { + rootCmd.AddCommand(cleanLogsCmd) +} diff --git a/agent/cmd/disable_integration.go b/agent/cmd/disable_integration.go new file mode 100644 index 000000000..f2bc3b63f --- /dev/null +++ b/agent/cmd/disable_integration.go @@ -0,0 +1,73 @@ +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/collector" + "github.com/utmstack/UTMStack/agent/config" +) + +var disableIntegrationCmd = &cobra.Command{ + Use: "disable-integration [protocol]", + Short: "Disable integration for a specific integration and protocol", + Long: `Disable integration for a specific integration and protocol. + +For syslog integrations, protocol (tcp/udp) is required. +For file integrations (nginx, postgresql), protocol is not needed. + +Examples: + utmstack_agent disable-integration syslog tcp # Disable syslog TCP + utmstack_agent disable-integration nginx # Disable file-based nginx collector`, + Args: cobra.RangeArgs(1, 2), + PreRunE: requireInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("Changing integration status ...") + integration := args[0] + + // Check integration type + intType := config.ValidateModuleType(integration) + + switch intType { + case "file": + // File-based integration - no protocol needed + _, err := collector.ChangeFileIntegrationStatus(integration, false) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + fmt.Printf("Integration %s disabled\n", integration) + time.Sleep(5 * time.Second) + return nil + + case "syslog", "netflow": + // Syslog/netflow integration - protocol required + if len(args) < 2 { + fmt.Println("Error: protocol (tcp/udp) is required for this integration type") + os.Exit(1) + } + proto := args[1] + + port, err := collector.ChangeIntegrationStatus(integration, proto, false) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + fmt.Printf("Integration %s %s disabled (port %s freed)\n", integration, proto, port) + time.Sleep(5 * time.Second) + return nil + + default: + fmt.Printf("Error: invalid integration: %s\n", integration) + os.Exit(1) + return nil + } + }, +} + +func init() { + rootCmd.AddCommand(disableIntegrationCmd) +} diff --git a/agent/cmd/enable_integration.go b/agent/cmd/enable_integration.go new file mode 100644 index 000000000..1e93f4d6c --- /dev/null +++ b/agent/cmd/enable_integration.go @@ -0,0 +1,125 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/collector" + "github.com/utmstack/UTMStack/agent/config" +) + +var enableTLS bool + +var enableIntegrationCmd = &cobra.Command{ + Use: "enable-integration [protocol]", + Short: "Enable integration for a specific integration and protocol", + Long: `Enable integration for a specific integration and protocol. + +For syslog integrations, protocol (tcp/udp) is required. +For file integrations (nginx, postgresql), protocol is not needed. + +Available flag: --tls (enable TLS for TCP only) + +Examples: + utmstack_agent enable-integration syslog tcp --tls # Enable syslog with TLS + utmstack_agent enable-integration syslog tcp # Enable syslog without TLS + utmstack_agent enable-integration nginx # Enable file-based nginx collector`, + Args: cobra.RangeArgs(1, 2), + PreRunE: requireInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("Changing integration status ...") + integration := args[0] + + // Check integration type + intType := config.ValidateModuleType(integration) + + switch intType { + case "file": + // File-based integration - no protocol needed + paths, err := collector.ChangeFileIntegrationStatus(integration, true) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + fmt.Printf("Integration %s enabled. Watching paths:\n", integration) + for _, p := range paths { + fmt.Printf(" - %s\n", p) + } + time.Sleep(5 * time.Second) + return nil + + case "syslog": + // Syslog integration - protocol required + if len(args) < 2 { + fmt.Println("Error: protocol (tcp/udp) is required for syslog integrations") + os.Exit(1) + } + proto := args[1] + + var port string + var err error + + if enableTLS { + port, err = collector.ChangeIntegrationStatus(integration, proto, true, true) + } else { + port, err = collector.ChangeIntegrationStatus(integration, proto, true, false) + } + + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + if enableTLS { + fmt.Printf("Integration %s %s enabled with TLS on port %s\n", integration, proto, port) + } else { + fmt.Printf("Integration %s %s enabled on port %s\n", integration, proto, port) + } + time.Sleep(5 * time.Second) + return nil + + case "netflow": + // Netflow integration + if len(args) < 2 { + fmt.Println("Error: protocol (udp) is required for netflow integration") + os.Exit(1) + } + proto := args[1] + port, err := collector.ChangeIntegrationStatus(integration, proto, true) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + fmt.Printf("Integration %s %s enabled on port %s\n", integration, proto, port) + time.Sleep(5 * time.Second) + return nil + + default: + fmt.Printf("Error: invalid integration: %s\n", integration) + fmt.Println("Valid syslog integrations:", strings.Join(getSyslogIntegrations(), ", ")) + fmt.Println("Valid file integrations:", strings.Join(getFileIntegrations(), ", ")) + os.Exit(1) + return nil + } + }, +} + +func getSyslogIntegrations() []string { + return []string{"syslog", "vmware-esxi", "antivirus-esmc-eset", "antivirus-kaspersky", + "firewall-cisco-asa", "firewall-cisco-firepower", "cisco-switch", "firewall-meraki", + "firewall-fortigate-traffic", "firewall-paloalto", "firewall-mikrotik", "firewall-sophos-xg", + "firewall-sonicwall", "deceptive-bytes", "antivirus-sentinel-one", "ibm-aix", + "firewall-pfsense", "firewall-fortiweb", "suricata"} +} + +func getFileIntegrations() []string { + return []string{"nginx", "postgresql"} +} + +func init() { + enableIntegrationCmd.Flags().BoolVar(&enableTLS, "tls", false, "Enable TLS for TCP") + rootCmd.AddCommand(enableIntegrationCmd) +} diff --git a/agent/cmd/helpers.go b/agent/cmd/helpers.go new file mode 100644 index 000000000..6b1ccf9a3 --- /dev/null +++ b/agent/cmd/helpers.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/utils" +) + +func requireInstalled(cmd *cobra.Command, args []string) error { + isInstalled, err := utils.CheckIfServiceIsInstalled("UTMStackAgent") + if err != nil { + return fmt.Errorf("error checking if service is installed: %v", err) + } + if !isInstalled { + return fmt.Errorf("UTMStackAgent service is not installed") + } + return nil +} + +func requireNotInstalled(cmd *cobra.Command, args []string) error { + isInstalled, err := utils.CheckIfServiceIsInstalled("UTMStackAgent") + if err != nil { + return fmt.Errorf("error checking if service is installed: %v", err) + } + if isInstalled { + return fmt.Errorf("UTMStackAgent service is already installed") + } + return nil +} diff --git a/agent/cmd/install.go b/agent/cmd/install.go new file mode 100644 index 000000000..1625015cc --- /dev/null +++ b/agent/cmd/install.go @@ -0,0 +1,73 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/agent" + pb "github.com/utmstack/UTMStack/agent/agent" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/serv" + "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" + "github.com/utmstack/UTMStack/shared/http" +) + +var installCmd = &cobra.Command{ + Use: "install ", + Short: "Install the UTMStackAgent service", + Args: cobra.ExactArgs(3), + PreRunE: requireNotInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + cnf := &config.Config{ + Server: args[0], + SkipCertValidation: args[2] == "yes", + } + utmKey := args[1] + + utils.PrintBanner() + fmt.Println("Installing UTMStackAgent service ...") + + fmt.Print("Checking server connection ... ") + if err := utils.ArePortsReachable(cnf.Server, config.AgentManagerPort, config.LogAuthProxyPort, config.DependenciesPort); err != nil { + fmt.Println("\nError trying to connect to server: ", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Print("Downloading version info ... ") + versionURL := fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, "version.json") + if err := http.DownloadFile(versionURL, nil, "version.json", fs.GetExecutablePath(), cnf.SkipCertValidation); err != nil { + fmt.Println("\nError downloading version.json: ", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Print("Configuring agent ... ") + if err := pb.RegisterAgent(cnf, utmKey); err != nil { + fmt.Println("\nError registering agent: ", err) + os.Exit(1) + } + if err := config.SaveConfig(cnf); err != nil { + fmt.Println("\nError saving config: ", err) + os.Exit(1) + } + if err := agent.SetDataRetention(""); err != nil { + fmt.Println("\nError setting retention: ", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Print("Creating service ... ") + serv.InstallService() + fmt.Println("[OK]") + fmt.Println("UTMStackAgent service installed correctly") + + return nil + }, +} + +func init() { + rootCmd.AddCommand(installCmd) +} diff --git a/agent/cmd/load_tls_certs.go b/agent/cmd/load_tls_certs.go new file mode 100644 index 000000000..eaa6006a0 --- /dev/null +++ b/agent/cmd/load_tls_certs.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" +) + +var loadTLSCertsCmd = &cobra.Command{ + Use: "load-tls-certs [ca_certificate_path]", + Short: "Load your own TLS certificates (RECOMMENDED for production)", + Long: `Load your own TLS certificates. + +Examples: + utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key /path/to/ca.crt + utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key # Without CA`, + Args: cobra.RangeArgs(2, 3), + PreRunE: requireInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + userCertPath := args[0] + userKeyPath := args[1] + var userCAPath string + if len(args) > 2 { + userCAPath = args[2] + } + + fmt.Println("Loading user TLS certificates ...") + + fmt.Print("Validating certificate files ... ") + if err := utils.ValidateIntegrationCertificates(userCertPath, userKeyPath); err != nil { + fmt.Printf("\nError: Invalid certificate files: %v\n", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Print("Installing certificates ... ") + src := utils.CertificateFiles{ + CertPath: userCertPath, + KeyPath: userKeyPath, + CAPath: userCAPath, + } + dest := utils.CertificateFiles{ + CertPath: config.IntegrationCertPath, + KeyPath: config.IntegrationKeyPath, + CAPath: config.IntegrationCAPath, + } + if err := utils.LoadUserCertificatesWithStruct(src, dest); err != nil { + fmt.Printf("\nError loading certificates: %v\n", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Println("TLS certificates loaded successfully!") + time.Sleep(5 * time.Second) + + return nil + }, +} + +func init() { + rootCmd.AddCommand(loadTLSCertsCmd) +} diff --git a/agent/cmd/root.go b/agent/cmd/root.go new file mode 100644 index 000000000..e50d8fcde --- /dev/null +++ b/agent/cmd/root.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/serv" +) + +var rootCmd = &cobra.Command{ + Use: "utmstack_agent", + Short: "UTMStack Agent CLI", + Long: `UTMStack Agent CLI + +Usage Examples: + utmstack_agent install + utmstack_agent enable-integration [--tls] + utmstack_agent disable-integration + utmstack_agent change-port + utmstack_agent change-retention + utmstack_agent load-tls-certs [ca] + utmstack_agent clean-logs + utmstack_agent uninstall + +TLS Certificate Management: + # Load your own certificates (RECOMMENDED) + utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key /path/to/ca.crt + utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key # Without CA + +TLS Integration Examples: + utmstack_agent enable-integration syslog tcp --tls # Enable with TLS + utmstack_agent enable-integration syslog tcp # Enable without TLS (default) + utmstack_agent disable-integration syslog tcp # Disable (auto-disables TLS) + +Note: + - Make sure to run commands with appropriate permissions. + - All commands require administrative privileges. + - For detailed logs, check the service log file.`, + Run: func(cmd *cobra.Command, args []string) { + serv.RunService() + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/agent/cmd/run.go b/agent/cmd/run.go new file mode 100644 index 000000000..e875e392f --- /dev/null +++ b/agent/cmd/run.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/utmstack/UTMStack/agent/serv" +) + +var runCmd = &cobra.Command{ + Use: "run", + Short: "Run the UTMStackAgent service", + Args: cobra.NoArgs, + PreRunE: requireInstalled, + Run: func(cmd *cobra.Command, args []string) { + serv.RunService() + }, +} + +func init() { + rootCmd.AddCommand(runCmd) +} diff --git a/agent/cmd/uninstall.go b/agent/cmd/uninstall.go new file mode 100644 index 000000000..996ad0f49 --- /dev/null +++ b/agent/cmd/uninstall.go @@ -0,0 +1,68 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/spf13/cobra" + pb "github.com/utmstack/UTMStack/agent/agent" + "github.com/utmstack/UTMStack/agent/collector" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/dependency" + "github.com/utmstack/UTMStack/agent/serv" + "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" +) + +var uninstallCmd = &cobra.Command{ + Use: "uninstall", + Short: "Uninstall the UTMStackAgent service", + Args: cobra.NoArgs, + PreRunE: requireInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("Uninstalling UTMStackAgent service ...") + + fmt.Print("Stopping UTMStackUpdater service... ") + updaterPath := filepath.Join(fs.GetExecutablePath(), dependency.UpdaterFile("")) + if fs.Exists(updaterPath) { + err := exec.Run(updaterPath, fs.GetExecutablePath(), "uninstall") + if err != nil { + fmt.Printf("Warning: %v\n", err) + } else { + fmt.Println("[OK]") + } + time.Sleep(2 * time.Second) + } else { + fmt.Println("[SKIPPED - not found]") + } + + cnf, err := config.GetCurrentConfig() + if err != nil { + fmt.Println("Error getting config: ", err) + os.Exit(1) + } + if err = pb.DeleteAgent(cnf); err != nil { + utils.Logger.ErrorF("error deleting agent: %v", err) + } + if err = collector.UninstallAll(); err != nil { + fmt.Printf("error uninstalling collectors: %v\n", err) + os.Exit(1) + } + os.Remove(config.ConfigurationFile) + + serv.UninstallService() + + fmt.Println("[OK]") + fmt.Println("UTMStackAgent service uninstalled correctly") + os.Exit(0) + + return nil + }, +} + +func init() { + rootCmd.AddCommand(uninstallCmd) +} diff --git a/agent/collector/collector.go b/agent/collector/collector.go new file mode 100644 index 000000000..34c413c74 --- /dev/null +++ b/agent/collector/collector.go @@ -0,0 +1,118 @@ +package collector + +import ( + "context" + "fmt" + "sync" + + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/agent" + "github.com/utmstack/UTMStack/agent/collector/file" + "github.com/utmstack/UTMStack/agent/collector/netflow" + "github.com/utmstack/UTMStack/agent/collector/platform" + "github.com/utmstack/UTMStack/agent/collector/syslog" + "github.com/utmstack/UTMStack/agent/utils" +) + +// Collector is the interface that every collector must implement. +type Collector interface { + Name() string + Start(ctx context.Context, queue chan *plugins.Log) + Stop() +} + +// Installable is an optional interface for collectors that install system services. +type Installable interface { + Install() error + Uninstall() error +} + +var ( + activeCollectors []Collector + collectorsMu sync.Mutex +) + +// StartAll starts all collectors: platform-specific, syslog and netflow. +// The context is used for graceful shutdown signaling. +func StartAll(ctx context.Context) { + collectorsMu.Lock() + defer collectorsMu.Unlock() + + // Clear previous collectors + activeCollectors = nil + + // Create syslog collector + syslogCollector := syslog.New() + activeCollectors = append(activeCollectors, syslogCollector) + go runCollector(ctx, syslogCollector, agent.LogQueue) + + // Create netflow collector + netflowCollector := netflow.New() + activeCollectors = append(activeCollectors, netflowCollector) + go runCollector(ctx, netflowCollector, agent.LogQueue) + + // Create file collector (nginx, postgresql, etc.) + fileCollector := file.New() + activeCollectors = append(activeCollectors, fileCollector) + go runCollector(ctx, fileCollector, agent.LogQueue) + + // Create platform collectors (filebeat, winlogbeat, macos, etc.) + platformCollectors := platform.GetCollectors() + for _, c := range platformCollectors { + activeCollectors = append(activeCollectors, c) + go runCollector(ctx, c, agent.LogQueue) + } + + utils.Logger.Info("All collectors started") +} + +// runCollector runs a collector with panic recovery. +func runCollector(ctx context.Context, c Collector, queue chan *plugins.Log) { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in collector %s: %v", c.Name(), r) + } + }() + c.Start(ctx, queue) +} + +// StopAll stops all active collectors. +func StopAll() { + collectorsMu.Lock() + defer collectorsMu.Unlock() + + for _, c := range activeCollectors { + utils.Logger.Info("Stopping collector: %s", c.Name()) + c.Stop() + } + activeCollectors = nil + utils.Logger.Info("All collectors stopped") +} + +// InstallAll installs all platform collectors that implement Installable. +func InstallAll() error { + platformCollectors := platform.GetCollectors() + for _, c := range platformCollectors { + if inst, ok := c.(Installable); ok { + if err := inst.Install(); err != nil { + return fmt.Errorf("%v", err) + } + } + } + utils.Logger.LogF(100, "collectors installed correctly") + return nil +} + +// UninstallAll uninstalls all platform collectors that implement Installable. +func UninstallAll() error { + platformCollectors := platform.GetCollectors() + for _, c := range platformCollectors { + if inst, ok := c.(Installable); ok { + if err := inst.Uninstall(); err != nil { + return fmt.Errorf("%v", err) + } + } + } + utils.Logger.LogF(100, "collectors uninstalled correctly") + return nil +} diff --git a/agent/collector/config.go b/agent/collector/config.go new file mode 100644 index 000000000..0ceb22023 --- /dev/null +++ b/agent/collector/config.go @@ -0,0 +1,394 @@ +package collector + +import ( + "fmt" + "net" + + "github.com/utmstack/UTMStack/agent/collector/schema" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" +) + +// SyncCollectorConfig ensures the collector config file exists and is synchronized +// with the current ProtoPorts and FilePaths. It handles both initial creation and version upgrades: +// - If config doesn't exist → creates it with all integrations from ProtoPorts and FilePaths +// - If config exists → adds new integrations, removes obsolete ones +func SyncCollectorConfig() error { + cnf, err := schema.ReadCollectorConfig() + if err != nil { + // Config file doesn't exist, create it + integrations := make(map[string]schema.Integration) + for logTyp, ports := range config.ProtoPorts { + integrations[string(logTyp)] = schema.Integration{ + TCP: schema.Port{IsListen: false, Port: ports.TCP}, + UDP: schema.Port{IsListen: false, Port: ports.UDP}, + } + } + + fileIntegrations := make(map[string]schema.FileIntegration) + for logTyp, paths := range config.FilePaths { + fileIntegrations[string(logTyp)] = schema.FileIntegration{ + Enabled: false, + Paths: paths, + } + } + + newConfig := &schema.CollectorConfig{ + Integrations: integrations, + FileIntegrations: fileIntegrations, + } + return schema.WriteCollectorConfig(newConfig) + } + + modified := false + + // Add new integrations from ProtoPorts + for logTyp, ports := range config.ProtoPorts { + key := string(logTyp) + if _, exists := cnf.Integrations[key]; !exists { + cnf.Integrations[key] = schema.Integration{ + TCP: schema.Port{IsListen: false, Port: ports.TCP}, + UDP: schema.Port{IsListen: false, Port: ports.UDP}, + } + modified = true + utils.Logger.Info("Added new integration to config: %s", key) + } + } + + // Remove integrations that no longer exist in ProtoPorts + for key := range cnf.Integrations { + if _, exists := config.ProtoPorts[config.DataType(key)]; !exists { + delete(cnf.Integrations, key) + modified = true + utils.Logger.Info("Removed obsolete integration from config: %s", key) + } + } + + // Initialize FileIntegrations map if nil + if cnf.FileIntegrations == nil { + cnf.FileIntegrations = make(map[string]schema.FileIntegration) + } + + // Add new file integrations from FilePaths + for logTyp, paths := range config.FilePaths { + key := string(logTyp) + if _, exists := cnf.FileIntegrations[key]; !exists { + cnf.FileIntegrations[key] = schema.FileIntegration{ + Enabled: false, + Paths: paths, + } + modified = true + utils.Logger.Info("Added new file integration to config: %s", key) + } + } + + // Remove file integrations that no longer exist in FilePaths + for key := range cnf.FileIntegrations { + if _, exists := config.FilePaths[config.DataType(key)]; !exists { + delete(cnf.FileIntegrations, key) + modified = true + utils.Logger.Info("Removed obsolete file integration from config: %s", key) + } + } + + if modified { + return schema.WriteCollectorConfig(&cnf) + } + return nil +} + +// ConfigureFirstTime is an alias for SyncCollectorConfig for backward compatibility. +// Deprecated: Use SyncCollectorConfig instead. +func ConfigureFirstTime() error { + return SyncCollectorConfig() +} + +// ChangeIntegrationStatus enables or disables an integration. +func ChangeIntegrationStatus(logTyp string, proto string, isEnabled bool, tlsOptions ...bool) (string, error) { + var port string + cnf, err := schema.ReadCollectorConfig() + if err != nil { + return "", fmt.Errorf("error reading collector config: %v", err) + } + + if valid := config.ValidateModuleType(logTyp); valid == "nil" { + return "", fmt.Errorf("invalid integration: %s", logTyp) + } + + integration := cnf.Integrations[logTyp] + switch proto { + case "tcp": + port = integration.TCP.Port + case "udp": + port = integration.UDP.Port + default: + return "", fmt.Errorf("invalid protocol: %s", proto) + } + + // When enabling, validate port is available + if isEnabled { + if conflicting := CheckPortConflict(port, proto, &cnf, logTyp); conflicting != "" { + return "", fmt.Errorf("port %s is already in use by integration '%s'", port, conflicting) + } + if !IsPortBindable(port, proto) { + return "", fmt.Errorf("port %s is already in use by another process", port) + } + } + + switch proto { + case "tcp": + integration.TCP.IsListen = isEnabled + + if len(tlsOptions) > 0 && isEnabled { + if tlsOptions[0] { + if !fs.Exists(config.IntegrationCertPath) || !fs.Exists(config.IntegrationKeyPath) { + return "", fmt.Errorf("TLS certificates not found. Please load certificates first") + } + integration.TCP.TLSEnabled = true + } else { + integration.TCP.TLSEnabled = false + } + } + + if !isEnabled { + integration.TCP.TLSEnabled = false + } + + case "udp": + integration.UDP.IsListen = isEnabled + + if len(tlsOptions) > 0 && tlsOptions[0] { + return "", fmt.Errorf("TLS is not supported for UDP protocol. Use TCP for TLS connections") + } + } + + cnf.Integrations[logTyp] = integration + return port, schema.WriteCollectorConfig(&cnf) +} + +// ChangePortResult contains the result of a port change operation. +type ChangePortResult struct { + OldPort string + Warning string +} + +// ChangePort changes the port for an integration. +// Returns the old port and a warning if another integration has the same port configured. +func ChangePort(logTyp string, proto string, port string) (ChangePortResult, error) { + result := ChangePortResult{} + cnf, err := schema.ReadCollectorConfig() + if err != nil { + return result, fmt.Errorf("error reading collector config: %v", err) + } + + if valid := config.ValidateModuleType(logTyp); valid == "nil" { + return result, fmt.Errorf("invalid integration: %s", logTyp) + } + + if !schema.ValidatePortChange(port) { + return result, fmt.Errorf("port %s is out of valid range %s-%s", port, config.PortRangeMin, config.PortRangeMax) + } + + // Check if port is in use by an enabled integration (error) + if conflicting := CheckPortConflict(port, proto, &cnf, logTyp); conflicting != "" { + return result, fmt.Errorf("port %s is already in use by integration '%s'", port, conflicting) + } + + // Check if port is configured by another integration (warning) + if configured := CheckPortConfigured(port, proto, &cnf, logTyp); configured != "" { + result.Warning = fmt.Sprintf("port %s is also configured for integration '%s' (currently disabled)", port, configured) + } + + integration := cnf.Integrations[logTyp] + switch proto { + case "tcp": + result.OldPort = integration.TCP.Port + integration.TCP.Port = port + case "udp": + result.OldPort = integration.UDP.Port + integration.UDP.Port = port + default: + return result, fmt.Errorf("invalid protocol: %s", proto) + } + + cnf.Integrations[logTyp] = integration + return result, schema.WriteCollectorConfig(&cnf) +} + +// CheckPortConflict checks if a port is in use by another enabled integration. +// Returns the integration name if there's a conflict, empty string otherwise. +func CheckPortConflict(port string, proto string, cnf *schema.CollectorConfig, currentIntegration string) string { + for integration, integrationConfig := range cnf.Integrations { + if integration == currentIntegration { + continue + } + if proto == "tcp" && integrationConfig.TCP.Port == port && integrationConfig.TCP.IsListen { + return integration + } + if proto == "udp" && integrationConfig.UDP.Port == port && integrationConfig.UDP.IsListen { + return integration + } + } + return "" +} + +// CheckPortConfigured checks if a port is configured (but not necessarily enabled) by another integration. +// Returns the integration name if configured, empty string otherwise. +func CheckPortConfigured(port string, proto string, cnf *schema.CollectorConfig, currentIntegration string) string { + for integration, integrationConfig := range cnf.Integrations { + if integration == currentIntegration { + continue + } + if proto == "tcp" && integrationConfig.TCP.Port == port { + return integration + } + if proto == "udp" && integrationConfig.UDP.Port == port { + return integration + } + } + return "" +} + +// IsPortBindable checks if the system can bind to a port. +func IsPortBindable(port string, proto string) bool { + addr := ":" + port + switch proto { + case "tcp": + listener, err := net.Listen("tcp", addr) + if err != nil { + return false + } + listener.Close() + return true + case "udp": + conn, err := net.ListenPacket("udp", addr) + if err != nil { + return false + } + conn.Close() + return true + default: + return false + } +} + +// EnableTLSForIntegration enables TLS for an integration. +func EnableTLSForIntegration(logTyp string, proto string) (string, error) { + cnf, err := schema.ReadCollectorConfig() + if err != nil { + return "", fmt.Errorf("error reading collector config: %v", err) + } + + if valid := config.ValidateModuleType(logTyp); valid == "nil" { + return "", fmt.Errorf("invalid integration: %s", logTyp) + } + + integration := cnf.Integrations[logTyp] + var port string + + switch proto { + case "tcp": + if integration.TCP.Port == "" { + return "", fmt.Errorf("TCP port not configured for %s", logTyp) + } + port = integration.TCP.Port + integration.TCP.TLSEnabled = true + case "udp": + return "", fmt.Errorf("TLS not supported for UDP protocol") + default: + return "", fmt.Errorf("invalid protocol: %s", proto) + } + + cnf.Integrations[logTyp] = integration + return port, schema.WriteCollectorConfig(&cnf) +} + +// DisableTLSForIntegration disables TLS for an integration. +func DisableTLSForIntegration(logTyp string, proto string) error { + cnf, err := schema.ReadCollectorConfig() + if err != nil { + return fmt.Errorf("error reading collector config: %v", err) + } + + integration := cnf.Integrations[logTyp] + switch proto { + case "tcp": + integration.TCP.TLSEnabled = false + case "udp": + return fmt.Errorf("TLS not supported for UDP protocol") + default: + return fmt.Errorf("invalid protocol: %s", proto) + } + + cnf.Integrations[logTyp] = integration + return schema.WriteCollectorConfig(&cnf) +} + +// ChangeFileIntegrationStatus enables or disables a file-based integration. +func ChangeFileIntegrationStatus(logTyp string, isEnabled bool) ([]string, error) { + cnf, err := schema.ReadCollectorConfig() + if err != nil { + return nil, fmt.Errorf("error reading collector config: %v", err) + } + + if valid := config.ValidateModuleType(logTyp); valid != "file" { + return nil, fmt.Errorf("invalid file integration: %s", logTyp) + } + + integration, exists := cnf.FileIntegrations[logTyp] + if !exists { + return nil, fmt.Errorf("file integration not found: %s", logTyp) + } + + integration.Enabled = isEnabled + cnf.FileIntegrations[logTyp] = integration + return integration.Paths, schema.WriteCollectorConfig(&cnf) +} + +// ChangeFilePathsResult contains the result of a path change operation. +type ChangeFilePathsResult struct { + OldPaths []string +} + +// ChangeFilePaths changes the paths for a file-based integration. +func ChangeFilePaths(logTyp string, paths []string) (ChangeFilePathsResult, error) { + result := ChangeFilePathsResult{} + cnf, err := schema.ReadCollectorConfig() + if err != nil { + return result, fmt.Errorf("error reading collector config: %v", err) + } + + if valid := config.ValidateModuleType(logTyp); valid != "file" { + return result, fmt.Errorf("invalid file integration: %s", logTyp) + } + + integration, exists := cnf.FileIntegrations[logTyp] + if !exists { + return result, fmt.Errorf("file integration not found: %s", logTyp) + } + + if len(paths) == 0 { + return result, fmt.Errorf("at least one path is required") + } + + result.OldPaths = integration.Paths + integration.Paths = paths + cnf.FileIntegrations[logTyp] = integration + return result, schema.WriteCollectorConfig(&cnf) +} + +// GetFileIntegrationPaths returns the configured paths for a file integration. +func GetFileIntegrationPaths(logTyp string) ([]string, error) { + cnf, err := schema.ReadCollectorConfig() + if err != nil { + return nil, fmt.Errorf("error reading collector config: %v", err) + } + + integration, exists := cnf.FileIntegrations[logTyp] + if !exists { + return nil, fmt.Errorf("file integration not found: %s", logTyp) + } + + return integration.Paths, nil +} diff --git a/agent/collector/configwatcher/watcher.go b/agent/collector/configwatcher/watcher.go new file mode 100644 index 000000000..dc1149682 --- /dev/null +++ b/agent/collector/configwatcher/watcher.go @@ -0,0 +1,95 @@ +// Package configwatcher provides a shared config file watcher using fsnotify. +package configwatcher + +import ( + "context" + "path/filepath" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" +) + +const ( + // FallbackInterval is used as a safety net in case fsnotify misses events + FallbackInterval = 5 * time.Minute +) + +// Watch monitors the collector config file for changes and calls onConfigChange +// when the file is modified. It also calls onConfigChange periodically as a fallback. +// This function blocks until ctx is cancelled. +func Watch(ctx context.Context, name string, onConfigChange func()) { + // Initial call + onConfigChange() + + // Set up fsnotify watcher + watcher, err := fsnotify.NewWatcher() + if err != nil { + utils.Logger.ErrorF("%s: failed to create fsnotify watcher: %v, falling back to polling", name, err) + runPollingFallback(ctx, name, onConfigChange) + return + } + defer watcher.Close() + + // Watch the directory containing the config file + configDir := filepath.Dir(config.CollectorFileName) + configBase := filepath.Base(config.CollectorFileName) + + if err := watcher.Add(configDir); err != nil { + utils.Logger.ErrorF("%s: failed to watch config directory: %v, falling back to polling", name, err) + runPollingFallback(ctx, name, onConfigChange) + return + } + + utils.Logger.Info("%s: watching config file for changes", name) + + // Fallback timer as safety net + fallbackTicker := time.NewTicker(FallbackInterval) + defer fallbackTicker.Stop() + + for { + select { + case <-ctx.Done(): + utils.Logger.Info("%s: stopping config watcher", name) + return + + case event, ok := <-watcher.Events: + if !ok { + return + } + if filepath.Base(event.Name) == configBase { + if event.Op&(fsnotify.Write|fsnotify.Create) != 0 { + utils.Logger.Info("%s: config file changed, reconciling", name) + onConfigChange() + } + } + + case err, ok := <-watcher.Errors: + if !ok { + return + } + utils.Logger.ErrorF("%s: fsnotify error: %v", name, err) + + case <-fallbackTicker.C: + onConfigChange() + } + } +} + +// runPollingFallback is used when fsnotify cannot be initialized. +func runPollingFallback(ctx context.Context, name string, onConfigChange func()) { + utils.Logger.Info("%s: using polling fallback", name) + ticker := time.NewTicker(FallbackInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + utils.Logger.Info("%s: stopping config watcher", name) + return + case <-ticker.C: + onConfigChange() + } + } +} diff --git a/agent/collector/file/file.go b/agent/collector/file/file.go new file mode 100644 index 000000000..7083e3991 --- /dev/null +++ b/agent/collector/file/file.go @@ -0,0 +1,278 @@ +package file + +import ( + "bufio" + "context" + "io" + "os" + "path/filepath" + "sync" + "time" + + "github.com/threatwinds/go-sdk/entities" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/collector/configwatcher" + "github.com/utmstack/UTMStack/agent/collector/schema" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" +) + +const pollInterval = 1 * time.Second + +// fileWatcher represents an active file being tailed. +type fileWatcher struct { + dataType string + path string + file *os.File + offset int64 + cancel context.CancelFunc +} + +// FileCollector manages file-based log collection. +type FileCollector struct { + watchers map[string]*fileWatcher // key: dataType+path + mu sync.RWMutex + queue chan *plugins.Log +} + +// New creates a new FileCollector. +func New() *FileCollector { + return &FileCollector{ + watchers: make(map[string]*fileWatcher), + } +} + +func (fc *FileCollector) Name() string { + return "file" +} + +func (fc *FileCollector) Stop() { + fc.mu.Lock() + defer fc.mu.Unlock() + for key, w := range fc.watchers { + if w.cancel != nil { + w.cancel() + } + if w.file != nil { + w.file.Close() + } + delete(fc.watchers, key) + } +} + +// Start begins watching for configuration changes using fsnotify. +// It performs an initial reconciliation and then reacts to config file changes. +func (fc *FileCollector) Start(ctx context.Context, queue chan *plugins.Log) { + fc.queue = queue + configwatcher.Watch(ctx, "file collector", func() { + fc.reconcile(ctx) + }) + fc.Stop() +} + +func (fc *FileCollector) reconcile(ctx context.Context) { + cnf, err := schema.ReadCollectorConfig() + if err != nil { + utils.Logger.ErrorF("file collector: error reading config: %v", err) + return + } + + // Build set of desired watchers from enabled file integrations + desiredWatchers := make(map[string]struct{}) + + for intType, integration := range cnf.FileIntegrations { + // Only handle file-type integrations + if config.ValidateModuleType(intType) != "file" { + continue + } + + if !integration.Enabled { + // Stop watchers for disabled integrations + fc.stopWatchersForDataType(intType) + continue + } + + // Expand glob patterns and start watchers + for _, pathPattern := range integration.Paths { + matches, err := filepath.Glob(pathPattern) + if err != nil { + utils.Logger.ErrorF("file collector: invalid glob pattern %s: %v", pathPattern, err) + continue + } + + if len(matches) == 0 { + utils.Logger.LogF(100, "file collector: no files match pattern %s for %s", pathPattern, intType) + continue + } + + for _, filePath := range matches { + key := intType + ":" + filePath + desiredWatchers[key] = struct{}{} + fc.ensureWatcher(ctx, intType, filePath) + } + } + } + + // Stop watchers that are no longer needed + fc.mu.Lock() + for key, w := range fc.watchers { + if _, exists := desiredWatchers[key]; !exists { + utils.Logger.Info("file collector: stopping watcher for %s", key) + if w.cancel != nil { + w.cancel() + } + if w.file != nil { + w.file.Close() + } + delete(fc.watchers, key) + } + } + fc.mu.Unlock() +} + +func (fc *FileCollector) stopWatchersForDataType(dataType string) { + fc.mu.Lock() + defer fc.mu.Unlock() + for key, w := range fc.watchers { + if w.dataType == dataType { + utils.Logger.Info("file collector: stopping watcher for %s", key) + if w.cancel != nil { + w.cancel() + } + if w.file != nil { + w.file.Close() + } + delete(fc.watchers, key) + } + } +} + +func (fc *FileCollector) ensureWatcher(ctx context.Context, dataType, filePath string) { + key := dataType + ":" + filePath + + fc.mu.Lock() + if _, exists := fc.watchers[key]; exists { + fc.mu.Unlock() + return + } + + // Create a new watcher + watchCtx, cancel := context.WithCancel(ctx) + w := &fileWatcher{ + dataType: dataType, + path: filePath, + cancel: cancel, + } + fc.watchers[key] = w + fc.mu.Unlock() + + // Start tailing in a goroutine + go fc.tailFile(watchCtx, w) + utils.Logger.Info("file collector: started watching %s for %s", filePath, dataType) +} + +func (fc *FileCollector) tailFile(ctx context.Context, w *fileWatcher) { + hostname, err := os.Hostname() + if err != nil { + hostname = "unknown" + } + + for { + select { + case <-ctx.Done(): + return + default: + } + + // Open the file if not already open or if it was rotated + if w.file == nil { + file, err := os.Open(w.path) + if err != nil { + utils.Logger.LogF(100, "file collector: error opening %s: %v", w.path, err) + time.Sleep(pollInterval) + continue + } + w.file = file + + // Seek to end to only read new content + offset, err := file.Seek(0, io.SeekEnd) + if err != nil { + utils.Logger.ErrorF("file collector: error seeking %s: %v", w.path, err) + w.file.Close() + w.file = nil + time.Sleep(pollInterval) + continue + } + w.offset = offset + } + + // Check for file rotation (file was replaced) + stat, err := os.Stat(w.path) + if err != nil { + // File may have been deleted, close and retry + w.file.Close() + w.file = nil + time.Sleep(pollInterval) + continue + } + + fileStat, err := w.file.Stat() + if err != nil || !os.SameFile(stat, fileStat) { + // File was rotated, reopen + w.file.Close() + w.file = nil + w.offset = 0 + continue + } + + // Check if file was truncated + if stat.Size() < w.offset { + w.offset = 0 + w.file.Seek(0, io.SeekStart) + } + + // Read new lines + reader := bufio.NewReader(w.file) + for { + select { + case <-ctx.Done(): + return + default: + } + + line, err := reader.ReadString('\n') + if err != nil { + if err != io.EOF { + utils.Logger.ErrorF("file collector: error reading %s: %v", w.path, err) + } + break + } + + if len(line) == 0 { + continue + } + + // Update offset + w.offset += int64(len(line)) + + // Validate and send log + validatedLog, _, err := entities.ValidateString(line, false) + if err != nil { + utils.Logger.LogF(100, "file collector: validation error for %s: %v", w.path, err) + continue + } + + select { + case fc.queue <- &plugins.Log{ + DataType: w.dataType, + DataSource: hostname, + Raw: validatedLog, + }: + default: + utils.Logger.LogF(100, "file collector: queue full, discarding log from %s", w.path) + } + } + + time.Sleep(pollInterval) + } +} diff --git a/agent/collector/netflow/goflow.go b/agent/collector/netflow/goflow.go new file mode 100644 index 000000000..b105079d3 --- /dev/null +++ b/agent/collector/netflow/goflow.go @@ -0,0 +1,263 @@ +package netflow + +import ( + "encoding/binary" + "fmt" + "net" + "time" + + goflownetflow "github.com/netsampler/goflow2/decoders/netflow" + "github.com/netsampler/goflow2/decoders/netflowlegacy" +) + +// PrepareGoflowV5 converts goflow2 NetFlow v5 packet to metrics +func PrepareGoflowV5(addr string, p *netflowlegacy.PacketNetFlowV5) []Metric { + nfExporter, _, _ := net.SplitHostPort(addr) + var metrics []Metric + + for _, r := range p.Records { + met := Metric{OutBytes: "0", InBytes: "0", OutPacket: "0", InPacket: "0", NFSender: nfExporter} + met.FlowVersion = "Netflow-V5" + + // Convert timestamps (First and Last are relative to SysUptime in milliseconds) + met.First = time.Unix(int64(p.UnixSecs), int64(p.UnixNSecs)).Add(-time.Duration(p.SysUptime-r.First) * time.Millisecond).Format(time.RFC3339Nano) + met.Last = time.Unix(int64(p.UnixSecs), int64(p.UnixNSecs)).Add(-time.Duration(p.SysUptime-r.Last) * time.Millisecond).Format(time.RFC3339Nano) + + met.Protocol = ProtoToName(fmt.Sprintf("%v", r.Proto)) + met.Bytes = fmt.Sprintf("%v", r.DOctets) + met.Packets = fmt.Sprintf("%v", r.DPkts) + met.TCPFlags = fmt.Sprintf("%v", r.TCPFlags) + met.SrcAs = fmt.Sprintf("%v", r.SrcAS) + met.DstAs = fmt.Sprintf("%v", r.DstAS) + met.SrcMask = fmt.Sprintf("%v", r.SrcMask) + met.DstMask = fmt.Sprintf("%v", r.DstMask) + + // Convert uint32 IPs to string + srcIP := make(net.IP, 4) + binary.BigEndian.PutUint32(srcIP, r.SrcAddr) + met.SrcIP = srcIP.String() + + dstIP := make(net.IP, 4) + binary.BigEndian.PutUint32(dstIP, r.DstAddr) + met.DstIP = dstIP.String() + + nextHop := make(net.IP, 4) + binary.BigEndian.PutUint32(nextHop, r.NextHop) + met.NextHop = nextHop.String() + + met.SrcPort = fmt.Sprintf("%v", r.SrcPort) + met.DstPort = fmt.Sprintf("%v", r.DstPort) + + met.InEthernet = fmt.Sprintf("%v", r.Input) + met.OutEthernet = fmt.Sprintf("%v", r.Output) + + metrics = append(metrics, met) + } + + return metrics +} + +// PrepareGoflowV9 converts goflow2 NetFlow v9 packet to metrics +func PrepareGoflowV9(addr string, p *goflownetflow.NFv9Packet) []Metric { + nfExporter, _, _ := net.SplitHostPort(addr) + var metrics []Metric + + for _, fs := range p.FlowSets { + // FlowSets can be TemplateFlowSet, DataFlowSet, or OptionsDataFlowSet + switch ds := fs.(type) { + case goflownetflow.DataFlowSet: + for _, record := range ds.Records { + met := Metric{OutBytes: "0", InBytes: "0", OutPacket: "0", InPacket: "0", NFSender: nfExporter} + met.FlowVersion = "Netflow-V9" + + for _, field := range record.Values { + extractFieldValue(&met, field.Type, field.Value, p.UnixSeconds) + } + + metrics = append(metrics, met) + } + } + } + + return metrics +} + +// PrepareGoflowIPFIX converts goflow2 IPFIX packet to metrics +func PrepareGoflowIPFIX(addr string, p *goflownetflow.IPFIXPacket) []Metric { + nfExporter, _, _ := net.SplitHostPort(addr) + var metrics []Metric + + for _, fs := range p.FlowSets { + // FlowSets can be TemplateFlowSet, DataFlowSet, or OptionsDataFlowSet + switch ds := fs.(type) { + case goflownetflow.DataFlowSet: + for _, record := range ds.Records { + met := Metric{OutBytes: "0", InBytes: "0", OutPacket: "0", InPacket: "0", NFSender: nfExporter} + met.FlowVersion = "IPFIX" + + for _, field := range record.Values { + extractFieldValue(&met, field.Type, field.Value, p.ExportTime) + } + + metrics = append(metrics, met) + } + } + } + + return metrics +} + +// extractFieldValue extracts field values based on IPFIX/NetFlow v9 field type IDs +// Field type IDs are defined in RFC 5102 and RFC 3954 +func extractFieldValue(met *Metric, fieldType uint16, value interface{}, exportTime uint32) { + switch fieldType { + // Timestamps + case 21: // flowEndSysUpTime + if v, ok := toUint32(value); ok { + met.Last = time.Unix(int64(exportTime), 0).Add(-time.Duration(v) * time.Millisecond).Format(time.RFC3339Nano) + } + case 22: // flowStartSysUpTime + if v, ok := toUint32(value); ok { + met.First = time.Unix(int64(exportTime), 0).Add(-time.Duration(v) * time.Millisecond).Format(time.RFC3339Nano) + } + case 150: // flowStartSeconds + if v, ok := toUint32(value); ok { + met.First = time.Unix(int64(v), 0).Format(time.RFC3339Nano) + } + case 151: // flowEndSeconds + if v, ok := toUint32(value); ok { + met.Last = time.Unix(int64(v), 0).Format(time.RFC3339Nano) + } + + // Byte and packet counts + case 1: // octetDeltaCount + met.Bytes = fmt.Sprintf("%v", value) + case 2: // packetDeltaCount + met.Packets = fmt.Sprintf("%v", value) + case 85: // octetTotalCount + met.Bytes = fmt.Sprintf("%v", value) + case 86: // packetTotalCount + met.Packets = fmt.Sprintf("%v", value) + + // Interfaces + case 10: // ingressInterface + met.InEthernet = fmt.Sprintf("%v", value) + case 14: // egressInterface + met.OutEthernet = fmt.Sprintf("%v", value) + + // IPv4 addresses + case 8: // sourceIPv4Address + if ip, ok := toIP(value); ok { + met.SrcIP = ip.String() + } else { + met.SrcIP = fmt.Sprintf("%v", value) + } + case 12: // destinationIPv4Address + if ip, ok := toIP(value); ok { + met.DstIP = ip.String() + } else { + met.DstIP = fmt.Sprintf("%v", value) + } + case 15: // ipNextHopIPv4Address + if ip, ok := toIP(value); ok { + met.NextHop = ip.String() + } else { + met.NextHop = fmt.Sprintf("%v", value) + } + + // IPv6 addresses + case 27: // sourceIPv6Address + if ip, ok := toIP(value); ok { + met.SrcIP = ip.String() + } else { + met.SrcIP = fmt.Sprintf("%v", value) + } + case 28: // destinationIPv6Address + if ip, ok := toIP(value); ok { + met.DstIP = ip.String() + } else { + met.DstIP = fmt.Sprintf("%v", value) + } + case 62: // ipNextHopIPv6Address + if ip, ok := toIP(value); ok { + met.NextHop = ip.String() + } else { + met.NextHop = fmt.Sprintf("%v", value) + } + + // Protocol and ports + case 4: // protocolIdentifier + met.Protocol = ProtoToName(fmt.Sprintf("%v", value)) + case 7: // sourceTransportPort + met.SrcPort = fmt.Sprintf("%v", value) + case 11: // destinationTransportPort + met.DstPort = fmt.Sprintf("%v", value) + + // Masks + case 9: // sourceIPv4PrefixLength + met.SrcMask = fmt.Sprintf("%v", value) + case 13: // destinationIPv4PrefixLength + met.DstMask = fmt.Sprintf("%v", value) + case 29: // sourceIPv6PrefixLength + met.SrcMask = fmt.Sprintf("%v", value) + case 30: // destinationIPv6PrefixLength + met.DstMask = fmt.Sprintf("%v", value) + + // AS numbers + case 16: // bgpSourceAsNumber + met.SrcAs = fmt.Sprintf("%v", value) + case 17: // bgpDestinationAsNumber + met.DstAs = fmt.Sprintf("%v", value) + + // TCP flags + case 6: // tcpControlBits + met.TCPFlags = fmt.Sprintf("%v", value) + + // Flow direction + case 61: // flowDirection + switch fmt.Sprintf("%v", value) { + case "0": + met.Direction = "Ingress" + case "1": + met.Direction = "Egress" + default: + met.Direction = fmt.Sprintf("%v", value) + } + } +} + +// toUint32 attempts to convert value to uint32 +func toUint32(value interface{}) (uint32, bool) { + switch v := value.(type) { + case uint32: + return v, true + case uint64: + return uint32(v), true + case int64: + return uint32(v), true + case int: + return uint32(v), true + case []byte: + if len(v) == 4 { + return binary.BigEndian.Uint32(v), true + } + } + return 0, false +} + +// toIP attempts to convert value to net.IP +func toIP(value interface{}) (net.IP, bool) { + switch v := value.(type) { + case net.IP: + return v, true + case []byte: + if len(v) == 4 || len(v) == 16 { + return net.IP(v), true + } + case uint32: + ip := make(net.IP, 4) + binary.BigEndian.PutUint32(ip, v) + return ip, true + } + return nil, false +} diff --git a/agent/collector/netflow/metrics.go b/agent/collector/netflow/metrics.go new file mode 100644 index 000000000..413ef5908 --- /dev/null +++ b/agent/collector/netflow/metrics.go @@ -0,0 +1,43 @@ +package netflow + +type Metric struct { + FlowVersion string `header:"version"` + NFSender string `header:"exporter"` + Last string `header:"last"` + First string `header:"first"` + Bytes string `header:"bytes"` + Packets string `header:"packets"` + InBytes string `header:"bytesIn"` + InPacket string `header:"packetsIn"` + OutBytes string `header:"bytesOut"` + OutPacket string `header:"packetsOut"` + InEthernet string `header:"inEth"` + OutEthernet string `header:"outEth"` + SrcIP string `header:"srcIp"` + SrcIp2lCountryShort string `header:"sCountry_S"` + SrcIp2lCountryLong string `header:"sCountry_L"` + SrcIp2lState string `header:"sState"` + SrcIp2lCity string `header:"sCity"` + SrcIp2lLat string `header:"sLat"` + SrcIp2lLong string `header:"sLong"` + DstIP string `header:"dstIp"` + DstIp2lCountryShort string `header:"dCountry_S"` + DstIp2lCountryLong string `header:"dCountry_L"` + DstIp2lState string `header:"dState"` + DstIp2lCity string `header:"dCity"` + DstIp2lLat string `header:"dLat"` + DstIp2lLong string `header:"dLong"` + Protocol string `header:"proto"` + SrcToS string `header:"srcToS"` + SrcPort string `header:"srcPort"` + DstPort string `header:"dstPort"` + FlowSamplerId string `header:"flowId"` + VendorPROPRIETARY string `header:"vendor"` + NextHop string `header:"nextHop"` + DstMask string `header:"dstMask"` + SrcMask string `header:"srcMask"` + TCPFlags string `header:"tcpFlags"` + Direction string `header:"direction"` + DstAs string `header:"dstAs"` + SrcAs string `header:"srcAs"` +} diff --git a/agent/collector/netflow/netflow.go b/agent/collector/netflow/netflow.go new file mode 100644 index 000000000..9e26e5628 --- /dev/null +++ b/agent/collector/netflow/netflow.go @@ -0,0 +1,449 @@ +package netflow + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/netsampler/goflow2/decoders/netflow" + "github.com/netsampler/goflow2/decoders/netflowlegacy" + tehmaze "github.com/tehmaze/netflow" + "github.com/tehmaze/netflow/session" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/collector/configwatcher" + "github.com/utmstack/UTMStack/agent/collector/schema" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" +) + +const ( + cacheCleanupInterval = 5 * time.Minute + cacheTTL = 30 * time.Minute +) + +// templateSystem implements netflow.NetFlowTemplateSystem for goflow2 +type templateSystem struct { + templates map[uint16]map[uint32]map[uint16]interface{} + lastUsed time.Time + mu sync.RWMutex +} + +func newTemplateSystem() *templateSystem { + return &templateSystem{ + templates: make(map[uint16]map[uint32]map[uint16]interface{}), + lastUsed: time.Now(), + } +} + +// legacyDecoderEntry wraps a decoder with its last used timestamp +type legacyDecoderEntry struct { + decoder *tehmaze.Decoder + lastUsed time.Time +} + +func (t *templateSystem) GetTemplate(version uint16, obsDomainId uint32, templateId uint16) (interface{}, error) { + t.mu.RLock() + defer t.mu.RUnlock() + + if versionMap, ok := t.templates[version]; ok { + if domainMap, ok := versionMap[obsDomainId]; ok { + if template, ok := domainMap[templateId]; ok { + return template, nil + } + } + } + return nil, fmt.Errorf("template not found: version=%d, obsDomainId=%d, templateId=%d", version, obsDomainId, templateId) +} + +func (t *templateSystem) AddTemplate(version uint16, obsDomainId uint32, template interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + + if _, ok := t.templates[version]; !ok { + t.templates[version] = make(map[uint32]map[uint16]interface{}) + } + if _, ok := t.templates[version][obsDomainId]; !ok { + t.templates[version][obsDomainId] = make(map[uint16]interface{}) + } + + var templateId uint16 + switch tmpl := template.(type) { + case netflow.TemplateRecord: + templateId = tmpl.TemplateId + case netflow.IPFIXOptionsTemplateRecord: + templateId = tmpl.TemplateId + case netflow.NFv9OptionsTemplateRecord: + templateId = tmpl.TemplateId + default: + return + } + + t.templates[version][obsDomainId][templateId] = template +} + +// NetflowCollector manages the single netflow UDP listener. +// It reads the config file periodically and reconciles port state internally. +type NetflowCollector struct { + dataType string + legacyDecoders map[string]*legacyDecoderEntry + templateSystem map[string]*templateSystem + listener *net.UDPConn + ctx context.Context + cancel context.CancelFunc + isEnabled bool + port string + mu sync.RWMutex + queue chan *plugins.Log +} + +// New creates a new NetflowCollector. +func New() *NetflowCollector { + return &NetflowCollector{ + dataType: "netflow", + legacyDecoders: make(map[string]*legacyDecoderEntry), + templateSystem: make(map[string]*templateSystem), + isEnabled: false, + port: config.ProtoPorts[config.DataTypeNetflow].UDP, + } +} + +func (nc *NetflowCollector) Name() string { + return "netflow" +} + +func (nc *NetflowCollector) Stop() { + nc.disablePort() +} + +// Start begins watching for configuration changes using fsnotify. +// It performs an initial reconciliation and then reacts to config file changes. +func (nc *NetflowCollector) Start(ctx context.Context, queue chan *plugins.Log) { + nc.queue = queue + + // Start cache cleanup goroutine + go func() { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in netflow cache cleanup: %v", r) + } + }() + + ticker := time.NewTicker(cacheCleanupInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + nc.cleanupStaleEntries() + } + } + }() + + configwatcher.Watch(ctx, "netflow collector", nc.reconcile) +} + +func (nc *NetflowCollector) reconcile() { + cnf, err := schema.ReadCollectorConfig() + if err != nil { + utils.Logger.ErrorF("error reading collector config: %v", err) + return + } + + integration, ok := cnf.Integrations["netflow"] + if !ok { + return + } + + cfgEnabled := integration.UDP.IsListen + cfgPort := integration.UDP.Port + + nc.mu.RLock() + isListening := nc.isEnabled + currentPort := nc.port + nc.mu.RUnlock() + + needKill := false + needStart := false + + if isListening && !cfgEnabled { + needKill = true + } else if !isListening && cfgEnabled { + needStart = true + } else if isListening && cfgEnabled && currentPort != cfgPort { + needKill = true + needStart = true + } + + if needKill { + nc.disablePort() + if needStart { + time.Sleep(200 * time.Millisecond) + } + } + + if cfgPort != "" { + nc.mu.Lock() + nc.port = cfgPort + nc.mu.Unlock() + } + + if needStart { + nc.enablePort() + } +} + +func (nc *NetflowCollector) enablePort() { + nc.mu.Lock() + if nc.isEnabled { + nc.mu.Unlock() + return + } + + port, err := strconv.Atoi(nc.port) + if err != nil { + nc.mu.Unlock() + utils.Logger.ErrorF("error converting port to int: %v", err) + return + } + + listener, err := net.ListenUDP("udp", &net.UDPAddr{ + Port: port, + IP: net.ParseIP("0.0.0.0"), + }) + if err != nil { + nc.mu.Unlock() + utils.Logger.ErrorF("error listening netflow: %v", err) + return + } + + nc.isEnabled = true + nc.listener = listener + nc.ctx, nc.cancel = context.WithCancel(context.Background()) + nc.mu.Unlock() + + utils.Logger.Info("Server %s listening in port: %s protocol: UDP", nc.dataType, nc.port) + + buffer := make([]byte, 65535) + + go func() { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in netflow listener: %v", r) + } + }() + + for { + select { + case <-nc.ctx.Done(): + return + default: + nc.listener.SetDeadline(time.Now().Add(1 * time.Second)) + + length, addr, err := nc.listener.ReadFromUDP(buffer) + if err != nil { + if errors.Is(err, net.ErrClosed) { + return + } + + var netOpErr *net.OpError + ok := errors.As(err, &netOpErr) + if ok && netOpErr.Timeout() { + continue + } + + utils.Logger.ErrorF("error connecting with netflow listener: %v", err) + continue + } + + packetData := buffer[:length] + packetInfo, validationErr := validateNetflowPacket(packetData) + if validationErr != nil { + utils.Logger.ErrorF("invalid NetFlow packet from %s (length: %d bytes): %v", addr.String(), length, validationErr) + continue + } + + var message interface{} + + switch packetInfo.version { + case 5: + msg, err := netflowlegacy.DecodeMessage(bytes.NewBuffer(packetData)) + if err != nil { + utils.Logger.ErrorF("error decoding %s message from %s: %v", packetInfo.versionName, addr.String(), err) + continue + } + message = msg + + case 9, 10: + ts := nc.getOrCreateTemplateSystem(addr.String()) + msg, err := netflow.DecodeMessage(bytes.NewBuffer(packetData), ts) + if err != nil { + if !strings.Contains(err.Error(), "template not found") { + utils.Logger.ErrorF("error decoding %s message from %s: %v", packetInfo.versionName, addr.String(), err) + } + continue + } + message = msg + + case 1, 6, 7: + d := nc.getOrCreateLegacyDecoder(addr.String()) + msg, err := d.Read(bytes.NewBuffer(packetData)) + if err != nil { + utils.Logger.ErrorF("error decoding %s message from %s: %v", packetInfo.versionName, addr.String(), err) + nc.removeLegacyDecoder(addr.String()) + continue + } + message = msg + + default: + utils.Logger.ErrorF("unsupported NetFlow version %d from %s", packetInfo.version, addr.String()) + continue + } + + err = processMessage(addr.String(), message, nc.queue) + if err != nil { + utils.Logger.ErrorF("error parsing netflow: %v", err) + } + } + } + }() +} + +func (nc *NetflowCollector) disablePort() { + nc.mu.Lock() + defer nc.mu.Unlock() + + if nc.isEnabled { + utils.Logger.Info("Server %s closed in port: %s protocol: UDP", nc.dataType, nc.port) + nc.cancel() + nc.listener.Close() + nc.isEnabled = false + } +} + +func (nc *NetflowCollector) getOrCreateTemplateSystem(addr string) *templateSystem { + nc.mu.Lock() + defer nc.mu.Unlock() + + if ts, ok := nc.templateSystem[addr]; ok { + ts.lastUsed = time.Now() + return ts + } + ts := newTemplateSystem() + nc.templateSystem[addr] = ts + return ts +} + +func (nc *NetflowCollector) getOrCreateLegacyDecoder(addr string) *tehmaze.Decoder { + nc.mu.Lock() + defer nc.mu.Unlock() + + if entry, ok := nc.legacyDecoders[addr]; ok { + entry.lastUsed = time.Now() + return entry.decoder + } + s := session.New() + d := tehmaze.NewDecoder(s) + nc.legacyDecoders[addr] = &legacyDecoderEntry{ + decoder: d, + lastUsed: time.Now(), + } + return d +} + +func (nc *NetflowCollector) removeLegacyDecoder(addr string) { + nc.mu.Lock() + defer nc.mu.Unlock() + delete(nc.legacyDecoders, addr) +} + +// cleanupStaleEntries removes entries that haven't been used recently +func (nc *NetflowCollector) cleanupStaleEntries() { + nc.mu.Lock() + defer nc.mu.Unlock() + + now := time.Now() + + // Cleanup legacy decoders + for addr, entry := range nc.legacyDecoders { + if now.Sub(entry.lastUsed) > cacheTTL { + delete(nc.legacyDecoders, addr) + utils.Logger.Info("Removed stale legacy decoder for %s", addr) + } + } + + // Cleanup template systems + for addr, ts := range nc.templateSystem { + if now.Sub(ts.lastUsed) > cacheTTL { + delete(nc.templateSystem, addr) + utils.Logger.Info("Removed stale template system for %s", addr) + } + } +} + +// --- Packet validation --- + +type netflowPacketInfo struct { + version uint16 + count uint16 + minSize int + versionName string +} + +func validateNetflowPacket(data []byte) (*netflowPacketInfo, error) { + if len(data) < 4 { + return nil, fmt.Errorf("packet too small: %d bytes (minimum 4 bytes for version and count)", len(data)) + } + + version := binary.BigEndian.Uint16(data[0:2]) + count := binary.BigEndian.Uint16(data[2:4]) + + info := &netflowPacketInfo{ + version: version, + count: count, + } + + switch version { + case 1: + info.versionName = "NetFlow v1" + info.minSize = 24 + int(count)*48 + case 5: + info.versionName = "NetFlow v5" + info.minSize = 24 + int(count)*48 + case 6: + info.versionName = "NetFlow v6" + info.minSize = 24 + int(count)*52 + case 7: + info.versionName = "NetFlow v7" + info.minSize = 24 + int(count)*52 + case 9: + info.versionName = "NetFlow v9" + info.minSize = 20 + case 10: + info.versionName = "IPFIX" + info.minSize = 16 + ipfixLength := binary.BigEndian.Uint16(data[2:4]) + if int(ipfixLength) != len(data) { + return nil, fmt.Errorf("IPFIX length mismatch: header says %d bytes, received %d bytes", ipfixLength, len(data)) + } + return info, nil + default: + return nil, fmt.Errorf("unsupported NetFlow version: %d", version) + } + + if len(data) < info.minSize { + return nil, fmt.Errorf("%s packet too small: received %d bytes, minimum expected %d bytes (count=%d)", + info.versionName, len(data), info.minSize, count) + } + + return info, nil +} + diff --git a/agent/collector/netflow/parser.go b/agent/collector/netflow/parser.go new file mode 100644 index 000000000..2a88c50d8 --- /dev/null +++ b/agent/collector/netflow/parser.go @@ -0,0 +1,87 @@ +package netflow + +import ( + "fmt" + "reflect" + "strings" + + goflownetflow "github.com/netsampler/goflow2/decoders/netflow" + "github.com/netsampler/goflow2/decoders/netflowlegacy" + "github.com/tehmaze/netflow/netflow1" + "github.com/tehmaze/netflow/netflow6" + "github.com/tehmaze/netflow/netflow7" + "github.com/threatwinds/go-sdk/entities" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" +) + +// processMessage converts a decoded netflow message to log entries and sends them to the queue. +func processMessage(remote string, message interface{}, queue chan *plugins.Log) error { + var metrics []Metric + + switch m := message.(type) { + // goflow2 types (primary for v5, v9, IPFIX) + case netflowlegacy.PacketNetFlowV5: + metrics = PrepareGoflowV5(remote, &m) + case *netflowlegacy.PacketNetFlowV5: + metrics = PrepareGoflowV5(remote, m) + case goflownetflow.NFv9Packet: + metrics = PrepareGoflowV9(remote, &m) + case *goflownetflow.NFv9Packet: + metrics = PrepareGoflowV9(remote, m) + case goflownetflow.IPFIXPacket: + metrics = PrepareGoflowIPFIX(remote, &m) + case *goflownetflow.IPFIXPacket: + metrics = PrepareGoflowIPFIX(remote, m) + + // tehmaze types (fallback for v1, v6, v7) + case *netflow1.Packet: + metrics = PrepareV1(remote, m) + case *netflow6.Packet: + metrics = PrepareV6(remote, m) + case *netflow7.Packet: + metrics = PrepareV7(remote, m) + + default: + return fmt.Errorf("unknown netflow message type: %T", m) + } + + messages := dumpMetrics(metrics) + + for _, msg := range messages { + message, _, err := entities.ValidateString(msg, false) + if err != nil { + utils.Logger.ErrorF("error validating string: %v: message: %s", err, message) + continue + } + queue <- &plugins.Log{ + DataType: string(config.DataTypeNetflow), + DataSource: remote, + Raw: msg, + } + } + + return nil +} + +// dumpMetrics converts metrics to key-value string format. +func dumpMetrics(metrics []Metric) []string { + var allKVPairs []string + for _, metric := range metrics { + t := reflect.TypeOf(metric) + v := reflect.ValueOf(metric) + var kvPairs []string + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + value := v.Field(i) + if value.String() == "" { + continue + } + header := field.Tag.Get("header") + kvPairs = append(kvPairs, fmt.Sprintf("%s=\"%v\"", header, value)) + } + allKVPairs = append(allKVPairs, strings.Join(kvPairs, " ")) + } + return allKVPairs +} diff --git a/agent/collector/netflow/proto.go b/agent/collector/netflow/proto.go new file mode 100644 index 000000000..322b731fe --- /dev/null +++ b/agent/collector/netflow/proto.go @@ -0,0 +1,158 @@ +package netflow + +import "fmt" + +var Proto = map[string]string{ + "0": "HOPOPT", // + "1": "ICMP", // 1 + "2": "IGMP", // 2 + "3": "GGP", // + "4": "IPv4", // 4 + "5": "ST", // + "6": "TCP", // 6 + "7": "CBT", // + "8": "EGP", // + "9": "IGP", // + "10": "BBN-RCC-MON", // + "11": "NVP-II", // + "12": "PUP", // + "13": "ARGUS", // + "14": "EMCON", // + "15": "XNET", // + "16": "CHAOS", // + "17": "UDP", // 17 + "18": "MUX", // + "19": "DCN-MEAS", // + "20": "HMP", // + "21": "PRM", // + "22": "XNS-IDP", // + "23": "TRUNK-1", // + "24": "TRUNK-2", // + "25": "LEAF-1", // + "26": "LEAF-2", // + "27": "RDP", // + "28": "IRTP", // + "29": "ISO-TP4", // + "30": "NETBLT", // + "31": "MFE-NSP", // + "32": "MERIT-INP", // + "33": "DCCP", // + "34": "3PC", // + "35": "IDPR", // + "36": "XTP", // + "37": "DDP", // + "38": "IDPR-CMTP", // + "39": "TP++", // + "40": "IL", // + "41": "IPv6", // + "42": "SDRP", // + "43": "IPv6-Route", // + "44": "IPv6-Frag", // + "45": "IDRP", // + "46": "RSVP", // + "47": "GRE", // + "48": "DSR", // + "49": "BNA", // + "50": "ESP", // + "51": "AH", // + "52": "I-NLSP", // + "53": "SWIPE", // + "54": "NARP", // + "55": "MOBILE", // + "56": "TLSP", // + "57": "SKIP", // + "58": "ICMP-V6", // 58 + "59": "IPv6-NoNxt", // + "60": "IPv6-Opts", // + //"61": "", // + "62": "CFTP", // + //"63": "", // + "64": "SAT-EXPAK", // + "65": "KRYPTOLAN", // + "66": "RVD", // + "67": "IPPC", // + //"68": "", // + "69": "SAT-MON", // + "70": "VISA", // + "71": "IPCV", // + "72": "CPNX", // + "73": "CPHB", // + "74": "WSN", // + "75": "PVP", // + "76": "BR-SAT-MON", // + "77": "SUN-ND", // + "78": "WB-MON", // + "79": "WB-EXPAK", // + "80": "ISO-IP", // + "81": "VMTP", // + "82": "SECURE-VMTP", // + "83": "VINES", // + //"84": "", // + "84": "TTP-IPTM", // + "85": "NSFNET-IGP", // + "86": "DGP", // + "87": "TCF", // + "88": "EIGRP", // + "89": "OSPFIGP", // + "90": "Sprite-RPC", // + "91": "LARP", // + "92": "MTP", // + "93": "AX.25", // + "94": "IPIP", // + "95": "MICP", // + "96": "SCC-SP", // + "97": "ETHERIP", // + "98": "ENCAP", // + //"99": "", // + "100": "GMTP", // + "101": "IFMP", // + "102": "PNNI", // + "103": "PIM", // + "104": "ARIS", // + "105": "SCPS", // + "106": "QNX", // + "107": "A/N", // + "108": "IPComp", // + "109": "SNP", // + "110": "Compaq-Peer", // + "111": "IPX-in-IP", // + "112": "VRRP", // + "113": "PGM", // + //"114": "", // + "115": "L2TP", // + "116": "DDX", // + "117": "IATP", // + "118": "STP", // + "119": "SRP", // + "120": "UTI", // + "121": "SMP", // + "122": "SM", // + "123": "PTP", // + "124": "ISIS over IPv4", // + "125": "FIRE", // + "126": "CRTP", // + "127": "CRUDP", // + "128": "SSCOPMCE", // + "129": "IPLT", // + "130": "SPS", // + "131": "PIPE", // + "132": "SCTP", // + "133": "FC", // + "134": "RSVP-E2E-IGNORE", // + "135": "Mobility-Header", // + "136": "UDPLite", // + "137": "MPLS-in-IP", // + "138": "MANET", // + "139": "HIP", // + "140": "Shim6", // + "141": "WESP", // + "142": "ROHC", // +} + +// ProtoToName - Get Protocol Id and return Protocol Name +func ProtoToName(p string) string { + if v, ok := Proto[p]; ok { + return v + } + return fmt.Sprintf("/%v", p) +} diff --git a/agent/collector/netflow/tehmaze.go b/agent/collector/netflow/tehmaze.go new file mode 100644 index 000000000..264332303 --- /dev/null +++ b/agent/collector/netflow/tehmaze.go @@ -0,0 +1,94 @@ +package netflow + +import ( + "fmt" + "net" + "time" + + "github.com/tehmaze/netflow/netflow1" + "github.com/tehmaze/netflow/netflow6" + "github.com/tehmaze/netflow/netflow7" +) + +// PrepareV1 converts tehmaze NetFlow v1 packet to metrics. +func PrepareV1(addr string, p *netflow1.Packet) []Metric { + nfExporter, _, _ := net.SplitHostPort(addr) + var metrics []Metric + + for _, r := range p.Records { + met := Metric{OutBytes: "0", InBytes: "0", OutPacket: "0", InPacket: "0", NFSender: nfExporter} + met.FlowVersion = "Netflow-V1" + met.First = time.Unix(int64(r.First), 0).Format(time.RFC3339Nano) + met.Last = time.Unix(int64(r.Last), 0).Format(time.RFC3339Nano) + met.Protocol = ProtoToName(fmt.Sprintf("%v", r.Protocol)) + met.Bytes = fmt.Sprintf("%v", r.Bytes) + met.Packets = fmt.Sprintf("%v", r.Packets) + met.TCPFlags = fmt.Sprintf("%v", r.Flags) + met.NextHop = fmt.Sprintf("%v", r.NextHop) + met.SrcIP = fmt.Sprintf("%v", r.SrcAddr) + met.DstIP = fmt.Sprintf("%v", r.DstAddr) + met.SrcPort = fmt.Sprintf("%v", r.SrcPort) + met.DstPort = fmt.Sprintf("%v", r.DstPort) + metrics = append(metrics, met) + } + + return metrics +} + +// PrepareV6 converts tehmaze NetFlow v6 packet to metrics. +func PrepareV6(addr string, p *netflow6.Packet) []Metric { + nfExporter, _, _ := net.SplitHostPort(addr) + var metrics []Metric + + for _, r := range p.Records { + met := Metric{OutBytes: "0", InBytes: "0", OutPacket: "0", InPacket: "0", NFSender: nfExporter} + met.FlowVersion = "Netflow-V6" + met.First = time.Unix(int64(r.First), 0).Format(time.RFC3339Nano) + met.Last = time.Unix(int64(r.Last), 0).Format(time.RFC3339Nano) + met.Protocol = ProtoToName(fmt.Sprintf("%v", r.Protocol)) + met.Bytes = fmt.Sprintf("%v", r.Bytes) + met.Packets = fmt.Sprintf("%v", r.Packets) + met.TCPFlags = fmt.Sprintf("%v", r.TCPFlags) + met.SrcAs = fmt.Sprintf("%v", r.SrcAS) + met.DstAs = fmt.Sprintf("%v", r.DstAS) + met.SrcMask = fmt.Sprintf("%v", r.SrcMask) + met.DstMask = fmt.Sprintf("%v", r.DstMask) + met.NextHop = fmt.Sprintf("%v", r.NextHop) + met.SrcIP = fmt.Sprintf("%v", r.SrcAddr) + met.DstIP = fmt.Sprintf("%v", r.DstAddr) + met.SrcPort = fmt.Sprintf("%v", r.SrcPort) + met.DstPort = fmt.Sprintf("%v", r.DstPort) + metrics = append(metrics, met) + } + + return metrics +} + +// PrepareV7 converts tehmaze NetFlow v7 packet to metrics. +func PrepareV7(addr string, p *netflow7.Packet) []Metric { + nfExporter, _, _ := net.SplitHostPort(addr) + var metrics []Metric + + for _, r := range p.Records { + met := Metric{OutBytes: "0", InBytes: "0", OutPacket: "0", InPacket: "0", NFSender: nfExporter} + met.FlowVersion = "Netflow-V7" + met.First = time.Unix(int64(r.First), 0).Format(time.RFC3339Nano) + met.Last = time.Unix(int64(r.Last), 0).Format(time.RFC3339Nano) + met.Protocol = ProtoToName(fmt.Sprintf("%v", r.Protocol)) + met.Bytes = fmt.Sprintf("%v", r.Bytes) + met.Packets = fmt.Sprintf("%v", r.Packets) + met.TCPFlags = fmt.Sprintf("%v", r.TCPFlags) + met.SrcAs = fmt.Sprintf("%v", r.SrcAS) + met.DstAs = fmt.Sprintf("%v", r.DstAS) + met.SrcMask = fmt.Sprintf("%v", r.SrcMask) + met.DstMask = fmt.Sprintf("%v", r.DstMask) + met.NextHop = fmt.Sprintf("%v", r.NextHop) + met.SrcIP = fmt.Sprintf("%v", r.SrcAddr) + met.DstIP = fmt.Sprintf("%v", r.DstAddr) + met.SrcPort = fmt.Sprintf("%v", r.SrcPort) + met.DstPort = fmt.Sprintf("%v", r.DstPort) + metrics = append(metrics, met) + } + + return metrics +} diff --git a/agent/collector/platform/darwin.go b/agent/collector/platform/darwin.go new file mode 100644 index 000000000..12ec935e8 --- /dev/null +++ b/agent/collector/platform/darwin.go @@ -0,0 +1,168 @@ +//go:build darwin +// +build darwin + +package platform + +import ( + "bufio" + "context" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/threatwinds/go-sdk/entities" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" +) + +const ( + maxRestartDelay = 5 * time.Minute + baseRestartDelay = 5 * time.Second +) + +type Darwin struct{} + +func GetCollectors() []Collector { + return []Collector{Darwin{}} +} + +func (d Darwin) Name() string { + return "darwin" +} + +func (d Darwin) Start(ctx context.Context, queue chan *plugins.Log) { + path := fs.GetExecutablePath() + collectorPath := filepath.Join(path, "utmstack-collector-mac") + + // Verify binary exists before attempting to run + if _, err := os.Stat(collectorPath); os.IsNotExist(err) { + utils.Logger.ErrorF("macOS collector binary not found at: %s", collectorPath) + return + } + + host, err := os.Hostname() + if err != nil { + utils.Logger.ErrorF("error getting hostname: %v", err) + host = "unknown" + } + + restartDelay := baseRestartDelay + + // Restart loop with exponential backoff + for { + select { + case <-ctx.Done(): + utils.Logger.Info("macOS collector stopping due to context cancellation") + return + default: + } + + exitCode := d.runCollector(collectorPath, host, queue) + + if exitCode == 0 { + utils.Logger.Info("macOS collector exited normally") + } else { + utils.Logger.ErrorF("macOS collector exited with code %d, restarting in %v", exitCode, restartDelay) + } + + time.Sleep(restartDelay) + + // Exponential backoff up to max delay + restartDelay *= 2 + if restartDelay > maxRestartDelay { + restartDelay = maxRestartDelay + } + } +} + +func (d Darwin) runCollector(collectorPath, host string, queue chan *plugins.Log) int { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in macOS collector: %v", r) + } + }() + + cmd := exec.Command(collectorPath) + + stdout, err := cmd.StdoutPipe() + if err != nil { + utils.Logger.ErrorF("error creating stdout pipe: %v", err) + return -1 + } + + stderr, err := cmd.StderrPipe() + if err != nil { + utils.Logger.ErrorF("error creating stderr pipe: %v", err) + return -1 + } + + if err := cmd.Start(); err != nil { + utils.Logger.ErrorF("error starting macOS collector: %v", err) + return -1 + } + + utils.Logger.Info("macOS collector started successfully") + + go func() { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in stdout reader: %v", r) + } + }() + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + logLine := scanner.Text() + utils.Logger.LogF(100, "output: %s", logLine) + + validatedLog, _, err := entities.ValidateString(logLine, false) + if err != nil { + utils.Logger.ErrorF("error validating log: %s: %v", logLine, err) + continue + } + + queue <- &plugins.Log{ + DataType: string(config.DataTypeMacOs), + DataSource: host, + Raw: validatedLog, + } + } + + if err := scanner.Err(); err != nil { + utils.Logger.ErrorF("error reading stdout: %v", err) + } + }() + + go func() { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in stderr reader: %v", r) + } + }() + + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + errLine := scanner.Text() + utils.Logger.ErrorF("collector error: %s", errLine) + } + + if err := scanner.Err(); err != nil { + utils.Logger.ErrorF("error reading stderr: %v", err) + } + }() + + if err := cmd.Wait(); err != nil { + utils.Logger.ErrorF("macOS collector process ended with error: %v", err) + if exitErr, ok := err.(*exec.ExitError); ok { + return exitErr.ExitCode() + } + return -1 + } + + return 0 +} + +func (d Darwin) Stop() {} diff --git a/agent/collector/platform/filebeat_amd64.go b/agent/collector/platform/filebeat_amd64.go new file mode 100644 index 000000000..0b829ebe7 --- /dev/null +++ b/agent/collector/platform/filebeat_amd64.go @@ -0,0 +1,201 @@ +package platform + +import ( + "context" + "fmt" + "os" + "path/filepath" + "regexp" + "runtime" + + "github.com/threatwinds/go-sdk/entities" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" +) + +// beatsRegexps maps data types to their identifying regex patterns. +var beatsRegexps = map[config.DataType]*regexp.Regexp{ + config.DataTypeApacheModule: regexp.MustCompile(`"type":"apache"|"module":"apache"`), + config.DataTypeLinuxAuditdModule: regexp.MustCompile(`"type":"auditd"|"module":"auditd"`), + config.DataTypeElasticsearchModule: regexp.MustCompile(`"type":"elasticsearch"|"module":"elasticsearch"`), + config.DataTypeKafkaModule: regexp.MustCompile(`"type":"kafka"|"module":"kafka"`), + config.DataTypeKibanaModule: regexp.MustCompile(`"type":"kibana"|"module":"kibana"`), + config.DataTypeLogstashModule: regexp.MustCompile(`"type":"logstash"|"module":"logstash"`), + config.DataTypeMongodbModule: regexp.MustCompile(`"type":"mongodb"|"module":"mongodb"`), + config.DataTypeMysqlModule: regexp.MustCompile(`"type":"mysql"|"module":"mysql"`), + config.DataTypeNginxModule: regexp.MustCompile(`"type":"nginx"|"module":"nginx"`), + config.DataTypeOsqueryModule: regexp.MustCompile(`"type":"osquery"|"module":"osquery"`), + config.DataTypePostgresqlModule: regexp.MustCompile(`"type":"postgresql"|"module":"postgresql"`), + config.DataTypeRedisModule: regexp.MustCompile(`"type":"redis"|"module":"redis"`), + config.DataTypeLinuxAgent: regexp.MustCompile(`"type":"system"|"module":"system"`), + config.DataTypeIisModule: regexp.MustCompile(`"type":"iis"|"module":"iis"`), + config.DataTypeTraefikModule: regexp.MustCompile(`"type":"traefik"|"module":"traefik"`), + config.DataTypeNatsModule: regexp.MustCompile(`"type":"nats"|"module":"nats"`), + config.DataTypeHaproxyModule: regexp.MustCompile(`"type":"haproxy"|"module":"haproxy"`), +} + +// identifyBeatsSource determines the data type of a log message. +func identifyBeatsSource(log string) config.DataType { + for dataType, re := range beatsRegexps { + if re.MatchString(log) { + return dataType + } + } + return config.DataTypeGeneric +} + +type Filebeat struct{} + +func (f Filebeat) Name() string { + return "filebeat" +} + +func (f Filebeat) Install() error { + path := fs.GetExecutablePath() + + filebLogPath := filepath.Join(path, "beats", "filebeat") + beatConfig := CollectorConfig{ + LogsPath: filepath.Join(filebLogPath, "logs"), + LogFileName: "modulescollector", + } + + if isInstalled, err := utils.CheckIfServiceIsInstalled(config.ModulesServName); err != nil { + return fmt.Errorf("error checking if %s service is installed: %v", config.ModulesServName, err) + } else if !isInstalled { + if err = fs.CreateDirIfNotExist(beatConfig.LogsPath); err != nil { + return fmt.Errorf("error creating %s folder", beatConfig.LogsPath) + } + + configFile := filepath.Join(filebLogPath, "filebeat.yml") + templateFile := filepath.Join(path, "templates", "filebeat.yml") + if err = utils.GenerateFromTemplate(beatConfig, templateFile, configFile); err != nil { + return fmt.Errorf("error configuration from %s: %v", templateFile, err) + } + switch runtime.GOOS { + case "windows": + err = exec.Run("sc", + filebLogPath, + "create", + config.ModulesServName, + "binPath=", + fmt.Sprintf("\"%s\\filebeat.exe\" --environment=windows_service -c \"%s\\filebeat.yml\" --path.home \"%s\" --path.data \"C:\\ProgramData\\filebeat\" --path.logs \"C:\\ProgramData\\filebeat\\logs\" -E logging.files.redirect_stderr=true", filebLogPath, filebLogPath, filebLogPath), + "DisplayName=", + config.ModulesServName, + "start=", + "auto") + if err != nil { + return fmt.Errorf("error installing %s service: %s", config.ModulesServName, err) + } + + err = exec.Run("sc", filebLogPath, "start", config.ModulesServName) + if err != nil { + return fmt.Errorf("error starting %s service: %s", config.ModulesServName, err) + } + case "linux": + if err = utils.CreateLinuxService(config.ModulesServName, fmt.Sprintf( + "%s -c %s -path.home %s -path.config %s -path.data /var/lib/filebeat -path.logs /var/log/filebeat", + filepath.Join(filebLogPath, "filebeat"), + filepath.Join(filebLogPath, "filebeat.yml"), + filebLogPath, + filebLogPath, + ), + ); err != nil { + return fmt.Errorf("error creating %s service: %v", config.ModulesServName, err) + } + + if err = exec.Run("chmod", filebLogPath, "-R", "755", "filebeat"); err != nil { + return fmt.Errorf("error executing chmod: %v", err) + } + + if err = exec.Run("systemctl", filebLogPath, "daemon-reload"); err != nil { + return fmt.Errorf("error reloading daemon: %v", err) + } + + err := exec.Run("systemctl", filebLogPath, "enable", config.ModulesServName) + if err != nil { + return fmt.Errorf("%s", err) + } + + err = exec.Run("systemctl", filebLogPath, "start", config.ModulesServName) + if err != nil { + return fmt.Errorf("%s", err) + } + + err = exec.Run("./filebeat", filebLogPath, "modules", "enable", "system") + if err != nil { + return fmt.Errorf("%s", err) + } + + err = exec.Run("sed", filepath.Join(filebLogPath, "modules.d"), "-i", "s/enabled: false/enabled: true/g", "system.yml") + if err != nil { + return fmt.Errorf("%s", err) + } + + err = exec.Run("systemctl", filebLogPath, "restart", config.ModulesServName) + if err != nil { + return fmt.Errorf("%s", err) + } + } + } + + return nil +} + +func (f Filebeat) Start(ctx context.Context, queue chan *plugins.Log) { + logLinesChan := make(chan string) + path := fs.GetExecutablePath() + filebLogPath := filepath.Join(path, "beats", "filebeat", "logs") + host, err := os.Hostname() + if err != nil { + utils.Logger.ErrorF("error getting hostname: %v", err) + host = "unknown" + } + + go utils.WatchFolder("modulescollector", filebLogPath, logLinesChan) + + for { + select { + case <-ctx.Done(): + utils.Logger.Info("filebeat collector stopping due to context cancellation") + return + case logLine, ok := <-logLinesChan: + if !ok { + return + } + message, _, err := entities.ValidateString(logLine, false) + if err != nil { + utils.Logger.ErrorF("error validating string: %v: message: %s", err, message) + continue + } + + dataType := identifyBeatsSource(logLine) + queue <- &plugins.Log{ + DataType: string(dataType), + DataSource: host, + Raw: logLine, + } + } + } +} + +func (f Filebeat) Uninstall() error { + if isInstalled, err := utils.CheckIfServiceIsInstalled(config.ModulesServName); err != nil { + return fmt.Errorf("error checking if %s is running: %v", config.ModulesServName, err) + } else if isInstalled { + err = utils.StopService(config.ModulesServName) + if err != nil { + return fmt.Errorf("error stopping %s: %v", config.ModulesServName, err) + } + + err = utils.UninstallService(config.ModulesServName) + if err != nil { + return fmt.Errorf("error uninstalling %s: %v", config.ModulesServName, err) + } + } + return nil +} + +func (f Filebeat) Stop() {} diff --git a/agent/collector/platform/linux_amd64.go b/agent/collector/platform/linux_amd64.go new file mode 100644 index 000000000..673319dce --- /dev/null +++ b/agent/collector/platform/linux_amd64.go @@ -0,0 +1,162 @@ +//go:build linux && amd64 +// +build linux,amd64 + +package platform + +import ( + "bufio" + "context" + "os" + "os/exec" + "sync" + "time" + + "github.com/threatwinds/go-sdk/entities" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" +) + +const ( + journaldRestartDelay = 5 * time.Second + journaldMaxRestartDelay = 5 * time.Minute +) + +type LinuxSystem struct { + cmd *exec.Cmd + cancel context.CancelFunc + mu sync.Mutex +} + +func (l *LinuxSystem) Name() string { + return "linux-system" +} + +func (l *LinuxSystem) Start(ctx context.Context, queue chan *plugins.Log) { + host, err := os.Hostname() + if err != nil { + utils.Logger.ErrorF("error getting hostname: %v", err) + host = "unknown" + } + + restartDelay := journaldRestartDelay + + for { + select { + case <-ctx.Done(): + utils.Logger.Info("Linux system collector stopping due to context cancellation") + return + default: + } + + exitCode := l.runJournalctl(ctx, host, queue) + + if exitCode == 0 { + utils.Logger.Info("journalctl exited normally") + } else { + utils.Logger.ErrorF("journalctl exited with code %d, restarting in %v", exitCode, restartDelay) + } + + select { + case <-ctx.Done(): + return + case <-time.After(restartDelay): + } + + // Exponential backoff + restartDelay *= 2 + if restartDelay > journaldMaxRestartDelay { + restartDelay = journaldMaxRestartDelay + } + } +} + +func (l *LinuxSystem) runJournalctl(ctx context.Context, host string, queue chan *plugins.Log) int { + l.mu.Lock() + cmdCtx, cancel := context.WithCancel(ctx) + l.cancel = cancel + + // journalctl -f: follow, -o json: JSON output, --no-pager: don't use pager + l.cmd = exec.CommandContext(cmdCtx, "journalctl", "-f", "-o", "json", "--no-pager") + + stdout, err := l.cmd.StdoutPipe() + if err != nil { + l.mu.Unlock() + utils.Logger.ErrorF("error creating journalctl stdout pipe: %v", err) + return -1 + } + + stderr, err := l.cmd.StderrPipe() + if err != nil { + l.mu.Unlock() + utils.Logger.ErrorF("error creating journalctl stderr pipe: %v", err) + return -1 + } + + if err := l.cmd.Start(); err != nil { + l.mu.Unlock() + utils.Logger.ErrorF("error starting journalctl: %v", err) + return -1 + } + l.mu.Unlock() + + utils.Logger.Info("Linux system log collector started (journald)") + + // Read stderr in background + go func() { + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + utils.Logger.ErrorF("journalctl error: %s", scanner.Text()) + } + }() + + // Read stdout (log entries) + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + + validatedLog, _, err := entities.ValidateString(line, false) + if err != nil { + utils.Logger.ErrorF("error validating log: %v", err) + continue + } + + queue <- &plugins.Log{ + DataType: string(config.DataTypeLinuxAgent), + DataSource: host, + Raw: validatedLog, + } + } + + if err := scanner.Err(); err != nil { + utils.Logger.ErrorF("error reading journalctl output: %v", err) + } + + if err := l.cmd.Wait(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + return exitErr.ExitCode() + } + return -1 + } + + return 0 +} + +func (l *LinuxSystem) Stop() { + l.mu.Lock() + defer l.mu.Unlock() + + if l.cancel != nil { + l.cancel() + } + if l.cmd != nil && l.cmd.Process != nil { + l.cmd.Process.Kill() + } +} + +func GetCollectors() []Collector { + return []Collector{ + &LinuxSystem{}, + // Filebeat{}, // TODO: remove after testing native collector + } +} diff --git a/agent/collector/platform/linux_arm64.go b/agent/collector/platform/linux_arm64.go new file mode 100644 index 000000000..1d8e43926 --- /dev/null +++ b/agent/collector/platform/linux_arm64.go @@ -0,0 +1,161 @@ +//go:build linux && arm64 +// +build linux,arm64 + +package platform + +import ( + "bufio" + "context" + "os" + "os/exec" + "sync" + "time" + + "github.com/threatwinds/go-sdk/entities" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" +) + +const ( + journaldRestartDelayArm64 = 5 * time.Second + journaldMaxRestartDelayArm64 = 5 * time.Minute +) + +type LinuxSystemArm64 struct { + cmd *exec.Cmd + cancel context.CancelFunc + mu sync.Mutex +} + +func (l *LinuxSystemArm64) Name() string { + return "linux-system" +} + +func (l *LinuxSystemArm64) Start(ctx context.Context, queue chan *plugins.Log) { + host, err := os.Hostname() + if err != nil { + utils.Logger.ErrorF("error getting hostname: %v", err) + host = "unknown" + } + + restartDelay := journaldRestartDelayArm64 + + for { + select { + case <-ctx.Done(): + utils.Logger.Info("Linux system collector stopping due to context cancellation") + return + default: + } + + exitCode := l.runJournalctl(ctx, host, queue) + + if exitCode == 0 { + utils.Logger.Info("journalctl exited normally") + } else { + utils.Logger.ErrorF("journalctl exited with code %d, restarting in %v", exitCode, restartDelay) + } + + select { + case <-ctx.Done(): + return + case <-time.After(restartDelay): + } + + // Exponential backoff + restartDelay *= 2 + if restartDelay > journaldMaxRestartDelayArm64 { + restartDelay = journaldMaxRestartDelayArm64 + } + } +} + +func (l *LinuxSystemArm64) runJournalctl(ctx context.Context, host string, queue chan *plugins.Log) int { + l.mu.Lock() + cmdCtx, cancel := context.WithCancel(ctx) + l.cancel = cancel + + // journalctl -f: follow, -o json: JSON output, --no-pager: don't use pager + l.cmd = exec.CommandContext(cmdCtx, "journalctl", "-f", "-o", "json", "--no-pager") + + stdout, err := l.cmd.StdoutPipe() + if err != nil { + l.mu.Unlock() + utils.Logger.ErrorF("error creating journalctl stdout pipe: %v", err) + return -1 + } + + stderr, err := l.cmd.StderrPipe() + if err != nil { + l.mu.Unlock() + utils.Logger.ErrorF("error creating journalctl stderr pipe: %v", err) + return -1 + } + + if err := l.cmd.Start(); err != nil { + l.mu.Unlock() + utils.Logger.ErrorF("error starting journalctl: %v", err) + return -1 + } + l.mu.Unlock() + + utils.Logger.Info("Linux system log collector started (journald)") + + // Read stderr in background + go func() { + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + utils.Logger.ErrorF("journalctl error: %s", scanner.Text()) + } + }() + + // Read stdout (log entries) + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + + validatedLog, _, err := entities.ValidateString(line, false) + if err != nil { + utils.Logger.ErrorF("error validating log: %v", err) + continue + } + + queue <- &plugins.Log{ + DataType: string(config.DataTypeLinuxAgent), + DataSource: host, + Raw: validatedLog, + } + } + + if err := scanner.Err(); err != nil { + utils.Logger.ErrorF("error reading journalctl output: %v", err) + } + + if err := l.cmd.Wait(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + return exitErr.ExitCode() + } + return -1 + } + + return 0 +} + +func (l *LinuxSystemArm64) Stop() { + l.mu.Lock() + defer l.mu.Unlock() + + if l.cancel != nil { + l.cancel() + } + if l.cmd != nil && l.cmd.Process != nil { + l.cmd.Process.Kill() + } +} + +func GetCollectors() []Collector { + return []Collector{ + &LinuxSystemArm64{}, + } +} diff --git a/agent/collector/platform/platform.go b/agent/collector/platform/platform.go new file mode 100644 index 000000000..bb8a00383 --- /dev/null +++ b/agent/collector/platform/platform.go @@ -0,0 +1,20 @@ +package platform + +import ( + "context" + + "github.com/threatwinds/go-sdk/plugins" +) + +// CollectorConfig holds paths for log collection. +type CollectorConfig struct { + LogsPath string + LogFileName string +} + +// Collector is the interface that platform collectors must implement. +type Collector interface { + Name() string + Start(ctx context.Context, queue chan *plugins.Log) + Stop() +} diff --git a/agent/collector/platform/windows_amd64.go b/agent/collector/platform/windows_amd64.go new file mode 100644 index 000000000..5d3a9b61c --- /dev/null +++ b/agent/collector/platform/windows_amd64.go @@ -0,0 +1,415 @@ +//go:build windows && amd64 +// +build windows,amd64 + +package platform + +import ( + "context" + "encoding/json" + "encoding/xml" + "fmt" + "log" + "os" + "strconv" + "strings" + "sync" + "syscall" + "time" + "unsafe" + + "github.com/threatwinds/go-sdk/entities" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" + "golang.org/x/sys/windows" +) + +type Event struct { + XMLName xml.Name `xml:"Event"` + System SystemData `xml:"System"` + EventData []*EventData `xml:"EventData>Data"` +} + +type EventData struct { + Key string `xml:"Name,attr"` + Value string `xml:",chardata"` +} + +type ProviderData struct { + ProviderName string `xml:"Name,attr"` + ProviderGUID string `xml:"Guid,attr"` +} + +type TimeCreatedData struct { + SystemTime string `xml:"SystemTime,attr"` +} + +type CorrelationData struct { + ActivityID string `xml:"ActivityID,attr"` +} + +type ExecutionData struct { + ProcessID int `xml:"ProcessID,attr"` + ThreadID int `xml:"ThreadID,attr"` +} + +type SecurityData struct{} + +type SystemData struct { + Provider ProviderData `xml:"Provider"` + EventID int `xml:"EventID"` + Version int `xml:"Version"` + Level int `xml:"Level"` + Task int `xml:"Task"` + Opcode int `xml:"Opcode"` + Keywords string `xml:"Keywords"` + TimeCreated TimeCreatedData `xml:"TimeCreated"` + EventRecordID int64 `xml:"EventRecordID"` + Correlation CorrelationData `xml:"Correlation"` + Execution ExecutionData `xml:"Execution"` + Channel string `xml:"Channel"` + Computer string `xml:"Computer"` + Security SecurityData `xml:"Security"` +} + +type EventSubscription struct { + Channel string + Query string + Errors chan error + winAPIHandle windows.Handle + + mu sync.Mutex + running bool +} + +const ( + EvtSubscribeToFutureEvents = 1 + evtSubscribeActionError = 0 + evtSubscribeActionDeliver = 1 + evtRenderEventXML = 1 +) + +var ( + modwevtapi = windows.NewLazySystemDLL("wevtapi.dll") + procEvtSubscribe = modwevtapi.NewProc("EvtSubscribe") + procEvtRender = modwevtapi.NewProc("EvtRender") + procEvtClose = modwevtapi.NewProc("EvtClose") + incomingEvents = make(chan string, 1024) +) + +func (evtSub *EventSubscription) Create() error { + evtSub.mu.Lock() + defer evtSub.mu.Unlock() + + if evtSub.winAPIHandle != 0 { + return fmt.Errorf("windows_events: subscription has already been created") + } + + winChannel, err := windows.UTF16PtrFromString(evtSub.Channel) + if err != nil { + return fmt.Errorf("windows_events: invalid channel name: %s", err) + } + + winQuery, err := windows.UTF16PtrFromString(evtSub.Query) + if err != nil { + return fmt.Errorf("windows_events: invalid query: %s", err) + } + + callback := syscall.NewCallback(evtSub.winAPICallback) + + log.Printf("Debug - Subscribing to channel: %s", evtSub.Channel) + + handle, _, err := procEvtSubscribe.Call( + 0, + 0, + uintptr(unsafe.Pointer(winChannel)), + uintptr(unsafe.Pointer(winQuery)), + 0, + 0, + callback, + uintptr(EvtSubscribeToFutureEvents), + ) + + if handle == 0 { + return fmt.Errorf("windows_events: failed to subscribe to events: %v", err) + } + + evtSub.winAPIHandle = windows.Handle(handle) + return nil +} + +func (evtSub *EventSubscription) Close() error { + evtSub.mu.Lock() + defer evtSub.mu.Unlock() + + if evtSub.winAPIHandle == 0 { + return fmt.Errorf("windows_events: no active subscription to close") + } + ret, _, err := procEvtClose.Call(uintptr(evtSub.winAPIHandle)) + if ret == 0 { + return fmt.Errorf("windows_events: error closing handle: %s", err) + } + evtSub.winAPIHandle = 0 + return nil +} + +func (evtSub *EventSubscription) winAPICallback(action, userContext, event uintptr) uintptr { + switch action { + case evtSubscribeActionError: + err := fmt.Errorf("windows_events: error in callback, code: %x", uint16(event)) + evtSub.Errors <- err + + go func(channel string) { + utils.Logger.LogF(100, "Attempting to resubscribe to channel: %s after error: %v", channel, err) + evtSub.mu.Lock() + defer evtSub.mu.Unlock() + + _ = evtSub.Close() + + for { + time.Sleep(5 * time.Second) + if err := evtSub.Create(); err != nil { + utils.Logger.ErrorF("Retry failed for channel %s: %s", channel, err) + } else { + utils.Logger.LogF(100, "Resubscribed to channel: %s", channel) + break + } + } + }(evtSub.Channel) + + case evtSubscribeActionDeliver: + utils.Logger.LogF(100, "Received event from channel: %s", evtSub.Channel) + xmlStr, err := quickRenderXML(event) + if err != nil { + evtSub.Errors <- fmt.Errorf("render in callback: %v", err) + break + } + select { + case incomingEvents <- xmlStr: + default: + utils.Logger.ErrorF("incomingEvents full: event discarded") + } + default: + evtSub.Errors <- fmt.Errorf("windows_events: unsupported action in callback: %x", uint16(action)) + } + return 0 +} + +func quickRenderXML(h uintptr) (string, error) { + bufSize := uint32(4096) + for { + space := make([]uint16, bufSize/2) + used := uint32(0) + prop := uint32(0) + + ret, _, err := procEvtRender.Call( + 0, h, evtRenderEventXML, + uintptr(bufSize), + uintptr(unsafe.Pointer(&space[0])), + uintptr(unsafe.Pointer(&used)), + uintptr(unsafe.Pointer(&prop)), + ) + if ret == 0 { + if err == windows.ERROR_INSUFFICIENT_BUFFER { + bufSize *= 2 + continue + } + return "", err + } + return cleanXML(windows.UTF16ToString(space)), nil + } +} + +func cleanXML(xmlStr string) string { + xmlStr = strings.TrimSpace(xmlStr) + if idx := strings.Index(xmlStr, " 0 { + xmlStr = xmlStr[idx:] + } + xmlStr = strings.Map(func(r rune) rune { + if r < 32 && r != '\n' && r != '\r' && r != '\t' { + return -1 + } + return r + }, xmlStr) + return xmlStr +} + +type Windows struct { + stopChan chan struct{} + subscriptions []*EventSubscription + mu sync.Mutex +} + +var windowsCollector = &Windows{ + stopChan: make(chan struct{}), +} + +func GetCollectors() []Collector { + return []Collector{ + windowsCollector, + // Filebeat{}, // TODO: remove after testing native collector + } +} + +func (w *Windows) Name() string { + return "windows-amd64" +} + +func (w *Windows) Start(ctx context.Context, queue chan *plugins.Log) { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in Windows AMD64 collector: %v", r) + } + }() + + errorsChan := make(chan error, 10) + go eventWorker(queue) + + channels := []string{ + "Security", "Application", "System", "Windows Powershell", "Microsoft-Windows-Powershell/Operational", "ForwardedEvents", + "Microsoft-Windows-WinLogon/Operational", "Microsoft-Windows-Windows Firewall With Advanced Security/Firewall", + "Microsoft-Windows-Windows Defender/Operational", + } + + w.mu.Lock() + w.subscriptions = nil + for _, channel := range channels { + sub := &EventSubscription{ + Channel: channel, + Query: "*", + Errors: errorsChan, + } + if err := sub.Create(); err != nil { + utils.Logger.ErrorF("Error subscribing to channel %s: %s", channel, err) + continue + } + w.subscriptions = append(w.subscriptions, sub) + utils.Logger.LogF(100, "Subscribed to channel: %s", channel) + } + w.mu.Unlock() + + go func() { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in error handler: %v", r) + } + }() + for err := range errorsChan { + utils.Logger.ErrorF("Subscription error: %s", err) + } + }() + + // Block until context is cancelled or Stop() is called + select { + case <-ctx.Done(): + case <-w.stopChan: + } + + utils.Logger.Info("Windows AMD64 collector stopping...") + w.mu.Lock() + for _, sub := range w.subscriptions { + if err := sub.Close(); err != nil { + utils.Logger.ErrorF("Error closing subscription for %s: %v", sub.Channel, err) + } + } + w.subscriptions = nil + w.mu.Unlock() + utils.Logger.Info("Windows AMD64 collector stopped.") +} + +func eventWorker(queue chan *plugins.Log) { + host, err := os.Hostname() + if err != nil { + utils.Logger.ErrorF("error getting hostname: %v", err) + host = "unknown" + } + + for xmlStr := range incomingEvents { + ev := new(Event) + if err := xml.Unmarshal([]byte(xmlStr), ev); err != nil { + utils.Logger.ErrorF("unmarshal error: %v", err) + continue + } + + eventJSON, err := convertEventToJSON(ev) + if err != nil { + utils.Logger.ErrorF("toJSON error: %v", err) + continue + } + + validatedLog, _, err := entities.ValidateString(eventJSON, false) + if err != nil { + utils.Logger.LogF(100, "validation error: %s: %v", eventJSON, err) + continue + } + + select { + case queue <- &plugins.Log{ + DataSource: host, + DataType: string(config.DataTypeWindowsAgent), + Raw: validatedLog, + }: + default: + utils.Logger.LogF(100, "LogQueue full: event discarded") + } + } +} + +func convertEventToJSON(event *Event) (string, error) { + eventMap := map[string]interface{}{ + "timestamp": event.System.TimeCreated.SystemTime, + "provider_name": event.System.Provider.ProviderName, + "provider_guid": event.System.Provider.ProviderGUID, + "eventCode": event.System.EventID, + "version": event.System.Version, + "level": event.System.Level, + "task": event.System.Task, + "opcode": event.System.Opcode, + "keywords": event.System.Keywords, + "timeCreated": event.System.TimeCreated.SystemTime, + "recordId": event.System.EventRecordID, + "correlation": event.System.Correlation, + "execution": event.System.Execution, + "channel": event.System.Channel, + "computer": event.System.Computer, + "data": make(map[string]interface{}), + } + + dataMap := eventMap["data"].(map[string]interface{}) + for _, data := range event.EventData { + if strings.HasPrefix(data.Value, "0x") { + if val, err := strconv.ParseInt(data.Value[2:], 16, 64); err == nil { + dataMap[data.Key] = val + continue + } + } + if data.Key != "" { + value := strings.TrimSpace(data.Value) + if value != "" { + dataMap[data.Key] = value + } + } + } + + jsonBytes, err := json.Marshal(eventMap) + if err != nil { + return "", err + } + return string(jsonBytes), nil +} + +func (w *Windows) Install() error { + return nil +} + +func (w *Windows) Uninstall() error { + return nil +} + +func (w *Windows) Stop() { + select { + case w.stopChan <- struct{}{}: + default: + // Already stopped or not started + } +} diff --git a/agent/collectors/windows_arm64.go b/agent/collector/platform/windows_arm64.go similarity index 86% rename from agent/collectors/windows_arm64.go rename to agent/collector/platform/windows_arm64.go index 5a844926e..8b31d70c3 100644 --- a/agent/collectors/windows_arm64.go +++ b/agent/collector/platform/windows_arm64.go @@ -1,15 +1,15 @@ //go:build windows && arm64 // +build windows,arm64 -package collectors +package platform import ( + "context" "encoding/json" "encoding/xml" "fmt" "log" "os" - "os/signal" "strconv" "strings" "sync" @@ -20,7 +20,6 @@ import ( "github.com/threatwinds/go-sdk/entities" "github.com/threatwinds/go-sdk/plugins" "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/logservice" "github.com/utmstack/UTMStack/agent/utils" "golang.org/x/sys/windows" ) @@ -235,17 +234,33 @@ func cleanXML(xmlStr string) string { return xmlStr } -type Windows struct{} +type Windows struct { + stopChan chan struct{} + subscriptions []*EventSubscription + mu sync.Mutex +} + +var windowsCollector = &Windows{ + stopChan: make(chan struct{}), +} + +func GetCollectors() []Collector { + return []Collector{windowsCollector} +} -func getCollectorsInstances() []Collector { - var collectors []Collector - collectors = append(collectors, Windows{}) - return collectors +func (w *Windows) Name() string { + return "windows-arm64" } -func (w Windows) SendLogs() { +func (w *Windows) Start(ctx context.Context, queue chan *plugins.Log) { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in Windows ARM64 collector: %v", r) + } + }() + errorsChan := make(chan error, 10) - go eventWorker() + go eventWorker(queue) channels := []string{ "Security", "Application", "System", "Windows Powershell", "Microsoft-Windows-Powershell/Operational", "ForwardedEvents", @@ -253,8 +268,8 @@ func (w Windows) SendLogs() { "Microsoft-Windows-Windows Defender/Operational", } - var subscriptions []*EventSubscription - + w.mu.Lock() + w.subscriptions = nil for _, channel := range channels { sub := &EventSubscription{ Channel: channel, @@ -265,29 +280,41 @@ func (w Windows) SendLogs() { utils.Logger.ErrorF("Error subscribing to channel %s: %s", channel, err) continue } - subscriptions = append(subscriptions, sub) + w.subscriptions = append(w.subscriptions, sub) utils.Logger.LogF(100, "Subscribed to channel: %s", channel) } + w.mu.Unlock() go func() { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in error handler: %v", r) + } + }() for err := range errorsChan { utils.Logger.ErrorF("Subscription error: %s", err) } }() - exitChan := make(chan os.Signal, 1) - signal.Notify(exitChan, os.Interrupt) - <-exitChan - utils.Logger.LogF(100, "Interrupt received, closing subscriptions...") - for _, sub := range subscriptions { + // Block until context is cancelled or Stop() is called + select { + case <-ctx.Done(): + case <-w.stopChan: + } + + utils.Logger.Info("Windows ARM64 collector stopping...") + w.mu.Lock() + for _, sub := range w.subscriptions { if err := sub.Close(); err != nil { utils.Logger.ErrorF("Error closing subscription for %s: %v", sub.Channel, err) } } - utils.Logger.LogF(100, "Agent finished successfully.") + w.subscriptions = nil + w.mu.Unlock() + utils.Logger.Info("Windows ARM64 collector stopped.") } -func eventWorker() { +func eventWorker(queue chan *plugins.Log) { host, err := os.Hostname() if err != nil { utils.Logger.ErrorF("error getting hostname: %v", err) @@ -314,7 +341,7 @@ func eventWorker() { } select { - case logservice.LogQueue <- &plugins.Log{ + case queue <- &plugins.Log{ DataSource: host, DataType: string(config.DataTypeWindowsAgent), Raw: validatedLog, @@ -368,10 +395,18 @@ func convertEventToJSON(event *Event) (string, error) { return string(jsonBytes), nil } -func (w Windows) Install() error { +func (w *Windows) Install() error { return nil } -func (w Windows) Uninstall() error { +func (w *Windows) Uninstall() error { return nil } + +func (w *Windows) Stop() { + select { + case w.stopChan <- struct{}{}: + default: + // Already stopped or not started + } +} diff --git a/agent/collector/schema/schema.go b/agent/collector/schema/schema.go new file mode 100644 index 000000000..8a9d87db3 --- /dev/null +++ b/agent/collector/schema/schema.go @@ -0,0 +1,67 @@ +// Package schema defines shared configuration types for collector packages. +// This package exists to avoid circular imports between collector, syslog, and netflow. +package schema + +import ( + "encoding/json" + "os" + "strconv" + + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/shared/fs" +) + +// Port represents a TCP or UDP port configuration. +type Port struct { + IsListen bool `json:"enabled"` + Port string `json:"value"` + TLSEnabled bool `json:"tls_enabled,omitempty"` +} + +// Integration represents a collector integration configuration (syslog/netflow). +type Integration struct { + TCP Port `json:"tcp_port,omitempty"` + UDP Port `json:"udp_port,omitempty"` +} + +// FileIntegration represents a file-based log collector configuration. +type FileIntegration struct { + Enabled bool `json:"enabled"` + Paths []string `json:"paths"` +} + +// CollectorConfig represents the full collector configuration file. +type CollectorConfig struct { + Integrations map[string]Integration `json:"integrations"` + FileIntegrations map[string]FileIntegration `json:"file_integrations,omitempty"` +} + +// ReadCollectorConfig reads the collector configuration from disk. +func ReadCollectorConfig() (CollectorConfig, error) { + cnf := CollectorConfig{} + err := fs.ReadJSON(config.CollectorFileName, &cnf) + if err != nil { + return cnf, err + } + return cnf, nil +} + +// WriteCollectorConfig writes the collector configuration to disk. +func WriteCollectorConfig(cnf *CollectorConfig) error { + data, err := json.MarshalIndent(cnf, "", " ") + if err != nil { + return err + } + return os.WriteFile(config.CollectorFileName, data, 0644) +} + +// ValidatePortChange returns true if the port is within the valid range. +func ValidatePortChange(newPort string) bool { + port, err := strconv.Atoi(newPort) + if err != nil { + return false + } + min, _ := strconv.Atoi(config.PortRangeMin) + max, _ := strconv.Atoi(config.PortRangeMax) + return port >= min && port <= max +} diff --git a/agent/collector/syslog/framing.go b/agent/collector/syslog/framing.go new file mode 100644 index 000000000..a10bdfd91 --- /dev/null +++ b/agent/collector/syslog/framing.go @@ -0,0 +1,109 @@ +package syslog + +import ( + "bufio" + "fmt" + "io" + "strconv" + "strings" + + "github.com/utmstack/UTMStack/agent/utils" +) + +// Buffer size constants for syslog message processing. +const ( + MinBufferSize = 480 + RecommendedBufferSize = 2048 + MaxBufferSize = 8192 + UDPBufferSize = 2048 +) + +// FramingMethod represents the syslog message framing method. +type FramingMethod int + +const ( + FramingNewline FramingMethod = iota + FramingOctetCounting +) + +// detectFramingMethod determines whether the message uses octet counting or newline framing. +func detectFramingMethod(reader *bufio.Reader) (FramingMethod, error) { + firstByte, err := reader.Peek(1) + if err != nil { + utils.Logger.ErrorF("failed to peek first byte for framing detection: %v", err) + return 0, fmt.Errorf("failed to peek first byte: %w", err) + } + + if firstByte[0] >= '0' && firstByte[0] <= '9' { + return FramingOctetCounting, nil + } + + if firstByte[0] == '<' { + return FramingNewline, nil + } + + utils.Logger.ErrorF("unknown framing method detected, first byte: 0x%02x", firstByte[0]) + return 0, fmt.Errorf("unknown framing method, first byte: 0x%02x", firstByte[0]) +} + +// readOctetCountingFrame reads a syslog message using octet counting framing (RFC 5425). +func readOctetCountingFrame(reader *bufio.Reader) (string, error) { + lengthStr, err := reader.ReadString(' ') + if err != nil { + utils.Logger.ErrorF("failed to read message length in octet counting frame: %v", err) + return "", fmt.Errorf("failed to read message length: %w", err) + } + + lengthStr = strings.TrimSuffix(lengthStr, " ") + msgLen, err := strconv.Atoi(lengthStr) + if err != nil { + utils.Logger.ErrorF("invalid message length '%s' in octet counting frame: %v", lengthStr, err) + return "", fmt.Errorf("invalid message length '%s': %w", lengthStr, err) + } + + if msgLen < 1 { + utils.Logger.ErrorF("message length %d is too small (minimum 1 byte)", msgLen) + return "", fmt.Errorf("message length %d is too small (minimum 1)", msgLen) + } + if msgLen > MaxBufferSize { + utils.Logger.ErrorF("message length %d exceeds maximum %d bytes", msgLen, MaxBufferSize) + return "", fmt.Errorf("message length %d exceeds maximum %d", msgLen, MaxBufferSize) + } + + msgBytes := make([]byte, msgLen) + _, err = io.ReadFull(reader, msgBytes) + if err != nil { + utils.Logger.ErrorF("failed to read %d byte message body: %v", msgLen, err) + return "", fmt.Errorf("failed to read %d byte message body: %w", msgLen, err) + } + + return string(msgBytes), nil +} + +// readNewlineFrame reads a syslog message using newline-delimited framing. +func readNewlineFrame(reader *bufio.Reader) (string, error) { + message, err := reader.ReadString('\n') + if err != nil { + utils.Logger.ErrorF("failed to read newline-delimited message: %v", err) + return "", fmt.Errorf("failed to read newline-delimited message: %w", err) + } + return message, nil +} + +// readSyslogMessage reads a complete syslog message, auto-detecting the framing method. +func readSyslogMessage(reader *bufio.Reader) (string, error) { + method, err := detectFramingMethod(reader) + if err != nil { + return "", err + } + + switch method { + case FramingOctetCounting: + return readOctetCountingFrame(reader) + case FramingNewline: + return readNewlineFrame(reader) + default: + utils.Logger.ErrorF("unsupported framing method: %d", method) + return "", fmt.Errorf("unsupported framing method: %d", method) + } +} diff --git a/agent/collector/syslog/handler.go b/agent/collector/syslog/handler.go new file mode 100644 index 000000000..69fcea32e --- /dev/null +++ b/agent/collector/syslog/handler.go @@ -0,0 +1,157 @@ +package syslog + +import ( + "bufio" + "context" + "crypto/tls" + "io" + "net" + "os" + "strings" + "time" + + "github.com/threatwinds/go-sdk/entities" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/models" + "github.com/utmstack/UTMStack/agent/utils" +) + +// resolveRemoteAddr extracts the IP from addr and replaces localhost with hostname. +func resolveRemoteAddr(addr string) string { + host, _, err := net.SplitHostPort(addr) + if err != nil { + utils.Logger.ErrorF("error splitting host and port: %v", err) + return "unknown" + } + if host == "127.0.0.1" { + if hostname, err := os.Hostname(); err == nil { + return hostname + } + } + return host +} + +// readLoop reads syslog messages from reader and sends them to msgChannel. +// If conn is provided, it sets a deadline before each read (used for TLS). +func (inst *syslogInstance) readLoop(ctx context.Context, reader *bufio.Reader, remoteAddr string, msgChannel chan models.MSGDS, conn net.Conn) { + connType := "TCP" + if conn != nil { + connType = "TLS" + } + + for { + select { + case <-ctx.Done(): + return + default: + if conn != nil { + conn.SetDeadline(time.Now().Add(30 * time.Second)) + } + message, err := readSyslogMessage(reader) + if err != nil { + if err == io.EOF { + utils.Logger.Info("%s connection closed by %s", connType, remoteAddr) + return + } + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + utils.Logger.Info("%s connection timeout from %s", connType, remoteAddr) + return + } + utils.Logger.ErrorF("error reading %s data from %s: %v", connType, remoteAddr, err) + return + } + msgChannel <- models.MSGDS{ + DataSource: remoteAddr, + Message: message, + } + } + } +} + +func (inst *syslogInstance) handleConnectionTCP(c net.Conn, queue chan *plugins.Log) { + defer c.Close() + reader := bufio.NewReader(c) + remoteAddr := resolveRemoteAddr(c.RemoteAddr().String()) + + // Detect and reject TLS connections when TLS is disabled + c.SetReadDeadline(time.Now().Add(5 * time.Second)) + firstBytes := make([]byte, 3) + n, err := reader.Read(firstBytes) + if err != nil { + utils.Logger.ErrorF("error reading initial bytes from %s: %v", remoteAddr, err) + return + } + + // TLS handshake starts with: 0x16 (22 decimal) for TLS 1.0-1.3 + if n >= 1 && firstBytes[0] == 0x16 { + utils.Logger.ErrorF("TLS connection rejected from %s: TLS is disabled, only plain text connections accepted", remoteAddr) + return + } + + // Reset deadline and create a new reader that includes the read bytes + c.SetReadDeadline(time.Time{}) + reader = bufio.NewReader(io.MultiReader(strings.NewReader(string(firstBytes[:n])), reader)) + + msgChannel := make(chan models.MSGDS) + defer close(msgChannel) + go inst.handleMessage(inst.TCPListener.CTX, msgChannel, queue) + + inst.readLoop(inst.TCPListener.CTX, reader, remoteAddr, msgChannel, nil) +} + +func (inst *syslogInstance) handleTLSConnection(conn net.Conn, queue chan *plugins.Log) { + defer conn.Close() + + remoteAddr := resolveRemoteAddr(conn.RemoteAddr().String()) + + tlsConfig, err := utils.LoadIntegrationTLSConfig( + config.IntegrationCertPath, + config.IntegrationKeyPath, + ) + if err != nil { + utils.Logger.ErrorF("error loading TLS config: %v", err) + return + } + + tlsConn := tls.Server(conn, tlsConfig) + + conn.SetDeadline(time.Now().Add(10 * time.Second)) + if err := tlsConn.Handshake(); err != nil { + utils.Logger.ErrorF("TLS handshake failed from %s: %v", remoteAddr, err) + return + } + + reader := bufio.NewReader(tlsConn) + msgChannel := make(chan models.MSGDS) + defer close(msgChannel) + go inst.handleMessage(inst.TCPListener.CTX, msgChannel, queue) + + inst.readLoop(inst.TCPListener.CTX, reader, remoteAddr, msgChannel, conn) +} + +// handleMessage processes messages from the channel and sends them to the queue. +func (inst *syslogInstance) handleMessage(ctx context.Context, logsChannel chan models.MSGDS, queue chan *plugins.Log) { + for { + select { + case <-ctx.Done(): + return + case msgDS, ok := <-logsChannel: + if !ok { + return // channel closed + } + message := strings.TrimSuffix(msgDS.Message, "\n") + message, _, err := entities.ValidateString(message, false) + if err != nil { + utils.Logger.ErrorF("error validating string: %v: message: %s", err, message) + continue + } + + queue <- &plugins.Log{ + DataType: inst.DataType, + DataSource: msgDS.DataSource, + Raw: message, + } + } + } +} diff --git a/agent/collector/syslog/listener.go b/agent/collector/syslog/listener.go new file mode 100644 index 000000000..84cb929d6 --- /dev/null +++ b/agent/collector/syslog/listener.go @@ -0,0 +1,301 @@ +package syslog + +import ( + "context" + "errors" + "fmt" + "net" + "sync" + "time" + + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/models" + "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" +) + +// listenerState holds common state for TCP and UDP listeners. +type listenerState struct { + CTX context.Context + Cancel context.CancelFunc + IsEnabled bool + Port string +} + +// disable cancels the context and marks the listener as disabled. +// The caller must close the actual listener and hold the appropriate lock. +func (ls *listenerState) disable() { + if ls.Cancel != nil { + ls.Cancel() + } + ls.IsEnabled = false +} + +type listenerTCP struct { + listenerState + Listener net.Listener + TLSEnabled bool +} + +type listenerUDP struct { + listenerState + Listener net.PacketConn +} + +type syslogInstance struct { + DataType string + TCPListener listenerTCP + UDPListener listenerUDP + mu sync.RWMutex +} + +// --- Port query methods --- + +func (inst *syslogInstance) isPortListen(proto string) bool { + inst.mu.RLock() + defer inst.mu.RUnlock() + switch proto { + case "tcp": + return inst.TCPListener.IsEnabled + case "udp": + return inst.UDPListener.IsEnabled + default: + return false + } +} + +func (inst *syslogInstance) getPort(proto string) string { + inst.mu.RLock() + defer inst.mu.RUnlock() + switch proto { + case "tcp": + return inst.TCPListener.Port + case "udp": + return inst.UDPListener.Port + default: + return "" + } +} + +func (inst *syslogInstance) setNewPort(proto string, port string) { + inst.mu.Lock() + defer inst.mu.Unlock() + switch proto { + case "tcp": + inst.TCPListener.Port = port + case "udp": + inst.UDPListener.Port = port + } +} + +// --- Port enable/disable --- + +func (inst *syslogInstance) enablePort(proto string, enableTLS bool, queue chan *plugins.Log) error { + switch proto { + case "tcp": + if enableTLS { + if !fs.Exists(config.IntegrationCertPath) || !fs.Exists(config.IntegrationKeyPath) { + return fmt.Errorf("TLS certificates not found. Please load certificates first") + } + } + inst.TCPListener.TLSEnabled = enableTLS + go inst.enableTCP(queue) + return nil + case "udp": + if enableTLS { + return fmt.Errorf("TLS not supported for UDP protocol") + } + go inst.enableUDP(queue) + return nil + default: + return fmt.Errorf("unsupported protocol: %s", proto) + } +} + +func (inst *syslogInstance) disablePort(proto string) { + switch proto { + case "tcp": + inst.disableTCP() + case "udp": + inst.disableUDP() + } +} + +func (inst *syslogInstance) enableTCP(queue chan *plugins.Log) { + inst.mu.Lock() + if inst.TCPListener.IsEnabled || inst.TCPListener.Port == "" { + inst.mu.Unlock() + return + } + + listener, err := net.Listen("tcp", "0.0.0.0:"+inst.TCPListener.Port) + if err != nil { + inst.mu.Unlock() + utils.Logger.ErrorF("error listening TCP in port %s: %v", inst.TCPListener.Port, err) + return + } + + inst.TCPListener.IsEnabled = true + inst.TCPListener.Listener = listener + inst.TCPListener.CTX, inst.TCPListener.Cancel = context.WithCancel(context.Background()) + inst.mu.Unlock() + + utils.Logger.Info("Server %s listening in port: %s protocol: TCP", inst.DataType, inst.TCPListener.Port) + if inst.TCPListener.TLSEnabled { + utils.Logger.Info("Server %s TLS enabled in port: %s protocol: TCP", inst.DataType, inst.TCPListener.Port) + } + + go func() { + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in TCP listener for %s: %v", inst.DataType, r) + } + err = inst.TCPListener.Listener.Close() + if err != nil { + utils.Logger.ErrorF("error closing tcp listener: %v", err) + } + }() + for { + select { + case <-inst.TCPListener.CTX.Done(): + return + default: + conn, err := inst.TCPListener.Listener.Accept() + if err != nil { + if errors.Is(err, net.ErrClosed) { + return + } + + var netOpErr *net.OpError + ok := errors.As(err, &netOpErr) + if ok && netOpErr.Timeout() { + continue + } + + utils.Logger.ErrorF("error connecting with tcp listener: %v", err) + continue + } + + if inst.TCPListener.TLSEnabled { + go inst.handleTLSConnection(conn, queue) + } else { + go inst.handleConnectionTCP(conn, queue) + } + } + } + }() +} + +func (inst *syslogInstance) enableUDP(queue chan *plugins.Log) { + inst.mu.Lock() + if inst.UDPListener.IsEnabled || inst.UDPListener.Port == "" { + inst.mu.Unlock() + return + } + + listener, err := net.ListenPacket("udp", "0.0.0.0:"+inst.UDPListener.Port) + if err != nil { + inst.mu.Unlock() + utils.Logger.ErrorF("error listening UDP in port %s: %v", inst.UDPListener.Port, err) + return + } + + udpListener, ok := listener.(*net.UDPConn) + if !ok { + inst.mu.Unlock() + utils.Logger.ErrorF("could not assert to *net.UDPConn") + listener.Close() + return + } + + inst.UDPListener.IsEnabled = true + inst.UDPListener.Listener = listener + inst.UDPListener.CTX, inst.UDPListener.Cancel = context.WithCancel(context.Background()) + inst.mu.Unlock() + + utils.Logger.Info("Server %s listening in port: %s protocol: UDP", inst.DataType, inst.UDPListener.Port) + + buffer := make([]byte, UDPBufferSize) + msgChannel := make(chan models.MSGDS) + + go inst.handleMessage(inst.UDPListener.CTX, msgChannel, queue) + + go func() { + defer close(msgChannel) + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in UDP listener for %s: %v", inst.DataType, r) + } + if err := inst.UDPListener.Listener.Close(); err != nil { + utils.Logger.ErrorF("error closing udp listener: %v", err) + } + }() + for { + select { + case <-inst.UDPListener.CTX.Done(): + return + default: + udpListener.SetDeadline(time.Now().Add(time.Second * 1)) + + n, addr, err := listener.ReadFrom(buffer) + if err != nil { + if errors.Is(err, net.ErrClosed) { + return + } + + var netOpErr *net.OpError + ok := errors.As(err, &netOpErr) + if ok && netOpErr.Timeout() { + continue + } + + utils.Logger.ErrorF("error connecting with udp listener: %v", err) + continue + } + + remoteAddr := resolveRemoteAddr(addr.String()) + msgChannel <- models.MSGDS{ + DataSource: remoteAddr, + Message: string(buffer[:n]), + } + } + } + }() +} + +func (inst *syslogInstance) disableTCP() { + inst.mu.Lock() + defer inst.mu.Unlock() + + if !inst.TCPListener.IsEnabled { + return + } + + utils.Logger.Info("Server %s closed in port: %s protocol: TCP", inst.DataType, inst.TCPListener.Port) + + if inst.TCPListener.Listener != nil { + if err := inst.TCPListener.Listener.Close(); err != nil { + utils.Logger.ErrorF("error closing TCP listener: %v", err) + } + } + inst.TCPListener.disable() +} + +func (inst *syslogInstance) disableUDP() { + inst.mu.Lock() + defer inst.mu.Unlock() + + if !inst.UDPListener.IsEnabled { + return + } + + utils.Logger.Info("Server %s closed in port: %s protocol: UDP", inst.DataType, inst.UDPListener.Port) + + if inst.UDPListener.Listener != nil { + if err := inst.UDPListener.Listener.Close(); err != nil { + utils.Logger.ErrorF("error closing UDP listener: %v", err) + } + } + inst.UDPListener.disable() +} diff --git a/agent/collector/syslog/syslog.go b/agent/collector/syslog/syslog.go new file mode 100644 index 000000000..60435263f --- /dev/null +++ b/agent/collector/syslog/syslog.go @@ -0,0 +1,183 @@ +package syslog + +import ( + "context" + "sync" + "time" + + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/collector/configwatcher" + "github.com/utmstack/UTMStack/agent/collector/schema" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" +) + +// SyslogCollector manages all syslog instances. It reads the config file +// periodically and reconciles port state internally. +type SyslogCollector struct { + instances map[string]*syslogInstance + mu sync.RWMutex + queue chan *plugins.Log +} + +// New creates a new SyslogCollector. +func New() *SyslogCollector { + return &SyslogCollector{ + instances: make(map[string]*syslogInstance), + } +} + +func (sc *SyslogCollector) Name() string { + return "syslog" +} + +func (sc *SyslogCollector) Stop() { + sc.mu.RLock() + defer sc.mu.RUnlock() + for _, inst := range sc.instances { + inst.disableTCP() + inst.disableUDP() + } +} + +// Start begins watching for configuration changes using fsnotify. +// It performs an initial reconciliation and then reacts to config file changes. +func (sc *SyslogCollector) Start(ctx context.Context, queue chan *plugins.Log) { + sc.queue = queue + configwatcher.Watch(ctx, "syslog collector", sc.reconcile) +} + +func (sc *SyslogCollector) reconcile() { + cnf, err := schema.ReadCollectorConfig() + if err != nil { + utils.Logger.ErrorF("error reading collector config: %v", err) + return + } + + for intType, integration := range cnf.Integrations { + // Only handle syslog-type integrations + if config.ValidateModuleType(intType) != "syslog" { + continue + } + + inst := sc.getOrCreateInstance(intType) + + sc.reconcileProto(inst, intType, "tcp", integration) + sc.reconcileProto(inst, intType, "udp", integration) + } +} + +func (sc *SyslogCollector) getOrCreateInstance(dataType string) *syslogInstance { + sc.mu.Lock() + defer sc.mu.Unlock() + + if inst, ok := sc.instances[dataType]; ok { + return inst + } + + inst := &syslogInstance{ + DataType: dataType, + TCPListener: listenerTCP{ + listenerState: listenerState{ + IsEnabled: false, + Port: config.ProtoPorts[config.DataType(dataType)].TCP, + }, + }, + UDPListener: listenerUDP{ + listenerState: listenerState{ + IsEnabled: false, + Port: config.ProtoPorts[config.DataType(dataType)].UDP, + }, + }, + } + sc.instances[dataType] = inst + return inst +} + +func (sc *SyslogCollector) reconcileProto(inst *syslogInstance, intType string, proto string, integration schema.Integration) { + var cfgEnabled bool + var cfgPort string + var cfgTLS bool + + switch proto { + case "tcp": + cfgEnabled = integration.TCP.IsListen + cfgPort = integration.TCP.Port + cfgTLS = integration.TCP.TLSEnabled + case "udp": + cfgEnabled = integration.UDP.IsListen + cfgPort = integration.UDP.Port + } + + isListening := inst.isPortListen(proto) + currentPort := inst.getPort(proto) + + needKill := false + needStart := false + + if isListening && !cfgEnabled { + needKill = true + } else if !isListening && cfgEnabled { + needStart = true + } else if isListening && cfgEnabled && currentPort != cfgPort { + needKill = true + needStart = true + } + + changeAllowed := true + if cfgPort != "" && currentPort != cfgPort { + changeAllowed = schema.ValidatePortChange(cfgPort) + } + + if needKill { + inst.disablePort(proto) + if needStart { + time.Sleep(200 * time.Millisecond) + } + } + + if changeAllowed { + inst.setNewPort(proto, cfgPort) + if needStart { + enableTLS := proto == "tcp" && cfgTLS + err := inst.enablePort(proto, enableTLS, sc.queue) + if err != nil { + utils.Logger.ErrorF("error enabling port for %s %s: %v", intType, proto, err) + } + } + } else { + utils.Logger.Info("port %s is out of valid range %s-%s", cfgPort, config.PortRangeMin, config.PortRangeMax) + sc.writeConfigFromInstances() + } +} + +// writeConfigFromInstances writes the current live state back to the config file (rollback). +func (sc *SyslogCollector) writeConfigFromInstances() { + sc.mu.RLock() + defer sc.mu.RUnlock() + + // Read current config to preserve FileIntegrations + cnf, err := schema.ReadCollectorConfig() + if err != nil { + cnf = schema.CollectorConfig{ + Integrations: make(map[string]schema.Integration), + } + } + + // Update only the syslog integrations + for _, inst := range sc.instances { + cnf.Integrations[inst.DataType] = schema.Integration{ + TCP: schema.Port{ + IsListen: inst.isPortListen("tcp"), + Port: inst.getPort("tcp"), + }, + UDP: schema.Port{ + IsListen: inst.isPortListen("udp"), + Port: inst.getPort("udp"), + }, + } + } + if err := schema.WriteCollectorConfig(&cnf); err != nil { + utils.Logger.ErrorF("error fixing collector config: %v", err) + } +} diff --git a/agent/collectors/collectors.go b/agent/collectors/collectors.go deleted file mode 100644 index 753a655ff..000000000 --- a/agent/collectors/collectors.go +++ /dev/null @@ -1,54 +0,0 @@ -package collectors - -import ( - "fmt" - - "github.com/utmstack/UTMStack/agent/utils" -) - -type CollectorConfig struct { - LogsPath string - LogFileName string -} - -type Collector interface { - Install() error - SendLogs() - Uninstall() error -} - -func InstallCollectors() error { - collectors := getCollectorsInstances() - - for _, collector := range collectors { - err := collector.Install() - if err != nil { - return fmt.Errorf("%v", err) - } - } - - utils.Logger.LogF(100, "collectors installed correctly") - - return nil -} - -func LogsReader() { - collectors := getCollectorsInstances() - for _, collector := range collectors { - go collector.SendLogs() - } -} - -func UninstallCollectors() error { - collectors := getCollectorsInstances() - - for _, collector := range collectors { - err := collector.Uninstall() - if err != nil { - return fmt.Errorf("%v", err) - } - } - - utils.Logger.LogF(100, "collectors uninstalled correctly") - return nil -} diff --git a/agent/collectors/filebeat_amd64.go b/agent/collectors/filebeat_amd64.go deleted file mode 100644 index 50f40065c..000000000 --- a/agent/collectors/filebeat_amd64.go +++ /dev/null @@ -1,152 +0,0 @@ -package collectors - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - - "github.com/threatwinds/go-sdk/entities" - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/logservice" - "github.com/utmstack/UTMStack/agent/parser" - "github.com/utmstack/UTMStack/agent/utils" -) - -type Filebeat struct{} - -func (f Filebeat) Install() error { - path := utils.GetMyPath() - - filebLogPath := filepath.Join(path, "beats", "filebeat") - beatConfig := CollectorConfig{ - LogsPath: filepath.Join(filebLogPath, "logs"), - LogFileName: "modulescollector", - } - - if isInstalled, err := utils.CheckIfServiceIsInstalled(config.ModulesServName); err != nil { - return fmt.Errorf("error checking if %s service is installed: %v", config.ModulesServName, err) - } else if !isInstalled { - if err = utils.CreatePathIfNotExist(beatConfig.LogsPath); err != nil { - return fmt.Errorf("error creating %s folder", beatConfig.LogsPath) - } - - configFile := filepath.Join(filebLogPath, "filebeat.yml") - templateFile := filepath.Join(path, "templates", "filebeat.yml") - if err = utils.GenerateFromTemplate(beatConfig, templateFile, configFile); err != nil { - return fmt.Errorf("error configuration from %s: %v", templateFile, err) - } - switch runtime.GOOS { - case "windows": - err = utils.Execute("sc", - filebLogPath, - "create", - config.ModulesServName, - "binPath=", - fmt.Sprintf("\"%s\\filebeat.exe\" --environment=windows_service -c \"%s\\filebeat.yml\" --path.home \"%s\" --path.data \"C:\\ProgramData\\filebeat\" --path.logs \"C:\\ProgramData\\filebeat\\logs\" -E logging.files.redirect_stderr=true", filebLogPath, filebLogPath, filebLogPath), - "DisplayName=", - config.ModulesServName, - "start=", - "auto") - if err != nil { - return fmt.Errorf("error installing %s service: %s", config.ModulesServName, err) - } - - err = utils.Execute("sc", filebLogPath, "start", config.ModulesServName) - if err != nil { - return fmt.Errorf("error starting %s service: %s", config.ModulesServName, err) - } - case "linux": - if err = utils.CreateLinuxService(config.ModulesServName, fmt.Sprintf( - "%s -c %s -path.home %s -path.config %s -path.data /var/lib/filebeat -path.logs /var/log/filebeat", - filepath.Join(filebLogPath, "filebeat"), - filepath.Join(filebLogPath, "filebeat.yml"), - filebLogPath, - filebLogPath, - ), - ); err != nil { - return fmt.Errorf("error creating %s service: %v", config.ModulesServName, err) - } - - if err = utils.Execute("chmod", filebLogPath, "-R", "755", "filebeat"); err != nil { - return fmt.Errorf("error executing chmod: %v", err) - } - - if err = utils.Execute("systemctl", filebLogPath, "daemon-reload"); err != nil { - return fmt.Errorf("error reloading daemon: %v", err) - } - - err := utils.Execute("systemctl", filebLogPath, "enable", config.ModulesServName) - if err != nil { - return fmt.Errorf("%s", err) - } - - err = utils.Execute("systemctl", filebLogPath, "start", config.ModulesServName) - if err != nil { - return fmt.Errorf("%s", err) - } - - err = utils.Execute("./filebeat", filebLogPath, "modules", "enable", "system") - if err != nil { - return fmt.Errorf("%s", err) - } - - err = utils.Execute("sed", filepath.Join(filebLogPath, "modules.d"), "-i", "s/enabled: false/enabled: true/g", "system.yml") - if err != nil { - return fmt.Errorf("%s", err) - } - - err = utils.Execute("systemctl", filebLogPath, "restart", config.ModulesServName) - if err != nil { - return fmt.Errorf("%s", err) - } - } - } - - return nil -} - -func (f Filebeat) SendLogs() { - logLinesChan := make(chan string) - path := utils.GetMyPath() - filebLogPath := filepath.Join(path, "beats", "filebeat", "logs") - host, err := os.Hostname() - if err != nil { - utils.Logger.ErrorF("error getting hostname: %v", err) - host = "unknown" - } - - parser := parser.GetParser("beats") - - go utils.WatchFolder("modulescollector", filebLogPath, logLinesChan) - - for logLine := range logLinesChan { - message, _, err := entities.ValidateString(logLine, false) - if err != nil { - utils.Logger.ErrorF("error validating string: %v: message: %s", err, message) - continue - } - err = parser.ProcessData(logLine, host, logservice.LogQueue) - if err != nil { - utils.Logger.ErrorF("error processing beats data: %v", err) - continue - } - } -} - -func (f Filebeat) Uninstall() error { - if isInstalled, err := utils.CheckIfServiceIsInstalled(config.ModulesServName); err != nil { - return fmt.Errorf("error checking if %s is running: %v", config.ModulesServName, err) - } else if isInstalled { - err = utils.StopService(config.ModulesServName) - if err != nil { - return fmt.Errorf("error stopping %s: %v", config.ModulesServName, err) - } - - err = utils.UninstallService(config.ModulesServName) - if err != nil { - return fmt.Errorf("error uninstalling %s: %v", config.ModulesServName, err) - } - } - return nil -} diff --git a/agent/collectors/linux_amd64.go b/agent/collectors/linux_amd64.go deleted file mode 100644 index fb50593f2..000000000 --- a/agent/collectors/linux_amd64.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build linux && amd64 -// +build linux,amd64 - -package collectors - -func getCollectorsInstances() []Collector { - var collectors []Collector - collectors = append(collectors, Filebeat{}) - - return collectors -} diff --git a/agent/collectors/macos.go b/agent/collectors/macos.go deleted file mode 100644 index 323fbf535..000000000 --- a/agent/collectors/macos.go +++ /dev/null @@ -1,103 +0,0 @@ -//go:build darwin -// +build darwin - -package collectors - -import ( - "bufio" - "os" - "os/exec" - "path/filepath" - - "github.com/threatwinds/go-sdk/entities" - "github.com/threatwinds/go-sdk/plugins" - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/logservice" - "github.com/utmstack/UTMStack/agent/utils" -) - -type Darwin struct{} - -func (d Darwin) Install() error { - return nil -} - -func getCollectorsInstances() []Collector { - var collectors []Collector - collectors = append(collectors, Darwin{}) - return collectors -} - -func (d Darwin) SendLogs() { - path := utils.GetMyPath() - collectorPath := filepath.Join(path, "utmstack-collector-mac") - host, err := os.Hostname() - if err != nil { - utils.Logger.ErrorF("error getting hostname: %v", err) - host = "unknown" - } - - cmd := exec.Command(collectorPath) - - stdout, err := cmd.StdoutPipe() - if err != nil { - _ = utils.Logger.ErrorF("error creating stdout pipe: %v", err) - return - } - - stderr, err := cmd.StderrPipe() - if err != nil { - _ = utils.Logger.ErrorF("error creating stderr pipe: %v", err) - return - } - - if err := cmd.Start(); err != nil { - _ = utils.Logger.ErrorF("error starting macOS collector: %v", err) - return - } - - go func() { - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - logLine := scanner.Text() - - utils.Logger.LogF(100, "output: %s", logLine) - - validatedLog, _, err := entities.ValidateString(logLine, false) - if err != nil { - utils.Logger.ErrorF("error validating log: %s: %v", logLine, err) - continue - } - - logservice.LogQueue <- &plugins.Log{ - DataType: string(config.DataTypeMacOs), - DataSource: host, - Raw: validatedLog, - } - } - - if err := scanner.Err(); err != nil { - _ = utils.Logger.ErrorF("error reading stdout: %v", err) - } - }() - - go func() { - scanner := bufio.NewScanner(stderr) - for scanner.Scan() { - errLine := scanner.Text() - _ = utils.Logger.ErrorF("collector error: %s", errLine) - } - - if err := scanner.Err(); err != nil { - _ = utils.Logger.ErrorF("error reading stderr: %v", err) - } - }() - - if err := cmd.Wait(); err != nil { - _ = utils.Logger.ErrorF("macOS collector process ended with error: %v", err) - } -} - -func (d Darwin) Uninstall() error { - return nil -} diff --git a/agent/collectors/windows_amd64.go b/agent/collectors/windows_amd64.go deleted file mode 100644 index 4d4db012c..000000000 --- a/agent/collectors/windows_amd64.go +++ /dev/null @@ -1,115 +0,0 @@ -//go:build windows && amd64 -// +build windows,amd64 - -package collectors - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/threatwinds/go-sdk/entities" - "github.com/threatwinds/go-sdk/plugins" - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/logservice" - "github.com/utmstack/UTMStack/agent/utils" -) - -type Windows struct{} - -func getCollectorsInstances() []Collector { - var collectors []Collector - collectors = append(collectors, Windows{}) - collectors = append(collectors, Filebeat{}) - - return collectors -} - -func (w Windows) Install() error { - path := utils.GetMyPath() - - winlogPath := filepath.Join(path, "beats", "winlogbeat") - beatConfig := CollectorConfig{ - LogsPath: filepath.Join(winlogPath, "logs"), - LogFileName: "windowscollector", - } - - if isInstalled, err := utils.CheckIfServiceIsInstalled(config.WinServName); err != nil { - return fmt.Errorf("error checking if %s service is installed: %v", config.WinServName, err) - } else if !isInstalled { - err = utils.CreatePathIfNotExist(beatConfig.LogsPath) - if err != nil { - return fmt.Errorf("error creating %s folder", beatConfig.LogsPath) - } - - configFile := filepath.Join(winlogPath, "winlogbeat.yml") - templateFile := filepath.Join(path, "templates", "winlogbeat.yml") - err = utils.GenerateFromTemplate(beatConfig, templateFile, configFile) - if err != nil { - return fmt.Errorf("error configuration from %s: %v", templateFile, err) - } - - err = utils.Execute("sc", - winlogPath, - "create", - config.WinServName, - "binPath=", - fmt.Sprintf("\"%s\\winlogbeat.exe\" --environment=windows_service -c \"%s\\winlogbeat.yml\" --path.home \"%s\" --path.data \"C:\\ProgramData\\winlogbeat\" --path.logs \"C:\\ProgramData\\winlogbeat\\logs\" -E logging.files.redirect_stderr=true", winlogPath, winlogPath, winlogPath), - "DisplayName=", - config.WinServName, - "start=", - "auto") - if err != nil { - return fmt.Errorf("error installing %s service: %s", config.WinServName, err) - } - - err = utils.Execute("sc", winlogPath, "start", config.WinServName) - if err != nil { - return fmt.Errorf("error starting %s service: %s", config.WinServName, err) - } - } - - return nil -} - -func (w Windows) SendLogs() { - logLinesChan := make(chan string) - path := utils.GetMyPath() - winbLogPath := filepath.Join(path, "beats", "winlogbeat", "logs") - host, err := os.Hostname() - if err != nil { - utils.Logger.ErrorF("error getting hostname: %v", err) - host = "unknown" - } - - go utils.WatchFolder("windowscollector", winbLogPath, logLinesChan) - for logLine := range logLinesChan { - validatedLog, _, err := entities.ValidateString(logLine, false) - if err != nil { - utils.Logger.ErrorF("error validating log: %s: %v", logLine, err) - continue - } - logservice.LogQueue <- &plugins.Log{ - DataType: string(config.DataTypeWindowsAgent), - DataSource: host, - Raw: validatedLog, - } - } -} - -func (w Windows) Uninstall() error { - if isInstalled, err := utils.CheckIfServiceIsInstalled(config.WinServName); err != nil { - return fmt.Errorf("error checking if %s is running: %v", config.WinServName, err) - } else if isInstalled { - err = utils.StopService(config.WinServName) - if err != nil { - return fmt.Errorf("error stopping %s: %v", config.WinServName, err) - } - err = utils.UninstallService(config.WinServName) - if err != nil { - return fmt.Errorf("error uninstalling %s: %v", config.WinServName, err) - } - } - - return nil -} diff --git a/agent/config/config.go b/agent/config/config.go index 1f4042e5c..97cb46d3d 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -2,19 +2,13 @@ package config import ( "fmt" - "os" "sync" - aesCrypt "github.com/AtlasInsideCorp/AtlasInsideAES" "github.com/google/uuid" "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" ) -type MSGDS struct { - DataSource string - Message string -} - type InstallationUUID struct { UUID string `yaml:"uuid"` } @@ -26,19 +20,6 @@ type Config struct { SkipCertValidation bool `yaml:"insecure"` } -func GetInitialConfig() (*Config, string) { - cnf := Config{ - Server: os.Args[2], - } - skip := os.Args[4] - if skip == "yes" { - cnf.SkipCertValidation = true - } else { - cnf.SkipCertValidation = false - } - return &cnf, os.Args[3] -} - var ( cnf = Config{} confOnce sync.Once @@ -49,39 +30,21 @@ var ( func GetCurrentConfig() (*Config, error) { var errR error confOnce.Do(func() { - uuidExists := utils.CheckIfPathExist(UUIDFileName) - var encryptConfig Config - if err := utils.ReadYAML(ConfigurationFile, &encryptConfig); err != nil { + if err := fs.ReadYAML(ConfigurationFile, &encryptConfig); err != nil { errR = fmt.Errorf("error reading config file: %v", err) return } - var key []byte - var err error - if uuidExists { - id, err := GetUUID() - if err != nil { - errR = fmt.Errorf("failed to get uuid: %v", err) - return - } - - key, err = utils.GenerateKeyByUUID(REPLACE_KEY, id) - if err != nil { - errR = fmt.Errorf("error geneating key: %v", err) - return - } - } else { - key, err = utils.GenerateKey(REPLACE_KEY) - if err != nil { - errR = fmt.Errorf("error geneating key: %v", err) - return - } + id, err := GetUUID() + if err != nil { + errR = fmt.Errorf("failed to get uuid: %v", err) + return } - agentKey, err := aesCrypt.AESDecrypt(encryptConfig.AgentKey, key) + agentKey, err := utils.DecryptAES(encryptConfig.AgentKey, REPLACE_KEY, id) if err != nil { - errR = fmt.Errorf("error encoding agent key: %v", err) + errR = fmt.Errorf("error decrypting agent key: %v", err) return } @@ -89,13 +52,6 @@ func GetCurrentConfig() (*Config, error) { cnf.AgentID = encryptConfig.AgentID cnf.AgentKey = agentKey cnf.SkipCertValidation = encryptConfig.SkipCertValidation - - if !uuidExists { - if err := SaveConfig(&cnf); err != nil { - errR = fmt.Errorf("error writing config file: %v", err) - return - } - } }) if errR != nil { return nil, errR @@ -109,14 +65,9 @@ func SaveConfig(cnf *Config) error { return fmt.Errorf("failed to generate uuid: %v", err) } - key, err := utils.GenerateKeyByUUID(REPLACE_KEY, id) - if err != nil { - return fmt.Errorf("error geneating key: %v", err) - } - - agentKey, err := aesCrypt.AESEncrypt(cnf.AgentKey, key) + agentKey, err := utils.EncryptAES(cnf.AgentKey, REPLACE_KEY, id) if err != nil { - return fmt.Errorf("error encoding agent key: %v", err) + return fmt.Errorf("error encrypting agent key: %v", err) } encryptConf := &Config{ @@ -126,7 +77,7 @@ func SaveConfig(cnf *Config) error { SkipCertValidation: cnf.SkipCertValidation, } - if err := utils.WriteYAML(ConfigurationFile, encryptConf); err != nil { + if err := fs.WriteYAML(ConfigurationFile, encryptConf); err != nil { return err } return nil @@ -142,7 +93,7 @@ func GenerateNewUUID() (string, error) { UUID: id.String(), } - if err = utils.WriteYAML(UUIDFileName, InstallationUUID); err != nil { + if err = fs.WriteYAML(UUIDFileName, InstallationUUID); err != nil { return "", fmt.Errorf("error writing uuid file: %v", err) } @@ -153,7 +104,7 @@ func GetUUID() (string, error) { var errR error installationIdOnce.Do(func() { var id = InstallationUUID{} - if err := utils.ReadYAML(UUIDFileName, &id); err != nil { + if err := fs.ReadYAML(UUIDFileName, &id); err != nil { errR = fmt.Errorf("error reading uuid file: %v", err) return } diff --git a/agent/config/const.go b/agent/config/const.go index 910c9593b..7cd13ea87 100644 --- a/agent/config/const.go +++ b/agent/config/const.go @@ -3,7 +3,7 @@ package config import ( "path/filepath" - "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" ) type DataType string @@ -25,24 +25,24 @@ var ( LogAuthProxyPort = "50051" DependenciesPort = "9001" - ServiceLogFile = filepath.Join(utils.GetMyPath(), "logs", "utmstack_agent.log") + ServiceLogFile = filepath.Join(fs.GetExecutablePath(), "logs", "utmstack_agent.log") ModulesServName = "UTMStackModulesLogsCollector" WinServName = "UTMStackWindowsLogsCollector" - CollectorFileName = filepath.Join(utils.GetMyPath(), "log-collector-config.json") - UUIDFileName = filepath.Join(utils.GetMyPath(), "uuid.yml") - ConfigurationFile = filepath.Join(utils.GetMyPath(), "config.yml") - PortRangeMin = "7000" - PortRangeMax = "9000" - RetentionConfigFile = filepath.Join(utils.GetMyPath(), "retention.json") - LogsDBFile = filepath.Join(utils.GetMyPath(), "logs_process", "logs.db") - CertPath = filepath.Join(utils.GetMyPath(), "certs", "utm.crt") - VersionPath = filepath.Join(utils.GetMyPath(), "version.json") + CollectorFileName = filepath.Join(fs.GetExecutablePath(), "log-collector-config.json") + UUIDFileName = filepath.Join(fs.GetExecutablePath(), "uuid.yml") + ConfigurationFile = filepath.Join(fs.GetExecutablePath(), "config.yml") + PortRangeMin = "1" + PortRangeMax = "65535" + RetentionConfigFile = filepath.Join(fs.GetExecutablePath(), "retention.json") + LogsDBFile = filepath.Join(fs.GetExecutablePath(), "logs_process", "logs.db") + CertPath = filepath.Join(fs.GetExecutablePath(), "certs", "utm.crt") + VersionPath = filepath.Join(fs.GetExecutablePath(), "version.json") UpdaterSelfLinux = "utmstack_updater_self" // TLS Configuration for Integrations - IntegrationCertPath = filepath.Join(utils.GetMyPath(), "certs", "integration.crt") - IntegrationKeyPath = filepath.Join(utils.GetMyPath(), "certs", "integration.key") - IntegrationCAPath = filepath.Join(utils.GetMyPath(), "certs", "integration-ca.crt") + IntegrationCertPath = filepath.Join(fs.GetExecutablePath(), "certs", "integration.crt") + IntegrationKeyPath = filepath.Join(fs.GetExecutablePath(), "certs", "integration.key") + IntegrationCAPath = filepath.Join(fs.GetExecutablePath(), "certs", "integration-ca.crt") DataTypeWindowsAgent DataType = "wineventlog" DataTypeSyslog DataType = "syslog" @@ -77,7 +77,6 @@ var ( DataTypeIisModule DataType = "iis" DataTypeApacheModule DataType = "apache" DataTypeSentinelOne DataType = "antivirus-sentinel-one" - DataTypeCiscoGeneric DataType = "cisco" DataTypeMacOs DataType = "macos" DataTypeGeneric DataType = "generic" DataTypeNetflow DataType = "netflow" @@ -91,7 +90,10 @@ var ( DataTypeVmware: {UDP: "7002", TCP: "7002"}, DataTypeEset: {UDP: "7003", TCP: "7003"}, DataTypeKaspersky: {UDP: "7004", TCP: "7004"}, - DataTypeCiscoGeneric: {UDP: "514", TCP: "1470"}, + DataTypeCiscoAsa: {UDP: "514", TCP: "1470"}, + DataTypeCiscoFirepower: {UDP: "514", TCP: "1470"}, + DataTypeCiscoSwitch: {UDP: "514", TCP: "1470"}, + DataTypeCiscoMeraki: {UDP: "514", TCP: "1470"}, DataTypeFortinet: {UDP: "7005", TCP: "7005"}, DataTypePaloalto: {UDP: "7006", TCP: "7006"}, DataTypeMikrotik: {UDP: "7007", TCP: "7007"}, @@ -106,20 +108,27 @@ var ( DataTypeNetflow: {UDP: "2055", TCP: ""}, } - ProhibitedPortsChange = []DataType{DataTypeCiscoGeneric, DataTypeNetflow} + // FilePaths defines default log file paths for file-based integrations + FilePaths = map[DataType][]string{ + DataTypeNginxModule: {"/var/log/nginx/access.log", "/var/log/nginx/error.log"}, + DataTypePostgresqlModule: {"/var/log/postgresql/postgresql-*-main.log"}, + } ) func ValidateModuleType(typ string) string { switch DataType(typ) { case DataTypeSyslog, DataTypeVmware, DataTypeEset, DataTypeKaspersky, DataTypeFortinet, DataTypePaloalto, - DataTypeMikrotik, DataTypeSophosXG, DataTypeSonicwall, DataTypeSentinelOne, DataTypeCiscoGeneric, + DataTypeMikrotik, DataTypeSophosXG, DataTypeSonicwall, DataTypeSentinelOne, + DataTypeCiscoAsa, DataTypeCiscoFirepower, DataTypeCiscoSwitch, DataTypeCiscoMeraki, DataTypeDeceptivebytes, DataTypeAix, DataTypePfsense, DataTypeFortiweb, DataTypeSuricata: return "syslog" case DataTypeNetflow: return "netflow" - case DataTypeWindowsAgent, DataTypeLinuxAgent, DataTypeTraefikModule, DataTypeMongodbModule, DataTypeMysqlModule, DataTypePostgresqlModule, + case DataTypeNginxModule, DataTypePostgresqlModule: + return "file" + case DataTypeWindowsAgent, DataTypeLinuxAgent, DataTypeTraefikModule, DataTypeMongodbModule, DataTypeMysqlModule, DataTypeRedisModule, DataTypeElasticsearchModule, DataTypeKafkaModule, DataTypeKibanaModule, DataTypeLogstashModule, DataTypeNatsModule, - DataTypeOsqueryModule, DataTypeLinuxAuditdModule, DataTypeHaproxyModule, DataTypeNginxModule, DataTypeIisModule, DataTypeApacheModule: + DataTypeOsqueryModule, DataTypeLinuxAuditdModule, DataTypeHaproxyModule, DataTypeIisModule, DataTypeApacheModule: return "beats" default: return "nil" diff --git a/agent/database/db.go b/agent/database/db.go index fa7ac083c..a9e2af5b6 100644 --- a/agent/database/db.go +++ b/agent/database/db.go @@ -3,14 +3,13 @@ package database import ( "errors" "fmt" - "log" "os" "path/filepath" "sync" "github.com/glebarez/sqlite" "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" "gorm.io/gorm" "gorm.io/gorm/logger" ) @@ -18,6 +17,7 @@ import ( var ( dbInstance *Database dbOnce sync.Once + dbInitErr error ) type Database struct { @@ -26,14 +26,20 @@ type Database struct { } func (d *Database) Migrate(data interface{}) error { + d.locker.Lock() + defer d.locker.Unlock() return d.db.AutoMigrate(data) } func (d *Database) Create(data interface{}) error { + d.locker.Lock() + defer d.locker.Unlock() return d.db.Create(data).Error } func (d *Database) Find(data interface{}, field string, value interface{}) (bool, error) { + d.locker.RLock() + defer d.locker.RUnlock() err := d.db.Where(fmt.Sprintf("%v = ?", field), value).Find(data).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -45,6 +51,8 @@ func (d *Database) Find(data interface{}, field string, value interface{}) (bool } func (d *Database) GetAll(data interface{}) error { + d.locker.RLock() + defer d.locker.RUnlock() if err := d.db.Find(data).Error; err != nil { return err } @@ -52,14 +60,30 @@ func (d *Database) GetAll(data interface{}) error { } func (d *Database) Update(data interface{}, searchField string, searchValue string, modifyField string, newValue interface{}) error { + d.locker.Lock() + defer d.locker.Unlock() return d.db.Model(data).Where(fmt.Sprintf("%v = ?", searchField), searchValue).Update(modifyField, newValue).Error } func (d *Database) Delete(data interface{}, field string, value string) error { + d.locker.Lock() + defer d.locker.Unlock() return d.db.Where(fmt.Sprintf("%v = ?", field), value).Delete(data).Error } +func (d *Database) Close() error { + d.locker.Lock() + defer d.locker.Unlock() + sqlDB, err := d.db.DB() + if err != nil { + return err + } + return sqlDB.Close() +} + func (d *Database) DeleteOld(data interface{}, retentionMegabytes int) (int, error) { + d.locker.Lock() + defer d.locker.Unlock() currentSize, err := GetDatabaseSizeInMB() if err != nil { return 0, fmt.Errorf("error getting database size: %v", err) @@ -67,41 +91,41 @@ func (d *Database) DeleteOld(data interface{}, retentionMegabytes int) (int, err var rowsAffected int for currentSize > retentionMegabytes { - result := d.db.Where("1 = 1").Order("created_at ASC").Limit(10).Delete(data) + result := d.db.Where("1 = 1").Order("created_at ASC").Limit(500).Delete(data) if result.Error != nil { - return rowsAffected, result.Error + break } rowsAffected += int(result.RowsAffected) - d.db.Exec("VACUUM;") + if result.RowsAffected == 0 { + break + } currentSize, err = GetDatabaseSizeInMB() if err != nil { - return rowsAffected, fmt.Errorf("error getting database size: %v", err) + break } } - return rowsAffected, nil -} - -func (d *Database) Lock() { - d.locker.Lock() -} + if rowsAffected > 0 { + d.db.Exec("VACUUM;") + } -func (d *Database) Unlock() { - d.locker.Unlock() + return rowsAffected, nil } -func GetDB() *Database { +func GetDB() (*Database, error) { dbOnce.Do(func() { - path := filepath.Join(utils.GetMyPath(), "logs_process") - err := utils.CreatePathIfNotExist(path) - if err != nil { - log.Fatalf("error creating database path: %v", err) + path := filepath.Join(fs.GetExecutablePath(), "logs_process") + if err := fs.CreateDirIfNotExist(path); err != nil { + dbInitErr = fmt.Errorf("creating database path: %w", err) + return } + path = config.LogsDBFile if _, err := os.Stat(path); os.IsNotExist(err) { file, err := os.Create(path) if err != nil { - log.Fatalf("error creating database file: %v", err) + dbInitErr = fmt.Errorf("creating database file: %w", err) + return } file.Close() } @@ -110,14 +134,17 @@ func GetDB() *Database { Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { - log.Fatalf("error connecting with database: %v", err) + dbInitErr = fmt.Errorf("connecting with database: %w", err) + return } dbInstance = &Database{db: conn} - }) - return dbInstance + if dbInitErr != nil { + return nil, dbInitErr + } + return dbInstance, nil } func GetDatabaseSizeInMB() (int, error) { diff --git a/agent/dependency/dependency.go b/agent/dependency/dependency.go new file mode 100644 index 000000000..47d908f3d --- /dev/null +++ b/agent/dependency/dependency.go @@ -0,0 +1,273 @@ +package dependency + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + + "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" + "github.com/utmstack/UTMStack/shared/http" +) + +const ( + updaterBaseName = "utmstack_updater_service" + + // Dependency versions - single source of truth + UpdaterVersion = "11.2.3" + BeatsVersion = "11.2.3" + MacosCollectorVersion = "11.2.3" +) + +// UpdaterFile returns the updater binary name with OS and architecture suffix. +// Format: utmstack_updater_service__[.exe] +// Examples: +// - utmstack_updater_service_linux_amd64 +// - utmstack_updater_service_windows_amd64.exe +// - utmstack_updater_service_darwin_arm64 +func UpdaterFile(suffix string) string { + name := fmt.Sprintf("%s_%s_%s%s", updaterBaseName, runtime.GOOS, runtime.GOARCH, suffix) + if runtime.GOOS == "windows" { + return name + ".exe" + } + return name +} + +// Dependency represents a dependency that the agent needs. +type Dependency struct { + Name string // Unique identifier + Version string // Current version in this agent build + BinaryPath string // Path to check if already exists + DownloadURL func(server string) string // URL template to download from + DownloadName string // Filename to save as (if different from BinaryPath basename) + Critical bool // If true, failure blocks agent startup + PostDownload func() error // Run after download (e.g., unzip). Can be nil. + Configure func() error // Run on first install (can be nil) + Update func() error // Run on version change (can be nil, uses Configure) + Uninstall func() error // Run when dependency is removed (can be nil) +} + +// Exists checks if the dependency binary exists on disk. +func (d *Dependency) Exists() bool { + return fs.Exists(d.BinaryPath) +} + +// InstalledDep represents a dependency that has been installed and tracked. +type InstalledDep struct { + Name string `json:"name"` + Version string `json:"version"` +} + +// InstalledDeps is the list of installed dependencies (persisted to JSON). +type InstalledDeps struct { + Dependencies []InstalledDep `json:"dependencies"` +} + +func (i *InstalledDeps) Get(name string) *InstalledDep { + for idx := range i.Dependencies { + if i.Dependencies[idx].Name == name { + return &i.Dependencies[idx] + } + } + return nil +} + +func (i *InstalledDeps) Add(name, version string) { + i.Dependencies = append(i.Dependencies, InstalledDep{Name: name, Version: version}) +} + +func (i *InstalledDeps) Update(name, version string) { + for idx := range i.Dependencies { + if i.Dependencies[idx].Name == name { + i.Dependencies[idx].Version = version + return + } + } +} + +func (i *InstalledDeps) Remove(name string) { + for idx := range i.Dependencies { + if i.Dependencies[idx].Name == name { + i.Dependencies = append(i.Dependencies[:idx], i.Dependencies[idx+1:]...) + return + } + } +} + +func (i *InstalledDeps) Has(name string) bool { + return i.Get(name) != nil +} + +var ( + installedDepsFile = filepath.Join(fs.GetExecutablePath(), "dependencies.json") + reconcileMu sync.Mutex +) + +// readInstalledDeps reads the installed dependencies from disk. +func readInstalledDeps() (*InstalledDeps, error) { + installed := &InstalledDeps{Dependencies: []InstalledDep{}} + if !fs.Exists(installedDepsFile) { + return installed, nil + } + if err := fs.ReadJSON(installedDepsFile, installed); err != nil { + return nil, fmt.Errorf("error reading dependencies file: %v", err) + } + return installed, nil +} + +// writeInstalledDeps writes the installed dependencies to disk. +func writeInstalledDeps(installed *InstalledDeps) error { + return fs.WriteJSON(installedDepsFile, installed) +} + +// Reconcile ensures all dependencies are installed and up-to-date. +// This should be called at agent startup, before starting collectors. +func Reconcile(server string, skipCertValidation bool) error { + reconcileMu.Lock() + defer reconcileMu.Unlock() + + utils.Logger.Info("Starting dependency reconciliation...") + + installed, err := readInstalledDeps() + if err != nil { + return fmt.Errorf("failed to read installed dependencies: %v", err) + } + + desired := GetDependencies() + var criticalErrors []error + + // Process each desired dependency + for _, dep := range desired { + inst := installed.Get(dep.Name) + + if inst == nil { + // Not tracked yet + if dep.Exists() { + // MIGRATION: Already installed by previous version, just track it + utils.Logger.Info("Migrating existing dependency: %s (version %s)", dep.Name, dep.Version) + installed.Add(dep.Name, dep.Version) + } else { + // FRESH INSTALL: Download and configure + utils.Logger.Info("Installing new dependency: %s (version %s)", dep.Name, dep.Version) + if err := downloadDependency(dep, server, skipCertValidation); err != nil { + errMsg := fmt.Errorf("failed to download dependency %s: %v", dep.Name, err) + utils.Logger.ErrorF("%v", errMsg) + if dep.Critical { + criticalErrors = append(criticalErrors, errMsg) + } + continue + } + + if dep.Configure != nil { + if err := dep.Configure(); err != nil { + errMsg := fmt.Errorf("failed to configure dependency %s: %v", dep.Name, err) + utils.Logger.ErrorF("%v", errMsg) + if dep.Critical { + criticalErrors = append(criticalErrors, errMsg) + } + continue + } + } + installed.Add(dep.Name, dep.Version) + utils.Logger.Info("Dependency %s installed successfully", dep.Name) + } + } else if inst.Version != dep.Version { + // VERSION CHANGED: Download and update + utils.Logger.Info("Updating dependency: %s (%s -> %s)", dep.Name, inst.Version, dep.Version) + if err := downloadDependency(dep, server, skipCertValidation); err != nil { + errMsg := fmt.Errorf("failed to download dependency update %s: %v", dep.Name, err) + utils.Logger.ErrorF("%v", errMsg) + if dep.Critical { + criticalErrors = append(criticalErrors, errMsg) + } + continue + } + + updateFn := dep.Update + if updateFn == nil { + updateFn = dep.Configure + } + if updateFn != nil { + if err := updateFn(); err != nil { + errMsg := fmt.Errorf("failed to update dependency %s: %v", dep.Name, err) + utils.Logger.ErrorF("%v", errMsg) + if dep.Critical { + criticalErrors = append(criticalErrors, errMsg) + } + continue + } + } + installed.Update(dep.Name, dep.Version) + utils.Logger.Info("Dependency %s updated successfully", dep.Name) + } else { + // Same version, nothing to do + utils.Logger.Info("Dependency %s is up to date (version %s)", dep.Name, dep.Version) + } + } + + // CLEANUP: Remove dependencies that are no longer needed + for _, inst := range installed.Dependencies { + found := false + for _, dep := range desired { + if dep.Name == inst.Name { + found = true + break + } + } + if !found { + utils.Logger.Info("Removing obsolete dependency: %s", inst.Name) + // Find the uninstall function if we have it from old code + // For now, just remove from tracking + installed.Remove(inst.Name) + } + } + + // Save the updated installed dependencies + if err := writeInstalledDeps(installed); err != nil { + return fmt.Errorf("failed to write installed dependencies: %v", err) + } + + if len(criticalErrors) > 0 { + return fmt.Errorf("critical dependency errors: %v", criticalErrors) + } + + utils.Logger.Info("Dependency reconciliation completed") + return nil +} + +// downloadDependency downloads the dependency binary. +func downloadDependency(dep Dependency, server string, skipCertValidation bool) error { + if dep.DownloadURL == nil { + return fmt.Errorf("dependency %s has no download URL", dep.Name) + } + + url := dep.DownloadURL(server) + + // Use DownloadName if specified, otherwise use basename of BinaryPath + filename := dep.DownloadName + if filename == "" { + filename = filepath.Base(dep.BinaryPath) + } + + destDir := filepath.Dir(dep.BinaryPath) + + // Ensure destination directory exists + if err := os.MkdirAll(destDir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %v", destDir, err) + } + + if err := http.DownloadFile(url, map[string]string{}, filename, destDir, skipCertValidation); err != nil { + return err + } + + // Run post-download hook if defined (e.g., unzip) + if dep.PostDownload != nil { + if err := dep.PostDownload(); err != nil { + return fmt.Errorf("post-download failed: %v", err) + } + } + + return nil +} diff --git a/agent/dependency/deps_darwin.go b/agent/dependency/deps_darwin.go new file mode 100644 index 000000000..02244bf9b --- /dev/null +++ b/agent/dependency/deps_darwin.go @@ -0,0 +1,68 @@ +//go:build darwin +// +build darwin + +package dependency + +import ( + "fmt" + "path/filepath" + + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" +) + +const macosCollectorBinary = "utmstack-collector-mac" + +// GetDependencies returns the list of dependencies for macOS. +func GetDependencies() []Dependency { + basePath := fs.GetExecutablePath() + + return []Dependency{ + { + Name: "updater", + Version: UpdaterVersion, + BinaryPath: filepath.Join(basePath, UpdaterFile("")), + DownloadURL: func(server string) string { + return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile("")) + }, + Critical: false, + Configure: configureUpdater, + Uninstall: uninstallUpdater, + }, + { + Name: "macos-collector", + Version: MacosCollectorVersion, + BinaryPath: filepath.Join(basePath, macosCollectorBinary), + DownloadURL: func(server string) string { + return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, macosCollectorBinary) + }, + Critical: true, + Configure: configureMacosCollector, + }, + } +} + +func configureMacosCollector() error { + collectorPath := filepath.Join(fs.GetExecutablePath(), macosCollectorBinary) + return exec.Run("chmod", fs.GetExecutablePath(), "755", collectorPath) +} + +func configureUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + + // Set executable permissions + if err := exec.Run("chmod", fs.GetExecutablePath(), "755", updaterPath); err != nil { + return fmt.Errorf("error setting permissions on updater: %v", err) + } + + return exec.Run(updaterPath, fs.GetExecutablePath(), "install") +} + +func uninstallUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + if !fs.Exists(updaterPath) { + return nil + } + return exec.Run(updaterPath, fs.GetExecutablePath(), "uninstall") +} diff --git a/agent/dependency/deps_linux_amd64.go b/agent/dependency/deps_linux_amd64.go new file mode 100644 index 000000000..3b552475b --- /dev/null +++ b/agent/dependency/deps_linux_amd64.go @@ -0,0 +1,98 @@ +//go:build linux && amd64 +// +build linux,amd64 + +package dependency + +import ( + "fmt" + "path/filepath" + + "github.com/utmstack/UTMStack/agent/collector" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" +) + +// TODO: Remove after testing native collectors +// const beatsZip = "utmstack_agent_dependencies_linux.zip" + +// GetDependencies returns the list of dependencies for Linux amd64. +func GetDependencies() []Dependency { + basePath := fs.GetExecutablePath() + + return []Dependency{ + { + Name: "updater", + Version: UpdaterVersion, + BinaryPath: filepath.Join(basePath, UpdaterFile("")), + DownloadURL: func(server string) string { + return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile("")) + }, + Critical: false, + Configure: configureUpdater, + Uninstall: uninstallUpdater, + }, + // TODO: Remove beats dependency after testing native collectors + // { + // Name: "beats", + // Version: BeatsVersion, + // BinaryPath: filepath.Join(basePath, "beats", "filebeat", "filebeat"), + // DownloadName: beatsZip, + // DownloadURL: func(server string) string { + // return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, beatsZip) + // }, + // Critical: false, + // PostDownload: func() error { + // zipPath := filepath.Join(basePath, beatsZip) + // if err := archive.Unzip(zipPath, basePath); err != nil { + // return fmt.Errorf("error unzipping beats: %v", err) + // } + // // Remove zip after extraction + // os.Remove(zipPath) + // // Set executable permissions + // beatsPath := filepath.Join(basePath, "beats") + // exec.Run("chmod", basePath, "-R", "755", beatsPath) + // return nil + // }, + // Configure: configureBeats, + // Uninstall: uninstallBeats, + // }, + + // New beats dependency - only for uninstalling existing filebeat/winlogbeat + // No download, no install - native collectors are used instead + { + Name: "beats", + Version: BeatsVersion, + Critical: false, + Uninstall: uninstallBeats, + }, + } +} + +func configureUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + + // Set executable permissions + if err := exec.Run("chmod", fs.GetExecutablePath(), "755", updaterPath); err != nil { + return fmt.Errorf("error setting permissions on updater: %v", err) + } + + return exec.Run(updaterPath, fs.GetExecutablePath(), "install") +} + +func uninstallUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + if !fs.Exists(updaterPath) { + return nil + } + return exec.Run(updaterPath, fs.GetExecutablePath(), "uninstall") +} + +// TODO: Remove after testing native collectors +// func configureBeats() error { +// return collector.InstallAll() +// } + +func uninstallBeats() error { + return collector.UninstallAll() +} diff --git a/agent/dependency/deps_linux_arm64.go b/agent/dependency/deps_linux_arm64.go new file mode 100644 index 000000000..66473f04e --- /dev/null +++ b/agent/dependency/deps_linux_arm64.go @@ -0,0 +1,51 @@ +//go:build linux && arm64 +// +build linux,arm64 + +package dependency + +import ( + "fmt" + "path/filepath" + + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" +) + +// GetDependencies returns the list of dependencies for Linux arm64. +// Linux arm64 uses integrated collectors, no beats needed. +func GetDependencies() []Dependency { + basePath := fs.GetExecutablePath() + + return []Dependency{ + { + Name: "updater", + Version: UpdaterVersion, + BinaryPath: filepath.Join(basePath, UpdaterFile("")), + DownloadURL: func(server string) string { + return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile("")) + }, + Critical: false, + Configure: configureUpdater, + Uninstall: uninstallUpdater, + }, + } +} + +func configureUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + + if err := exec.Run("chmod", fs.GetExecutablePath(), "755", updaterPath); err != nil { + return fmt.Errorf("error setting permissions on updater: %v", err) + } + + return exec.Run(updaterPath, fs.GetExecutablePath(), "install") +} + +func uninstallUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + if !fs.Exists(updaterPath) { + return nil + } + return exec.Run(updaterPath, fs.GetExecutablePath(), "uninstall") +} diff --git a/agent/dependency/deps_windows_amd64.go b/agent/dependency/deps_windows_amd64.go new file mode 100644 index 000000000..067e6e529 --- /dev/null +++ b/agent/dependency/deps_windows_amd64.go @@ -0,0 +1,86 @@ +//go:build windows && amd64 +// +build windows,amd64 + +package dependency + +import ( + "fmt" + "path/filepath" + + "github.com/utmstack/UTMStack/agent/collector" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" +) + +// GetDependencies returns the list of dependencies for Windows amd64. +func GetDependencies() []Dependency { + basePath := fs.GetExecutablePath() + + return []Dependency{ + { + Name: "updater", + Version: UpdaterVersion, + BinaryPath: filepath.Join(basePath, UpdaterFile("")), + DownloadURL: func(server string) string { + return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile("")) + }, + Critical: false, // Agent can run without updater + Configure: configureUpdater, + Uninstall: uninstallUpdater, + }, + // TODO: Remove beats dependency after testing native collectors + // { + // Name: "beats", + // Version: BeatsVersion, + // BinaryPath: filepath.Join(basePath, "beats", "filebeat", "filebeat.exe"), + // DownloadName: beatsZip, + // DownloadURL: func(server string) string { + // return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, beatsZip) + // }, + // Critical: false, + // PostDownload: func() error { + // zipPath := filepath.Join(basePath, beatsZip) + // if err := archive.Unzip(zipPath, basePath); err != nil { + // return fmt.Errorf("error unzipping beats: %v", err) + // } + // // Remove zip after extraction + // os.Remove(zipPath) + // return nil + // }, + // Configure: configureBeats, + // Uninstall: uninstallBeats, + // }, + + // New beats dependency - only for uninstalling existing filebeat/winlogbeat + // No download, no install - native collectors are used instead + { + Name: "beats", + Version: BeatsVersion, + Critical: false, + Uninstall: uninstallBeats, + }, + } +} + +func configureUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + return exec.Run(updaterPath, fs.GetExecutablePath(), "install") +} + +func uninstallUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + if !fs.Exists(updaterPath) { + return nil + } + return exec.Run(updaterPath, fs.GetExecutablePath(), "uninstall") +} + +// TODO: Remove after testing native collectors +// func configureBeats() error { +// return collector.InstallAll() +// } + +func uninstallBeats() error { + return collector.UninstallAll() +} diff --git a/agent/dependency/deps_windows_arm64.go b/agent/dependency/deps_windows_arm64.go new file mode 100644 index 000000000..a45d4e8f4 --- /dev/null +++ b/agent/dependency/deps_windows_arm64.go @@ -0,0 +1,46 @@ +//go:build windows && arm64 +// +build windows,arm64 + +package dependency + +import ( + "fmt" + "path/filepath" + + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" +) + +// GetDependencies returns the list of dependencies for Windows arm64. +// Windows arm64 uses integrated Windows collector, no beats needed. +func GetDependencies() []Dependency { + basePath := fs.GetExecutablePath() + + return []Dependency{ + { + Name: "updater", + Version: UpdaterVersion, + BinaryPath: filepath.Join(basePath, UpdaterFile("")), + DownloadURL: func(server string) string { + return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile("")) + }, + Critical: false, + Configure: configureUpdater, + Uninstall: uninstallUpdater, + }, + } +} + +func configureUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + return exec.Run(updaterPath, fs.GetExecutablePath(), "install") +} + +func uninstallUpdater() error { + updaterPath := filepath.Join(fs.GetExecutablePath(), UpdaterFile("")) + if !fs.Exists(updaterPath) { + return nil + } + return exec.Run(updaterPath, fs.GetExecutablePath(), "uninstall") +} diff --git a/agent/go.mod b/agent/go.mod index c43246ec4..b90c5ee3e 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -9,26 +9,30 @@ require ( github.com/google/uuid v1.6.0 github.com/kardianos/service v1.2.4 github.com/netsampler/goflow2 v1.3.7 + github.com/spf13/cobra v1.10.2 github.com/tehmaze/netflow v0.0.0-20240303214733-8c13bb004068 - github.com/threatwinds/go-sdk v1.1.7 + github.com/threatwinds/go-sdk v1.1.14 github.com/threatwinds/logger v1.2.3 - golang.org/x/sys v0.40.0 - google.golang.org/grpc v1.78.0 + github.com/utmstack/UTMStack/shared v0.0.0 + golang.org/x/sys v0.41.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 - gopkg.in/yaml.v2 v2.4.0 gorm.io/gorm v1.31.1 ) +replace github.com/utmstack/UTMStack/shared => ../shared + require ( cel.dev/expr v0.25.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.14.2 // indirect - github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/go-windows v1.0.2 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect @@ -37,9 +41,10 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -55,7 +60,7 @@ require ( github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -67,8 +72,8 @@ require ( golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect howett.net/plist v1.0.1 // indirect diff --git a/agent/go.sum b/agent/go.sum index 99830d697..cfb551e58 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -6,12 +6,15 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= -github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= -github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= -github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -22,8 +25,10 @@ github.com/elastic/go-sysinfo v1.15.4 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886ey github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU= github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI= github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -50,8 +55,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -61,6 +66,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= @@ -106,8 +113,11 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -116,15 +126,14 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tehmaze/netflow v0.0.0-20240303214733-8c13bb004068 h1:1B+EAUqxEyPByCfk55tB21DtR7WF7Q2w71g7+uVkvIg= github.com/tehmaze/netflow v0.0.0-20240303214733-8c13bb004068/go.mod h1:QRP5wJOf7gGMGL2fCAfmh/5CMZQspRxT5DqghaPRrjM= -github.com/threatwinds/go-sdk v1.1.7 h1:2IJAWTCxZU4BDFiavPjH8MqpA/mam1QyIsjySbZLlRo= -github.com/threatwinds/go-sdk v1.1.7/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= +github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/threatwinds/logger v1.2.3 h1:V2SVAXzbq+/huCvIWOfqzMTH+WBHJxankyBgVG2hy1Y= github.com/threatwinds/logger v1.2.3/go.mod h1:N+bJKvF4FQNJZLfQpVYWpr6D8iEAFnAQfHYqH5iR1TI= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= @@ -145,16 +154,16 @@ github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -174,20 +183,20 @@ golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM= -google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -196,8 +205,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/agent/main.go b/agent/main.go index 395a831b5..81100ca63 100644 --- a/agent/main.go +++ b/agent/main.go @@ -1,306 +1,12 @@ package main import ( - "fmt" - "os" - "path/filepath" - "time" - - pb "github.com/utmstack/UTMStack/agent/agent" - "github.com/utmstack/UTMStack/agent/collectors" + "github.com/utmstack/UTMStack/agent/cmd" "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/database" - "github.com/utmstack/UTMStack/agent/logservice" - "github.com/utmstack/UTMStack/agent/models" - "github.com/utmstack/UTMStack/agent/modules" - "github.com/utmstack/UTMStack/agent/serv" - "github.com/utmstack/UTMStack/agent/updates" "github.com/utmstack/UTMStack/agent/utils" ) func main() { utils.InitLogger(config.ServiceLogFile) - - if len(os.Args) > 1 { - arg := os.Args[1] - - isInstalled, err := utils.CheckIfServiceIsInstalled("UTMStackAgent") - if err != nil { - fmt.Println("Error checking if service is installed: ", err) - os.Exit(1) - } - if arg != "install" && !isInstalled { - fmt.Println("UTMStackAgent service is not installed") - os.Exit(1) - } else if arg == "install" && isInstalled { - fmt.Println("UTMStackAgent service is already installed") - os.Exit(1) - } - - switch arg { - case "run": - serv.RunService() - case "install": - utils.PrintBanner() - fmt.Println("Installing UTMStackAgent service ...") - - cnf, utmKey := config.GetInitialConfig() - - fmt.Print("Checking server connection ... ") - if err := utils.ArePortsReachable(cnf.Server, config.AgentManagerPort, config.LogAuthProxyPort, config.DependenciesPort); err != nil { - fmt.Println("\nError trying to connect to server: ", err) - os.Exit(1) - } - fmt.Println("[OK]") - - fmt.Print("Downloading dependencies ... ") - if err := updates.DownloadFirstDependencies(cnf.Server, cnf.SkipCertValidation); err != nil { - fmt.Println("\nError downloading dependencies: ", err) - os.Exit(1) - } - fmt.Println("[OK]") - - fmt.Print("Configuring agent ... ") - err = pb.RegisterAgent(cnf, utmKey) - if err != nil { - fmt.Println("\nError registering agent: ", err) - os.Exit(1) - } - if err = config.SaveConfig(cnf); err != nil { - fmt.Println("\nError saving config: ", err) - os.Exit(1) - } - if err = modules.ConfigureCollectorFirstTime(); err != nil { - fmt.Println("\nError configuring collector: ", err) - os.Exit(1) - } - if err = collectors.InstallCollectors(); err != nil { - fmt.Println("\nError installing beats: ", err) - os.Exit(1) - } - if err := logservice.SetDataRetention(""); err != nil { - fmt.Println("\nError setting retention: ", err) - os.Exit(1) - } - fmt.Println("[OK]") - - fmt.Print(("Creating service ... ")) - serv.InstallService() - fmt.Println("[OK]") - fmt.Println("UTMStackAgent service installed correctly") - - case "enable-integration", "disable-integration": - fmt.Println("Changing integration status ...") - integration := os.Args[2] - proto := os.Args[3] - - tlsEnabled := false - for _, arg := range os.Args[4:] { - if arg == "--tls" { - tlsEnabled = true - break - } - } - - var port string - var err error - - if arg == "enable-integration" && tlsEnabled { - port, err = modules.ChangeIntegrationStatus(integration, proto, true, true) - } else if arg == "enable-integration" { - port, err = modules.ChangeIntegrationStatus(integration, proto, true, false) - } else { - port, err = modules.ChangeIntegrationStatus(integration, proto, false) - } - - if err != nil { - fmt.Println("Error:", err) - os.Exit(1) - } - - if arg == "enable-integration" && tlsEnabled { - fmt.Printf("Integration %s %s enabled with TLS on port %s\n", integration, proto, port) - } else if arg == "enable-integration" { - fmt.Printf("Integration %s %s enabled on port %s\n", integration, proto, port) - } else { - fmt.Printf("Integration %s %s disabled (port %s freed)\n", integration, proto, port) - } - time.Sleep(5 * time.Second) - - case "load-tls-certs": - if len(os.Args) < 4 { - fmt.Println("Usage: ./utmstack_agent load-tls-certs [ca_certificate_path]") - fmt.Println("Example: ./utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key /path/to/ca.crt") - os.Exit(1) - } - - userCertPath := os.Args[2] - userKeyPath := os.Args[3] - var userCAPath string - if len(os.Args) > 4 { - userCAPath = os.Args[4] - } - - fmt.Println("Loading user TLS certificates ...") - - fmt.Print("Validating certificate files ... ") - if err := utils.ValidateIntegrationCertificates(userCertPath, userKeyPath); err != nil { - fmt.Printf("\nError: Invalid certificate files: %v\n", err) - os.Exit(1) - } - fmt.Println("[OK]") - - fmt.Print("Installing certificates ... ") - src := utils.CertificateFiles{ - CertPath: userCertPath, - KeyPath: userKeyPath, - CAPath: userCAPath, - } - dest := utils.CertificateFiles{ - CertPath: config.IntegrationCertPath, - KeyPath: config.IntegrationKeyPath, - CAPath: config.IntegrationCAPath, - } - if err := utils.LoadUserCertificatesWithStruct(src, dest); err != nil { - fmt.Printf("\nError loading certificates: %v\n", err) - os.Exit(1) - } - fmt.Println("[OK]") - - fmt.Println("TLS certificates loaded successfully!") - time.Sleep(5 * time.Second) - - case "change-port": - fmt.Println("Changing integration port ...") - integration := os.Args[2] - proto := os.Args[3] - port := os.Args[4] - - old, err := modules.ChangePort(integration, proto, port) - if err != nil { - fmt.Println("Error trying to change integration port: ", err) - os.Exit(1) - } - fmt.Printf("Port changed correctly from %s to %s\n", old, port) - time.Sleep(5 * time.Second) - - case "change-retention": - fmt.Println("Changing log retention ...") - retention := os.Args[2] - - if err := logservice.SetDataRetention(retention); err != nil { - fmt.Println("Error trying to change retention: ", err) - os.Exit(1) - } - - fmt.Printf("Retention changed correctly to %s\n", retention) - time.Sleep(5 * time.Second) - - case "clean-logs": - fmt.Println("Cleaning old logs ...") - db := database.GetDB() - datR, err := logservice.GetDataRetention() - if err != nil { - fmt.Println("Error getting retention: ", err) - os.Exit(1) - } - _, err = db.DeleteOld(models.Log{}, datR) - if err != nil { - fmt.Println("Error cleaning logs: ", err) - os.Exit(1) - } - fmt.Println("Logs cleaned correctly") - time.Sleep(5 * time.Second) - - case "uninstall": - fmt.Println("Uninstalling UTMStackAgent service ...") - - fmt.Print("Stopping UTMStackUpdater service... ") - updaterPath := filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.UpdaterFile, "")) - if utils.CheckIfPathExist(updaterPath) { - err := utils.Execute(updaterPath, utils.GetMyPath(), "uninstall") - if err != nil { - fmt.Printf("Warning: %v\n", err) - } else { - fmt.Println("[OK]") - } - time.Sleep(2 * time.Second) - } else { - fmt.Println("[SKIPPED - not found]") - } - - cnf, err := config.GetCurrentConfig() - if err != nil { - fmt.Println("Error getting config: ", err) - os.Exit(1) - } - if err = pb.DeleteAgent(cnf); err != nil { - utils.Logger.ErrorF("error deleting agent: %v", err) - } - if err = collectors.UninstallCollectors(); err != nil { - utils.Logger.Fatal("error uninstalling collectors: %v", err) - } - os.Remove(config.ConfigurationFile) - - serv.UninstallService() - - fmt.Println("[OK]") - fmt.Println("UTMStackAgent service uninstalled correctly") - os.Exit(1) - case "help": - Help() - default: - fmt.Println("unknown option") - } - } else { - serv.RunService() - } -} - -func Help() { - fmt.Println("### UTMStackAgent ###") - fmt.Println("Usage:") - fmt.Println(" To run the service: ./utmstack_agent run") - fmt.Println(" To install the service: ./utmstack_agent install") - fmt.Println(" To enable integration: ./utmstack_agent enable-integration [--tls]") - fmt.Println(" To disable integration: ./utmstack_agent disable-integration ") - fmt.Println(" To change integration port: ./utmstack_agent change-port ") - fmt.Println(" To change log retention: ./utmstack_agent change-retention ") - fmt.Println(" To clean old logs: ./utmstack_agent clean-logs") - fmt.Println(" To load user TLS certificates: ./utmstack_agent load-tls-certs [ca]") - fmt.Println(" To check TLS certificates: ./utmstack_agent check-tls-certs") - fmt.Println(" To uninstall the service: ./utmstack_agent uninstall") - fmt.Println(" For help (this message): ./utmstack_agent help") - fmt.Println() - fmt.Println("Options:") - fmt.Println(" run Run the UTMStackAgent service") - fmt.Println(" install Install the UTMStackAgent service") - fmt.Println(" enable-integration Enable integration for a specific and ") - fmt.Println(" Available flag: --tls (enable TLS for TCP only)") - fmt.Println(" disable-integration Disable integration for a specific and (auto-disables TLS)") - fmt.Println(" change-port Change the port for a specific and to ") - fmt.Println(" change-retention Change the log retention to . Retention must be a number of megabytes. Example: 20") - fmt.Println(" clean-logs Clean old logs from the database") - fmt.Println(" load-tls-certs Load your own TLS certificates (RECOMMENDED for production)") - fmt.Println(" check-tls-certs Check status and validity of TLS certificates") - fmt.Println(" uninstall Uninstall the UTMStackAgent service") - fmt.Println(" help Display this help message") - fmt.Println() - fmt.Println("TLS Certificate Management:") - fmt.Println(" # Load your own certificates (RECOMMENDED)") - fmt.Println(" ./utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key /path/to/ca.crt") - fmt.Println(" ./utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key # Without CA") - fmt.Println() - fmt.Println("TLS Integration Examples:") - fmt.Println(" ./utmstack_agent enable-integration syslog tcp --tls # Enable with TLS") - fmt.Println(" ./utmstack_agent enable-integration syslog tcp # Enable without TLS (default)") - fmt.Println(" ./utmstack_agent disable-integration syslog tcp # Disable (auto-disables TLS)") - fmt.Println(" ./utmstack_agent check-tls-certs # Check certificate status") - fmt.Println() - fmt.Println("Note:") - fmt.Println(" - Make sure to run commands with appropriate permissions.") - fmt.Println(" - All commands require administrative privileges.") - fmt.Println(" - For detailed logs, check the service log file.") - fmt.Println() - os.Exit(0) + cmd.Execute() } diff --git a/agent/models/data.go b/agent/models/data.go index 051820585..7c676a48a 100644 --- a/agent/models/data.go +++ b/agent/models/data.go @@ -3,3 +3,9 @@ package models type DataRetention struct { Retention int `json:"retention"` } + +// MSGDS represents a message with its data source, used by collectors. +type MSGDS struct { + DataSource string + Message string +} diff --git a/agent/modules/configuration.go b/agent/modules/configuration.go deleted file mode 100644 index 4088d080f..000000000 --- a/agent/modules/configuration.go +++ /dev/null @@ -1,295 +0,0 @@ -package modules - -import ( - "fmt" - "net" - "os" - "strings" - "time" - - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/utils" -) - -type Port struct { - IsListen bool `json:"enabled"` - Port string `json:"value"` - TLSEnabled bool `json:"tls_enabled,omitempty"` -} - -type Integration struct { - TCP Port `json:"tcp_port,omitempty"` - UDP Port `json:"udp_port,omitempty"` -} - -type CollectorConfiguration struct { - Integrations map[string]Integration `json:"integrations"` -} - -func ReadCollectorConfig() (CollectorConfiguration, error) { - cnf := CollectorConfiguration{} - err := utils.ReadJson(config.CollectorFileName, &cnf) - if err != nil { - return cnf, err - } - - return cnf, nil -} - -func ConfigureCollectorFirstTime() error { - integrations := make(map[string]Integration) - for logTyp, ports := range config.ProtoPorts { - newIntegration := Integration{} - newIntegration.TCP.IsListen = false - newIntegration.TCP.Port = ports.TCP - newIntegration.UDP.IsListen = false - newIntegration.UDP.Port = ports.UDP - integrations[string(logTyp)] = newIntegration - } - return WriteCollectorConfig(integrations, config.CollectorFileName) -} - -func ChangeIntegrationStatus(logTyp string, proto string, isEnabled bool, tlsOptions ...bool) (string, error) { - var port string - cnf, err := ReadCollectorConfig() - if err != nil { - return "", fmt.Errorf("error reading collector config: %v", err) - } - - if valid := config.ValidateModuleType(logTyp); valid == "nil" { - return "", fmt.Errorf("invalid integration: %s", logTyp) - } - - integration := cnf.Integrations[logTyp] - switch proto { - case "tcp": - integration.TCP.IsListen = isEnabled - port = integration.TCP.Port - - // Handle TLS configuration if specified - if len(tlsOptions) > 0 && isEnabled { - if tlsOptions[0] { - if !utils.CheckIfPathExist(config.IntegrationCertPath) || !utils.CheckIfPathExist(config.IntegrationKeyPath) { - return "", fmt.Errorf("TLS certificates not found. Please load certificates first") - } - // Enable TLS - integration.TCP.TLSEnabled = true - mod := GetModule(logTyp) - if mod != nil && mod.IsPortListen(proto) { - mod.DisablePort(proto) - time.Sleep(200 * time.Millisecond) - err := mod.EnablePort(proto, true) - if err != nil { - return "", fmt.Errorf("error enabling TLS on running module: %v", err) - } - } - } else { - // Disable TLS - integration.TCP.TLSEnabled = false - mod := GetModule(logTyp) - if mod != nil && mod.IsPortListen(proto) { - mod.DisablePort(proto) - time.Sleep(200 * time.Millisecond) - err := mod.EnablePort(proto, false) - if err != nil { - return "", fmt.Errorf("error disabling TLS on running module: %v", err) - } - } - } - } - - // Auto-disable TLS when disabling integration - if !isEnabled { - integration.TCP.TLSEnabled = false - } - - case "udp": - integration.UDP.IsListen = isEnabled - port = integration.UDP.Port - - // TLS validation for UDP - if len(tlsOptions) > 0 && tlsOptions[0] { - return "", fmt.Errorf("TLS is not supported for UDP protocol. Use TCP for TLS connections") - } - - default: - return "", fmt.Errorf("invalid protocol: %s", proto) - } - - cnf.Integrations[logTyp] = integration - return port, WriteCollectorConfig(cnf.Integrations, config.CollectorFileName) -} - -func ChangePort(logTyp string, proto string, port string) (string, error) { - var old string - cnf, err := ReadCollectorConfig() - if err != nil { - return "", fmt.Errorf("error reading collector config: %v", err) - } - - if valid := config.ValidateModuleType(logTyp); valid == "nil" { - return "", fmt.Errorf("invalid integration: %s", logTyp) - } - - if changeValid := ValidateChangeInPort(port, logTyp); !changeValid { - return "", fmt.Errorf("change in port %s protocol %s not allowed for %s or out range %s-%s", port, proto, logTyp, config.PortRangeMin, config.PortRangeMax) - } - - if !IsPortAvailable(port, proto, &cnf, logTyp) { - return "", fmt.Errorf("port %s is already in use", port) - } - - integration := cnf.Integrations[logTyp] - switch proto { - case "tcp": - old = integration.TCP.Port - integration.TCP.Port = port - case "udp": - old = integration.UDP.Port - integration.UDP.Port = port - default: - return "", fmt.Errorf("invalid protocol: %s", proto) - } - - cnf.Integrations[logTyp] = integration - return old, WriteCollectorConfig(cnf.Integrations, config.CollectorFileName) -} - -func IsPortAvailable(port string, proto string, cnf *CollectorConfiguration, currentIntegration string) bool { - for integration, integrationConfig := range cnf.Integrations { - if integration != currentIntegration { - if integrationConfig.TCP.Port == port || integrationConfig.UDP.Port == port { - return false - } - } - } - - listener, err := net.Listen(proto, ":"+port) - if err != nil { - return false - } - - listener.Close() - - return true -} - -func WriteCollectorConfig(integrations map[string]Integration, filename string) error { - fileContent := "{\n \"integrations\": {\n" - for name, integration := range integrations { - fileContent += fmt.Sprintf(" \"%s\": {\n", name) - if integration.TCP.Port != "" { - fileContent += fmt.Sprintf(" \"tcp_port\": {\"enabled\": %t, \"value\": \"%s\"", integration.TCP.IsListen, integration.TCP.Port) - if integration.TCP.TLSEnabled { - fileContent += fmt.Sprintf(", \"tls_enabled\": %t", integration.TCP.TLSEnabled) - } - fileContent += "},\n" - } - if integration.UDP.Port != "" { - fileContent += fmt.Sprintf(" \"udp_port\": {\"enabled\": %t, \"value\": \"%s\"},\n", integration.UDP.IsListen, integration.UDP.Port) - } - if strings.HasSuffix(fileContent, ",\n") { - fileContent = fileContent[:len(fileContent)-2] + "\n" - } - fileContent += " },\n" - } - if strings.HasSuffix(fileContent, ",\n") { - fileContent = fileContent[:len(fileContent)-2] + "\n" - } - fileContent += " }\n}\n" - - err := os.WriteFile(filename, []byte(fileContent), 0644) - if err != nil { - return err - } - - return nil -} - -func WriteCollectorConfigFromModules(mod []Module, filename string) error { - integrations := make(map[string]Integration) - - for _, m := range mod { - integrations[m.GetDataType()] = Integration{ - TCP: Port{ - IsListen: m.IsPortListen("tcp"), - Port: m.GetPort("tcp"), - }, - UDP: Port{ - IsListen: m.IsPortListen("udp"), - Port: m.GetPort("udp"), - }, - } - } - return WriteCollectorConfig(integrations, filename) -} - -func EnableTLSForIntegration(logTyp string, proto string) (string, error) { - cnf, err := ReadCollectorConfig() - if err != nil { - return "", fmt.Errorf("error reading collector config: %v", err) - } - - if valid := config.ValidateModuleType(logTyp); valid == "nil" { - return "", fmt.Errorf("invalid integration: %s", logTyp) - } - - integration := cnf.Integrations[logTyp] - var port string - - switch proto { - case "tcp": - if integration.TCP.Port == "" { - return "", fmt.Errorf("TCP port not configured for %s", logTyp) - } - port = integration.TCP.Port - integration.TCP.TLSEnabled = true - - mod := GetModule(logTyp) - if mod != nil && mod.IsPortListen(proto) { - mod.DisablePort(proto) - time.Sleep(200 * time.Millisecond) - err := mod.EnablePort(proto, true) - if err != nil { - return port, fmt.Errorf("error enabling TLS on running module: %v", err) - } - } - case "udp": - return "", fmt.Errorf("TLS not supported for UDP protocol") - default: - return "", fmt.Errorf("invalid protocol: %s", proto) - } - - cnf.Integrations[logTyp] = integration - return port, WriteCollectorConfig(cnf.Integrations, config.CollectorFileName) -} - -func DisableTLSForIntegration(logTyp string, proto string) error { - cnf, err := ReadCollectorConfig() - if err != nil { - return fmt.Errorf("error reading collector config: %v", err) - } - - integration := cnf.Integrations[logTyp] - switch proto { - case "tcp": - integration.TCP.TLSEnabled = false - - mod := GetModule(logTyp) - if mod != nil && mod.IsPortListen(proto) { - mod.DisablePort(proto) - time.Sleep(200 * time.Millisecond) - err := mod.EnablePort(proto, false) - if err != nil { - return fmt.Errorf("error disabling TLS on running module: %v", err) - } - } - case "udp": - return fmt.Errorf("TLS not supported for UDP protocol") - default: - return fmt.Errorf("invalid protocol: %s", proto) - } - - cnf.Integrations[logTyp] = integration - return WriteCollectorConfig(cnf.Integrations, config.CollectorFileName) -} diff --git a/agent/modules/modules.go b/agent/modules/modules.go deleted file mode 100644 index b5e247cba..000000000 --- a/agent/modules/modules.go +++ /dev/null @@ -1,153 +0,0 @@ -package modules - -import ( - "time" - - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/utils" -) - -const ( - delayCheckSyslogCnfig = 10 * time.Second -) - -var ( - moCache = make([]Module, 0, 10) -) - -type Module interface { - GetDataType() string - IsPortListen(proto string) bool - SetNewPort(proto string, port string) - GetPort(proto string) string - EnablePort(proto string, enableTLS bool) error - DisablePort(proto string) -} - -func GetModule(typ string) Module { - switch config.ValidateModuleType(typ) { - case "syslog": - return GetSyslogModule(typ, config.ProtoPorts[config.DataType(typ)]) - case "netflow": - return GetNetflowModule() - default: - return nil - } -} - -func StartModules() { - for { - time.Sleep(delayCheckSyslogCnfig) - logCollectorConfig, err := ReadCollectorConfig() - if err != nil { - utils.Logger.Fatal("error reading collector config: %v", err) - } - - for intType, cnf := range logCollectorConfig.Integrations { - index := -1 - for i, mod := range moCache { - if mod.GetDataType() == intType { - index = i - break - } - } - - if index == -1 { - newModule := GetModule(intType) - if newModule == nil { - utils.Logger.LogF(100, "error getting module %s", intType) - continue - } - moCache = append(moCache, newModule) - index = len(moCache) - 1 - } - - configs, err := processConfigs(moCache[index], cnf) - if err != nil { - utils.Logger.ErrorF("error processing configs: %v", err) - continue - } - - for proto, conf := range configs { - changeAllowed := true - port := "" - - switch proto { - case "tcp": - port = cnf.TCP.Port - case "udp": - port = cnf.UDP.Port - } - - if port != "" && moCache[index].GetPort(proto) != port { - changeAllowed = ValidateChangeInPort(port, intType) - } - if conf[0] { - moCache[index].DisablePort(proto) - if conf[1] { - time.Sleep(200 * time.Millisecond) - } - } - if changeAllowed { - moCache[index].SetNewPort(proto, port) - if conf[1] { - enableTLS := proto == "tcp" && cnf.TCP.TLSEnabled - - err := moCache[index].EnablePort(proto, enableTLS) - if err != nil { - utils.Logger.ErrorF("error enabling port for %s %s: %v", intType, proto, err) - } - } - } else { - utils.Logger.Info("change in port %s protocol %s not allowed for %s or out range %s-%s", port, proto, intType, config.PortRangeMin, config.PortRangeMax) - err := WriteCollectorConfigFromModules(moCache, config.CollectorFileName) - if err != nil { - utils.Logger.ErrorF("error fixing collector config: %v", err) - continue - } - } - } - } - } -} - -func processConfigs(mod Module, cnf Integration) (map[string][]bool, error) { - configs := make(map[string][]bool) // first bool is if is necessary kill the port, second bool is if is necessary start the port - - protocols := []string{"tcp", "udp"} - for _, protocol := range protocols { - var isEnabled bool - var port string - - switch protocol { - case "tcp": - isEnabled = cnf.TCP.IsListen - port = cnf.TCP.Port - case "udp": - isEnabled = cnf.UDP.IsListen - port = cnf.UDP.Port - } - - if mod.IsPortListen(protocol) && !isEnabled { - configs[protocol] = []bool{true, false} - } else if !mod.IsPortListen(protocol) && isEnabled { - configs[protocol] = []bool{false, true} - } else if mod.IsPortListen(protocol) && isEnabled && mod.GetPort(protocol) != port { - configs[protocol] = []bool{true, true} - } else { - configs[protocol] = []bool{false, false} - } - } - - return configs, nil -} - -// ValidateChangeInPort returns true if the port change is allowed -func ValidateChangeInPort(newPort string, dataType string) bool { - for _, logType := range config.ProhibitedPortsChange { - if string(logType) == dataType { - return false - } - } - return config.PortRangeMin <= newPort && newPort <= config.PortRangeMax -} diff --git a/agent/modules/netflow.go b/agent/modules/netflow.go deleted file mode 100644 index e3ff518b2..000000000 --- a/agent/modules/netflow.go +++ /dev/null @@ -1,352 +0,0 @@ -package modules - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "net" - "strconv" - "strings" - "sync" - "time" - - "github.com/netsampler/goflow2/decoders/netflow" - "github.com/netsampler/goflow2/decoders/netflowlegacy" - tehmaze "github.com/tehmaze/netflow" - "github.com/tehmaze/netflow/session" - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/logservice" - "github.com/utmstack/UTMStack/agent/parser" - "github.com/utmstack/UTMStack/agent/utils" -) - -var ( - netflowModule *NetflowModule - netflowOnce sync.Once -) - -// templateSystem implements netflow.NetFlowTemplateSystem for goflow2 -type templateSystem struct { - templates map[uint16]map[uint32]map[uint16]interface{} - mu sync.RWMutex -} - -func newTemplateSystem() *templateSystem { - return &templateSystem{ - templates: make(map[uint16]map[uint32]map[uint16]interface{}), - } -} - -func (t *templateSystem) GetTemplate(version uint16, obsDomainId uint32, templateId uint16) (interface{}, error) { - t.mu.RLock() - defer t.mu.RUnlock() - - if versionMap, ok := t.templates[version]; ok { - if domainMap, ok := versionMap[obsDomainId]; ok { - if template, ok := domainMap[templateId]; ok { - return template, nil - } - } - } - return nil, fmt.Errorf("template not found: version=%d, obsDomainId=%d, templateId=%d", version, obsDomainId, templateId) -} - -func (t *templateSystem) AddTemplate(version uint16, obsDomainId uint32, template interface{}) { - t.mu.Lock() - defer t.mu.Unlock() - - if _, ok := t.templates[version]; !ok { - t.templates[version] = make(map[uint32]map[uint16]interface{}) - } - if _, ok := t.templates[version][obsDomainId]; !ok { - t.templates[version][obsDomainId] = make(map[uint16]interface{}) - } - - // Extract template ID based on type - var templateId uint16 - switch tmpl := template.(type) { - case netflow.TemplateRecord: - templateId = tmpl.TemplateId - case netflow.IPFIXOptionsTemplateRecord: - templateId = tmpl.TemplateId - case netflow.NFv9OptionsTemplateRecord: - templateId = tmpl.TemplateId - default: - return - } - - t.templates[version][obsDomainId][templateId] = template -} - -type NetflowModule struct { - DataType string - Parser parser.Parser - LegacyDecoders map[string]*tehmaze.Decoder // For v1, v6, v7 (tehmaze/netflow) - TemplateSystem map[string]*templateSystem // For v5, v9, IPFIX (goflow2) - Listener *net.UDPConn - CTX context.Context - Cancel context.CancelFunc - IsEnabled bool - mu sync.RWMutex -} - -func GetNetflowModule() *NetflowModule { - netflowOnce.Do(func() { - netflowModule = &NetflowModule{ - Parser: parser.GetParser("netflow"), - DataType: "netflow", - IsEnabled: false, - LegacyDecoders: make(map[string]*tehmaze.Decoder), - TemplateSystem: make(map[string]*templateSystem), - } - }) - return netflowModule -} - -func (m *NetflowModule) getOrCreateTemplateSystem(addr string) *templateSystem { - m.mu.Lock() - defer m.mu.Unlock() - - if ts, ok := m.TemplateSystem[addr]; ok { - return ts - } - ts := newTemplateSystem() - m.TemplateSystem[addr] = ts - return ts -} - -func (m *NetflowModule) getOrCreateLegacyDecoder(addr string) *tehmaze.Decoder { - m.mu.Lock() - defer m.mu.Unlock() - - if d, ok := m.LegacyDecoders[addr]; ok { - return d - } - s := session.New() - d := tehmaze.NewDecoder(s) - m.LegacyDecoders[addr] = d - return d -} - -func (m *NetflowModule) removeLegacyDecoder(addr string) { - m.mu.Lock() - defer m.mu.Unlock() - delete(m.LegacyDecoders, addr) -} - -func (m *NetflowModule) EnablePort(proto string, enableTLS bool) error { - if enableTLS { - return fmt.Errorf("TLS not supported for NetFlow protocol") - } - - if proto == "udp" && !m.IsEnabled { - port, err := strconv.Atoi(config.ProtoPorts[config.DataTypeNetflow].UDP) - if err != nil { - utils.Logger.ErrorF("error converting port to int: %v", err) - return err - } - - listener, err := net.ListenUDP("udp", &net.UDPAddr{ - Port: port, - IP: net.ParseIP("0.0.0.0"), - }) - if err != nil { - utils.Logger.ErrorF("error listening netflow: %v", err) - return err - } - - m.IsEnabled = true - m.Listener = listener - m.CTX, m.Cancel = context.WithCancel(context.Background()) - - utils.Logger.Info("Server %s listening in port: %s protocol: UDP", m.DataType, config.ProtoPorts[config.DataTypeNetflow].UDP) - - buffer := make([]byte, 65535) - - go func() { - for { - select { - case <-m.CTX.Done(): - return - default: - m.Listener.SetDeadline(time.Now().Add(1 * time.Second)) - - length, addr, err := m.Listener.ReadFromUDP(buffer) - if err != nil { - if errors.Is(err, net.ErrClosed) { - return - } - - var netOpErr *net.OpError - ok := errors.As(err, &netOpErr) - if ok && netOpErr.Timeout() { - continue - } - - utils.Logger.ErrorF("error connecting with netflow listener: %v", err) - continue - } - - // Validate packet structure before attempting to decode - packetData := buffer[:length] - packetInfo, validationErr := validateNetflowPacket(packetData) - if validationErr != nil { - utils.Logger.ErrorF("invalid NetFlow packet from %s (length: %d bytes): %v", addr.String(), length, validationErr) - continue - } - - var message interface{} - - // Use hybrid approach: goflow2 for v5/v9/IPFIX, tehmaze for v1/v6/v7 - switch packetInfo.version { - case 5: - // Use goflow2 for NetFlow v5 - msg, err := netflowlegacy.DecodeMessage(bytes.NewBuffer(packetData)) - if err != nil { - utils.Logger.ErrorF("error decoding %s message from %s: %v", packetInfo.versionName, addr.String(), err) - continue - } - message = msg - - case 9, 10: - // Use goflow2 for NetFlow v9 and IPFIX - ts := m.getOrCreateTemplateSystem(addr.String()) - msg, err := netflow.DecodeMessage(bytes.NewBuffer(packetData), ts) - if err != nil { - // Template not found is expected when data arrives before template - // This is normal NetFlow v9/IPFIX behavior, don't log as error - if !strings.Contains(err.Error(), "template not found") { - utils.Logger.ErrorF("error decoding %s message from %s: %v", packetInfo.versionName, addr.String(), err) - } - continue - } - message = msg - - case 1, 6, 7: - // Use tehmaze/netflow for legacy versions (v1, v6, v7) - d := m.getOrCreateLegacyDecoder(addr.String()) - msg, err := d.Read(bytes.NewBuffer(packetData)) - if err != nil { - utils.Logger.ErrorF("error decoding %s message from %s: %v", packetInfo.versionName, addr.String(), err) - m.removeLegacyDecoder(addr.String()) - continue - } - message = msg - - default: - utils.Logger.ErrorF("unsupported NetFlow version %d from %s", packetInfo.version, addr.String()) - continue - } - - err = m.Parser.ProcessData(parser.NetflowObject{ - Remote: addr.String(), - Message: message, - }, "", logservice.LogQueue) - if err != nil { - utils.Logger.ErrorF("error parsing netflow: %v", err) - } - } - } - }() - return nil - } - return fmt.Errorf("NetFlow only supports UDP protocol") -} - -func (m *NetflowModule) DisablePort(proto string) { - if proto == "udp" && m.IsEnabled { - utils.Logger.Info("Server %s closed in port: %s protocol: UDP", m.DataType, config.ProtoPorts[config.DataTypeNetflow].UDP) - m.Cancel() - m.Listener.Close() - m.IsEnabled = false - } -} - -func (m *NetflowModule) GetDataType() string { - return m.DataType -} - -func (m *NetflowModule) IsPortListen(proto string) bool { - switch proto { - case "udp": - return m.IsEnabled - default: - return false - } -} - -func (m *NetflowModule) SetNewPort(_ string, _ string) { - //TODO: implement this function -} - -func (m *NetflowModule) GetPort(proto string) string { - switch proto { - case "udp": - return config.ProtoPorts[config.DataTypeNetflow].UDP - default: - return "" - } -} - -// netflowPacketInfo contains basic information about a NetFlow packet for validation -type netflowPacketInfo struct { - version uint16 - count uint16 - minSize int - versionName string -} - -// validateNetflowPacket checks if a NetFlow packet has valid structure before decoding -// Returns packet info if valid, error otherwise -func validateNetflowPacket(data []byte) (*netflowPacketInfo, error) { - if len(data) < 4 { - return nil, fmt.Errorf("packet too small: %d bytes (minimum 4 bytes for version and count)", len(data)) - } - - version := binary.BigEndian.Uint16(data[0:2]) - count := binary.BigEndian.Uint16(data[2:4]) - - info := &netflowPacketInfo{ - version: version, - count: count, - } - - switch version { - case 1: - info.versionName = "NetFlow v1" - info.minSize = 24 + int(count)*48 // header (24) + records (48 each) - case 5: - info.versionName = "NetFlow v5" - info.minSize = 24 + int(count)*48 // header (24) + records (48 each) - case 6: - info.versionName = "NetFlow v6" - info.minSize = 24 + int(count)*52 // header (24) + records (52 each) - case 7: - info.versionName = "NetFlow v7" - info.minSize = 24 + int(count)*52 // header (24) + records (52 each) - case 9: - info.versionName = "NetFlow v9" - // NetFlow v9 header is 20 bytes, minimum packet size is just the header - info.minSize = 20 - case 10: - info.versionName = "IPFIX" - // IPFIX header is 16 bytes, field at offset 2-4 is the total message length - info.minSize = 16 - ipfixLength := binary.BigEndian.Uint16(data[2:4]) - if int(ipfixLength) != len(data) { - return nil, fmt.Errorf("IPFIX length mismatch: header says %d bytes, received %d bytes", ipfixLength, len(data)) - } - return info, nil - default: - return nil, fmt.Errorf("unsupported NetFlow version: %d", version) - } - - if len(data) < info.minSize { - return nil, fmt.Errorf("%s packet too small: received %d bytes, minimum expected %d bytes (count=%d)", - info.versionName, len(data), info.minSize, count) - } - - return info, nil -} diff --git a/agent/modules/syslog.go b/agent/modules/syslog.go deleted file mode 100644 index 5d0987a11..000000000 --- a/agent/modules/syslog.go +++ /dev/null @@ -1,634 +0,0 @@ -package modules - -import ( - "bufio" - "context" - "crypto/tls" - "errors" - "fmt" - "io" - "net" - "os" - "strconv" - "strings" - "sync" - "time" - - "github.com/threatwinds/go-sdk/entities" - "github.com/threatwinds/go-sdk/plugins" - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/logservice" - "github.com/utmstack/UTMStack/agent/parser" - "github.com/utmstack/UTMStack/agent/utils" -) - -var ( - syslogModules = make(map[string]*SyslogModule) - syslogMutex sync.RWMutex -) - -const ( - MinBufferSize = 480 - RecommendedBufferSize = 2048 - MaxBufferSize = 8192 - UDPBufferSize = 2048 -) - -type FramingMethod int - -const ( - FramingNewline FramingMethod = iota - FramingOctetCounting -) - -type SyslogModule struct { - DataType string - TCPListener listenerTCP - UDPListener listenerUDP - Parser parser.Parser - mu sync.RWMutex -} - -type listenerTCP struct { - Listener net.Listener - CTX context.Context - Cancel context.CancelFunc - IsEnabled bool - Port string - TLSEnabled bool -} - -type listenerUDP struct { - Listener net.PacketConn - CTX context.Context - Cancel context.CancelFunc - IsEnabled bool - Port string -} - -func GetSyslogModule(dataType string, protoPorts config.ProtoPort) *SyslogModule { - syslogMutex.Lock() - defer syslogMutex.Unlock() - - if mod, exists := syslogModules[dataType]; exists { - return mod - } - - newModule := &SyslogModule{ - DataType: dataType, - TCPListener: listenerTCP{ - IsEnabled: false, - Port: protoPorts.TCP, - }, - UDPListener: listenerUDP{ - IsEnabled: false, - Port: protoPorts.UDP, - }, - Parser: parser.GetParser(dataType), - } - - syslogModules[dataType] = newModule - return newModule -} - -func (m *SyslogModule) GetDataType() string { - return m.DataType -} - -func (m *SyslogModule) IsPortListen(proto string) bool { - m.mu.RLock() - defer m.mu.RUnlock() - - switch proto { - case "tcp": - return m.TCPListener.IsEnabled - case "udp": - return m.UDPListener.IsEnabled - default: - return false - } -} - -func (m *SyslogModule) SetNewPort(proto string, port string) { - m.mu.Lock() - defer m.mu.Unlock() - - switch proto { - case "tcp": - m.TCPListener.Port = port - case "udp": - m.UDPListener.Port = port - } -} - -func (m *SyslogModule) GetPort(proto string) string { - m.mu.RLock() - defer m.mu.RUnlock() - - switch proto { - case "tcp": - return m.TCPListener.Port - case "udp": - return m.UDPListener.Port - default: - return "" - } -} - -func (m *SyslogModule) EnablePort(proto string, enableTLS bool) error { - switch proto { - case "tcp": - if enableTLS { - if !utils.CheckIfPathExist(config.IntegrationCertPath) || !utils.CheckIfPathExist(config.IntegrationKeyPath) { - return fmt.Errorf("TLS certificates not found. Please load certificates first") - } - } - - m.TCPListener.TLSEnabled = enableTLS - go m.enableTCP() - return nil - case "udp": - if enableTLS { - return fmt.Errorf("TLS not supported for UDP protocol") - } - go m.enableUDP() - return nil - default: - return fmt.Errorf("unsupported protocol: %s", proto) - } -} - -func (m *SyslogModule) DisablePort(proto string) { - switch proto { - case "tcp": - m.disableTCP() - case "udp": - m.disableUDP() - } -} - -func (m *SyslogModule) enableTCP() { - m.mu.Lock() - if m.TCPListener.IsEnabled || m.TCPListener.Port == "" { - m.mu.Unlock() - return - } - - listener, err := net.Listen("tcp", "0.0.0.0:"+m.TCPListener.Port) - if err != nil { - m.mu.Unlock() - utils.Logger.ErrorF("error listening TCP in port %s: %v", m.TCPListener.Port, err) - return - } - - // Solo setear IsEnabled DESPUÉS de confirmar que el listener está activo - m.TCPListener.IsEnabled = true - m.TCPListener.Listener = listener - m.TCPListener.CTX, m.TCPListener.Cancel = context.WithCancel(context.Background()) - m.mu.Unlock() - - utils.Logger.Info("Server %s listening in port: %s protocol: TCP", m.DataType, m.TCPListener.Port) - if m.TCPListener.TLSEnabled { - utils.Logger.Info("Server %s TLS enabled in port: %s protocol: TCP", m.DataType, m.TCPListener.Port) - } - - go func() { - defer func() { - err = m.TCPListener.Listener.Close() - if err != nil { - utils.Logger.ErrorF("error closing tcp listener: %v", err) - } - }() - for { - select { - case <-m.TCPListener.CTX.Done(): - return - default: - conn, err := m.TCPListener.Listener.Accept() - if err != nil { - if errors.Is(err, net.ErrClosed) { - return - } - - var netOpErr *net.OpError - ok := errors.As(err, &netOpErr) - if ok && netOpErr.Timeout() { - continue - } - - utils.Logger.ErrorF("error connecting with tcp listener: %v", err) - continue - } - - // Connection handling based on TLS configuration - if m.TCPListener.TLSEnabled { - go m.handleTLSConnection(conn) - } else { - go m.handleConnectionTCP(conn) - } - } - } - }() -} - -func (m *SyslogModule) enableUDP() { - m.mu.Lock() - if m.UDPListener.IsEnabled || m.UDPListener.Port == "" { - m.mu.Unlock() - return - } - - listener, err := net.ListenPacket("udp", "0.0.0.0:"+m.UDPListener.Port) - if err != nil { - m.mu.Unlock() - utils.Logger.ErrorF("error listening UDP in port %s: %v", m.UDPListener.Port, err) - return - } - - udpListener, ok := listener.(*net.UDPConn) - if !ok { - m.mu.Unlock() - utils.Logger.ErrorF("could not assert to *net.UDPConn") - listener.Close() - return - } - - // Solo setear IsEnabled DESPUÉS de confirmar que el listener está activo - m.UDPListener.IsEnabled = true - m.UDPListener.Listener = listener - m.UDPListener.CTX, m.UDPListener.Cancel = context.WithCancel(context.Background()) - m.mu.Unlock() - - utils.Logger.Info("Server %s listening in port: %s protocol: UDP", m.DataType, m.UDPListener.Port) - - buffer := make([]byte, UDPBufferSize) - msgChannel := make(chan config.MSGDS) - - go m.handleConnectionUDP(msgChannel) - - go func() { - defer func() { - err = m.UDPListener.Listener.Close() - if err != nil { - utils.Logger.ErrorF("error closing udp listener: %v", err) - } - }() - for { - select { - case <-m.UDPListener.CTX.Done(): - return - default: - udpListener.SetDeadline(time.Now().Add(time.Second * 1)) - - n, add, err := listener.ReadFrom(buffer) - if err != nil { - if errors.Is(err, net.ErrClosed) { - return - } - - var netOpErr *net.OpError - ok := errors.As(err, &netOpErr) - if ok && netOpErr.Timeout() { - continue - } - - utils.Logger.ErrorF("error connecting with udp listener: %v", err) - continue - } - remoteAddr := add.String() - remoteAddr, _, err = net.SplitHostPort(remoteAddr) - if err != nil { - utils.Logger.ErrorF("error getting remote addr: %v", err) - continue - } - if remoteAddr == "127.0.0.1" { - remoteAddr, err = os.Hostname() - if err != nil { - utils.Logger.ErrorF("error getting hostname: %v\n", err) - continue - } - } - msgChannel <- config.MSGDS{ - DataSource: remoteAddr, - Message: string(buffer[:n]), - } - } - } - }() -} - -func (m *SyslogModule) disableTCP() { - m.mu.Lock() - defer m.mu.Unlock() - - if m.TCPListener.IsEnabled && m.TCPListener.Port != "" { - utils.Logger.Info("Server %s closed in port: %s protocol: TCP", m.DataType, m.TCPListener.Port) - - if m.TCPListener.Listener != nil { - if err := m.TCPListener.Listener.Close(); err != nil { - utils.Logger.ErrorF("error closing TCP listener: %v", err) - } - } - - m.TCPListener.Cancel() - m.TCPListener.IsEnabled = false - } -} - -func (m *SyslogModule) disableUDP() { - m.mu.Lock() - defer m.mu.Unlock() - - if m.UDPListener.IsEnabled && m.UDPListener.Port != "" { - utils.Logger.Info("Server %s closed in port: %s protocol: UDP", m.DataType, m.UDPListener.Port) - - if m.UDPListener.Listener != nil { - if err := m.UDPListener.Listener.Close(); err != nil { - utils.Logger.ErrorF("error closing UDP listener: %v", err) - } - } - - m.UDPListener.Cancel() - m.UDPListener.IsEnabled = false - } -} - -// detectFramingMethod detects the syslog framing method by peeking at the first byte -func detectFramingMethod(reader *bufio.Reader) (FramingMethod, error) { - firstByte, err := reader.Peek(1) - if err != nil { - utils.Logger.ErrorF("failed to peek first byte for framing detection: %v", err) - return 0, fmt.Errorf("failed to peek first byte: %w", err) - } - - if firstByte[0] >= '0' && firstByte[0] <= '9' { - return FramingOctetCounting, nil - } - - if firstByte[0] == '<' { - return FramingNewline, nil - } - - utils.Logger.ErrorF("unknown framing method detected, first byte: 0x%02x", firstByte[0]) - return 0, fmt.Errorf("unknown framing method, first byte: 0x%02x", firstByte[0]) -} - -// readOctetCountingFrame reads a syslog message using octet counting framing method -func readOctetCountingFrame(reader *bufio.Reader) (string, error) { - lengthStr, err := reader.ReadString(' ') - if err != nil { - utils.Logger.ErrorF("failed to read message length in octet counting frame: %v", err) - return "", fmt.Errorf("failed to read message length: %w", err) - } - - lengthStr = strings.TrimSuffix(lengthStr, " ") - msgLen, err := strconv.Atoi(lengthStr) - if err != nil { - utils.Logger.ErrorF("invalid message length '%s' in octet counting frame: %v", lengthStr, err) - return "", fmt.Errorf("invalid message length '%s': %w", lengthStr, err) - } - - if msgLen < 1 { - utils.Logger.ErrorF("message length %d is too small (minimum 1 byte)", msgLen) - return "", fmt.Errorf("message length %d is too small (minimum 1)", msgLen) - } - if msgLen > MaxBufferSize { - utils.Logger.ErrorF("message length %d exceeds maximum %d bytes", msgLen, MaxBufferSize) - return "", fmt.Errorf("message length %d exceeds maximum %d", msgLen, MaxBufferSize) - } - - msgBytes := make([]byte, msgLen) - _, err = io.ReadFull(reader, msgBytes) - if err != nil { - utils.Logger.ErrorF("failed to read %d byte message body: %v", msgLen, err) - return "", fmt.Errorf("failed to read %d byte message body: %w", msgLen, err) - } - - return string(msgBytes), nil -} - -// readNewlineFrame reads a syslog message using newline-delimited framing method -func readNewlineFrame(reader *bufio.Reader) (string, error) { - message, err := reader.ReadString('\n') - if err != nil { - utils.Logger.ErrorF("failed to read newline-delimited message: %v", err) - return "", fmt.Errorf("failed to read newline-delimited message: %w", err) - } - return message, nil -} - -// readSyslogMessage reads a syslog message with automatic framing detection -func readSyslogMessage(reader *bufio.Reader) (string, error) { - method, err := detectFramingMethod(reader) - if err != nil { - return "", err - } - - switch method { - case FramingOctetCounting: - return readOctetCountingFrame(reader) - case FramingNewline: - return readNewlineFrame(reader) - default: - utils.Logger.ErrorF("unsupported framing method: %d", method) - return "", fmt.Errorf("unsupported framing method: %d", method) - } -} - -func (m *SyslogModule) handleConnectionTCP(c net.Conn) { - defer c.Close() - reader := bufio.NewReader(c) - remoteAddr := c.RemoteAddr().String() - - var err error - remoteAddr, _, err = net.SplitHostPort(remoteAddr) - if err != nil { - utils.Logger.ErrorF("error spliting host and port: %v", err) - } - - if remoteAddr == "127.0.0.1" { - remoteAddr, err = os.Hostname() - if err != nil { - utils.Logger.ErrorF("error getting hostname: %v\n", err) - } - } - - // Detect and reject TLS connections when TLS is disabled - c.SetReadDeadline(time.Now().Add(5 * time.Second)) - firstBytes := make([]byte, 3) - n, err := reader.Read(firstBytes) - if err != nil { - utils.Logger.ErrorF("error reading initial bytes from %s: %v", remoteAddr, err) - return - } - - // TLS handshake starts with: 0x16 (22 decimal) for TLS 1.0-1.3 - if n >= 1 && firstBytes[0] == 0x16 { - utils.Logger.ErrorF("TLS connection rejected from %s: TLS is disabled, only plain text connections accepted", remoteAddr) - return - } - - // Reset deadline and create a new reader that includes the read bytes - c.SetReadDeadline(time.Time{}) - reader = bufio.NewReader(io.MultiReader(strings.NewReader(string(firstBytes[:n])), reader)) - - msgChannel := make(chan config.MSGDS) - go m.handleMessageTCP(msgChannel) - - for { - select { - case <-m.TCPListener.CTX.Done(): - return - default: - message, err := readSyslogMessage(reader) - if err != nil { - if err == io.EOF { - utils.Logger.Info("TCP connection closed by %s", remoteAddr) - return - } - if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - utils.Logger.Info("TCP connection timeout from %s", remoteAddr) - return - } - utils.Logger.ErrorF("error reading syslog message from %s: %v", remoteAddr, err) - return - } - msgChannel <- config.MSGDS{ - DataSource: remoteAddr, - Message: message, - } - } - } -} - -func (m *SyslogModule) handleTLSConnection(conn net.Conn) { - defer conn.Close() - - remoteAddr := conn.RemoteAddr().String() - remoteAddr, _, err := net.SplitHostPort(remoteAddr) - if err != nil { - utils.Logger.ErrorF("error splitting host and port: %v", err) - remoteAddr = "unknown" - } - - if remoteAddr == "127.0.0.1" { - if hostname, err := os.Hostname(); err == nil { - remoteAddr = hostname - } - } - - tlsConfig, err := utils.LoadIntegrationTLSConfig( - config.IntegrationCertPath, - config.IntegrationKeyPath, - ) - if err != nil { - utils.Logger.ErrorF("error loading TLS config: %v", err) - return - } - - tlsConn := tls.Server(conn, tlsConfig) - - conn.SetDeadline(time.Now().Add(10 * time.Second)) - if err := tlsConn.Handshake(); err != nil { - utils.Logger.ErrorF("TLS handshake failed from %s: %v", remoteAddr, err) - return - } - // Keep a reasonable read timeout instead of removing it entirely - conn.SetDeadline(time.Now().Add(30 * time.Second)) - - reader := bufio.NewReader(tlsConn) - msgChannel := make(chan config.MSGDS) - go m.handleMessageTCP(msgChannel) - - for { - select { - case <-m.TCPListener.CTX.Done(): - return - default: - // Set read timeout for each message - conn.SetDeadline(time.Now().Add(30 * time.Second)) - message, err := readSyslogMessage(reader) - if err != nil { - if err == io.EOF { - utils.Logger.Info("TLS connection closed by %s", remoteAddr) - return - } - if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - utils.Logger.Info("TLS connection timeout from %s", remoteAddr) - return - } - utils.Logger.ErrorF("error reading TLS data from %s: %v", remoteAddr, err) - return - } - msgChannel <- config.MSGDS{ - DataSource: remoteAddr, - Message: message, - } - } - } -} - -func (m *SyslogModule) handleMessageTCP(logsChannel chan config.MSGDS) { - for { - select { - case <-m.TCPListener.CTX.Done(): - return - - case msgDS := <-logsChannel: - message := msgDS.Message - message = strings.TrimSuffix(message, "\n") - message, _, err := entities.ValidateString(message, false) - if err != nil { - utils.Logger.ErrorF("error validating string: %v: message: %s", err, message) - continue - } - - if m.Parser != nil { - err := m.Parser.ProcessData(message, msgDS.DataSource, logservice.LogQueue) - if err != nil { - utils.Logger.ErrorF("error parsing data: %v", err) - continue - } - } else { - logservice.LogQueue <- &plugins.Log{ - DataType: m.DataType, - DataSource: msgDS.DataSource, - Raw: message, - } - } - - } - } -} - -func (m *SyslogModule) handleConnectionUDP(logsChannel chan config.MSGDS) { - for { - select { - case <-m.UDPListener.CTX.Done(): - return - - case msgDS := <-logsChannel: - message := msgDS.Message - message = strings.TrimSuffix(message, "\n") - message, _, err := entities.ValidateString(message, false) - if err != nil { - utils.Logger.ErrorF("error validating string: %v: message: %s", err, message) - continue - } - - if m.Parser != nil { - err := m.Parser.ProcessData(message, msgDS.DataSource, logservice.LogQueue) - if err != nil { - utils.Logger.ErrorF("error parsing data: %v", err) - continue - } - } else { - logservice.LogQueue <- &plugins.Log{ - DataType: m.DataType, - DataSource: msgDS.DataSource, - Raw: message, - } - } - } - } -} diff --git a/agent/protos/agent.proto b/agent/protos/agent.proto index 5495bb3be..39d98fdd4 100644 --- a/agent/protos/agent.proto +++ b/agent/protos/agent.proto @@ -9,6 +9,7 @@ import "common.proto"; service AgentService { rpc RegisterAgent(AgentRequest) returns (AuthResponse) {} + rpc UpdateAgent(AgentRequest) returns (AuthResponse) {} rpc DeleteAgent(DeleteRequest) returns (AuthResponse) {} rpc ListAgents (ListRequest) returns (ListAgentsResponse) {} rpc AgentStream(stream BidirectionalStream) returns (stream BidirectionalStream) {} @@ -78,6 +79,7 @@ message UtmCommand { string origin_type = 5; string origin_id = 6; string reason = 7; + string shell = 8; // Shell to execute command: "cmd", "powershell" (Windows), "sh", "bash" (Linux/macOS). Empty = default } message CommandResult { diff --git a/agent/serv/service.go b/agent/serv/service.go index ec0eeb0e8..ced41abf9 100644 --- a/agent/serv/service.go +++ b/agent/serv/service.go @@ -2,29 +2,32 @@ package serv import ( "context" - "fmt" "os" "os/signal" - "path/filepath" - "runtime" "strconv" + "sync" "syscall" + "time" "github.com/kardianos/service" pb "github.com/utmstack/UTMStack/agent/agent" - "github.com/utmstack/UTMStack/agent/collectors" + "github.com/utmstack/UTMStack/agent/collector" "github.com/utmstack/UTMStack/agent/config" "github.com/utmstack/UTMStack/agent/database" - "github.com/utmstack/UTMStack/agent/logservice" + "github.com/utmstack/UTMStack/agent/dependency" "github.com/utmstack/UTMStack/agent/models" - "github.com/utmstack/UTMStack/agent/modules" - "github.com/utmstack/UTMStack/agent/updates" "github.com/utmstack/UTMStack/agent/utils" "google.golang.org/grpc/metadata" ) -type program struct{} +const shutdownTimeout = 30 * time.Second + +type program struct { + cancel context.CancelFunc + wg sync.WaitGroup + mu sync.Mutex +} func (p *program) Start(_ service.Service) error { go p.run() @@ -32,10 +35,58 @@ func (p *program) Start(_ service.Service) error { } func (p *program) Stop(_ service.Service) error { - // TODO: implement this function + utils.Logger.Info("Stopping UTMStack Agent...") + + p.mu.Lock() + cancel := p.cancel + p.mu.Unlock() + + if cancel != nil { + cancel() + } + + // Stop all collectors + collector.StopAll() + + // Wait for goroutines with timeout + done := make(chan struct{}) + go func() { + p.wg.Wait() + close(done) + }() + + select { + case <-done: + utils.Logger.Info("All goroutines stopped gracefully") + case <-time.After(shutdownTimeout): + utils.Logger.ErrorF("Shutdown timeout after %v, some goroutines may not have stopped", shutdownTimeout) + } + + // Close database + if db, err := database.GetDB(); err == nil && db != nil { + if err := db.Close(); err != nil { + utils.Logger.ErrorF("error closing database: %v", err) + } + } + + utils.Logger.Info("UTMStack Agent stopped") return nil } +// goSafe launches a goroutine with panic recovery and WaitGroup tracking. +func (p *program) goSafe(name string, fn func()) { + p.wg.Add(1) + go func() { + defer p.wg.Done() + defer func() { + if r := recover(); r != nil { + utils.Logger.ErrorF("panic in %s: %v", name, r) + } + }() + fn() + }() +} + func (p *program) run() { utils.InitLogger(config.ServiceLogFile) cnf, err := config.GetCurrentConfig() @@ -43,82 +94,67 @@ func (p *program) run() { utils.Logger.Fatal("error getting config: %v", err) } - db := database.GetDB() - err = db.Migrate(models.Log{}) + db, err := database.GetDB() if err != nil { + utils.Logger.ErrorF("error initializing database: %v", err) + } else if err = db.Migrate(models.Log{}); err != nil { utils.Logger.ErrorF("error migrating logs table: %v", err) } - ensureUpdaterServiceInstalled() + // Reconcile dependencies (updater, beats, etc.) before starting collectors + if err := dependency.Reconcile(cnf.Server, cnf.SkipCertValidation); err != nil { + utils.Logger.ErrorF("error reconciling dependencies: %v", err) + // Continue anyway - agent should try to run with what it has + } ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + p.mu.Lock() + p.cancel = cancel + p.mu.Unlock() + ctx = metadata.AppendToOutgoingContext(ctx, "key", cnf.AgentKey) ctx = metadata.AppendToOutgoingContext(ctx, "id", strconv.Itoa(int(cnf.AgentID))) ctx = metadata.AppendToOutgoingContext(ctx, "type", "agent") - go pb.IncidentResponseStream(cnf, ctx) - go pb.StartPing(cnf, ctx) - - logProcessor := logservice.GetLogProcessor() - go logProcessor.ProcessLogs(cnf, ctx) - - go pb.UpdateAgent(cnf, ctx) - go modules.StartModules() - collectors.LogsReader() - - go updates.UpdateDependencies(cnf) - - signals := make(chan os.Signal, 1) - signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) - <-signals -} - -func ensureUpdaterServiceInstalled() { - isInstalled, err := utils.CheckIfServiceIsInstalled("UTMStackUpdater") - if err != nil { - utils.Logger.ErrorF("error checking if updater service is installed: %v", err) - return - } + // Start all goroutines with panic recovery + p.goSafe("IncidentResponseStream", func() { + pb.IncidentResponseStream(cnf, ctx) + }) - if isInstalled { - utils.Logger.Info("updater service is already installed") - return - } + p.goSafe("StartPing", func() { + pb.StartPing(cnf, ctx) + }) - utils.Logger.Info("updater service not found, installing...") - - updaterPath := filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.UpdaterFile, "")) - - if !utils.CheckIfPathExist(updaterPath) { - - cnf, err := config.GetCurrentConfig() + p.goSafe("ProcessLogs", func() { + logProcessor, err := pb.GetLogProcessor() if err != nil { - utils.Logger.ErrorF("error getting config to download updater: %v", err) + utils.Logger.ErrorF("error initializing log processor: %v", err) return } + logProcessor.ProcessLogs(cnf, ctx) + }) - updaterBinary := fmt.Sprintf(config.UpdaterFile, "") - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, updaterBinary), map[string]string{}, updaterBinary, utils.GetMyPath(), cnf.SkipCertValidation); err != nil { - utils.Logger.ErrorF("error downloading updater binary: %v", err) - return - } + p.goSafe("UpdateAgent", func() { + pb.UpdateAgent(cnf, ctx) + }) - if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { - if err := utils.Execute("chmod", utils.GetMyPath(), "755", updaterBinary); err != nil { - utils.Logger.ErrorF("error setting permissions on updater: %v", err) - return - } - } - - utils.Logger.Info("updater binary downloaded successfully") + // Sync collector config with current version's ProtoPorts + if err := collector.SyncCollectorConfig(); err != nil { + utils.Logger.ErrorF("error syncing collector config: %v", err) } - err = utils.Execute(updaterPath, utils.GetMyPath(), "install") - if err != nil { - utils.Logger.ErrorF("error installing updater service: %v", err) - return - } + // Start collectors (they manage their own goroutines with context) + collector.StartAll(ctx) - utils.Logger.Info("updater service installed successfully") + // Wait for shutdown signal + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) + + select { + case sig := <-signals: + utils.Logger.Info("Received signal: %v", sig) + case <-ctx.Done(): + utils.Logger.Info("Context cancelled") + } } + diff --git a/agent/serv/uninstall.go b/agent/serv/uninstall.go index 644bc713a..f2bba7dff 100644 --- a/agent/serv/uninstall.go +++ b/agent/serv/uninstall.go @@ -2,10 +2,11 @@ package serv import ( "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/svc" ) func UninstallService() { - err := utils.StopService("UTMStackAgent") + err := svc.Stop("UTMStackAgent") if err != nil { utils.Logger.Fatal("error stopping UTMStackAgent: %v", err) } diff --git a/agent/updater/config/config.go b/agent/updater/config/config.go index 8daefeb23..6527a45fb 100644 --- a/agent/updater/config/config.go +++ b/agent/updater/config/config.go @@ -2,11 +2,10 @@ package config import ( "fmt" - "os" "path/filepath" "sync" - "gopkg.in/yaml.v3" + "github.com/utmstack/UTMStack/shared/fs" ) type Config struct { @@ -22,24 +21,11 @@ var ( func GetCurrentConfig() (*Config, error) { var errR error confOnce.Do(func() { - ex, err := os.Executable() - if err != nil { - errR = fmt.Errorf("error getting executable path: %v", err) - return - } - exPath := filepath.Dir(ex) - - configPath := filepath.Join(exPath, "config.yml") - content, err := os.ReadFile(configPath) - if err != nil { - errR = fmt.Errorf("error reading config file: %v", err) - return - } + configPath := filepath.Join(fs.GetExecutablePath(), "config.yml") var loadedConfig Config - err = yaml.Unmarshal(content, &loadedConfig) - if err != nil { - errR = fmt.Errorf("error parsing config file: %v", err) + if err := fs.ReadYAML(configPath, &loadedConfig); err != nil { + errR = fmt.Errorf("error reading config file: %v", err) return } diff --git a/agent/updater/config/const.go b/agent/updater/config/const.go index 5032035e0..e57a491db 100644 --- a/agent/updater/config/const.go +++ b/agent/updater/config/const.go @@ -1,14 +1,18 @@ package config import ( + "fmt" "path/filepath" + "runtime" - "github.com/utmstack/UTMStack/agent/updater/utils" + "github.com/utmstack/UTMStack/shared/fs" ) const ( SERV_LOG = "utmstack_updater.log" SERV_AGENT_NAME = "UTMStackAgent" + + agentBaseName = "utmstack_agent_service" ) var ( @@ -17,5 +21,19 @@ var ( LogAuthProxyPort = "50051" DependenciesPort = "9001" - VersionPath = filepath.Join(utils.GetMyPath(), "version.json") + VersionPath = filepath.Join(fs.GetExecutablePath(), "version.json") ) + +// ServiceFile returns the agent binary name with OS and architecture suffix. +// Format: utmstack_agent_service__[.exe] +// Examples: +// - utmstack_agent_service_linux_amd64 +// - utmstack_agent_service_windows_amd64.exe +// - utmstack_agent_service_darwin_arm64 +func ServiceFile(suffix string) string { + name := fmt.Sprintf("%s_%s_%s%s", agentBaseName, runtime.GOOS, runtime.GOARCH, suffix) + if runtime.GOOS == "windows" { + return name + ".exe" + } + return name +} diff --git a/agent/updater/config/linux_amd64.go b/agent/updater/config/linux_amd64.go deleted file mode 100644 index 026f6c3f9..000000000 --- a/agent/updater/config/linux_amd64.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build linux && amd64 -// +build linux,amd64 - -package config - -var ( - ServiceFile = "utmstack_agent_service%s" - DependFiles = []string{"utmstack_agent_dependencies_linux.zip"} -) diff --git a/agent/updater/config/linux_arm64.go b/agent/updater/config/linux_arm64.go deleted file mode 100644 index d77ff9f09..000000000 --- a/agent/updater/config/linux_arm64.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build linux && arm64 -// +build linux,arm64 - -package config - -var ( - ServiceFile = "utmstack_agent_service_arm64%s" - DependFiles = []string{"utmstack_agent_dependencies_linux_arm64.zip"} -) diff --git a/agent/updater/config/macos.go b/agent/updater/config/macos.go deleted file mode 100644 index ecb80457a..000000000 --- a/agent/updater/config/macos.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build darwin -// +build darwin - -package config - -var ( - ServiceFile = "utmstack_agent_service%s" - DependFiles = []string{} -) diff --git a/agent/updater/config/windows_amd64.go b/agent/updater/config/windows_amd64.go deleted file mode 100644 index adad86d9f..000000000 --- a/agent/updater/config/windows_amd64.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build windows && amd64 -// +build windows,amd64 - -package config - -var ( - ServiceFile = "utmstack_agent_service%s.exe" - DependFiles = []string{"utmstack_agent_dependencies_windows.zip"} -) diff --git a/agent/updater/config/windows_arm64.go b/agent/updater/config/windows_arm64.go deleted file mode 100644 index 4705118c1..000000000 --- a/agent/updater/config/windows_arm64.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build windows && arm64 -// +build windows,arm64 - -package config - -var ( - ServiceFile = "utmstack_agent_service_arm64%s.exe" - DependFiles = []string{"utmstack_agent_dependencies_windows_arm64.zip"} -) diff --git a/agent/updater/go.mod b/agent/updater/go.mod index 8b7e97c5f..d9b407d6a 100644 --- a/agent/updater/go.mod +++ b/agent/updater/go.mod @@ -4,17 +4,19 @@ go 1.25.5 require ( github.com/kardianos/service v1.2.4 - github.com/threatwinds/go-sdk v1.1.7 + github.com/threatwinds/go-sdk v1.1.14 github.com/threatwinds/logger v1.2.3 - gopkg.in/yaml.v3 v3.0.1 + github.com/utmstack/UTMStack/shared v0.0.0 ) +replace github.com/utmstack/UTMStack/shared => ../../shared + require ( github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.14.2 // indirect - github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -45,5 +47,6 @@ require ( golang.org/x/text v0.33.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/agent/updater/go.sum b/agent/updater/go.sum index c710c1a04..455acc4b0 100644 --- a/agent/updater/go.sum +++ b/agent/updater/go.sum @@ -1,17 +1,17 @@ github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= -github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= -github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= -github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -74,8 +74,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.7 h1:2IJAWTCxZU4BDFiavPjH8MqpA/mam1QyIsjySbZLlRo= -github.com/threatwinds/go-sdk v1.1.7/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= +github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/threatwinds/logger v1.2.3 h1:V2SVAXzbq+/huCvIWOfqzMTH+WBHJxankyBgVG2hy1Y= github.com/threatwinds/logger v1.2.3/go.mod h1:N+bJKvF4FQNJZLfQpVYWpr6D8iEAFnAQfHYqH5iR1TI= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= @@ -94,8 +94,8 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= diff --git a/agent/updater/main.go b/agent/updater/main.go index 9aae73b21..5b9d278cd 100644 --- a/agent/updater/main.go +++ b/agent/updater/main.go @@ -7,19 +7,20 @@ import ( "github.com/utmstack/UTMStack/agent/updater/config" "github.com/utmstack/UTMStack/agent/updater/service" - "github.com/utmstack/UTMStack/agent/updater/utils" + "github.com/utmstack/UTMStack/shared/fs" + "github.com/utmstack/UTMStack/shared/logger" ) func main() { - path := utils.GetMyPath() - utils.InitLogger(filepath.Join(path, "logs", config.SERV_LOG)) + basePath := fs.GetExecutablePath() + logger.Init(filepath.Join(basePath, "logs", config.SERV_LOG), logger.LevelInfo) if len(os.Args) > 1 { switch os.Args[1] { case "install": fmt.Println("Installing UTMStack Updater service...") - fmt.Print(("Creating service ... ")) + fmt.Print("Creating service ... ") service.InstallService() fmt.Println("[OK]") @@ -35,7 +36,6 @@ func main() { return case "stop": fmt.Println("Stopping UTMStack Updater service...") - // Will be handled by systemd return } } diff --git a/agent/updater/models/version.go b/agent/updater/models/version.go deleted file mode 100644 index ed01f06e4..000000000 --- a/agent/updater/models/version.go +++ /dev/null @@ -1,5 +0,0 @@ -package models - -type Version struct { - Version string `json:"version"` -} diff --git a/agent/updater/service/install.go b/agent/updater/service/install.go index 62b09f261..95db779c9 100644 --- a/agent/updater/service/install.go +++ b/agent/updater/service/install.go @@ -5,7 +5,7 @@ import ( "os" "github.com/kardianos/service" - "github.com/utmstack/UTMStack/agent/updater/utils" + "github.com/utmstack/UTMStack/shared/logger" ) func InstallService() { @@ -27,7 +27,7 @@ func InstallService() { fmt.Println("\nError starting new service: ", err) os.Exit(1) } - utils.UpdaterLogger.Info("updater service installed successfully") + logger.Info("updater service installed successfully") } func UninstallService() { diff --git a/agent/updater/service/service.go b/agent/updater/service/service.go index 837037b09..40429b7e7 100644 --- a/agent/updater/service/service.go +++ b/agent/updater/service/service.go @@ -4,7 +4,7 @@ import ( "github.com/kardianos/service" "github.com/utmstack/UTMStack/agent/updater/config" "github.com/utmstack/UTMStack/agent/updater/updates" - "github.com/utmstack/UTMStack/agent/updater/utils" + "github.com/utmstack/UTMStack/shared/logger" ) type program struct{} @@ -21,7 +21,7 @@ func (p *program) Stop(s service.Service) error { func (p *program) run() { cnf, err := config.GetCurrentConfig() if err != nil { - utils.UpdaterLogger.ErrorF("error getting config: %v", err) + logger.Error("error getting config: %v", err) return } @@ -33,11 +33,11 @@ func RunService() { prg := new(program) newService, err := service.New(prg, svcConfig) if err != nil { - utils.UpdaterLogger.Fatal("error creating service: %v", err) + logger.Fatal("error creating service: %v", err) } err = newService.Run() if err != nil { - utils.UpdaterLogger.Fatal("error running service: %v", err) + logger.Fatal("error running service: %v", err) } } diff --git a/agent/updater/updates/update.go b/agent/updater/updates/update.go index 80f8779db..fecaad400 100644 --- a/agent/updater/updates/update.go +++ b/agent/updater/updates/update.go @@ -8,147 +8,176 @@ import ( "time" "github.com/utmstack/UTMStack/agent/updater/config" - "github.com/utmstack/UTMStack/agent/updater/models" - "github.com/utmstack/UTMStack/agent/updater/utils" + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" + "github.com/utmstack/UTMStack/shared/http" + "github.com/utmstack/UTMStack/shared/logger" + "github.com/utmstack/UTMStack/shared/svc" ) const ( checkEvery = 5 * time.Minute ) -var currentVersion = models.Version{} +// Version represents the version info from version.json +type Version struct { + Version string `json:"version"` +} + +// legacyServiceFile returns the old naming convention for the agent binary. +// This is used for migration from old agents that don't have OS/arch suffix. +func legacyServiceFile() string { + if runtime.GOOS == "windows" { + return "utmstack_agent_service.exe" + } + return "utmstack_agent_service" +} + +var currentVersion = Version{} func UpdateDependencies(cnf *config.Config) { - if utils.CheckIfPathExist(config.VersionPath) { - err := utils.ReadJson(config.VersionPath, ¤tVersion) - if err != nil { - utils.UpdaterLogger.ErrorF("error reading version file: %v", err) + basePath := fs.GetExecutablePath() + + if fs.Exists(config.VersionPath) { + if err := fs.ReadJSON(config.VersionPath, ¤tVersion); err != nil { + logger.Error("error reading version file: %v", err) } } for { time.Sleep(checkEvery) - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, "version.json"), map[string]string{}, "version_new.json", utils.GetMyPath(), cnf.SkipCertValidation); err != nil { - utils.UpdaterLogger.ErrorF("error downloading version.json: %v", err) + if err := http.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, "version.json"), nil, "version_new.json", basePath, cnf.SkipCertValidation); err != nil { + logger.Error("error downloading version.json: %v", err) continue } - newVersion := models.Version{} - err := utils.ReadJson(filepath.Join(utils.GetMyPath(), "version_new.json"), &newVersion) - if err != nil { - utils.UpdaterLogger.ErrorF("error reading version file: %v", err) + + newVersion := Version{} + if err := fs.ReadJSON(filepath.Join(basePath, "version_new.json"), &newVersion); err != nil { + logger.Error("error reading version file: %v", err) continue } if newVersion.Version != currentVersion.Version { - utils.UpdaterLogger.Info("New version of agent found: %s", newVersion.Version) - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, fmt.Sprintf(config.ServiceFile, "")), map[string]string{}, fmt.Sprintf(config.ServiceFile, "_new"), utils.GetMyPath(), cnf.SkipCertValidation); err != nil { - utils.UpdaterLogger.ErrorF("error downloading agent: %v", err) + logger.Info("New version of agent found: %s", newVersion.Version) + + agentBinary := config.ServiceFile("") + if err := http.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, agentBinary), nil, config.ServiceFile("_new"), basePath, cnf.SkipCertValidation); err != nil { + logger.Error("error downloading agent: %v", err) continue } if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { - if err = utils.Execute("chmod", utils.GetMyPath(), "-R", "755", filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceFile, "_new"))); err != nil { - utils.UpdaterLogger.ErrorF("error executing chmod: %v", err) + if err := exec.Run("chmod", basePath, "-R", "755", filepath.Join(basePath, config.ServiceFile("_new"))); err != nil { + logger.Error("error executing chmod: %v", err) } } - utils.UpdaterLogger.Info("Starting update process...") - err = runUpdateProcess() - if err != nil { - utils.UpdaterLogger.ErrorF("error updating service: %v", err) - os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) - os.Remove(filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceFile, "_new"))) + logger.Info("Starting update process...") + if err := runUpdateProcess(basePath); err != nil { + logger.Error("error updating service: %v", err) + os.Remove(filepath.Join(basePath, "version_new.json")) + os.Remove(filepath.Join(basePath, config.ServiceFile("_new"))) } else { - utils.UpdaterLogger.Info("Update completed successfully") - if utils.CheckIfPathExist(config.VersionPath) { - err := utils.ReadJson(config.VersionPath, ¤tVersion) - if err != nil { - utils.UpdaterLogger.ErrorF("error reading updated version file: %v", err) + logger.Info("Update completed successfully") + if fs.Exists(config.VersionPath) { + if err := fs.ReadJSON(config.VersionPath, ¤tVersion); err != nil { + logger.Error("error reading updated version file: %v", err) } } } } else { - os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) + os.Remove(filepath.Join(basePath, "version_new.json")) } } } -func runUpdateProcess() error { - path := utils.GetMyPath() - - newBin := fmt.Sprintf(config.ServiceFile, "_new") - oldBin := fmt.Sprintf(config.ServiceFile, "") - backupBin := fmt.Sprintf(config.ServiceFile, ".old") +func runUpdateProcess(basePath string) error { + newBin := config.ServiceFile("_new") + oldBin := config.ServiceFile("") + backupBin := config.ServiceFile(".old") - agentNew := filepath.Join(path, newBin) + agentNew := filepath.Join(basePath, newBin) if _, err := os.Stat(agentNew); err != nil { return fmt.Errorf("no _new binary found to update") } - if err := utils.StopService(config.SERV_AGENT_NAME); err != nil { + if err := svc.Stop(config.SERV_AGENT_NAME); err != nil { return fmt.Errorf("error stopping agent: %v", err) } time.Sleep(10 * time.Second) - backupPath := filepath.Join(path, backupBin) - if utils.CheckIfPathExist(backupPath) { - utils.UpdaterLogger.Info("Removing previous backup: %s", backupPath) + // Migration: check if old naming convention exists and migrate to new naming + oldBinPath := filepath.Join(basePath, oldBin) + if !fs.Exists(oldBinPath) { + legacyBin := legacyServiceFile() + legacyBinPath := filepath.Join(basePath, legacyBin) + if fs.Exists(legacyBinPath) { + logger.Info("Migrating legacy binary from %s to %s", legacyBin, oldBin) + if err := os.Rename(legacyBinPath, oldBinPath); err != nil { + return fmt.Errorf("error migrating legacy binary: %v", err) + } + } + } + + backupPath := filepath.Join(basePath, backupBin) + if fs.Exists(backupPath) { + logger.Info("Removing previous backup: %s", backupPath) if err := os.Remove(backupPath); err != nil { - utils.UpdaterLogger.ErrorF("could not remove old backup: %v", err) + logger.Error("could not remove old backup: %v", err) } } - if err := os.Rename(filepath.Join(path, oldBin), backupPath); err != nil { + if err := os.Rename(filepath.Join(basePath, oldBin), backupPath); err != nil { return fmt.Errorf("error backing up old binary: %v", err) } - if err := os.Rename(filepath.Join(path, newBin), filepath.Join(path, oldBin)); err != nil { - os.Rename(backupPath, filepath.Join(path, oldBin)) + if err := os.Rename(filepath.Join(basePath, newBin), filepath.Join(basePath, oldBin)); err != nil { + os.Rename(backupPath, filepath.Join(basePath, oldBin)) return fmt.Errorf("error renaming new binary: %v", err) } - if err := utils.StartService(config.SERV_AGENT_NAME); err != nil { - rollbackAgent(oldBin, backupBin, path) + if err := svc.Start(config.SERV_AGENT_NAME); err != nil { + rollbackAgent(oldBin, backupBin, basePath) return fmt.Errorf("error starting agent: %v", err) } time.Sleep(30 * time.Second) - isHealthy, err := utils.CheckIfServiceIsActive(config.SERV_AGENT_NAME) + isHealthy, err := svc.IsActive(config.SERV_AGENT_NAME) if err != nil || !isHealthy { - utils.UpdaterLogger.Info("New version failed health check, rolling back...") - rollbackAgent(oldBin, backupBin, path) + logger.Info("New version failed health check, rolling back...") + rollbackAgent(oldBin, backupBin, basePath) return fmt.Errorf("rollback completed: new version failed health check") } - utils.UpdaterLogger.Info("Health check passed for agent") + logger.Info("Health check passed for agent") - versionNewPath := filepath.Join(path, "version_new.json") - versionPath := filepath.Join(path, "version.json") - if utils.CheckIfPathExist(versionNewPath) { + versionNewPath := filepath.Join(basePath, "version_new.json") + versionPath := filepath.Join(basePath, "version.json") + if fs.Exists(versionNewPath) { if err := os.Rename(versionNewPath, versionPath); err != nil { - utils.UpdaterLogger.ErrorF("error updating version file: %v", err) + logger.Error("error updating version file: %v", err) } else { - utils.UpdaterLogger.Info("Version file updated successfully") + logger.Info("Version file updated successfully") } } return nil } -func rollbackAgent(currentBin, backupBin, path string) { - utils.UpdaterLogger.Info("Rolling back agent to previous version...") +func rollbackAgent(currentBin, backupBin, basePath string) { + logger.Info("Rolling back agent to previous version...") - utils.StopService(config.SERV_AGENT_NAME) + svc.Stop(config.SERV_AGENT_NAME) time.Sleep(5 * time.Second) - os.Remove(filepath.Join(path, currentBin)) - os.Rename(filepath.Join(path, backupBin), filepath.Join(path, currentBin)) + os.Remove(filepath.Join(basePath, currentBin)) + os.Rename(filepath.Join(basePath, backupBin), filepath.Join(basePath, currentBin)) - utils.StartService(config.SERV_AGENT_NAME) - os.Remove(filepath.Join(path, "version_new.json")) + svc.Start(config.SERV_AGENT_NAME) + os.Remove(filepath.Join(basePath, "version_new.json")) - utils.UpdaterLogger.Info("Rollback completed for agent") + logger.Info("Rollback completed for agent") } diff --git a/agent/updater/utils/cmd.go b/agent/updater/utils/cmd.go deleted file mode 100644 index eae4140d9..000000000 --- a/agent/updater/utils/cmd.go +++ /dev/null @@ -1,39 +0,0 @@ -package utils - -import ( - "errors" - "os/exec" - - twsdk "github.com/threatwinds/go-sdk/entities" -) - -func ExecuteWithResult(c string, dir string, arg ...string) (string, bool) { - cmd := exec.Command(c, arg...) - - cmd.Dir = dir - if errors.Is(cmd.Err, exec.ErrDot) { - cmd.Err = nil - } - - out, err := cmd.Output() - if err != nil { - return string(out[:]) + err.Error(), true - } - - if string(out[:]) == "" { - return "Command executed successfully but no output", false - } - validUtf8Out, _, err := twsdk.ValidateString(string(out[:]), false) - if err != nil { - return string(out[:]) + err.Error(), true - } - - return validUtf8Out, false -} - -func Execute(c string, dir string, arg ...string) error { - cmd := exec.Command(c, arg...) - cmd.Dir = dir - - return cmd.Run() -} diff --git a/agent/updater/utils/download.go b/agent/updater/utils/download.go deleted file mode 100644 index b8c223447..000000000 --- a/agent/updater/utils/download.go +++ /dev/null @@ -1,50 +0,0 @@ -package utils - -import ( - "crypto/tls" - "fmt" - "io" - "net/http" - "os" - "path/filepath" -) - -func DownloadFile(url string, headers map[string]string, fileName string, path string, skipTlsVerification bool) error { - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return fmt.Errorf("error creating new request: %v", err) - } - for key, value := range headers { - req.Header.Add(key, value) - } - - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTlsVerification}, - DisableCompression: true, - } - client := &http.Client{Transport: tr} - defer tr.CloseIdleConnections() - - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("error sending request: %v", err) - } - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("expected status %d; got %d", http.StatusOK, resp.StatusCode) - } - - out, err := os.Create(filepath.Join(path, fileName)) - if err != nil { - return fmt.Errorf("error creating file: %v", err) - } - defer func() { _ = out.Close() }() - - _, err = io.Copy(out, resp.Body) - if err != nil { - return fmt.Errorf("error copying file: %v", err) - } - - return nil -} diff --git a/agent/updater/utils/files.go b/agent/updater/utils/files.go deleted file mode 100644 index 6b0180721..000000000 --- a/agent/updater/utils/files.go +++ /dev/null @@ -1,92 +0,0 @@ -package utils - -import ( - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" -) - -func GetMyPath() string { - ex, err := os.Executable() - if err != nil { - return "" - } - exPath := filepath.Dir(ex) - return exPath -} - -func CreatePathIfNotExist(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - if err := os.MkdirAll(path, 0755); err != nil { - return fmt.Errorf("error creating path: %v", err) - } - } else if err != nil { - return fmt.Errorf("error checking path: %v", err) - } - return nil -} - -func CheckIfPathExist(path string) bool { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false - } - return true -} - -func ReadJson(fileName string, data interface{}) error { - content, err := os.ReadFile(fileName) - if err != nil { - return err - } - - err = json.Unmarshal(content, data) - if err != nil { - return err - } - - return nil -} - -func WriteStringToFile(fileName string, body string) error { - file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.ModePerm) - if err != nil { - return err - } - defer func() { _ = file.Close() }() - - _, err = file.WriteString(body) - return err -} - -func WriteJSON(path string, data interface{}) error { - jsonData, err := json.MarshalIndent(data, "", " ") - if err != nil { - return err - } - - err = WriteStringToFile(path, string(jsonData)) - if err != nil { - return err - } - - return nil -} - -func copyFile(src, dst string) error { - sourceFile, err := os.Open(src) - if err != nil { - return err - } - defer sourceFile.Close() - - destFile, err := os.Create(dst) - if err != nil { - return err - } - defer destFile.Close() - - _, err = io.Copy(destFile, sourceFile) - return err -} diff --git a/agent/updater/utils/logger.go b/agent/updater/utils/logger.go deleted file mode 100644 index fc0cbb757..000000000 --- a/agent/updater/utils/logger.go +++ /dev/null @@ -1,20 +0,0 @@ -package utils - -import ( - "sync" - - "github.com/threatwinds/logger" -) - -var ( - UpdaterLogger *logger.Logger - loggerOnceInstance sync.Once -) - -func InitLogger(filename string) { - loggerOnceInstance.Do(func() { - UpdaterLogger = logger.NewLogger( - &logger.Config{Format: "text", Level: 100, Output: filename, Retries: 3, Wait: 5}, - ) - }) -} diff --git a/agent/updater/utils/services.go b/agent/updater/utils/services.go deleted file mode 100644 index 183980245..000000000 --- a/agent/updater/utils/services.go +++ /dev/null @@ -1,135 +0,0 @@ -package utils - -import ( - "fmt" - "runtime" - "strings" -) - -func CheckIfServiceIsActive(serv string) (bool, error) { - var errB bool - var output string - path := GetMyPath() - - switch runtime.GOOS { - case "windows": - output, errB = ExecuteWithResult("sc", path, "query", serv) - case "linux": - output, errB = ExecuteWithResult("systemctl", path, "is-active", serv) - case "darwin": - output, errB = ExecuteWithResult("launchctl", path, "list", serv) - default: - return false, fmt.Errorf("unknown operating system") - } - - if errB { - return false, nil - } - - serviceStatus := strings.ToLower(strings.TrimSpace(output)) - - switch runtime.GOOS { - case "windows": - return strings.Contains(serviceStatus, "running"), nil - case "linux": - return serviceStatus == "active", nil - case "darwin": - // launchctl list returns a JSON-ish block or error.If the service is listed, it's running - return true, nil - default: - return false, fmt.Errorf("unsupported operating system") - } -} - -func RestartService(serv string) error { - path := GetMyPath() - isRunning, err := CheckIfServiceIsActive(serv) - if err != nil { - return fmt.Errorf("error checking if %s service is active: %v", serv, err) - } - - switch runtime.GOOS { - case "windows": - if isRunning { - err := Execute("sc", path, "stop", serv) - if err != nil { - return fmt.Errorf("error stopping service: %v", err) - } - } - err := Execute("sc", path, "start", serv) - if err != nil { - return fmt.Errorf("error starting service: %v", err) - } - - case "linux": - if isRunning { - err := Execute("systemctl", path, "restart", serv) - if err != nil { - return fmt.Errorf("error restarting service: %v", err) - } - } else { - err := Execute("systemctl", path, "start", serv) - if err != nil { - return fmt.Errorf("error starting service: %v", err) - } - } - case "darwin": - plistPath := fmt.Sprintf("/Library/LaunchDaemons/%s.plist", serv) - - if isRunning { - if err := Execute("launchctl", path, "remove", serv); err != nil { - return fmt.Errorf("error stopping macOS service: %v", err) - } - } - - if err := Execute("launchctl", path, "load", plistPath); err != nil { - return fmt.Errorf("error starting macOS service: %v", err) - } - } - return nil -} - -func StopService(name string) error { - path := GetMyPath() - switch runtime.GOOS { - case "windows": - err := Execute("sc", path, "stop", name) - if err != nil { - return fmt.Errorf("error stoping service: %v", err) - } - case "linux": - err := Execute("systemctl", path, "stop", name) - if err != nil { - return fmt.Errorf("error stoping service: %v", err) - } - case "darwin": - err := Execute("launchctl", path, "remove", name) - if err != nil { - return fmt.Errorf("error stopping macOS service: %v", err) - } - } - return nil -} - -func StartService(name string) error { - path := GetMyPath() - switch runtime.GOOS { - case "windows": - err := Execute("sc", path, "start", name) - if err != nil { - return fmt.Errorf("error starting service: %v", err) - } - case "linux": - err := Execute("systemctl", path, "start", name) - if err != nil { - return fmt.Errorf("error starting service: %v", err) - } - case "darwin": - plistPath := fmt.Sprintf("/Library/LaunchDaemons/%s.plist", name) - err := Execute("launchctl", path, "load", plistPath) - if err != nil { - return fmt.Errorf("error starting macOS service: %v", err) - } - } - return nil -} diff --git a/agent/updater/utils/zip.go b/agent/updater/utils/zip.go deleted file mode 100644 index ecb690a38..000000000 --- a/agent/updater/utils/zip.go +++ /dev/null @@ -1,52 +0,0 @@ -package utils - -import ( - "archive/zip" - "io" - "os" - "path" - "path/filepath" -) - -func Unzip(zipFile, destPath string) error { - archive, err := zip.OpenReader(zipFile) - if err != nil { - return err - } - defer archive.Close() - - for _, f := range archive.File { - err := func() error { - filePath := path.Join(destPath, f.Name) - if f.FileInfo().IsDir() { - os.MkdirAll(filePath, os.ModePerm) - return nil - } - if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { - return err - } - - dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) - if err != nil { - return err - } - defer dstFile.Close() - - fileInArchive, err := f.Open() - if err != nil { - return err - } - defer fileInArchive.Close() - - if _, err := io.Copy(dstFile, fileInArchive); err != nil { - return err - } - - return nil - }() - if err != nil { - return err - } - } - return nil -} diff --git a/agent/updates/dependencies.go b/agent/updates/dependencies.go deleted file mode 100644 index 1b7f2aae7..000000000 --- a/agent/updates/dependencies.go +++ /dev/null @@ -1,63 +0,0 @@ -package updates - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/utils" -) - -func DownloadFirstDependencies(address string, insecure bool) error { - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, "version.json"), map[string]string{}, "version.json", utils.GetMyPath(), insecure); err != nil { - return fmt.Errorf("error downloading version.json : %v", err) - } - - updaterBinary := fmt.Sprintf(config.UpdaterFile, "") - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, updaterBinary), map[string]string{}, updaterBinary, utils.GetMyPath(), insecure); err != nil { - return fmt.Errorf("error downloading updater binary %s: %v", updaterBinary, err) - } - - if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { - if err := utils.Execute("chmod", utils.GetMyPath(), "755", updaterBinary); err != nil { - return fmt.Errorf("error setting permissions on updater: %v", err) - } - } - - dependFiles := config.DependFiles - for _, file := range dependFiles { - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, file), map[string]string{}, file, utils.GetMyPath(), insecure); err != nil { - return fmt.Errorf("error downloading file %s: %v", file, err) - } - } - - if err := handleDependenciesPostDownload(dependFiles); err != nil { - return err - } - - return nil - -} - -func handleDependenciesPostDownload(dependencies []string) error { - for _, file := range dependencies { - if strings.HasSuffix(file, ".zip") { - if err := utils.Unzip(filepath.Join(utils.GetMyPath(), file), utils.GetMyPath()); err != nil { - return fmt.Errorf("error unzipping dependencies: %v", err) - } - - if err := os.Remove(filepath.Join(utils.GetMyPath(), file)); err != nil { - return fmt.Errorf("error removing file %s: %v", file, err) - } - } else if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { - if err := utils.Execute("chmod", utils.GetMyPath(), "-R", "755", file); err != nil { - return fmt.Errorf("error executing chmod on %s: %v", file, err) - } - } - } - - return nil -} diff --git a/agent/updates/update.go b/agent/updates/update.go deleted file mode 100644 index 0d743734d..000000000 --- a/agent/updates/update.go +++ /dev/null @@ -1,154 +0,0 @@ -package updates - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "time" - - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/models" - "github.com/utmstack/UTMStack/agent/utils" -) - -const ( - checkEvery = 5 * time.Minute -) - -var currentVersion = models.Version{} - -func UpdateDependencies(cnf *config.Config) { - if utils.CheckIfPathExist(config.VersionPath) { - err := utils.ReadJson(config.VersionPath, ¤tVersion) - if err != nil { - utils.Logger.Fatal("error reading version file: %v", err) - } - } - - for { - time.Sleep(checkEvery) - - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, "version.json"), map[string]string{}, "version_new.json", utils.GetMyPath(), cnf.SkipCertValidation); err != nil { - utils.Logger.ErrorF("error downloading version.json: %v", err) - continue - } - newVersion := models.Version{} - err := utils.ReadJson(filepath.Join(utils.GetMyPath(), "version_new.json"), &newVersion) - if err != nil { - utils.Logger.ErrorF("error reading version file: %v", err) - continue - } - - if newVersion.UpdaterVersion != currentVersion.UpdaterVersion { - utils.Logger.Info("New version of updater found: %s", newVersion.UpdaterVersion) - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, fmt.Sprintf(config.UpdaterFile, "")), map[string]string{}, fmt.Sprintf(config.UpdaterFile, "_new"), utils.GetMyPath(), cnf.SkipCertValidation); err != nil { - utils.Logger.ErrorF("error downloading updater: %v", err) - continue - } - - if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { - if err = utils.Execute("chmod", utils.GetMyPath(), "-R", "755", filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.UpdaterFile, "_new"))); err != nil { - utils.Logger.ErrorF("error executing chmod: %v", err) - } - } - - utils.Logger.Info("Starting updater update process...") - err = runUpdateProcess() - if err != nil { - utils.Logger.ErrorF("error updating updater: %v", err) - os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) - os.Remove(filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.UpdaterFile, "_new"))) - } else { - utils.Logger.Info("Updater update completed successfully") - if utils.CheckIfPathExist(config.VersionPath) { - err := utils.ReadJson(config.VersionPath, ¤tVersion) - if err != nil { - utils.Logger.ErrorF("error reading updated version file: %v", err) - } - } - } - } else { - os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) - } - } -} - -func runUpdateProcess() error { - path := utils.GetMyPath() - - newBin := fmt.Sprintf(config.UpdaterFile, "_new") - oldBin := fmt.Sprintf(config.UpdaterFile, "") - backupBin := fmt.Sprintf(config.UpdaterFile, ".old") - - updaterNew := filepath.Join(path, newBin) - if _, err := os.Stat(updaterNew); err != nil { - return fmt.Errorf("no _new binary found to update") - } - - if err := utils.StopService(config.SERVICE_UPDATER_NAME); err != nil { - return fmt.Errorf("error stopping updater: %v", err) - } - - time.Sleep(10 * time.Second) - - backupPath := filepath.Join(path, backupBin) - if utils.CheckIfPathExist(backupPath) { - utils.Logger.Info("Removing previous backup: %s", backupPath) - if err := os.Remove(backupPath); err != nil { - utils.Logger.ErrorF("could not remove old backup: %v", err) - } - } - - if err := os.Rename(filepath.Join(path, oldBin), backupPath); err != nil { - return fmt.Errorf("error backing up old binary: %v", err) - } - - if err := os.Rename(filepath.Join(path, newBin), filepath.Join(path, oldBin)); err != nil { - os.Rename(backupPath, filepath.Join(path, oldBin)) - return fmt.Errorf("error renaming new binary: %v", err) - } - - if err := utils.StartService(config.SERVICE_UPDATER_NAME); err != nil { - rollbackUpdater(oldBin, backupBin, path) - return fmt.Errorf("error starting updater: %v", err) - } - - time.Sleep(30 * time.Second) - - isHealthy, err := utils.CheckIfServiceIsActive(config.SERVICE_UPDATER_NAME) - if err != nil || !isHealthy { - utils.Logger.Info("New version failed health check, rolling back...") - rollbackUpdater(oldBin, backupBin, path) - return fmt.Errorf("rollback completed: new version failed health check") - } - - utils.Logger.Info("Health check passed for updater") - - versionNewPath := filepath.Join(path, "version_new.json") - versionPath := filepath.Join(path, "version.json") - if utils.CheckIfPathExist(versionNewPath) { - if err := os.Rename(versionNewPath, versionPath); err != nil { - utils.Logger.ErrorF("error updating version file: %v", err) - } else { - utils.Logger.Info("Version file updated successfully") - } - } - - return nil -} - -func rollbackUpdater(currentBin, backupBin, path string) { - utils.Logger.Info("Rolling back updater to previous version...") - - utils.StopService(config.SERVICE_UPDATER_NAME) - time.Sleep(5 * time.Second) - - os.Remove(filepath.Join(path, currentBin)) - os.Rename(filepath.Join(path, backupBin), filepath.Join(path, currentBin)) - - utils.StartService(config.SERVICE_UPDATER_NAME) - os.Remove(filepath.Join(path, "version_new.json")) - - utils.Logger.Info("Rollback completed for updater") -} diff --git a/agent/utils/cmd.go b/agent/utils/cmd.go index bc8caa535..bdfd3f43e 100644 --- a/agent/utils/cmd.go +++ b/agent/utils/cmd.go @@ -30,10 +30,3 @@ func ExecuteWithResult(c string, dir string, arg ...string) (string, bool) { return validUtf8Out, false } - -func Execute(c string, dir string, arg ...string) error { - cmd := exec.Command(c, arg...) - cmd.Dir = dir - - return cmd.Run() -} diff --git a/agent/utils/crypt.go b/agent/utils/crypt.go index 740a2a5b6..d784dd962 100644 --- a/agent/utils/crypt.go +++ b/agent/utils/crypt.go @@ -3,21 +3,39 @@ package utils import ( "encoding/base64" "fmt" -) -func GenerateKey(baseKey string) ([]byte, error) { - info, err := GetOsInfo() - if err != nil { - return nil, fmt.Errorf("error getting os info: %v", err) - } - - data := []byte(info.Hostname + info.Mac + info.OsType) - base64Key := base64.StdEncoding.EncodeToString(data) - return []byte(baseKey + base64Key), nil -} + aesCrypt "github.com/AtlasInsideCorp/AtlasInsideAES" +) +// GenerateKeyByUUID generates an AES key from baseKey and uuid. func GenerateKeyByUUID(baseKey string, uuid string) ([]byte, error) { data := []byte(baseKey + uuid) base64Key := base64.StdEncoding.EncodeToString(data) return []byte(base64Key), nil } + +// EncryptAES encrypts plaintext using AES with a key derived from baseKey and uuid. +func EncryptAES(plaintext, baseKey, uuid string) (string, error) { + key, err := GenerateKeyByUUID(baseKey, uuid) + if err != nil { + return "", fmt.Errorf("error generating key: %v", err) + } + encrypted, err := aesCrypt.AESEncrypt(plaintext, key) + if err != nil { + return "", fmt.Errorf("error encrypting: %v", err) + } + return encrypted, nil +} + +// DecryptAES decrypts ciphertext using AES with a key derived from baseKey and uuid. +func DecryptAES(ciphertext, baseKey, uuid string) (string, error) { + key, err := GenerateKeyByUUID(baseKey, uuid) + if err != nil { + return "", fmt.Errorf("error generating key: %v", err) + } + decrypted, err := aesCrypt.AESDecrypt(ciphertext, key) + if err != nil { + return "", fmt.Errorf("error decrypting: %v", err) + } + return decrypted, nil +} diff --git a/agent/utils/files.go b/agent/utils/files.go index 699910dce..d6903ddc5 100644 --- a/agent/utils/files.go +++ b/agent/utils/files.go @@ -2,50 +2,12 @@ package utils import ( "bufio" - "encoding/json" - "fmt" "html/template" "io" "os" "path/filepath" - "reflect" - - "gopkg.in/yaml.v2" ) -func GetMyPath() string { - ex, err := os.Executable() - if err != nil { - return "" - } - exPath := filepath.Dir(ex) - return exPath -} - -func ReadYAML(path string, result interface{}) error { - if result == nil { - return fmt.Errorf("result interface is nil") - } - - rv := reflect.ValueOf(result) - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return fmt.Errorf("result must be a non-nil pointer") - } - - file, err := os.Open(path) - if err != nil { - return err - } - defer func() { _ = file.Close() }() - - d := yaml.NewDecoder(file) - if err := d.Decode(result); err != nil { - return err - } - - return nil -} - func WriteStringToFile(fileName string, body string) error { file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.ModePerm) if err != nil { @@ -57,48 +19,6 @@ func WriteStringToFile(fileName string, body string) error { return err } -func WriteYAML(url string, data interface{}) error { - config, err := yaml.Marshal(data) - if err != nil { - return err - } - - err = WriteStringToFile(url, string(config)) - if err != nil { - return err - } - - return nil -} - -func WriteJSON(path string, data interface{}) error { - jsonData, err := json.MarshalIndent(data, "", " ") - if err != nil { - return err - } - - err = WriteStringToFile(path, string(jsonData)) - if err != nil { - return err - } - - return nil -} - -func ReadJson(fileName string, data interface{}) error { - content, err := os.ReadFile(fileName) - if err != nil { - return err - } - - err = json.Unmarshal(content, data) - if err != nil { - return err - } - - return nil -} - func GenerateFromTemplate(data interface{}, templateFile string, configFile string) error { _, fileName := filepath.Split(templateFile) ut, err := template.New(fileName).ParseFiles(templateFile) @@ -121,24 +41,6 @@ func GenerateFromTemplate(data interface{}, templateFile string, configFile stri return nil } -func CreatePathIfNotExist(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - if err := os.MkdirAll(path, 0755); err != nil { - return fmt.Errorf("error creating path: %v", err) - } - } else if err != nil { - return fmt.Errorf("error checking path: %v", err) - } - return nil -} - -func CheckIfPathExist(path string) bool { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false - } - return true -} - func ReadFileLines(path string) ([]string, error) { file, err := os.Open(path) if err != nil { diff --git a/agent/utils/int_tls.go b/agent/utils/int_tls.go index 5ca5928f3..1c74ec7c1 100644 --- a/agent/utils/int_tls.go +++ b/agent/utils/int_tls.go @@ -8,6 +8,8 @@ import ( "os" "path/filepath" "time" + + "github.com/utmstack/UTMStack/shared/fs" ) const ( @@ -63,11 +65,11 @@ func LoadIntegrationTLSConfig(certPath, keyPath string) (*tls.Config, error) { } func ValidateIntegrationCertificates(certPath, keyPath string) error { - if !CheckIfPathExist(certPath) { + if !fs.Exists(certPath) { return fmt.Errorf("certificate file not found: %s", certPath) } - if !CheckIfPathExist(keyPath) { + if !fs.Exists(keyPath) { return fmt.Errorf("private key file not found: %s", keyPath) } @@ -121,10 +123,10 @@ func ValidateIntegrationCertificates(certPath, keyPath string) error { func LoadUserCertificatesWithStruct(src, dest CertificateFiles) error { // Validate source certificates - if !CheckIfPathExist(src.CertPath) { + if !fs.Exists(src.CertPath) { return fmt.Errorf("user certificate file not found: %s", src.CertPath) } - if !CheckIfPathExist(src.KeyPath) { + if !fs.Exists(src.KeyPath) { return fmt.Errorf("user private key file not found: %s", src.KeyPath) } if err := ValidateIntegrationCertificates(src.CertPath, src.KeyPath); err != nil { @@ -133,7 +135,7 @@ func LoadUserCertificatesWithStruct(src, dest CertificateFiles) error { // Prepare destination directory certsDir := filepath.Dir(dest.CertPath) - if err := CreatePathIfNotExist(certsDir); err != nil { + if err := fs.CreateDirIfNotExist(certsDir); err != nil { return fmt.Errorf("error creating certificates directory: %w", err) } @@ -147,7 +149,7 @@ func LoadUserCertificatesWithStruct(src, dest CertificateFiles) error { // Copy CA certificate (use source CA if exists, otherwise use cert as CA) caSource := src.CAPath - if caSource == "" || !CheckIfPathExist(caSource) { + if caSource == "" || !fs.Exists(caSource) { caSource = src.CertPath } if err := copyFile(caSource, dest.CAPath); err != nil { diff --git a/agent/utils/logger.go b/agent/utils/logger.go index a93ac7455..81e6abc3a 100644 --- a/agent/utils/logger.go +++ b/agent/utils/logger.go @@ -5,12 +5,13 @@ import ( "sync" "github.com/threatwinds/logger" + "github.com/utmstack/UTMStack/shared/fs" ) var ( Logger *logger.Logger loggerOnceInstance sync.Once - logLevelConfigFile = filepath.Join(GetMyPath(), "log_level.yml") + logLevelConfigFile = filepath.Join(fs.GetExecutablePath(), "log_level.yml") LogLevelMap = map[string]int{ "debug": 100, "info": 200, @@ -28,7 +29,7 @@ type LogLevels struct { func InitLogger(filename string) { logLevel := LogLevels{} - err := ReadYAML(logLevelConfigFile, &logLevel) + err := fs.ReadYAML(logLevelConfigFile, &logLevel) if err != nil { logLevel.Level = "info" } diff --git a/agent/utils/req.go b/agent/utils/req.go index a7d0dfa5d..d0ed278dd 100644 --- a/agent/utils/req.go +++ b/agent/utils/req.go @@ -21,11 +21,10 @@ func DoReq[response any](url string, data []byte, method string, headers map[str req.Header.Add(k, v) } - tr := &http.Transport{ + client := &http.Client{} + client.Transport = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTlsVerification}, } - client := &http.Client{Transport: tr} - defer tr.CloseIdleConnections() resp, err := client.Do(req) if err != nil { diff --git a/agent/utils/services.go b/agent/utils/services.go index a0a828d3f..30f0cf4ee 100644 --- a/agent/utils/services.go +++ b/agent/utils/services.go @@ -5,12 +5,15 @@ import ( "os" "runtime" "strings" + + "github.com/utmstack/UTMStack/shared/exec" + "github.com/utmstack/UTMStack/shared/fs" ) func CheckIfServiceIsActive(serv string) (bool, error) { var errB bool var output string - path := GetMyPath() + path := fs.GetExecutablePath() switch runtime.GOOS { case "windows": @@ -42,21 +45,21 @@ func CheckIfServiceIsActive(serv string) (bool, error) { } func StartService(name string) error { - path := GetMyPath() + path := fs.GetExecutablePath() switch runtime.GOOS { case "windows": - err := Execute("sc", path, "start", name) + err := exec.Run("sc", path, "start", name) if err != nil { return fmt.Errorf("error starting service: %v", err) } case "linux": - err := Execute("systemctl", path, "start", name) + err := exec.Run("systemctl", path, "start", name) if err != nil { return fmt.Errorf("error starting service: %v", err) } case "darwin": plistPath := fmt.Sprintf("/Library/LaunchDaemons/%s.plist", name) - err := Execute("launchctl", path, "load", plistPath) + err := exec.Run("launchctl", path, "load", plistPath) if err != nil { return fmt.Errorf("error starting macOS service: %v", err) } @@ -65,20 +68,20 @@ func StartService(name string) error { } func StopService(name string) error { - path := GetMyPath() + path := fs.GetExecutablePath() switch runtime.GOOS { case "windows": - err := Execute("sc", path, "stop", name) + err := exec.Run("sc", path, "stop", name) if err != nil { return fmt.Errorf("error stoping service: %v", err) } case "linux": - err := Execute("systemctl", path, "stop", name) + err := exec.Run("systemctl", path, "stop", name) if err != nil { return fmt.Errorf("error stoping service: %v", err) } case "darwin": - err := Execute("launchctl", path, "remove", name) + err := exec.Run("launchctl", path, "remove", name) if err != nil { return fmt.Errorf("error stopping macOS service: %v", err) } @@ -87,41 +90,41 @@ func StopService(name string) error { } func UninstallService(name string) error { - path := GetMyPath() + path := fs.GetExecutablePath() switch runtime.GOOS { case "windows": - err := Execute("sc", path, "delete", name) + err := exec.Run("sc", path, "delete", name) if err != nil { return fmt.Errorf("error uninstalling service: %v", err) } case "linux": - err := Execute("systemctl", path, "disable", name) + err := exec.Run("systemctl", path, "disable", name) if err != nil { return fmt.Errorf("error uninstalling service: %v", err) } - err = Execute("rm", "/etc/systemd/system/", "/etc/systemd/system/"+name+".service") + err = exec.Run("rm", "/etc/systemd/system/", "/etc/systemd/system/"+name+".service") if err != nil { return fmt.Errorf("error uninstalling service: %v", err) } case "darwin": - Execute("launchctl", path, "remove", name) - Execute("rm", "/Library/LaunchDaemons/"+name+".plist") - Execute("rm", "/Users/"+os.Getenv("USER")+"/Library/LaunchAgents/"+name+".plist") + exec.Run("launchctl", path, "remove", name) + exec.Run("rm", "/Library/LaunchDaemons/"+name+".plist") + exec.Run("rm", "/Users/"+os.Getenv("USER")+"/Library/LaunchAgents/"+name+".plist") } return nil } func CheckIfServiceIsInstalled(serv string) (bool, error) { - path := GetMyPath() + path := fs.GetExecutablePath() var err error switch runtime.GOOS { case "windows": - err = Execute("sc", path, "query", serv) + err = exec.Run("sc", path, "query", serv) case "linux": - err = Execute("systemctl", path, "status", serv) + err = exec.Run("systemctl", path, "status", serv) case "darwin": - err = Execute("launchctl", path, "list", serv) + err = exec.Run("launchctl", path, "list", serv) default: return false, fmt.Errorf("operative system unknown") } @@ -131,7 +134,7 @@ func CheckIfServiceIsInstalled(serv string) (bool, error) { func CreateLinuxService(serviceName string, execStart string) error { servicePath := "/etc/systemd/system/" + serviceName + ".service" - if !CheckIfPathExist(servicePath) { + if !fs.Exists(servicePath) { file, err := os.Create(servicePath) if err != nil { return fmt.Errorf("error creating %s file: %v", servicePath, err) diff --git a/agent/utils/watcher.go b/agent/utils/watcher.go index 4ad83f882..109b623ce 100644 --- a/agent/utils/watcher.go +++ b/agent/utils/watcher.go @@ -1,53 +1,79 @@ package utils import ( + "bufio" "fmt" + "io" + "os" "regexp" "time" ) -func TailLogFile(filePath string, logLinesChan chan string, stopChan chan bool) { - latestLine := "null" +func TailLogFile(filePath string, logLinesChan chan string, stopChan chan struct{}) { + var offset int64 + + // Start from end of file to avoid re-sending old lines + if info, err := os.Stat(filePath); err == nil { + offset = info.Size() + } -loop: for { select { case <-stopChan: - break loop - + return default: - lines, err := ReadFileLines(filePath) + newOffset, err := readNewLines(filePath, offset, logLinesChan) if err != nil { - Logger.Info("error reading file %s: %v\n", filePath, err) - continue - } - if len(lines) == 1 && latestLine != lines[0] { - logLinesChan <- lines[0] - latestLine = lines[0] - } else if len(lines) > 1 && latestLine != lines[len(lines)-1] { - var startIndex = 0 - if latestLine != "null" { - for i, v := range lines { - if v == latestLine { - startIndex = i + 1 - break - } - } - } - for _, line := range lines[startIndex:] { - logLinesChan <- line - } - if len(lines) > 0 { - latestLine = lines[len(lines)-1] - } + Logger.Info("error reading file %s: %v", filePath, err) + } else { + offset = newOffset } time.Sleep(time.Second) } } } +func readNewLines(filePath string, offset int64, logLinesChan chan string) (int64, error) { + file, err := os.Open(filePath) + if err != nil { + return offset, err + } + defer file.Close() + + // Check if file was truncated or rotated + info, err := file.Stat() + if err != nil { + return offset, err + } + if info.Size() < offset { + offset = 0 + } + + if info.Size() == offset { + return offset, nil + } + + if _, err := file.Seek(offset, io.SeekStart); err != nil { + return offset, err + } + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if line != "" { + logLinesChan <- line + } + } + + newOffset, err := file.Seek(0, io.SeekCurrent) + if err != nil { + return offset, err + } + return newOffset, scanner.Err() +} + func WatchFolder(logType string, logsPath string, logLinesChan chan string) { - stopChan := make(chan bool) + var currentStopChan chan struct{} latestLog := "" pattern := regexp.MustCompile(fmt.Sprintf(`%s-(\d+)(?:-(\d+))?\.ndjson`, logType)) @@ -56,7 +82,7 @@ func WatchFolder(logType string, logsPath string, logLinesChan chan string) { for range ticker.C { isEmpty, err := IsDirEmpty(logsPath) if err != nil { - Logger.Info("error checking if %s is empty: %v\n", logsPath, err) + Logger.Info("error checking if %s is empty: %v", logsPath, err) continue } if !isEmpty { @@ -66,11 +92,12 @@ func WatchFolder(logType string, logsPath string, logLinesChan chan string) { continue } if newLatestLog != latestLog && newLatestLog != "" { - if latestLog != "" { - stopChan <- true + if currentStopChan != nil { + close(currentStopChan) } latestLog = newLatestLog - go TailLogFile(latestLog, logLinesChan, stopChan) + currentStopChan = make(chan struct{}) + go TailLogFile(latestLog, logLinesChan, currentStopChan) } } } diff --git a/agent/version.json b/agent/version.json index 685be4c14..b60b61712 100644 --- a/agent/version.json +++ b/agent/version.json @@ -1,4 +1,4 @@ { - "version": "11.1.3", - "updater_version": "1.0.2" + "version": "11.1.4", + "updater_version": "1.0.4" } diff --git a/backend/.gitignore b/backend/.gitignore index 518edd788..95d319e15 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -144,3 +144,9 @@ Desktop.ini ###################### .eslintcache init-utmstack-backend.bat + +###################### +# ENV +###################### + +.env/ diff --git a/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java b/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java index 9252976f4..9478be6b8 100644 --- a/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java @@ -128,8 +128,7 @@ public void configure(HttpSecurity http) throws Exception { .and() .saml2Login() .successHandler(new Saml2LoginSuccessHandler(tokenProvider, - userRepository, - saml2LoginFailureHandler())) + userRepository)) .failureHandler(new Saml2LoginFailureHandler()) .and() .apply(securityConfigurerAdapterForJwt()) diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java new file mode 100644 index 000000000..9264ed181 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java @@ -0,0 +1,57 @@ +package com.park.utmstack.config.saml; + +import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.concurrent.*; + +@Slf4j +public class SamlMetadataFetcher { + + private static final Duration TIMEOUT = Duration.ofSeconds(10); + + private final ExecutorService executor = Executors.newFixedThreadPool(5, r -> { + Thread t = new Thread(r); + t.setName("saml-metadata-fetch"); + t.setDaemon(true); + return t; + }); + + public RelyingPartyRegistration fetch(IdentityProviderConfig entity, + PrivateKey spKey, + X509Certificate spCert) { + + CompletableFuture future = + CompletableFuture.supplyAsync(() -> { + try { + return RelyingPartyRegistrations + .fromMetadataLocation(entity.getMetadataUrl()) + .registrationId(entity.getName()) + .entityId(entity.getSpEntityId()) + .assertionConsumerServiceLocation(entity.getSpAcsUrl()) + .signingX509Credentials(c -> c.add(Saml2X509Credential.signing(spKey, spCert))) + .build(); + } catch (Exception e) { + throw new CompletionException(e); + } + }, executor); + + try { + return future.get(TIMEOUT.getSeconds(), TimeUnit.SECONDS); + + } catch (Exception e) { + future.cancel(true); + log.error("Metadata fetch failed for provider '{}': {}", entity.getName(), e.getMessage()); + return null; + } + } +} + + + diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java index 49c063cc6..d466998af 100644 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java @@ -5,22 +5,28 @@ import com.park.utmstack.repository.idp_provider.IdentityProviderConfigRepository; import com.park.utmstack.util.CipherUtil; import com.park.utmstack.util.saml.PemUtils; -import org.springframework.security.saml2.core.Saml2X509Credential; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +@Slf4j public class SamlRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository { - private final Map registrations = new ConcurrentHashMap<>(); + private volatile Map registrations = new ConcurrentHashMap<>(); + private final SamlMetadataFetcher fetcher; + private final String encryptionKey; public SamlRelyingPartyRegistrationRepository(IdentityProviderConfigRepository jpaProviderRepository) { + + encryptionKey = getValidatedEncryptionKey(); + fetcher = new SamlMetadataFetcher(); + loadProviders(jpaProviderRepository); } @@ -30,32 +36,71 @@ public RelyingPartyRegistration findByRegistrationId(String registrationId) { } public void reloadProviders(IdentityProviderConfigRepository jpaProviderRepository) { - registrations.clear(); - loadProviders(jpaProviderRepository); + try { + registrations = loadActiveProviders(jpaProviderRepository); + log.info("SAML providers reloaded successfully: {} providers loaded", registrations.size()); + } catch (Exception e) { + log.error("Failed to reload SAML providers - keeping previous configuration", e); + } } + /** + * Loads SAML providers using the specialized loader. + * Delegates all async loading logic to SamlProvidersLoader. + * App will start without providers if loading fails. + */ private void loadProviders(IdentityProviderConfigRepository jpaProviderRepository) { - jpaProviderRepository.findAllByActiveTrue().forEach(entity -> { - RelyingPartyRegistration registration = buildRelyingPartyRegistration(entity); - registrations.put(entity.getProviderType().name().toLowerCase(), registration); - }); + try { + registrations = loadActiveProviders(jpaProviderRepository); + if (registrations.isEmpty()) { + log.warn("No active SAML2 providers found. SAML2 authentication will not be available."); + } else { + log.info("Successfully loaded {} SAML2 provider(s) on startup", registrations.size()); + } + } catch (Exception e) { + log.error("Error during SAML provider loading - app will start without SAML2 authentication: {}", + e.getMessage(), e); + } } - private RelyingPartyRegistration buildRelyingPartyRegistration(IdentityProviderConfig entity) { - - PrivateKey spKey = PemUtils.parsePrivateKey(CipherUtil.decrypt( - entity.getSpPrivateKeyPem(), - System.getenv(Constants.ENV_ENCRYPTION_KEY) - )); - X509Certificate spCert = PemUtils.parseCertificate(entity.getSpCertificatePem()); - - return RelyingPartyRegistrations - .fromMetadataLocation(entity.getMetadataUrl()) - .registrationId(entity.getName()) - .entityId(entity.getSpEntityId()) - .assertionConsumerServiceLocation(entity.getSpAcsUrl()) - .signingX509Credentials(c -> c.add(Saml2X509Credential.signing(spKey, spCert))) - .build(); + /** + * Validates and retrieves the encryption key from environment variables. + * + * @return The validated encryption key + * @throws IllegalStateException if ENCRYPTION_KEY is not configured + */ + private String getValidatedEncryptionKey() { + String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); + if (encryptionKey == null || encryptionKey.isBlank()) { + throw new IllegalStateException( + "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); + } + return encryptionKey; + } + + private Map loadActiveProviders(IdentityProviderConfigRepository repo) { + + Map map = new ConcurrentHashMap<>(); + + List activeProviders = repo.findAllByActiveTrue(); + + activeProviders.forEach(entity -> { + + PrivateKey spKey = PemUtils.parsePrivateKey(CipherUtil.decrypt( + entity.getSpPrivateKeyPem(), + encryptionKey)); + + X509Certificate spCert = PemUtils.parseCertificate(entity.getSpCertificatePem()); + + RelyingPartyRegistration reg = fetcher.fetch(entity, spKey, spCert); + + if (reg != null) { + map.put(entity.getProviderType().name().toLowerCase(), reg); + } + }); + + return map; } + } \ No newline at end of file diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleApache.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleApache.java deleted file mode 100644 index 99926b557..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleApache.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleApache implements IModule { - private static final String CLASSNAME = "ModuleApache"; - - private final UtmModuleService moduleService; - - public ModuleApache(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.APACHE); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.APACHE; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleAuditD.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleAuditD.java deleted file mode 100644 index f284c3cd0..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleAuditD.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleAuditD implements IModule { - private static final String CLASSNAME = "ModuleAuditD"; - - private final UtmModuleService moduleService; - - public ModuleAuditD(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.AUDITD); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.AUDITD; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleElasticsearch.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleElasticsearch.java deleted file mode 100644 index 2027641cb..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleElasticsearch.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleElasticsearch implements IModule { - private static final String CLASSNAME = "ModuleElasticsearch"; - - private final UtmModuleService moduleService; - - public ModuleElasticsearch(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.ELASTICSEARCH); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.ELASTICSEARCH; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleHaProxy.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleHaProxy.java deleted file mode 100644 index 82af81dbc..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleHaProxy.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleHaProxy implements IModule { - private static final String CLASSNAME = "ModuleHaProxy"; - - private final UtmModuleService moduleService; - - public ModuleHaProxy(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.HAPROXY); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.HAPROXY; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleIIS.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleIIS.java deleted file mode 100644 index ae40db0a2..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleIIS.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleIIS implements IModule { - private static final String CLASSNAME = "ModuleIIS"; - - private final UtmModuleService moduleService; - - public ModuleIIS(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.IIS); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.IIS; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKafka.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKafka.java deleted file mode 100644 index fb79ed7bb..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKafka.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleKafka implements IModule { - private static final String CLASSNAME = "ModuleKafka"; - - private final UtmModuleService moduleService; - - public ModuleKafka(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.KAFKA); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.KAFKA; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKibana.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKibana.java deleted file mode 100644 index 37a6b8583..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKibana.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleKibana implements IModule { - private static final String CLASSNAME = "ModuleKibana"; - - private final UtmModuleService moduleService; - - public ModuleKibana(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.KIBANA); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.KIBANA; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleLogstash.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleLogstash.java deleted file mode 100644 index cef8dde4f..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleLogstash.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleLogstash implements IModule { - private static final String CLASSNAME = "ModuleLogstash"; - - private final UtmModuleService moduleService; - - public ModuleLogstash(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.LOGSTASH); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.LOGSTASH; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMongoDb.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMongoDb.java deleted file mode 100644 index 66e55819a..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMongoDb.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleMongoDb implements IModule { - private static final String CLASSNAME = "ModuleMongoDb"; - - private final UtmModuleService moduleService; - - public ModuleMongoDb(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.MONGODB); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.MONGODB; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMySql.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMySql.java deleted file mode 100644 index c0728dc34..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMySql.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleMySql implements IModule { - private static final String CLASSNAME = "ModuleMySql"; - - private final UtmModuleService moduleService; - - public ModuleMySql(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.MYSQL); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.MYSQL; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNats.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNats.java deleted file mode 100644 index 8bf67719c..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNats.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleNats implements IModule { - private static final String CLASSNAME = "ModuleNats"; - - private final UtmModuleService moduleService; - - public ModuleNats(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.NATS); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.NATS; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNginx.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNginx.java deleted file mode 100644 index 72c865ac1..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNginx.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleNginx implements IModule { - private static final String CLASSNAME = "ModuleNginx"; - - private final UtmModuleService moduleService; - - public ModuleNginx(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.NGINX); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.NGINX; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleOsQuery.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleOsQuery.java deleted file mode 100644 index d8aa48f02..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleOsQuery.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleOsQuery implements IModule { - private static final String CLASSNAME = "ModuleOsQuery"; - - private final UtmModuleService moduleService; - - public ModuleOsQuery(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.OSQUERY); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.OSQUERY; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModulePostgreSql.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModulePostgreSql.java deleted file mode 100644 index 095990766..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModulePostgreSql.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModulePostgreSql implements IModule { - private static final String CLASSNAME = "ModulePostgreSql"; - - private final UtmModuleService moduleService; - - public ModulePostgreSql(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.POSTGRESQL); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.POSTGRESQL; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleRedis.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleRedis.java deleted file mode 100644 index 516ec2cff..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleRedis.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleRedis implements IModule { - private static final String CLASSNAME = "ModuleRedis"; - - private final UtmModuleService moduleService; - - public ModuleRedis(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.REDIS); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.REDIS; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleTraefik.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleTraefik.java deleted file mode 100644 index 2c45d2306..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleTraefik.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleTraefik implements IModule { - private static final String CLASSNAME = "ModuleTraefik"; - - private final UtmModuleService moduleService; - - public ModuleTraefik(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.TRAEFIK); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.TRAEFIK; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/chart_builder/UtmVisualization.java b/backend/src/main/java/com/park/utmstack/domain/chart_builder/UtmVisualization.java index d3f0b0875..0f999ce11 100644 --- a/backend/src/main/java/com/park/utmstack/domain/chart_builder/UtmVisualization.java +++ b/backend/src/main/java/com/park/utmstack/domain/chart_builder/UtmVisualization.java @@ -219,7 +219,7 @@ public void setChartConfig(String chartConfig) { this.chartConfig = chartConfig; } - public AggregationType getAggregationType() throws UtmSerializationException { + public AggregationType getAggregationType() { if (StringUtils.hasText(aggregation)) aggregationType = UtilSerializer.jsonDeserialize(AggregationType.class, aggregation); return aggregationType; diff --git a/backend/src/main/java/com/park/utmstack/domain/correlation/config/UtmTenantConfig.java b/backend/src/main/java/com/park/utmstack/domain/correlation/config/UtmTenantConfig.java index 31881145e..a6fa81ab3 100644 --- a/backend/src/main/java/com/park/utmstack/domain/correlation/config/UtmTenantConfig.java +++ b/backend/src/main/java/com/park/utmstack/domain/correlation/config/UtmTenantConfig.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.park.utmstack.util.UtilSerializer; import com.park.utmstack.util.exceptions.UtmSerializationException; +import lombok.Getter; +import lombok.Setter; import org.hibernate.annotations.GenericGenerator; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -25,16 +27,22 @@ public class UtmTenantConfig implements Serializable { private static final long serialVersionUID = 1L; + @Setter + @Getter @Id @GenericGenerator(name = "CustomIdentityGenerator", strategy = "com.park.utmstack.util.CustomIdentityGenerator") @GeneratedValue(generator = "CustomIdentityGenerator") @Column(name = "id", updatable = false) private Long id; + @Setter + @Getter @Size(max = 250) @Column(name = "asset_name", length = 250, nullable = false) private String assetName; + @Setter + @Getter @JsonIgnore @Column(name = "asset_hostname_list_def") private String assetHostnameListDef; @@ -44,6 +52,8 @@ public class UtmTenantConfig implements Serializable { @JsonDeserialize private List assetHostnameList; + @Setter + @Getter @JsonIgnore @Column(name = "asset_ip_list_def") private String assetIpListDef; @@ -53,26 +63,25 @@ public class UtmTenantConfig implements Serializable { @JsonDeserialize private List assetIpList; + @Setter + @Getter @Column(name = "asset_confidentiality", nullable = false) private Integer assetConfidentiality; + @Setter + @Getter @Column(name = "asset_integrity", nullable = false) private Integer assetIntegrity; + @Setter + @Getter @Column(name = "asset_availability", nullable = false) private Integer assetAvailability; + @Getter @Column(name = "last_update") private Instant lastUpdate; - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - public List getAssetHostnameList() throws UtmSerializationException { if (StringUtils.hasText(assetHostnameListDef)) assetHostnameList = UtilSerializer.jsonDeserializeList(String.class, assetHostnameListDef); @@ -87,10 +96,6 @@ public void setAssetHostnameList(List assetHostnameList) throws UtmSeria this.assetHostnameList = assetHostnameList; } - public String getAssetHostnameListDef() { - return assetHostnameListDef; - } - public List getAssetIpList() throws UtmSerializationException { if (StringUtils.hasText(assetIpListDef)) assetIpList = UtilSerializer.jsonDeserializeList(String.class, assetIpListDef); @@ -105,54 +110,6 @@ public void setAssetIpList(List assetIpList) throws UtmSerializationExce this.assetIpList = assetIpList; } - public void setAssetHostnameListDef(String assetHostnameListDef) { - this.assetHostnameListDef = assetHostnameListDef; - } - - public String getAssetIpListDef() { - return assetIpListDef; - } - - public void setAssetIpListDef(String assetIpListDef) { - this.assetIpListDef = assetIpListDef; - } - - public Integer getAssetAvailability() { - return assetAvailability; - } - - public void setAssetAvailability(Integer assetAvailability) { - this.assetAvailability = assetAvailability; - } - - public Integer getAssetIntegrity() { - return assetIntegrity; - } - - public void setAssetIntegrity(Integer assetIntegrity) { - this.assetIntegrity = assetIntegrity; - } - - public Integer getAssetConfidentiality() { - return assetConfidentiality; - } - - public void setAssetConfidentiality(Integer assetConfidentiality) { - this.assetConfidentiality = assetConfidentiality; - } - - public String getAssetName() { - return assetName; - } - - public void setAssetName(String assetName) { - this.assetName = assetName; - } - - public Instant getLastUpdate() { - return lastUpdate; - } - public void setLastUpdate() { this.lastUpdate = Instant.now(Clock.systemUTC()); } diff --git a/backend/src/main/java/com/park/utmstack/domain/datainput_ingestion/UtmDataInputStatus.java b/backend/src/main/java/com/park/utmstack/domain/datainput_ingestion/UtmDataInputStatus.java index 8a4dd7046..19a94835e 100644 --- a/backend/src/main/java/com/park/utmstack/domain/datainput_ingestion/UtmDataInputStatus.java +++ b/backend/src/main/java/com/park/utmstack/domain/datainput_ingestion/UtmDataInputStatus.java @@ -43,6 +43,10 @@ public class UtmDataInputStatus implements Serializable { @Column(name = "median") private Long median; + @Size(max = 500) + @Column(name = "alias", length = 500, nullable = true) + private String alias; + /** * Define if a source is down or up. * Null is returned when the calculation could not be done. diff --git a/backend/src/main/java/com/park/utmstack/repository/correlation/config/UtmTenantConfigRepository.java b/backend/src/main/java/com/park/utmstack/repository/correlation/config/UtmTenantConfigRepository.java index 34fe0e922..4719e200f 100644 --- a/backend/src/main/java/com/park/utmstack/repository/correlation/config/UtmTenantConfigRepository.java +++ b/backend/src/main/java/com/park/utmstack/repository/correlation/config/UtmTenantConfigRepository.java @@ -9,10 +9,15 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.Optional; + public interface UtmTenantConfigRepository extends JpaRepository, JpaSpecificationExecutor { + @Query(value = "SELECT t FROM UtmTenantConfig t WHERE" + "(:search IS NULL OR ((t.assetName LIKE :search OR lower(t.assetName) LIKE lower(:search))))") Page searchByFilters(@Param("search") String search, Pageable pageable); + + Optional findByAssetName(String assetName); } diff --git a/backend/src/main/java/com/park/utmstack/repository/datainput_ingestion/UtmDataInputStatusRepository.java b/backend/src/main/java/com/park/utmstack/repository/datainput_ingestion/UtmDataInputStatusRepository.java index 47be50866..a167dde27 100644 --- a/backend/src/main/java/com/park/utmstack/repository/datainput_ingestion/UtmDataInputStatusRepository.java +++ b/backend/src/main/java/com/park/utmstack/repository/datainput_ingestion/UtmDataInputStatusRepository.java @@ -65,4 +65,6 @@ and lower(trim(ds.dataType)) != lower(trim(:dataType)) @Query("SELECT s FROM UtmDataInputStatus s WHERE s.source = :ip OR s.source = :hostname") List findByIpOrHostname(@Param("ip") String ip, @Param("hostname") String hostname); + + Optional findBySourceIsIn(List sources); } diff --git a/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java b/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java index 536418db3..27aabe8f5 100644 --- a/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java +++ b/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java @@ -12,7 +12,6 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.web.util.UriComponentsBuilder; @@ -22,9 +21,6 @@ import java.net.URI; import java.util.Collection; import java.util.Objects; -import java.util.Optional; - -import static com.park.utmstack.config.Constants.FRONT_BASE_URL; /** * Success handler for SAML2 login. @@ -38,7 +34,6 @@ public class Saml2LoginSuccessHandler implements AuthenticationSuccessHandler { private final TokenProvider tokenProvider; private final UserRepository userRepository; - private final Saml2LoginFailureHandler failureHandler; @Override @@ -47,15 +42,17 @@ public void onAuthenticationSuccess(HttpServletRequest request, Authentication authentication) throws IOException { String scheme = Objects.requireNonNullElse(request.getHeader("X-Forwarded-Proto"), request.getScheme()); - String host = Objects.requireNonNullElse(request.getHeader("Host"), request.getServerName()); - + String host = Objects.requireNonNullElse(request.getHeader("X-Forwarded-Host"), request.getServerName()); String frontBaseUrl = scheme + "://" + host; Saml2AuthenticatedPrincipal samlUser = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); String username = samlUser.getName(); User user = userRepository.findOneByLogin(username) - .orElseThrow(() -> new BadCredentialsException("The provided credentials do not match any active user account.")); + .orElseThrow(() -> { + log.warn("SAML2 authentication successful for '{}' but user not found in local database", username); + return new BadCredentialsException("User not provisioned in local system"); + }); Collection authorities = Objects.requireNonNull(user.getAuthorities()) .stream() @@ -75,6 +72,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, .build() .toUri(); + log.info("SAML2 login successful for user: {}", username); response.sendRedirect(redirectUri.toString()); } } diff --git a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java index 7bbd975f6..c1129e065 100644 --- a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java +++ b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java @@ -1,8 +1,9 @@ package com.park.utmstack.service; import com.park.utmstack.config.Constants; -import com.park.utmstack.domain.datainput_ingestion.UtmDataInputStatus; import com.park.utmstack.domain.UtmServerModule; +import com.park.utmstack.domain.correlation.config.UtmTenantConfig; +import com.park.utmstack.domain.datainput_ingestion.UtmDataInputStatus; import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.domain.chart_builder.types.query.FilterType; import com.park.utmstack.domain.chart_builder.types.query.OperatorType; @@ -17,6 +18,7 @@ import com.park.utmstack.repository.correlation.config.UtmDataTypesRepository; import com.park.utmstack.repository.network_scan.UtmNetworkScanRepository; import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.service.correlation.config.UtmTenantConfigService; import com.park.utmstack.service.elasticsearch.ElasticsearchService; import com.park.utmstack.service.elasticsearch.SearchUtil; import com.park.utmstack.service.logstash_pipeline.response.statistic.StatisticDocument; @@ -34,7 +36,6 @@ import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -61,13 +62,14 @@ public class UtmDataInputStatusService { private final Logger log = LoggerFactory.getLogger(UtmDataInputStatusService.class); private final UtmDataInputStatusRepository dataInputStatusRepository; - private final UtmServerModuleService serverModuleService; private final ApplicationEventService applicationEventService; private final UtmNetworkScanService networkScanService; private final ElasticsearchService elasticsearchService; private final UtmDataTypesRepository dataTypesRepository; private final UtmNetworkScanRepository networkScanRepository; private final UtmDataInputStatusCheckpointRepository checkpointRepository; + private final UtmTenantConfigService utmTenantConfigService; + private final UtmServerModuleService serverModuleService; /** @@ -120,7 +122,7 @@ public void delete(String id) { dataInputStatusRepository.deleteById(id); } - @Scheduled(fixedDelay = 900000) + /* @Scheduled(fixedDelay = 900000)*/ public void checkDatasource() { final String ctx = CLASSNAME + ".checkDatasource"; final List types = Arrays.asList("aws", "o365", "hids"); @@ -174,7 +176,7 @@ private void checkDataInputStatus(List inputs, String server } } - @Scheduled(fixedDelay = 15000, initialDelay = 30000) + /* @Scheduled(fixedDelay = 15000, initialDelay = 30000)*/ public void syncDataInputStatus() { final String ctx = CLASSNAME + ".syncDataInputStatus"; @@ -193,10 +195,14 @@ public void syncDataInputStatus() { latestStats.forEach((key, stat) -> { try { String dataType = stat.getDataType(); - String dataSource = stat.getDataSource(); + String statName = stat.getDataSource(); + String sourceWithAlias = this.getSourceName(statName); + String resolvedAlias = sourceWithAlias != null ? statName : null; + String source = sourceWithAlias == null ? statName : sourceWithAlias; + long timestamp = Instant.parse(stat.getTimestamp()).getEpochSecond(); - String compositeKey = dataType + "-" + dataSource; + String compositeKey = dataType + "-" + source; UtmDataInputStatus status = existing.get(compositeKey); boolean changed = false; @@ -205,13 +211,16 @@ public void syncDataInputStatus() { status = UtmDataInputStatus.builder() .id(compositeKey) .dataType(dataType) - .source(dataSource) + .source(source) + .alias(resolvedAlias) .timestamp(timestamp) .median(86400L) .build(); changed = true; - } else if (status.getTimestamp() != timestamp) { + + } else if (status.getTimestamp() != timestamp || !Objects.equals(status.getAlias(), resolvedAlias)) { status.setTimestamp(timestamp); + status.setAlias(resolvedAlias); changed = true; } @@ -237,7 +246,7 @@ public void syncDataInputStatus() { * Gets the sources from utm_data_input_status that are not registered in utm_network_scan table * and create new assets with it. This method is a schedule with a delay of 1 hour */ - @Scheduled(fixedDelay = 30000, initialDelay = 60000) + /*@Scheduled(fixedDelay = 30000, initialDelay = 60000)*/ public void syncSourcesToAssets() { final String ctx = CLASSNAME + ".syncSourcesToAssets"; try { @@ -268,6 +277,7 @@ public void synchronizeSourcesToAssets() { } Map sourcesWithStatus = extractSourcesWithUpDownStatus(sources); + Map sourcesWithAlias = extractSourcesWithAlias(sources); List keys = new ArrayList<>(sourcesWithStatus.keySet()); List assets = networkScanRepository.findByAssetIpInOrAssetNameIn(keys, keys); @@ -281,16 +291,27 @@ public void synchronizeSourcesToAssets() { for (Map.Entry entry : sourcesWithStatus.entrySet()) { String key = entry.getKey(); + String alias = sourcesWithAlias.get(key); Boolean alive = entry.getValue(); + UtmNetworkScan asset = assetsByKey.get(key); + if (asset == null && StringUtils.hasText(alias)) { + asset = assetsByKey.get(alias); + } + if (asset != null) { if (asset.getUpdateLevel() == null || asset.getUpdateLevel().equals(UpdateLevel.DATASOURCE) || asset.getUpdateLevel().equals(UpdateLevel.AGENT)) { + + if (StringUtils.hasText(alias) && !alias.equals(asset.getAssetAlias())) { + asset.assetAliases(alias); + } + asset.assetAlive(alive) - .updateLevel(UpdateLevel.DATASOURCE) - .assetStatus(AssetStatus.CHECK) - .modifiedAt(LocalDateTime.now().toInstant(ZoneOffset.UTC)); + .updateLevel(UpdateLevel.DATASOURCE) + .assetStatus(AssetStatus.CHECK) + .modifiedAt(LocalDateTime.now().toInstant(ZoneOffset.UTC)); networkScanService.save(asset); } @@ -305,14 +326,14 @@ public void synchronizeSourcesToAssets() { if (missing && UpdateLevel.DATASOURCE.equals(asset.getUpdateLevel())) { asset.assetStatus(AssetStatus.MISSING) - .updateLevel(null) - .modifiedAt(LocalDateTime.now().toInstant(ZoneOffset.UTC)); + .updateLevel(null) + .modifiedAt(LocalDateTime.now().toInstant(ZoneOffset.UTC)); networkScanService.save(asset); } }); - networkScanRepository.deleteAllAssetsByDataType(excludeDataTypes); + networkScanRepository.deleteAllAssetsByDataType(excludeDataTypes); } catch (Exception e) { log.error("{}: Error synchronizing sources to assets - {}", ctx, e.getMessage(), e); @@ -320,6 +341,17 @@ public void synchronizeSourcesToAssets() { } } + private Map extractSourcesWithAlias(List sources) { + Map alias = new HashMap<>(); + + sources.forEach(src -> { + if (StringUtils.hasText(src.getAlias())) { + alias.put(src.getSource(), src.getAlias()); + } + }); + return alias; + } + private Map extractSourcesWithUpDownStatus(List sources) { Map upDown = new HashMap<>(); sources.forEach(src -> { @@ -497,4 +529,83 @@ private Map getLatestStatisticsByDataSource() { return result; } + private String getSourceName(String assetName) { + final String ctx = CLASSNAME + ".getDataSource"; + + Optional tenantConfig = this.utmTenantConfigService.findByAssetName(assetName); + + if (tenantConfig.isEmpty()) { + return null; + } + + List sources = buildSourcesList(tenantConfig.get()); + + if (CollectionUtils.isEmpty(sources)) { + return null; + } + + Optional dataInputStatus = this.findDataInputBySource(sources); + + return dataInputStatus + .map(UtmDataInputStatus::getSource) + .orElse(null); + } + + /** + * Builds a combined list of hostnames and IPs from tenant configuration + * + * @param tenantConfig the tenant configuration + * @return combined list of sources, or empty list if none available + */ + private List buildSourcesList(UtmTenantConfig tenantConfig) { + List sources = new ArrayList<>(); + + if (!CollectionUtils.isEmpty(tenantConfig.getAssetHostnameList())) { + sources.addAll(tenantConfig.getAssetHostnameList()); + } + + if (!CollectionUtils.isEmpty(tenantConfig.getAssetIpList())) { + sources.addAll(tenantConfig.getAssetIpList()); + } + + return sources; + } + + /** + * Finds a data input status by searching in the provided list of sources. + * Returns the first available source from the database. + * + * @param sources list of source hostnames/IPs to search for + * @return Optional containing the data input status if found + */ + public Optional findDataInputBySource(List sources) { + final String ctx = CLASSNAME + ".findDataInputBySource"; + + if (CollectionUtils.isEmpty(sources)) { + return Optional.empty(); + } + + try { + return this.dataInputStatusRepository.findBySourceIsIn(sources); + + } catch (Exception ex) { + log.error("{}: Error finding data input status by source {} - {}", + ctx, sources, ex.getMessage(), ex); + return Optional.empty(); + } + } + + public List findDataInputStatus() { + + List excludeDataTypes = dataTypesRepository.findAllByIncludedFalse() + .stream() + .map(UtmDataTypes::getDataType) + .collect(Collectors.toList()); + + excludeDataTypes.add(DataSourceConstants.IBM_AS400_TYPE); + + return dataInputStatusRepository.extractSourcesToExport(excludeDataTypes); + } + + } diff --git a/backend/src/main/java/com/park/utmstack/service/UtmServerModuleService.java b/backend/src/main/java/com/park/utmstack/service/UtmServerModuleService.java index 82da15235..02124b152 100644 --- a/backend/src/main/java/com/park/utmstack/service/UtmServerModuleService.java +++ b/backend/src/main/java/com/park/utmstack/service/UtmServerModuleService.java @@ -2,10 +2,12 @@ import com.park.utmstack.domain.UtmServerModule; import com.park.utmstack.repository.UtmServerModuleRepository; +import com.park.utmstack.util.exceptions.ApiException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -93,11 +95,42 @@ public void delete(Long id) { */ public List getModulesWithIntegrations(Long serverId, String prettyName) { List modules = utmServerModuleRepository.getModulesWithIntegrations(serverId, - StringUtils.hasText(prettyName) ? "'%" + prettyName + "%'" : null); + StringUtils.hasText(prettyName) ? "'%" + prettyName + "%'" : null); return !CollectionUtils.isEmpty(modules) ? modules : Collections.emptyList(); } public List findAllByModuleName(String moduleName) { return utmServerModuleRepository.findAllByModuleName(moduleName); } + + @Transactional + public void markForRestart(String moduleName) { + log.info("Marking module '{}' for restart due to inactivity.", moduleName); + + try { + List modules = utmServerModuleRepository.findAllByModuleName(moduleName); + + if (modules.isEmpty()) { + log.warn("No modules found with name '{}'. Skipping restart trigger.", moduleName); + return; + } + + List modulesToUpdate = modules.stream() + .filter(m -> !m.isNeedsRestart()) + .peek(m -> m.setNeedsRestart(true)) + .toList(); + + if (!modulesToUpdate.isEmpty()) { + utmServerModuleRepository.saveAll(modulesToUpdate); + log.info("Successfully marked {} instances of '{}' for restart.", + modulesToUpdate.size(), moduleName); + } else { + log.debug("Module '{}' was already marked for restart.", moduleName); + } + + } catch (Exception e) { + log.error("Failed to mark module '{}' for restart: {}", moduleName, e.getMessage()); + throw new ApiException("Error triggering module restart for: " + moduleName, HttpStatus.INTERNAL_SERVER_ERROR); + } + } } diff --git a/backend/src/main/java/com/park/utmstack/service/agent_manager/AgentService.java b/backend/src/main/java/com/park/utmstack/service/agent_manager/AgentService.java index a8cd2d5d5..ec94671bb 100644 --- a/backend/src/main/java/com/park/utmstack/service/agent_manager/AgentService.java +++ b/backend/src/main/java/com/park/utmstack/service/agent_manager/AgentService.java @@ -45,7 +45,7 @@ public class AgentService { * * @return A list of ${@link Agent} */ - private List getInstalledAgents() { + public List getInstalledAgents() { final String ctx = CLASSNAME + ".getInstalledAgents"; try { @@ -98,7 +98,7 @@ public Optional getAgentByHostName(String hostname) { } @Transactional - @Scheduled(fixedDelay = 15000, initialDelay = 30000) + /* @Scheduled(fixedDelay = 15000, initialDelay = 30000)*/ public void synchronizeAgents() { final String ctx = CLASSNAME + ".synchronizeAgents"; diff --git a/backend/src/main/java/com/park/utmstack/service/correlation/config/UtmTenantConfigService.java b/backend/src/main/java/com/park/utmstack/service/correlation/config/UtmTenantConfigService.java index 3f752c079..b8e7bc43b 100644 --- a/backend/src/main/java/com/park/utmstack/service/correlation/config/UtmTenantConfigService.java +++ b/backend/src/main/java/com/park/utmstack/service/correlation/config/UtmTenantConfigService.java @@ -118,12 +118,15 @@ public Optional findOne(Long id) { } } - /** - * Get all UtmTenantConfig. - * - * @param p the pagination parameters - * @return the list of tenant configurations - */ + public Optional findByAssetName(String assetName) { + final String ctx = CLASSNAME + ".findByAssetName"; + try { + return utmTenantConfigRepository.findByAssetName(assetName); + } catch (Exception e) { + throw new RuntimeException(ctx + ": " + e.getMessage()); + } + } + public Page findAll(String search, Pageable p) { final String ctx = CLASSNAME + ".findAll"; try { @@ -132,4 +135,5 @@ public Page findAll(String search, Pageable p) { throw new RuntimeException(ctx + ": " + e.getMessage()); } } + } diff --git a/backend/src/main/java/com/park/utmstack/service/elasticsearch/ElasticsearchService.java b/backend/src/main/java/com/park/utmstack/service/elasticsearch/ElasticsearchService.java index d70b28f45..0b4e6605c 100644 --- a/backend/src/main/java/com/park/utmstack/service/elasticsearch/ElasticsearchService.java +++ b/backend/src/main/java/com/park/utmstack/service/elasticsearch/ElasticsearchService.java @@ -20,6 +20,7 @@ import com.utmstack.opensearch_connector.types.IndexSort; import com.utmstack.opensearch_connector.types.SearchSqlResponse; import com.utmstack.opensearch_connector.types.SqlQueryRequest; +import lombok.extern.slf4j.Slf4j; import org.opensearch.client.opensearch._types.SortOrder; import org.opensearch.client.opensearch._types.query_dsl.Query; import org.opensearch.client.opensearch.cat.indices.IndicesRecord; @@ -47,6 +48,7 @@ * @author Leonardo M. López */ @Service +@Slf4j public class ElasticsearchService { private static final String CLASSNAME = "ElasticsearchService"; private final Logger log = LoggerFactory.getLogger(ElasticsearchService.class); @@ -78,7 +80,7 @@ public List getFieldValues(String keyword, String indexPattern) { final String ctx = CLASSNAME + ".getFieldValues"; try { return new ArrayList<>(client.getClient().getFieldValues(keyword, indexPattern, - null, 10000, TermOrder.Count, SortOrder.Desc).keySet()); + null, 10000, TermOrder.Count, SortOrder.Desc).keySet()); } catch (Exception e) { throw new RuntimeException(ctx + ": " + e.getLocalizedMessage()); } @@ -98,7 +100,7 @@ public Map getFieldValuesWithCount(String field, String index, Lis final String ctx = CLASSNAME + ".getFieldValuesWithCount"; try { return client.getClient().getFieldValues(field, index, SearchUtil.toQuery(filters), top, - orderByCount ? TermOrder.Count : TermOrder.Key, sortAsc ? SortOrder.Asc : SortOrder.Desc); + orderByCount ? TermOrder.Count : TermOrder.Key, sortAsc ? SortOrder.Asc : SortOrder.Desc); } catch (Exception e) { throw new RuntimeException(ctx + ": " + e.getLocalizedMessage()); } @@ -143,15 +145,17 @@ public IndexResponse index(String index, T document) { public List getIndexProperties(String indexPattern) { final String ctx = CLASSNAME + ".getIndexProperties"; - if (!indexExist(indexPattern)) - throw new OpenSearchIndexNotFoundException(ctx + ": Index [" + indexPattern + "] not found"); + if (!indexExist(indexPattern)) { + log.info("{} Index pattern {} does not exist", ctx, indexPattern); + return Collections.emptyList(); + } try { Map properties = client.getClient().getIndexProperties(indexPattern); if (CollectionUtils.isEmpty(properties)) return Collections.emptyList(); return properties.entrySet() - .stream().map(e -> new IndexPropertyType(e.getKey(), e.getValue())).collect(Collectors.toList()); + .stream().map(e -> new IndexPropertyType(e.getKey(), e.getValue())).collect(Collectors.toList()); } catch (Exception e) { throw new RuntimeException(ctx + ": " + e.getMessage()); } @@ -167,7 +171,7 @@ public List getIndexProperties(String indexPattern) { * @throws UtmElasticsearchException In case of any error */ public Page getAllIndexes(boolean includeSystemIndex, String pattern, Pageable pageable) throws - UtmElasticsearchException { + UtmElasticsearchException { final String ctx = CLASSNAME + ".getAllIndexes"; try { Assert.notNull(pageable, "Argument pageable can't be null"); @@ -179,7 +183,7 @@ public Page getAllIndexes(boolean includeSystemIndex, String patt if (!includeSystemIndex) indices = indices.stream().filter(index -> !index.index().startsWith(".")) - .collect(Collectors.toList()); + .collect(Collectors.toList()); PagedListHolder pageDefinition = new PagedListHolder<>(); pageDefinition.setSource(indices); @@ -198,7 +202,7 @@ private IndexSort from(Sort sort) { return IndexSort.unSorted(); IndexSort.Builder sortBuilder = IndexSort.builder(); sort.forEach(order -> sortBuilder.with(IndexSortableProperty.fromJsonValue(order.getProperty()), - order.getDirection().isAscending() ? SortOrder.Asc : SortOrder.Desc)); + order.getDirection().isAscending() ? SortOrder.Asc : SortOrder.Desc)); return sortBuilder.build(); } catch (Exception e) { throw new RuntimeException(ctx + ": " + e.getLocalizedMessage()); @@ -239,14 +243,14 @@ public void preventSystemCrashBySpace() { return; UtmSpaceNotificationControl notificationControl = spaceNotificationControlService.findById(1L) - .orElse(new UtmSpaceNotificationControl()); + .orElse(new UtmSpaceNotificationControl()); if (Objects.isNull(notificationControl.getId())) notificationControl.setId(1L); Instant now = LocalDateTime.now().toInstant(ZoneOffset.UTC); if (Objects.isNull(notificationControl.getNextNotification()) || - now.isAfter(notificationControl.getNextNotification())) { + now.isAfter(notificationControl.getNextNotification())) { mailService.sendLowSpaceEmail(admins, clusterStatus); notificationControl.setNextNotification(now.plus(24, ChronoUnit.HOURS)); spaceNotificationControlService.save(notificationControl); @@ -266,7 +270,7 @@ private void deleteOldestIndices() { final String ctx = CLASSNAME + ".deleteOldestIndices"; try { List indices = client.getClient().getIndices(Constants.SYS_INDEX_PATTERN.get(SystemIndexPattern.LOGS), IndexSort.builder() - .with(IndexSortableProperty.CreationDate, SortOrder.Asc).build()); + .with(IndexSortableProperty.CreationDate, SortOrder.Asc).build()); // If no index that match with log-* was found then te function is terminated if (CollectionUtils.isEmpty(indices)) @@ -278,10 +282,10 @@ private void deleteOldestIndices() { // Delete oldest indices deleteIndex(Collections.singletonList(index.index())); eventService.createEvent(String.format("Index %1$s was deleted to avoid system crash by space:\n" + - "Creation Date: %2$s\n" + - "Docs Count: %3$s\n" + - "Size: %4$s", - index.index(), index.creationDateString(), index.docsCount(), index.storeSize()), ApplicationEventType.INFO); + "Creation Date: %2$s\n" + + "Docs Count: %3$s\n" + + "Size: %4$s", + index.index(), index.creationDateString(), index.docsCount(), index.storeSize()), ApplicationEventType.INFO); } catch (Exception e) { String msg = String.format("%1$s: Fail to delete index: %2$s with message: %3$s", ctx, index.index(), e.getMessage()); eventService.createEvent(msg, ApplicationEventType.WARNING); diff --git a/backend/src/main/java/com/park/utmstack/service/grpc/AgentManagerGrpc.java b/backend/src/main/java/com/park/utmstack/service/grpc/AgentManagerGrpc.java index d48b04bf1..b72898c05 100644 --- a/backend/src/main/java/com/park/utmstack/service/grpc/AgentManagerGrpc.java +++ b/backend/src/main/java/com/park/utmstack/service/grpc/AgentManagerGrpc.java @@ -92,39 +92,40 @@ public static void registerAllExtensions( "\n\taddresses\030\016 \001(\t\"u\n\023BidirectionalStream" + "\022$\n\007command\030\001 \001(\0132\021.agent.UtmCommandH\000\022&" + "\n\006result\030\002 \001(\0132\024.agent.CommandResultH\000B\020" + - "\n\016stream_message\"\214\001\n\nUtmCommand\022\020\n\010agent" + + "\n\016stream_message\"\233\001\n\nUtmCommand\022\020\n\010agent" + "_id\030\001 \001(\t\022\017\n\007command\030\002 \001(\t\022\023\n\013executed_b" + "y\030\003 \001(\t\022\016\n\006cmd_id\030\004 \001(\t\022\023\n\013origin_type\030\005" + - " \001(\t\022\021\n\torigin_id\030\006 \001(\t\022\016\n\006reason\030\007 \001(\t\"" + - "r\n\rCommandResult\022\020\n\010agent_id\030\001 \001(\t\022\016\n\006re" + - "sult\030\002 \001(\t\022/\n\013executed_at\030\003 \001(\0132\032.google" + - ".protobuf.Timestamp\022\016\n\006cmd_id\030\004 \001(\t\"N\n\032L" + - "istAgentsCommandsResponse\022!\n\004rows\030\001 \003(\0132" + - "\023.agent.AgentCommand\022\r\n\005total\030\002 \001(\005\"\261\002\n\014" + - "AgentCommand\022.\n\ncreated_at\030\001 \001(\0132\032.googl" + - "e.protobuf.Timestamp\022.\n\nupdated_at\030\002 \001(\013" + - "2\032.google.protobuf.Timestamp\022\020\n\010agent_id" + - "\030\003 \001(\r\022\017\n\007command\030\004 \001(\t\0221\n\016command_statu" + - "s\030\005 \001(\0162\031.agent.AgentCommandStatus\022\016\n\006re" + - "sult\030\006 \001(\t\022\023\n\013executed_by\030\007 \001(\t\022\016\n\006cmd_i" + - "d\030\010 \001(\t\022\016\n\006reason\030\t \001(\t\022\023\n\013origin_type\030\n" + - " \001(\t\022\021\n\torigin_id\030\013 \001(\t*W\n\022AgentCommandS" + - "tatus\022\020\n\014NOT_EXECUTED\020\000\022\t\n\005QUEUE\020\001\022\013\n\007PE" + - "NDING\020\002\022\014\n\010EXECUTED\020\003\022\t\n\005ERROR\020\0042\234\003\n\014Age" + - "ntService\0229\n\013UpdateAgent\022\023.agent.AgentRe" + - "quest\032\023.agent.AuthResponse\"\000\022;\n\rRegister" + - "Agent\022\023.agent.AgentRequest\032\023.agent.AuthR" + - "esponse\"\000\022:\n\013DeleteAgent\022\024.agent.DeleteR" + - "equest\032\023.agent.AuthResponse\"\000\022=\n\nListAge" + - "nts\022\022.agent.ListRequest\032\031.agent.ListAgen" + - "tsResponse\"\000\022K\n\013AgentStream\022\032.agent.Bidi" + - "rectionalStream\032\032.agent.BidirectionalStr" + - "eam\"\000(\0010\001\022L\n\021ListAgentCommands\022\022.agent.L" + - "istRequest\032!.agent.ListAgentsCommandsRes" + - "ponse\"\0002O\n\014PanelService\022?\n\016ProcessComman" + - "d\022\021.agent.UtmCommand\032\024.agent.CommandResu" + - "lt\"\000(\0010\001B7\n\036com.park.utmstack.service.gr" + - "pcB\020AgentManagerGrpcP\001\210\001\001b\006proto3" + " \001(\t\022\021\n\torigin_id\030\006 \001(\t\022\016\n\006reason\030\007 \001(\t\022" + + "\r\n\005shell\030\010 \001(\t\"r\n\rCommandResult\022\020\n\010agent" + + "_id\030\001 \001(\t\022\016\n\006result\030\002 \001(\t\022/\n\013executed_at" + + "\030\003 \001(\0132\032.google.protobuf.Timestamp\022\016\n\006cm" + + "d_id\030\004 \001(\t\"N\n\032ListAgentsCommandsResponse" + + "\022!\n\004rows\030\001 \003(\0132\023.agent.AgentCommand\022\r\n\005t" + + "otal\030\002 \001(\005\"\261\002\n\014AgentCommand\022.\n\ncreated_a" + + "t\030\001 \001(\0132\032.google.protobuf.Timestamp\022.\n\nu" + + "pdated_at\030\002 \001(\0132\032.google.protobuf.Timest" + + "amp\022\020\n\010agent_id\030\003 \001(\r\022\017\n\007command\030\004 \001(\t\0221" + + "\n\016command_status\030\005 \001(\0162\031.agent.AgentComm" + + "andStatus\022\016\n\006result\030\006 \001(\t\022\023\n\013executed_by" + + "\030\007 \001(\t\022\016\n\006cmd_id\030\010 \001(\t\022\016\n\006reason\030\t \001(\t\022\023" + + "\n\013origin_type\030\n \001(\t\022\021\n\torigin_id\030\013 \001(\t*W" + + "\n\022AgentCommandStatus\022\020\n\014NOT_EXECUTED\020\000\022\t" + + "\n\005QUEUE\020\001\022\013\n\007PENDING\020\002\022\014\n\010EXECUTED\020\003\022\t\n\005" + + "ERROR\020\0042\234\003\n\014AgentService\0229\n\013UpdateAgent\022" + + "\023.agent.AgentRequest\032\023.agent.AuthRespons" + + "e\"\000\022;\n\rRegisterAgent\022\023.agent.AgentReques" + + "t\032\023.agent.AuthResponse\"\000\022:\n\013DeleteAgent\022" + + "\024.agent.DeleteRequest\032\023.agent.AuthRespon" + + "se\"\000\022=\n\nListAgents\022\022.agent.ListRequest\032\031" + + ".agent.ListAgentsResponse\"\000\022K\n\013AgentStre" + + "am\022\032.agent.BidirectionalStream\032\032.agent.B" + + "idirectionalStream\"\000(\0010\001\022L\n\021ListAgentCom" + + "mands\022\022.agent.ListRequest\032!.agent.ListAg" + + "entsCommandsResponse\"\0002O\n\014PanelService\022?" + + "\n\016ProcessCommand\022\021.agent.UtmCommand\032\024.ag" + + "ent.CommandResult\"\000(\0010\001B7\n\036com.park.utms" + + "tack.service.grpcB\020AgentManagerGrpcP\001\210\001\001" + + "b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, @@ -161,7 +162,7 @@ public static void registerAllExtensions( internal_static_agent_UtmCommand_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_agent_UtmCommand_descriptor, - new java.lang.String[] { "AgentId", "Command", "ExecutedBy", "CmdId", "OriginType", "OriginId", "Reason", }); + new java.lang.String[] { "AgentId", "Command", "ExecutedBy", "CmdId", "OriginType", "OriginId", "Reason", "Shell", }); internal_static_agent_CommandResult_descriptor = getDescriptor().getMessageTypes().get(5); internal_static_agent_CommandResult_fieldAccessorTable = new diff --git a/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommand.java b/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommand.java index 83b39c5a5..84c4f287c 100644 --- a/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommand.java +++ b/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommand.java @@ -34,6 +34,7 @@ private UtmCommand() { originType_ = ""; originId_ = ""; reason_ = ""; + shell_ = ""; } public static final com.google.protobuf.Descriptors.Descriptor @@ -322,6 +323,45 @@ public java.lang.String getReason() { } } + public static final int SHELL_FIELD_NUMBER = 8; + @SuppressWarnings("serial") + private volatile java.lang.Object shell_ = ""; + /** + * string shell = 8; + * @return The shell. + */ + @java.lang.Override + public java.lang.String getShell() { + java.lang.Object ref = shell_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + shell_ = s; + return s; + } + } + /** + * string shell = 8; + * @return The bytes for shell. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getShellBytes() { + java.lang.Object ref = shell_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + shell_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { @@ -357,6 +397,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (!com.google.protobuf.GeneratedMessage.isStringEmpty(reason_)) { com.google.protobuf.GeneratedMessage.writeString(output, 7, reason_); } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(shell_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 8, shell_); + } getUnknownFields().writeTo(output); } @@ -387,6 +430,9 @@ public int getSerializedSize() { if (!com.google.protobuf.GeneratedMessage.isStringEmpty(reason_)) { size += com.google.protobuf.GeneratedMessage.computeStringSize(7, reason_); } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(shell_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(8, shell_); + } size += getUnknownFields().getSerializedSize(); memoizedSize = size; return size; @@ -416,6 +462,8 @@ public boolean equals(final java.lang.Object obj) { .equals(other.getOriginId())) return false; if (!getReason() .equals(other.getReason())) return false; + if (!getShell() + .equals(other.getShell())) return false; if (!getUnknownFields().equals(other.getUnknownFields())) return false; return true; } @@ -441,6 +489,8 @@ public int hashCode() { hash = (53 * hash) + getOriginId().hashCode(); hash = (37 * hash) + REASON_FIELD_NUMBER; hash = (53 * hash) + getReason().hashCode(); + hash = (37 * hash) + SHELL_FIELD_NUMBER; + hash = (53 * hash) + getShell().hashCode(); hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -579,6 +629,7 @@ public Builder clear() { originType_ = ""; originId_ = ""; reason_ = ""; + shell_ = ""; return this; } @@ -633,6 +684,9 @@ private void buildPartial0(com.park.utmstack.service.grpc.UtmCommand result) { if (((from_bitField0_ & 0x00000040) != 0)) { result.reason_ = reason_; } + if (((from_bitField0_ & 0x00000080) != 0)) { + result.shell_ = shell_; + } } @java.lang.Override @@ -682,6 +736,11 @@ public Builder mergeFrom(com.park.utmstack.service.grpc.UtmCommand other) { bitField0_ |= 0x00000040; onChanged(); } + if (!other.getShell().isEmpty()) { + shell_ = other.shell_; + bitField0_ |= 0x00000080; + onChanged(); + } this.mergeUnknownFields(other.getUnknownFields()); onChanged(); return this; @@ -743,6 +802,11 @@ public Builder mergeFrom( bitField0_ |= 0x00000040; break; } // case 58 + case 66: { + shell_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000080; + break; + } // case 66 default: { if (!super.parseUnknownField(input, extensionRegistry, tag)) { done = true; // was an endgroup tag @@ -1264,6 +1328,78 @@ public Builder setReasonBytes( return this; } + private java.lang.Object shell_ = ""; + /** + * string shell = 8; + * @return The shell. + */ + public java.lang.String getShell() { + java.lang.Object ref = shell_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + shell_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string shell = 8; + * @return The bytes for shell. + */ + public com.google.protobuf.ByteString + getShellBytes() { + java.lang.Object ref = shell_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + shell_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string shell = 8; + * @param value The shell to set. + * @return This builder for chaining. + */ + public Builder setShell( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + shell_ = value; + bitField0_ |= 0x00000080; + onChanged(); + return this; + } + /** + * string shell = 8; + * @return This builder for chaining. + */ + public Builder clearShell() { + shell_ = getDefaultInstance().getShell(); + bitField0_ = (bitField0_ & ~0x00000080); + onChanged(); + return this; + } + /** + * string shell = 8; + * @param value The bytes for shell to set. + * @return This builder for chaining. + */ + public Builder setShellBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + shell_ = value; + bitField0_ |= 0x00000080; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:agent.UtmCommand) } diff --git a/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommandOrBuilder.java b/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommandOrBuilder.java index 7a7305596..215ccc7ad 100644 --- a/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommandOrBuilder.java +++ b/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommandOrBuilder.java @@ -92,4 +92,16 @@ public interface UtmCommandOrBuilder extends */ com.google.protobuf.ByteString getReasonBytes(); + + /** + * string shell = 8; + * @return The shell. + */ + java.lang.String getShell(); + /** + * string shell = 8; + * @return The bytes for shell. + */ + com.google.protobuf.ByteString + getShellBytes(); } diff --git a/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java b/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java index 19f404b76..fa67a2e30 100644 --- a/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java +++ b/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java @@ -1,6 +1,7 @@ package com.park.utmstack.service.idp_provider; +import com.park.utmstack.config.Constants; import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; import com.park.utmstack.repository.idp_provider.IdentityProviderConfigRepository; import com.park.utmstack.service.dto.idp_provider.dto.*; @@ -18,11 +19,10 @@ import java.io.IOException; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; import java.time.LocalDateTime; -import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -32,13 +32,13 @@ public class IdentityProviderService { private final IdentityProviderConfigRepository repository; private final ApplicationEventPublisher publisher; - public List getAllActiveProviders() { - return repository.findAllByActiveTrue(); - } - public IdentityProviderConfigResponseDto create(IdentityProviderCreateConfigDto dto) { validateMetadataUrl(dto.getMetadataUrl()); + + // Validate encryption key before mapper encrypts the private key + getValidatedEncryptionKey(); + IdentityProviderConfig entity = mapper.toEntity(dto); entity.setCreatedAt(LocalDateTime.now()); entity.setUpdatedAt(LocalDateTime.now()); @@ -65,7 +65,8 @@ public IdentityProviderConfigResponseDto update(Long id, IdentityProviderConfigR if(dto instanceof IdentityProviderCreateConfigDto createDto){ if (createDto.getSpPrivateKeyPem() != null) { - String encryptedKey = CipherUtil.encrypt(createDto.getSpPrivateKeyPem(), System.getenv("ENCRYPTION_KEY")); + String encryptionKey = getValidatedEncryptionKey(); + String encryptedKey = CipherUtil.encrypt(createDto.getSpPrivateKeyPem(), encryptionKey); existing.setSpPrivateKeyPem(encryptedKey); } if (createDto.getSpCertificatePem() != null) { @@ -103,24 +104,52 @@ public void delete(Long id) { repository.deleteById(id); } + /** + * Validates and retrieves the encryption key from environment variables. + * + * @return The validated encryption key + * @throws IllegalStateException if ENCRYPTION_KEY is not configured + */ + private String getValidatedEncryptionKey() { + String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); + if (encryptionKey == null || encryptionKey.isBlank()) { + throw new IllegalStateException( + "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); + } + return encryptionKey; + } + private void validateMetadataUrl(String metadataUrl) { if (metadataUrl == null || metadataUrl.trim().isEmpty()) { throw new SamlMetadataUrlInvalidException("Metadata URL is required"); } + HttpURLConnection connection = null; try { URL url = new URL(metadataUrl); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(5000); connection.setReadTimeout(5000); int responseCode = connection.getResponseCode(); if (responseCode != 200) { - throw new SamlMetadataUrlInvalidException("Metadata URL is not accessible"); + throw new SamlMetadataUrlInvalidException( + String.format("Metadata URL is not accessible. HTTP Status: %d", responseCode)); } + } catch (MalformedURLException e) { + throw new SamlMetadataUrlInvalidException( + "Invalid metadata URL format: " + e.getMessage()); } catch (IOException e) { - throw new SamlMetadataUrlInvalidException("Failed to access metadata URL"); + throw new SamlMetadataUrlInvalidException( + "Failed to access metadata URL: " + e.getMessage()); + } catch (Exception e) { + throw new SamlMetadataUrlInvalidException( + "Unexpected error validating metadata URL: " + e.getMessage()); + } finally { + if (connection != null) { + connection.disconnect(); + } } } diff --git a/backend/src/main/java/com/park/utmstack/service/logstash_pipeline/UtmLogstashPipelineService.java b/backend/src/main/java/com/park/utmstack/service/logstash_pipeline/UtmLogstashPipelineService.java index 125926afb..4b2383177 100644 --- a/backend/src/main/java/com/park/utmstack/service/logstash_pipeline/UtmLogstashPipelineService.java +++ b/backend/src/main/java/com/park/utmstack/service/logstash_pipeline/UtmLogstashPipelineService.java @@ -20,6 +20,7 @@ import com.park.utmstack.service.logstash_pipeline.response.pipeline.PipelineStats; import com.park.utmstack.service.logstash_pipeline.response.statistic.StatisticDocument; import com.park.utmstack.service.web_clients.rest_template.RestTemplateService; +import com.park.utmstack.util.exceptions.ApiException; import com.park.utmstack.web.rest.vm.UtmLogstashPipelineVM; import com.utmstack.opensearch_connector.parsers.TermAggregateParser; import com.utmstack.opensearch_connector.types.BucketAggregation; @@ -38,6 +39,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -267,57 +269,56 @@ public ApiEngineResponse logstashJvmApiResponse() { * Getting active pipelines stats from DB, general jvm stats from logstash */ public ApiStatsResponse getLogstashStats() throws Exception { - final String ctx = CLASSNAME + ".getLogstashStats"; - ApiStatsResponse statsResponse = new ApiStatsResponse(); + final String ctx = CLASSNAME + ".getLogstashStats"; - // Variables used to set the general pipeline's status - AtomicInteger activePipelinesCount = new AtomicInteger(); - AtomicInteger upPipelinesCount = new AtomicInteger(); + try { + ApiStatsResponse statsResponse = new ApiStatsResponse(); boolean isCorrelationUp = isEngineUp(); - try { - // Getting Jvm information (not used) - ApiEngineResponse jvmData = logstashJvmApiResponse(); - if (jvmData != null) { - statsResponse.setGeneral(jvmData); - } - // List to store stats mapped from DB - List infoStats; - - // Getting the active pipelines statistics - infoStats = activePipelinesList().stream().map(activePip -> { - - // Calculating stats for pipelines - // Setting stats for non-logstash pipelines (correlation engine) - if (isCorrelationUp) { - activePipelinesCount.getAndIncrement(); // Total pipelines that have to be active - if (activePip.getPipelineStatus().equals(PipelineStatus.PIPELINE_STATUS_UP.get())) { - upPipelinesCount.getAndIncrement(); - } - } else { - activePip.setPipelineStatus(PipelineStatus.PIPELINE_STATUS_DOWN.get()); - } - // } - // Mapping stats from DB pipeline - return PipelineStats.getPipelineStats(activePip); - }).collect(Collectors.toList()); - - // Setting the final global status of pipelines - if (isCorrelationUp) { - if (upPipelinesCount.get() == 0) { - jvmData.setStatus(PipelineStatus.ENGINE_STATUS_RED.get()); - } else if (upPipelinesCount.get() == activePipelinesCount.get()) { - jvmData.setStatus(PipelineStatus.ENGINE_STATUS_GREEN.get()); - } else { - jvmData.setStatus(PipelineStatus.ENGINE_STATUS_YELLOW.get()); - } + ApiEngineResponse jvmData = logstashJvmApiResponse(); + if (jvmData != null) { + statsResponse.setGeneral(jvmData); + } + + List activePipelines = activePipelinesList(); + + if (!isCorrelationUp) { + activePipelines.forEach(p -> + p.setPipelineStatus(PipelineStatus.PIPELINE_STATUS_DOWN.get()) + ); + } + + List pipelineStatsList = activePipelines.stream() + .map(PipelineStats::getPipelineStats) + .sorted( Comparator.comparing(PipelineStats::getPipelineStatus).reversed()) + .collect(Collectors.toList()); + + statsResponse.setPipelines(pipelineStatsList); + + if (isCorrelationUp && jvmData != null) { + long upCount = activePipelines.stream() + .filter(p -> PipelineStatus.PIPELINE_STATUS_UP.get() + .equals(p.getPipelineStatus())) + .count(); + + int totalCount = activePipelines.size(); + + if (upCount == 0) { + jvmData.setStatus(PipelineStatus.ENGINE_STATUS_RED.get()); + } else if (upCount == totalCount) { + jvmData.setStatus(PipelineStatus.ENGINE_STATUS_GREEN.get()); + } else { + jvmData.setStatus(PipelineStatus.ENGINE_STATUS_YELLOW.get()); } - statsResponse.setPipelines(infoStats); - } catch (Exception ex) { - throw new Exception(ctx + ": " + ex.getMessage()); } + return statsResponse; + + } catch (Exception ex) { + log.error("{}: An error occurred while fetching logstash stats: {}", ctx, ex.getMessage(), ex); + throw new ApiException(String.format("%s: An error occurred while fetching logstash stats", ctx), HttpStatus.INTERNAL_SERVER_ERROR); } +} /** * Method to set the DB pipelines status diff --git a/backend/src/main/java/com/park/utmstack/service/network_scan/AssetSynchronizationService.java b/backend/src/main/java/com/park/utmstack/service/network_scan/AssetSynchronizationService.java new file mode 100644 index 000000000..7736314c3 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/network_scan/AssetSynchronizationService.java @@ -0,0 +1,255 @@ +package com.park.utmstack.service.network_scan; + +import com.park.utmstack.domain.correlation.config.UtmTenantConfig; +import com.park.utmstack.domain.datainput_ingestion.UtmDataInputStatus; +import com.park.utmstack.domain.network_scan.UtmNetworkScan; +import com.park.utmstack.domain.network_scan.enums.AssetStatus; +import com.park.utmstack.domain.network_scan.enums.UpdateLevel; +import com.park.utmstack.repository.datainput_ingestion.UtmDataInputStatusRepository; +import com.park.utmstack.repository.network_scan.UtmNetworkScanRepository; +import com.park.utmstack.service.UtmDataInputStatusService; +import com.park.utmstack.service.agent_manager.AgentService; +import com.park.utmstack.service.correlation.config.UtmTenantConfigService; +import com.park.utmstack.service.dto.agent_manager.AgentDTO; +import com.park.utmstack.service.logstash_pipeline.response.statistic.StatisticDocument; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.transaction.Transactional; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@Slf4j +@RequiredArgsConstructor +public class AssetSynchronizationService { + + private final AgentService agentService; + private final SourceActivityProvider sourceActivityProvider; + private final UtmDataInputStatusRepository dataInputStatusRepository; + private final UtmNetworkScanRepository networkScanRepository; + private final UtmDataInputStatusService dataInputStatusService; + private final UtmTenantConfigService tenantConfigService; + + @Transactional + @Scheduled(fixedDelay = 60000, initialDelay = 120000) + public void syncDataInputsAndAssets() { + String correlationId = UUID.randomUUID().toString().substring(0, 8); + log.info("[{}] Starting unified asset synchronization cycle", correlationId); + + try { + Map statsMap = sourceActivityProvider.fetchLatestSourceActivity(); + if (statsMap.isEmpty()) { + log.debug("[{}] No new activity detected in data sources", correlationId); + return; + } + + Map agentsMap = loadAgents(); + Map statusMap = buildDataInputStatusMap(); + + Map assetsNameMap = buildNetworkAssetsNameMap( + new ArrayList<>(statsMap.values() + .stream() + .map(StatisticDocument::getDataSource) + .collect(Collectors.toSet()))); + + Map assetsNameIpMap = buildNetworkAssetsIpMap( + new ArrayList<>(statsMap.values() + .stream() + .map(StatisticDocument::getDataSource) + .collect(Collectors.toSet()))); + + List statusToSave = new ArrayList<>(); + List assetsToSave = new ArrayList<>(); + + for (String sourceName : statsMap.keySet()) { + + StatisticDocument stat = statsMap.get(sourceName); + UtmDataInputStatus status = processDataInputStatus(stat, statusMap); + statusToSave.add(status); + + UtmNetworkScan asset = processNetworkAsset(stat.getDataSource(), agentsMap, assetsNameMap, assetsNameIpMap, statusMap); + assetsToSave.add(asset); + } + + if (!statusToSave.isEmpty()) { + dataInputStatusRepository.saveAll(statusToSave); + } + + if (!assetsToSave.isEmpty()) { + networkScanRepository.saveAll(assetsToSave); + } + + log.info("[{}] Asset synchronization cycle completed - {} status updated, {} assets synced", + correlationId, statusToSave.size(), assetsToSave.size()); + + } catch (Exception e) { + log.error("[{}] Critical error during asset synchronization: {}", correlationId, e.getMessage(), e); + } + } + + private Map buildDataInputStatusMap() { + return dataInputStatusService.findDataInputStatus() + .stream() + .collect(Collectors.toMap(UtmDataInputStatus::getId, Function.identity())); + } + + private Map buildNetworkAssetsNameMap(List sourcesKeys) { + return networkScanRepository.findByAssetIpInOrAssetNameIn(sourcesKeys, sourcesKeys) + .stream() + .collect(Collectors.toMap(UtmNetworkScan::getAssetName, Function.identity(), (a1, a2) -> a1)); + } + + private Map buildNetworkAssetsIpMap(List sourcesKeys) { + return networkScanRepository.findByAssetIpInOrAssetNameIn(sourcesKeys, sourcesKeys) + .stream() + .collect(Collectors.toMap(UtmNetworkScan::getAssetIp, Function.identity(), (a1, a2) -> a1)); + } + + private UtmDataInputStatus processDataInputStatus(StatisticDocument stat, + Map statusMap) { + String statusId = stat.getDataType() + "-" + stat.getDataSource(); + long statTimestamp = Instant.parse(stat.getTimestamp()).getEpochSecond(); + + UtmDataInputStatus status = statusMap.getOrDefault(statusId, createNewDataInputStatus(statusId, stat, statTimestamp)); + + if (status.getTimestamp() != statTimestamp) { + status.setTimestamp(statTimestamp); + } + + return status; + } + + private UtmDataInputStatus createNewDataInputStatus(String id, StatisticDocument stat, long timestamp) { + return UtmDataInputStatus.builder() + .id(id) + .dataType(stat.getDataType()) + .timestamp(timestamp) + .source(stat.getDataSource()) + .median(86400L) + .build(); + } + + private UtmNetworkScan processNetworkAsset(String sourceName, + Map agentsMap, + Map assetsNameMap, + Map assetsIpMap, + Map statusMap) { + boolean isAlive = isDataSourceAlive(sourceName, statusMap); + UtmNetworkScan asset = resolveAsset(sourceName, assetsNameMap, assetsIpMap); + boolean isExisting = asset != null && asset.getId() != null; + + if (asset == null) { + asset = new UtmNetworkScan(sourceName, isAlive); + } + + enrichAssetWithData(asset, sourceName, agentsMap, isAlive, isExisting); + return asset; + } + + private boolean isDataSourceAlive(String sourceName, + Map statusMap) { + return statusMap.values().stream() + .filter(status -> status.getSource().equalsIgnoreCase(sourceName)) + .anyMatch(s -> !s.isDown()); + } + + private UtmNetworkScan resolveAsset(String sourceName, + Map assetsNameMap, + Map assetsIpMap) { + UtmNetworkScan asset = assetsNameMap.get(sourceName); + + if (asset == null) { + asset = assetsIpMap.get(sourceName); + } + + if (asset == null) { + asset = resolveAssetNameFromTenantConfig(sourceName); + } + + return asset; + } + + private void enrichAssetWithData(UtmNetworkScan asset, + String sourceName, + Map agentsMap, + boolean isAlive, + boolean isExisting) { + asset.assetAlive(isAlive) + .updateLevel(UpdateLevel.DATASOURCE) + .modifiedAt(LocalDateTime.now().toInstant(ZoneOffset.UTC)); + + if (isExisting) { + asset.assetStatus(AssetStatus.CHECK); + } + + AgentDTO agentInfo = agentsMap.get(sourceName); + if (agentInfo != null) { + asset.setAssetIp(agentInfo.getIp()); + asset.setAssetOs(agentInfo.getOs()); + asset.setAssetOsPlatform(agentInfo.getPlatform()); + asset.setAssetOsVersion(agentInfo.getVersion()); + asset.setIsAgent(true); + } else { + asset.setIsAgent(false); + } + } + + private UtmNetworkScan resolveAssetNameFromTenantConfig(String sourceName) { + + if (!StringUtils.hasText(sourceName)) { + return null; + } + + try { + + Optional configOpt = tenantConfigService.findByAssetName(sourceName); + + if (configOpt.isEmpty()) { + return null; + } + + UtmTenantConfig config = configOpt.get(); + List hostnames = config.getAssetHostnameList(); + List ips = config.getAssetIpList(); + + List networkScans = + networkScanRepository.findByAssetIpInOrAssetNameIn(ips, hostnames) + .stream() + .toList(); + + if (networkScans.isEmpty()) { + return null; + } + + for (UtmNetworkScan networkScan : networkScans) { + + if (hostnames != null && hostnames.contains(networkScan.getAssetName()) || + ips != null && ips.contains(networkScan.getAssetIp())) { + + return networkScan; + } + + } + + + } catch (Exception e) { + log.warn("Error resolving asset name from tenant config for source {}: {}", sourceName, e.getMessage()); + } + + return null; + } + + private Map loadAgents() { + return agentService.getInstalledAgents().stream() + .collect(Collectors.toMap(AgentDTO::getHostname, Function.identity(), (a1, a2) -> a1)); + } + +} diff --git a/backend/src/main/java/com/park/utmstack/service/network_scan/SourceActivityProvider.java b/backend/src/main/java/com/park/utmstack/service/network_scan/SourceActivityProvider.java new file mode 100644 index 000000000..36d1d56f9 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/network_scan/SourceActivityProvider.java @@ -0,0 +1,179 @@ +package com.park.utmstack.service.network_scan; + +import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.chart_builder.types.query.FilterType; +import com.park.utmstack.domain.chart_builder.types.query.OperatorType; +import com.park.utmstack.domain.datainput_ingestion.UtmDataInputStatusCheckpoint; +import com.park.utmstack.repository.datainput_ingestion.UtmDataInputStatusCheckpointRepository; +import com.park.utmstack.service.elasticsearch.ElasticsearchService; +import com.park.utmstack.service.elasticsearch.SearchUtil; +import com.park.utmstack.service.logstash_pipeline.response.statistic.StatisticDocument; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.opensearch.client.opensearch._types.SortOrder; +import org.opensearch.client.opensearch.core.SearchRequest; +import org.opensearch.client.opensearch.core.SearchResponse; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@Slf4j +@RequiredArgsConstructor +public class SourceActivityProvider { + + private final ElasticsearchService elasticsearchService; + private final UtmDataInputStatusCheckpointRepository checkpointRepository; + + private static final long CHECKPOINT_ID = 1L; + private static final int OVERLAP_SECONDS = 5; + + public Map fetchLatestSourceActivity() { + UtmDataInputStatusCheckpoint checkpoint = getOrCreateCheckpoint(); + + String fromTimestamp = checkpoint.getLastProcessedTimestamp() + .minus(OVERLAP_SECONDS, ChronoUnit.SECONDS) + .toString(); + + SearchRequest searchRequest = buildActivityQuery(fromTimestamp); + + try { + SearchResponse response = + elasticsearchService.search(searchRequest, StatisticDocument.class); + + Map activityMap = extractLatestHits(response); + + if (!activityMap.isEmpty()) { + log.debug("Fetched {} active sources from statistics index", activityMap.size()); + updateCheckpoint(checkpoint, activityMap); + } else { + log.debug("No new source activity found since checkpoint: {}", checkpoint.getLastProcessedTimestamp()); + } + + return activityMap; + } catch (Exception e) { + log.error("Error fetching telemetry from Elasticsearch: {}", e.getMessage(), e); + return Collections.emptyMap(); + } + } + + private SearchRequest buildActivityQuery(String fromTimestamp) { + List filters = List.of( + new FilterType("type", OperatorType.IS, "enqueue_success"), + new FilterType("@timestamp", OperatorType.IS_GREATER_THAN, fromTimestamp) + ); + + return SearchRequest.of(s -> s + .index(Constants.STATISTICS_INDEX_PATTERN) + .query(SearchUtil.toQuery(filters)) + .aggregations("by_source", agg -> agg + .terms(t -> t + .field("dataSource.keyword") + .size(10000) + ) + .aggregations("by_type", typeAgg -> typeAgg + .terms(t -> t + .field("dataType.keyword") + .size(10000) + ) + // Get the latest document for each dataSource + dataType combination + .aggregations("latest_doc", latestAgg -> latestAgg + .topHits(th -> th + .size(1) + .sort(sort -> sort.field(f -> f.field("@timestamp").order(SortOrder.Desc))) + ) + ) + ) + ) + .size(0) + ); + } + + private Map extractLatestHits(SearchResponse response) { + Map results = new HashMap<>(); + + try { + var aggregations = response.aggregations(); + if (aggregations == null || aggregations.get("by_source") == null) { + log.warn("No aggregation results found in response"); + return results; + } + + var bySourceAgg = aggregations.get("by_source").sterms(); + if (bySourceAgg == null || bySourceAgg.buckets().array().isEmpty()) { + log.debug("No data source buckets found in aggregation"); + return results; + } + + bySourceAgg.buckets().array().forEach(sourceBucket -> { + String dataSource = sourceBucket.key(); + + var byTypeAgg = sourceBucket.aggregations().get("by_type").sterms(); + if (byTypeAgg == null || byTypeAgg.buckets().array().isEmpty()) { + log.debug("No data type buckets found for source: {}", dataSource); + return; + } + + byTypeAgg.buckets().array().forEach(typeBucket -> { + String dataType = typeBucket.key(); + + var latestDocsAgg = typeBucket.aggregations().get("latest_doc"); + if (latestDocsAgg != null) { + var topHits = latestDocsAgg.topHits(); + if (topHits != null && !topHits.hits().hits().isEmpty()) { + var hit = topHits.hits().hits().get(0); + if (hit.source() != null) { + StatisticDocument doc = hit.source().to(StatisticDocument.class); + if (doc != null) { + String compositeKey = dataSource + "|" + dataType; + results.put(compositeKey, doc); + } + } + } + } + }); + }); + + return results; + } catch (Exception e) { + log.error("Error extracting latest hits from aggregation response: {}", e.getMessage(), e); + return results; + } + } + + private void updateCheckpoint(UtmDataInputStatusCheckpoint checkpoint, Map activityMap) { + activityMap.values().stream() + .map(doc -> { + try { + return Instant.parse(doc.getTimestamp()); + } catch (Exception e) { + log.error("Failed to parse timestamp '{}': {}", doc.getTimestamp(), e.getMessage()); + return null; + } + }) + .filter(java.util.Objects::nonNull) + .max(Instant::compareTo) + .ifPresentOrElse( + latest -> { + checkpoint.setLastProcessedTimestamp(latest); + checkpointRepository.save(checkpoint); + log.info("Checkpoint updated to: {} ({} active sources)", latest, activityMap.size()); + }, + () -> log.debug("No valid timestamps found to update checkpoint") + ); + } + + private UtmDataInputStatusCheckpoint getOrCreateCheckpoint() { + return checkpointRepository.findById(CHECKPOINT_ID) + .orElseGet(() -> { + UtmDataInputStatusCheckpoint cp = new UtmDataInputStatusCheckpoint(); + cp.setLastProcessedTimestamp(Instant.now().minus(1, ChronoUnit.HOURS)); + return cp; + }); + } +} diff --git a/backend/src/main/java/com/park/utmstack/util/UtilSerializer.java b/backend/src/main/java/com/park/utmstack/util/UtilSerializer.java index 7eec18a6a..9784a321a 100644 --- a/backend/src/main/java/com/park/utmstack/util/UtilSerializer.java +++ b/backend/src/main/java/com/park/utmstack/util/UtilSerializer.java @@ -67,7 +67,7 @@ public static String jsonSerialize(T obj) throws UtmSerializationException { } } - public static T jsonDeserialize(Class type, String json) throws UtmSerializationException { + public static T jsonDeserialize(Class type, String json) { String ctx = CLASS_NAME + ".jsonDeserialize"; try { ObjectMapper om = new ObjectMapper(); diff --git a/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java b/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java index cb49b9df1..a5c35aae6 100644 --- a/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java +++ b/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java @@ -18,10 +18,7 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class RequestDsl { @@ -74,7 +71,25 @@ private void applyPagination(Pageable pageable, int top) { if (pageable != null && pageable.isPaged()) { SearchUtil.applyPaginationAndSort(searchRequestBuilder, pageable, top); } else { - searchRequestBuilder.size(10000); + if (visualization.getChartType() == ChartType.LIST_CHART) { + + searchRequestBuilder.size(10000); + + Set fields = new HashSet<>(); + AggregationType agg = visualization.getAggregationType(); + if (agg != null && agg.getBucket() != null) { + collectBucketFields(agg.getBucket(), fields); + } + + List includeList = new ArrayList<>(fields) + .stream() + .map(this::normalizeField) + .toList(); + + searchRequestBuilder.source(s -> s.filter(f -> f.excludes("*"))); + searchRequestBuilder.source(s -> s.filter(f + -> f.includes(includeList))); + } } } @@ -98,7 +113,6 @@ public SearchRequest.Builder getSearchSourceBuilderForCount() throws UtmElastics } - /** * Build an aggregation section for an elasticsearch dsl request * @@ -157,19 +171,19 @@ private Map buildBucketAggregation switch (bucket.getAggregation()) { case TERMS: TermsAggregation term = new TermsAggregation.Builder().field(bucket.getField()) - .size(bucket.getTerms().getSize()).order(List.of(Map.of(bucket.getTerms().getSortBy(), - bucket.getTerms().getAsc() ? SortOrder.Asc : SortOrder.Desc))).build(); + .size(bucket.getTerms().getSize()).order(List.of(Map.of(bucket.getTerms().getSortBy(), + bucket.getTerms().getAsc() ? SortOrder.Asc : SortOrder.Desc))).build(); bucketAggregations.put(bucket.toString(), new Aggregation.Builder().terms(term)); break; case RANGE: RangeAggregation range = new RangeAggregation.Builder().field(bucket.getField()) - .ranges(bucket.getRanges().stream().map(r -> AggregationRange.of(a -> a.from(String.valueOf(r.getFrom())) - .to(String.valueOf(r.getTo())))).collect(Collectors.toList())).build(); + .ranges(bucket.getRanges().stream().map(r -> AggregationRange.of(a -> a.from(String.valueOf(r.getFrom())) + .to(String.valueOf(r.getTo())))).collect(Collectors.toList())).build(); bucketAggregations.put(bucket.toString(), new Aggregation.Builder().range(range)); break; case DATE_HISTOGRAM: DateHistogramAggregation.Builder histogram = new DateHistogramAggregation.Builder().field(bucket.getField()) - .timeZone(TimezoneUtil.getAppTimezone().toString()); + .timeZone(TimezoneUtil.getAppTimezone().toString()); String interval = bucket.getDateHistogram().getInterval(); if (bucket.getDateHistogram().isFixedInterval()) histogram.fixedInterval(i -> i.time(interval)); @@ -204,24 +218,24 @@ private Map buildMetricAggregation(List metrics) { switch (metric.getAggregation()) { case AVERAGE: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.avg(avg -> avg.field(metric.getField())))); + agg.avg(avg -> avg.field(metric.getField())))); break; case MAX: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.max(max -> max.field(metric.getField())))); + agg.max(max -> max.field(metric.getField())))); break; case MIN: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.min(min -> min.field(metric.getField())))); + agg.min(min -> min.field(metric.getField())))); break; case SUM: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.sum(sum -> sum.field(metric.getField())))); + agg.sum(sum -> sum.field(metric.getField())))); break; case MEDIAN: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.percentiles(percentile -> percentile.field(metric.getField()) - .keyed(false).percents(50D)))); + agg.percentiles(percentile -> percentile.field(metric.getField()) + .keyed(false).percents(50D)))); break; } } @@ -230,4 +244,35 @@ private Map buildMetricAggregation(List metrics) { throw new RuntimeException(ctx + ": " + e.getMessage()); } } + + private void collectBucketFields(Bucket bucket, Set fields) { + if (bucket == null) return; + + // 1. Field of this bucket (covers terms, ranges, date histogram) + if (bucket.getField() != null && !bucket.getField().isEmpty()) { + fields.add(bucket.getField()); + } + + // 2. Date histogram (field is in the bucket, not in DateHistogramBucket) + if (bucket.getDateHistogram() != null && + bucket.getField() != null && + !bucket.getField().isEmpty()) { + fields.add(bucket.getField()); + } + + // 3. Recurse into subBucket + if (bucket.getSubBucket() != null) { + collectBucketFields(bucket.getSubBucket(), fields); + } + } + + + private String normalizeField(String field) { + if (field.endsWith(".keyword")) { + return field.substring(0, field.length() - ".keyword".length()); + } + return field; + } + + } diff --git a/backend/src/main/java/com/park/utmstack/util/exceptions/UtmSerializationException.java b/backend/src/main/java/com/park/utmstack/util/exceptions/UtmSerializationException.java index 7b0aed56b..d92ebd4dc 100644 --- a/backend/src/main/java/com/park/utmstack/util/exceptions/UtmSerializationException.java +++ b/backend/src/main/java/com/park/utmstack/util/exceptions/UtmSerializationException.java @@ -1,6 +1,6 @@ package com.park.utmstack.util.exceptions; -public class UtmSerializationException extends Exception { +public class UtmSerializationException extends RuntimeException { public UtmSerializationException(String message) { super(message); } diff --git a/backend/src/main/proto/agent.proto b/backend/src/main/proto/agent.proto index c9dc990ef..d5ee2df98 100644 --- a/backend/src/main/proto/agent.proto +++ b/backend/src/main/proto/agent.proto @@ -82,6 +82,7 @@ message UtmCommand { string origin_type = 5; string origin_id = 6; string reason = 7; + string shell = 8; } message CommandResult { diff --git a/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml new file mode 100644 index 000000000..7bd925e57 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml @@ -0,0 +1,385 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260211005_update_default_time_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260211005_update_default_time_visualizations.xml new file mode 100644 index 000000000..33d13b25c --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260211005_update_default_time_visualizations.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260212001_update_filter_netflow.xml b/backend/src/main/resources/config/liquibase/changelog/20260212001_update_filter_netflow.xml new file mode 100644 index 000000000..4e0b88a0b --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260212001_update_filter_netflow.xml @@ -0,0 +1,1182 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/changelog/20260212002_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260212002_update_windows_visualizations.xml new file mode 100644 index 000000000..12d015453 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260212002_update_windows_visualizations.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260212003_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260212003_update_windows_visualizations.xml new file mode 100644 index 000000000..3a31a149f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260212003_update_windows_visualizations.xml @@ -0,0 +1,413 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260212004_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260212004_update_windows_visualizations.xml new file mode 100644 index 000000000..ded05c74a --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260212004_update_windows_visualizations.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260212005_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260212005_update_windows_visualizations.xml new file mode 100644 index 000000000..e76b4cee6 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260212005_update_windows_visualizations.xml @@ -0,0 +1,309 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260213001_update_bit_defender_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260213001_update_bit_defender_visualizations.xml new file mode 100644 index 000000000..89f6c8542 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260213001_update_bit_defender_visualizations.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260213002_update_vmware_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260213002_update_vmware_visualizations.xml new file mode 100644 index 000000000..2d6732334 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260213002_update_vmware_visualizations.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260213003_update_filter_bit_defender.xml b/backend/src/main/resources/config/liquibase/changelog/20260213003_update_filter_bit_defender.xml new file mode 100644 index 000000000..cfb7be90c --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260213003_update_filter_bit_defender.xml @@ -0,0 +1,301 @@ + + + + + + + ' + - fieldName: log.syslogVersion + pattern: '{{.integer}}' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.syslogHostIP + pattern: '{{.ipv4}}|{{.ipv6}}|{{.word}}' + - fieldName: log.notDefined + pattern: '{{.integer}}' + - fieldName: log.0trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.syslogVersion + pattern: '{{.integer}}' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.hostId + pattern: '{{.word}}' + - fieldName: log.0trash + pattern: '{{.word}}' + - fieldName: log.processPid + pattern: '\[{{.integer}}\]' + - fieldName: log.1trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.hostId + pattern: '{{.word}}' + - fieldName: log.0trash + pattern: '{{.word}}' + - fieldName: log.processPid + pattern: '\[{{.integer}}\]' + - fieldName: log.1trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.syslogVersion + pattern: '{{.integer}}' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.syslogHostIP + pattern: '{{.ipv4}}|{{.ipv6}}|{{.word}}' + - fieldName: log.0trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.syslogVersion + pattern: '{{.integer}}' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.0trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.cefVersion + pattern: 'CEF\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + # Using grok to parse components of the cef_message + - grok: + patterns: + - fieldName: log.productVendor + pattern: '\|{{.data}}\|' + - fieldName: log.product + pattern: '{{.data}}\|' + - fieldName: log.productVersion + pattern: '{{.data}}\|' + - fieldName: log.signatureID + pattern: '{{.data}}\|' + - fieldName: log.eventType + pattern: '{{.data}}\|' + - fieldName: log.severity + pattern: '{{.data}}\|' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: log.restData + + # Using grok to parse kv issued fields with space + - grok: + patterns: + - fieldName: log.2trash + pattern: '{{.data}}dvc=' + - fieldName: log.dvcToParse + pattern: '{{.data}}{{.word}}\=' + - fieldName: log.irrelevant + pattern: '{{.greedy}}' + source: log.restData + + - grok: + patterns: + - fieldName: log.2trash + pattern: '{{.data}}request=' + - fieldName: log.requestToParse + pattern: '{{.data}}{{.word}}\=' + - fieldName: log.irrelevant + pattern: '{{.greedy}}' + source: log.restData + + # Applying grok to remove unnecessary data + - grok: + patterns: + - fieldName: log.deviceIps + pattern: '{{.greedy}}{{.space}}' + - fieldName: log.irrelevant + pattern: '{{.greedy}}' + source: log.dvcToParse + + - grok: + patterns: + - fieldName: log.requested + pattern: '{{.greedy}}{{.space}}' + - fieldName: log.irrelevant + pattern: '{{.greedy}}' + source: log.requestToParse + + # Using the kv filter with default config, usefull in key-value logs + - kv: + fieldSplit: " " + valueSplit: "=" + source: log.restData + + # Renaming useful fields + - rename: + from: + - log.spt + to: origin.port + + - rename: + from: + - log.src + to: origin.ip + + - rename: + from: + - log.deviceIps + to: origin.ip + + - rename: + from: + - log.dvchost + to: target.host + + - rename: + from: + - log.sproc + to: target.path + + - rename: + from: + - log.filePath + to: origin.path + + - rename: + from: + - log.act + to: action + + # Removing unnecessary characters + - trim: + function: prefix + substring: '|' + fields: + - log.productVendor + + - trim: + function: suffix + substring: '|' + fields: + - log.productVendor + - log.product + - log.productVersion + - log.signatureID + - log.eventType + - log.severity + + - trim: + function: prefix + substring: '<' + fields: + - log.syslogPriority + + - trim: + function: suffix + substring: '>' + fields: + - log.syslogPriority + + - trim: + function: prefix + substring: '[' + fields: + - log.processPid + + - trim: + function: suffix + substring: ']' + fields: + - log.processPid + + # Adding geolocation to origin ip + - dynamic: + plugin: com.utmstack.geolocation + params: + source: origin.ip + destination: origin.geolocation + where: exists("origin.ip") + + # Reformat and field conversions + - cast: + fields: + - origin.port + to: int + + # Removing unused fields + - delete: + fields: + - log.0trash + - log.1trash + - log.2trash + - log.restData + - log.irrelevant + - log.spt + - log.src + - log.sproc + - log.filePath + - log.dvc + - log.request + - log.dvcToParse + - log.cefVersion$$ + WHERE id = 1514; + ]]> + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/changelog/20260216001_update_filter_vmware_esxi.xml b/backend/src/main/resources/config/liquibase/changelog/20260216001_update_filter_vmware_esxi.xml new file mode 100644 index 000000000..941f18aa3 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260216001_update_filter_vmware_esxi.xml @@ -0,0 +1,157 @@ + + + + + + + ' + - fieldName: log.deviceTime + pattern: '{{.year}}(-){{.monthNumber}}(-){{.monthDay}}(T){{.time}}(Z)' + - fieldName: origin.hostname + pattern: '{{.hostname}}' + - fieldName: log.process + pattern: '{{.hostname}}(\:)' + - fieldName: severity + pattern: '{{.word}}' + - fieldName: log.processName + pattern: '{{.hostname}}' + - fieldName: log.pid + pattern: '\[{{.data}}\]' + - fieldName: log.eventInfo + pattern: '\[{{.data}}\]' + - fieldName: log.message + pattern: '{{.greedy}}' + + - grok: + patterns: + - fieldName: log.priority + pattern: '\<{{.data}}\>' + - fieldName: log.deviceTime + pattern: '{{.year}}(-){{.monthNumber}}(-){{.monthDay}}(T){{.time}}(Z)' + - fieldName: origin.hostname + pattern: '{{.hostname}}' + - fieldName: log.process + pattern: '{{.hostname}}' + - fieldName: log.pid + pattern: '\[{{.data}}\]:' + - fieldName: log.message + pattern: '{{.greedy}}' + + - grok: + patterns: + - fieldName: log.priority + pattern: '\<{{.data}}\>' + - fieldName: log.deviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}T{{.time}}Z' + - fieldName: origin.hostname + pattern: '{{.hostname}}' + - fieldName: log.process + pattern: '{{.hostname}}' + - fieldName: log.pid + pattern: '\[{{.data}}\]:' + - fieldName: log.originIdComponent + pattern: '\[{{.data}}\]' + - fieldName: log.message + pattern: '{{.greedy}}' + + - grok: + patterns: + - fieldName: log.moduleIdentifier + pattern: '\[{{.data}}\@' + - fieldName: log.irrelevant + pattern: '{{.data}}\=' + - fieldName: log.subModuleIdentifier + pattern: '{{.word}}\]' + source: log.originIdComponent + + - grok: + patterns: + - fieldName: log.irrelevant2 + pattern: '{{.data}}level{{.space}}=' + - fieldName: log.level + pattern: '{{.integer}}' + source: log.message + + # Removing unused caracters + - trim: + function: prefix + substring: '<' + fields: + - log.priority + - trim: + function: prefix + substring: '[' + fields: + - log.pid + - log.eventInfo + - log.moduleIdentifier + - trim: + function: prefix + substring: '-' + fields: + - log.message + - trim: + function: suffix + substring: '>' + fields: + - log.priority + - trim: + function: suffix + substring: ':' + fields: + - log.pid + - log.process + - trim: + function: suffix + substring: ']' + fields: + - log.pid + - log.eventInfo + - log.subModuleIdentifier + - trim: + function: suffix + substring: '-' + fields: + - log.message + - trim: + function: suffix + substring: '@' + fields: + - log.moduleIdentifier + + # Removing unused fields + - delete: + fields: + - log.processName + - log.irrelevant$$ + WHERE id = 1001; + ]]> + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218001_remove_redis_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218001_remove_redis_integration.xml new file mode 100644 index 000000000..56b04a54a --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218001_remove_redis_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_redis(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218002_remove_nginx_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218002_remove_nginx_integration.xml new file mode 100644 index 000000000..92547ba6d --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218002_remove_nginx_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_nginx(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218003_remove_postgresql_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218003_remove_postgresql_integration.xml new file mode 100644 index 000000000..ce4554d1e --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218003_remove_postgresql_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_postgresql(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218004_remove_apache_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218004_remove_apache_integration.xml new file mode 100644 index 000000000..0d9c2acea --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218004_remove_apache_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_apache(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218005_remove_mysql_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218005_remove_mysql_integration.xml new file mode 100644 index 000000000..9b23fd9f0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218005_remove_mysql_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_mysql(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218006_remove_mongodb_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218006_remove_mongodb_integration.xml new file mode 100644 index 000000000..2f2b334d0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218006_remove_mongodb_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_mongodb(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218007_remove_elastic_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218007_remove_elastic_integration.xml new file mode 100644 index 000000000..78c192062 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218007_remove_elastic_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_elasticsearch(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218008_remove_logstash_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218008_remove_logstash_integration.xml new file mode 100644 index 000000000..700fd1663 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218008_remove_logstash_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_logstash(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218009_remove_kibana_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218009_remove_kibana_integration.xml new file mode 100644 index 000000000..4a9b51019 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218009_remove_kibana_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_kibana(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218010_remove_kafka_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218010_remove_kafka_integration.xml new file mode 100644 index 000000000..bddee7300 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218010_remove_kafka_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_kafka(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218011_remove_nats_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218011_remove_nats_integration.xml new file mode 100644 index 000000000..920edef51 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218011_remove_nats_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_nats(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218012_remove_traefik_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218012_remove_traefik_integration.xml new file mode 100644 index 000000000..d463ee01b --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218012_remove_traefik_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_traefik(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218013_remove_audit_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218013_remove_audit_integration.xml new file mode 100644 index 000000000..235698d86 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218013_remove_audit_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_ad_audit(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218014_remove_hap_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218014_remove_hap_integration.xml new file mode 100644 index 000000000..07ff2c475 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218014_remove_hap_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_hap(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218015_remove_iis_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218015_remove_iis_integration.xml new file mode 100644 index 000000000..2b691f116 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218015_remove_iis_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_iis(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218016_remove_os_query_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218016_remove_os_query_integration.xml new file mode 100644 index 000000000..d5c178137 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218016_remove_os_query_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_osquery(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260220001_update_filter_linux.xml b/backend/src/main/resources/config/liquibase/changelog/20260220001_update_filter_linux.xml new file mode 100644 index 000000000..7169e2b19 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260220001_update_filter_linux.xml @@ -0,0 +1,393 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/changelog/20260220002_update_linux_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260220002_update_linux_visualizations.xml new file mode 100644 index 000000000..60c0959b8 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260220002_update_linux_visualizations.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml b/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml new file mode 100644 index 000000000..3fbc5da66 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml @@ -0,0 +1,2934 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/changelog/20260223003_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260223003_update_windows_visualizations.xml new file mode 100644 index 000000000..410d2b6b0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260223003_update_windows_visualizations.xml @@ -0,0 +1,407 @@ + + + + + Update Windows visualizations to match logstash filter v3.1.0 field transformations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260223004_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260223004_update_windows_visualizations.xml new file mode 100644 index 000000000..59f6b2c2e --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260223004_update_windows_visualizations.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.winlog.recordNumber.keyword"', '"log.recordNumber.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.winlog.recordNumber.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam11.keyword"', '"log.eventDataParam11.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam11.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam12.keyword"', '"log.eventDataParam12.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam12.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam13.keyword"', '"log.eventDataParam13.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam13.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam17.keyword"', '"log.eventDataParam17.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam17.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam20.keyword"', '"log.eventDataParam20.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam20.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam3.keyword"', '"log.eventDataParam3.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam3.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam4.keyword"', '"log.eventDataParam4.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam4.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam8.keyword"', '"log.eventDataParam8.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam8.keyword%' + AND system_owner = true; + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260225001_add_unique_constraint_asset_name.xml b/backend/src/main/resources/config/liquibase/changelog/20260225001_add_unique_constraint_asset_name.xml new file mode 100644 index 000000000..cadac062f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260225001_add_unique_constraint_asset_name.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260226001_add_alias_to_data_input_status.xml b/backend/src/main/resources/config/liquibase/changelog/20260226001_add_alias_to_data_input_status.xml new file mode 100644 index 000000000..da6b01924 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260226001_add_alias_to_data_input_status.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302001_update_winevent_correlation_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302001_update_winevent_correlation_rules.xml new file mode 100644 index 000000000..16373d1f7 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302001_update_winevent_correlation_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302002_update_bit-defender_correlation_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302002_update_bit-defender_correlation_rules.xml new file mode 100644 index 000000000..412779b59 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302002_update_bit-defender_correlation_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302003_update_deceptive_bytes_correlation_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302003_update_deceptive_bytes_correlation_rules.xml new file mode 100644 index 000000000..61a7d96eb --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302003_update_deceptive_bytes_correlation_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302004_update_eset_correlation_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302004_update_eset_correlation_rules.xml new file mode 100644 index 000000000..f8d44fb35 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302004_update_eset_correlation_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302005_update_kaspersky_correlation_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302005_update_kaspersky_correlation_rules.xml new file mode 100644 index 000000000..b6ba02853 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302005_update_kaspersky_correlation_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302006_update_cisco_asa_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302006_update_cisco_asa_rules.xml new file mode 100644 index 000000000..083a85ef0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302006_update_cisco_asa_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302007_update_cisco_switch_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302007_update_cisco_switch_rules.xml new file mode 100644 index 000000000..2b2840504 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302007_update_cisco_switch_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302008_update_cisco_firepower_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302008_update_cisco_firepower_rules.xml new file mode 100644 index 000000000..4cd833387 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302008_update_cisco_firepower_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302009_update_cisco_meraki_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302009_update_cisco_meraki_rules.xml new file mode 100644 index 000000000..e9b4ce8ad --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302009_update_cisco_meraki_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302010_update_aws_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302010_update_aws_rules.xml new file mode 100644 index 000000000..ff13e2d8f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302010_update_aws_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302011_update_azure_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302011_update_azure_rules.xml new file mode 100644 index 000000000..b29460a38 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302011_update_azure_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302012_update_google_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302012_update_google_rules.xml new file mode 100644 index 000000000..ea636700b --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302012_update_google_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260302013_add_crowdstrike_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302013_add_crowdstrike_rules.xml new file mode 100644 index 000000000..1e80d9936 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260302013_add_crowdstrike_rules.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260303001_update_filter_macos.xml b/backend/src/main/resources/config/liquibase/changelog/20260303001_update_filter_macos.xml new file mode 100644 index 000000000..b9b017ac6 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260303001_update_filter_macos.xml @@ -0,0 +1,49 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/changelog/20260303002_update_filter_fortigate.xml b/backend/src/main/resources/config/liquibase/changelog/20260303002_update_filter_fortigate.xml new file mode 100644 index 000000000..f6c7e494f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260303002_update_filter_fortigate.xml @@ -0,0 +1,1108 @@ + + + + + + + ' + - fieldName: log.kvMessage + pattern: '{{.greedy}}' + + #Using the kv filter with default config, usefull in key-value logs + - kv: + source: log.kvMessage + fieldSplit: " " + valueSplit: "=" + + # Remove fields that have issues with kv filter + - delete: + fields: + - log.msg + + # Using grok to parse kv issued fields + - grok: + patterns: + - fieldName: log.irrelevant + pattern: '{{.data}}(msg=)' + - fieldName: log.msg + pattern: '{{.data}}({{.word}}=)' + - fieldName: log.irrelevant + pattern: '{{.greedy}}' + source: log.kvMessage + + # Using grok to remove irrelevant data + - grok: + patterns: + - fieldName: log.msg + pattern: '{{.greedy}}{{.space}}' + - fieldName: log.irrelevant + pattern: '{{.word}}(=)' + source: log.msg + + # Rename standard fields and fields out of kv result + - rename: + from: + - log.action + to: action + - rename: + from: + - log.dstip + to: target.ip + - rename: + from: + - log.dstport + to: target.port + - rename: + from: + - log.srcip + to: origin.ip + - rename: + from: + - log.srcport + to: origin.port + - rename: + from: + - log.mastersrcmac + to: log.masterSourceMac + - rename: + from: + - log.osname + to: log.osName + - rename: + from: + - log.unauthusersource + to: log.unauthUserSource + - rename: + from: + - log.srchwvendor + to: log.sourceVendor + - rename: + from: + - log.srcmac + to: origin.mac + - rename: + from: + - log.dest_ip + to: target.ip + - rename: + from: + - log.dest_port + to: target.port + - rename: + from: + - log.src_ip + to: origin.ip + - rename: + from: + - log.src_port + to: origin.port + + # Removing unused caracters + - trim: + function: prefix + substring: '<' + fields: + - log.priority + - trim: + function: suffix + substring: '>' + fields: + - log.priority + - trim: + function: prefix + substring: '"' + fields: + - log.devname + - log.devid + - log.logid + - log.type + - log.subtype + - log.eventtype + - log.level + - log.devid + - log.vd + - log.srccountry + - log.dstcountry + - log.srcintf + - log.srcintfrole + - log.dstintf + - log.dstintfrole + - log.direction + - log.poluuid + - log.policytype + - action + - log.appcat + - log.app + - log.hostname + - log.url + - log.apprisk + - log.scertcname + - log.scertissuer + - log.appact + - log.applist + - log.masterSourceMac + - log.osName + - log.service + - log.trandisp + - log.tz + - log.srcswversion + - log.unauthUserSource + - origin.mac + - log.unauthuser + - log.srcname + - log.sourceVendor + - trim: + function: suffix + substring: '"' + fields: + - log.devname + - log.devid + - log.logid + - log.type + - log.subtype + - log.eventtype + - log.level + - log.devid + - log.vd + - log.srccountry + - log.dstcountry + - log.srcintf + - log.srcintfrole + - log.dstintf + - log.dstintfrole + - log.direction + - log.poluuid + - log.policytype + - action + - log.appcat + - log.app + - log.hostname + - log.url + - log.apprisk + - log.scertcname + - log.scertissuer + - log.appact + - log.applist + - log.masterSourceMac + - log.osName + - log.service + - log.trandisp + - log.tz + - log.srcswversion + - log.unauthUserSource + - origin.mac + - log.unauthuser + - log.srcname + - log.sourceVendor + + # Adding geolocation to origin.ip + - dynamic: + plugin: com.utmstack.geolocation + params: + source: origin.ip + destination: origin.geolocation + where: exists("origin.ip") + + # Adding geolocation to target.ip + - dynamic: + plugin: com.utmstack.geolocation + params: + source: target.ip + destination: target.geolocation + where: exists("target.ip") + + # Adding protocol field based on IANA protocol numbers + - add: + function: "string" + params: + key: protocol + value: "HOPOPT" + where: equals("log.proto", "0") + - add: + function: "string" + params: + key: protocol + value: "ICMP" + where: equals("log.proto", "1") + - add: + function: "string" + params: + key: protocol + value: "IGMP" + where: equals("log.proto", "2") + - add: + function: "string" + params: + key: protocol + value: "GGP" + where: equals("log.proto", "3") + - add: + function: "string" + params: + key: protocol + value: "IP-in-IP" + where: equals("log.proto", "4") + - add: + function: "string" + params: + key: protocol + value: "ST" + where: equals("log.proto", "5") + - add: + function: "string" + params: + key: protocol + value: "TCP" + where: equals("log.proto", "6") + - add: + function: "string" + params: + key: protocol + value: "CBT" + where: equals("log.proto", "7") + - add: + function: "string" + params: + key: protocol + value: "EGP" + where: equals("log.proto", "8") + - add: + function: "string" + params: + key: protocol + value: "IGP" + where: equals("log.proto", "9") + - add: + function: "string" + params: + key: protocol + value: "BBN-RCC-MON" + where: equals("log.proto", "10") + - add: + function: "string" + params: + key: protocol + value: "NVP-II" + where: equals("log.proto", "11") + - add: + function: "string" + params: + key: protocol + value: "PUP" + where: equals("log.proto", "12") + - add: + function: "string" + params: + key: protocol + value: "ARGUS" + where: equals("log.proto", "13") + - add: + function: "string" + params: + key: protocol + value: "EMCON" + where: equals("log.proto", "14") + - add: + function: "string" + params: + key: protocol + value: "XNET" + where: equals("log.proto", "15") + - add: + function: "string" + params: + key: protocol + value: "CHAOS" + where: equals("log.proto", "16") + - add: + function: "string" + params: + key: protocol + value: "UDP" + where: equals("log.proto", "17") + - add: + function: "string" + params: + key: protocol + value: "MUX" + where: equals("log.proto", "18") + - add: + function: "string" + params: + key: protocol + value: "DCN-MEAS" + where: equals("log.proto", "19") + - add: + function: "string" + params: + key: protocol + value: "HMP" + where: equals("log.proto", "20") + - add: + function: "string" + params: + key: protocol + value: "PRM" + where: equals("log.proto", "21") + - add: + function: "string" + params: + key: protocol + value: "XNS-IDP" + where: equals("log.proto", "22") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-1" + where: equals("log.proto", "23") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-2" + where: equals("log.proto", "24") + - add: + function: "string" + params: + key: protocol + value: "LEAF-1" + where: equals("log.proto", "25") + - add: + function: "string" + params: + key: protocol + value: "LEAF-2" + where: equals("log.proto", "26") + - add: + function: "string" + params: + key: protocol + value: "RDP" + where: equals("log.proto", "27") + - add: + function: "string" + params: + key: protocol + value: "IRTP" + where: equals("log.proto", "28") + - add: + function: "string" + params: + key: protocol + value: "ISO-TP4" + where: equals("log.proto", "29") + - add: + function: "string" + params: + key: protocol + value: "NETBLT" + where: equals("log.proto", "30") + - add: + function: "string" + params: + key: protocol + value: "MFE-NSP" + where: equals("log.proto", "31") + - add: + function: "string" + params: + key: protocol + value: "MERIT-INP" + where: equals("log.proto", "32") + - add: + function: "string" + params: + key: protocol + value: "DCCP" + where: equals("log.proto", "33") + - add: + function: "string" + params: + key: protocol + value: "3PC" + where: equals("log.proto", "34") + - add: + function: "string" + params: + key: protocol + value: "IDPR" + where: equals("log.proto", "35") + - add: + function: "string" + params: + key: protocol + value: "XTP" + where: equals("log.proto", "36") + - add: + function: "string" + params: + key: protocol + value: "DDP" + where: equals("log.proto", "37") + - add: + function: "string" + params: + key: protocol + value: "IDPR-CMTP" + where: equals("log.proto", "38") + - add: + function: "string" + params: + key: protocol + value: "TP++" + where: equals("log.proto", "39") + - add: + function: "string" + params: + key: protocol + value: "IL" + where: equals("log.proto", "40") + - add: + function: "string" + params: + key: protocol + value: "IPv6" + where: equals("log.proto", "41") + - add: + function: "string" + params: + key: protocol + value: "SDRP" + where: equals("log.proto", "42") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Route" + where: equals("log.proto", "43") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Frag" + where: equals("log.proto", "44") + - add: + function: "string" + params: + key: protocol + value: "IDRP" + where: equals("log.proto", "45") + - add: + function: "string" + params: + key: protocol + value: "RSVP" + where: equals("log.proto", "46") + - add: + function: "string" + params: + key: protocol + value: "GRE" + where: equals("log.proto", "47") + - add: + function: "string" + params: + key: protocol + value: "DSR" + where: equals("log.proto", "48") + - add: + function: "string" + params: + key: protocol + value: "BNA" + where: equals("log.proto", "49") + - add: + function: "string" + params: + key: protocol + value: "ESP" + where: equals("log.proto", "50") + - add: + function: "string" + params: + key: protocol + value: "AH" + where: equals("log.proto", "51") + - add: + function: "string" + params: + key: protocol + value: "I-NLSP" + where: equals("log.proto", "52") + - add: + function: "string" + params: + key: protocol + value: "SwIPe" + where: equals("log.proto", "53") + - add: + function: "string" + params: + key: protocol + value: "NARP" + where: equals("log.proto", "54") + - add: + function: "string" + params: + key: protocol + value: "MOBILE" + where: equals("log.proto", "55") + - add: + function: "string" + params: + key: protocol + value: "TLSP" + where: equals("log.proto", "56") + - add: + function: "string" + params: + key: protocol + value: "SKIP" + where: equals("log.proto", "57") + - add: + function: "string" + params: + key: protocol + value: "IPv6-ICMP" + where: equals("log.proto", "58") + - add: + function: "string" + params: + key: protocol + value: "IPv6-NoNxt" + where: equals("log.proto", "59") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Opts" + where: equals("log.proto", "60") + - add: + function: "string" + params: + key: protocol + value: "CFTP" + where: equals("log.proto", "62") + - add: + function: "string" + params: + key: protocol + value: "SAT-EXPAK" + where: equals("log.proto", "64") + - add: + function: "string" + params: + key: protocol + value: "KRYPTOLAN" + where: equals("log.proto", "65") + - add: + function: "string" + params: + key: protocol + value: "RVD" + where: equals("log.proto", "66") + - add: + function: "string" + params: + key: protocol + value: "IPPC" + where: equals("log.proto", "67") + - add: + function: "string" + params: + key: protocol + value: "SAT-MON" + where: equals("log.proto", "69") + - add: + function: "string" + params: + key: protocol + value: "VISA" + where: equals("log.proto", "70") + - add: + function: "string" + params: + key: protocol + value: "IPCU" + where: equals("log.proto", "71") + - add: + function: "string" + params: + key: protocol + value: "CPNX" + where: equals("log.proto", "72") + - add: + function: "string" + params: + key: protocol + value: "CPHB" + where: equals("log.proto", "73") + - add: + function: "string" + params: + key: protocol + value: "WSN" + where: equals("log.proto", "74") + - add: + function: "string" + params: + key: protocol + value: "PVP" + where: equals("log.proto", "75") + - add: + function: "string" + params: + key: protocol + value: "BR-SAT-MON" + where: equals("log.proto", "76") + - add: + function: "string" + params: + key: protocol + value: "SUN-ND" + where: equals("log.proto", "77") + - add: + function: "string" + params: + key: protocol + value: "WB-MON" + where: equals("log.proto", "78") + - add: + function: "string" + params: + key: protocol + value: "WB-EXPAK" + where: equals("log.proto", "79") + - add: + function: "string" + params: + key: protocol + value: "ISO-IP" + where: equals("log.proto", "80") + - add: + function: "string" + params: + key: protocol + value: "VMTP" + where: equals("log.proto", "81") + - add: + function: "string" + params: + key: protocol + value: "SECURE-VMTP" + where: equals("log.proto", "82") + - add: + function: "string" + params: + key: protocol + value: "VINES" + where: equals("log.proto", "83") + - add: + function: "string" + params: + key: protocol + value: "IPTM" + where: equals("log.proto", "84") + - add: + function: "string" + params: + key: protocol + value: "NSFNET-IGP" + where: equals("log.proto", "85") + - add: + function: "string" + params: + key: protocol + value: "DGP" + where: equals("log.proto", "86") + - add: + function: "string" + params: + key: protocol + value: "TCF" + where: equals("log.proto", "87") + - add: + function: "string" + params: + key: protocol + value: "EIGRP" + where: equals("log.proto", "88") + - add: + function: "string" + params: + key: protocol + value: "OSPF" + where: equals("log.proto", "89") + - add: + function: "string" + params: + key: protocol + value: "Sprite-RPC" + where: equals("log.proto", "90") + - add: + function: "string" + params: + key: protocol + value: "LARP" + where: equals("log.proto", "91") + - add: + function: "string" + params: + key: protocol + value: "MTP" + where: equals("log.proto", "92") + - add: + function: "string" + params: + key: protocol + value: "AX.25" + where: equals("log.proto", "93") + - add: + function: "string" + params: + key: protocol + value: "OS" + where: equals("log.proto", "94") + - add: + function: "string" + params: + key: protocol + value: "MICP" + where: equals("log.proto", "95") + - add: + function: "string" + params: + key: protocol + value: "SCC-SP" + where: equals("log.proto", "96") + - add: + function: "string" + params: + key: protocol + value: "ETHERIP" + where: equals("log.proto", "97") + - add: + function: "string" + params: + key: protocol + value: "ENCAP" + where: equals("log.proto", "98") + - add: + function: "string" + params: + key: protocol + value: "GMTP" + where: equals("log.proto", "100") + - add: + function: "string" + params: + key: protocol + value: "IFMP" + where: equals("log.proto", "101") + - add: + function: "string" + params: + key: protocol + value: "PNNI" + where: equals("log.proto", "102") + - add: + function: "string" + params: + key: protocol + value: "PIM" + where: equals("log.proto", "103") + - add: + function: "string" + params: + key: protocol + value: "ARIS" + where: equals("log.proto", "104") + - add: + function: "string" + params: + key: protocol + value: "SCPS" + where: equals("log.proto", "105") + - add: + function: "string" + params: + key: protocol + value: "QNX" + where: equals("log.proto", "106") + - add: + function: "string" + params: + key: protocol + value: "A/N" + where: equals("log.proto", "107") + - add: + function: "string" + params: + key: protocol + value: "IPComp" + where: equals("log.proto", "108") + - add: + function: "string" + params: + key: protocol + value: "SNP" + where: equals("log.proto", "109") + - add: + function: "string" + params: + key: protocol + value: "Compaq-Peer" + where: equals("log.proto", "110") + - add: + function: "string" + params: + key: protocol + value: "IPX-in-IP" + where: equals("log.proto", "111") + - add: + function: "string" + params: + key: protocol + value: "VRRP" + where: equals("log.proto", "112") + - add: + function: "string" + params: + key: protocol + value: "PGM" + where: equals("log.proto", "113") + - add: + function: "string" + params: + key: protocol + value: "L2TP" + where: equals("log.proto", "115") + - add: + function: "string" + params: + key: protocol + value: "DDX" + where: equals("log.proto", "116") + - add: + function: "string" + params: + key: protocol + value: "IATP" + where: equals("log.proto", "117") + - add: + function: "string" + params: + key: protocol + value: "STP" + where: equals("log.proto", "118") + - add: + function: "string" + params: + key: protocol + value: "SRP" + where: equals("log.proto", "119") + - add: + function: "string" + params: + key: protocol + value: "UTI" + where: equals("log.proto", "120") + - add: + function: "string" + params: + key: protocol + value: "SMP" + where: equals("log.proto", "121") + - add: + function: "string" + params: + key: protocol + value: "SM" + where: equals("log.proto", "122") + - add: + function: "string" + params: + key: protocol + value: "PTP" + where: equals("log.proto", "123") + - add: + function: "string" + params: + key: protocol + value: "IS-IS" + where: equals("log.proto", "124") + - add: + function: "string" + params: + key: protocol + value: "FIRE" + where: equals("log.proto", "125") + - add: + function: "string" + params: + key: protocol + value: "CRTP" + where: equals("log.proto", "126") + - add: + function: "string" + params: + key: protocol + value: "CRUDP" + where: equals("log.proto", "127") + - add: + function: "string" + params: + key: protocol + value: "SSCOPMCE" + where: equals("log.proto", "128") + - add: + function: "string" + params: + key: protocol + value: "IPLT" + where: equals("log.proto", "129") + - add: + function: "string" + params: + key: protocol + value: "SPS" + where: equals("log.proto", "130") + - add: + function: "string" + params: + key: protocol + value: "PIPE" + where: equals("log.proto", "131") + - add: + function: "string" + params: + key: protocol + value: "SCTP" + where: equals("log.proto", "132") + - add: + function: "string" + params: + key: protocol + value: "FC" + where: equals("log.proto", "133") + - add: + function: "string" + params: + key: protocol + value: "RSVP-E2E-IGNORE" + where: equals("log.proto", "134") + - add: + function: "string" + params: + key: protocol + value: "Mobility-Header" + where: equals("log.proto", "135") + - add: + function: "string" + params: + key: protocol + value: "UDPLite" + where: equals("log.proto", "136") + - add: + function: "string" + params: + key: protocol + value: "MPLS-in-IP" + where: equals("log.proto", "137") + - add: + function: "string" + params: + key: protocol + value: "manet" + where: equals("log.proto", "138") + - add: + function: "string" + params: + key: protocol + value: "HIP" + where: equals("log.proto", "139") + - add: + function: "string" + params: + key: protocol + value: "Shim6" + where: equals("log.proto", "140") + - add: + function: "string" + params: + key: protocol + value: "WESP" + where: equals("log.proto", "141") + - add: + function: "string" + params: + key: protocol + value: "ROHC" + where: equals("log.proto", "142") + - add: + function: "string" + params: + key: protocol + value: "Ethernet" + where: equals("log.proto", "143") + - add: + function: "string" + params: + key: protocol + value: "AGGFRAG" + where: equals("log.proto", "144") + - add: + function: "string" + params: + key: protocol + value: "NSH" + where: equals("log.proto", "145") + - add: + function: "string" + params: + key: protocol + value: "Homa" + where: equals("log.proto", "146") + - add: + function: "string" + params: + key: protocol + value: "BIT-EMU" + where: equals("log.proto", "147") + + # Removing unused fields + - delete: + fields: + - log.kvMessage$$ + WHERE id=901; + ]]> + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/changelog/20260303003_update_filter_windows.xml b/backend/src/main/resources/config/liquibase/changelog/20260303003_update_filter_windows.xml new file mode 100644 index 000000000..68fd8032b --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260303003_update_filter_windows.xml @@ -0,0 +1,2934 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/data/20260302/aws/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/aws/utm_correlation_rules.sql new file mode 100644 index 000000000..86b62d739 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/aws/utm_correlation_rules.sql @@ -0,0 +1,780 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1186, 'AWS Management Console Brute Force of Root User Identity', 3, 2, 1, 'Credential Access', 'T1110 - Brute Force', 'Identifies a high number of failed authentication attempts to the AWS management console for the Root user identity. An adversary may attempt to brute force the password for the Root user identity, as it has complete access to all services and resources for the AWS account', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1110/","https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html"]', e'equals("log.eventSource", "signin.amazonaws.com") && +equals("log.eventName", "ConsoleLogin") && +equals("log.userIdentityType", "root") && +(exists("log.errorCode") || exists("log.errorMessage")) +', '2026-03-02 19:28:13.989146', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-15m","count":5}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1187, 'AWS IAM Brute Force of Assume Role Policy', 3, 2, 1, 'Credential Access', 'T1110 - Brute Force', 'Identifies a high number of failed attempts to assume an AWS Identity and Access Management (IAM) role. IAM roles are used to delegate access to users or services. An adversary may attempt to enumerate IAM roles in order to determine if a role exists before attempting to assume or hijack the discovered role', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1110/","https://www.praetorian.com/blog/aws-iam-assume-role-vulnerabilities","https://rhinosecuritylabs.com/aws/assume-worst-aws-assume-role-enumeration/"]', e'equals("log.eventSource", "iam.amazonaws.com") && +equals("log.eventName", "UpdateAssumeRolePolicy") && +equals("log.errorCode", "MalformedPolicyDocumentException") +', '2026-03-02 19:28:15.419013', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"origin.user","operator":"filter_term","value":"{{.origin.user}}"}],"or":null,"within":"now-15m","count":5}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1188, 'AWS Root Login Without MFA', 3, 2, 2, 'Initial Access', 'T1078 - Valid Accounts', 'Identifies attempts to login to AWS as the root user without using multi-factor authentication (MFA). Amazon AWS best practices indicate that the root user should be protected by MFA', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1078/","https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html"]', e'equals("log.eventSource", "signin.amazonaws.com") && +equals("log.eventName", "ConsoleLogin") && +equals("log.userIdentityType", "root") && +equals("log.additionalEventData.MFAUsed", "no") +', '2026-03-02 19:28:16.726404', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1242, 'EC2 Instance Metadata Abuse', 3, 2, 1, 'Credential Access', 'T1552.005 - Unsecured Credentials: Cloud Instance Metadata API', e'Detects potential abuse of EC2 instance metadata service (IMDS) which could indicate SSRF exploitation or credential theft. Monitors for unusual API calls using credentials with IMDSv1 role delivery or suspicious patterns of EC2 metadata access. + +Next Steps: +1. Identify the EC2 instance and application making the metadata requests +2. Check if the instance has IMDSv2 enforced (HttpTokens set to "required") +3. Review the instance\'s IAM role permissions and recent API activity +4. Investigate any web applications running on the instance for SSRF vulnerabilities +5. Check CloudTrail logs for unusual API calls using the instance profile credentials +6. If unauthorized access is confirmed, rotate the instance profile credentials and enforce IMDSv2 +', '["https://hackingthe.cloud/aws/exploitation/ec2-metadata-ssrf/","https://attack.mitre.org/techniques/T1552/005/"]', e'(equals("log.eventSource", "ec2.amazonaws.com") && + equals("log.eventName", "ModifyInstanceMetadataOptions") && + equals("log.errorCode", "") && + contains("log.requestParameters", "httpTokens\\":\\"optional")) || +(exists("log.requestParameters") && + contains("log.requestParameters", "ec2:RoleDelivery\\":\\"1.0") && + equals("log.errorCode", "")) +', '2026-03-02 19:29:28.778287', true, true, 'origin', null, '[]', '["lastEvent.log.sourceIPAddress","lastEvent.log.userIdentityAccountId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1243, 'AWS EC2 Cryptomining Instance Launch Detection', 1, 2, 3, 'Resource Hijacking', 'T1496 - Resource Hijacking', e'Detects EC2 instance launches using GPU or high-compute instance types commonly associated with cryptomining operations. Attackers with compromised AWS credentials frequently launch expensive GPU instances (p3, p4, g4, g5) or large compute-optimized instances for cryptocurrency mining. + +Next Steps: +1. Verify the identity launching the instance ({{log.userIdentityArn}}) and confirm authorization +2. Check if the instance type matches legitimate workloads for the account +3. Review the source IP ({{log.sourceIPAddress}}) for suspicious origins +4. Examine the AMI used for the instance launch for known mining software +5. Check billing dashboards for unexpected cost spikes +6. If unauthorized, terminate the instance immediately and rotate compromised credentials +7. Review IAM policies to restrict instance type launches using SCPs +8. Enable AWS Budgets alerts for cost anomaly detection +', '["https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html","https://attack.mitre.org/techniques/T1496/"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +equals("log.eventName", "RunInstances") && +equals("log.errorCode", "") && +regexMatch("log.requestParameters.instanceType", "(?i)^(p3|p4d|p4de|p5|g4dn|g4ad|g5|g5g|c5\\\\.18xlarge|c5\\\\.24xlarge|c5a\\\\.24xlarge|c6i\\\\.32xlarge|c7g\\\\.16xlarge)") +', '2026-03-02 19:29:30.086015', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.requestParameters.instanceType"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1189, 'AWS IAM Assume Role Policy Update', 2, 2, 1, 'Initial Access', 'T1078 - Valid Accounts', 'Identifies attempts to modify an AWS IAM Assume Role Policy. An adversary may attempt to modify the AssumeRolePolicy of a misconfigured role in order to gain the privileges of that role', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1078/","https://labs.bishopfox.com/tech-blog/5-privesc-attack-vectors-in-aws"]', e'equals("log.eventSource", "iam.amazonaws.com") && +equals("log.eventName", "UpdateAssumeRolePolicy") +', '2026-03-02 19:28:18.139259', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1190, 'AWS Route 53 Domain Transferred to Another Account', 3, 3, 3, 'Persistence', 'T1098 - Account Manipulation', 'Identifies when a request has been made to transfer a Route 53 domain to another AWS account', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1098/","https://attack.mitre.org/tactics/TA0006/","https://docs.aws.amazon.com/Route53/latest/APIReference/API_Operations_Amazon_Route_53.html"]', e'equals("log.eventSource", "route53.amazonaws.com") && +equals("log.eventName", "TransferDomainToAnotherAwsAccount") +', '2026-03-02 19:28:19.570552', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1191, 'AWS Route 53 Domain Transfer Lock Disabled', 3, 2, 2, 'Persistence', 'T1098 - Account Manipulation', 'Identifies when a transfer lock was removed from a Route 53 domain. It is recommended to refrain from performing this action unless intending to transfer the domain to a different registrar', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1098/","https://attack.mitre.org/tactics/TA0006/","https://docs.aws.amazon.com/Route53/latest/APIReference/API_Operations_Amazon_Route_53.html","https://docs.aws.amazon.com/Route53/latest/APIReference/API_domains_DisableDomainTransferLock.html"]', e'equals("log.eventSource", "route53.amazonaws.com") && +equals("log.eventName", "DisableDomainTransferLock") +', '2026-03-02 19:28:20.877451', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1192, 'AWS Execution via System Manager', 2, 1, 1, 'Initial Access', 'T1566 - Phishing', 'Identifies the execution of commands and scripts via System Manager. Execution methods such as RunShellScript, RunPowerShellScript, and alike can be abused by an authenticated attacker to install a backdoor or to interact with a compromised instance via reverse-shell using system only commands
Potential false positives
Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Suspicious commands from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1566/","https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-plugins.html"]', e'equals("log.eventSource", "ssm.amazonaws.com") && +equals("log.eventName", "SendCommand") +', '2026-03-02 19:28:22.195231', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1193, 'AWS IAM Password Recovery Requested', 2, 1, 0, 'Initial Access', 'T1078 - Valid Accounts', 'Identifies AWS IAM password recovery requests. An adversary may attempt to gain unauthorized AWS access by abusing password recovery mechanisms.
Potential false positives
Verify whether the user identity, user agent, and/or hostname should be requesting changes in your environment. Password reset attempts from unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1078/","https://www.cadosecurity.com/2020/06/11/an-ongoing-aws-phishing-campaign/"]', e'equals("log.eventSource", "signin.amazonaws.com") && +equals("log.eventName", "PasswordRecoveryRequested") +', '2026-03-02 19:28:23.414873', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1194, 'AWS Management Console Root Login', 3, 3, 3, 'Initial Access', 'T1078 - Valid Accounts', 'Identifies a successful login to the AWS Management Console by the Root user.
Adversaries may obtain and abuse credentials of a cloud account as a means of gaining Initial Access, Persistence, Privilege Escalation, or Defense Evasion.
Compromised credentials for cloud accounts can be used to harvest sensitive data from online storage accounts and databases.
Potential false positives
It’s strongly recommended that the root user is not used for everyday tasks, including the administrative ones. Verify whether the IP address, location, and/or hostname should be logging in as root in your environment. Unfamiliar root logins should be investigated immediately. If known behavior is causing false positives, it can be exempted from the rule.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1078/","https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html"]', e'equals("log.eventSource", "signin.amazonaws.com") && +equals("log.eventName", "ConsoleLogin") && +equals("log.userIdentityType", "root") +', '2026-03-02 19:28:24.682561', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1195, 'AWS IAM Deactivation of MFA Device', 3, 2, 2, 'Impact', 'T1531 - Account Access Removal', 'Identifies the deactivation of a specified multi-factor authentication (MFA) device and removes it from association with the user name for which it was originally enabled. In AWS Identity and Access Management (IAM), a device must be deactivated before it can be deleted', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1531/","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/deactivate-mfa-device.html","https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeactivateMFADevice.html"]', e'equals("log.eventSource", "iam.amazonaws.com") && +oneOf("log.eventName", ["DeactivateMFADevice", "DeleteVirtualMFADevice"]) +', '2026-03-02 19:28:26.077268', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1196, 'AWS EC2 Encryption Disabled', 3, 2, 2, 'Impact', 'T1565 - Data Manipulation', 'Identifies disabling of Amazon Elastic Block Store (EBS) encryption by default in the current region. Disabling encryption by default does not change the encryption status of your existing volumes', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1565/","https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/disable-ebs-encryption-by-default.html","https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DisableEbsEncryptionByDefault.html"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +equals("log.eventName", "DisableEbsEncryptionByDefault") +', '2026-03-02 19:28:27.480574', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1197, 'AWS CloudWatch Log Stream Deletion', 3, 2, 2, 'Impact', 'T1485 - Data Destruction', 'Identifies the deletion of an AWS CloudWatch log stream, which permanently deletes all associated archived log events with the stream', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1485/","https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/logs/delete-log-stream.html","https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DeleteLogStream.html"]', e'equals("log.eventSource", "logs.amazonaws.com") && +equals("log.eventName", "DeleteLogStream") +', '2026-03-02 19:28:28.742279', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1198, 'AWS RDS Cluster Deletion', 3, 2, 2, 'Impact', 'T1485 - Data Destruction', 'Identifies the deletion of an Amazon Relational Database Service (RDS) Aurora database cluster or global database cluster', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1485/","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/delete-db-cluster.html","https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DeleteDBCluster.html","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/delete-global-cluster.html","https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DeleteGlobalCluster.html"]', e'equals("log.eventSource", "rds.amazonaws.com") && +oneOf("log.eventName", ["DeleteDBCluster", "DeleteGlobalCluster"]) +', '2026-03-02 19:28:30.142155', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1199, 'AWS CloudWatch Log Group Deletion', 3, 2, 2, 'Impact', 'T1485 - Data Destruction', 'Identifies the deletion of a specified AWS CloudWatch log group. When a log group is deleted, all the archived log events associated with the log group are also permanently deleted', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1485/","https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/logs/delete-log-group.html","https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DeleteLogGroup.html"]', e'equals("log.eventSource", "logs.amazonaws.com") && +equals("log.eventName", "DeleteLogGroup") +', '2026-03-02 19:28:32.197934', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1200, 'AWS CloudTrail Log Updated', 2, 2, 1, 'Impact', 'T1565 - Data Manipulation', 'Identifies an update to an AWS log trail setting that specifies the delivery of log files', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1565/","https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1530/","https://docs.aws.amazon.com/awscloudtrail/latest/APIReference/API_UpdateTrail.html","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudtrail/update-trail.html"]', e'equals("log.eventSource", "cloudtrail.amazonaws.com") && +equals("log.eventName", "UpdateTrail") +', '2026-03-02 19:28:33.440966', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1201, 'AWS RDS Snapshot Export', 3, 2, 2, 'Exfiltration', 'T1537 - Transfer Data to Cloud Account', 'Identifies the export of an Amazon Relational Database Service (RDS) Aurora database snapshot', '["https://attack.mitre.org/tactics/TA0010/","https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_StartExportTask.html"]', e'equals("log.eventSource", "rds.amazonaws.com") && +equals("log.eventName", "StartExportTask") +', '2026-03-02 19:28:34.650471', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1202, 'AWS EC2 VM Export Failure', 3, 2, 2, 'Exfiltration', 'T1537 - Transfer Data to Cloud Account', 'Identifies an attempt to export an AWS EC2 instance. A virtual machine (VM) export may indicate an attempt to extract or exfiltrate information', '["https://attack.mitre.org/techniques/T1537/","https://attack.mitre.org/tactics/TA0010/","https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1005/","https://docs.aws.amazon.com/vm-import/latest/userguide/vmexport.html#export-instance"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +equals("log.eventName", "CreateInstanceExportTask") +', '2026-03-02 19:28:35.962704', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1203, 'AWS EC2 Snapshot Activity', 3, 2, 2, 'Exfiltration', 'T1537 - Transfer Data to Cloud Account', 'An attempt was made to modify AWS EC2 snapshot attributes. Snapshots are sometimes shared by threat actors in order to exfiltrate bulk data from an EC2 fleet. If the permissions were modified, verify the snapshot was not shared with an unauthorized or unexpected AWS account', '["https://attack.mitre.org/tactics/TA0010/","https://attack.mitre.org/techniques/T1537/","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/modify-snapshot-attribute.html","https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifySnapshotAttribute.html"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +equals("log.eventName", "ModifySnapshotAttribute") +', '2026-03-02 19:28:37.214013', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1204, 'AWS EC2 Full Network Packet Capture Detected', 3, 2, 2, 'Exfiltration', 'T1020 - Automated Exfiltration', 'Identifies potential Traffic Mirroring in an Amazon Elastic Compute Cloud (EC2) instance. Traffic Mirroring is an Amazon VPC feature that you can use to copy network traffic from an elastic network interface. This feature can potentially be abused to exfiltrate sensitive data from unencrypted internal traffic', '["https://attack.mitre.org/tactics/TA0010/","https://attack.mitre.org/techniques/T1020/","https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1074/","https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_TrafficMirrorFilter.html","https://github.com/easttimor/aws-incident-response"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +(equals("log.eventName", "CreateTrafficMirrorFilter") || +equals("log.eventName", "CreateTrafficMirrorFilterRule") || +equals("log.eventName", "CreateTrafficMirrorSession") || +equals("log.eventName", "CreateTrafficMirrorTarget")) +', '2026-03-02 19:28:38.332137', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1205, 'AWS WAF Rule or Rule Group Deletion', 3, 2, 2, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies the deletion of a specified AWS Web Application Firewall (WAF) rule or rule group', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/waf/delete-rule-group.html","https://docs.aws.amazon.com/waf/latest/APIReference/API_waf_DeleteRuleGroup.html"]', e'oneOf("log.eventSource", ["waf.amazonaws.com", "waf-regional.amazonaws.com", "wafv2.amazonaws.com"]) && +(equals("log.eventName", "DeleteRule") || equals("log.eventName", "DeleteRuleGroup")) +', '2026-03-02 19:28:39.542463', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1206, 'AWS WAF Access Control List Deletion', 3, 2, 2, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies the deletion of a specified AWS Web Application Firewall (WAF) access control list', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/waf-regional/delete-web-acl.html","https://docs.aws.amazon.com/waf/latest/APIReference/API_wafRegional_DeleteWebACL.html"]', e'oneOf("log.eventSource", ["waf.amazonaws.com", "waf-regional.amazonaws.com", "wafv2.amazonaws.com"]) && +equals("log.eventName", "DeleteWebACL") +', '2026-03-02 19:28:40.676263', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1207, 'AWS S3 Bucket Configuration Deletion', 3, 2, 2, 'Defense Evasion', 'T1070 - Indicator Removal', 'Identifies the deletion of various Amazon Simple Storage Service (S3) bucket configuration components', '["https://attack.mitre.org/techniques/T1070/","https://attack.mitre.org/tactics/TA0005/","https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketPolicy.html","https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html","https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketCors.html","https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketEncryption.html","https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketLifecycle.html"]', e'equals("log.eventSource", "s3.amazonaws.com") && +oneOf("log.eventName", ["DeleteBucketPolicy", "DeleteBucketReplication", +"DeleteBucketCors", "DeleteBucketEncryption", "DeleteBucketLifecycle"]) +', '2026-03-02 19:28:41.939392', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1208, 'AWS GuardDuty Detector Deletion', 3, 2, 2, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies the deletion of an Amazon GuardDuty detector. Upon deletion, GuardDuty stops monitoring the environment and all existing findings are lost', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/guardduty/delete-detector.html","https://docs.aws.amazon.com/guardduty/latest/APIReference/API_DeleteDetector.html"]', e'equals("log.eventSource", "guardduty.amazonaws.com") && +equals("log.eventName", "DeleteDetector") +', '2026-03-02 19:28:43.252126', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1209, 'AWS EC2 Flow Log Deletion', 3, 2, 2, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies the deletion of one or more flow logs in AWS Elastic Compute Cloud (EC2). An adversary may delete flow logs in an attempt to evade defenses', '["https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/delete-flow-logs.html","https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DeleteFlowLogs.html","https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +equals("log.eventName", "DeleteFlowLogs") +', '2026-03-02 19:28:44.521354', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1210, 'AWS Configuration Recorder Stopped', 3, 2, 2, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies an AWS configuration change to stop recording a designated set of resources', '["https://awscli.amazonaws.com/v2/documentation/api/latest/reference/configservice/stop-configuration-recorder.html","https://docs.aws.amazon.com/config/latest/APIReference/API_StopConfigurationRecorder.html","https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/"]', e'equals("log.eventSource", "config.amazonaws.com") && +equals("log.eventName", "StopConfigurationRecorder") +', '2026-03-02 19:28:45.916524', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1211, 'AWS Config Service Tampering', 3, 2, 2, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies attempts to delete an AWS Config Service resource. An adversary may tamper with Config services in order to reduce visibility into the security posture of an account and / or its workload instances', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/","https://docs.aws.amazon.com/config/latest/developerguide/how-does-config-work.html","https://docs.aws.amazon.com/config/latest/APIReference/API_Operations.html"]', e'equals("log.eventSource", "config.amazonaws.com") && +oneOf("log.eventName", ["DeleteConfigRule", "DeleteOrganizationConfigRule", +"DeleteConfigurationAggregator", "DeleteConfigurationRecorder", +"DeleteConformancePack", "DeleteOrganizationConformancePack", +"DeleteDeliveryChannel", "DeleteRemediationConfiguration", +"DeleteRetentionConfiguration"]) +', '2026-03-02 19:28:47.262856', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1212, 'AWS CloudTrail Log Suspended', 3, 2, 2, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies suspending the recording of AWS API calls and log file delivery for the specified trail. An adversary may suspend trails in an attempt to evade defenses', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/","https://docs.aws.amazon.com/awscloudtrail/latest/APIReference/API_StopLogging.html","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudtrail/stop-logging.html"]', e'equals("log.eventSource", "cloudtrail.amazonaws.com") && +equals("log.eventName", "StopLogging") +', '2026-03-02 19:28:48.579210', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1213, 'AWS CloudTrail Log Deleted', 2, 3, 2, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies the deletion of an AWS log trail. An adversary may delete trails in an attempt to evade defenses', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/","https://docs.aws.amazon.com/awscloudtrail/latest/APIReference/API_DeleteTrail.html","https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudtrail/delete-trail.html"]', e'equals("log.eventSource", "cloudtrail.amazonaws.com") && +equals("log.eventName", "DeleteTrail") +', '2026-03-02 19:28:49.886394', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1214, 'AWS VPC Flow Log Anomalies', 3, 2, 2, 'Discovery', 'T1046 - Network Service Discovery', e'Detects anomalies in VPC Flow Logs configuration that could indicate attempts to hide malicious network activity. Monitors for deletion or modification of flow log configurations. + +Next Steps: +1. Verify if the flow log changes were authorized by reviewing the AWS CloudTrail logs for the userIdentityArn +2. Check if the source IP address belongs to known administrative systems or jump boxes +3. Review other activities from the same source IP and user identity in the past 24-48 hours +4. Examine the affected VPC and its resources to understand the impact of disabled flow logging +5. If unauthorized, immediately re-enable flow logs and investigate what network activity may have occurred while logging was disabled +6. Review IAM permissions for the user/role that made these changes to ensure least privilege +7. Consider implementing preventive controls using AWS Config rules or SCPs to prevent unauthorized flow log modifications +', '["https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html","https://attack.mitre.org/techniques/T1046/"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +oneOf("log.eventName", ["DeleteFlowLogs", "CreateFlowLogs", "ModifyFlowLogsAttribute"]) && +exists("log.sourceIPAddress") && +equals("log.errorCode", "") && +( + equals("log.eventName", "DeleteFlowLogs") || + (equals("log.eventName", "CreateFlowLogs") && contains("log.requestParameters.deliverLogsStatus", "FAILED")) || + (equals("log.eventName", "ModifyFlowLogsAttribute") && equals("log.requestParameters.deliverLogsStatus", "INACTIVE")) +) +', '2026-03-02 19:28:51.243729', true, true, 'origin', '["lastEvent.log.sourceIPAddress","lastEvent.log.userIdentity.arn","lastEvent.log.awsRegion"]', '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.sourceIPAddress","operator":"filter_term","value":"{{.log.sourceIPAddress}}"},{"field":"log.eventName","operator":"filter_term","value":"DeleteFlowLogs"}],"or":null,"within":"now-24h","count":2}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1215, 'AWS Unusual API Call Patterns', 2, 1, 1, 'Execution', 'T1106 - Execution through API', e'Detects unusual API call patterns in AWS that may indicate unauthorized access or reconnaissance activities. This rule triggers when multiple sensitive API calls are made from the same source IP within a short time window, suggesting potential enumeration or discovery activities by attackers. + +Next Steps: +- Review the source IP address and determine if it\'s authorized for AWS API access +- Check if the user identity associated with these calls is legitimate and expected +- Examine the specific API calls made to understand the reconnaissance pattern and scope +- Review CloudTrail logs for the full session to identify any successful exploitation attempts +- Check if any resources were modified or accessed following the reconnaissance activities +- Verify if the API calls originated from expected geographical locations +- Consider blocking the source IP if unauthorized activity is confirmed +- Implement additional monitoring for the affected account and resources +', '["https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-record-contents.html","https://attack.mitre.org/techniques/T1106/"]', e'exists("log.eventSource") && +exists("log.sourceIPAddress") && +exists("log.eventName") && +( + oneOf("log.eventName", ["DescribeSecurityGroups", "DescribeNetworkAcls", "DescribeVpcs", "DescribeSubnets", "DescribeRouteTables", "DescribeInstances", "DescribeSnapshots", "DescribeVolumes", "DescribeImages", "DescribeKeyPairs", "ListBuckets", "GetBucketAcl", "GetBucketPolicy", "ListAccessKeys", "ListUsers", "ListRoles", "ListPolicies", "GetAccountAuthorizationDetails", "GenerateCredentialReport", "GetCredentialReport"]) +) && +equals("log.errorCode", "") +', '2026-03-02 19:28:52.646279', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.sourceIPAddress","operator":"filter_term","value":"{{.log.sourceIPAddress}}"},{"field":"log.eventName","operator":"filter_match","value":"Describe List Get Generate"}],"or":null,"within":"now-10m","count":50}]', '["lastEvent.log.sourceIPAddress"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1216, 'AWS STS Token Abuse Detection', 3, 3, 1, 'Privilege Escalation', 'T1078.004 - Cloud Accounts', e'Detects potential abuse of AWS STS AssumeRole operations. This rule identifies when roles are assumed from unusual IP addresses or when there are multiple role assumptions in a short time period, which could indicate lateral movement or privilege escalation. The rule specifically flags AssumeRole operations performed without MFA authentication. + +Next Steps: +1. Verify if the source IP address belongs to your organization\'s known IP ranges +2. Check if the assumed role is appropriate for the user or service that initiated the request +3. Review the user identity and verify if this is expected behavior for this account +4. Examine CloudTrail logs for other suspicious activities from the same source IP or user identity +5. If unauthorized, immediately revoke the temporary credentials and review IAM policies +6. Consider implementing MFA requirements for sensitive role assumptions +7. Review and potentially restrict the trust policy for the assumed role +', '["https://docs.aws.amazon.com/IAM/latest/UserGuide/cloudtrail-integration.html","https://attack.mitre.org/techniques/T1078/004/","https://www.elastic.co/security-labs/exploring-aws-sts-assumeroot"]', e'equals("log.eventSource", "sts.amazonaws.com") && +equals("log.eventName", "AssumeRole") && +equals("log.errorCode", "") && +!equals("log.userIdentitySessionContextAttributesMfaAuthenticated", "true") && +!equals("log.userIdentityType", "AWSService") && +!contains("log.userAgent", "aws-sdk") && +!contains("log.userAgent", "Botocore") +', '2026-03-02 19:28:53.957418', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.sourceIPAddress","operator":"filter_term","value":"{{.log.sourceIPAddress}}"}],"or":null,"within":"now-15m","count":20}]', '["lastEvent.log.userIdentityArn","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1217, 'AWS Systems Manager Session Abuse', 3, 3, 2, 'Lateral Movement', 'T1021 - Remote Services', e'Detects suspicious use of AWS Systems Manager (SSM) for remote access including StartSession, SendCommand, and SendSSHPublicKey. Attackers use SSM to establish interactive sessions or execute commands on EC2 instances without requiring direct SSH access or security group changes. + +Next Steps: +1. Verify the IAM principal initiating the SSM session is authorized for remote access +2. Review the target instance(s) and confirm legitimate operational need +3. Check the commands sent via SendCommand for suspicious payloads +4. Review the source IP address for unusual origins +5. Examine the timing of the session for off-hours access +6. If unauthorized, terminate active sessions and review instance for compromise +7. Implement SSM session logging to S3 and CloudWatch for audit trails +8. Restrict SSM access using IAM policies with condition keys +', '["https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html","https://attack.mitre.org/techniques/T1021/"]', e'equals("log.eventSource", "ssm.amazonaws.com") && +oneOf("log.eventName", ["StartSession", "ResumeSession", "SendCommand", "StartAutomationExecution"]) && +equals("log.errorCode", "") +', '2026-03-02 19:28:55.353960', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventSource","operator":"filter_term","value":"ssm.amazonaws.com"}],"or":null,"within":"now-30m","count":5}]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1218, 'AWS Security Group Modifications', 2, 3, 2, 'Defense Evasion', 'T1562.007 - Impair Defenses: Disable or Modify Cloud Firewall', e'Detects modifications to AWS security groups that could weaken network security posture. Monitors for changes that add permissive rules or remove restrictive rules, particularly those allowing unrestricted access (0.0.0.0/0 or ::/0). + +Next Steps: +1. Review the security group change details in CloudTrail logs +2. Verify if the change was authorized and follows security policies +3. Check the user/role that made the modification +4. Assess if the new rules expose sensitive resources +5. If unauthorized, immediately revert the changes +6. Review other recent activities from the same source IP or user +7. Consider implementing preventive controls via AWS Config rules or SCPs +', '["https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-record-contents.html","https://attack.mitre.org/techniques/T1562/007/"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +oneOf("log.eventName", ["AuthorizeSecurityGroupIngress", "AuthorizeSecurityGroupEgress", "RevokeSecurityGroupIngress", "RevokeSecurityGroupEgress", "CreateSecurityGroup", "DeleteSecurityGroup", "ModifySecurityGroupRules"]) && +exists("log.sourceIPAddress") && +equals("log.errorCode", "") && +( + contains("log.requestParameters.ipPermissions.ipProtocol", "-1") || + contains("log.requestParameters.ipPermissions.cidrIp", "0.0.0.0/0") || + contains("log.requestParameters.ipPermissions.ipv6CidrIp", "::/0") || + contains("log.requestParameters.ipRanges.cidrIp", "0.0.0.0/0") || + contains("log.requestParameters.ipv6Ranges.cidrIpv6", "::/0") +) +', '2026-03-02 19:28:56.662706', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.sourceIPAddress","operator":"filter_term","value":"{{.log.sourceIPAddress}}"},{"field":"log.eventSource","operator":"filter_term","value":"ec2.amazonaws.com"}],"or":null,"within":"now-30m","count":3}]', '["lastEvent.log.sourceIPAddress","lastEvent.log.userIdentity.arn"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1219, 'AWS Secrets Manager Suspicious Access Pattern', 3, 2, 1, 'Credential Access', 'T1552.004 - Private Keys', e'Detects unusual access patterns to AWS Secrets Manager that could indicate credential theft or unauthorized access attempts. This rule monitors for multiple GetSecretValue or BatchGetSecretValue operations from the same source within a short time window, which may indicate an attacker attempting to harvest credentials. + +Next Steps: +1. Review the CloudTrail logs for the identified user/role to understand which secrets were accessed +2. Check if the accessing identity has legitimate business need for these secrets +3. Verify if the access pattern matches normal usage for this identity +4. Review the source IP addresses and locations for anomalies +5. If unauthorized, immediately rotate the accessed secrets and review IAM permissions +6. Check for any subsequent API calls using potentially compromised credentials +', '["https://docs.aws.amazon.com/secretsmanager/latest/userguide/monitoring-cloudtrail.html","https://attack.mitre.org/techniques/T1552/004/"]', 'equals("log.eventSource", "secretsmanager.amazonaws.com") && (equals("log.eventName", "GetSecretValue") || equals("log.eventName", "BatchGetSecretValue")) && equals("log.errorCode", "")', '2026-03-02 19:28:58.057840', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventName","operator":"filter_term","value":"GetSecretValue"}],"or":null,"within":"now-10m","count":10},{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventName","operator":"filter_term","value":"BatchGetSecretValue"}],"or":null,"within":"now-10m","count":5}]', '["lastEvent.log.sourceIPAddress","lastEvent.log.userIdentityArn"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1220, 'AWS S3 Bulk Data Exfiltration Detected', 2, 1, 1, 'Data Exfiltration', 'T1530 - Data from Cloud Storage Object', e'Detects bulk GetObject operations from S3 buckets indicating potential data exfiltration. When an attacker gains access to AWS credentials, they may attempt to download large amounts of data from S3 buckets in a short period. + +Next Steps: +1. Identify the S3 bucket(s) targeted and classify the data sensitivity +2. Review the IAM principal performing the downloads and verify authorization +3. Check the source IP address for known threat indicators +4. Examine the volume and types of objects downloaded +5. Verify if this matches any legitimate data processing or backup patterns +6. If unauthorized, revoke the credentials used and block the source IP +7. Enable S3 server access logging for affected buckets +8. Review S3 bucket policies and tighten access controls +', '["https://docs.aws.amazon.com/AmazonS3/latest/userguide/cloudtrail-logging.html","https://attack.mitre.org/techniques/T1530/"]', e'equals("log.eventSource", "s3.amazonaws.com") && +equals("log.eventName", "GetObject") && +equals("log.errorCode", "") +', '2026-03-02 19:28:59.249633', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventName","operator":"filter_term","value":"GetObject"}],"or":null,"within":"now-15m","count":100}]', '["adversary.user","lastEvent.log.requestParameters.bucketName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1221, 'AWS Route 53 DNS Hijacking Attempt', 3, 3, 3, 'Initial Access', 'T1584.002 - Compromise Infrastructure: DNS Server', e'Detects potential DNS hijacking attempts through unauthorized changes to Route 53 DNS records. This rule monitors for ChangeResourceRecordSets operations that could indicate an attacker modifying DNS entries to redirect traffic. + +Next Steps: +1. Review the CloudTrail logs to identify what DNS records were modified and the specific changes made +2. Verify if the user identity making the changes is authorized to modify Route 53 records +3. Check if the source IP address is from a known and trusted location +4. Review the modified DNS records to ensure they point to legitimate resources +5. If unauthorized, immediately revert the DNS changes and rotate the compromised credentials +6. Enable MFA for all users with Route 53 permissions +7. Consider implementing AWS Config rules to monitor Route 53 changes +8. Review and restrict IAM policies for Route 53 access +', '["https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/logging-using-cloudtrail.html","https://attack.mitre.org/techniques/T1584/002/"]', 'equals("log.eventSource", "route53.amazonaws.com") && equals("log.eventName", "ChangeResourceRecordSets") && equals("log.errorCode", "")', '2026-03-02 19:29:00.592800', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventName","operator":"filter_term","value":"ChangeResourceRecordSets"}],"or":null,"within":"now-30m","count":10}]', '["lastEvent.log.sourceIPAddress","lastEvent.log.userIdentityArn"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1222, 'AWS Mass Resource Deletion', 1, 3, 3, 'Impact', 'T1485 - Data Destruction', e'Detects mass deletion of AWS resources which could indicate destructive attack or insider threat. Monitors for multiple delete operations across various AWS services within a 10-minute window. + +Next Steps: +1. Immediately identify the user/role performing the deletions through the userIdentity.arn field +2. Review CloudTrail logs to determine the scope and specific resources being deleted +3. Contact the user to verify if these actions are authorized +4. If unauthorized, immediately revoke the user\'s AWS credentials and permissions +5. Enable MFA delete protection on critical resources if not already enabled +6. Consider implementing SCPs (Service Control Policies) to prevent mass deletions +7. Review and restore deleted resources from backups if necessary +8. Document the incident and update IAM policies to prevent future occurrences +', '["https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-concepts.html","https://attack.mitre.org/techniques/T1485/"]', '(contains("log.eventName", "Delete") || contains("log.eventName", "Terminate") || contains("log.eventName", "Remove")) && equals("log.errorCode", "") && !equals("log.eventSource", "s3.amazonaws.com")', '2026-03-02 19:29:01.863432', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentity.arn","operator":"filter_term","value":"{{.log.userIdentity.arn}}"}],"or":null,"within":"now-10m","count":15}]', '["lastEvent.log.sourceIPAddress","lastEvent.log.userIdentity.arn"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1223, 'Lambda Function Privilege Escalation', 3, 3, 2, 'Privilege Escalation', 'T1548 - Abuse Elevation Control Mechanism', e'Detects potential privilege escalation through Lambda functions when IAM policies are attached to roles or users that can be exploited. This may indicate an attacker attempting to escalate privileges by attaching administrative policies to Lambda execution roles. + +Next Steps: +1. Verify if the policy attachment was authorized and follows change management procedures +2. Review the attached policy permissions, especially if AdministratorAccess or IAMFullAccess policies were attached +3. Check the Lambda function\'s code and recent invocations for suspicious activity +4. Review CloudTrail logs for other IAM changes by the same user/role +5. Validate if the Lambda function legitimately requires the elevated permissions +6. Consider revoking the policy attachment if unauthorized and investigate the source of the change +7. Check for any unusual Lambda function executions following the policy attachment +8. Review the user/role history for previous privilege escalation attempts +', '["https://bishopfox.com/blog/privilege-escalation-in-aws","https://attack.mitre.org/techniques/T1548/"]', e'equals("log.eventSource", "iam.amazonaws.com") && +oneOf("log.eventName", ["AttachRolePolicy", "AttachUserPolicy"]) && +equals("log.errorCode", "") && +(contains("log.requestParameters.policyArn", "AdministratorAccess") || contains("log.requestParameters.policyArn", "IAMFullAccess")) +', '2026-03-02 19:29:03.262145', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentity.arn","operator":"filter_term","value":"{{.log.userIdentity.arn}}"},{"field":"log.eventName","operator":"filter_term","value":"AttachRolePolicy"}],"or":null,"within":"now-1h","count":2}]', '["lastEvent.log.requestParameters.roleArn","lastEvent.log.userIdentity.arn"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1224, 'AWS IAM Privilege Escalation Path Detection', 3, 3, 2, 'Privilege Escalation', 'T1098 - Account Manipulation', e'Detects IAM actions commonly chained for privilege escalation including PassRole, CreatePolicyVersion, AttachUserPolicy, and AssumeRole. Attackers exploit these API calls to elevate their permissions within an AWS environment by creating new policy versions with elevated privileges or assuming roles with higher access. + +Next Steps: +1. Review the IAM actions performed and determine if they constitute a privilege escalation chain +2. Check the IAM principal and verify their authorized permission level +3. Examine the policy document or role being targeted for overly permissive access +4. Verify through change management if these IAM changes were approved +5. If unauthorized, immediately revert the IAM changes and rotate credentials +6. Review IAM Access Analyzer findings for excessive permissions +7. Implement permission boundaries to limit privilege escalation paths +8. Enable AWS CloudTrail Insights for anomaly detection on IAM operations +', '["https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage.html","https://attack.mitre.org/techniques/T1098/","https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/"]', e'equals("log.eventSource", "iam.amazonaws.com") && +oneOf("log.eventName", ["CreatePolicyVersion", "SetDefaultPolicyVersion", "AttachUserPolicy", "AttachGroupPolicy", "AttachRolePolicy", "PutUserPolicy", "PutGroupPolicy", "PutRolePolicy", "AddUserToGroup"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:04.569454', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventSource","operator":"filter_term","value":"iam.amazonaws.com"}],"or":null,"within":"now-30m","count":3}]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1225, 'AWS IAM Backdoor Creation Attempts', 3, 3, 2, 'Privilege Escalation', 'T1136.003 - Create Account: Cloud Account', e'Detects potential IAM backdoor creation attempts through suspicious IAM user creation, access key generation, or policy attachment activities that could provide persistent access. + +Next Steps: +1. Review the source IP address and user identity performing these actions +2. Verify if the IAM user creation and policy attachments are authorized +3. Check CloudTrail logs for the complete sequence of IAM actions from this source +4. Review the policies attached to determine if they grant excessive permissions +5. If unauthorized, immediately disable the created user and revoke access keys +6. Investigate other activities from the same source IP or user identity +', '["https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage.html","https://attack.mitre.org/techniques/T1136/003/"]', 'equals("log.eventSource", "iam.amazonaws.com") && oneOf("log.eventName", ["CreateUser", "CreateAccessKey", "AttachUserPolicy", "PutUserPolicy", "CreateLoginProfile"]) && equals("log.errorCode", "")', '2026-03-02 19:29:05.887758', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.sourceIPAddress","operator":"filter_term","value":"{{.log.sourceIPAddress}}"},{"field":"log.eventSource","operator":"filter_term","value":"iam.amazonaws.com"}],"or":null,"within":"now-30m","count":3}]', '["lastEvent.log.sourceIPAddress","lastEvent.log.userIdentity.arn"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1226, 'AWS Cross-Account Access Anomalies', 3, 3, 1, 'Unauthorized Access', 'T1550.001 - Use Alternate Authentication Material: Application Access Token', e'Detects anomalous cross-account access patterns in AWS that may indicate account compromise or privilege escalation. Monitors for AssumeRole activities across different accounts where the assumed role ARN does not match the originating account ID, potentially indicating unauthorized cross-account access. + +Next Steps: +1. Verify if the cross-account access was authorized by checking AWS IAM policies and trust relationships +2. Review the source IP address to determine if it matches known corporate IP ranges or expected locations +3. Check the assumed role permissions to understand what access was granted +4. Look for any subsequent API calls made using the assumed role credentials +5. Contact the owner of the originating account to verify if the activity was legitimate +6. If unauthorized, immediately revoke the assumed role session and review all IAM trust policies +', '["https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html","https://attack.mitre.org/techniques/T1550/001/"]', e'equals("log.eventSource", "sts.amazonaws.com") && +equals("log.eventName", "AssumeRole") && +exists("log.userIdentityAccountId") && +exists("log.responseElementsAssumedRoleUserArn") && +exists("log.sourceIPAddress") && +equals("log.errorCode", "") && +!contains(safe(log.responseElementsAssumedRoleUserArn, ""), safe(log.userIdentityAccountId, "")) +', '2026-03-02 19:29:07.145599', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.sourceIPAddress","operator":"filter_term","value":"{{.log.sourceIPAddress}}"},{"field":"log.eventName","operator":"filter_term","value":"AssumeRole"}],"or":null,"within":"now-15m","count":15}]', '["lastEvent.log.responseElementsAssumedRoleUserArn","lastEvent.log.sourceIPAddress","lastEvent.log.userIdentityAccountId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1227, 'AWS Console Login Impossible Travel Detection', 3, 2, 1, 'Credential Access', 'T1078.004 - Valid Accounts: Cloud Accounts', e'Detects AWS Console logins from different geographic locations within a short time window, indicating potential credential compromise. This correlation identifies when the same user authenticates from different countries within 30 minutes, which would be physically impossible. + +Next Steps: +1. Review the login locations and IP addresses for the affected user +2. Check if the user employs a VPN or proxy service that could explain different geolocations +3. Verify with the user whether both login sessions are legitimate +4. Review the actions performed in each session for suspicious activity +5. If unauthorized, immediately disable the user account and rotate credentials +6. Enable MFA if not already configured for the affected account +7. Review CloudTrail logs for any API calls made during the suspicious session +8. Check for concurrent sessions from the compromised account +', '["https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html","https://attack.mitre.org/techniques/T1078/004/"]', e'equals("log.eventName", "ConsoleLogin") && +equals("log.responseElements.ConsoleLogin", "Success") && +exists("origin.geolocation.countryCode") +', '2026-03-02 19:29:08.633139', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventName","operator":"filter_term","value":"ConsoleLogin"},{"field":"origin.geolocation.countryCode","operator":"must_not_term","value":"{{.origin.geolocation.countryCode}}"}],"or":null,"within":"now-30m","count":1}]', '["adversary.user","adversary.geolocation.countryCode"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1228, 'AWS CloudFormation Stack Deletion', 1, 3, 3, 'Impact', 'T1485 - Data Destruction', e'Detects deletion of CloudFormation stacks which could indicate destructive actions by an attacker or unauthorized infrastructure changes. This rule monitors for DeleteStack operations that could result in loss of critical infrastructure. Multiple stack deletions from the same user within a short time frame may indicate malicious activity. + +Next Steps: +1. Verify if the stack deletion was authorized and part of planned maintenance +2. Check the user identity who performed the deletion and validate if they should have this permission +3. Review CloudTrail logs for other destructive actions by the same user +4. Examine what resources were contained in the deleted stack +5. If unauthorized, immediately revoke the user\'s permissions and investigate for other compromise indicators +6. Consider enabling stack termination protection on critical stacks +7. Review and implement least privilege access policies for CloudFormation operations +', '["https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/logging-cloudformation-api-calls.html","https://attack.mitre.org/techniques/T1485/"]', 'equals("log.eventSource", "cloudformation.amazonaws.com") && equals("log.eventName", "DeleteStack") && equals("log.errorCode", "")', '2026-03-02 19:29:09.950883', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventSource","operator":"filter_term","value":"cloudformation.amazonaws.com"}],"or":null,"within":"now-30m","count":5}]', '["lastEvent.log.userIdentityAccountId","lastEvent.log.userIdentityArn"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1229, 'AWS SSO Suspicious Activities', 3, 3, 1, 'Credential Access, Defense Evasion, Persistence', 'T1556.006 - Modify Authentication Process: Multi-Factor Authentication', e'Detects suspicious AWS SSO activities including multiple failed login attempts, unusual permission set assignments, or SSO configuration changes that could indicate an attempt to compromise single sign-on authentication mechanisms. + +Next Steps: +1. Review the source IP address and user identity associated with the suspicious SSO activity +2. Check if the user\'s credentials may have been compromised +3. Verify if the permission set changes or role assumptions were authorized +4. Review CloudTrail logs for other suspicious activities from the same IP or user +5. If unauthorized, immediately revoke the affected SSO sessions and reset user credentials +6. Consider implementing additional MFA requirements for SSO administrative actions +', '["https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html","https://attack.mitre.org/techniques/T1556/006/"]', e'equals("log.eventSource", "sso.amazonaws.com") && +( + oneOf("log.eventName", ["AssumeRoleWithSAML", "CreatePermissionSet", "AttachManagedPolicyToPermissionSet", "DeletePermissionSet", "PutInlinePolicyToPermissionSet"]) || + exists("log.errorCode") +) +', '2026-03-02 19:29:11.211276', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.sourceIPAddress","operator":"filter_term","value":"{{.log.sourceIPAddress}}"},{"field":"log.eventSource","operator":"filter_term","value":"sso.amazonaws.com"}],"or":null,"within":"now-30m","count":10}]', '["lastEvent.log.sourceIPAddress","lastEvent.log.userIdentity.principalId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1230, 'AWS SSM SendCommand Remote Execution', 2, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', e'Detects AWS Systems Manager SendCommand API calls which allow remote command execution on EC2 instances. Attackers with sufficient IAM permissions abuse SSM to execute commands across multiple instances without SSH/RDP access. + +Next Steps: +1. Review the command document and parameters sent to the instances +2. Verify the user identity has legitimate need for remote command execution +3. Check the target instances and whether they should be managed via SSM +4. Review the command output for suspicious activity +5. Correlate with instance-level logs for the executed commands +6. If unauthorized, cancel pending commands and investigate the user +', '["https://docs.aws.amazon.com/systems-manager/latest/userguide/run-command.html","https://attack.mitre.org/techniques/T1059/"]', e'equals("log.eventSource", "ssm.amazonaws.com") && +oneOf("log.eventName", ["SendCommand", "StartSession"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:12.747199', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventSource","operator":"filter_term","value":"ssm.amazonaws.com"}],"or":null,"within":"now-30m","count":5}]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1231, 'AWS SecurityHub Finding Evasion', 2, 3, 2, 'Defense Evasion', 'T1562 - Impair Defenses', e'Detects attempts to suppress or manipulate AWS SecurityHub findings through BatchUpdateFindings, DeleteInsight, or UpdateFindings operations. Attackers use these to hide evidence of their activities from security monitoring. + +Next Steps: +1. Review what findings were modified or suppressed and their severity +2. Check the user identity performing these actions for legitimacy +3. Investigate whether this was part of legitimate finding management workflow +4. Review the original findings that were suppressed for security relevance +5. Check for other defense evasion activities from the same user or IP +6. Restore suppressed findings if the action was unauthorized +7. Review SecurityHub aggregation and automation rules for tampering +', '["https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-findings.html","https://attack.mitre.org/techniques/T1562/"]', e'equals("log.eventSource", "securityhub.amazonaws.com") && +oneOf("log.eventName", ["BatchUpdateFindings", "DeleteInsight", "UpdateFindings", "DeleteActionTarget"]) && +equals("log.errorCode", "") && +!equals("log.userIdentityType", "AWSService") +', '2026-03-02 19:29:13.879754', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityArn","operator":"filter_term","value":"{{.log.userIdentityArn}}"},{"field":"log.eventSource","operator":"filter_term","value":"securityhub.amazonaws.com"}],"or":null,"within":"now-30m","count":5}]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1232, 'AWS Golden SAML Attack Detected', 3, 3, 3, 'Credential Access', 'T1556 - Modify Authentication Process', e'Detects potential Golden SAML attacks by monitoring for AssumeRoleWithSAML calls combined with UpdateSAMLProvider or CreateSAMLProvider operations. Golden SAML was the technique used in the SolarWinds attack, allowing attackers to forge SAML tokens and assume any role in the AWS account. + +Next Steps: +1. Immediately investigate the SAML provider configuration changes and the AssumeRoleWithSAML calls +2. Verify the SAML provider metadata document has not been tampered with +3. Check if the identity provider federation trust is legitimate +4. Review the assumed role ARN and session name for suspicious values +5. Correlate with identity provider logs to verify the SAML assertion was legitimately issued +6. Check for lateral movement or privilege escalation after the role assumption +7. If unauthorized, rotate the SAML provider trust and revoke all active sessions +', '["https://www.cyberark.com/resources/threat-research-blog/golden-saml-newly-discovered-attack-technique-forges-authentication-to-cloud-apps","https://attack.mitre.org/techniques/T1556/"]', e'equals("log.eventSource", "sts.amazonaws.com") && +equals("log.eventName", "AssumeRoleWithSAML") && +equals("log.errorCode", "") +', '2026-03-02 19:29:15.012526', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.userIdentityAccountId","operator":"filter_term","value":"{{.log.userIdentityAccountId}}"}],"or":[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.eventName","operator":"filter_term","value":"UpdateSAMLProvider"}],"or":null,"within":"now-24h","count":1},{"indexPattern":"v11-log-aws-*","with":[{"field":"log.eventName","operator":"filter_term","value":"CreateSAMLProvider"}],"or":null,"within":"now-24h","count":1}],"within":"now-24h","count":1}]', '["adversary.user","lastEvent.log.requestParametersRoleArn"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1233, 'AWS ECS Task Credential Endpoint Query', 3, 2, 1, 'Credential Access', 'T1552.005 - Unsecured Credentials: Cloud Instance Metadata API', e'Detects queries to the ECS task credential endpoint that may indicate container credential theft. Attackers who compromise a container can query the credential endpoint to steal IAM role credentials attached to the ECS task. + +Next Steps: +1. Identify the ECS task and cluster involved +2. Check if the API calls were from expected task processes +3. Review the IAM role attached to the task for excessive permissions +4. Check for lateral movement using the stolen credentials +5. Investigate the container image and running processes for compromise +6. If unauthorized, rotate the task role credentials and investigate the container +', '["https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html","https://attack.mitre.org/techniques/T1552/005/"]', e'equals("log.eventSource", "ecs.amazonaws.com") && +oneOf("log.eventName", ["DescribeTaskDefinition", "RunTask", "StartTask"]) && +equals("log.errorCode", "") && +equals("log.userIdentityType", "AssumedRole") && +contains("log.userIdentityArn", ":assumed-role/") +', '2026-03-02 19:29:16.411056', true, true, 'origin', null, '[{"indexPattern":"v11-log-aws-*","with":[{"field":"log.sourceIPAddress","operator":"filter_term","value":"{{.log.sourceIPAddress}}"},{"field":"log.eventSource","operator":"filter_term","value":"ecs.amazonaws.com"}],"or":null,"within":"now-30m","count":5}]', '["adversary.user","lastEvent.log.sourceIPAddress"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1234, 'AWS WAF and Shield Rule Modifications', 2, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects modifications or deletions of AWS WAF web ACLs and rules which could be used to remove web application protections before launching an attack. Attackers may disable WAF rules to allow malicious traffic or remove rate limiting protections. + +Next Steps: +1. Verify if the WAF modification was authorized through change management +2. Review the specific rules or web ACL that were modified or deleted +3. Check the IAM principal and source IP performing the change +4. Assess whether any web applications are now exposed without WAF protection +5. Review recent web application logs for attack attempts following the WAF change +6. If unauthorized, restore the WAF configuration from backup or AWS Config +7. Implement IAM policies to restrict WAF modification access +8. Enable AWS Config rules to monitor WAF configuration compliance +', '["https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html","https://attack.mitre.org/techniques/T1562/001/"]', e'(equals("log.eventSource", "wafv2.amazonaws.com") || equals("log.eventSource", "waf.amazonaws.com")) && +oneOf("log.eventName", ["DeleteWebACL", "DeleteRule", "DeleteRuleGroup", "UpdateWebACL", "DeleteIPSet", "DeleteRegexPatternSet", "DisassociateWebACL"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:17.677995', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1235, 'AWS S3 Bucket Public Exposure', 3, 2, 1, 'Collection', 'T1530 - Data from Cloud Storage Object', e'Detects S3 bucket configuration changes that expose buckets to public access, including ACL modifications and public access block removal. This can lead to unauthorized data exposure and potential data breach. + +Next Steps: +1. Immediately review the affected S3 bucket permissions and revert unauthorized changes +2. Check CloudTrail logs for the source of the configuration change +3. Verify if the change was authorized by examining change management tickets +4. Review bucket contents to determine sensitivity of potentially exposed data +5. Enable S3 Block Public Access at the account level to prevent future exposures +6. Implement bucket policies that restrict public access +7. Enable S3 access logging for the affected bucket +8. Consider implementing AWS Config rules to monitor S3 bucket permissions +', '["https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html","https://attack.mitre.org/techniques/T1530/"]', e'equals("log.eventSource", "s3.amazonaws.com") && +(equals("log.eventName", "PutBucketAcl") || + equals("log.eventName", "PutBucketPublicAccessBlock") || + equals("log.eventName", "DeleteBucketPublicAccessBlock") || + equals("log.eventName", "PutObjectAcl")) && +equals("log.errorCode", "") && +(oneOf("log.requestParameters.x-amz-acl", ["public-read", "public-read-write"]) || + contains("log.requestParameters.acl", "AllUsers") || + equals("log.eventName", "DeleteBucketPublicAccessBlock")) +', '2026-03-02 19:29:19.074343', true, true, 'origin', null, '[]', '["lastEvent.log.requestParameters.bucketName","lastEvent.log.userIdentity.accessKeyId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1236, 'AWS Root Account Usage Without MFA', 3, 3, 2, 'Defense Evasion, Persistence, Privilege Escalation, Initial Access', 'T1078.004 - Valid Accounts: Cloud Accounts', e'Detects usage of AWS root account without Multi-Factor Authentication (MFA). Root account usage should be avoided for daily operations and must always use MFA when necessary. This is a critical security violation as the root account has unrestricted access to all AWS resources. + +Next Steps: +1. Immediately verify if this root account activity is authorized +2. Check the source IP address and user agent for suspicious patterns +3. Review all actions performed by the root account during this session +4. Enable MFA for the root account if not already enabled +5. Investigate why MFA was not used (possible bypass or configuration issue) +6. Consider implementing SCPs to restrict root account usage +7. Audit all recent root account activities for potential compromise +8. Review AWS CloudTrail logs for complete session details +9. Check for any privilege escalation attempts or unusual API calls +10. Implement additional monitoring for root account activities +', '["https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html","https://attack.mitre.org/techniques/T1078/004/"]', e'equals("log.userIdentityType", "Root") && +!equals("log.userIdentitySessionContextAttributesMfaAuthenticated", "true") && +equals("log.errorCode", "") +', '2026-03-02 19:29:20.389256', true, true, 'origin', null, '[]', '["lastEvent.log.sourceIPAddress","lastEvent.log.userIdentityAccountId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1237, 'AWS RDS Snapshot Sharing for Data Exfiltration', 3, 2, 1, 'Data Exfiltration', 'T1537 - Transfer Data to Cloud Account', e'Detects RDS snapshot sharing modifications that could be used for data exfiltration. Attackers may share database snapshots with external AWS accounts to exfiltrate sensitive data, or make snapshots public to download them from an attacker-controlled account. + +Next Steps: +1. Identify which RDS snapshot was shared and its data sensitivity +2. Verify the target AWS account ID the snapshot was shared with +3. Check if the snapshot was made public (shared with all AWS accounts) +4. Verify the change was authorized through data governance procedures +5. If unauthorized, immediately remove the snapshot sharing and rotate database credentials +6. Review the database contents for sensitive or regulated data +7. Check for other snapshot operations by the same principal +8. Implement preventive SCPs to restrict snapshot sharing +', '["https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ShareSnapshot.html","https://attack.mitre.org/techniques/T1537/"]', e'equals("log.eventSource", "rds.amazonaws.com") && +oneOf("log.eventName", ["ModifyDBSnapshotAttribute", "ModifyDBClusterSnapshotAttribute"]) && +equals("log.errorCode", "") && +(contains("log.requestParameters", "attributeName\\":\\"restore") || + contains("log.requestParameters", "all")) +', '2026-03-02 19:29:21.659417', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.requestParameters.dBSnapshotIdentifier"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1238, 'RDS Security Group Changes', 3, 2, 2, 'Defense Evasion', 'T1562.007 - Impair Defenses: Disable or Modify Cloud Firewall', e'Detects modifications to RDS database security groups that could expose databases to unauthorized access. This includes adding new ingress rules or modifying existing security group configurations that may compromise database security. + +Next Steps: +1. Review the security group changes to determine if they were authorized +2. Check if the changes expose RDS instances to public internet (0.0.0.0/0) +3. Verify the identity of the user who made the changes and confirm authorization +4. Review CloudTrail logs for additional suspicious activities by the same user +5. If unauthorized, immediately revert the security group changes +6. Consider implementing preventive controls using AWS Config rules or SCPs +7. Document the incident and update change management procedures if needed +', '["https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/logging-using-cloudtrail.html","https://attack.mitre.org/techniques/T1562/007/"]', e'equals("log.eventSource", "rds.amazonaws.com") && +oneOf("log.eventName", ["AuthorizeDBSecurityGroupIngress", "CreateDBSecurityGroup", "DeleteDBSecurityGroup", "RevokeDBSecurityGroupIngress"]) && +equals("log.errorCode", "") && +contains("log.requestParameters", "0.0.0.0/0") +', '2026-03-02 19:29:23.141256', true, true, 'origin', null, '[]', '["lastEvent.log.eventName","lastEvent.log.userIdentityArn"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1239, 'AWS Lambda Function URL Backdoor Creation', 3, 3, 1, 'Persistence', 'T1059 - Command and Scripting Interpreter', e'Detects creation of Lambda function URLs which provide direct HTTPS endpoints to Lambda functions. Attackers may create function URL configurations as backdoor endpoints for persistent access, command execution, or data exfiltration without requiring API Gateway. + +Next Steps: +1. Review the Lambda function code for malicious payloads or backdoor functionality +2. Check the function URL authentication type (AWS_IAM vs NONE) +3. Verify the IAM principal creating the function URL has authorization +4. Review the Lambda function\'s execution role for excessive permissions +5. Check if CORS settings allow cross-origin requests from unauthorized domains +6. If unauthorized, delete the function URL configuration and the Lambda function +7. Review CloudTrail logs for the function\'s invocation history +8. Implement SCPs to restrict Lambda function URL creation to authorized roles +', '["https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html","https://attack.mitre.org/techniques/T1059/"]', e'equals("log.eventSource", "lambda.amazonaws.com") && +oneOf("log.eventName", ["CreateFunctionUrlConfig", "UpdateFunctionUrlConfig"]) && +equals("log.errorCode", "") && +contains("log.requestParameters", "\\"authType\\":\\"NONE\\"") +', '2026-03-02 19:29:24.488238', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.requestParameters.functionName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1240, 'AWS GuardDuty High-Severity Finding', 3, 3, 2, 'Defense Evasion, Persistence, Privilege Escalation, Initial Access', 'T1078 - Valid Accounts', e'Detects high-severity findings from AWS GuardDuty indicating potential security threats such as malicious activity, unauthorized access, or compromised instances. GuardDuty analyzes CloudTrail events, VPC Flow Logs, and DNS logs to identify threats. + +Next Steps: +1. Review the specific GuardDuty finding details including the threat type and affected resources +2. Check the affected AWS account and region for any unauthorized changes +3. Investigate the source IP addresses and user activities associated with the finding +4. Review CloudTrail logs for suspicious API calls around the time of the finding +5. If compromise is confirmed, rotate credentials and review IAM permissions +6. Enable AWS CloudTrail logging if not already enabled +7. Consider implementing AWS Security Hub for centralized security findings +', '["https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings.html","https://attack.mitre.org/techniques/T1078/"]', 'equals("log.eventSource", "guardduty.amazonaws.com") && greaterOrEqual("log.severity", 7)', '2026-03-02 19:29:25.890455', true, true, 'origin', null, '[]', '["lastEvent.log.accountId","lastEvent.log.region","lastEvent.log.type"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1241, 'AWS ECS/EKS Container Abuse Detection', 3, 3, 2, 'Execution', 'T1610 - Deploy Container', e'Detects suspicious container operations in AWS ECS and EKS including registration of task definitions with privileged mode, host network access, or sensitive volume mounts. Attackers may deploy malicious containers to execute code, escalate privileges, or access host resources. + +Next Steps: +1. Review the task definition or pod specification for privileged settings +2. Check if the container image is from a trusted registry +3. Verify the IAM principal registering the task has authorization +4. Examine container environment variables for embedded credentials +5. Review network mode settings for host network access +6. Check volume mounts for access to sensitive host paths +7. If unauthorized, deregister the task definition and terminate running tasks +8. Implement OPA/Gatekeeper policies to prevent privileged containers +', '["https://docs.aws.amazon.com/AmazonECS/latest/developerguide/security.html","https://attack.mitre.org/techniques/T1610/"]', e'(equals("log.eventSource", "ecs.amazonaws.com") && + equals("log.eventName", "RegisterTaskDefinition") && + equals("log.errorCode", "") && + (contains("log.requestParameters", "\\"privileged\\":true") || + contains("log.requestParameters", "\\"networkMode\\":\\"host\\"") || + contains("log.requestParameters", "\\"pidMode\\":\\"host\\"") || + contains("log.requestParameters", "/var/run/docker.sock") || + contains("log.requestParameters", "/etc/shadow") || + contains("log.requestParameters", "/root"))) +', '2026-03-02 19:29:27.249861', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1244, 'EBS Snapshot Sharing Violations', 3, 2, 1, 'Exfiltration', 'T1537 - Transfer Data to Cloud Account', e'Detects potential data exfiltration through EBS snapshot sharing. Monitors for CreateSnapshot followed by ModifySnapshotAttribute events that could indicate an attacker creating snapshots and sharing them with external accounts or making them public. + +Next Steps: +1. Identify the affected snapshot ID and volume ID from the event details +2. Review the account or group the snapshot was shared with in requestParameters +3. Verify if the sharing was authorized by checking with the account owner +4. If unauthorized, immediately revoke the snapshot sharing permissions +5. Review CloudTrail logs for other suspicious activities by the same user/role +6. Check if the source volume contains sensitive data +7. Consider implementing preventive controls using IAM policies or SCPs to restrict snapshot sharing +', '["https://securitylabs.datadoghq.com/cloud-security-atlas/attacks/sharing-ebs-snapshot/","https://attack.mitre.org/techniques/T1537/"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +equals("log.eventName", "ModifySnapshotAttribute") && +equals("log.errorCode", "") && +( + contains("log.requestParameters", "CREATE_VOLUME_PERMISSION") || + contains("log.requestParameters", "createVolumePermission") || + (contains("log.requestParameters", "group") && contains("log.requestParameters", "all")) +) +', '2026-03-02 19:29:31.400560', true, true, 'origin', null, '[]', '["lastEvent.log.responseElements.snapshotId","lastEvent.log.userIdentity.accountId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1245, 'AWS CloudTrail Logging Disabled', 3, 3, 2, 'Defense Evasion', 'T1562.008 - Impair Defenses: Disable Cloud Logs', e'Detects attempts to disable CloudTrail logging which could be used to hide malicious activities and evade detection. CloudTrail provides audit logs of all AWS API calls and is critical for security monitoring and compliance. + +Next Steps: +1. Immediately verify if the CloudTrail modification was authorized +2. Check the user identity ({{log.userIdentityArn}}) and source IP ({{log.sourceIPAddress}}) for legitimacy +3. Review CloudTrail configuration to ensure logging is re-enabled for all regions +4. Check for any suspicious API calls made around the time of this event +5. If unauthorized, investigate what activities may have occurred while logging was disabled +6. Consider implementing SCPs or IAM policies to prevent CloudTrail modifications +7. Verify if the trail was configured for multi-region logging +8. Check if compliance requirements have been violated +9. Review AWS Config rules for CloudTrail compliance +10. Enable CloudTrail Insights for anomaly detection +', '["https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-concepts.html","https://attack.mitre.org/techniques/T1562/008/"]', e'equals("log.eventSource", "cloudtrail.amazonaws.com") && +oneOf("log.eventName", ["StopLogging", "DeleteTrail"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:32.704760', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.requestParameters.name"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1246, 'AWS CloudTrail Event Selector Manipulation', 3, 3, 2, 'Defense Evasion', 'T1562.008 - Impair Defenses: Disable Cloud Logs', e'Detects manipulation of CloudTrail event selectors which can be used to selectively exclude data events from logging. Attackers may use PutEventSelectors to disable logging of S3, Lambda, or DynamoDB data events to hide their data exfiltration or manipulation activities. + +Next Steps: +1. Immediately review the CloudTrail event selector configuration changes +2. Verify if the change was authorized through change management +3. Check which data event types were excluded from logging +4. Review the IAM principal ({{log.userIdentityArn}}) making the change +5. Restore event selectors to include all critical data event types +6. Review recent activities that may have been hidden by the selector change +7. Implement SCPs to prevent modification of CloudTrail event selectors +8. Enable AWS Config rules to monitor CloudTrail configuration compliance +', '["https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html","https://attack.mitre.org/techniques/T1562/008/"]', e'equals("log.eventSource", "cloudtrail.amazonaws.com") && +oneOf("log.eventName", ["PutEventSelectors", "PutInsightSelectors"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:34.072331', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.requestParameters.trailName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1247, 'AWS TruffleHog Credential Scanning Detected', 3, 1, 0, 'Credential Access', 'T1552 - Unsecured Credentials', e'Detects TruffleHog credential scanning tool activity in AWS CloudTrail logs identified by its characteristic user agent string. TruffleHog is used to scan for exposed secrets and credentials in AWS environments. + +Next Steps: +1. Identify the source of the TruffleHog scan (user, role, source IP) +2. Determine if this is an authorized security assessment +3. Review what resources were accessed during the scan +4. If unauthorized, revoke the credentials used and investigate +5. Check for any credentials that may have been discovered and exfiltrated +6. Review IAM policies to limit enumeration permissions +', '["https://github.com/trufflesecurity/trufflehog","https://attack.mitre.org/techniques/T1552/"]', e'contains("log.userAgent", "trufflehog") || +contains("log.userAgent", "TruffleHog") +', '2026-03-02 19:29:35.286155', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.sourceIPAddress"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1248, 'AWS SSO Identity Provider Configuration Changed', 3, 3, 2, 'Credential Access', 'T1556 - Modify Authentication Process', e'Detects changes to AWS SSO identity provider configuration including directory association and external IdP configuration changes. Modifying the identity provider can enable impersonation of any user in the organization. + +Next Steps: +1. Verify the IdP configuration change was part of an approved change request +2. Check the user identity performing the change for proper authorization +3. Review the new IdP configuration for legitimacy +4. Check for suspicious sign-ins following the IdP change +5. Validate the external IdP settings against the organization\'s known configuration +6. If unauthorized, revert the IdP configuration immediately +7. Audit all SSO-based sessions created after the change +', '["https://docs.aws.amazon.com/singlesignon/latest/userguide/manage-your-identity-source.html","https://attack.mitre.org/techniques/T1556/"]', e'equals("log.eventSource", "sso.amazonaws.com") && +oneOf("log.eventName", ["AssociateDirectory", "DisassociateDirectory", "EnableExternalIdPConfiguration", "DisableExternalIdPConfiguration", "UpdateExternalIdPConfiguration"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:36.728046', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1249, 'AWS EC2 Snapshot Shared with External Account', 3, 2, 1, 'Exfiltration', 'T1537 - Transfer Data to Cloud Account', e'Detects ModifySnapshotAttribute API calls that share EBS snapshots with external AWS accounts. Attackers exfiltrate data by sharing snapshots containing sensitive data with accounts they control. + +Next Steps: +1. Identify the shared snapshot and its contents +2. Verify the target account ID is a known and authorized account +3. Check the user identity and source IP for legitimacy +4. Review the snapshot\'s source volume for sensitive data +5. If unauthorized, immediately remove the sharing permission +6. Check for other snapshots shared around the same time +7. Review IAM policies to restrict snapshot sharing +', '["https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-modifying-snapshot-permissions.html","https://attack.mitre.org/techniques/T1537/"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +equals("log.eventName", "ModifySnapshotAttribute") && +equals("log.errorCode", "") && +contains("log.requestParameters", "createVolumePermission") +', '2026-03-02 19:29:38.092147', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.requestParameters.snapshotId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1250, 'AWS S3 Bucket Versioning Suspended', 2, 3, 3, 'Impact', 'T1490 - Inhibit System Recovery', e'Detects when S3 bucket versioning is suspended via PutBucketVersioning. Disabling versioning is a cloud ransomware preparation technique that prevents recovery of overwritten or deleted objects from version history. + +Next Steps: +1. Verify if the versioning suspension was authorized and part of a change request +2. Identify the affected S3 bucket and its contents sensitivity +3. Check for subsequent DeleteObject or PutObject calls that would overwrite data +4. Review the user identity and source IP for legitimacy +5. Re-enable versioning immediately if unauthorized +6. Check if MFA Delete was configured on the bucket +7. Review S3 bucket lifecycle policies for any recent changes +', '["https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html","https://attack.mitre.org/techniques/T1490/"]', e'equals("log.eventSource", "s3.amazonaws.com") && +equals("log.eventName", "PutBucketVersioning") && +equals("log.errorCode", "") && +contains("log.requestParameters", "Suspended") +', '2026-03-02 19:29:39.433504', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.requestParameters.bucketName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1251, 'AWS RDS Database Restored as Publicly Accessible', 3, 2, 1, 'Exfiltration', 'T1020 - Automated Exfiltration', e'Detects RestoreDBInstanceFromDBSnapshot or RestoreDBClusterFromSnapshot API calls that may create publicly accessible database instances. Attackers use this to exfiltrate database contents by restoring a snapshot as a publicly accessible instance. + +Next Steps: +1. Verify the database restore was authorized and part of an approved workflow +2. Check if the restored instance is publicly accessible +3. Review the security group and subnet configuration of the restored instance +4. Identify the source snapshot and its data sensitivity +5. If publicly accessible and unauthorized, immediately modify the instance to private +6. Check for any connections to the restored instance from external IPs +', '["https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_RestoreFromSnapshot.html","https://attack.mitre.org/techniques/T1020/"]', e'equals("log.eventSource", "rds.amazonaws.com") && +oneOf("log.eventName", ["RestoreDBInstanceFromDBSnapshot", "RestoreDBClusterFromSnapshot"]) && +equals("log.errorCode", "") && +contains("log.requestParameters", "publiclyAccessible") +', '2026-03-02 19:29:40.621788', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1252, 'AWS KMS Key Material Import or Deletion', 3, 3, 3, 'Impact', 'T1486 - Data Encrypted for Impact', e'Detects ImportKeyMaterial or DeleteImportedKeyMaterial operations on AWS KMS. These are extremely rare operations that could indicate cloud ransomware preparation - an attacker importing their own key material to encrypt data and then deleting the imported key material to make decryption impossible without paying ransom. + +Next Steps: +1. Immediately verify if this KMS key material operation was authorized +2. Identify the KMS key ID affected and all resources encrypted with it +3. Check the user identity and source IP for legitimacy +4. Review if any data encryption operations followed this event +5. Verify backup key material exists and is securely stored +6. If unauthorized, immediately disable the KMS key and investigate data integrity +7. Check for associated DeleteImportedKeyMaterial calls that would prevent decryption +', '["https://docs.aws.amazon.com/kms/latest/developerguide/importing-keys.html","https://attack.mitre.org/techniques/T1486/"]', e'equals("log.eventSource", "kms.amazonaws.com") && +oneOf("log.eventName", ["ImportKeyMaterial", "DeleteImportedKeyMaterial"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:41.886072', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1253, 'AWS IAM Login Profile Modified for Another User', 3, 3, 1, 'Persistence', 'T1098 - Account Manipulation', e'Detects UpdateLoginProfile API calls where the modifier is potentially different from the target user. This can indicate an attacker changing another user\'s password to maintain access or enable console login for a compromised programmatic-only account. + +Next Steps: +1. Verify the password change was authorized by comparing the caller with the target user +2. Check if the target user reported any password issues +3. Review subsequent console logins for the target user +4. Investigate the source IP and user agent for the modification +5. Check if the target user\'s MFA was also modified +6. If unauthorized, reset the password and rotate all credentials for the target user +', '["https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_admin-change-user.html","https://attack.mitre.org/techniques/T1098/"]', e'equals("log.eventSource", "iam.amazonaws.com") && +equals("log.eventName", "UpdateLoginProfile") && +equals("log.errorCode", "") +', '2026-03-02 19:29:43.067349', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.requestParameters.userName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1254, 'AWS Glue Development Endpoint Privilege Escalation', 3, 3, 2, 'Privilege Escalation', 'T1078.004 - Valid Accounts: Cloud Accounts', e'Detects creation or modification of AWS Glue development endpoints which can be used for privilege escalation. An attacker can pass an IAM role to a Glue dev endpoint and then use it to execute code with that role\'s permissions, potentially escalating privileges. + +Next Steps: +1. Verify the Glue development endpoint creation was authorized +2. Check the IAM role passed to the endpoint for excessive permissions +3. Review the user identity and source IP for legitimacy +4. Check for code execution on the development endpoint +5. If unauthorized, delete the endpoint and investigate the passed role +6. Review IAM policies to restrict Glue PassRole permissions +', '["https://rhinosecuritylabs.com/aws/escalating-aws-iam-privileges-undocumented-codestar-api/","https://attack.mitre.org/techniques/T1078/004/"]', e'equals("log.eventSource", "glue.amazonaws.com") && +oneOf("log.eventName", ["CreateDevEndpoint", "UpdateDevEndpoint"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:44.373009', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1255, 'AWS EC2 Instance Startup Script Modified', 3, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', e'Detects ModifyInstanceAttribute API calls targeting userData, which modifies the EC2 instance startup script. Attackers use this for persistent code execution with root/SYSTEM privileges on instance start. + +Next Steps: +1. Identify the instance and review the new userData content (base64 decode it) +2. Check if the modification was part of an authorized deployment +3. Review the user identity and source IP for legitimacy +4. Check if the instance was stopped and restarted after the modification +5. Examine the instance for signs of compromise +6. If unauthorized, remove the userData and investigate the instance +', '["https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html","https://attack.mitre.org/techniques/T1059/"]', e'equals("log.eventSource", "ec2.amazonaws.com") && +equals("log.eventName", "ModifyInstanceAttribute") && +equals("log.errorCode", "") && +contains("log.requestParameters", "userData") +', '2026-03-02 19:29:45.644330', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.requestParameters.instanceId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1256, 'AWS Console GetSigninToken Abuse', 3, 2, 1, 'Credential Access', 'T1550.001 - Use Alternate Authentication Material: Application Access Token', e'Detects GetSigninToken API calls which allow federated users to get a console sign-in token. Attackers abuse this to pivot from programmatic credentials to console access, potentially bypassing MFA requirements. + +Next Steps: +1. Verify the federated sign-in was from an authorized user and application +2. Check the source IP and user agent for legitimacy +3. Review what actions were performed in the console after sign-in +4. Correlate with the original AssumeRole call that created the federated session +5. Check if MFA was properly enforced on the original authentication +6. If unauthorized, revoke the federated session immediately +', '["https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html","https://attack.mitre.org/techniques/T1550/001/"]', e'equals("log.eventSource", "signin.amazonaws.com") && +equals("log.eventName", "GetSigninToken") && +equals("log.errorCode", "") +', '2026-03-02 19:29:46.741585', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.sourceIPAddress"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1257, 'AWS Config Service Disabled', 2, 3, 2, 'Defense Evasion', 'T1562.008 - Impair Defenses: Disable Cloud Logs', e'Detects attempts to disable AWS Config by stopping the configuration recorder or deleting the delivery channel. AWS Config tracks resource configuration changes and compliance, and disabling it is a common defense evasion technique. + +Next Steps: +1. Verify if the AWS Config modification was authorized +2. Check the user identity and source IP for legitimacy +3. Review any infrastructure changes made while Config was disabled +4. Re-enable AWS Config immediately if unauthorized +5. Check for other defense evasion activities from the same source +6. Review CloudTrail for API calls during the gap in Config monitoring +7. Verify compliance requirements are being met +', '["https://docs.aws.amazon.com/config/latest/developerguide/stop-start-recorder.html","https://attack.mitre.org/techniques/T1562/008/"]', e'equals("log.eventSource", "config.amazonaws.com") && +oneOf("log.eventName", ["DeleteDeliveryChannel", "StopConfigurationRecorder", "DeleteConfigurationRecorder"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:47.949489', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.eventName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1258, 'AWS Backup Deletion for Impact', 1, 3, 3, 'Impact', 'T1490 - Inhibit System Recovery', e'Detects deletion of AWS Backup vaults, recovery points, or backup plans which could indicate an attacker attempting to prevent recovery after a destructive attack. Removing backups is a common precursor to ransomware or data destruction campaigns. + +Next Steps: +1. Immediately verify if the backup deletion was authorized +2. Check the IAM principal and source IP performing the deletion +3. Assess which backup vaults and recovery points were affected +4. Verify if backup vault lock policies were in place and bypassed +5. Check for concurrent destructive activities (instance termination, data deletion) +6. If unauthorized, immediately implement vault lock on remaining backup vaults +7. Restore deleted recovery points from any cross-region or cross-account copies +8. Enable MFA delete on backup vaults and implement SCPs to restrict backup deletion +', '["https://docs.aws.amazon.com/aws-backup/latest/devguide/API_Operations.html","https://attack.mitre.org/techniques/T1490/"]', e'equals("log.eventSource", "backup.amazonaws.com") && +oneOf("log.eventName", ["DeleteBackupVault", "DeleteRecoveryPoint", "DeleteBackupPlan", "DeleteBackupVaultAccessPolicy", "DeleteBackupVaultLockConfiguration"]) && +equals("log.errorCode", "") +', '2026-03-02 19:29:49.135879', true, true, 'origin', null, '[]', '["adversary.user","lastEvent.log.eventName"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/aws/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/aws/utm_group_rules_data_type.sql new file mode 100644 index 000000000..f8ce34c28 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/aws/utm_group_rules_data_type.sql @@ -0,0 +1,73 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1186, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1187, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1188, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1242, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1243, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1189, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1190, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1191, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1192, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1193, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1194, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1195, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1196, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1197, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1198, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1199, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1200, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1201, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1202, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1203, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1204, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1205, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1206, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1207, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1208, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1209, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1210, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1211, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1212, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1213, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1214, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1215, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1216, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1217, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1218, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1219, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1220, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1221, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1222, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1223, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1224, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1225, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1226, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1227, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1228, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1229, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1230, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1231, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1232, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1233, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1234, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1235, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1236, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1237, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1238, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1239, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1240, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1241, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1244, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1245, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1246, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1247, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1248, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1249, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1250, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1251, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1252, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1253, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1254, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1255, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1256, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1257, 2, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1258, 2, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/azure/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/azure/utm_correlation_rules.sql new file mode 100644 index 000000000..4d6cfc46c --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/azure/utm_correlation_rules.sql @@ -0,0 +1,773 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1259, 'Azure PIM Role Activation Anomaly', 3, 3, 1, 'Privilege Escalation', 'T1078 - Valid Accounts', e'Detects unusual Privileged Identity Management (PIM) role activation patterns including activation of high-privilege roles such as Global Administrator or Privileged Role Administrator. Repeated or unusual PIM activations may indicate an attacker leveraging compromised credentials to escalate privileges. + +Next Steps: +1. Verify the user activating the PIM role has legitimate business justification +2. Review the specific role being activated and its scope +3. Check the activation justification message provided by the user +4. Review the activation duration and whether it exceeds normal patterns +5. Check for unusual source IP or device during the activation +6. If unauthorized, immediately deactivate the role and disable the user account +7. Review PIM audit logs for other suspicious activations by the same user +8. Implement PIM access reviews and require approval for critical roles +', '["https://learn.microsoft.com/en-us/azure/active-directory/privileged-identity-management/pim-configure","https://attack.mitre.org/techniques/T1078/"]', e'(contains("log.operationName", "Add member to role completed (PIM activation)") || + contains("log.operationName", "Add eligible member to role in PIM completed") || + contains("log.operationName", "Activate PIM role")) && +equals("log.categoryValue", "Administrative") +', '2026-03-02 22:35:56.083827', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.user","operator":"filter_term","value":"{{.origin.user}}"},{"field":"log.categoryValue","operator":"filter_term","value":"Administrative"}],"or":null,"within":"now-4h","count":3}]', '["lastEvent.log.operationName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1260, 'Azure Managed Identity Token Abuse', 3, 3, 1, 'Credential Access', 'T1078.004 - Valid Accounts: Cloud Accounts', e'Detects suspicious token acquisition from Azure Instance Metadata Service (IMDS) by managed identities. Attackers who compromise an Azure VM can abuse managed identities to obtain access tokens for Azure resources without credentials, enabling lateral movement across the cloud environment. + +Next Steps: +1. Identify the Azure resource (VM, App Service, Function) where the token was acquired +2. Review the target resource being accessed with the managed identity token +3. Check if the managed identity\'s permissions follow least privilege principles +4. Investigate the process or application that requested the token +5. Review Azure Activity logs for actions performed using the managed identity +6. If unauthorized, restrict the managed identity\'s role assignments immediately +7. Investigate the source VM for signs of compromise +8. Implement Conditional Access policies for workload identities +', '["https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview","https://attack.mitre.org/techniques/T1078/004/"]', e'contains("log.operationName", "Microsoft.ManagedIdentity") && +equals("log.categoryValue", "Administrative") && +(contains("log.properties.message", "token") || + contains("log.operationName", "tokens")) +', '2026-03-02 22:35:57.698411', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-1h","count":5}]', '["lastEvent.log.operationName","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1261, 'Azure Key Vault Excessive Access Detected', 3, 2, 1, 'Collection', 'T1530 - Data from Cloud Storage Object', e'Detects unusual spikes in Azure Key Vault access patterns. Monitors for multiple secret retrieval operations from the same source, which could indicate credential harvesting or data exfiltration attempts. + +Next Steps: +1. Investigate the source IP address and verify if it\'s a legitimate system or user +2. Review the specific secrets/keys being accessed and their criticality +3. Check for any recent changes to Key Vault access policies +4. Correlate with user authentication logs to identify the account responsible +5. Verify if the access pattern aligns with normal business operations +6. Consider implementing additional access controls or monitoring if suspicious activity is confirmed +', '["https://learn.microsoft.com/en-us/azure/key-vault/general/logging","https://attack.mitre.org/techniques/T1530/"]', e'equals("log.category", "AuditEvent") && +oneOf("log.operationName", ["SecretGet", "SecretList", "KeyGet"]) +', '2026-03-02 22:35:59.272256', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.category","operator":"filter_term","value":"AuditEvent"}],"or":null,"within":"now-10m","count":20}]', '["lastEvent.log.resourceId","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1262, 'Azure AD Resource Owner Password Credentials Flow Detected', 2, 2, 1, 'Credential Access', 'T1078 - Valid Accounts', e'Detects use of the Resource Owner Password Credentials (ROPC) OAuth flow in Azure AD. ROPC sends plain-text credentials directly to the token endpoint, bypassing MFA and conditional access. It is commonly abused by attackers for credential stuffing and automated account compromise. + +Next Steps: +1. Identify the application using ROPC flow and verify its legitimacy +2. Check if the application has a legitimate need for ROPC (legacy/headless apps) +3. Review the source IPs making ROPC requests for suspicious patterns +4. Check for high volumes of failed ROPC requests (credential stuffing) +5. Migrate the application to a modern auth flow (authorization code, device code) +6. If unauthorized, block the application and reset affected user passwords +', '["https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth-ropc","https://attack.mitre.org/techniques/T1078/"]', e'contains("log.properties", "urn:ietf:params:oauth:grant-type:password") || +(contains("log.operationName", "Sign-in") && contains("log.properties", "ropc")) +', '2026-03-02 22:36:00.844215', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-1h","count":5}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1263, 'Azure AD LAPS Password Recovery', 3, 2, 1, 'Credential Access', 'T1003 - OS Credential Dumping', e'Detects Local Administrator Password Solution (LAPS) password recovery from Entra ID. While LAPS recovery is a legitimate admin operation, excessive or unauthorized recovery attempts indicate credential dumping for lateral movement. + +Next Steps: +1. Verify the user recovering the LAPS password has legitimate need +2. Check the target device and whether the user is responsible for it +3. Review the frequency of LAPS password recoveries by this user +4. Correlate with subsequent RDP or SMB connections to the target device +5. If unauthorized, rotate the LAPS password and investigate the user\'s activities +6. Review RBAC for LAPS password read permissions +', '["https://learn.microsoft.com/en-us/entra/identity/devices/howto-manage-local-admin-passwords","https://attack.mitre.org/techniques/T1003/"]', e'contains("log.operationName", "Recover device local administrator password") || +(contains("log.operationName", "Read device local administrator password") && exists("log.properties")) +', '2026-03-02 22:36:02.459874', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-1h","count":3}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1264, 'Azure Kubernetes Secret Write or Delete', 3, 3, 2, 'Credential Access', 'T1552.007 - Unsecured Credentials: Container API', e'Detects write or delete operations on Kubernetes Secrets in Azure Kubernetes Service. Secrets contain sensitive data like service account tokens, TLS certificates, and database credentials. Unauthorized access indicates potential credential theft or data tampering. + +Next Steps: +1. Identify the user or service account accessing the secrets +2. Review which secrets were accessed, modified, or deleted +3. Check if the operation was part of a legitimate deployment workflow +4. Audit the RBAC permissions of the identity performing the action +5. If unauthorized, rotate all affected secrets immediately +6. Review pod specifications for secrets mounted as volumes or environment variables +', '["https://kubernetes.io/docs/concepts/configuration/secret/","https://attack.mitre.org/techniques/T1552/007/"]', e'contains("log.operationName", "MICROSOFT.CONTAINERSERVICE") && +contains("log.properties", "secrets") && +(contains("log.properties", "create") || contains("log.properties", "update") || contains("log.properties", "delete") || contains("log.properties", "patch")) +', '2026-03-02 22:36:04.036942', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-30m","count":5}]', '["lastEvent.log.resourceId","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1265, 'Azure AD Bulk Privileged Role Assignment Changes', 3, 3, 2, 'Privilege Escalation', 'T1098 - Account Manipulation', e'Detects mass privileged role assignment changes in Azure AD. Multiple role assignments in a short time window indicate an attacker rapidly escalating privileges across multiple accounts for persistence and lateral movement. + +Next Steps: +1. Review all role assignments made in the burst +2. Identify the admin account making the changes +3. Check if these changes were part of an approved onboarding or migration +4. Review the specific roles assigned (Global Admin, Exchange Admin, etc.) +5. If unauthorized, revert all role assignments and investigate the admin account +6. Enable Azure PIM for just-in-time role activation +', '["https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-resource-roles-assign-roles","https://attack.mitre.org/techniques/T1098/"]', e'contains("log.operationName", "Add member to role") || +contains("log.operationName", "Add eligible member to role") +', '2026-03-02 22:36:05.649800', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-30m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1266, 'Azure AD Password Spray Attack Detection', 3, 2, 1, 'Credential Access', 'T1110 - Brute Force', e'Detects password spray attacks against Azure AD by correlating failed sign-in attempts across multiple usernames from the same source IP within a short time window. Password spraying tries common passwords against many accounts to avoid account lockout thresholds. + +Next Steps: +1. Identify the source IP and check threat intelligence feeds for known malicious sources +2. Review the list of targeted user accounts for patterns (executives, admins, service accounts) +3. Check if any of the targeted accounts subsequently had successful logins +4. Verify that account lockout policies are properly configured +5. Block the source IP at the network level if confirmed malicious +6. Enable Azure AD Smart Lockout for brute force protection +7. Implement Conditional Access policies requiring MFA +8. Review password policies and enforce complexity requirements +', '["https://learn.microsoft.com/en-us/azure/active-directory/identity-protection/concept-identity-protection-risks","https://attack.mitre.org/techniques/T1110/"]', e'contains("log.operationName", "Sign-in activity") && +(equals("log.properties.status.errorCode", "50126") || + equals("log.properties.status.errorCode", "50053") || + equals("log.properties.status.errorCode", "50057")) && +exists("origin.ip") +', '2026-03-02 22:36:07.222474', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.properties.status.errorCode","operator":"filter_match","value":"5005"}],"or":null,"within":"now-15m","count":15}]', '["lastEvent.log.operationName","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1267, 'Azure AD App Registration with High-Privilege API Permissions', 3, 3, 1, 'Persistence', 'T1098.001 - Account Manipulation: Additional Cloud Credentials', e'Detects creation of new Azure AD application registrations which may be used to establish persistence with high-privilege API permissions. Attackers create app registrations with permissions like Mail.ReadWrite, Directory.ReadWrite.All, or RoleManagement.ReadWrite.Directory to maintain access. + +Next Steps: +1. Review the application registration and its requested API permissions +2. Verify the creator has authorization to register applications +3. Check if admin consent was granted for the application\'s permissions +4. Review the application\'s redirect URIs for suspicious external domains +5. Examine the application\'s credential types (secrets, certificates) +6. If unauthorized, delete the application registration and revoke any granted consents +7. Implement app registration policies to restrict who can create applications +8. Enable admin consent workflow for application permission requests +', '["https://learn.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals","https://attack.mitre.org/techniques/T1098/001/"]', e'(contains("log.operationName", "Add application") || + contains("log.operationName", "Add service principal") || + contains("log.operationName", "Consent to application")) && +equals("log.categoryValue", "Administrative") +', '2026-03-02 22:36:08.795179', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.user","operator":"filter_term","value":"{{.origin.user}}"},{"field":"log.categoryValue","operator":"filter_term","value":"Administrative"}],"or":null,"within":"now-1h","count":3}]', '["lastEvent.log.operationName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1268, 'Application Gateway WAF Security Alerts', 3, 3, 2, 'Initial Access', 'T1190 - Exploit Public-Facing Application', e'Detects Web Application Firewall alerts from Azure Application Gateway indicating potential web attacks or malicious activity. This rule triggers when WAF blocks or detects suspicious requests that match security rules. + +**Next Steps:** +1. Review the specific WAF rule ID and message details to understand the attack type +2. Analyze the source IP address for reputation and geographic location +3. Examine the request URL, headers, and payload for attack indicators +4. Check for additional requests from the same source IP within the time window +5. Verify if this is a legitimate application behavior or actual attack attempt +6. Consider implementing additional WAF rules or IP blocking if confirmed malicious +7. Review application logs for any successful bypass attempts +', '["https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/web-application-firewall-logs","https://attack.mitre.org/techniques/T1190/"]', e'(equals("log.operationName", "ApplicationGatewayFirewallLog") || equals("log.type", "ApplicationGatewayFirewallLog")) && +equals("log.action", "Blocked") && +exists("log.ruleId") +', '2026-03-02 22:36:10.423092', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-10m","count":5}]', '["lastEvent.log.ruleId","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1269, 'Azure AKS Container Security Threat Detection', 3, 3, 2, 'Execution', 'T1610 - Deploy Container', e'Detects suspicious container operations in Azure Kubernetes Service (AKS) including privileged pod creation, container exec commands, and potential container escape attempts. These activities may indicate an attacker attempting to deploy malicious workloads or escape container isolation. + +Next Steps: +1. Review the Kubernetes audit logs for the specific pod or container operation +2. Check if the container image is from an approved registry +3. Verify the service account and RBAC permissions used for the operation +4. Examine pod security context for privileged flags, host network, or host PID access +5. Review the container command for suspicious payloads or reverse shells +6. If unauthorized, delete the pod and investigate the cluster for further compromise +7. Implement Azure Policy for AKS to enforce pod security standards +8. Enable Microsoft Defender for Containers for runtime protection +', '["https://learn.microsoft.com/en-us/azure/defender-for-cloud/defender-for-containers-introduction","https://attack.mitre.org/techniques/T1610/"]', e'(contains("log.operationName", "Microsoft.ContainerService") || + contains("log.operationName", "MICROSOFT.KUBERNETES")) && +(contains("log.operationName", "write") || + contains("log.operationName", "create") || + contains("log.operationName", "exec")) && +equals("log.resultType", "Success") +', '2026-03-02 22:36:12.096678', true, true, 'origin', null, '[{"indexPattern":"v11-log-azure-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.operationName","operator":"filter_match","value":"Container"}],"or":null,"within":"now-30m","count":10}]', '["lastEvent.log.operationName","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1270, 'Azure Subscription Ownership Transfer Detected', 3, 3, 2, 'Identity and Access Management', 'T1078 - Valid Accounts', e'Detects when ownership of an Azure subscription is transferred by monitoring role assignment changes at the subscription level. This could indicate unauthorized access or insider threat activity. + +Next Steps: +1. Verify the legitimacy of the ownership transfer with the subscription administrator +2. Check if the user performing the transfer is authorized for this action +3. Review the timing and context of the transfer (business hours, planned change) +4. Examine other recent activities by the same user or from the same source IP +5. Validate that proper change management procedures were followed +6. Check for any unusual activity following the ownership transfer +7. If unauthorized, immediately revoke the new owner\'s access and escalate to security team +', '["https://learn.microsoft.com/en-us/azure/role-based-access-control/change-history-report","https://attack.mitre.org/techniques/T1078/"]', e'equals("log.operationName", "Microsoft.Authorization/roleAssignments/write") && +contains("log.properties", "Owner") && +equals("log.category", "Administrative") && +contains("log.resourceId", "/subscriptions/") && +!contains("log.resourceId", "/resourceGroups/") +', '2026-03-02 22:36:13.559108', true, true, 'origin', null, '[]', '["lastEvent.log.correlationId","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1271, 'Storage Account Public Access Enabled', 3, 2, 1, 'Collection', 'T1530 - Data from Cloud Storage Object', e'Detects when public access is enabled on Azure Storage Accounts which could lead to unauthorized data exposure. +This configuration change creates a significant security risk as it allows anonymous access to stored data. + +Next Steps: +1. Immediately review the affected storage account configuration +2. Verify if public access was intentionally enabled and properly authorized +3. Check if any sensitive data is stored in the account +4. Review access logs for any unauthorized access attempts +5. Consider disabling public access if not required for business operations +6. Implement network restrictions and access policies if public access is necessary +7. Monitor for any data exfiltration activities +', '["https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log-schema","https://attack.mitre.org/techniques/T1530/"]', e'contains("log.operationName", "Microsoft.Storage/storageAccounts") && +(contains("log.operationName", "/write") || contains("log.operationName", "/blobServices/write")) && +equals("log.category", "Administrative") && +equals("log.actionResult", "accepted") && +(contains("log.properties", "allowBlobPublicAccess") || contains("log.properties", "publicAccess")) +', '2026-03-02 22:36:15.084397', true, true, 'origin', null, '[]', '["lastEvent.log.aadObjectId","lastEvent.log.resourceId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1272, 'Multi-Factor Authentication Disabled for an Azure User', 3, 3, 2, 'Persistence', 'T1556 - Modify Authentication Process', e'Detects when multi-factor authentication (MFA) is disabled for an Azure AD/Entra ID user account through Audit Logs. + +**Security Context:** +Multi-factor authentication is a critical security control that requires users to provide additional verification beyond just a password. Disabling MFA for user accounts significantly weakens authentication security and is a common technique used by adversaries to maintain persistent access. Once MFA is disabled, attackers can authenticate using only compromised credentials without triggering additional verification steps, making detection more difficult. + +**Detection Logic:** +This rule monitors AuditLogs for successful "Disable Strong Authentication" operations, which represent the per-user MFA setting being turned off in Azure AD/Entra ID. This operation is distinct from Conditional Access MFA policies and represents the legacy per-user MFA enforcement method. + +**Investigation Steps:** +1. Identify the disabler: Check log.propertiesInitiatedBy for who disabled MFA +2. Identify affected user: Examine log.propertiesTargetResources for the user whose MFA was disabled +3. Verify authorization: Confirm if the MFA disabling was part of legitimate administrative action +4. Review user privilege: Determine if the affected user has elevated permissions (admins, privileged roles) +5. Check timing: Analyze if MFA was disabled after suspicious authentication events +6. Review authentication history: Look for failed authentication attempts before MFA disabling +7. Check for compromise indicators: Search for unusual sign-in patterns, impossible travel, or risky sign-ins +8. Examine subsequent logins: Monitor for authentication activity immediately after MFA disabling +9. Review MFA methods: Check what MFA methods the user had registered before disabling +10. Correlate with other events: Look for privilege escalation or data access after MFA disabling + +**Recommended Actions:** +- If unauthorized, immediately re-enable MFA for the affected user +- Force password reset for the affected account +- Review all authentication activity for the affected user +- Check for compromised credentials using Azure AD Identity Protection +- Revoke all active sessions for the affected user +- Enable Conditional Access policies instead of per-user MFA for better control +- Implement PIM approval workflows for modifying MFA settings +- Enable alerts for MFA changes on privileged accounts +- Audit accounts with permissions to modify user authentication settings +- Review and restrict who can disable MFA (typically requires User Administrator or higher) + +**Modern MFA Management:** +- **Per-user MFA (legacy)**: This detection targets the legacy per-user MFA setting +- **Conditional Access**: Modern approach using policies instead of per-user settings +- **Authentication Methods Policy**: Newer method for managing FIDO2, passwordless, etc. + +Organizations should migrate from per-user MFA to Conditional Access policies for more granular control. + +**Common Attack Patterns:** +- Disabling MFA after compromising an administrator account +- Removing MFA from privileged accounts for easier persistent access +- Disabling MFA before credential harvesting or lateral movement +- Insider threats removing MFA from their own accounts +- Disabling MFA on service accounts to enable automated authentication attacks + +**Related Detections:** +- MFA method removal/changes +- Conditional Access policy modifications +- Authentication methods policy changes +- Privileged role assignments without MFA + +**MITRE ATT&CK Reference:** T1556 - Modify Authentication Process + +**Azure Documentation:** +- AuditLogs table: https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs +- Per-user MFA: https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-userstates +', '["https://attack.mitre.org/techniques/T1556/","https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs","https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-userstates","https://learn.microsoft.com/en-us/entra/identity/authentication/concept-mfa-licensing"]', e'equalsIgnoreCase("log.category", "AuditLogs") && +equalsIgnoreCase("log.operationName", "Disable Strong Authentication") && +(equals("log.resultType", "0") || equalsIgnoreCase("actionResult", "SUCCESS")) +', '2026-03-02 22:36:16.382203', true, true, 'target', null, '[]', '["target.ip","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1273, 'Azure Global Administrator Role Addition to PIM User', 3, 3, 3, 'Persistence', 'T1098.001 - Account Manipulation: Additional Cloud Credentials', e'Detects when users are granted Global Administrator (Company Administrator) role assignments through Azure AD/Entra ID Privileged Identity Management (PIM). + +**Security Context:** +The Global Administrator role is the most powerful administrative role in Azure AD/Entra ID, granting complete control over all aspects of the directory and services that use Azure AD identities. PIM enables just-in-time privileged access through eligible (requires activation) or time-bound assignments. Adversaries who gain sufficient privileges may add themselves or other compromised accounts to this role to establish persistence and maintain full administrative control over the tenant. + +**Detection Logic:** +This rule monitors AuditLogs for successful PIM role assignments specifically for the Global Administrator role. It detects both: +- **Eligible assignments (permanent)**: User can activate the role when needed +- **Active assignments (time-bound)**: Role is directly active for a specified duration + +The rule identifies these assignments through the operation names and filters for the Global Administrator role specifically. + +**Investigation Steps:** +1. Identify the assignor: Check log.propertiesInitiatedBy for who made the role assignment +2. Identify the assignee: Examine log.propertiesTargetResources for the user receiving the role +3. Verify authorization: Confirm if this assignment was part of approved privileged access request +4. Check assignment type: Determine if it\'s eligible (requires activation) or time-bound (direct) +5. Review duration: For time-bound assignments, check the duration of the assignment +6. Analyze timing: Determine if assignment follows suspicious authentication or compromise indicators +7. Review justification: Check if a business justification was provided in log.propertiesAdditionalDetails +8. Check user history: Review the assignee\'s account for recent suspicious activity +9. Examine recent actions: Look for privileged operations performed immediately after assignment +10. Correlate with sign-ins: Check for unusual authentication patterns before/after assignment + +**Recommended Actions:** +- If unauthorized, immediately revoke the Global Administrator role assignment +- Review all recent PIM role assignments for anomalies +- Enable PIM approval workflows for Global Administrator role assignments +- Implement maximum assignment duration limits for time-bound assignments +- Require MFA and justification for all Global Administrator activations +- Enable PIM alerts for high-privilege role assignments +- Audit accounts with Privileged Role Administrator permissions +- Review and limit the number of permanent Global Administrator assignments +- Enable Azure AD Identity Protection to detect compromised credentials +- Implement break-glass emergency access accounts following best practices + +**PIM Assignment Types:** +- **Eligible (permanent)**: User must activate the role when needed, typically with MFA and justification +- **Active (time-bound)**: Role is directly assigned for a limited duration without activation required +- Both types should be monitored as adversaries may use either for persistence + +**Common Attack Patterns:** +- Compromised Privileged Role Administrator adding backdoor accounts +- Insider threat establishing persistent administrative access +- Privilege escalation from lower-privilege administrative roles +- Adding service principals or managed identities to Global Administrator role +- Creating long-duration time-bound assignments for sustained access + +**MITRE ATT&CK Reference:** T1098.001 - Account Manipulation: Additional Cloud Credentials + +**Azure Documentation:** +- AuditLogs table: https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs +- PIM for Azure AD roles: https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-configure +', '["https://attack.mitre.org/techniques/T1098/001/","https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs","https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-configure","https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#global-administrator"]', e'equalsIgnoreCase("log.category", "AuditLogs") && +(equals("log.resultType", "0") || equalsIgnoreCase("actionResult", "SUCCESS")) && +(contains("log.operationName", "Add eligible member to role") || contains("log.operationName", "Add member to role")) && +(contains("log.properties.targetResources.displayName", "Global Administrator") || contains("log.properties.targetResources.displayName", "Company Administrator")) +', '2026-03-02 22:36:17.750986', true, true, 'target', null, '[]', '["target.ip","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1274, 'MFA Disabled for Privileged Azure AD User', 3, 3, 1, 'Defense Evasion', 'T1556 - Modify Authentication Process', e'Detects when Multi-Factor Authentication (MFA) is disabled for privileged users in Azure AD. This could indicate an attempt to weaken security controls for unauthorized access. + +Next Steps: +1. Verify if the MFA disable action was authorized and legitimate +2. Check who initiated the change and from which IP address +3. Review the user\'s recent login activity and permissions +4. Ensure the user account has not been compromised +5. Re-enable MFA if the change was unauthorized +6. Consider implementing conditional access policies to prevent unauthorized MFA changes +', '["https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-reporting","https://attack.mitre.org/techniques/T1556/"]', e'(equals("log.operationName", "Disable Strong Authentication") || + (equals("log.operationName", "Update user") && contains("log.properties", "StrongAuthenticationMethod"))) && +equals("log.categoryValue", "Administrative") +', '2026-03-02 22:36:19.154309', true, true, 'origin', null, '[]', '["lastEvent.log.correlationId","lastEvent.log.targetUserPrincipalName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1275, 'Possible Consent Grant Attack via Azure-Registered Application', 3, 3, 2, 'Initial Access', 'T1078 - Valid Accounts', 'Detects when a user grants permissions to an Azure-registered application or when an administrator grants tenant-wide permissions to an application. An adversary may create an Azure-registered application that requests access to data such as contact information, email, or documents. Consent grant attacks are commonly used in phishing campaigns where malicious OAuth applications trick users into granting excessive permissions, enabling data exfiltration or unauthorized access to organizational resources.', '["https://attack.mitre.org/techniques/T1566/","https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/manage-consent-requests","https://learn.microsoft.com/en-us/defender-cloud-apps/investigate-risky-oauth"]', e'(equalsIgnoreCase("log.category", "AuditLogs") || contains("log.category", "Audit")) && +equalsIgnoreCase("log.operationName", "Consent to application") && +equals("log.resultType", "0") +', '2026-03-02 22:36:20.530093', true, true, 'target', null, '[]', '["target.ip","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1276, 'Azure Active Directory High Risk Sign-in', 3, 3, 2, 'Initial Access', 'T1078 - Valid Accounts', 'Identifies high risk Azure Active Directory (AD) sign-ins by leveraging Microsoft''s Identity Protection machine learning and heuristics. Identity Protection categorizes risk into three tiers: low, medium, and high. While Microsoft does not provide specific details about how risk is calculated, each level brings higher confidence that the user or sign-in is compromised. This rule triggers on ''high'' risk level sign-ins, which indicate strong indicators of compromise such as impossible travel, anonymous IP usage, or leaked credentials.', '["https://attack.mitre.org/techniques/T1078/","https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks","https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/signinlogs"]', e'equalsIgnoreCase("log.category", "SignInLogs") && +equalsIgnoreCase("log.properties.RiskLevelDuringSignIn", "high") && +equalsIgnoreCase("log.propertiesTokenIssuerType", "AzureAD") && +equals("log.resultType", "0") +', '2026-03-02 22:36:21.903345', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1277, 'Azure Service Principal Credentials Added', 3, 3, 2, 'Persistence', 'T1098.001 - Account Manipulation: Additional Cloud Credentials', e'Detects when new credentials (certificates or secrets) are added to Azure service principals through Azure AD/Entra ID Audit Logs. + +**Security Context:** +Adversaries may add credentials to service principals to maintain persistent access to victim Azure accounts. By hijacking an application with granted permissions through adding rogue secrets or certificates, attackers can access protected data and bypass MFA requirements. This technique is commonly used after initial compromise to establish long-term persistence. + +**Detection Logic:** +This rule monitors AuditLogs for successful "Add service principal" operations, which indicate new credentials being added to service principals. The operation captures both certificate and secret additions. + +**Investigation Steps:** +1. Identify the actor who added the credentials: Check log.propertiesInitiatedBy for the user or service principal +2. Review the target service principal: Examine log.propertiesTargetResources for the affected service principal name and ID +3. Verify if the action was authorized: Correlate with change management tickets +4. Check service principal permissions: Review what resources this service principal can access +5. Examine recent sign-in activity: Look for unusual authentication patterns using the service principal +6. Review credential type: Determine if a certificate or secret was added via log.propertiesModifiedProperties + +**Recommended Actions:** +- If unauthorized, immediately revoke the newly added credentials +- Review and rotate all credentials for the affected service principal +- Audit all resources accessible by the service principal for signs of compromise +- Enable alerts for future credential additions to critical service principals +- Implement conditional access policies and privileged identity management + +**MITRE ATT&CK Reference:** T1098.001 - Account Manipulation: Additional Cloud Credentials + +**Azure Documentation:** +- AuditLogs table: https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs +- Service Principal credentials: https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal +', '["https://attack.mitre.org/techniques/T1098/001/","https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs","https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal"]', e'equalsIgnoreCase("log.category", "AuditLogs") && +contains("log.operationName", "Add service principal") && +(equals("log.resultType", "0") || equalsIgnoreCase("actionResult", "SUCCESS")) +', '2026-03-02 22:36:23.393358', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1278, 'Azure AD Golden SAML and Federation Domain Abuse', 3, 3, 2, 'Credential Access', 'T1606.002 - Forge Web Credentials: SAML Tokens', e'Detects additions or modifications of federated domains in Azure AD which could indicate Golden SAML attacks. Attackers who compromise AD FS signing certificates or add rogue federation domains can forge SAML tokens to impersonate any user in the organization. + +Next Steps: +1. Immediately verify if the federation domain change was authorized +2. Review the domain being added and its federation metadata endpoint +3. Check the AD FS signing certificate for unauthorized modifications +4. Verify the identity of the administrator making the change +5. Review Azure AD audit logs for other suspicious tenant-level changes +6. If unauthorized, immediately remove the federated domain and revoke all active sessions +7. Rotate the AD FS token signing certificate +8. Enable Certificate Authority revocation checking for federation certificates +', '["https://learn.microsoft.com/en-us/azure/active-directory/hybrid/whatis-fed","https://attack.mitre.org/techniques/T1606/002/"]', e'(contains("log.operationName", "Set federation settings on domain") || + contains("log.operationName", "Set domain authentication") || + contains("log.operationName", "Add unverified domain") || + contains("log.operationName", "Add verified domain") || + contains("log.operationName", "Set DomainFederationSettings")) && +equals("log.categoryValue", "Administrative") +', '2026-03-02 22:36:24.966927', true, true, 'origin', null, '[]', '["lastEvent.log.operationName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1279, 'Azure Diagnostic Settings Tampering', 2, 3, 2, 'Defense Evasion', 'T1562.008 - Impair Defenses: Disable Cloud Logs', e'Detects deletion or modification of Azure diagnostic settings which are used to route platform logs and metrics to monitoring destinations. Attackers may disable diagnostic settings to prevent their activities from being logged and detected. + +Next Steps: +1. Verify if the diagnostic settings change was authorized through change management +2. Identify which resources lost their diagnostic logging +3. Review the identity performing the change and confirm authorization +4. Check if any suspicious activities occurred after logging was disabled +5. Restore diagnostic settings for affected resources immediately +6. Implement Azure Policy to enforce diagnostic settings on all resources +7. Set up alerts for diagnostic settings modifications +8. Review Azure Activity Log for other defense evasion activities by the same identity +', '["https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings","https://attack.mitre.org/techniques/T1562/008/"]', e'contains("log.operationName", "Microsoft.Insights/diagnosticSettings") && +(contains("log.operationName", "delete") || + contains("log.operationName", "Delete")) && +equals("log.resultType", "Success") +', '2026-03-02 22:36:26.578967', true, true, 'origin', null, '[]', '["lastEvent.log.resourceId","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1280, 'Azure Event Hub Deletion', 1, 3, 3, 'Defense Evasion', 'T1562.008 - Impair Defenses: Disable Cloud Logs', e'Detects the deletion of an Azure Event Hub, which is a critical event processing service that ingests and processes large volumes of events, logs, and telemetry data. Event Hubs are commonly used for security monitoring, log aggregation, and SIEM integration. Adversaries may delete Event Hubs to evade detection by disrupting log collection pipelines and preventing security events from reaching monitoring systems. + +Threat Context: +- Event Hubs are often used to stream logs to SIEM solutions +- Deletion interrupts security monitoring and incident detection capabilities +- Can be part of anti-forensics activities to cover tracks +- May indicate an attempt to blind security operations before further attacks + +Legitimate Use Cases: +- Decommissioning unused Event Hubs during cost optimization +- Infrastructure cleanup during application retirement +- Migration to new Event Hub namespaces or different logging solutions +- Testing and development environment cleanup + +Suspicious Indicators: +- Event Hub actively receiving logs suddenly deleted +- Deletion performed by non-administrative accounts +- Multiple Event Hubs deleted in quick succession +- Deletion outside change management windows +- Deletion from unusual locations or IP addresses +- Event Hub connected to production SIEM or security monitoring + +Next Steps: +1. Verify if the deletion was authorized via change management process +2. Identify who performed the deletion (caller) and their role +3. Check if the Event Hub was actively receiving security logs +4. Determine the impact on security monitoring and log collection +5. Review recent authentication activity for the caller account +6. Check for other suspicious activities in the timeline (diagnostic settings changes, etc.) +7. Verify if backups of the Event Hub configuration exist +8. If unauthorized, restore the Event Hub and investigate for account compromise +9. Review authorization rules and access policies for remaining Event Hubs +', '["https://attack.mitre.org/techniques/T1562/008/","https://attack.mitre.org/tactics/TA0005/","https://learn.microsoft.com/en-us/azure/event-hubs/monitor-event-hubs","https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log"]', e'(equalsIgnoreCase("log.category", "Administrative") || contains("log.category", "Activity")) && +(equalsIgnoreCase("log.operationName", "MICROSOFT.EVENTHUB/NAMESPACES/EVENTHUBS/DELETE") || +contains("log.operationName", "Delete EventHub")) && +(equalsIgnoreCase("log.resultType", "0") || equalsIgnoreCase("actionResult", "SUCCESS")) +', '2026-03-02 22:36:28.109574', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1281, 'Azure Diagnostic Settings Deletion', 1, 3, 3, 'Defense Evasion', 'T1562.008 - Impair Defenses: Disable Cloud Logs', e'Detects the deletion of diagnostic settings in Azure, which are critical for sending platform logs, metrics, and activity data to destinations like Log Analytics workspaces, Event Hubs, or storage accounts. Adversaries delete diagnostic settings to evade detection by disabling security monitoring and audit logging capabilities. + +This technique is commonly observed when attackers: +- Attempt to hide malicious activities from security teams +- Disable logging before executing destructive operations +- Remove evidence trails of their presence in the environment +- Prevent detection of lateral movement or data exfiltration + +Legitimate deletions are rare and typically occur only during: +- Infrastructure decommissioning or major reconfigurations +- Cost optimization initiatives (but should be heavily scrutinized) +- Migration to new monitoring solutions + +Next Steps: +1. Immediately verify if the deletion was authorized and documented +2. Identify who performed the operation and from which IP address +3. Check if diagnostic settings were immediately recreated (potential test) +4. Review recent activities on the affected resource for suspicious behavior +5. Verify if other resources had their diagnostic settings deleted +6. Restore diagnostic settings immediately to resume monitoring +7. Investigate the caller\'s account for potential compromise +8. Check for other defense evasion techniques in the timeline +', '["https://attack.mitre.org/techniques/T1562/008/","https://attack.mitre.org/tactics/TA0005/","https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings","https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log"]', e'(equalsIgnoreCase("log.category", "Administrative") || contains("log.category", "Activity")) && +(equalsIgnoreCase("log.operationName", "MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE") || +contains("log.operationName", "Delete diagnostic setting")) && +equalsIgnoreCase("log.resultType", "0") +', '2026-03-02 22:36:29.724542', true, true, 'target', null, '[]', '["target.ip","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1282, 'Azure Defender for Cloud Critical Security Alert', 3, 3, 2, 'Intrusion Detection', 'TA0001 - Initial Access', e'Detects critical severity alerts from Azure Defender for Cloud indicating potential active threats, malware infections, or successful breach attempts that require immediate response. + +Next Steps: +1. Review the full alert details in Azure Defender for Cloud portal +2. Verify the affected resource and assess the scope of potential compromise +3. Check for related suspicious activities on the affected resource +4. Implement immediate containment measures if threat is confirmed +5. Review security policies and configurations for the affected resource +6. Document the incident and update security procedures as needed +', '["https://learn.microsoft.com/en-us/azure/defender-for-cloud/alerts-overview","https://learn.microsoft.com/en-us/azure/defender-for-cloud/alerts-schemas","https://attack.mitre.org/tactics/TA0001/"]', e'(equals("log.eventName", "Microsoft.Security/locations/alerts/Activate/action") || contains("log.operationName", "Microsoft.Security")) && +equals("log.category", "Security") && +oneOf("log.level", ["Critical", "High", "Error"]) && +(equals("log.properties.severity", "High") || equals("log.properties.alertSeverity", "High")) +', '2026-03-02 22:36:31.298397', true, true, 'origin', null, '[]', '["lastEvent.log.correlationId","lastEvent.log.eventDataId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1283, 'Azure Key Vault Modified', 3, 3, 2, 'Credential Access', 'T1552 - Unsecured Credentials', 'Identifies modifications to a Key Vault in Azure. The Key Vault is a service that safeguards encryption keys and secrets like certificates, connection strings, and passwords. Because this data is sensitive and business critical, access to key vaults should be secured to allow only authorized applications and users. Adversaries may modify Key Vault configurations to weaken security controls, add unauthorized access policies, or change network rules to facilitate credential theft and unauthorized access to sensitive secrets.', '["https://attack.mitre.org/techniques/T1552/","https://attack.mitre.org/tactics/TA0006/","https://learn.microsoft.com/en-us/azure/key-vault/general/security-features"]', e'(equalsIgnoreCase("log.category", "Administrative") || contains("log.category", "Activity")) && +(equalsIgnoreCase("log.operationName", "MICROSOFT.KEYVAULT/VAULTS/WRITE") || +contains("log.operationName", "Microsoft.KeyVault/vaults/write")) && +equals("log.resultType", "0") +', '2026-03-02 22:36:32.910598', true, true, 'target', null, '[]', '["target.ip","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1284, 'Azure Subscription Permission Elevation via ElevateAccess', 3, 3, 3, 'Privilege Escalation', 'T1078.004 - Valid Accounts: Cloud Accounts', e'Detects the MICROSOFT.AUTHORIZATION/ELEVATEACCESS/ACTION operation which grants a Global Administrator access to ALL Azure subscriptions in the tenant. This is an extremely high-impact action that should be very rare and carefully monitored. + +Next Steps: +1. Immediately verify this action was authorized by a known Global Administrator +2. Check if a change request or emergency procedure exists for this action +3. Review what subscription-level changes were made after the elevation +4. Check for new role assignments at the management group or subscription level +5. If unauthorized, remove the User Access Administrator role and audit all changes +6. Enable Azure PIM (Privileged Identity Management) if not already in use +', '["https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin","https://attack.mitre.org/techniques/T1078/004/"]', e'regexMatch("log.operationName", "(?i)MICROSOFT\\\\.AUTHORIZATION/ELEVATEACCESS/ACTION") +', '2026-03-02 22:36:34.355261', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1285, 'Azure AD Temporary Access Pass Registration', 3, 2, 1, 'Credential Access', 'T1078.004 - Valid Accounts: Cloud Accounts', e'Detects registration of Temporary Access Pass (TAP) in Azure AD. TAPs can be used to bypass MFA requirements and are a growing attack vector for initial access and MFA circumvention. + +Next Steps: +1. Verify the TAP was requested through legitimate channels (IT helpdesk) +2. Check the admin user who created the TAP for legitimacy +3. Review the target user and reason for TAP issuance +4. Check for sign-ins using the TAP, especially from unusual locations +5. Verify MFA registration events following the TAP usage +6. If unauthorized, revoke the TAP immediately and investigate +7. Review TAP policy settings for appropriate lifetime and usage limits +', '["https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass","https://attack.mitre.org/techniques/T1078/004/"]', e'(contains("log.operationName", "Admin registered security info") && contains("log.properties", "Temporary Access Pass")) || +(contains("log.operationName", "Update user") && contains("log.properties", "TemporaryAccessPass")) +', '2026-03-02 22:36:35.767739', true, true, 'origin', null, '[]', '["lastEvent.log.properties.targetResources","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1286, 'Azure Sentinel High/Critical Alert Pattern Detection', 3, 3, 2, 'Threat Detection', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects high-severity or critical alerts from Azure Sentinel that may indicate coordinated attack activity or serious security incidents requiring immediate investigation. This rule identifies new alerts with High or Critical severity levels from Microsoft Sentinel that could represent active threats. + +Next Steps: +1. Review the alert details and affected resources immediately +2. Correlate with other security events in the environment +3. Check for signs of lateral movement or privilege escalation +4. Verify if the alert represents a true positive through manual investigation +5. Implement containment measures if attack activity is confirmed +6. Document findings and update incident response procedures +', '["https://learn.microsoft.com/en-us/azure/sentinel/security-alert-schema","https://attack.mitre.org/techniques/T1562/"]', e'oneOf("log.AlertSeverity", ["High", "Critical"]) && +equals("log.VendorName", "Microsoft Sentinel") && +equals("log.Status", "New") +', '2026-03-02 22:36:37.309930', true, true, 'origin', null, '[]', '["lastEvent.log.AlertType","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1287, 'Azure AD Password Spray Attack Detected', 3, 2, 1, 'Credential Access', 'T1110.003 - Brute Force: Password Spraying', e'Detects Azure Identity Protection password spray attack signals. Microsoft\'s ML-based detection identifies distributed password spray attempts across multiple accounts using common passwords. + +Next Steps: +1. Identify all affected user accounts in the password spray +2. Check if any accounts were successfully compromised +3. Force password resets for all targeted accounts +4. Review source IPs for known attack infrastructure +5. Check for successful sign-ins from the same source IPs +6. Enable smart lockout policies if not already configured +7. Review MFA enforcement across all targeted accounts +', '["https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks","https://attack.mitre.org/techniques/T1110/003/"]', e'contains("log.operationName", "Password Spray") || +(contains("log.properties", "riskEventType") && contains("log.properties", "passwordSpray")) +', '2026-03-02 22:36:38.834034', true, true, 'origin', null, '[]', '["lastEvent.log.operationName","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1288, 'Azure Primary Refresh Token Access Attempt', 3, 3, 1, 'Credential Access', 'T1528 - Steal Application Access Token', e'Detects attempts to access the Primary Refresh Token (PRT) in Azure AD. PRT theft is a high-confidence compromise indicator as PRTs provide SSO access across all Azure AD-integrated applications and can be used to bypass conditional access policies. + +Next Steps: +1. Immediately investigate the user account associated with this alert +2. Check the device from which the PRT access was attempted +3. Review sign-in logs for the affected user for anomalous patterns +4. Check for token replay attacks or sessions from unexpected locations +5. If compromise is confirmed, revoke all refresh tokens for the user +6. Re-register the device and force re-authentication +7. Review conditional access policies for PRT-based bypass vulnerabilities +', '["https://learn.microsoft.com/en-us/entra/identity/devices/concept-primary-refresh-token","https://attack.mitre.org/techniques/T1528/"]', e'contains("log.operationName", "Primary Refresh Token") || +(contains("log.properties", "PRT") && contains("log.properties", "access")) +', '2026-03-02 22:36:40.294393', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1289, 'Azure AD New Root Certificate Authority Added', 3, 3, 2, 'Persistence', 'T1556 - Modify Authentication Process', e'Detects when a new root certificate authority is added to the TrustedCAsForPasswordlessAuth configuration in Azure AD. Adding a rogue root CA enables persistent passwordless authentication backdoor access. + +Next Steps: +1. Immediately verify the root CA addition was authorized +2. Review the certificate details and issuing authority +3. Check the user identity performing the change +4. Validate the CA against your organization\'s known PKI infrastructure +5. If unauthorized, remove the root CA immediately +6. Audit all certificate-based authentications since the CA was added +7. Review Azure AD authentication methods policies +', '["https://learn.microsoft.com/en-us/entra/identity/authentication/concept-certificate-based-authentication","https://attack.mitre.org/techniques/T1556/"]', e'contains("log.operationName", "TrustedCAsForPasswordlessAuth") || +(contains("log.operationName", "Update organization settings") && contains("log.properties", "certificateAuthorities")) +', '2026-03-02 22:36:41.980438', true, true, 'origin', null, '[]', '["lastEvent.log.operationName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1290, 'Azure Application Credential Modification', 3, 3, 2, 'Defense Evasion', 'T1098.001 - Account Manipulation: Additional Cloud Credentials', e'Detects when a new credential (certificate or secret) is added to an Azure AD application. Applications can use certificates or secret strings to authenticate when requesting tokens. Adversaries may add additional authentication credentials to existing applications to establish persistence, evade defenses, or enable privilege escalation by impersonating legitimate applications. + +This technique is commonly used in post-compromise scenarios where attackers: +- Add secrets to high-privilege applications to maintain access +- Create backdoor authentication methods to evade MFA requirements +- Establish persistence mechanisms that survive password resets +- Enable token-based authentication for automated attacks + +Next Steps: +1. Verify if the credential modification was authorized and expected +2. Identify who performed the operation (check InitiatedBy field) +3. Review the affected application\'s permissions and access scope +4. Check for subsequent suspicious sign-in activity using the application +5. Audit other applications for similar unauthorized modifications +6. If unauthorized, immediately remove the suspicious credentials +7. Review application usage logs for potential abuse +8. Investigate the source IP address and user agent of the modification +', '["https://attack.mitre.org/techniques/T1098/001/","https://attack.mitre.org/tactics/TA0005/","https://learn.microsoft.com/en-us/azure/active-directory/reports-monitoring/concept-audit-logs","https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-audit-activities"]', e'(equalsIgnoreCase("log.category", "AuditLogs") || contains("log.category", "Audit")) && +(contains("log.operationName", "Certificates and secrets management") || +equalsIgnoreCase("log.operationName", "Add service principal credentials") || +equalsIgnoreCase("log.operationName", "Update application") || +equalsIgnoreCase("log.operationName", "Update application - Certificates and secrets management")) && +equalsIgnoreCase("log.resultType", "0") +', '2026-03-02 22:36:43.553631', true, true, 'target', null, '[]', '["target.ip","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1291, 'Azure AD Leaked Credentials Detection', 3, 3, 2, 'Credential Access', 'T1078 - Valid Accounts', e'Detects Azure Identity Protection alerts for leaked credentials found on dark web, paste sites, or other sources. This indicates user credentials have been exposed and may be used for unauthorized access. + +Next Steps: +1. Immediately force a password reset for the affected user +2. Revoke all active sessions and refresh tokens +3. Review recent sign-in activity for unauthorized access +4. Check for any data access or configuration changes after the leak +5. Enable MFA if not already required for the user +6. Investigate how the credentials were leaked (phishing, malware, reuse) +7. Check if the same password was used across other services +', '["https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks","https://attack.mitre.org/techniques/T1078/"]', e'contains("log.operationName", "Leaked Credentials") || +(contains("log.properties", "riskEventType") && contains("log.properties", "leakedCredentials")) +', '2026-03-02 22:36:45.168922', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1292, 'Azure Kubernetes Events Deleted', 1, 3, 2, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects deletion of Kubernetes events in Azure Kubernetes Service (AKS). Attackers delete events to cover traces of their activities within the cluster. + +Next Steps: +1. Identify the user or service account that deleted the events +2. Check for other suspicious Kubernetes operations from the same identity +3. Review AKS audit logs for activities that occurred before the event deletion +4. Verify if this was part of a legitimate cluster maintenance operation +5. If unauthorized, investigate the cluster for signs of compromise +6. Review RBAC policies to restrict event deletion permissions +', '["https://learn.microsoft.com/en-us/azure/aks/monitor-aks","https://attack.mitre.org/techniques/T1562/001/"]', e'contains("log.operationName", "MICROSOFT.CONTAINERSERVICE") && +(contains("log.properties", "events") && contains("log.properties", "delete")) +', '2026-03-02 22:36:46.747455', true, true, 'origin', null, '[]', '["lastEvent.log.resourceId","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1293, 'Azure Kubernetes Admission Webhook Modified', 3, 3, 2, 'Persistence', 'T1078.004 - Valid Accounts: Cloud Accounts', e'Detects creation or modification of MutatingAdmissionWebhook or ValidatingAdmissionWebhook configurations in Azure Kubernetes Service. Attackers use admission controllers to inject malicious containers or modify workload specifications. + +Next Steps: +1. Review the webhook configuration and its target service +2. Verify the webhook was created as part of a legitimate deployment +3. Check the webhook\'s namespace selector and object selector +4. Examine what resources the webhook intercepts (pods, deployments, etc.) +5. If unauthorized, delete the webhook and audit all recent pod deployments +6. Review cluster RBAC for excessive admission controller permissions +', '["https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/","https://attack.mitre.org/techniques/T1078/004/"]', e'contains("log.operationName", "MICROSOFT.CONTAINERSERVICE") && +(contains("log.properties", "MutatingWebhookConfiguration") || contains("log.properties", "ValidatingWebhookConfiguration")) && +(contains("log.properties", "create") || contains("log.properties", "update") || contains("log.properties", "patch")) +', '2026-03-02 22:36:48.115412', true, true, 'origin', null, '[]', '["lastEvent.log.resourceId","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1294, 'Azure AD Impossible Travel Sign-In', 3, 2, 1, 'Initial Access', 'T1078 - Valid Accounts', e'Detects Azure Identity Protection impossible travel alerts where a user signs in from geographically distant locations in a timeframe that makes physical travel impossible. This strongly indicates credential theft or session hijacking. + +Next Steps: +1. Contact the user to verify both sign-in locations +2. Check if a VPN or proxy could explain the geolocation discrepancy +3. Review the sign-in details (device, browser, app) for both locations +4. If unauthorized, force password reset and revoke all sessions +5. Review data access and actions from the suspicious location +6. Enable location-based conditional access policies +7. Check for other users with similar patterns from the same locations +', '["https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks","https://attack.mitre.org/techniques/T1078/"]', e'contains("log.operationName", "Impossible Travel") || +(contains("log.properties", "riskEventType") && contains("log.properties", "impossibleTravel")) +', '2026-03-02 22:36:49.648902', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1295, 'Azure AD Federation Settings Modified', 3, 3, 3, 'Credential Access', 'T1556 - Modify Authentication Process', e'Detects modifications to Azure AD domain federation settings. Changing federation configuration is a critical attack technique that enables Golden SAML attacks and domain takeover, allowing attackers to forge authentication tokens for any user. + +Next Steps: +1. Immediately verify the federation modification was authorized +2. Check the user identity and source IP performing the change +3. Review the new federation settings for suspicious IdP configurations +4. Validate the signing certificate in the federation configuration +5. Check for subsequent sign-ins using federated authentication +6. If unauthorized, revert the federation changes and investigate all federated sessions +7. Review all privileged role assignments that occurred after the change +', '["https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/whatis-fed","https://attack.mitre.org/techniques/T1556/"]', e'contains("log.operationName", "Set federation settings on domain") || +(contains("log.operationName", "Set domain authentication") && contains("log.properties", "Federated")) +', '2026-03-02 22:36:50.988512', true, true, 'origin', null, '[]', '["lastEvent.log.operationName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1296, 'Azure Disk Snapshot Exfiltration', 3, 2, 1, 'Data Exfiltration', 'T1537 - Transfer Data to Cloud Account', e'Detects Azure disk snapshot operations that could be used for data exfiltration, including sharing snapshots across subscriptions, generating SAS URIs for download, or copying snapshots to external storage accounts. + +Next Steps: +1. Identify the disk snapshot and the virtual machine it was taken from +2. Review the target location or account where the snapshot is being shared +3. Verify the operator has authorization for cross-subscription snapshot operations +4. Check if a SAS URI was generated that could allow external download +5. Review the data sensitivity of the affected virtual machine\'s disk +6. If unauthorized, revoke any generated SAS tokens and delete shared snapshots +7. Implement Azure Policy to restrict snapshot sharing across subscriptions +8. Enable diagnostic logging for disk operations +', '["https://learn.microsoft.com/en-us/azure/virtual-machines/disks-incremental-snapshots","https://attack.mitre.org/techniques/T1537/"]', e'(contains("log.operationName", "Microsoft.Compute/snapshots") || + contains("log.operationName", "Microsoft.Compute/disks")) && +(contains("log.operationName", "beginGetAccess") || + contains("log.operationName", "export")) && +equals("log.resultType", "Success") +', '2026-03-02 22:36:52.358497', true, true, 'origin', null, '[]', '["lastEvent.log.operationName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1297, 'Azure AD Device Code Authentication Flow Detected', 3, 2, 1, 'Initial Access', 'T1078 - Valid Accounts', e'Detects OAuth device code flow authentication in Azure AD. Device code phishing is a growing attack vector where attackers trick users into authenticating on a device the attacker controls, granting the attacker access tokens. + +Next Steps: +1. Verify the device code authentication was initiated by the user on a legitimate device +2. Check the application requesting the device code for legitimacy +3. Review the source IP where the token was redeemed +4. Check for subsequent suspicious activities using the obtained token +5. If unauthorized, revoke the session and all refresh tokens +6. Consider blocking device code flow via conditional access policies +7. Educate users about device code phishing attacks +', '["https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code","https://attack.mitre.org/techniques/T1078/"]', e'(contains("log.properties", "deviceCode") && contains("log.operationName", "Sign-in")) || +contains("log.properties", "urn:ietf:params:oauth:grant-type:device_code") +', '2026-03-02 22:36:53.925728', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1298, 'AzureHound Reconnaissance Tool Detected', 2, 1, 0, 'Discovery', 'T1087.004 - Account Discovery: Cloud Account', e'Detects AzureHound user agent in Azure AD sign-in logs. AzureHound is the Azure AD data collector for BloodHound, used to enumerate all users, groups, roles, apps, and relationships in the tenant for attack path analysis. + +Next Steps: +1. Identify the user account running AzureHound +2. Determine if this is an authorized security assessment +3. Review the scope of data collected (users, groups, roles, apps) +4. Check for lateral movement or privilege escalation following the enumeration +5. If unauthorized, revoke the user\'s tokens and investigate +6. Review API permissions that allowed the enumeration +7. Consider implementing Graph API rate limiting or monitoring +', '["https://bloodhound.readthedocs.io/en/latest/data-collection/azurehound.html","https://attack.mitre.org/techniques/T1087/004/"]', e'contains("log.properties", "azurehound") || +contains("log.properties", "AzureHound") +', '2026-03-02 22:36:55.414706', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1299, 'Azure AD Privileged App Role Assignment', 3, 3, 2, 'Privilege Escalation', 'T1098.003 - Account Manipulation: Additional Cloud Roles', e'Detects privileged app role assignments to service principals in Azure AD, which is the mechanism used in illicit consent grant attacks. Attackers create or modify applications with high-privilege API permissions to access organizational data. + +Next Steps: +1. Review the application and the specific API permissions granted +2. Verify the consent was authorized by a legitimate administrator +3. Check if the application is known and trusted +4. Review the application publisher and redirect URIs +5. Check for data access using the application\'s permissions +6. If unauthorized, remove the role assignment and revoke application consent +7. Review and restrict user consent settings in Azure AD +', '["https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals","https://attack.mitre.org/techniques/T1098/003/"]', e'contains("log.operationName", "Add app role assignment to service principal") || +(contains("log.operationName", "Consent to application") && contains("log.properties", "AppRoleAssignment")) +', '2026-03-02 22:36:56.907396', true, true, 'origin', null, '[]', '["lastEvent.log.properties.targetResources","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1300, 'Azure AD Application Credential Added', 3, 3, 2, 'Persistence', 'T1098.001 - Account Manipulation: Additional Cloud Credentials', e'Detects when new certificates or client secrets are added to Azure AD application registrations. This is the primary Azure AD persistence technique - attackers add credentials to existing apps to maintain access even after password resets. + +Next Steps: +1. Verify the credential addition was authorized by the application owner +2. Identify the application and its permissions (especially Graph API permissions) +3. Check the user identity adding the credential for legitimacy +4. Review the credential type (certificate vs secret) and expiration +5. Check for subsequent sign-ins using the new application credential +6. If unauthorized, remove the credential and rotate all app secrets +7. Review the application\'s API permissions for excessive access +', '["https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal","https://attack.mitre.org/techniques/T1098/001/"]', e'oneOf("log.operationName", ["Add service principal credentials", "Update application - Certificates and secrets management"]) || +(contains("log.operationName", "application") && contains("log.properties", "KeyCredentials")) +', '2026-03-02 22:36:58.474218', true, true, 'origin', null, '[]', '["lastEvent.log.properties.targetResources","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1301, 'Azure AD Anomalous Token Detection', 3, 2, 1, 'Credential Access', 'T1528 - Steal Application Access Token', e'Detects Azure Identity Protection alerts for anomalous tokens with unusual lifetime, unfamiliar locations, or other suspicious properties. These indicate potential token theft or manipulation. + +Next Steps: +1. Review the token properties that triggered the anomaly detection +2. Check the user\'s recent sign-in activity for suspicious patterns +3. Verify the source IP and device used for the authentication +4. Check for impossible travel or unfamiliar location patterns +5. If compromise is suspected, revoke all refresh tokens for the user +6. Force MFA re-registration if MFA token was compromised +7. Review conditional access policies for token protection gaps +', '["https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks","https://attack.mitre.org/techniques/T1528/"]', e'contains("log.operationName", "Anomalous Token") || +(contains("log.properties", "riskEventType") && contains("log.properties", "anomalousToken")) +', '2026-03-02 22:36:59.892966', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1302, 'Azure Security Alert Suppression Rule Created', 2, 3, 2, 'Defense Evasion', 'T1562 - Impair Defenses', e'Detects creation of alert suppression rules in Azure Defender / Microsoft Defender for Cloud. Attackers create suppression rules to hide security alerts generated by their activities. + +Next Steps: +1. Review the suppression rule and what alert types it suppresses +2. Verify the rule creation was part of an authorized security operations workflow +3. Check the user identity for legitimate security team membership +4. Review recent security alerts that may have been suppressed +5. If unauthorized, delete the suppression rule and review suppressed alerts +6. Check for other defense evasion activities from the same user +', '["https://learn.microsoft.com/en-us/azure/defender-for-cloud/alerts-suppression-rules","https://attack.mitre.org/techniques/T1562/"]', e'regexMatch("log.operationName", "(?i)MICROSOFT\\\\.SECURITY/ALERTSSUPPRESSIONRULES/WRITE") +', '2026-03-02 22:37:01.427523', true, true, 'origin', null, '[]', '["lastEvent.log.operationName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1303, 'Azure AD Impossible Travel Sign-In Detection', 3, 2, 1, 'Credential Access', 'T1078 - Valid Accounts', e'Detects Azure AD sign-ins flagged as risky due to impossible travel, anonymous IP usage, or unfamiliar locations. These risk detections indicate potential credential compromise when a user authenticates from geographically impossible locations or through anonymizing services. + +Next Steps: +1. Review the sign-in details including IP addresses and geographic locations +2. Check if the user employs VPN services that could explain different locations +3. Verify with the user whether the sign-in attempts are legitimate +4. Review the risk level and risk detail provided by Azure AD Identity Protection +5. Check for MFA challenges and their outcomes during the sign-in +6. If compromised, immediately reset user credentials and revoke active sessions +7. Enable Conditional Access policies requiring MFA for risky sign-ins +8. Review Azure AD sign-in logs for other accounts from the same suspicious IPs +', '["https://learn.microsoft.com/en-us/azure/active-directory/identity-protection/concept-identity-protection-risks","https://attack.mitre.org/techniques/T1078/"]', e'contains("log.operationName", "Sign-in activity") && +(equals("log.properties.riskLevelDuringSignIn", "high") || + equals("log.properties.riskState", "atRisk") || + contains("log.properties.riskEventTypes", "impossibleTravel") || + contains("log.properties.riskEventTypes", "anonymizedIPAddress")) +', '2026-03-02 22:37:02.995700', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1304, 'Azure Automation Runbook Abuse', 3, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', e'Detects creation or modification of Azure Automation runbooks which can be abused for code execution with managed identity privileges. Attackers may create runbooks to execute arbitrary code, establish persistence, or perform lateral movement using the automation account\'s managed identity. + +Next Steps: +1. Review the runbook content for malicious scripts or commands +2. Verify the automation account\'s managed identity permissions +3. Check the user creating or modifying the runbook has authorization +4. Review the runbook schedule for unauthorized execution times +5. Examine the runbook\'s Run As account credentials +6. If unauthorized, disable the runbook and revoke the automation account\'s permissions +7. Review execution history for already-executed malicious runbooks +8. Implement RBAC to restrict automation account management +', '["https://learn.microsoft.com/en-us/azure/automation/automation-runbook-types","https://attack.mitre.org/techniques/T1059/"]', e'contains("log.operationName", "Microsoft.Automation") && +(contains("log.operationName", "runbooks/write") || + contains("log.operationName", "runbooks/publish") || + contains("log.operationName", "jobs/write") || + contains("log.operationName", "schedules/write")) && +equals("log.resultType", "Success") +', '2026-03-02 22:37:04.524865', true, true, 'origin', null, '[]', '["lastEvent.log.operationName","adversary.user"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/azure/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/azure/utm_group_rules_data_type.sql new file mode 100644 index 000000000..d31c3625a --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/azure/utm_group_rules_data_type.sql @@ -0,0 +1,46 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1259, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1260, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1261, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1262, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1263, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1264, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1265, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1266, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1267, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1268, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1269, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1270, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1271, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1272, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1273, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1274, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1275, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1276, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1277, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1278, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1279, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1280, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1281, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1282, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1283, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1284, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1285, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1286, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1287, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1288, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1289, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1290, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1291, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1292, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1293, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1294, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1295, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1296, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1297, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1298, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1299, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1300, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1301, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1302, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1303, 3, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1304, 3, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/bit-defender/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/bit-defender/utm_correlation_rules.sql new file mode 100644 index 000000000..9a58ffbcf --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/bit-defender/utm_correlation_rules.sql @@ -0,0 +1,380 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1100, 'USB-Based Malware Propagation', 3, 3, 2, 'Lateral Movement, Initial Access', 'T1091 - Replication Through Removable Media', e'Detects USB-based malware propagation attempts including autorun infections, removable media threats, and device control violations. This rule monitors for device control events and removable media access patterns that may indicate malware attempting to spread via USB devices. + +Next Steps: +1. Isolate the affected endpoint immediately to prevent further spread +2. Check device control logs for unauthorized USB device connections +3. Scan all removable media that were connected to the affected system +4. Review file creation/modification events on removable drives (especially autorun.inf) +5. Verify if similar events occurred on other endpoints in the network +6. Update device control policies to restrict USB usage if necessary +7. Consider implementing USB device whitelisting for critical systems +', '["https://attack.mitre.org/techniques/T1091/","https://www.bitdefender.com/business/support/en/77209-135324-event-types.html"]', e'(oneOf("log.eventType", ["device-control", "dp"]) && + (contains("log.restData", ["malware", "threat", "infection", "autorun", "suspicious"]) || + oneOf("log.severity", ["high", "critical", "4", "5"]))) || +(contains("log.requested", ["usb", "removable", "autorun"]) && + contains("log.restData", ["malware", "threat", "infection"])) +', '2026-03-02 15:08:08.501917', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-bitdefender-gz-*","with":[{"field":"log.hostId","operator":"filter_term","value":"{{.log.hostId}}"}],"or":null,"within":"now-30m","count":5}]', '["lastEvent.log.eventType","lastEvent.log.hostId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1101, 'Ransomware Behavior Detection', 3, 3, 3, 'Impact', 'T1486 - Data Encrypted for Impact', e'Detects ransomware behavior patterns including file encryption attempts, mass file modifications, and ransomware-specific malware types detected by Bitdefender GravityZone. + +Next Steps: +1. Immediately isolate the affected system from the network to prevent spread +2. Check for recent backup availability and integrity +3. Review process execution history on the affected host +4. Look for suspicious file modifications or mass encryption activities +5. Check for ransomware notes or changed file extensions +6. Investigate the source of infection (email attachments, downloads, RDP compromise) +7. Scan other systems for similar indicators +8. Consider engaging incident response team for containment and recovery +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1486/"]', e'(contains("log.message", ["ransomware", "ransom", "locky", "cerber", + "wannacry", "petya", "ryuk", "sodinokibi", "maze"]) || + contains("log.signatureID", "ransomware") || + (equals("log.severity", "10") && contains("log.eventType", "malware"))) && +exists("log.severity") +', '2026-03-02 15:08:10.423627', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-bitdefender-gz-*","with":[{"field":"log.hostId","operator":"filter_term","value":"{{.log.hostId}}"}],"or":null,"within":"now-10m","count":5}]', '["lastEvent.log.hostId","lastEvent.log.signatureID"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1102, 'Network-Based Threat Detection', 3, 2, 3, 'Command and Control', 'T1071 - Application Layer Protocol: Command and Control', e'Detects network-based threats including C2 communications, malicious network activity, and suspicious network connections identified by Bitdefender GravityZone. + +Next Steps: +1. Identify the affected host using log.hostId and check for other security events from this system +2. Review origin.ip to determine if it\'s a known malicious IP or C2 server +3. Check firewall logs for any blocked or allowed connections to/from the suspicious IP +4. Investigate running processes on the affected host for signs of malware +5. Review network traffic patterns for data exfiltration attempts +6. If ransomware is detected, immediately isolate the affected system +7. Collect network packet captures if available for deeper analysis +8. Check if other hosts have communicated with the same external IP address +9. Submit suspicious IPs to threat intelligence platforms for reputation checking +10. Document findings and update firewall rules to block confirmed malicious IPs +', '["https://attack.mitre.org/techniques/T1071/","https://www.bitdefender.com/business/support/en/77209-135324-event-types.html"]', e'(oneOf("log.eventType", ["network-sandboxing", "fw"]) && + oneOf("log.severity", ["high", "critical", "4", "5"])) || +(exists("origin.ip") && contains("log.eventType", "network") && + contains("log.restData", ["malware", "threat", "blocked", "c2", "botnet"])) || +(equals("log.severity", "critical") && contains("log.product", "network")) +', '2026-03-02 15:08:11.725118', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-bitdefender-gz-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":[{"indexPattern":"v11-log-antivirus-bitdefender-gz-*","with":[{"field":"log.hostId","operator":"filter_term","value":"{{.log.hostId}}"},{"field":"log.eventType","operator":"filter_term","value":"network-sandboxing"}],"or":null,"within":"now-4h","count":3}],"within":"now-2h","count":5}]', '["lastEvent.log.hostId","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1103, 'Multiple Malware Detections from Single Source', 3, 3, 2, 'Command and Control', 'T1105 - Ingress Tool Transfer', e'Detects when multiple malware threats are detected on a single host within a short time period. This could indicate a compromised system actively spreading malware or an attacker launching multiple malware variants. + +Next Steps: +1. Investigate the affected host: + - Identify the system using the hostId field + - Check if it\'s a critical system or server + - Review recent user activity on the host +2. Analyze the detected malware: + - Review the malware types and names detected (signatureID field) + - Check file paths and processes involved + - Determine if malware was successfully quarantined +3. Check for lateral movement: + - Look for connections from the affected host to other internal systems + - Review authentication logs for suspicious activity + - Check for file share access patterns +4. Remediation actions: + - Isolate the affected system if confirmed compromised + - Run full system scans on potentially affected systems + - Update antivirus signatures and definitions + - Consider reimaging if system is severely compromised +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1105/"]', e'equals("log.eventType", "AntiMalware") && +oneOf("log.severity", ["4", "5"]) && +exists("log.hostId") +', '2026-03-02 15:08:12.946636', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-bitdefender-gz-*","with":[{"field":"log.hostId","operator":"filter_term","value":"{{.log.hostId}}"},{"field":"log.eventType","operator":"filter_term","value":"AntiMalware"}],"or":null,"within":"now-1h","count":5}]', '["lastEvent.log.hostId","lastEvent.log.signatureID"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1104, 'Malware Outbreak Detection - Multiple Hosts Infected', 3, 3, 3, 'Command and Control', 'T1105 - Ingress Tool Transfer', e'Detects when the same malware signature or threat is detected on multiple endpoints within a short time window. This pattern indicates a potential malware outbreak spreading across the network environment. + +Next Steps: +1. Immediately isolate affected endpoints to prevent further spread +2. Identify the malware signature ID and research its capabilities and impact +3. Check network logs for lateral movement patterns between infected hosts +4. Review the initial infection vector - check email logs, web proxy logs, and USB device usage +5. Verify antivirus definitions are up-to-date on all endpoints +6. Conduct memory and disk forensics on patient zero if identifiable +7. Check for persistence mechanisms on infected systems +8. Review domain controller and authentication logs for credential compromise +9. Document all affected systems and timeline for incident response +10. Consider engaging incident response team if outbreak involves critical systems +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1105/"]', e'equals("log.eventType", "AntiMalware") && +oneOf("log.severity", ["4", "5"]) && +exists("log.signatureID") && +exists("log.syslogHostIP") +', '2026-03-02 15:08:14.258459', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-bitdefender-gz-*","with":[{"field":"log.signatureID","operator":"filter_term","value":"{{.log.signatureID}}"},{"field":"log.eventType","operator":"filter_term","value":"AntiMalware"}],"or":null,"within":"now-2h","count":10}]', '["lastEvent.log.signatureID","lastEvent.log.syslogHostIP"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1105, 'Bitdefender AV Policy Weakened', 3, 3, 2, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects when Bitdefender GravityZone antivirus policies are weakened by administrators, such as disabling real-time protection, reducing scan aggressiveness, or adding broad exclusions. This could indicate a compromised admin account or insider threat. + +Next Steps: +1. Identify the administrator who modified the policy +2. Verify the policy change was authorized through change management +3. Review the specific settings that were weakened +4. Check for concurrent suspicious activity on managed endpoints +5. Restore the previous policy configuration if unauthorized +6. Review admin account access logs for compromise indicators +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1562/001/"]', e'(contains("log.message", ["policy", "configuration", "setting"]) && + (contains("log.message", ["disabled", "weakened", "reduced", "lowered", "excluded"]) || + (contains("log.message", "real-time") && contains("log.message", "off")) || + (contains("log.message", "exclusion") && contains("log.message", "added")) || + (contains("log.message", "protection") && contains("log.message", "disabled")))) && +exists("log.severity") +', '2026-03-02 15:08:15.561087', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-bitdefender-gz-*","with":[{"field":"log.hostId","operator":"filter_term","value":"{{.log.hostId}}"}],"or":null,"within":"now-1h","count":3}]', '["lastEvent.log.eventType","lastEvent.log.hostId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1106, 'Bitdefender Console Used for Lateral Movement', 3, 3, 3, 'Lateral Movement', 'T1072 - Software Deployment Tools', e'Detects when the Bitdefender GravityZone management console is potentially being used to push malicious policies, scripts, or tasks to managed endpoints, indicating a compromised admin account being leveraged for lateral movement. + +Next Steps: +1. Review all recent task and policy deployments from the console +2. Identify the admin account used and verify its legitimacy +3. Check for unusual login patterns to the GravityZone console +4. Review the content of pushed policies for malicious configurations +5. Suspend the admin account if compromise is suspected +6. Audit all managed endpoints for signs of compromise +', '["https://attack.mitre.org/techniques/T1072/","https://www.bitdefender.com/business/support/en/77212-237089-event-types.html"]', e'(contains("log.message", ["remote task", "deploy", "push policy", "execute script"]) || + (contains("log.message", "task") && contains("log.message", "created") && + (contains("log.message", "scan") || contains("log.message", "install") || + contains("log.message", "uninstall") || contains("log.message", "execute")))) && +exists("log.severity") +', '2026-03-02 15:08:16.926735', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-bitdefender-gz-*","with":[{"field":"log.hostId","operator":"filter_term","value":"{{.log.hostId}}"}],"or":null,"within":"now-30m","count":10}]', '["lastEvent.log.eventType","lastEvent.log.hostId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1107, 'Bitdefender GravityZone Zero-Day Malware Detection', 3, 3, 2, 'Execution', 'T1203 - Exploitation for Client Execution', e'Detects potential zero-day malware identified by Bitdefender\'s advanced threat detection capabilities including HyperDetect and Sandbox Analyzer. These detection methods use behavioral analysis and machine learning to identify previously unknown threats. + +Next Steps: +1. Immediately isolate the affected system from the network to prevent lateral movement +2. Review the detection details including file path, process information, and threat indicators +3. Check if similar detections occurred on other systems in the environment +4. Collect the suspicious file/process for further analysis in a sandbox environment +5. Review system logs for any suspicious activities before and after the detection +6. Update security policies to block similar threats across the organization +7. Consider submitting the sample to Bitdefender for further analysis +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1203/"]', e'oneOf("log.eventType", ["HyperDetect Activity", "Sandbox Analyzer Detection", "hyperdetect"]) || +(equals("log.eventType", "avc") && equals("log.severity", "High")) +', '2026-03-02 15:08:18.064111', true, true, 'origin', null, '[]', '["lastEvent.log.hostId","lastEvent.log.syslogHostIP"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1108, 'Bitdefender GravityZone Suspicious Exclusion Added', 3, 3, 1, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects when exclusions are added to Bitdefender GravityZone that may allow malware to operate undetected. Attackers often add exclusions to antivirus software to prevent detection of their malicious tools and activities. + +Next Steps: +1. Review the exclusion details to determine what files, folders, or processes were excluded +2. Verify if the exclusion was authorized by security team or IT administrators +3. Check if the excluded path contains any suspicious executables or scripts +4. Review recent activity from the user who added the exclusion +5. If unauthorized, immediately remove the exclusion and scan the excluded locations +6. Consider implementing a change control process for antivirus exclusions +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1562/001/"]', e'equals("log.eventType", "exclusion_added") || +(oneOf("log.eventType", ["policy_change", "configuration_change"]) && + contains("log.requested", "exclusion")) +', '2026-03-02 15:08:19.240633', true, true, 'origin', null, '[]', '["lastEvent.log.eventType","lastEvent.log.hostId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1109, 'Rootkit Detection', 3, 3, 2, 'Defense Evasion', 'T1014 - Rootkit', e'Detects rootkit infections and kernel-level threats that attempt to hide malicious activity at the system level using Bitdefender GravityZone\'s advanced detection capabilities. + +Next Steps: +1. Immediately isolate the affected system from the network to prevent lateral movement +2. Capture a memory dump and disk image for forensic analysis +3. Check for signs of privilege escalation or kernel-level modifications +4. Review system logs for any suspicious driver installations or kernel module loading +5. Scan other systems in the same network segment for similar infections +6. Consider rebuilding the system from a known clean state as rootkits can be difficult to fully remove +7. Review how the rootkit was initially delivered (email attachment, exploit kit, etc.) +8. Update all security software and operating system patches +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1014/"]', e'equals("log.eventType", "malware_detected") && +oneOf("log.severity", ["high", "critical"]) && +( + contains("log.restData", ["rootkit", "kernel", "tdss", "zeroaccess", + "necurs", "bootkit", "alureon", "rustock", "sinowal"]) || + contains("log.requested", "rootkit") || + equals("log.signatureID", "rootkit") +) +', '2026-03-02 15:08:20.430868', true, true, 'origin', null, '[]', '["lastEvent.log.hostId","lastEvent.log.signatureID"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1110, 'Real-time Protection Disabled', 3, 3, 2, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects when real-time protection features are disabled on an endpoint. This is a critical security event as it leaves the system vulnerable to malware infections and requires immediate investigation. + +Next Steps: +1. Immediately investigate who disabled the real-time protection and why +2. Check if the action was authorized by IT security team +3. Review recent activity on the affected endpoint for signs of compromise +4. Re-enable real-time protection if the action was unauthorized +5. Check for any malware infections that may have occurred while protection was disabled +6. Review system logs for any suspicious activities during the protection downtime +7. Consider implementing additional controls to prevent unauthorized disabling of security tools +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1562/001/"]', e'exists("log.syslogHostIP") && +( + (equals("log.eventType", "modules") && + equals("log.product", "av") && + contains("log.restData", "real-time")) || + (equals("log.eventType", "Product ModulesStatus") && + oneOf("log.severity", ["4", "5"]) && + (contains("log.restData", "protection disabled") || + contains("log.restData", "real-time scanning disabled"))) +) +', '2026-03-02 15:08:21.638489', true, true, 'origin', null, '[]', '["lastEvent.log.eventType","lastEvent.log.syslogHostIP"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1111, 'Bitdefender GravityZone Quarantine Failure Detection', 3, 3, 2, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects when Bitdefender GravityZone fails to quarantine detected malware. This could indicate that the malware is actively resisting remediation attempts or that there are permission issues preventing proper quarantine. + +Next Steps: +- Immediately isolate the affected system from the network +- Check if the malware process is still running and attempt manual termination +- Verify antivirus permissions and ensure it has necessary privileges +- Review system logs for signs of privilege escalation or rootkit activity +- Consider reimaging the system if quarantine continues to fail +- Check for similar failures on other systems in the environment +- Investigate the specific malware detected and research its capabilities +- Review quarantine configuration and storage capacity +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1562/001/"]', e'oneOf("log.eventType", ["quarantine_failed", "quarantine_failure"]) || +(equals("log.eventType", "AntiMalware") && + (containsAll("log.requestToParse", ["quarantine", "fail"]) || + contains("log.restData", ["quarantine failed", "unable to quarantine", "failed to quarantine"]) || + (equals("log.severity", "failure") && contains("log.requestToParse", "quarantine")))) +', '2026-03-02 15:08:22.904073', true, true, 'origin', null, '[]', '["lastEvent.log.hostId","lastEvent.log.signatureID"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1112, 'Memory-Based Threat Detection', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1055 - Process Injection', e'Detects memory-based threats including process injection, memory manipulation, and fileless malware executing in memory based on Bitdefender GravityZone event types. + +Next Steps: +1. Identify the affected process and host using log.hostId and origin.path fields +2. Check if the process is legitimate or if it shows signs of compromise +3. Review the process tree to identify parent-child relationships +4. Look for other suspicious activities on the same host in the last hour +5. Collect memory dump if the process is still running +6. Analyze network connections from the affected process +7. Check for persistence mechanisms on the affected system +8. Isolate the host if active malicious behavior is confirmed +', '["https://attack.mitre.org/techniques/T1055/","https://www.bitdefender.com/business/support/en/77209-135324-event-types.html"]', e'exists("log.eventType") && +(oneOf("log.eventType", ["aph", "antiexploit", "hd"]) || + (exists("origin.path") && contains("origin.path", "memory"))) && +(oneOf("log.severity", ["critical", "high"]) || + exists("log.malwareName") || + exists("log.threatName")) +', '2026-03-02 15:08:24.053530', true, true, 'origin', null, '[]', '["lastEvent.log.hostId","adversary.path"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1113, 'Bitdefender GravityZone High Severity Threat Detection', 3, 3, 2, 'Execution', 'T1204.002 - User Execution: Malicious File', e'Detects high-severity malware threats identified by Bitdefender GravityZone that require immediate attention. This rule triggers on severity levels 8-10, which indicate critical threats such as trojans, ransomware, rootkits, or other advanced malware. + +Next Steps: +1. Immediately isolate the affected endpoint(s) from the network to prevent lateral movement +2. Review the threat details in Bitdefender GravityZone console: + - Check threat name and malware type from the event details + - Verify the affected file path and process information + - Review the action taken by Bitdefender (quarantine, delete, etc.) +3. Investigate the source of infection: + - Check origin.ip and origin.path for the malware source + - Review recent user activity and email attachments + - Look for similar threats on other endpoints +4. Perform forensic analysis: + - Collect memory dumps if rootkit or fileless malware is suspected + - Check for persistence mechanisms (registry, scheduled tasks, services) + - Review network connections from the affected host +5. Remediation actions: + - Ensure Bitdefender has successfully cleaned/quarantined the threat + - Run full system scan on affected and neighboring systems + - Update antivirus signatures and security policies + - Consider reimaging if system integrity is compromised +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1204/002/","https://attack.mitre.org/techniques/T1055/"]', 'oneOf("log.severity", ["8", "9", "10"]) && oneOf("log.eventType", ["avc", "malware_detected", "av"])', '2026-03-02 15:08:25.191189', true, true, 'origin', null, '[]', '["lastEvent.log.signatureID","lastEvent.log.syslogHostIP"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1114, 'Fileless Malware Detection', 2, 2, 2, 'Defense Evasion, Privilege Escalation', 'T1055 - Process Injection', e'Detects fileless malware attacks including PowerShell-based attacks, memory injection, and living-off-the-land techniques using Bitdefender GravityZone\'s HyperDetect and Command-Line Scanner modules. These attacks execute malicious code directly in memory without writing to disk, making them harder to detect with traditional antivirus. + +Next Steps: +- Isolate the affected endpoint immediately to prevent lateral movement +- Review process tree to identify the parent process and initial attack vector +- Check for PowerShell command history and script blocks (Event ID 4104) +- Look for suspicious WMI activity or unusual process spawning patterns +- Examine network connections from the affected process +- Collect memory dump if the process is still running +- Review user activity to determine if account is compromised +- Apply security patches if exploitation of vulnerability is suspected +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1055/","https://www.bitdefender.com/en-us/business/gravityzone-platform/fileless-attack-defense"]', e'oneOf("log.eventType", ["fileless_attack", "hyperdetect_fileless", "command_line_scanner"]) || +( + equals("log.eventType", "malware_detected") && + contains("log.restData", ["fileless", "memory injection", "powershell", + "wscript", "cscript", "mshta", "regsvr32", "rundll32"]) +) || +( + oneOf("log.severity", ["HIGH", "CRITICAL"]) && + contains("log.restData", "code injection") +) +', '2026-03-02 15:08:26.361516', true, true, 'origin', null, '[]', '["lastEvent.log.eventType","lastEvent.log.hostId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1115, 'Email-Based Threat Spreading', 3, 3, 2, 'Initial Access', 'T1566 - Phishing', e'Detects email-based malware spreading including phishing attempts, malicious attachments, and email-borne threats through Bitdefender\'s Exchange protection. This rule triggers on Exchange-specific malware events and monitors for patterns of email-based threats. + +Next Steps: +1. Investigate the affected email and sender: + - Check the sender\'s email address and domain reputation + - Review email headers for spoofing indicators + - Analyze attachment hash values if present in log.restData + - Check log.severity for threat level assessment +2. Review related events: + - Look for similar events from the same sender or to other recipients + - Check if the email was delivered or blocked + - Verify if any users clicked links or opened attachments + - Search for the same signatureID across other hosts +3. Remediation actions: + - If delivered, recall the email from all recipients immediately + - Reset credentials if phishing was successful + - Block sender domain/IP at email gateway + - Update email security policies if needed + - Scan affected endpoints for malware if attachments were opened + - Update Bitdefender Exchange protection rules +4. Investigation commands: + - Check host status: Verify log.hostId endpoint protection status + - Review product version: Ensure log.productVersion is up to date + - Analyze event patterns: Look for unusual log.eventType combinations +', '["https://attack.mitre.org/techniques/T1566/","https://www.bitdefender.com/business/support/en/77209-135324-event-types.html"]', e'oneOf("log.eventType", ["exchange-malware", "exchange-user-credentials", "exchange-organization-info"]) || +(contains("log.eventType", "exchange") && equals("log.severity", "High")) || +(contains("log.product", "Exchange") && contains("log.eventType", ["malware", "phishing"])) +', '2026-03-02 15:08:27.623193', true, true, 'origin', null, '[]', '["lastEvent.log.hostId","lastEvent.log.signatureID"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1116, 'Crypto-Mining Detection', 2, 2, 3, 'Impact', 'T1496 - Resource Hijacking', e'Detects cryptocurrency mining activities including miners, coin miners, and cryptojacking attempts detected by Bitdefender GravityZone. + +Next Steps: +- Review the affected endpoint details (hostname, IP) to identify the compromised system +- Check CPU and memory usage patterns on the affected system for unusual spikes +- Look for network connections to known mining pools or suspicious outbound traffic +- Search for related processes running with names like xmrig, minerd, cgminer, or bfgminer +- Review recent file downloads and installations on the affected system +- Check for persistence mechanisms (scheduled tasks, startup items, services) +- Isolate the affected system if active mining is confirmed +- Run a full system scan with updated definitions +- Consider reimaging the system if compromise is extensive', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1496/"]', e'equals("log.productVendor", "Bitdefender") && +equals("log.product", "GravityZone") && +( + contains("log.eventType", ["miner", "coin", "crypto", "CoinMiner"]) || + contains("log.requested", ["miner", "coin", "xmr", "monero", "bitcoin", + "ethereum", "xmrig", "minerd", "cgminer", "bfgminer", "coinhive"]) +) +', '2026-03-02 15:08:28.945981', true, true, 'origin', null, '[]', '["adversary.host","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1117, 'Bootkit/UEFI Threat Detection', 3, 3, 3, 'Defense Evasion, Persistence', 'T1542.001 - Boot or Logon Autostart Execution: System Firmware', e'Detects bootkit or UEFI-level threats that attempt to persist at the firmware level and compromise the boot process. These threats can survive system reinstalls and bypass traditional security measures by infecting the system firmware. + +Next Steps: +- Isolate the affected system immediately to prevent spread +- Review system boot logs and firmware settings for modifications +- Check for other malware detections on the same host in the past 24-48 hours +- Verify system integrity using offline scanning tools +- Consider reimaging the system and updating firmware/UEFI +- Enable Secure Boot if not already enabled +- Review user activity and recently installed software on the affected system +- Document the infection for incident response reporting +- Check if other systems with similar hardware/firmware versions are affected +', '["https://attack.mitre.org/techniques/T1542/001/","https://www.bitdefender.com/business/support/en/77209-135324-event-types.html"]', e'equals("log.eventType", "av") && +greaterOrEqual("log.severity", 8) && +( + contains("log.requested", ["boot", "uefi", "rootkit", "firmware"]) || + contains("log.restData", ["boot", "uefi", "rootkit", "firmware", + "\\\\EFI\\\\", "/EFI/", "\\\\boot\\\\", "/boot/"]) +) +', '2026-03-02 15:08:30.211693', true, true, 'origin', null, '[]', '["lastEvent.log.hostId","lastEvent.log.severity"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1118, 'Advanced Persistent Threat (APT) Detection', 3, 3, 2, 'Command and Control', 'TA0011 - Application Layer Protocol', e'Detects indicators of Advanced Persistent Threats including targeted attacks, sophisticated malware, and persistent threats detected by Bitdefender GravityZone\'s HyperDetect module. + +Next Steps: +- Investigate the affected endpoint to determine the scope of compromise +- Review process execution history and network connections from the affected system +- Check for lateral movement by examining authentication logs from the same source IP +- Isolate the affected system if active threat is confirmed +- Collect forensic artifacts including memory dumps and event logs +- Search for similar malware indicators across the environment +- Review user account activities for signs of credential compromise +- Contact security operations center if threat actors match known APT groups +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/tactics/TA0011/"]', e'equals("log.product", "Bitdefender GravityZone") && +greaterOrEqual("log.severity", 8) && +( + contains("log.eventType", ["apt", "targeted", "advanced", "persistent", "hyperdetect"]) || + contains("log.restData", ["apt", "targeted attack", "advanced persistent", + "lazarus", "equation", "sofacy", "cozy bear", "fancy bear", + "panda", "kitten", "carbanak", "fin7", "fileless"]) || + equals("log.signatureID", "hyperdetect") +) && +exists("log.hostId") +', '2026-03-02 15:08:31.607874', true, true, 'origin', null, '[]', '["lastEvent.log.eventType","lastEvent.log.hostId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1119, 'Antivirus Service Stopped or Disabled', 2, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects when the Bitdefender antivirus service or critical security modules are stopped, disabled, or experiencing failures. This is a critical security event that could indicate malicious tampering or system issues. + +Next Steps: +- Verify if the service was intentionally stopped by authorized personnel +- Check system logs for any errors or crashes that may have caused the service to stop +- Look for signs of malware or unauthorized access attempts around the time of the event +- Review recent system changes or updates that might have affected the antivirus service +- If tampering is suspected, isolate the affected system and perform a forensic analysis +- Restart the Bitdefender service and ensure all modules are functioning properly +- Monitor for recurring issues that might indicate persistent threats +', '["https://www.bitdefender.com/business/support/en/77212-237089-event-types.html","https://attack.mitre.org/techniques/T1562/001/"]', e'(equals("log.eventType", "modules") || + equals("log.eventType", "Product ModulesStatus") || + equals("log.eventType", "registration")) && +(oneOf("log.severity", ["high", "5"]) || + contains("log.product", "disabled") || + contains("log.product", "stopped") || + (contains("log.restData", "module") && contains("log.restData", "stopped")) || + (contains("log.restData", "module") && contains("log.restData", "disabled")) || + (contains("log.restData", "av") && contains("log.restData", "failure"))) +', '2026-03-02 15:08:32.822661', true, true, 'origin', null, '[]', '["lastEvent.log.eventType","lastEvent.log.hostId"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/bit-defender/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/bit-defender/utm_group_rules_data_type.sql new file mode 100644 index 000000000..b827a580f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/bit-defender/utm_group_rules_data_type.sql @@ -0,0 +1,20 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1100, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1101, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1102, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1103, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1104, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1105, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1106, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1107, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1108, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1109, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1110, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1111, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1112, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1113, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1114, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1115, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1116, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1117, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1118, 38, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1119, 38, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/cisco-asa/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/cisco-asa/utm_correlation_rules.sql new file mode 100644 index 000000000..72d3882e0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/cisco-asa/utm_correlation_rules.sql @@ -0,0 +1,48 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1168, 'Multiple Failed VPN Authentication Attempts', 3, 2, 1, 'Credential Access', 'T1110 - Brute Force', e'Detects multiple failed VPN authentication attempts from the same source IP address, which could indicate a brute force attack or password guessing attempt against VPN credentials. + +Next Steps: +- Review the source IP address and check if it is known or authorized +- Check for successful authentication attempts from the same IP after failed attempts +- Verify if the targeted user accounts exist and are active +- Consider temporarily blocking the source IP if attack continues +- Review VPN access logs for any unusual patterns or other indicators +- Contact the user if the IP is associated with a legitimate user to verify activity +', '["https://attack.mitre.org/techniques/T1110/","https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog/syslogs1.html"]', e'(equals("log.messageId", "113015") || + equals("log.messageId", "113021") || + equals("log.messageId", "109034") || + equals("log.messageId", "611102")) && +exists("origin.ip") && +(regexMatch("log.reason", "(?i)(invalid|failed|rejected|authentication)") || + regexMatch("log.message", "(?i)(authentication.*failed|invalid.*password)")) +', '2026-03-02 16:23:36.293561', true, true, 'origin', null, '[{"indexPattern":"v11-log-firewall-cisco-asa-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.messageId","operator":"filter_term","value":"113015"}],"or":[{"indexPattern":"v11-log-firewall-cisco-asa-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.messageId","operator":"filter_term","value":"113021"}],"or":null,"within":"now-15m","count":10},{"indexPattern":"v11-log-firewall-cisco-asa-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.messageId","operator":"filter_term","value":"109034"}],"or":null,"within":"now-15m","count":10},{"indexPattern":"v11-log-firewall-cisco-asa-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.messageId","operator":"filter_term","value":"611102"}],"or":null,"within":"now-15m","count":10}],"within":"now-15m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1169, 'IPS Signature Match - Malicious Pattern Detected', 3, 3, 2, 'Initial Access', 'T1190 - Exploit Public-Facing Application', e'Detects when ASA IPS features identify malicious patterns in network traffic. Message ID 108003 indicates ESMTP/SMTP connections terminated due to malicious patterns. Also monitors for general IPS/IDS signature matches and threat intelligence hits. + +Next Steps: +1. Review the specific IPS signature that was triggered and understand its severity +2. Investigate the source IP address for reputation and previous malicious activity +3. Check if the target system shows any signs of compromise +4. Review firewall logs for any successful connections from the same source +5. Consider blocking the source IP if multiple signatures are triggered +6. Verify that IPS signatures are up-to-date +7. Document the incident and any actions taken +', '["https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog.html","https://attack.mitre.org/techniques/T1190/"]', e'equals("log.messageId", "108003") +|| (contains("log.message", "malicious pattern") && contains("log.message", ["detected", "terminated", "blocked"])) +|| (contains("log.message", "IPS") && contains("log.message", "signature") && contains("log.message", ["matched", "triggered", "detected"])) +|| oneOf("log.action", ["ips_alert", "ids_alert", "threat_detected"]) +', '2026-03-02 16:23:37.737324', true, true, 'origin', null, '[{"indexPattern":"v11-log-firewall-cisco-asa-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-15m","count":3}]', '["adversary.ip","target.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1170, 'Botnet Command and Control Traffic Detected', 3, 2, 1, 'Command and Control', 'T1071 - Application Layer Protocol', e'Detects botnet command and control traffic identified by Cisco ASA\'s dynamic filter/botnet database. Message IDs 338001-338002 indicate blacklisted traffic from/to malicious addresses. This could indicate compromised hosts communicating with known botnet infrastructure. + +Next Steps: +1. Immediately isolate the affected host(s) to prevent further communication with C2 infrastructure +2. Review the source IP address (origin.ip) to identify the compromised internal host +3. Check the destination IP/domain against threat intelligence sources to confirm malicious nature +4. Examine other logs from the affected host for signs of initial compromise or lateral movement +5. Run full antivirus/anti-malware scans on the affected system +6. Review DNS logs for additional suspicious queries from the same host +7. Check for any data exfiltration attempts or unusual outbound traffic patterns +8. Consider reimaging the affected system if compromise is confirmed +', '["https://www.cisco.com/c/en/us/td/docs/security/asa/special/botnet/asa-botnet.pdf","https://attack.mitre.org/techniques/T1071/"]', e'oneOf("log.messageId", ["338001", "338002"]) +|| regexMatch("log.message", "botnet.*(detected|blocked|dropped)") +|| contains("log.message", "dynamic filter blacklisted") +|| contains("log.message", "malicious address") +', '2026-03-02 16:23:38.875569', true, true, 'origin', null, '[]', '["adversary.ip","target.ip"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/cisco-asa/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/cisco-asa/utm_group_rules_data_type.sql new file mode 100644 index 000000000..21e1ae7dc --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/cisco-asa/utm_group_rules_data_type.sql @@ -0,0 +1,3 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1168, 6, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1169, 6, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1170, 6, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/cisco-firepower/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/cisco-firepower/utm_correlation_rules.sql new file mode 100644 index 000000000..16da51d9e --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/cisco-firepower/utm_correlation_rules.sql @@ -0,0 +1,88 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1174, 'Command and Control on Non-Standard Ports', 3, 2, 1, 'Command and Control', 'T1571 - Non-Standard Port', e'Detects connections on non-standard ports that may indicate command and control (C2) communication. Identifies HTTP traffic on non-HTTP ports, encrypted traffic on unexpected ports, and application protocol mismatches detected by Firepower\'s application identification engine. + +Next Steps: +1. Investigate the internal host initiating the suspicious connection +2. Review the destination IP against threat intelligence feeds +3. Analyze the application identification results for protocol anomalies +4. Check if the destination port is commonly used for C2 frameworks +5. Examine the connection duration and data transfer patterns +6. Consider blocking the destination IP and scanning the internal host +', '["https://www.cisco.com/c/en/us/td/docs/security/secure-firewall/management-center/device-config/710/management-center-device-config-71/connection-log-fields.html","https://attack.mitre.org/techniques/T1571/"]', e'exists("log.appProto") && +exists("origin.ip") && +exists("target.ip") && +((contains("log.appProto", "HTTP") && !oneOf("target.port", [80, 443, 8080, 8443, 8000, 8888])) || + (contains("log.appProto", "SSL") && !oneOf("target.port", [443, 8443, 993, 995, 465, 636])) || + equals("log.appProto", "unknown-tcp")) && +equals("log.initiatorPackets", true) +', '2026-03-02 16:38:33.554409', true, true, 'origin', null, '[{"indexPattern":"v11-log-firewall-cisco-firepower-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"target.ip","operator":"filter_term","value":"{{.target.ip}}"}],"or":null,"within":"now-1h","count":5}]', '["adversary.ip","target.ip","target.port"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1175, 'Firepower IOC (Indicator of Compromise) Detection', 3, 3, 2, 'Initial Access', 'T1566 - Phishing', e'Detects when Firepower identifies an Indicator of Compromise (IOC), indicating a host may be infected with malware or compromised. IOCs can include file hashes, malware signatures, or behavioral patterns that suggest malicious activity. + +Next Steps: +1. Immediately isolate the affected host from the network to prevent lateral movement +2. Review the specific IOC details including threat name, SHA256 hash, and file path +3. Search for the same IOC across other endpoints in your environment +4. Check if the affected host has made any suspicious network connections recently +5. Collect memory dumps and disk images for forensic analysis if required +6. Review user activity logs to identify potential initial compromise vector +7. Update antivirus signatures and threat intelligence feeds with new IOC data +8. Perform deep scan of the affected system and related network segments +9. Consider reimaging the affected system after complete evidence collection +10. Update security controls to prevent similar future compromises +', '["https://www.cisco.com/c/en/us/td/docs/security/firepower/70/configuration/guide/fpmc-config-guide-v70/file_malware_events_and_network_file_trajectory.html","https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1566/"]', '(equals("log.eventType", "AMP_IOC") || equals("log.eventType", "IOC_DETECTED") || contains("log.message", "indication of compromise") || contains("log.message", "IOC")) && exists("origin.ip") && (exists("log.threatName") || exists("log.sha256") || exists("log.fileName"))', '2026-03-02 16:38:34.877284', true, true, 'origin', null, '[]', '["adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1176, 'Threat Intelligence Director (TID) Alert Detection', 3, 3, 2, 'Command and Control', 'T1071.001 - Application Layer Protocol: Web Protocols', e'Detects when Cisco Firepower Threat Intelligence Director identifies connections to known malicious indicators including IPs, domains, URLs, and SHA256 hashes from threat feeds. This rule triggers when TID blocks or would block connections based on threat intelligence matches with high confidence scores. + +Next Steps: +- Immediately isolate the affected system if the connection was not blocked +- Review the specific threat indicator (IP/domain/URL/hash) that triggered the alert +- Check the threat category and score to understand the severity +- Investigate all recent network activity from the source IP address +- Search for similar indicators across other systems in the network +- Review endpoint logs for signs of malware or suspicious processes +- If a file hash triggered the alert, locate and quarantine the file +- Check if other systems have communicated with the same malicious indicator +- Update firewall rules to ensure the indicator is blocked network-wide +- Report the incident to the security team for further investigation +', '["https://www.cisco.com/c/en/us/td/docs/security/firepower/70/configuration/guide/fpmc-config-guide-v70/tid_overview.html","https://attack.mitre.org/techniques/T1071/"]', e'equals("log.eventType", "TID_EVENT") && +(equals("log.action", "BLOCK") || + equals("log.action", "WOULD_BLOCK") || + exists("log.tidIndicatorType")) && +(exists("log.tidCategory") || + greaterOrEqual("log.threatScore", 80)) +', '2026-03-02 16:38:36.144371', true, true, 'origin', null, '[]', '["lastEvent.log.tidIndicator","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1177, 'Intrusion Prevention System High Priority Events', 3, 3, 3, 'Execution', 'T1203 - Exploitation for Client Execution', e'Detects high priority IPS events from Cisco Firepower indicating potential exploitation attempts, zero-day attacks, or critical vulnerabilities being targeted. These events represent immediate threats that require urgent investigation. + +Next Steps: +1. Immediately isolate the affected system if the attack was successful +2. Review the specific signature ID and classification to understand the attack vector +3. Check if the target system shows signs of compromise (unusual processes, network connections, file modifications) +4. Analyze firewall logs to determine if the attack was blocked or if any malicious traffic passed through +5. Search for similar attempts from the same source IP across other systems +6. Update IPS signatures and ensure all systems are patched against the exploited vulnerability +7. Consider blocking the source IP if it shows persistent malicious behavior +8. Document the incident and update security controls based on findings +', '["https://www.cisco.com/c/en/us/td/docs/security/secure-firewall/management-center/device-config/710/management-center-device-config-71/intrusion-overview.html","https://attack.mitre.org/techniques/T1203/"]', e'equals("log.eventType", "IPS_EVENT") && +(equals("log.priority", 1) || + lessOrEqual("log.severity", 2) || + equals("log.impact", "HIGH") || + contains("log.classification", "attempted-admin") || + contains("log.classification", "attempted-user") || + contains("log.classification", "web-application-attack") || + contains("log.classification", "exploit-kit")) +', '2026-03-02 16:38:37.496057', true, true, 'origin', null, '[]', '["adversary.ip","target.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1178, 'Advanced Malware Protection (AMP) Alert Detection', 3, 3, 2, 'Initial Access', 'T1566 - Phishing', e'Detects when Cisco Firepower Advanced Malware Protection (AMP) identifies malware or malicious files, including retrospective detections and high threat score files. This rule triggers on various malware dispositions including confirmed malware, custom detections, retrospective malware (files later identified as malicious), and files with high threat scores (>=70). + +Next Steps: +1. Identify the affected host using the origin.ip and log.deviceName fields +2. Review the file hash (log.sha256) in threat intelligence databases +3. Check if the malware was successfully blocked or if remediation is needed +4. Look for lateral movement attempts from the affected host +5. Verify if other hosts accessed the same malicious file +6. Consider isolating the affected system if malware execution is confirmed +7. Review the file trajectory to understand the infection vector +8. Update endpoint protection rules to prevent similar infections +', '["https://www.cisco.com/c/en/us/td/docs/security/firepower/70/configuration/guide/fpmc-config-guide-v70/file_malware_events_and_network_file_trajectory.html","https://attack.mitre.org/techniques/T1566/"]', e'equals("log.eventType", "MALWARE_EVENT") && +(equals("log.disposition", "MALWARE") || + equals("log.disposition", "CUSTOM_DETECTION") || + equals("log.disposition", "RETROSPECTIVE_MALWARE") || + greaterOrEqual("log.threatScore", 70)) +', '2026-03-02 16:38:38.796270', true, true, 'origin', null, '[]', '["lastEvent.log.sha256","adversary.ip"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/cisco-firepower/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/cisco-firepower/utm_group_rules_data_type.sql new file mode 100644 index 000000000..c4d45e7a4 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/cisco-firepower/utm_group_rules_data_type.sql @@ -0,0 +1,5 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1174, 13, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1175, 13, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1176, 13, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1177, 13, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1178, 13, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/cisco-meraki/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/cisco-meraki/utm_correlation_rules.sql new file mode 100644 index 000000000..5090d432d --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/cisco-meraki/utm_correlation_rules.sql @@ -0,0 +1,112 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1179, 'Meraki Client VPN Brute Force Attempts', 3, 2, 1, 'Credential Access', 'T1110 - Brute Force', e'Detects multiple failed client VPN authentication attempts from the same source IP on Meraki MX appliances, indicating potential brute force attacks against VPN credentials. + +Next Steps: +1. Review the source IP address and check geographic location +2. Verify if the targeted user account exists and is active +3. Check for any successful VPN connections from the same IP +4. Consider blocking the source IP at the MX appliance +5. Review VPN authentication settings and ensure MFA is enabled +6. Notify the targeted user if the account is legitimate +', '["https://documentation.meraki.com/MX/Client_VPN/Client_VPN_Overview","https://attack.mitre.org/techniques/T1110/"]', e'(oneOf("log.eventType", ["vpn_auth_failure", "client_vpn_auth_failure"]) || + (contains("log.message", "VPN") && contains("log.message", ["auth fail", "authentication failed", "invalid credentials"]))) && +exists("origin.ip") +', '2026-03-02 16:46:57.549445', true, true, 'origin', null, '[{"indexPattern":"v11-log-firewall-meraki-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-15m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1180, 'Wireless Intrusion Attempts', 3, 3, 2, 'Reconnaissance', 'T1595.002 - Active Scanning: Vulnerability Scanning', e'Detects wireless intrusion attempts including deauthentication attacks, association floods, and other wireless-specific attack patterns that could compromise the wireless network integrity. + +Next Steps: +1. Review the wireless access point logs for the affected device +2. Identify the source MAC address and physical location if possible +3. Check for unauthorized devices or rogue access points in the vicinity +4. Verify wireless security configurations and update if necessary +5. Consider implementing additional wireless monitoring and detection capabilities +6. Document the incident and update security policies if needed +', '["https://documentation.meraki.com/General_Administration/Monitoring_and_Reporting/Syslog_Event_Types_and_Log_Samples","https://attack.mitre.org/techniques/T1595/002/"]', e'equals("log.eventType", "wids_alerted") || +(equals("log.type", "airmarshal_events") && + (contains("log.subtype", "attack") || + contains("log.subtype", "flood") || + contains("log.subtype", "deauth"))) || +(contains("log.message", "deauthentication attack") || + contains("log.message", "association flood") || + contains("log.message", "wireless intrusion")) +', '2026-03-02 16:46:59.169784', true, true, 'origin', '["adversary.ip","target.mac"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1181, 'Rogue SSID Detection', 3, 3, 2, 'Initial Access', 'T1200 - Hardware Additions', e'Detects when a rogue SSID is identified in the wireless environment. This could indicate an evil twin attack or unauthorized access point deployment attempting to intercept wireless traffic or credentials. + +Next Steps: +1. Immediately investigate the rogue access point\'s physical location using the MAC address +2. Check if the rogue SSID name matches legitimate corporate SSIDs (potential evil twin attack) +3. Verify if the rogue AP is broadcasting from an unauthorized location +4. Review wireless client connection logs for any devices that may have connected to the rogue SSID +5. Consider performing a physical sweep of the area to locate and remove the unauthorized device +6. Update wireless intrusion detection policies if needed +7. Notify security team and facilities management for potential physical security breach +', '["https://documentation.meraki.com/General_Administration/Monitoring_and_Reporting/Syslog_Event_Types_and_Log_Samples","https://attack.mitre.org/techniques/T1200/"]', e'equals("log.eventType", "rogue_ssid_detected") || +(equals("log.type", "airmarshal_events") && + equals("log.subtype", "rogue_ssid_detected")) || +(contains("log.message", "rogue") && + contains("log.message", "SSID")) +', '2026-03-02 16:47:00.736696', true, true, 'origin', null, '[]', '["adversary.mac"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1182, 'Meraki IDS High Priority Intrusion Alert', 3, 3, 2, 'Initial Access', 'T1190 - Exploit Public-Facing Application', e'Detects high and medium priority intrusion detection alerts from Meraki IDS/IPS system. These alerts indicate potential exploitation attempts, malicious traffic patterns, or known attack signatures detected by the Snort engine. + +Next Steps: +1. Review the specific signature that triggered the alert and assess its severity +2. Investigate the source IP for additional malicious activity or reputation +3. Check if the destination system shows signs of compromise +4. Verify if this is part of a larger attack campaign by correlating with other security events +5. Consider blocking the source IP if confirmed malicious +6. Review firewall rules and IPS signatures for potential tuning +7. Document the incident and update threat intelligence feeds if applicable +', '["https://documentation.meraki.com/General_Administration/Monitoring_and_Reporting/Syslog_Event_Types_and_Log_Samples","https://attack.mitre.org/techniques/T1190/"]', e'equals("log.eventType", "security_event") && +equals("log.alertType", "ids_alerted") && +lessOrEqual("log.priority", 2) && +exists("origin.ip") && +exists("target.ip") +', '2026-03-02 16:47:02.274134', true, true, 'origin', null, '[]', '["lastEvent.log.signature","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1183, 'Evil Twin Access Point Detection', 3, 3, 1, 'Wireless Security', 'T1557 - Adversary-in-the-Middle', e'Detects evil twin attacks where a rogue access point mimics a legitimate corporate SSID to intercept wireless traffic. Meraki Air Marshal identifies spoofed SSIDs that match corporate network names but originate from unauthorized hardware. + +Next Steps: +1. Verify the detected SSID against authorized access point inventory +2. Check the BSSID (MAC address) against known Meraki access points +3. Use Air Marshal containment features to prevent client connections +4. Physically locate the rogue AP using signal strength triangulation +5. Check if any clients have connected to the rogue AP +6. Review network traffic from affected clients for signs of credential theft +', '["https://documentation.meraki.com/MR/Monitoring_and_Reporting/Air_Marshal","https://attack.mitre.org/techniques/T1557/"]', e'equals("log.eventType", "airmarshal_events") && +(equals("log.type", "ssid_spoofing") || + equals("log.type", "rogue_ssid_detected") || + (contains("log.message", "SSID Spoofing") || contains("log.message", "Evil Twin"))) && +exists("log.bssid") +', '2026-03-02 16:47:03.654329', true, true, 'origin', null, '[]', '["lastEvent.log.bssid","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1184, 'Air Marshal Rogue Access Point Detection', 3, 3, 2, 'Initial Access', 'T1200 - Hardware Additions', e'Detects when Meraki Air Marshal identifies rogue access points or unauthorized SSIDs in the wireless environment. This could indicate malicious wireless infrastructure attempting to intercept traffic or perform man-in-the-middle attacks. + +Next Steps: +1. Verify if the detected BSSID and SSID are known legitimate access points that may not be properly registered +2. Check the RSSI value to determine proximity - higher values indicate the rogue AP is closer to your infrastructure +3. Use wireless scanning tools to physically locate the rogue access point using the BSSID +4. Review network traffic logs for any suspicious connections to unknown wireless networks +5. Check if any sensitive data might have been exposed through connections to the rogue AP +6. Consider implementing MAC address filtering or 802.1X authentication to prevent unauthorized connections +7. Document the incident and update the wireless security policy if needed +', '["https://documentation.meraki.com/MR/Monitoring_and_Reporting/Air_Marshal","https://attack.mitre.org/techniques/T1200/"]', e'equals("log.eventType", "airmarshal_events") && +equals("log.type", "rogue_ssid_detected") && +exists("log.bssid") && +greaterOrEqual("log.rssi", -50) +', '2026-03-02 16:47:05.132607', true, true, 'origin', null, '[]', '["adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1185, 'Meraki Advanced Malware Protection Alert', 3, 3, 2, 'Execution', 'T1204.002 - User Execution: Malicious File', e'Detects when Meraki Advanced Malware Protection (AMP) identifies malicious files being downloaded or executed on the network. This includes retrospective alerts where files previously considered safe are later identified as malicious. + +Next Steps: +1. Immediately isolate the affected system(s) from the network to prevent lateral movement +2. Review the malware details including file hash, name, and threat severity in the Meraki dashboard +3. Check if the malicious file was executed or only downloaded +4. Scan other systems for the same file hash to identify additional infections +5. Review network traffic logs from the affected IP for suspicious communications +6. If file was executed, perform full system scan and consider reimaging the affected device +7. Update endpoint protection signatures and ensure all systems are patched +8. Document the incident and update security policies if needed +', '["https://documentation.meraki.com/MX/Content_Filtering_and_Threat_Protection/Advanced_Malware_Protection_(AMP)","https://attack.mitre.org/techniques/T1204/002/"]', e'equals("log.eventType", "security_event") && +(contains("log.message", "malware") || + contains("log.message", "AMP") || + contains("log.message", "malicious") || + equals("log.action", "malware_blocked") || + contains("log.eventName", "Advanced Malware Protection")) && +exists("origin.ip") +', '2026-03-02 16:47:06.658050', true, true, 'origin', null, '[]', '["adversary.hostname","adversary.ip"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/cisco-meraki/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/cisco-meraki/utm_group_rules_data_type.sql new file mode 100644 index 000000000..210db52f9 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/cisco-meraki/utm_group_rules_data_type.sql @@ -0,0 +1,7 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1179, 5, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1180, 5, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1181, 5, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1182, 5, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1183, 5, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1184, 5, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1185, 5, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/cisco-switch/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/cisco-switch/utm_correlation_rules.sql new file mode 100644 index 000000000..83d700936 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/cisco-switch/utm_correlation_rules.sql @@ -0,0 +1,48 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1171, 'MAC Address Spoofing Detection', 2, 3, 1, 'Initial Access', 'MAC Spoofing', e'Detects potential MAC address spoofing attempts by monitoring for MAC address flapping between ports, duplicate MAC addresses, or MAC addresses appearing on unexpected ports. This could indicate an attacker attempting to impersonate legitimate devices. + +Next Steps: +1. Identify the affected MAC address and ports involved in the flapping +2. Check if the MAC address belongs to a legitimate device that may be moving between ports +3. Review switch logs for any unauthorized configuration changes +4. Verify if port security or dynamic ARP inspection is properly configured +5. Investigate the source device and check for signs of ARP spoofing tools +6. Consider implementing port security to limit MAC addresses per port +7. Enable DHCP snooping and dynamic ARP inspection if not already configured +', '["https://www.cisco.com/c/en/us/support/docs/switches/catalyst-3750-series-switches/72846-layer2-secftrs-catl3fixed.html","https://attack.mitre.org/techniques/T1200/"]', e'(equals("log.facility", "SW_MATM") && equals("log.facilityMnemonic", "MACFLAP_NOTIF")) +|| (equals("log.facility", "SW_DAI") && oneOf("log.facilityMnemonic", ["INVALID_ARP", "DHCP_SNOOPING_DENY"])) +|| regexMatch("log.message", "(?i)(mac.*flap|duplicate.*mac|mac.*move.*between.*port)") +|| regexMatch("log.message", "(?i)(Host [0-9a-fA-F:.]+.*is flapping between port)") +|| (lessOrEqual("log.severity", 4) && regexMatch("log.message", "(?i)(mac.*address.*conflict|duplicate.*address.*detected)")) +', '2026-03-02 16:31:52.224411', true, true, 'origin', null, '[{"indexPattern":"v11-log-cisco-switch-*","with":[{"field":"origin.mac","operator":"filter_term","value":"{{.origin.mac}}"}],"or":null,"within":"now-10m","count":3}]', '["adversary.mac"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1172, 'ARP Poisoning Attack Detection', 3, 3, 2, 'Credential Access, Collection', 'T1557.002 - Adversary-in-the-Middle: ARP Cache Poisoning', e'Detects potential ARP poisoning attacks by monitoring for invalid ARP packets, DHCP snooping violations, and gratuitous ARP abuse. These attacks can enable man-in-the-middle attacks by corrupting the ARP cache of network devices and redirecting network traffic through an attacker-controlled system. + +Next Steps: +1. Identify the source MAC and IP addresses involved in the suspicious ARP activity +2. Check if the source device is authorized to be on the network segment +3. Review DHCP snooping and dynamic ARP inspection logs for additional violations +4. Verify if legitimate network changes (new devices, IP changes) may have triggered the alert +5. If confirmed malicious, immediately isolate the affected switch port and investigate the compromised device +6. Review network traffic for signs of data interception, credential harvesting, or traffic redirection +7. Update switch security configurations (enable port security, DHCP snooping, DAI if not already enabled) +8. Consider implementing additional network segmentation to limit attack impact +', '["https://www.cisco.com/c/en/us/td/docs/switches/lan/catalyst4500/12-2/25ew/configuration/guide/conf/dynarp.html","https://attack.mitre.org/techniques/T1557/002/"]', e'(equals("log.facility", "SW_DAI") && oneOf("log.facilityMnemonic", ["INVALID_ARP", "DHCP_SNOOPING_DENY", "ACL_DENY"])) +|| (equals("log.facility", "IP") && oneOf("log.facilityMnemonic", ["DUPADDR", "SOURCEGUARD"])) +|| contains("log.message", ["invalid arp", "arp inspection drop", "dhcp snooping deny", "gratuitous arp", "arp reply not request", "duplicate ip address", "IP source guard deny", "arp packet validation failed"]) +|| (lessOrEqual("log.severity", 3) && contains("log.message", ["arp spoofing", "arp poison", "man in the middle"])) +', '2026-03-02 16:31:53.525673', true, true, 'origin', null, '[{"indexPattern":"v11-log-cisco-switch-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-10m","count":5}]', '["adversary.ip","adversary.mac"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1173, 'VLAN Hopping Attack Detection', 3, 3, 2, 'Defense Evasion', 'T1599 - Network Boundary Bridging', e'Detects potential VLAN hopping attacks through switch spoofing or double tagging. Monitors for DTP negotiation attempts, trunk port changes, or multiple VLAN tags that could indicate an attacker trying to gain unauthorized access to other VLANs. + +Next Steps: +1. Immediately identify the affected switch port and connected device +2. Review switch configuration for DTP enabled ports and disable where not needed +3. Check trunk port configurations and ensure proper native VLAN settings +4. Verify VLAN access lists and ensure proper segmentation +5. Investigate the source MAC address for any previous suspicious activity +6. Review network topology to assess potential lateral movement paths +7. Consider implementing VLAN ACLs or private VLANs for additional protection +8. Document the incident and update switch hardening procedures +', '["https://www.cisco.com/c/en/us/support/docs/switches/catalyst-3750-series-switches/72846-layer2-secftrs-catl3fixed.html","https://attack.mitre.org/techniques/T1599/"]', e'(equals("log.facility", "SW_VLAN") && oneOf("log.facilityMnemonic", ["VLAN_INCONSISTENCY", "MACFLAP_NOTIF", "TRUNK_MODE_CHANGE"])) +|| (equals("log.facility", "DTP") && oneOf("log.facilityMnemonic", ["NONTRUNKPORTON", "DOMAINMISMATCH", "TRUNKPORTON"])) +|| regexMatch("log.message", "(?i)(received 802.1Q BPDU on non trunk|native vlan mismatch|inconsistent vlan|double tag)") +|| (lessOrEqual("log.severity", 4) && regexMatch("log.message", "(?i)(vlan.*tag.*tag|switch.*spoofing|dtp.*negotiation)")) +', '2026-03-02 16:31:54.914197', true, true, 'origin', null, '[]', '["adversary.ip","adversary.mac"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/cisco-switch/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/cisco-switch/utm_group_rules_data_type.sql new file mode 100644 index 000000000..a28a935a5 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/cisco-switch/utm_group_rules_data_type.sql @@ -0,0 +1,3 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1171, 35, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1172, 35, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1173, 35, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/crowdstrike/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/crowdstrike/utm_correlation_rules.sql new file mode 100644 index 000000000..e1f93bd65 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/crowdstrike/utm_correlation_rules.sql @@ -0,0 +1,34 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1339, 'CrowdStrike Hunting: Windows Event Log Clearing', 0, 3, 2, 'Defense Evasion', 'Indicator Removal: Clear Windows Event Logs', 'A raw process execution was detected attempting to clear Windows Event Logs. Adversaries use this technique to cover their tracks after compromising a host.', '["https://attack.mitre.org/techniques/T1070/001/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(wevtutil\\\\s+cl.*|Clear-EventLog.*|Remove-EventLog.*).*") +', '2026-03-02 23:03:17.052207', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.UserName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1340, 'Suspicious Native Downloaders (LoLBin)', 2, 2, 0, 'Command and Control', 'Ingress Tool Transfer', 'Execution of native binaries like certutil, bitsadmin, curl, or wget was detected making external connections, potentially indicating Ingress Tool Transfer by an adversary.', '["https://attack.mitre.org/techniques/T1105/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(certutil.*-urlcache|bitsadmin.*-transfer|curl.*http|wget.*http).*") +', '2026-03-02 23:03:18.451160', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.CommandLine"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1341, 'Suspicious Encoded PowerShell Execution', 3, 2, 1, 'Execution', 'Command and Scripting Interpreter: PowerShell', 'A PowerShell process was spawned with arguments indicating base64 encoded commands (-enc, -EncodedCommand). Malware and threat actors often use this to evade string-based detection.', '["https://attack.mitre.org/techniques/T1059/001/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(powershell|pwsh).*-(e|en|enc|encodedcommand|ec)\\\\s+.*") +', '2026-03-02 23:03:19.764318', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.CommandLine"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1342, 'Suspicious Downloader Execution (Linux/macOS)', 2, 2, 0, 'Command and Control', 'Ingress Tool Transfer', 'Execution of native downloaders like curl or wget was detected on a Linux or macOS endpoint making HTTP connections, potentially indicating Ingress Tool Transfer by an adversary.', '["https://attack.mitre.org/techniques/T1105/"]', e'exists("log.event_platform") && oneOf("log.event_platform", ["Mac", "Lin"]) && exists("log.event.CommandLine") && regexMatch("log.event.CommandLine", "(?i).*(curl|wget).*http.*") +', '2026-03-02 23:03:21.114399', true, true, 'origin', null, '[]', '["lastEvent.log.event.ComputerName","lastEvent.log.event.CommandLine"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1343, 'Security Policy Disabled or Deleted', 1, 3, 3, 'Defense Evasion', 'Impair Defenses: Disable or Modify Tools', 'An administrator or actor has disabled or deleted a security prevention policy in Falcon, which may leave endpoints vulnerable.', '["https://attack.mitre.org/techniques/T1562/001/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["disable_policy", "delete_policy", "remove_policy"]) +', '2026-03-02 23:03:22.378920', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1344, 'Security Defenses Impaired or Policy Disabled', 0, 3, 3, 'Defense Evasion', 'Impair Defenses: Disable or Modify Tools', 'An action was taken on the endpoint that resulted in a critical sensor process or security policy being disabled locally. This strongly indicates defense evasion tampering.', '["https://attack.mitre.org/techniques/T1562/001/"]', e'equals("event.PatternDispositionFlags.PolicyDisabled", true) || oneOf("event.PatternDispositionValue", [8192, 8208, 8320, 8704, 9216, 10240, 12304, 73728, 73744]) +', '2026-03-02 23:03:23.734360', true, true, 'origin', null, '[]', '["lastEvent.log.event.ComputerName","lastEvent.log.event.PatternDispositionDescription"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1345, 'Real-Time Response (RTR) Session Execution', 3, 3, 1, 'Execution', 'Remote Services', 'A user or API has initiated a remote response (RTR) session on an endpoint. This grants deep access to the host.', '["https://attack.mitre.org/techniques/T1021/"]', e'equals("log.metadataEventType", "RemoteResponseSessionStartEvent") +', '2026-03-02 23:03:24.925716', true, true, 'origin', null, '[]', '["lastEvent.log.metadataEventType","lastEvent.log.eventUserId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1346, 'OS Credential Dumping Activity', 3, 1, 0, 'Credential Access', 'OS Credential Dumping: LSASS Memory', 'The endpoint agent detected activity commonly associated with OS Credential Dumping. This includes attempts to read or dump LSASS memory using known tools.', '["https://attack.mitre.org/techniques/T1003/001/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(procdump.*lsass|mimikatz|sekurlsa|lsass\\\\.dmp).*") +', '2026-03-02 23:03:26.142641', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.UserName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1347, 'Multiple Authentication Failures (Possible Brute Force Attack)', 3, 0, 0, 'Credential Access', 'Brute Force: Password Guessing', 'A user or IP address has failed multiple authentication attempts on the CrowdStrike Falcon console within a short period of time.', '["https://attack.mitre.org/techniques/T1110/001/"]', e'equals("log.metadataEventType", "AuthActivityAuditEvent") && equals("log.eventSuccess", false) && exists("origin.ip") +', '2026-03-02 23:03:27.493691', true, true, 'origin', '["origin.ip"]', '[{"indexPattern":"v11-log-crowdstrike","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.eventSuccess","operator":"filter_term","value":"false"}],"or":null,"within":"now-15m","count":5}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1348, 'Major Incident Generated (CrowdScore)', 3, 3, 3, 'Lateral Movement', 'Lateral Tool Transfer', 'The CrowdScore engine has consolidated multiple detections into a critical incident, a possible indicator of Lateral Movement or widespread intrusion.', '["https://attack.mitre.org/techniques/T1570/","https://attack.mitre.org/tactics/TA0008/"]', e'equals("log.metadataEventType", "IncidentSummaryEvent") +', '2026-03-02 23:03:28.702741', true, true, 'origin', null, '[]', '["lastEvent.log.metadataEventType"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1349, 'IP Whitelisting Modification', 1, 3, 2, 'Defense Evasion', 'Impair Defenses: Disable or Modify Cloud Firewall', 'IP addresses have been added to or removed from the CrowdStrike whitelist. An attacker could use this to evade network blocking.', '["https://attack.mitre.org/techniques/T1562/007/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["ip_rules_added", "ip_rules_removed"]) +', '2026-03-02 23:03:30.070795', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1350, 'Inhibit System Recovery (Shadow Copy Deletion)', 0, 3, 3, 'Impact', 'Inhibit System Recovery', 'The Falcon agent detected command line activity attempting to delete Volume Shadow Copies or disable recovery options. This is a highly reliable precursor to Ransomware encryption.', '["https://attack.mitre.org/techniques/T1490/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(vssadmin.*delete shadows|wmic.*shadowcopy.*delete|bcdedit.*recoveryenabled.*no).*") +', '2026-03-02 23:03:31.262814', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.UserName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1351, 'Endpoint or XDR Detection Alert', 3, 3, 2, 'Threat Detection', 'Command and Scripting Interpreter', 'A critical detection summary has been generated from Falcon EPP or XDR indicating malicious activity or attack patterns.', '["https://attack.mitre.org/techniques/T1059/"]', e'exists("log.metadataEventType") && oneOf("log.metadataEventType", ["EppDetectionSummaryEvent", "XdrDetectionSummaryEvent"]) +', '2026-03-02 23:03:32.606143', true, true, 'origin', null, '[]', '["lastEvent.log.metadataEventType"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1352, 'Endpoint Network Containment Action', 0, 2, 3, 'Impact', 'Account Access Removal', 'Containment of a host on the network has been requested, or a previously applied containment has been lifted.', '["https://attack.mitre.org/techniques/T1531/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["containment_requested", "lift_containment_requested"]) +', '2026-03-02 23:03:33.875437', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1353, 'Deletion or Deactivation of User Account', 0, 3, 3, 'Account Manipulation', 'Account Manipulation', 'An administrator has deactivated or deleted a user account in the Falcon console. This indicates account manipulation.', '["https://attack.mitre.org/techniques/T1098/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["deactivateUser", "deleteUser"]) +', '2026-03-02 23:03:35.273093', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1354, 'Custom Indicator of Compromise (IoC) Detected', 3, 3, 1, 'Threat Detection', 'User Execution', 'The sensor has detected activity that matches an IoC (Hash, Domain, IP) supplied and entered by the client.', '["https://attack.mitre.org/techniques/T1204/"]', e'equals("log.metadataEventType", "CustomerIOCEvent") +', '2026-03-02 23:03:36.627153', true, true, 'origin', null, '[]', '["lastEvent.log.metadataEventType"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1355, 'Critical Role Modification (Privilege Escalation)', 3, 3, 1, 'Privilege Escalation', 'Account Manipulation: Additional Cloud Roles', 'New roles have been granted or updated for a user within the CrowdStrike administration console.', '["https://attack.mitre.org/techniques/T1098/003/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["grantUserRoles", "updateUserRoles"]) +', '2026-03-02 23:03:38.226516', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/crowdstrike/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/crowdstrike/utm_group_rules_data_type.sql new file mode 100644 index 000000000..6bbcc379b --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/crowdstrike/utm_group_rules_data_type.sql @@ -0,0 +1,17 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1339, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1340, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1341, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1342, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1343, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1344, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1345, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1346, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1347, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1348, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1349, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1350, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1351, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1352, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1353, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1354, 51, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1355, 51, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/deceptive-bytes/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/deceptive-bytes/utm_correlation_rules.sql new file mode 100644 index 000000000..24658bf2d --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/deceptive-bytes/utm_correlation_rules.sql @@ -0,0 +1,249 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1120, 'Zero-Day Behavior Patterns Detection', 3, 3, 3, 'Defense Evasion', 'T1211 - Exploitation for Defense Evasion', e'Identifies potential zero-day exploits and unknown malware through abnormal behavior patterns, deception interactions, and anomaly detection in endpoint activities. + +Next Steps: +1. Immediately isolate the affected system from the network to prevent lateral movement +2. Capture memory dumps and process information for forensic analysis +3. Check for similar behavioral anomalies on other endpoints in the same network segment +4. Review the exploit technique and process chain to understand the attack vector +5. Submit samples to threat intelligence platforms for analysis +6. Update security controls based on the identified exploit patterns +7. Document all IOCs (file hashes, network connections, process behaviors) for threat hunting +', '["https://attack.mitre.org/techniques/T1211/","https://attack.mitre.org/techniques/T1055/","https://attack.mitre.org/techniques/T1620/"]', e'oneOf("log.eventType", ["unknown_threat", "behavioral_anomaly", "zero_day_suspect"]) && +equals("log.threatSignature", "unknown") && +equals("log.deceptionEnvironment", true) && +( + (greaterOrEqual("log.memoryAnomalyScore", 90)) || + (greaterOrEqual("log.processChainAnomalyScore", 85)) || + (greaterOrEqual("log.networkBehaviorScore", 88)) || + (greaterOrEqual("log.fileSystemAnomalyScore", 92)) +) && +equals("log.knownMalwareFamily", "") && +exists("log.exploitTechnique") +', '2026-03-02 15:48:34.556929', true, true, 'origin', null, '[{"indexPattern":"v11-log-deceptive-bytes-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.processName","operator":"filter_term","value":"{{.log.processName}}"}],"or":null,"within":"now-30m","count":2}]', '["lastEvent.log.exploitTechnique","lastEvent.log.processHash","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1121, 'Ransomware Behavior Detected in Deception Environment', 3, 3, 3, 'Impact', 'T1486 - Data Encrypted for Impact', e'Detects ransomware-like behavior patterns when attackers interact with deceptive files, including rapid file enumeration, encryption attempts, and ransom note creation in the Deceptive Bytes deception environment. + +Next Steps: +1. Immediately isolate the affected system from the network to prevent ransomware spread +2. Check the process name and path identified in the alert for known ransomware indicators +3. Review file system activity from the same process for encryption patterns +4. Check for shadow copy deletion attempts (vssadmin, wmic shadowcopy delete) +5. Look for network connections to potential C2 servers from the identified process +6. Preserve forensic evidence and memory dumps if possible +7. Verify if this is a deception environment interaction or production system compromise +8. Check for lateral movement attempts from the source IP address +9. Review backup integrity and availability before any restoration attempts +', '["https://attack.mitre.org/techniques/T1486/","https://attack.mitre.org/techniques/T1490/","https://deceptivebytes.com/solution/"]', e'equals("log.event_type", "ransomware_behavior") && +oneOf("log.behavior_pattern", ["mass_encryption", "file_enumeration", "ransom_note_drop"]) +', '2026-03-02 15:48:35.988006', true, true, 'origin', null, '[{"indexPattern":"v11-log-deceptive-bytes-*","with":[{"field":"log.process","operator":"filter_term","value":"{{.log.process}}"},{"field":"log.source_ip","operator":"filter_term","value":"{{.log.source_ip}}"}],"or":null,"within":"now-15m","count":10}]', '["lastEvent.log.hostname","lastEvent.log.process"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1122, 'Honey Table Query Detection', 3, 2, 1, 'Collection', 'T1005 - Data from Local System', e'Detects when an attacker queries honey tables or decoy database objects deployed by Deceptive Bytes. This indicates potential data exfiltration attempts or database reconnaissance. Honey tables are deliberately placed decoy data designed to attract and identify unauthorized access attempts. + +Next Steps: +1. Identify the source IP and determine if it\'s an internal or external address +2. Check if the source IP has accessed other decoy resources or legitimate database tables +3. Review the specific honey table(s) that were queried to understand attacker interest +4. Correlate with authentication logs to identify the user account used +5. Check for any data exfiltration patterns following the honey table access +6. Isolate the compromised system or account if malicious activity is confirmed +7. Review database access logs for unauthorized queries to legitimate tables +8. Consider blocking the source IP if it\'s external and confirmed malicious +9. Document the incident and update security monitoring rules if needed +', '["https://attack.mitre.org/techniques/T1005/","https://deceptivebytes.com/solution/"]', 'equals("log.eventType", "decoy_access") && equals("log.resourceType", "database_table") && equals("log.action", "query") && exists("origin.ip")', '2026-03-02 15:48:37.349533', true, true, 'origin', null, '[{"indexPattern":"v11-log-deceptive-bytes-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.eventType","operator":"filter_term","value":"decoy_access"}],"or":null,"within":"now-1h","count":5}]', '["lastEvent.log.tableName","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1123, 'Decoy Share Access Monitoring', 3, 2, 1, 'Discovery', 'T1135 - Network Share Discovery', e'Detects when an attacker attempts to access decoy network shares set up by Deceptive Bytes. This indicates potential lateral movement or reconnaissance activity within the network. Any interaction with decoy shares is a high-confidence indicator of malicious activity since legitimate users should never access these resources. + +Next Steps: +- Immediately investigate the source IP and verify if it belongs to an authorized user or system +- Check for other suspicious activities from the same source IP in the last 24-48 hours +- Review authentication logs to identify any compromised credentials associated with this IP +- Look for lateral movement attempts or privilege escalation from the same source +- Consider isolating the source system if it shows signs of compromise +- Document all accessed decoy resources for threat intelligence purposes +- Update security controls to block or monitor the attacker\'s techniques +', '["https://attack.mitre.org/techniques/T1135/","https://deceptivebytes.com/solution/"]', 'equals("log.eventType", "decoy_access") && equals("log.resourceType", "network_share") && exists("origin.ip")', '2026-03-02 15:48:38.654988', true, true, 'origin', '["adversary.ip","lastEvent.log.resourceType"]', '[{"indexPattern":"v11-log-deceptive-bytes-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.eventType","operator":"filter_term","value":"decoy_access"},{"field":"log.resourceType","operator":"filter_term","value":"network_share"}],"or":null,"within":"now-30m","count":3}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1124, 'Deception Token Access Patterns', 3, 3, 1, 'Defense Evasion, Persistence, Privilege Escalation, Initial Access', 'T1078 - Valid Accounts: Credential Access', e'Detects when deception tokens or honeytokens are accessed, indicating potential unauthorized activity or insider threat. Multiple token accesses from the same source within a short timeframe suggest systematic reconnaissance or data harvesting attempts. Honeytokens are fake credentials or access tokens planted as traps to detect unauthorized access. + +Next Steps: +1. Identify the source IP and user account associated with the token access +2. Review access logs to determine if this is legitimate testing or actual malicious activity +3. Check for lateral movement from the same source IP across the network +4. Investigate any data access or exfiltration attempts following the token access +5. Consider immediately blocking the source IP if confirmed malicious +6. Review and rotate any potentially compromised credentials in the environment +7. Alert security team immediately as honeytoken access is a high-confidence indicator of compromise +8. Document the incident and update detection rules based on observed attack patterns +9. Verify the integrity of the deception infrastructure to ensure it wasn\'t compromised +', '["https://attack.mitre.org/techniques/T1078/","https://deceptivebytes.com/"]', e'equals("log.eventType", "token_access") && +equals("log.deceptionType", "honeytoken") && +exists("origin.ip") && +oneOf("log.severity", ["high", "critical"]) +', '2026-03-02 15:48:40.056094', true, true, 'origin', null, '[{"indexPattern":"v11-log-deceptive-bytes-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.eventType","operator":"filter_term","value":"token_access"}],"or":null,"within":"now-1h","count":3}]', '["lastEvent.log.tokenId","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1125, 'Data Theft Attempt on Decoy Files', 3, 2, 1, 'Collection', 'T1005 - Data from Local System', e'Detects attempts to access, copy, or exfiltrate deceptive decoy files and honeypot data, indicating potential data theft activities by an attacker. This rule triggers when an attacker interacts with high-sensitivity decoy files planted by Deceptive Bytes. + +Next Steps: +- Immediately isolate the affected endpoint to prevent lateral movement +- Review the source IP and user account for suspicious activity patterns +- Check for other decoy interactions from the same source in the past 24 hours +- Examine network traffic logs for potential data exfiltration attempts +- Verify if the user account has been compromised or if this is insider threat activity +- Consider resetting credentials for the affected user account +- Document all decoy files accessed for forensic analysis +', '["https://attack.mitre.org/techniques/T1005/","https://attack.mitre.org/techniques/T1567/","https://deceptivebytes.com/solution/"]', e'equals("log.event_type", "decoy_accessed") && +oneOf("log.action", ["file_read", "file_copy", "file_download"]) && +equals("log.decoy_sensitivity", "high") +', '2026-03-02 15:48:41.366195', true, true, 'origin', null, '[{"indexPattern":"v11-log-deceptive-bytes-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.event_type","operator":"filter_term","value":"decoy_accessed"}],"or":null,"within":"now-2h","count":3}]', '["lastEvent.log.decoy_file","adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1126, 'Advanced Threat Tactic Identification', 3, 3, 3, 'Advanced Persistent Threat', 'Multiple Tactics', e'Detects advanced threat tactics including initial access, execution, and persistence techniques by monitoring deception environment interactions and behavioral patterns. This rule triggers when deceptive assets are accessed with high behavior scores indicating sophisticated attack patterns. + +Next Steps: +1. Immediately isolate the affected endpoint(s) associated with the source IP +2. Review the specific tactic name to understand the attack phase (initial access, execution, persistence, etc.) +3. Check all deception assets that were triggered to map the attacker\'s movement +4. Analyze the behavior score details to understand the sophistication level +5. Look for related alerts from the same source IP across different systems +6. Collect forensic data from the endpoint before any remediation +7. Review authentication logs for any credential abuse from this source +8. Check network logs for lateral movement attempts +9. Update security controls to block the identified tactics +10. Consider deploying additional deception assets in the path of the attacker +', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/tactics/TA0003/"]', e'equals("log.eventType", "advanced_threat_detected") && +equals("log.threatLevel", "critical") && +(oneOf("log.tacticName", ["initial_access", "execution", "persistence", "privilege_escalation", "defense_evasion"])) && +equals("log.deceptionTriggered", true) && +greaterOrEqual("log.behaviorScore", 80) +', '2026-03-02 15:48:42.717733', true, true, 'origin', null, '[{"indexPattern":"v11-log-deceptive-bytes-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.tacticName","operator":"filter_term","value":"{{.log.tacticName}}"}],"or":null,"within":"now-15m","count":3}]', '["lastEvent.log.tacticName","lastEvent.log.threatLevel","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1127, 'Privilege Escalation Bait Accessed', 3, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', e'Detects when an attacker accesses deceptive privileged account baits or attempts to escalate privileges using trapped credentials, indicating active exploitation attempts. This is a high-priority alert as it indicates an active attacker who has progressed beyond initial access and is attempting to gain elevated privileges. + +Next Steps: +- Immediately isolate the affected system to prevent lateral movement +- Review authentication logs for the source IP and user account to identify scope of compromise +- Check for other deception bait interactions from the same source in the past 24 hours +- Investigate any legitimate user activity that may have been compromised +- Collect forensic data from the endpoint including running processes and network connections +- Review SIEM/EDR alerts for related suspicious activities from the same source +- Document the attacker\'s TTPs for threat intelligence sharing +- Consider resetting credentials for any accounts that may have been exposed +- Update firewall rules to block the attacker\'s source IP if confirmed malicious +', '["https://attack.mitre.org/techniques/T1068/","https://attack.mitre.org/techniques/T1078/","https://www.checkpoint.com/cyber-hub/cyber-security/what-is-deception-technology/"]', e'equals("log.event_type", "bait_accessed") && +equals("log.bait_type", "privileged_account") && +oneOf("log.target_privilege", ["admin", "system", "administrator"]) +', '2026-03-02 15:48:43.983729', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1128, 'Threat Actor Attribution', 2, 2, 2, 'Threat Intelligence', 'T1583 - Acquire Infrastructure', e'Correlates observed attack patterns, tools, techniques, and infrastructure with known threat actor profiles to provide attribution intelligence and identify potential threat actors based on high-confidence indicators. + +Next Steps: +1. Review the attributed threat actor profile and historical campaigns for context +2. Analyze the specific TTPs (Tactics, Techniques, and Procedures) that triggered the attribution +3. Check for related activity from the same actor across other systems or time periods +4. Correlate with threat intelligence feeds to validate attribution confidence +5. Document observed infrastructure and tooling for future threat hunting +6. Consider implementing specific detections for this actor\'s known techniques +7. Share attribution indicators with security teams for enhanced monitoring +8. Escalate to incident response team if high-profile threat actor is identified +', '["https://attack.mitre.org/groups/","https://malpedia.caad.fkie.fraunhofer.de/"]', e'equals("log.eventType", "threat_attribution") && +greaterOrEqual("log.attributionConfidence", 70) && +exists("log.actorProfile") && +equals("log.deceptionTriggered", true) && +(greaterOrEqual("log.ttpsMatched", 3) || + equals("log.infrastructureMatch", true) || + exists("log.toolingFingerprint")) && +equals("log.historicalCampaignMatch", true) +', '2026-03-02 15:48:45.260156', true, true, 'origin', null, '[]', '["lastEvent.log.actorProfile","lastEvent.log.campaignId","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1129, 'Nation-State Tactic Detection', 3, 3, 3, 'Advanced Persistent Threat', 'T1595 - Active Scanning / Nation-State Attack Patterns', e'Detects sophisticated attack patterns and techniques commonly associated with nation-state actors including advanced persistence mechanisms, custom tooling, and strategic lateral movement. + +Next Steps: +1) Immediately isolate affected systems and preserve forensic evidence +2) Review all decoy interactions and identify compromised credentials +3) Check for lateral movement attempts from the source IP across all systems +4) Analyze custom tools or malware samples if detected +5) Engage incident response team for potential APT activity +6) Review network traffic for command & control communications +7) Implement enhanced monitoring on high-value targets identified in the attack +', '["https://attack.mitre.org/groups/","https://www.cisa.gov/topics/cyber-threats-and-advisories/advanced-persistent-threats"]', e'oneOf("log.event_type", ["decoy_interaction", "honeypot_access", "deception_triggered"]) && +equals("log.threat_level", "critical") && +(equals("log.attack_sophistication", "advanced") || greaterOrEqual("log.threat_score", 85)) && +(equals("log.apt_indicators", true) || + equals("log.custom_malware", true) || + equals("log.advanced_ttps", true) || + greaterThan("log.targeted_decoys", 1) || + equals("log.persistence_attempt", true)) +', '2026-03-02 15:48:46.393702', true, true, 'origin', null, '[]', '["adversary.host","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1130, 'Living Off The Land Attack Using Deceptive Resources', 3, 3, 2, 'Defense Evasion', 'T1218 - Signed Binary Proxy Execution', e'Detects when attackers use legitimate system tools and binaries to interact with deceptive resources, indicating Living Off The Land (LOLBins) attack techniques. This is a high-confidence indicator of malicious activity as legitimate users should not be accessing deceptive resources with system binaries. + +Next Steps: +1. Immediately isolate the affected system to prevent lateral movement +2. Review the process execution chain to identify the parent process and any child processes +3. Check if the user account is compromised by reviewing recent authentication logs +4. Examine command line arguments and scripts executed by the LOLBin +5. Search for other deceptive resource interactions from the same user or system +6. Collect memory dump if possible for forensic analysis +7. Review network connections made by the process for C2 communication +8. Check for persistence mechanisms (scheduled tasks, registry modifications, services) +', '["https://attack.mitre.org/techniques/T1218/","https://attack.mitre.org/techniques/T1053/","https://lolbas-project.github.io/","https://deceptivebytes.com/solution/"]', 'equals("log.event_type", "lolbin_trap") && oneOf("log.process_name", ["powershell.exe", "cmd.exe", "wmic.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe", "certutil.exe", "bitsadmin.exe"]) && exists("log.deceptive_target")', '2026-03-02 15:48:47.671466', true, true, 'origin', null, '[]', '["lastEvent.log.deceptive_target","adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1131, 'Lateral Movement Trap Triggered', 3, 3, 2, 'Lateral Movement', 'T1021 - Remote Services', e'Detects when an attacker triggers a deceptive trap while attempting lateral movement across the network. This indicates potential compromise and active threat movement within the environment. + +Next Steps: +1. Immediately isolate the source IP address to prevent further lateral movement +2. Review all activities from the source IP in the last 24-48 hours +3. Check if the source system shows signs of compromise (unusual processes, new services, etc.) +4. Identify what credentials or methods were used in the lateral movement attempt +5. Review network logs for any successful connections from this source to other systems +6. Initiate incident response procedures for potential active threat +7. Consider deploying additional deception tokens around critical assets +', '["https://attack.mitre.org/techniques/T1021/","https://deceptivebytes.com/solution/"]', 'equals("log.event_type", "trap_triggered") && equals("log.trap_type", "lateral_movement") && exists("origin.ip")', '2026-03-02 15:48:48.784797', true, true, 'origin', null, '[]', '["lastEvent.log.trap_type","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1132, 'Fake User Authentication Attempts', 3, 3, 1, 'Credential Access', 'T1110 - Brute Force', e'Detects authentication attempts using decoy user accounts created by Deceptive Bytes. This indicates an attacker has obtained what they believe are valid credentials and is attempting to use them. + +Next Steps: +- Immediately investigate the source IP address for other suspicious activities +- Check if the same IP has triggered other deception alerts or security events +- Review how the attacker obtained the decoy credentials (phishing, credential dumping, insider threat) +- Examine network logs for lateral movement attempts from this IP +- Consider blocking the source IP if confirmed malicious +- Check for any legitimate user accounts that may have been compromised +- Review authentication logs for attempts using real credentials from the same source +- Notify the security team for potential active breach investigation +', '["https://attack.mitre.org/techniques/T1110/","https://deceptivebytes.com/solution/"]', 'equals("log.eventType", "authentication") && equals("log.isDecoyUser", true) && exists("log.authResult") && exists("origin.ip")', '2026-03-02 15:48:50.062435', true, true, 'origin', null, '[]', '["lastEvent.log.username","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1133, 'Decoy System Enumeration', 3, 2, 1, 'Discovery', 'T1082 - System Information Discovery', e'Detects when an attacker performs system enumeration activities on decoy systems or services. This includes port scanning, service discovery, or system information gathering on deception assets. + +Next Steps: +- Immediately investigate the source IP address for other suspicious activities +- Check if the source IP has attempted to access other decoy or real systems +- Review network logs for lateral movement attempts from this source +- Consider blocking the source IP if malicious intent is confirmed +- Document the attack pattern for threat intelligence sharing +- Verify if the attacker has discovered any real assets alongside decoys +', '["https://attack.mitre.org/techniques/T1082/","https://deceptivebytes.com/solution/"]', e'equals("log.eventType", "system_enumeration") && +(equals("log.isDecoy", true) || equals("log.isDecoy", "true")) && +oneOf("log.action", ["port_scan", "service_discovery", "system_info"]) && +exists("origin.ip") +', '2026-03-02 15:48:51.284971', true, true, 'origin', '["adversary.ip","lastEvent.log.targetHost","lastEvent.log.decoyName"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1134, 'Deception API Call Tracking', 2, 2, 1, 'Execution', 'T1106 - Native API', e'Tracks suspicious API calls made to decoy services or endpoints. This behavior indicates an attacker is attempting to interact with what they believe are legitimate services but are actually deception assets. + +Next Steps: +- Review the source IP address and check if it\'s from a known legitimate source +- Examine the API endpoint accessed and the HTTP method used +- Look for other activity from the same IP address across all log sources +- Check if the source IP has accessed multiple decoy endpoints (indicating reconnaissance) +- Investigate any authentication tokens or credentials used in the API calls +- Consider blocking the source IP if malicious intent is confirmed +- Document the attack pattern for threat intelligence sharing +', '["https://attack.mitre.org/techniques/T1106/","https://deceptivebytes.com/solution/"]', 'equals("log.eventType", "api_call") && equals("log.isDecoy", "true") && exists("log.httpMethod") && exists("origin.ip")', '2026-03-02 15:48:52.639498', true, true, 'origin', null, '[]', '["lastEvent.log.apiEndpoint","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1135, 'Criminal Group Signatures', 3, 3, 2, 'Organized Crime Activity', 'Criminal Group TTPs', e'Identifies attack signatures and behavioral patterns associated with known criminal groups including ransomware operators, financial crime syndicates, and organized cybercrime operations. Deception technology detects malicious activity by monitoring interactions with decoy assets that should never be accessed in legitimate workflows. + +Next Steps: +1. IMMEDIATE: Isolate the affected endpoint to prevent lateral movement +2. Verify the criminal group signature or toolset identified in the alert details +3. Check if the source IP/domain appears in threat intelligence feeds or previous incidents +4. Review all activity from the affected endpoint in the last 24-48 hours +5. Search for indicators of lateral movement or data staging activities +6. Scan other endpoints for similar patterns or IoCs +7. If ransomware indicators are present, activate ransomware response playbook +8. Collect forensic evidence: process creation logs, network connections, file modifications +9. Check for data exfiltration attempts to external IPs or cloud services +10. Review all user account activity associated with the endpoint for signs of compromise +11. Document all findings and coordinate with incident response team +12. Consider threat hunting across the environment for related criminal group activities +', '["https://attack.mitre.org/groups/","https://www.ic3.gov/Media/PDF/AnnualReport/2023_IC3Report.pdf","https://www.acalvio.com/cyber-deception/the-role-of-deception-technology-in-the-endpoint-security-reference-architecture/","https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a"]', e'oneOf("log.eventType", ["threat_detected", "deception_triggered", "malicious_activity"]) && +oneOf("log.threatType", ["criminal_group", "ransomware", "organized_crime"]) && +(exists("log.signature") || exists("log.toolset") || exists("log.groupName")) && +(oneOf("log.action", ["blocked", "detected", "prevented"]) || + oneOf("log.severity", ["high", "critical"])) && +(oneOf("log.indicatorType", ["ransomware", "financial_theft", "cryptomining", "data_exfiltration"]) || + greaterOrEqual("log.threatScore", 70)) +', '2026-03-02 15:48:53.794345', true, true, 'origin', null, '[]', '["lastEvent.log.groupName","lastEvent.log.signature","adversary.host"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/deceptive-bytes/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/deceptive-bytes/utm_group_rules_data_type.sql new file mode 100644 index 000000000..23b6060c0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/deceptive-bytes/utm_group_rules_data_type.sql @@ -0,0 +1,16 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1120, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1121, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1122, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1123, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1124, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1125, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1126, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1127, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1128, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1129, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1130, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1131, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1132, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1133, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1134, 37, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1135, 37, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/eset/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/eset/utm_correlation_rules.sql new file mode 100644 index 000000000..96b91e331 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/eset/utm_correlation_rules.sql @@ -0,0 +1,208 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1136, 'ESET Repeated Quarantine Failures', 2, 3, 2, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects repeated quarantine failures in ESET, which may indicate malware actively resisting quarantine through file locks, permission manipulation, or rapid re-creation of malicious files. + +Next Steps: +1. Identify the specific file or threat that cannot be quarantined +2. Check the file permissions and processes locking the file +3. Attempt manual quarantine or deletion in safe mode +4. Review the malware\'s persistence mechanisms +5. Consider isolating the endpoint for manual remediation +6. Run a boot-time scan if available +', '["https://help.eset.com/ees/8/en-US/","https://attack.mitre.org/techniques/T1562/001/"]', e'(contains("log.message", "quarantine") && + (contains("log.message", "failed") || contains("log.message", "error") || + contains("log.message", "unable") || contains("log.message", "denied"))) || +(contains("log.message", "clean") && contains("log.message", "failed") && + contains("log.message", "threat")) +', '2026-03-02 16:02:21.835806', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-esmc-eset-*","with":[{"field":"log.headHostname","operator":"filter_term","value":"{{.log.headHostname}}"}],"or":null,"within":"now-1h","count":5}]', '["lastEvent.log.headHostname","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1137, 'ESET ERA/ESMC Console Suspicious Activity', 3, 3, 2, 'Lateral Movement', 'T1072 - Software Deployment Tools', e'Detects suspicious activity on the ESET ERA/ESMC management console including unauthorized policy changes, mass task deployments, or admin account modifications that could indicate console compromise. + +Next Steps: +1. Verify the admin account performing console operations +2. Review recent policy changes and task deployments +3. Check admin login history for unauthorized access +4. Verify the content of any pushed policies or tasks +5. Suspend suspicious admin accounts +6. Audit endpoints affected by recent console changes +', '["https://help.eset.com/esmc_admin/70/en-US/","https://attack.mitre.org/techniques/T1072/"]', e'(contains("log.message", "policy") && + (contains("log.message", "modified") || contains("log.message", "assigned") || + contains("log.message", "created"))) || +(contains("log.message", "client task") && + (contains("log.message", "executed") || contains("log.message", "deployed"))) || +(contains("log.message", "administrator") && + ((contains("log.message", "created") || contains("log.message", "modified")) || + (contains("log.message", "login") && contains("log.message", "failed")))) +', '2026-03-02 16:02:23.011021', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-esmc-eset-*","with":[{"field":"log.headHostname","operator":"filter_term","value":"{{.log.headHostname}}"}],"or":null,"within":"now-30m","count":10}]', '["lastEvent.log.headHostname","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1138, 'Advanced Heuristic Detection Triggers', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1055 - Process Injection', e'Detects when ESET\'s advanced heuristic engine identifies suspicious behavior patterns that may indicate novel malware or zero-day threats. These detections use DNA signatures and behavioral analysis. + +Next Steps: +- Review the affected hostname and user context to understand the scope +- Check the process name (if available) that triggered the detection +- Verify if the action taken (cleaned/deleted/quarantined) was successful +- Look for related alerts from the same host within the past 24 hours +- If multiple hosts show similar detections, investigate potential lateral movement +- Consider isolating the affected system if threat persists +- Review ESET console link (if available) for detailed threat information +- Check file hash against threat intelligence databases if available +- Capture and analyze the malicious file sample if quarantined +- Review system logs for any unusual activities before and after detection +- Update ESET signatures and run a full system scan +', '["https://help.eset.com/eea/8/en-US/idh_config_threat_sense.html","https://attack.mitre.org/techniques/T1055/"]', e'oneOf("log.msgType", ["EnterpriseInspectorAlert_Event", "threat_event", "FirewallAggregatedAlert_Event"]) && +contains("log.jsonMessage", ["heuristic", "NewHeur", "suspicious behavior"]) && +contains("log.jsonMessage", ["cleaned", "deleted", "quarantined", "blocked"]) +', '2026-03-02 16:02:24.326768', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-esmc-eset-*","with":[{"field":"log.headHostname","operator":"filter_term","value":"{{.log.headHostname}}"}],"or":null,"within":"now-30m","count":3}]', '["lastEvent.log.headHostname","lastEvent.log.msgType"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1139, 'Suspicious Process Behavior Detection', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1055 - Process Injection', e'Detects suspicious process behaviors including injection attempts, privilege escalation, and abnormal process creation patterns identified by ESET\'s behavioral monitoring. This alert indicates potential malware activity or exploitation attempts on the affected system. + +Next Steps: +1. Immediately review the alert details to identify: + - Affected hostname (check log.headHostname) + - Specific threat or behavior detected (check log.jsonMessage) + - Process name and path if available + - Time of detection (check log.deviceTime) +2. Investigate the process that triggered the alert: + - Verify if it\'s a legitimate application or unknown/suspicious + - Check process creation chain and parent-child relationships + - Review file hash against threat intelligence sources +3. Check for related security events: + - Look for other ESET alerts from the same host + - Search for network connections from the suspicious process + - Review authentication events around the same timeframe +4. Containment actions if malicious: + - Isolate the affected host from the network + - Kill the suspicious process if still running + - Preserve forensic evidence (memory dump, logs) +5. Remediation steps: + - Run full antivirus scan on the affected system + - Check for persistence mechanisms (registry, scheduled tasks) + - Update ESET signatures and perform system hardening +6. Prevention measures: + - Review and update application control policies + - Ensure ESET real-time protection is enabled + - Consider implementing application whitelisting +', '["https://help.eset.com/ees/12/en-US/idh_dialog_epfw_ids_alert.html","https://attack.mitre.org/techniques/T1055/"]', e'oneOf("log.msgType", ["EnterpriseInspectorAlert_Event", "HIPS_Event"]) && +exists("log.jsonMessage") && +contains("log.jsonMessage", ["Process injection", "Suspicious behavior", + "Anomalous process", "blocked", "terminated", "prevented"]) +', '2026-03-02 16:02:25.460232', true, true, 'origin', null, '[]', '["lastEvent.log.headHostname","lastEvent.log.msgType"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1140, 'ESET Blocked Suspicious PowerShell Activity', 3, 3, 1, 'Execution', 'T1059.001 - Command and Scripting Interpreter: PowerShell', e'Detects when ESET blocks suspicious PowerShell commands or scripts that exhibit malicious behavior patterns, including obfuscated scripts, encoded commands, or attempts to bypass execution policies. This is a high-priority security event that indicates potential malicious activity was prevented. + +Next Steps: +1. Review the blocked PowerShell command details in the log message +2. Identify the user account and process that attempted to execute PowerShell +3. Check if this is part of legitimate administrative activity or scripting +4. Investigate the source of the PowerShell execution (parent process, script location) +5. Look for other suspicious activities from the same host or user +6. Consider isolating the affected system if malicious intent is confirmed +7. Review and update PowerShell execution policies if needed +', '["https://help.eset.com/ees/8/en-US/idh_hips_main.html","https://attack.mitre.org/techniques/T1059/001/"]', 'regexMatch("log.message", "(?i)(powershell|pwsh)") && equals("log.action", "blocked") && exists("log.headHostname")', '2026-03-02 16:02:26.813800', true, true, 'origin', null, '[]', '["lastEvent.log.headHostname","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1141, 'Suspicious Encrypted File Activity', 3, 3, 2, 'Impact', 'T1486 - Data Encrypted for Impact', e'Detects suspicious encrypted file activities that may indicate ransomware encryption attempts or unauthorized file encryption operations. This rule triggers when ESET detects ransomware-related threats or file encryption activities. + +Next Steps: +1. Immediately isolate the affected system to prevent spread +2. Check if backup systems are accessible and uncompromised +3. Review the threat details in log.jsonMessage for specific ransomware variant +4. Look for other systems showing similar encryption patterns +5. Preserve forensic evidence before remediation +6. Consider engaging incident response team for ransomware cases +7. Do not power off the system if encryption is in progress +', '["https://attack.mitre.org/techniques/T1486/","https://help.eset.com/protect_admin/10.1/en-US/events-exported-to-json-format.html"]', e'equals("log.msgType", "Threat_Event") && +contains("log.jsonMessage", ["ransomware", "filecoder", "encrypted", ".encrypted"]) +', '2026-03-02 16:02:27.993312', true, true, 'origin', null, '[]', '["lastEvent.log.msgType","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1142, 'Registry Modification Attempts Blocked', 2, 3, 2, 'Defense Evasion, Persistence', 'T1112 - Modify Registry', e'Identifies attempts to modify critical Windows registry keys that were blocked by ESET, indicating potential persistence or system tampering attempts. Registry modifications are a common technique used by malware to establish persistence, disable security features, or alter system behavior. + +Next Steps: +1. Review the blocked action details to understand what registry key was targeted +2. Investigate the source process and user account involved in the attempt +3. Check for other security events from the same host around the same time +4. Verify if this is legitimate administrative activity or potential malicious behavior +5. If suspicious, isolate the affected system and perform a full malware scan +6. Review system logs for any successful registry modifications before the block occurred +', '["https://help.eset.com/esmc_admin/70/en-US/events-exported-to-json-format.html","https://attack.mitre.org/techniques/T1112/"]', e'exists("log.jsonMessage") && +contains("log.jsonMessage", "registry") && +oneOf("log.action", ["blocked", "denied", "prevented"]) && +oneOf("log.severity", ["high", "medium"]) +', '2026-03-02 16:02:29.178944', true, true, 'origin', null, '[]', '["lastEvent.log.action","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1143, 'ESET Network Attack Detection', 3, 2, 1, 'Initial Access', 'T1190 - Exploit Public-Facing Application', e'Detects network-based attacks and exploits blocked by ESET\'s Network Attack Protection (IDS). This includes attempts to exploit known vulnerabilities in network services and protocols. + +Next Steps: +1. Review the attack details in ESET console to identify the specific vulnerability or attack pattern +2. Check if the source IP is known malicious using threat intelligence sources +3. Verify if other systems received similar attacks from the same source +4. Review firewall logs for additional suspicious activity from the source IP +5. Consider blocking the source IP at the perimeter firewall if attacks persist +6. Update network security policies and ensure all systems are patched +', '["https://help.eset.com/ees/7/en-US/idh_config_epfw_network_attack_protection.html","https://attack.mitre.org/techniques/T1190/"]', e'equals("log.event_type", "NetworkProtection_Event") && +equals("log.action", "blocked") && +exists("origin.ip") +', '2026-03-02 16:02:30.262729', true, true, 'origin', null, '[]', '["adversary.ip","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1144, 'Machine Learning Detection Anomalies', 3, 3, 2, 'Execution', 'T1204.002 - User Execution: Malicious File', e'Identifies threats detected by ESET\'s machine learning engine that analyzes file behavior patterns and characteristics to identify previously unknown malware variants. Machine learning detection indicates advanced malware that may evade signature-based detection methods. + +Next Steps: +- Immediately investigate the affected host for signs of compromise +- Review the threat details in the log message to understand the malware type and behavior +- Check if the malware was successfully blocked or quarantined +- Look for similar detections across other hosts in your environment +- Consider isolating the affected system if the threat was not successfully contained +- Review process activity around the time of detection for suspicious behavior +- Collect and analyze the malware sample if available for threat intelligence +- Update security policies to prevent similar threats +- Check for any data exfiltration or lateral movement attempts from the affected host +', '["https://help.eset.com/protect_admin/11.0/en-US/events-exported-to-json-format.html","https://attack.mitre.org/techniques/T1204/002/"]', e'contains("log.message", "machine learning") && +contains("log.message", ["threat", "detected", "found"]) && +exists("log.msgType") && +exists("log.headHostname") +', '2026-03-02 16:02:31.619452', true, true, 'origin', null, '[]', '["lastEvent.log.headHostname","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1145, 'ESET Host Intrusion Prevention System Triggered', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1055 - Process Injection', e'Detects when ESET\'s Host-based Intrusion Prevention System (HIPS) blocks suspicious behavior, including process manipulation, registry modifications, and file system changes that indicate potential malware activity. HIPS events indicate active attempts to compromise system integrity through various attack techniques. + +Next Steps: +1. Review the blocked process or action details in the ESET console +2. Identify the source application attempting the blocked behavior +3. Check if the blocked action is from legitimate software (false positive) +4. If malicious, isolate the affected system and perform full malware scan +5. Review system logs for any successful compromise attempts before HIPS activation +6. Update HIPS rules if necessary to prevent similar attacks +7. Check for persistence mechanisms on the affected host +8. Review network connections from the suspicious process if applicable +', '["https://help.eset.com/ees/8/en-US/idh_hips_main.html","https://attack.mitre.org/techniques/T1055/"]', 'equals("log.actionResult", "HIPS_Event") && equals("log.action", "blocked") && oneOf("log.severity", ["medium", "high"])', '2026-03-02 16:02:32.971290', true, true, 'origin', null, '[]', '["lastEvent.log.objectname","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1146, 'ESET Exploit Detection Alert', 3, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', e'Detects when ESET\'s Exploit Blocker identifies and blocks exploitation attempts targeting vulnerabilities in commonly exploited applications such as browsers, document readers, email clients, Flash, and Java. + +Next Steps: +1. Identify the affected host and the specific exploit attempt details +2. Check for any successful exploitation attempts on the same host +3. Review process execution logs for suspicious activity following the exploit attempt +4. Verify that the exploit was successfully blocked and no compromise occurred +5. Update the vulnerable application if a patch is available +6. Consider isolating the host if exploitation may have succeeded +', '["https://www.eset.com/us/about/technology/","https://attack.mitre.org/techniques/T1068/"]', e'(contains("log.jsonMessage", "exploit") || + oneOf("log.msgType", ["Exploit_Blocked", "Exploit"])) && +equals("actionResult", "blocked") && +oneOf("log.severity", ["medium", "high"]) +', '2026-03-02 16:02:34.327549', true, true, 'origin', null, '[]', '["lastEvent.log.jsonMessage","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1147, 'ESET Agent Disabled or Tampered', 3, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects when the ESET security agent is disabled, uninstalled, or tampered with. This is a critical defense evasion indicator as attackers commonly disable endpoint protection before executing their payload. + +Next Steps: +1. Immediately investigate the affected endpoint +2. Determine who or what process disabled the agent +3. Check for concurrent malicious activity on the endpoint +4. Reinstall and re-enable the ESET agent +5. Review the endpoint for malware or unauthorized software +6. Check if similar tampering occurred on other endpoints +', '["https://help.eset.com/ees/8/en-US/idh_config_era_agent.html","https://attack.mitre.org/techniques/T1562/001/"]', e'(regexMatch("log.message", "(?i)(eset|ekrn|egui|agent)") && + regexMatch("log.message", "(?i)(disabled|stopped|uninstalled|removed|tampered|terminated)")) || +(contains("log.message", "protection status") && contains("log.message", "disabled")) || +(contains("log.message", "agent") && contains("log.message", "not responding")) || +(equals("log.eventType", "AGENT_EVENT") && contains("log.message", "removed")) +', '2026-03-02 16:02:35.582991', true, true, 'origin', null, '[]', '["lastEvent.log.eventType","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1148, 'ESET Botnet Communication Detection', 3, 3, 2, 'Command and Control', 'T1071 - Application Layer Protocol', e'Detects attempts to communicate with known botnet command and control servers. ESET identifies typical communication patterns when a computer is infected and a bot is attempting to communicate with malicious C2 infrastructure. + +Next Steps: +1. Immediately isolate the affected system from the network to prevent further C2 communication +2. Check the hostname (log.headHostname) to identify the affected system +3. Review the full log message content (log.jsonMessage) for additional threat details including target IPs and processes +4. Review process activity on the affected host to identify the malicious process +5. Scan the system with ESET for complete malware removal +6. Check other systems in the network for similar C2 communication attempts +7. Update firewall rules to block any identified C2 server IPs found in the logs +8. Consider reimaging the system if the infection persists +9. Review ESET logs for the time period around this detection to identify related malicious activity +', '["https://www.eset.com/us/botnet/","https://support.eset.com/en/kb7487-resolve-the-incomingattackgeneric-or-botnetcncgeneric-network-protection-alert","https://attack.mitre.org/techniques/T1071/"]', e'contains("log.jsonMessage", ["Botnet", "CnC.Generic", "botnet", "C&C", "command and control"]) && +exists("log.headHostname") +', '2026-03-02 16:02:36.913305', true, true, 'origin', null, '[]', '["lastEvent.log.headHostname","lastEvent.log.jsonMessage"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/eset/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/eset/utm_group_rules_data_type.sql new file mode 100644 index 000000000..b635a26eb --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/eset/utm_group_rules_data_type.sql @@ -0,0 +1,13 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1136, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1137, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1138, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1139, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1140, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1141, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1142, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1143, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1144, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1145, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1146, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1147, 27, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1148, 27, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/google/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/google/utm_correlation_rules.sql new file mode 100644 index 000000000..a57f4f409 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/google/utm_correlation_rules.sql @@ -0,0 +1,375 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1305, 'Google Cloud Service Account Key Creation Spike', 3, 3, 2, 'Credential Access', 'Account Manipulation', e'Detects spikes in service account key creation which could indicate credential harvesting or preparation for unauthorized access. Service account keys provide long-term credentials that can be used to authenticate as the service account. Multiple key creations by the same user within a short timeframe may indicate malicious activity or preparation for privilege escalation attacks. + +Next Steps: +1. Investigate the user account creating multiple service account keys +2. Review the service accounts for which keys were created and their permissions +3. Check if the key creation was authorized and follows organizational policies +4. Examine subsequent activities performed using these service account credentials +5. Verify if the keys were created from expected IP addresses and locations +6. Review access patterns and identify any unusual resource access or API calls +7. Consider rotating or disabling the created keys if unauthorized activity is confirmed +', '["https://cloud.google.com/iam/docs/audit-logging/examples-service-accounts","https://attack.mitre.org/techniques/T1098/001/"]', e'equals("log.protoPayload.methodName", "google.iam.admin.v1.CreateServiceAccountKey") && +equals("log.protoPayload.serviceName", "iam.googleapis.com") +', '2026-03-02 22:50:02.585329', true, true, 'origin', null, '[{"indexPattern":"v11-log-google-*","with":[{"field":"log.protoPayload.authenticationInfo.principalEmail","operator":"filter_term","value":"{{.log.protoPayload.authenticationInfo.principalEmail}}"}],"or":null,"within":"now-1h","count":5}]', '["lastEvent.log.protoPayload.authenticationInfo.principalEmail","lastEvent.log.protoPayload.methodName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1306, 'GCP Secret Manager Bulk Access Detection', 3, 1, 1, 'Credential Access', 'T1552 - Unsecured Credentials', e'Detects bulk access to GCP Secret Manager secrets which may indicate credential harvesting. Attackers who gain access to a GCP project may enumerate and retrieve all stored secrets to obtain API keys, database credentials, and other sensitive data. + +Next Steps: +1. Review which secrets were accessed and their sensitivity classification +2. Verify the identity accessing the secrets has legitimate need +3. Check the access pattern for unusual timing or volume +4. Review the caller\'s IP address and user agent for anomalies +5. Determine if the accessed secrets have been used from unauthorized locations +6. If unauthorized, rotate all accessed secrets immediately +7. Review Secret Manager IAM bindings and apply least privilege +8. Enable VPC Service Controls to restrict secret access +', '["https://cloud.google.com/secret-manager/docs/audit-logging","https://attack.mitre.org/techniques/T1552/"]', e'contains("log.protoPayload.serviceName", "secretmanager.googleapis.com") && +contains("log.protoPayload.methodName", "AccessSecretVersion") +', '2026-03-02 22:50:03.849969', true, true, 'origin', null, '[{"indexPattern":"v11-log-google-*","with":[{"field":"log.protoPayload.authenticationInfo.principalEmail","operator":"filter_term","value":"{{.log.protoPayload.authenticationInfo.principalEmail}}"},{"field":"log.protoPayload.methodName","operator":"filter_term","value":"AccessSecretVersion"}],"or":null,"within":"now-15m","count":5}]', '["lastEvent.log.protoPayload.methodName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1307, 'GCP Service Account Impersonation Detection', 3, 3, 1, 'Credential Access', 'T1550.001 - Use Alternate Authentication Material: Application Access Token', e'Detects service account impersonation through token generation APIs including GenerateAccessToken, GenerateIdToken, and SignBlob. Attackers may impersonate service accounts to escalate privileges or access resources the service account has been granted. + +Next Steps: +1. Verify the identity performing the impersonation is authorized +2. Check the target service account and its IAM bindings +3. Review the permissions available through the impersonated service account +4. Examine the API calls made using the generated token +5. Verify if the impersonation is part of a legitimate workload chain +6. If unauthorized, remove the iam.serviceAccountTokenCreator role from the caller +7. Review the service account\'s access patterns for anomalies +8. Implement Organization Policy constraints to limit service account impersonation +', '["https://cloud.google.com/iam/docs/create-short-lived-credentials-direct","https://attack.mitre.org/techniques/T1550/001/"]', e'(contains("log.protoPayload.methodName", "GenerateAccessToken") || + contains("log.protoPayload.methodName", "GenerateIdToken") || + contains("log.protoPayload.methodName", "SignBlob") || + contains("log.protoPayload.methodName", "SignJwt")) && +contains("log.protoPayload.serviceName", "iamcredentials.googleapis.com") +', '2026-03-02 22:50:04.982229', true, true, 'origin', null, '[{"indexPattern":"v11-log-google-*","with":[{"field":"log.protoPayload.authenticationInfo.principalEmail","operator":"filter_term","value":"{{.log.protoPayload.authenticationInfo.principalEmail}}"}],"or":null,"within":"now-30m","count":10}]', '["lastEvent.log.protoPayload.methodName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1308, 'GCP probable Password Guessing', 3, 3, 2, 'Credential Access', 'T1110.001 - Brute Force: Password Guessing', 'Adversaries with no prior knowledge of legitimate credentials within the system or environment may guess passwords to attempt access to accounts. Without knowledge of the password for an account, an adversary may opt to systematically guess the password using a repetitive or iterative mechanism. An adversary may guess login credentials without prior knowledge of system or environment passwords during an operation by using a list of common passwords. Password guessing may or may not take into account the target''s policies on password complexity or use policies that may lock accounts out after a number of failed attempts.', '["https://attack.mitre.org/tactics/TA0006","https://attack.mitre.org/techniques/T1110/001/"]', e'equals("log.protoPayload.methodName", "google.login.LoginService.loginFailure") && exists("log.protoPayload.authenticationInfo.principalEmail") +', '2026-03-02 22:50:06.133124', true, true, 'origin', null, '[{"indexPattern":"v11-log-google-*","with":[{"field":"log.protoPayload.methodName","operator":"filter_term","value":"google.login.LoginService.loginFailure"},{"field":"log.protoPayload.authenticationInfo.principalEmail","operator":"filter_term","value":"{{.log.protoPayload.authenticationInfo.principalEmail}}"}],"or":null,"within":"now-5m","count":5}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1309, 'GCP Custom Role with Overly Permissive Permissions', 3, 3, 1, 'Privilege Escalation', 'T1098 - Account Manipulation', e'Detects creation or modification of GCP custom IAM roles which may include overly permissive permissions for privilege escalation. Attackers may create custom roles with broad permissions like iam.serviceAccountKeys.create, iam.serviceAccounts.actAs, or compute.instances.setMetadata to escalate privileges. + +Next Steps: +1. Review the custom role definition and its included permissions +2. Verify the role follows least privilege principles +3. Check for high-risk permissions like iam.* or resourcemanager.* +4. Review the identity creating the role and verify authorization +5. Check which users or service accounts are bound to the role +6. If overly permissive, modify the role to include only necessary permissions +7. Implement Organization Policy to restrict custom role creation +8. Use IAM Recommender to identify and reduce excess permissions +', '["https://cloud.google.com/iam/docs/creating-custom-roles","https://attack.mitre.org/techniques/T1098/"]', e'contains("log.protoPayload.serviceName", "iam.googleapis.com") && +(contains("log.protoPayload.methodName", "CreateRole") || + contains("log.protoPayload.methodName", "UpdateRole")) +', '2026-03-02 22:50:07.344879', true, true, 'origin', null, '[{"indexPattern":"v11-log-google-*","with":[{"field":"log.protoPayload.authenticationInfo.principalEmail","operator":"filter_term","value":"{{.log.protoPayload.authenticationInfo.principalEmail}}"}],"or":null,"within":"now-1h","count":2}]', '["lastEvent.log.protoPayload.methodName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1310, 'GCP BigQuery Data Exfiltration Detection', 3, 1, 1, 'Data Exfiltration', 'T1567 - Exfiltration Over Web Service', e'Detects BigQuery operations that may indicate data exfiltration including large data exports, table copies to external projects, and extract jobs writing to external storage. Attackers may use BigQuery to query and export large datasets from compromised projects. + +Next Steps: +1. Review the BigQuery job details including source and destination datasets +2. Check the data volume being exported or copied +3. Verify the destination project or storage bucket is legitimate +4. Review the identity performing the operation and verify authorization +5. Check if the query accesses sensitive tables or datasets +6. If unauthorized, cancel running jobs and revoke the identity\'s BigQuery permissions +7. Implement VPC Service Controls to restrict data export +8. Enable BigQuery authorized views to restrict data access +', '["https://cloud.google.com/bigquery/docs/audit-logging","https://attack.mitre.org/techniques/T1567/"]', e'contains("log.protoPayload.serviceName", "bigquery.googleapis.com") && +(contains("log.protoPayload.methodName", "jobservice.insert") || + contains("log.protoPayload.methodName", "tableservice.exportdata") || + contains("log.protoPayload.methodName", "datasets.copy")) +', '2026-03-02 22:50:08.569092', true, true, 'origin', null, '[{"indexPattern":"v11-log-google-*","with":[{"field":"log.protoPayload.authenticationInfo.principalEmail","operator":"filter_term","value":"{{.log.protoPayload.authenticationInfo.principalEmail}}"},{"field":"log.protoPayload.serviceName","operator":"filter_term","value":"bigquery.googleapis.com"}],"or":null,"within":"now-30m","count":10}]', '["lastEvent.log.protoPayload.methodName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1311, 'GCP 2-step verification disabled', 1, 2, 3, 'Defense Evasion', 'T1562 - Impair Defenses', 'Google Cloud has detected that 2-step verification was disabled for the organization or a user', '["https://attack.mitre.org/tactics/TA0005","https://attack.mitre.org/techniques/T1562/"]', e'equals("log.protoPayload.methodName", "google.login.LoginService.2svDisable") +', '2026-03-02 22:50:09.810631', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1312, 'GCP Workload Identity Federation Abuse', 3, 3, 1, 'Credential Access', 'T1078 - Valid Accounts', e'Detects creation or modification of workload identity pools and providers that enable external identities to access GCP resources. Attackers may create workload identity configurations to grant access to external attacker-controlled identity providers for persistent cloud access. + +Next Steps: +1. Review the workload identity pool and provider configuration +2. Verify the external identity provider is trusted and authorized +3. Check the attribute mappings and conditions for overly permissive access +4. Review which service accounts are bound to the workload identity pool +5. Verify the change was authorized through security change management +6. If unauthorized, delete the workload identity pool and revoke associated permissions +7. Audit all existing workload identity configurations for unauthorized providers +8. Implement Organization Policy to restrict workload identity pool creation +', '["https://cloud.google.com/iam/docs/workload-identity-federation","https://attack.mitre.org/techniques/T1078/"]', e'contains("log.protoPayload.serviceName", "iam.googleapis.com") && +(contains("log.protoPayload.methodName", "CreateWorkloadIdentityPool") || + contains("log.protoPayload.methodName", "CreateWorkloadIdentityPoolProvider") || + contains("log.protoPayload.methodName", "UpdateWorkloadIdentityPool") || + contains("log.protoPayload.methodName", "UpdateWorkloadIdentityPoolProvider")) +', '2026-03-02 22:50:11.101458', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.methodName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1313, 'GCP suspicious programmatic login', 1, 2, 3, 'Credential Access', 'T1110 - Brute Force', 'Google Cloud has detected a suspicious programmatic login. Programmatic login can be use to perform brute force attack.', '["https://attack.mitre.org/tactics/TA0006","https://attack.mitre.org/techniques/T1110"]', e'equals("log.protoPayload.methodName", "google.login.LoginService.suspiciousProgrammaticLogin") +', '2026-03-02 22:50:12.324932', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1314, 'Google Workspace MFA Enforcement Disabled', 3, 3, 1, 'Defense Evasion', 'T1556 - Modify Authentication Process', e'Detects when MFA enforcement is disabled in Google Workspace. Disabling MFA removes a critical security control and enables credential-based attacks against all users in the organization. + +Next Steps: +1. Immediately verify if the MFA policy change was authorized +2. Identify the admin who made the change and their authorization +3. Check for brute force or credential stuffing attempts following the change +4. Re-enable MFA enforcement immediately if unauthorized +5. Review all sign-ins that occurred while MFA was disabled +6. Check for other security policy changes from the same admin +7. Audit admin roles and consider implementing super admin 2SV enforcement +', '["https://support.google.com/a/answer/9176657","https://attack.mitre.org/techniques/T1556/"]', e'contains("log.protoPayload.methodName", "ENFORCE_STRONG_AUTHENTICATION") || +(contains("log.protoPayload.serviceName", "admin.googleapis.com") && contains("log.protoPayload.methodName", "2sv") && contains("log.protoPayload.request", "disable")) +', '2026-03-02 22:50:13.478141', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.authenticationInfo.principalEmail","lastEvent.log.protoPayload.methodName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1315, 'GCP suspicious login from less secure app', 1, 2, 3, 'Initial Access', 'T1190 - Exploit Public-Facing Application', 'Less secure apps (LSAs) are non-Google apps that can access your Google account with only a username and password. They make your account more vulnerable to hijacking attempts.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1190"]', e'equals("log.protoPayload.methodName", "google.login.LoginService.suspiciousLoginLessSecureApp") +', '2026-03-02 22:50:14.785489', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1316, 'GCP suspicious login blocked', 1, 2, 3, 'Initial Access', 'T1078 - Valid Accounts', 'A suspicious login to a user''s account was detected and blocked by Google Cloud.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1078"]', e'equals("log.protoPayload.methodName", "google.login.LoginService.suspiciousLogin") +', '2026-03-02 22:50:16.081535', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1317, 'GCP Cloud Storage Data Exfiltration', 3, 1, 1, 'Data Exfiltration', 'T1530 - Data from Cloud Storage Object', e'Detects GCP Cloud Storage operations indicating potential data exfiltration including making buckets publicly accessible, modifying IAM policies to grant allUsers access, or bulk object downloads. These actions may indicate an attacker attempting to exfiltrate data from cloud storage. + +Next Steps: +1. Review the affected bucket and its data classification +2. Check if the bucket was made publicly accessible +3. Verify the identity making the change has authorization +4. Review the IAM policy changes for allUsers or allAuthenticatedUsers bindings +5. Check for bulk GetObject operations following the policy change +6. If unauthorized, revert the bucket IAM policy and enable uniform bucket-level access +7. Review VPC Service Controls for the project +8. Enable Cloud Storage audit logging for data access events +', '["https://cloud.google.com/storage/docs/access-control","https://attack.mitre.org/techniques/T1530/"]', e'contains("log.protoPayload.serviceName", "storage.googleapis.com") && +(contains("log.protoPayload.methodName", "storage.setIamPermissions") || + contains("log.protoPayload.methodName", "storage.buckets.update") || + contains("log.protoPayload.methodName", "storage.objects.update")) && +(contains("log.protoPayload.request.policy.bindings", "allUsers") || + contains("log.protoPayload.request.policy.bindings", "allAuthenticatedUsers") || + contains("log.protoPayload.request.acl", "allUsers")) +', '2026-03-02 22:50:17.435194', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.resourceName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1318, 'GCP Project Manipulation and Shadow Projects', 2, 3, 3, 'Account Manipulation', 'T1578 - Modify Cloud Compute Infrastructure', e'Detects GCP project creation, deletion, and undelete operations that could indicate shadow project creation for persistence or project deletion for impact. Attackers may create new projects outside organizational controls to host malicious workloads. + +Next Steps: +1. Verify the project creation or deletion was authorized +2. Check if the new project is within the expected folder hierarchy +3. Review the project\'s billing account association +4. Examine IAM bindings on the new project for overly permissive access +5. Check if Organization Policies are applied to the new project +6. If unauthorized, shut down the project and investigate the creating identity +7. Implement Organization Policy constraints for project creation +8. Enable alerts for projects created outside approved folders +', '["https://cloud.google.com/resource-manager/docs/creating-managing-projects","https://attack.mitre.org/techniques/T1578/"]', e'contains("log.protoPayload.serviceName", "cloudresourcemanager.googleapis.com") && +(contains("log.protoPayload.methodName", "CreateProject") || + contains("log.protoPayload.methodName", "DeleteProject") || + contains("log.protoPayload.methodName", "UndeleteProject")) +', '2026-03-02 22:50:18.878275', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.methodName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1319, 'GCP probable Privilege Escalation, Kubernetes role bindings created or patched', 1, 2, 3, 'Privilege Escalation', 'T1548 - Abuse Elevation Control Mechanism', 'Privilege Escalation consists of techniques that adversaries use to gain higher-level permissions on a system or network. Adversaries can often enter and explore a network with unprivileged access but require elevated permissions to follow through on their objectives. Common approaches are to take advantage of system weaknesses, misconfigurations, and vulnerabilities. Identifies the creation or patching of potentially malicious role bindings. Users can use role bindings and cluster role bindings to assign roles to Kubernetes subjects (users, groups, or service accounts).', '["https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control","https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548"]', e'contains("log.protoPayload.methodName", ".rbac") && + regexMatch("log.protoPayload.methodName", \'((.+)\\\\.)?(cluster)?rolebinding(s)?\\\\.(create|patch)$\') && + !equals("log.protoPayload.authenticationInfo.principalEmail", "system:addon-manager") +', '2026-03-02 22:50:20.237466', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1320, 'GCP Network Packet Capture Configuration', 3, 1, 1, 'Credential Access', 'T1040 - Network Sniffing', e'Detects creation or modification of Packet Mirroring configurations in GCP. Attackers use packet mirroring to capture network traffic for credential theft, data exfiltration, or reconnaissance. + +Next Steps: +1. Verify the packet mirroring configuration was authorized for legitimate purposes +2. Review the mirrored network scope (which subnets, instances, protocols) +3. Check the collector destination for the mirrored traffic +4. Identify the user who created the configuration +5. If unauthorized, delete the packet mirroring policy immediately +6. Review the mirrored traffic destination for data exfiltration +7. Check for captured credentials or sensitive data +', '["https://cloud.google.com/vpc/docs/packet-mirroring","https://attack.mitre.org/techniques/T1040/"]', e'contains("log.protoPayload.methodName", "PacketMirrorings") && +(contains("log.protoPayload.methodName", "insert") || contains("log.protoPayload.methodName", "patch") || contains("log.protoPayload.methodName", "create")) +', '2026-03-02 22:50:21.673547', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.authenticationInfo.principalEmail","lastEvent.log.protoPayload.resourceName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1321, 'GKE Kubernetes Admission Webhook Modified', 3, 3, 2, 'Persistence', 'T1078.004 - Valid Accounts: Cloud Accounts', e'Detects creation or modification of admission webhook configurations in Google Kubernetes Engine. Attackers use malicious admission controllers to inject sidecar containers, modify workload specs, or intercept secrets. + +Next Steps: +1. Review the webhook configuration and its target service endpoint +2. Verify the webhook was deployed as part of a legitimate application +3. Check the namespace selector and object rules for the webhook +4. Examine what Kubernetes resources the webhook intercepts +5. If unauthorized, delete the webhook and audit all recent workload deployments +6. Review cluster RBAC for webhook management permissions +', '["https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/","https://attack.mitre.org/techniques/T1078/004/"]', e'contains("log.protoPayload.methodName", "admissionregistration.k8s.io") && +(contains("log.protoPayload.methodName", "mutatingwebhookconfigurations") || contains("log.protoPayload.methodName", "validatingwebhookconfigurations")) && +(contains("log.protoPayload.methodName", "create") || contains("log.protoPayload.methodName", "update") || contains("log.protoPayload.methodName", "patch")) +', '2026-03-02 22:50:23.085343', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.authenticationInfo.principalEmail","lastEvent.log.protoPayload.resourceName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1322, 'GCP probable Impact, Storage Bucket Deleted', 1, 2, 3, 'Impact', 'T1485 - Data Destruction', 'Impact consists of techniques that adversaries use to disrupt availability or compromise integrity by manipulating business and operational processes. Techniques used for impact can include destroying or tampering with data. In some cases, business processes can look fine, but may have been altered to benefit the adversaries goals. These techniques might be used by adversaries to follow through on their end goal or to provide cover for a confidentiality breach. Identifies when a Google Cloud Platform (GCP) storage bucket is deleted. An adversary may delete a storage bucket in order to disrupt their target''s business operations.', '["https://cloud.google.com/logging/docs/buckets","https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1485/"]', e'regexMatch("log.protoPayload.methodName", "(.+)\\\\.bucket(s)?\\\\.delete") +', '2026-03-02 22:50:24.421324', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1323, 'GCP KMS Key Destruction or Disabling', 1, 3, 3, 'Impact', 'T1552 - Unsecured Credentials', e'Detects destruction or disabling of Cloud KMS key versions which could render encrypted data unrecoverable. Attackers may destroy encryption keys as part of a destructive attack to prevent data recovery or to disrupt operations dependent on encrypted resources. + +Next Steps: +1. Immediately verify if the KMS key operation was authorized +2. Identify which resources are encrypted with the affected key +3. Check if the key version is in the scheduled destruction period and can be restored +4. Review the identity performing the operation and verify authorization +5. Assess the business impact of the key becoming unavailable +6. If unauthorized, restore the key version immediately during the destruction grace period +7. Implement IAM conditions to restrict KMS key destruction permissions +8. Enable Cloud KMS key rotation policies and cross-region key replication +', '["https://cloud.google.com/kms/docs/destroy-restore","https://attack.mitre.org/techniques/T1552/"]', e'contains("log.protoPayload.serviceName", "cloudkms.googleapis.com") && +(contains("log.protoPayload.methodName", "DestroyCryptoKeyVersion") || + contains("log.protoPayload.methodName", "DisableCryptoKeyVersion") || + contains("log.protoPayload.methodName", "UpdateCryptoKeyPrimaryVersion")) +', '2026-03-02 22:50:25.781701', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.resourceName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1324, 'GCP probable Government-backed attack', 3, 3, 2, 'Collection', 'T1560 - Archive Collected Data', 'A user''s account might have been targeted by government-backed attack. Government-backed attackers are trying to access the account of one of your users. An attack happens to less than 0.1% of all Google Account users. There''s a chance the alert is a false alarm. However, we believe we detected activities that government-backed attackers use to try to steal a password or other personal information. Such activity includes the user receiving an email containing a harmful attachment, links to malicious software downloads, or links to fake websites that are designed to access passwords.', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1560"]', e'contains("log.protoPayload.methodName", "google.login.LoginService.govAttackWarning") +', '2026-03-02 22:50:27.049223', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1325, 'GCP probable Exfiltration, Logging Sink Modification', 3, 2, 2, 'Exfiltration', 'T1537 - Transfer Data to Cloud Account', 'Exfiltration consists of techniques that adversaries may use to steal data from your network. Once they''ve collected data, adversaries often package it to avoid detection while removing it. This can include compression and encryption. Techniques for getting data out of a target network typically include transferring it over their command and control channel or an alternate channel and may also include putting size limits on the transmission. Identifies a modification to a Logging sink in Google Cloud Platform (GCP). Logging compares the log entry to the sinks in that resource. Each sink whose filter matches the log entry writes a copy of the log entry to the sink''s export destination. An adversary may update a Logging sink to exfiltrate logs to a different export destination.', '["https://cloud.google.com/logging/docs/export#how_sinks_work","https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks#LogSink","https://attack.mitre.org/techniques/T1537/","https://attack.mitre.org/tactics/TA0010/"]', e'regexMatch("log.protoPayload.methodName", "((.+)?sink(s)?\\\\.update|(.+)?v(\\\\w+)\\\\.ConfigServiceV(\\\\w+)\\\\.UpdateSink)") +', '2026-03-02 22:50:28.383564', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1326, 'GCP Domain-Wide API Access Granted', 3, 3, 2, 'Privilege Escalation', 'T1098 - Account Manipulation', e'Detects when domain-wide delegation is granted to a service account in Google Workspace. This allows the service account to impersonate any user in the domain and access their data, making it a high-impact privilege escalation vector. + +Next Steps: +1. Verify the domain-wide delegation was authorized by a domain administrator +2. Review the OAuth scopes granted to the service account +3. Check the service account\'s usage history and associated project +4. Verify the scopes follow the principle of least privilege +5. If unauthorized, revoke the delegation immediately +6. Audit all API calls made by the service account since the delegation was granted +7. Review Google Workspace admin logs for related changes +', '["https://cloud.google.com/iam/docs/using-iam-securely","https://attack.mitre.org/techniques/T1098/"]', e'contains("log.protoPayload.methodName", "AUTHORIZE_API_CLIENT_ACCESS") || +(contains("log.protoPayload.serviceName", "admin.googleapis.com") && contains("log.protoPayload.methodName", "GrantClientAccess")) +', '2026-03-02 22:50:29.709668', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.authenticationInfo.principalEmail","lastEvent.log.protoPayload.resourceName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1327, 'GCP DLP Re-Identification API Call', 3, 2, 0, 'Collection', 'T1565 - Data Manipulation', e'Detects calls to the DLP re-identification API which reverses data de-identification. This is a sensitive operation that could expose previously protected PII, financial data, or health records. Unauthorized use indicates potential data exfiltration attempts. + +Next Steps: +1. Verify the re-identification request was authorized for the specific use case +2. Review the data being re-identified and its sensitivity classification +3. Check the user identity and whether they have legitimate access to this data +4. Review the destination of the re-identified data +5. If unauthorized, revoke access and investigate potential data exposure +6. Review DLP API permissions and restrict re-identification access +', '["https://cloud.google.com/dlp/docs/reference/rest/v2/projects.content/reidentify","https://attack.mitre.org/techniques/T1565/"]', e'contains("log.protoPayload.methodName", "ReidentifyContent") || +contains("log.protoPayload.methodName", "reidentify") +', '2026-03-02 22:50:31.154034', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.authenticationInfo.principalEmail","lastEvent.log.protoPayload.methodName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1328, 'GCP probable Defense Evasion, Logging Sink Deletion', 1, 2, 3, 'Defense Evasion', 'T1562 - Impair Defenses', 'Defense Evasion consists of techniques that adversaries use to avoid detection throughout their compromise. Techniques used for defense evasion include uninstalling/disabling security software or obfuscating/encrypting data and scripts. Adversaries also leverage and abuse trusted processes to hide and masquerade their malware. Other tactics are cross-listed here when those techniques include the added benefit of subverting defenses. Identifies a Logging sink deletion in Google Cloud Platform (GCP). Every time a log entry arrives, Logging compares the log entry to the sinks in that resource. Each sink whose filter matches the log entry writes a copy of the log entry to the sink''s export destination. An adversary may delete a Logging sink to evade detection.', '["https://cloud.google.com/logging/docs/export","https://attack.mitre.org/techniques/T1562/","https://attack.mitre.org/tactics/TA0005/"]', e'regexMatch("log.protoPayload.methodName", "((.+)?sink(s)?\\\\.delete|(.+)?v(\\\\w+)\\\\.ConfigServiceV(\\\\w+)\\\\.DeleteSink)") +', '2026-03-02 22:50:32.509879', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1329, 'GCP Cryptomining Instance Launch Detection', 1, 2, 3, 'Resource Hijacking', 'T1496 - Resource Hijacking', e'Detects creation of GPU-accelerated or high-compute GCP instances commonly used for cryptomining. Attackers with compromised GCP credentials frequently launch expensive GPU instances (a2, g2) or compute-optimized instances in unusual regions for cryptocurrency mining operations. + +Next Steps: +1. Verify the identity launching the instance and confirm business justification +2. Check if GPU instances are normally used in this project +3. Review the instance\'s machine type and attached GPU accelerators +4. Examine the instance image for known mining software +5. Check billing dashboards for unexpected cost increases +6. If unauthorized, stop and delete the instance immediately +7. Rotate compromised credentials and review IAM bindings +8. Implement Organization Policy constraints to restrict GPU instance creation +', '["https://cloud.google.com/compute/docs/machine-types","https://attack.mitre.org/techniques/T1496/"]', e'contains("log.protoPayload.methodName", "compute.instances.insert") && +(contains("log.protoPayload.request.machineType", "a2-") || + contains("log.protoPayload.request.machineType", "g2-") || + contains("log.protoPayload.request.machineType", "n1-highmem-96") || + contains("log.protoPayload.request.machineType", "c2d-highcpu") || + contains("log.protoPayload.request.guestAccelerators", "nvidia")) +', '2026-03-02 22:50:33.819638', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.resourceName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1330, 'GCP Cloud Function and Cloud Run Abuse', 2, 2, 1, 'Persistence', 'T1059 - Command and Scripting Interpreter', e'Detects creation or modification of Cloud Functions and Cloud Run services which can be used for persistence, backdoor access, or command execution. Attackers may deploy serverless functions with high-privilege service accounts to maintain access or exfiltrate data. + +Next Steps: +1. Review the function or service code for malicious content +2. Check the associated service account and its permissions +3. Verify the deployer identity has authorization +4. Review the function trigger configuration (HTTP, Pub/Sub, etc.) +5. Check if the function allows unauthenticated invocations +6. If unauthorized, delete the function and revoke the service account\'s permissions +7. Review invocation logs for the function +8. Implement Organization Policy to restrict Cloud Function deployment +', '["https://cloud.google.com/functions/docs/securing","https://attack.mitre.org/techniques/T1059/"]', e'((contains("log.protoPayload.serviceName", "cloudfunctions.googleapis.com") && + (contains("log.protoPayload.methodName", "CreateFunction") || + contains("log.protoPayload.methodName", "UpdateFunction"))) || + (contains("log.protoPayload.serviceName", "run.googleapis.com") && + (contains("log.protoPayload.methodName", "CreateService") || + contains("log.protoPayload.methodName", "ReplaceService")))) +', '2026-03-02 22:50:35.261306', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.methodName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1331, 'GCP Break-Glass Container Workload Deployed', 3, 3, 2, 'Defense Evasion', 'T1548 - Abuse Elevation Control Mechanism', e'Detects deployment of container workloads using the break-glass mechanism to bypass Binary Authorization policy. While legitimate in emergency scenarios, this bypasses security controls and can be abused to deploy malicious or untrusted container images. + +Next Steps: +1. Verify the break-glass deployment was authorized and documented +2. Review the container image that was deployed +3. Check the user identity and their authorization level +4. Validate the business justification for the emergency bypass +5. Ensure Binary Authorization policies are restored after the emergency +6. Scan the deployed container for vulnerabilities and malware +7. Review cluster activity following the deployment +', '["https://cloud.google.com/binary-authorization/docs/using-breakglass","https://attack.mitre.org/techniques/T1548/"]', e'(equals("log.protoPayload.serviceName", "binaryauthorization.googleapis.com") && +contains("log.protoPayload.response", "breakglass")) || +(contains("log.protoPayload.methodName", "container.clusters") && +contains("log.protoPayload.request", "breakglass")) +', '2026-03-02 22:50:36.569229', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.authenticationInfo.principalEmail","lastEvent.log.protoPayload.resourceName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1332, 'GCP Audit Log Disabling or Tampering', 3, 3, 2, 'Defense Evasion', 'T1562.008 - Impair Defenses: Disable Cloud Logs', e'Detects attempts to disable GCP audit logging including sink deletion, log exclusion filter creation, and audit configuration changes. Attackers may manipulate logging infrastructure to hide their activities from security monitoring. + +Next Steps: +1. Immediately verify if the logging change was authorized +2. Review the specific sink or exclusion filter that was modified +3. Check the identity making the change and verify authorization +4. Assess what log types are no longer being collected +5. Restore logging configuration and ensure all critical logs are captured +6. Review activities that may have been hidden during the logging gap +7. Implement Organization Policy to prevent log sink deletion +8. Set up alerting on any changes to logging infrastructure +', '["https://cloud.google.com/logging/docs/audit","https://attack.mitre.org/techniques/T1562/008/"]', e'(contains("log.protoPayload.methodName", "DeleteSink") || + contains("log.protoPayload.methodName", "UpdateSink") || + contains("log.protoPayload.methodName", "CreateExclusion") || + contains("log.protoPayload.methodName", "UpdateExclusion") || + contains("log.protoPayload.methodName", "DeleteLog") || + contains("log.protoPayload.methodName", "SetIamPolicy")) && +(contains("log.protoPayload.serviceName", "logging.googleapis.com") || + contains("log.resource.type", "logging_sink") || + contains("log.resource.type", "logging_exclusion")) +', '2026-03-02 22:50:37.755334', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.methodName","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1333, 'GCP account is probably used for spamming', 1, 2, 3, 'Initial Access', 'T1566 - Phishing', 'A user''s account was disabled because Google has become aware that it was used to engage in spamming. Usually, spamming is used to perform other attacks like phishing or spread malware.', '["https://attack.mitre.org/tactics/TA0001","https://attack.mitre.org/techniques/T1566/"]', e'equals("log.protoPayload.methodName", "google.login.LoginService.accountDisabledSpamming") || +equals("log.protoPayload.methodName", "google.login.LoginService.accountDisabledSpammingThroughRelay") +', '2026-03-02 22:50:38.930239', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1334, 'GCP detected account with password leak', 3, 3, 2, 'Initial Access', 'T1078 - Valid Accounts', 'A user''s account was disabled because a password leak was detected by google.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1078"]', e'equals("log.protoPayload.methodName", "google.login.LoginService.accountDisabledPasswordLeak") +', '2026-03-02 22:50:40.003674', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1335, 'GCP probable hijacked account', 3, 3, 2, 'Collection', 'T1560 - Archive Collected Data', 'A user''s account was disabled because Google has detected a suspicious activity indicating it might have been compromised. Hijacked account can be used to perform other attacks like data collection and exfiltration', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1560"]', e'equals("log.protoPayload.methodName", "google.login.LoginService.accountDisabledHijacked") +', '2026-03-02 22:50:41.199708', true, true, 'target', null, '[]', '["target.ip","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1336, 'Cloud Identity Suspicious Sign-ins Detection', 3, 2, 1, 'Initial Access', 'T1078 - Valid Accounts', e'Detects suspicious sign-in attempts to Google Cloud Identity, including logins from unfamiliar locations, unusual IP addresses, or after multiple failed attempts. These could indicate compromised credentials or unauthorized access attempts. + +Next Steps: +1. Verify the legitimacy of the login attempt with the user +2. Check if the IP address is from a known malicious source +3. Review recent account activity for signs of compromise +4. Consider implementing additional MFA if not already enabled +5. If confirmed malicious, reset user credentials immediately +6. Review access logs for any unauthorized activities +', '["https://support.google.com/cloudidentity/answer/4580120?hl=en","https://cloud.google.com/blog/products/identity-security/logs-based-security-alerting-in-google-cloud","https://attack.mitre.org/techniques/T1078/"]', e'equals("log.protoPayload.serviceName", "login.googleapis.com") && +( + equals("log.protoPayload.metadata.event.type", "Suspicious Login") || + (equals("log.protoPayload.metadata.event.type", "login") && equals("log.protoPayload.metadata.event.parameter.is_suspicious", true)) || + equals("log.protoPayload.metadata.event.parameter.is_suspicious", true) +) +', '2026-03-02 22:50:42.596769', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.authenticationInfo.principalEmail","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1337, 'Binary Authorization Bypass Detection', 3, 3, 2, 'Defense Evasion', 'T1553 - Subvert Trust Controls', e'Detects attempts to bypass Binary Authorization controls including use of breakglass deployments, policy violations, and unauthorized container deployments. These events could indicate attempts to deploy untrusted or malicious container images. + +Next Steps: +1. Verify the legitimacy of the breakglass deployment or policy bypass +2. Review the container image source and verify its authenticity +3. Check if the user had proper authorization for emergency deployments +4. Examine the deployment context and business justification +5. Validate that security policies are restored after emergency deployment +6. Monitor for any subsequent suspicious activity from deployed containers +', '["https://cloud.google.com/binary-authorization/docs/audit-logging","https://cloud.google.com/binary-authorization/docs/run/using-breakglass-cloud-run","https://attack.mitre.org/techniques/T1553/"]', e'( + equals("log.protoPayload.serviceName", "binaryauthorization.googleapis.com") && + ( + contains("log.logName", "cloudaudit.googleapis.com/system_event") && + (contains("log.protoPayload.response.details", "breakglass") || equals("log.jsonPayload.breakglass", true)) + ) +) || +( + equals("log.resourceType", "cloud_run_revision") && + contains("log.logName", "cloudaudit.googleapis.com/system_event") && + ( + contains("log.protoPayload.response.status.conditions", "ContainerImageUnauthorized") || + equals("log.jsonPayload.policyViolation", true) || + equals("log.protoPayload.metadata.dryRun", true) + ) +) +', '2026-03-02 22:50:43.788032', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.authenticationInfo.principalEmail","lastEvent.log.protoPayload.resourceName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1338, 'Anthos Security Policy Violations', 3, 3, 2, 'Security Control Bypass', 'T1562 - Impair Defenses', e'Detects security-related events in Google Anthos environments including policy violations, authentication failures, and suspicious container activities. Monitors Anthos Service Mesh, Config Management, and Policy Controller events. + +Next Steps: +- Review the specific policy violation details in the event logs +- Verify if the violation was authorized or represents a legitimate security concern +- Check the source IP and user account associated with the violation +- Examine recent configuration changes to Anthos security policies +- Validate that security controls are properly configured and enforced +- Consider implementing additional monitoring for the affected resources +', '["https://cloud.google.com/anthos/docs/concepts/overview","https://attack.mitre.org/techniques/T1562/"]', e'( + oneOf("log.protoPayload.serviceName", ["anthos.googleapis.com", "anthospolicycontroller.googleapis.com", "anthosservicemesh.googleapis.com"]) || + oneOf("log.resourceType", ["k8s_cluster", "gke_cluster"]) +) && +( + contains("log.protoPayload.methodName", "Policy") || + oneOf("log.jsonPayload.type", ["admission.k8s.io/violation", "policy.violation", "security.alert"]) || + oneOf("log.severity", ["ERROR", "WARNING"]) +) && +( + equals("log.protoPayload.response.status", "PERMISSION_DENIED") || + contains("log.protoPayload.status.message", "violation") || + contains("log.protoPayload.status.message", "denied") || + contains("log.jsonPayload.details", "policy") +) +', '2026-03-02 22:50:45.089959', true, true, 'origin', null, '[]', '["lastEvent.log.protoPayload.resourceName","lastEvent.log.resource.labels.project_id"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/google/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/google/utm_group_rules_data_type.sql new file mode 100644 index 000000000..633d39a3a --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/google/utm_group_rules_data_type.sql @@ -0,0 +1,34 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1305, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1306, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1307, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1308, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1309, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1310, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1311, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1312, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1313, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1314, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1315, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1316, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1317, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1318, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1319, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1320, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1321, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1322, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1323, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1324, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1325, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1326, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1327, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1328, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1329, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1330, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1331, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1332, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1333, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1334, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1335, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1336, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1337, 12, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1338, 12, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/kaspersky-security/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/kaspersky-security/utm_correlation_rules.sql new file mode 100644 index 000000000..c6fd19e25 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/kaspersky-security/utm_correlation_rules.sql @@ -0,0 +1,351 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1160, 'Lateral Movement Indicators Detection', 3, 3, 2, 'Lateral Movement', 'T1021 - Remote Services', e'Detects indicators of lateral movement attempts within the network through Kaspersky antivirus logs. +Attackers use various techniques including PSExec, WMI, RDP, SMB shares, and exploitation tools to move +from one compromised system to others, expanding their access and control across the network. This rule +identifies blocked or detected activities that may indicate lateral movement attempts. + +Next Steps: +1. Investigate the source IP and hostname for signs of compromise +2. Review authentication logs for the same time period to identify potential credential theft +3. Check if the detected tools (PSExec, WMI, RDP) are authorized for use in your environment +4. Examine network traffic between the source and destination systems +5. Look for other suspicious activities from the same source host +6. Consider isolating affected systems if lateral movement is confirmed +7. Review similar patterns from the same source within the detection window +', '["https://attack.mitre.org/tactics/TA0008/","https://support.kaspersky.com/KESWin/11/en-us/151065.htm"]', e'(equals("log.cn1", "3") || equals("log.cs1", "DETECT") || equals("log.act", "blocked")) && +(contains("log.msg", ["psexec", "wmi", "rdp", "smb", "admin$", "ipc$", "c$", + "remote", "lateral", "pivot"]) || + contains("log.cs4", ["exploit", "mimikatz", "bloodhound", "sharphound", "propagat"])) && +exists("log.dst") && +exists("log.src") && +safe(log.src, "") != safe(log.dst, "") +', '2026-03-02 16:10:26.625080', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-kaspersky-*","with":[{"field":"log.src","operator":"filter_term","value":"{{.log.src}}"}],"or":null,"within":"now-2h","count":3}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1161, 'Kaspersky Rootkit Detection', 3, 3, 3, 'Defense Evasion', 'T1014 - Rootkit', e'Detects rootkit activity identified by Kaspersky security, including hidden processes, kernel-level modifications, and rootkit-specific malware classifications that indicate a deeply compromised system. + +Next Steps: +1. Immediately isolate the affected system +2. Do not trust any output from the compromised system +3. Perform offline forensic analysis +4. Plan for full system reimaging +5. Check for lateral movement from the compromised host +6. Determine the initial infection vector +', '["https://support.kaspersky.com/","https://attack.mitre.org/techniques/T1014/"]', e'exists("log.signatureID") && +(regexMatch("log.msg", "(?i)(rootkit|bootkit|Rootkit|hidden.*process|hidden.*module)") || + contains("log.cs2", "Rootkit") || contains("log.cs4", "Rootkit") || + contains("log.cs2", "Bootkit") || contains("log.cs4", "Bootkit") || + (contains("log.msg", "System Analysis") && contains("log.msg", "hidden"))) +', '2026-03-02 16:10:27.971187', true, true, 'origin', null, '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1162, 'Kaspersky Ransomware Behavior Detection', 3, 3, 3, 'Impact', 'T1486 - Data Encrypted for Impact', e'Detects ransomware behavior patterns identified by Kaspersky including mass file encryption, ransom note creation, and ransomware-specific malware classifications. + +Next Steps: +1. Immediately isolate the affected system from the network +2. Identify the ransomware variant from Kaspersky\'s classification +3. Check backup availability and integrity +4. Do not pay the ransom +5. Engage incident response team +6. Scan other systems for the same indicators +7. Determine the initial infection vector +', '["https://support.kaspersky.com/","https://attack.mitre.org/techniques/T1486/"]', e'exists("log.signatureID") && +(regexMatch("log.msg", "(?i)(ransomware|ransom|trojan-ransom|cryptolocker|locky|cerber|wannacry|ryuk|conti|lockbit|blackcat)") || + contains("log.cs2", "Trojan-Ransom") || contains("log.cs4", "Trojan-Ransom") || + (contains("log.msg", "encrypt") && contains("log.msg", "mass")) || + (contains("log.msg", "System Watcher") && contains("log.msg", "rollback"))) +', '2026-03-02 16:10:29.339649', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-kaspersky-*","with":[{"field":"log.src","operator":"filter_term","value":"{{.log.src}}"}],"or":null,"within":"now-10m","count":3}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1163, 'Kaspersky Agent Disabled or Tampered', 3, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects when the Kaspersky security agent is disabled, stopped, or tampered with. This is a critical indicator of defense evasion as attackers disable endpoint protection to execute malware undetected. + +Next Steps: +1. Immediately investigate the affected endpoint +2. Identify the user or process that disabled the agent +3. Check for concurrent malicious activity +4. Re-enable the Kaspersky agent +5. Perform a full system scan +6. Check for similar events on other endpoints +', '["https://support.kaspersky.com/","https://attack.mitre.org/techniques/T1562/001/"]', e'exists("log.signatureID") && +(regexMatch("log.msg", "(?i)(kaspersky|klnagent|kavfs|kesl).*( disabled| stopped| removed| tampered| uninstalled)") || + regexMatch("log.msg", "(?i)(protection|self-defense).*(disabled|off|stopped)") || + (contains("log.cs1", "PROTECTION") && contains("log.message", "disabled")) || + (contains("log.msg", "agent") && contains("log.message", "not running"))) +', '2026-03-02 16:10:30.733359', true, true, 'origin', null, '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1164, 'Kaspersky Data Exfiltration Attempts Detection', 3, 2, 1, 'Exfiltration', 'T1048 - Exfiltration Over Alternative Protocol', e'Detects potential data exfiltration attempts identified by Kaspersky through suspicious network traffic patterns, large data transfers, or connections to suspicious external destinations. This rule monitors for network threats, trojan/backdoor detections, and suspicious data transfer patterns that may indicate data exfiltration. + +Next Steps: +1. Immediately identify the source host (origin.ip) and any associated user accounts on the affected system +2. Check if the destination IP (target.ip) is known malicious using threat intelligence sources +3. Review the volume and frequency of data transfers to this destination in the last 24-48 hours +4. Search for any other malware detections (especially Trojans/Backdoors) on the same host +5. Analyze network traffic logs for unusual patterns or protocols from the source IP +6. Check if other hosts in your network have connected to the same destination +7. If confirmed malicious: + - Block the destination IP at firewall/proxy level + - Isolate the affected system from network + - Initiate full incident response procedures + - Preserve evidence for forensic analysis +8. Document all findings and actions taken for compliance and future reference +', '["https://attack.mitre.org/techniques/T1048/","https://support.kaspersky.com/KLMS/8.2/en-US/151684.htm"]', e'(equals("log.cat", "NetworkThreat") || + regexMatch("log.cs2", "(?i).*(trojan|backdoor).*") || + regexMatch("log.msg", "(?i).*(data.*transfer|exfiltrat|upload.*suspicious|unauthorized.*transfer).*") || + regexMatch("log.msg", "(?i).*(data.*exfiltration|suspicious.*upload|unauthorized.*transfer).*")) && +exists("target.ip") && +greaterOrEqual("log.cefDeviceSeverity", "3") +', '2026-03-02 16:10:32.000991', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-kaspersky-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.cat","operator":"filter_term","value":"NetworkThreat"}],"or":null,"within":"now-30m","count":5}]', '["origin.ip","target.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1165, 'Kaspersky Command and Control Communication Detection', 3, 3, 2, 'Command and Control', 'T1071 - Application Layer Protocol', e'Detects potential command and control (C2) communication attempts identified by Kaspersky, including suspicious outbound connections, malware callbacks, and botnet communication patterns. This rule triggers when Kaspersky identifies network threats related to botnet activity, C2 communications, or malware beaconing that was not successfully blocked. + +Next Steps: +1. Immediately isolate the affected system from the network to prevent further C2 communication +2. Review the target IP address against threat intelligence feeds to confirm malicious activity +3. Check if other systems have communicated with the same C2 server +4. Analyze the process or malware that initiated the connection +5. Review Kaspersky logs for additional context about the threat +6. Perform a full system scan and forensic analysis on the affected machine +7. Update antivirus signatures and ensure real-time protection is enabled +8. Consider reimaging the system if compromise is confirmed +', '["https://attack.mitre.org/techniques/T1071/","https://support.kaspersky.com/KLMS/8.2/en-US/151504.htm"]', e'(contains("log.cs2", ["Bot", "bot", "C2", "Command", "command"]) || + contains("log.msg", ["callback", "beacon", "botnet", "command and control"]) || + equals("log.cat", "NetworkThreat")) && +exists("target.ip") && +action != "blocked" && action != "Blocked" +', '2026-03-02 16:10:33.401026', true, true, 'origin', null, '[]', '["origin.host","target.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1166, 'Code Injection Attempt Detection', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1055 - Process Injection', e'Detects attempts to inject malicious code into legitimate processes. This technique is commonly used by malware to evade detection and gain elevated privileges by running within trusted processes. + +Next Steps: +1. Immediately isolate the affected system to prevent lateral movement +2. Identify the source process that attempted the injection +3. Check if the malware was successfully quarantined or if manual removal is needed +4. Review system logs for any suspicious activities around the same timeframe +5. Scan the system with updated antivirus definitions +6. Check for persistence mechanisms (scheduled tasks, registry keys, services) +7. Review network connections from the affected host for C2 communications +8. Consider reimaging the system if critical processes were compromised +', '["https://attack.mitre.org/techniques/T1055/","https://support.kaspersky.com/KESWin/11/en-us/151065.htm"]', e'(regexMatch("log.msg", "(?i).*(inject|injection|CreateRemoteThread|SetWindowsHookEx|WriteProcessMemory).*") || + (contains("log.cs4", ["inject", "hooking", "trojan", "backdoor"]) && + contains("action", ["terminate", "delete", "quarantine"]))) && +contains("log.msg", ["lsass", "csrss", "winlogon", "services", "svchost", "explorer"]) +', '2026-03-02 16:10:34.586175', true, true, 'origin', '["origin.host","log.cs4"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1167, 'Kaspersky Critical Object Detection', 3, 3, 2, 'Execution', 'T1204 - User Execution: Malicious File', e'Detects when Kaspersky identifies critical threats including malware, trojans, or other dangerous objects that require immediate attention. High severity detections often indicate active threats. + +Next Steps: +1. Immediately isolate the affected system from the network to prevent lateral movement +2. Identify the malware name/signature from log.cs1, log.cs2, or log.cs4 fields +3. Check if Kaspersky successfully quarantined or removed the threat +4. Scan other systems in the same network segment for similar infections +5. Review recent user activity and email attachments that could be the infection vector +6. Collect and preserve forensic artifacts if needed for incident response +7. Update antivirus signatures and run a full system scan +8. Consider reimaging the system if the infection is severe or persistent +', '["https://support.kaspersky.com/ScanEngine/1.0/en-US/186767.htm","https://attack.mitre.org/techniques/T1204/"]', e'(greaterOrEqual("log.cefDeviceSeverity", "7") || + equalsIgnoreCase("log.cefDeviceSeverity", "High") || + equalsIgnoreCase("log.cefDeviceSeverity", "Very-High")) && +(contains("log.cs1", ["INFECTED", "MALWARE", "TROJAN"]) || + contains("log.cs2", ["Trojan", "HEUR:", "PDM:", "UDS:"]) || + contains("log.cs4", ["Trojan", "HEUR:", "PDM:", "UDS:"]) || + contains("log.msg", ["infected", "malicious", "dangerous"])) +', '2026-03-02 16:10:35.811944', true, true, 'origin', null, '[]', '["origin.host","log.signatureID"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1149, 'Kaspersky WMI Abuse Detection', 3, 3, 2, 'Execution', 'T1047 - Windows Management Instrumentation', e'Detects potential Windows Management Instrumentation (WMI) abuse identified by Kaspersky, including suspicious WMI queries, event subscriptions, or process creation via WMI. WMI is a legitimate Windows component often abused by attackers for lateral movement, persistence, and code execution. + +Next Steps: +1. Identify the affected host and user account involved in the WMI activity +2. Review the specific WMI commands or queries that triggered the alert +3. Check for any unauthorized scheduled tasks or startup items created via WMI +4. Look for other indicators of compromise on the affected system +5. Verify if this is legitimate administrative activity or potential malicious behavior +6. If confirmed malicious, isolate the system and perform incident response procedures +', '["https://attack.mitre.org/techniques/T1047/","https://support.kaspersky.com/KLMS/8.2/en-US/151684.htm"]', e'(contains("log.msg", ["WMI", "wmi", "wmic", "winmgmt", "scrcons.exe"]) || + contains("log.msg", "WMI")) && +(greaterOrEqual("log.cefDeviceSeverity", "3") || equals("log.cat", "blocked")) +', '2026-03-02 16:10:11.855340', true, true, 'origin', null, '[]', '["origin.host","origin.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1150, 'Kaspersky Trusted Application Compromise Detection', 3, 3, 2, 'Defense Evasion', 'T1218 - Signed Binary Proxy Execution', e'Identifies when legitimate or trusted applications exhibit malicious behavior, potentially indicating compromise or exploitation. This includes detecting when signed binaries are used for malicious purposes or when trusted processes perform suspicious activities. This is a critical security event that indicates an attacker may be using living-off-the-land techniques to evade detection. + +Next Steps: +1. Immediately isolate the affected system to prevent lateral movement +2. Identify the compromised trusted application and its process chain +3. Review recent system changes and user activities on the affected host +4. Check for persistence mechanisms (scheduled tasks, services, registry keys) +5. Analyze network connections from the compromised application +6. Look for data exfiltration indicators from the affected system +7. Consider reimaging the system if compromise is confirmed +8. Update security policies to monitor the exploited application more closely +', '["https://www.kaspersky.com/enterprise-security/wiki-section/products/kaspersky-anti-targeted-attack-platform","https://attack.mitre.org/techniques/T1218/","https://attack.mitre.org/techniques/T1574/"]', e'exists("log.signatureID") && +(contains("log.msg", ["trusted application", "signed binary", "legitimate program"]) || + contains("log.cs1", "TRUSTED_COMP") || + contains("log.cs4", "TrustedApp") || + contains("log.msg", "whitelisted") || + (equals("log.cat", "Exploit Prevention") && contains("log.msg", "exploit")) || + containsAll("log.msg", ["behavior", "trusted"])) && +oneOf("log.cefDeviceSeverity", ["High", "Medium"]) +', '2026-03-02 16:10:13.161179', true, true, 'origin', null, '[]', '["origin.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1151, 'Kaspersky System File Tampering Detection', 2, 3, 1, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects attempts to tamper with critical system files, Windows services, or protected system components. This includes unauthorized modifications to system binaries, service configurations, or attempts to manipulate security-critical files. + +Next Steps: +1. Identify the affected system file or component from the log details +2. Check if the modification was authorized (planned maintenance, legitimate software update) +3. Review process information to identify the source of the tampering attempt +4. Look for additional indicators of compromise on the affected system +5. Investigate any parent processes or scripts that initiated the modification +6. Check for persistence mechanisms that may have been established +7. Consider isolating the system if unauthorized tampering is confirmed +', '["https://support.kaspersky.com/kwts/6.1/267200","https://attack.mitre.org/techniques/T1562/001/","https://attack.mitre.org/techniques/T1036/"]', e'exists("log.signatureID") && +(contains("log.msg", ["system file", "critical file", "protected file", "service tamper"]) || + contains("log.cs1", "SYSTEM_MOD") || + contains("log.cs4", "SystemFile") || + contains("log.msg", ["system modification", "unauthorized change"]) || + (equals("log.cat", "Behavior Detection") && contains("log.msg", "modify"))) +', '2026-03-02 16:10:14.568249', true, true, 'origin', null, '[]', '["origin.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1152, 'Kaspersky Suspicious Service Installation Detection', 2, 2, 2, 'Persistence, Privilege Escalation', 'T1543.003 - Create or Modify System Process: Windows Service', e'Detects suspicious Windows service installation or modification attempts identified by Kaspersky, which could indicate malware persistence mechanisms or privilege escalation attempts. Service manipulation is a common technique used by malware to maintain persistence on compromised systems. + +Next Steps: +1. Identify the service name and executable path from the alert details +2. Verify if the service installation was authorized and legitimate +3. Check the digital signature and reputation of the service executable +4. Review parent process that initiated the service installation +5. Look for other suspicious activities on the affected host around the same time +6. If confirmed malicious, stop and remove the service, quarantine associated files +7. Perform full system scan and check for additional compromise indicators +', '["https://attack.mitre.org/techniques/T1543/003/","https://support.kaspersky.com/ScanEngine/2.1/en-US/186767.htm"]', e'(containsAll("log.msg", ["Service", "install"]) || + containsAll("log.msg", ["sc.exe", "create"]) || + containsAll("log.msg", ["New", "Service"]) || + contains("log.fname", "\\\\services.exe") || + contains("log.cs2", "Service")) && +(oneOf("log.cs1", ["infected", "suspicious"]) || + greaterOrEqual("log.cefDeviceSeverity", "3")) +', '2026-03-02 16:10:15.873009', true, true, 'origin', null, '[]', '["origin.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1153, 'Kaspersky Suspicious Scheduled Tasks Detection', 3, 3, 2, 'Execution, Persistence, Privilege Escalation', 'T1053 - Scheduled Task/Job', e'Detects suspicious scheduled task creation or modification identified by Kaspersky, which could indicate persistence mechanisms used by malware or attackers. Scheduled tasks are commonly abused by attackers to maintain persistence, execute malicious code at specific times, or escalate privileges. + +Next Steps: +1. Review the scheduled task details in log.msg, log.fname, and log.descMsg fields +2. Check the specific threat signature in log.signatureID to understand the detection +3. Examine log.cs1 and log.cs2 fields for additional threat context and classification +4. Verify if the task creation was part of legitimate administrative activity +5. Check the affected host (origin.host) for other persistence mechanisms: + - Registry run keys + - Startup folder items + - Services + - WMI event subscriptions +6. Review log.deviceTime for timeline analysis and correlate with other security events +7. If confirmed malicious: + - Disable or remove the scheduled task immediately + - Scan the system for additional malware components + - Check if the malware has spread to other systems + - Preserve evidence and initiate incident response procedures +', '["https://attack.mitre.org/techniques/T1053/","https://support.kaspersky.com/ScanEngine/1.0/en-US/186767.htm"]', e'(containsAll("log.msg", ["scheduled", "task"]) || + contains("log.msg", ["schtasks", "schedule"]) || + contains("log.msg", ["scheduled", "task"]) || + contains("log.cs2", "persist") || + contains("log.fname", "\\\\Tasks\\\\") || + contains("log.cat", "persistence")) && +(exists("log.signatureID") || + oneOf("log.cs1", ["infected", "suspicious"]) || + exists("log.cefDeviceSeverity")) +', '2026-03-02 16:10:17.226855', true, true, 'origin', null, '[]', '["origin.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1154, 'Suspicious Packed Executable Detection', 3, 3, 2, 'Defense Evasion', 'T1027.002 - Obfuscated Files or Information: Software Packing', e'Detects when Kaspersky identifies suspicious packed executables, which are often used by malware to evade detection and analysis. Packed executables use compression or encryption to hide their true content and make reverse engineering more difficult. + +Next Steps: +1. Identify the affected system from origin.hostname and origin.ip fields +2. Review the detected threat details from log.descMsg and log.msg fields +3. Check the action taken by the antivirus (blocked/detected) in the action field +4. Verify if the file is legitimate software that uses packing for protection +5. If malicious, isolate the affected system immediately +6. Perform a full system scan to identify additional threats +7. Review process execution logs for suspicious child processes spawned by packed executables +8. Check network connections initiated by the suspicious executable +9. Submit the sample to Kaspersky or third-party sandbox for detailed analysis +10. Update antivirus signatures and ensure real-time protection is enabled +', '["https://www.kaspersky.com/resource-center/threats/suspicious-packers","https://attack.mitre.org/techniques/T1027/002/"]', e'oneOf("action", ["blocked", "detected"]) && +(contains("log.msg", ["Packed", "packer"]) || + contains("log.msg", ["packed", "Packed"]) || + contains("log.msg", ["NSAnti", "Themida", "VMProtect", "ASPack", "UPX", + "PECompact", "Enigma", "Armadillo"]) || + contains("log.msg", ["NSAnti", "Themida", "VMProtect", "ASPack", "UPX", + "PECompact", "Enigma", "Armadillo"]) || + contains("log.cat", ["Trojan.Packed", "Packed"])) +', '2026-03-02 16:10:18.535932', true, true, 'origin', null, '[]', '["origin.host","origin.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1155, 'Kaspersky Suspicious Network Activity Detection', 3, 2, 2, 'Command and Control', 'T1071 - Application Layer Protocol', e'Detects suspicious network activities including unusual connections, potential C2 communications, or network-based attacks identified by Kaspersky security monitoring. This rule triggers when Kaspersky blocks network connections that match suspicious patterns and multiple similar events occur from the same host. + +Next Steps: +1. Review the source and destination IP addresses for known malicious indicators using threat intelligence feeds +2. Check if the blocked connection was attempting to reach known C2 servers or suspicious domains +3. Examine the process that initiated the network connection (check log.processName or log.filePath if available) +4. Review other security events from the same host within the last hour for additional IOCs +5. Verify if multiple hosts are exhibiting similar network behavior (potential lateral movement or outbreak) +6. Check firewall logs for any successful connections to the same destination that may have bypassed Kaspersky +7. Consider isolating the affected system if C2 communication is confirmed +8. Run a full system scan on the affected host and check for persistence mechanisms +9. Review network traffic logs for data exfiltration attempts to the same destination +10. Document the incident and update block lists with confirmed malicious IPs/domains +', '["https://support.kaspersky.com/kwts/6.1/267200","https://attack.mitre.org/techniques/T1071/","https://attack.mitre.org/techniques/T1043/"]', e'exists("log.signatureID") && +(contains("log.msg", ["suspicious connection", "network attack", "port scan", "unusual traffic"]) || + contains("log.msg", "network") || + contains("log.cs1", "NETWORK") || + contains("log.cs2", "Net-Worm") || + contains("log.cs4", "Net-Worm") || + (exists("log.target.ip") && exists("log.dpt"))) && +equals("action", "Blocked") +', '2026-03-02 16:10:19.847840', true, true, 'origin', null, '[{"indexPattern":"v11-log-antivirus-kaspersky-*","with":[{"field":"log.src","operator":"filter_term","value":"{{.log.src}}"},{"field":"log.dstIP","operator":"filter_term","value":"{{.log.dstIP}}"}],"or":null,"within":"now-30m","count":5}]', '["target.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1156, 'Kaspersky Sandbox Evasion Attempts Detection', 3, 3, 2, 'Defense Evasion, Discovery', 'T1497 - Virtualization/Sandbox Evasion', e'Identifies malware attempting to detect and evade sandbox environments. This includes time-based evasion, environment checks, anti-VM techniques, and other behaviors designed to avoid analysis in controlled environments. + +Next Steps: +1. Immediately isolate the affected system to prevent potential malware spread +2. Review the process that triggered the sandbox evasion detection +3. Check for any suspicious parent processes or child processes +4. Collect memory dumps and samples for deeper analysis +5. Review recent file downloads and email attachments on the affected system +6. Check if similar detection occurred on other systems in the network +7. Consider submitting the sample to Kaspersky for further analysis +', '["https://www.kaspersky.com/enterprise-security/malware-sandbox","https://attack.mitre.org/techniques/T1497/","https://attack.mitre.org/techniques/T1497/001/"]', e'exists("log.signatureID") && +(contains("log.msg", ["sandbox", "evasion", "anti-VM", "virtualization"]) || + contains("log.msg", ["sandbox", "evasion"]) || + contains("log.cs1", "SANDBOX_") || + contains("log.cs4", ["Evasion", "AntiVM", "environment check", "time delay", "VM detection"]) || + (equals("log.cat", "Behavior Detection") && + contains("log.msg", ["delay", "sleep"]))) +', '2026-03-02 16:10:21.163229', true, true, 'origin', '["origin.host"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1157, 'Process Hollowing Detection', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1055.012 - Process Injection: Process Hollowing', e'Detects process hollowing attempts where malware creates a new process in suspended state, unmaps its memory, and replaces it with malicious code. This advanced technique is used to evade detection by hiding malicious code within legitimate processes. + +Next Steps: +1. Immediately isolate the affected system to prevent lateral movement +2. Identify the parent process that initiated the hollowing attempt +3. Check for persistence mechanisms on the affected host +4. Review process creation events around the same timeframe +5. Collect memory dumps for forensic analysis +6. Search for similar patterns across other endpoints +7. Update endpoint protection policies to block the identified threat +', '["https://attack.mitre.org/techniques/T1055/012/","https://www.kaspersky.com/enterprise-security/wiki-section/products/behavior-based-protection"]', e'(equals("log.signatureID", "3") || equals("log.cs1", "DETECT")) && +(contains("log.msg", ["hollow", "RunPE", "suspend", "NtUnmapViewOfSection", "ZwUnmapViewOfSection"]) || + contains("log.cs4", ["hollow", "RunPE", "replace"]) || + contains("log.msg", ["hollow", "suspended", "unmap"])) && +greaterOrEqual("log.cefDeviceSeverity", "3") +', '2026-03-02 16:10:22.559264', true, true, 'origin', null, '[]', '["log.cs5","origin.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1158, 'Kaspersky Application Privilege Escalation Detection', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1055/T1134 - Process Injection and Access Token Manipulation', e'Detects attempts to escalate privileges through application manipulation, process injection, or unauthorized elevation of permissions monitored by Kaspersky endpoint protection. These techniques are commonly used by attackers to gain higher-level permissions on compromised systems. + +Next Steps: +1. Immediately isolate the affected system to prevent lateral movement +2. Review the process that triggered the alert and its parent process chain +3. Check if the source process is legitimate or potentially malicious +4. Look for other suspicious activities on the same host in the last hour +5. Collect memory dumps if possible for forensic analysis +6. Review user account permissions and recent changes +7. Check for any unauthorized scheduled tasks or services +8. Update Kaspersky signatures and run a full system scan +', '["https://support.kaspersky.com/KLMS/8.2/en-US/151684.htm","https://attack.mitre.org/techniques/T1055/","https://attack.mitre.org/techniques/T1134/"]', e'exists("log.signatureID") && +!equals("action", "Allowed") && +(contains("log.msg", ["privilege", "elevation", "EXPLOIT", "Exploit", + "process injection", "token manipulation"]) || + contains("log.cs1", "EXPLOIT") || + contains("log.cs2", "Exploit") || + contains("log.cs4", "Exploit") || + contains("log.msg", ["privilege", "elevation"])) +', '2026-03-02 16:10:23.961675', true, true, 'origin', null, '[]', '["log.signatureID","origin.host","origin.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1159, 'Living Off the Land Binaries (LOLBins) Abuse Detection', 3, 3, 2, 'Defense Evasion', 'T1218 - System Binary Proxy Execution', e'Detects the malicious use of legitimate Windows system binaries (LOLBins) to execute malicious code. Attackers abuse these trusted tools to bypass security controls and maintain persistence. LOLBins are particularly dangerous because they are signed Microsoft binaries that are trusted by most security products. + +Next Steps: +1. Immediately isolate the affected system from the network to prevent lateral movement +2. Review the full context of the detection including command line parameters and parent processes +3. Check for any network connections or file downloads initiated by the LOLBin process +4. Look for persistence mechanisms (scheduled tasks, registry keys, services) created around the same time +5. Scan for additional indicators of compromise on the affected system +6. Review user account activity for signs of compromise or privilege escalation +7. Consider reimaging the system if fileless malware is confirmed +', '["https://attack.mitre.org/techniques/T1218/","https://lolbas-project.github.io/","https://www.kaspersky.com/enterprise-security/wiki-section/products/fileless-threats-protection"]', e'(equals("log.signatureID", "3") || equals("log.cs1", "DETECT")) && +(regexMatch("log.msg", "(?i).*(rundll32|regsvr32|mshta|certutil|bitsadmin|powershell|wmic|cscript|wscript|msiexec|installutil|regasm|regsvcs).*") || + contains("log.cs4", ["fileless", "LOLBin", "LOLBas"])) && +(contains("log.msg", ["download", "execute", "bypass", "encoded", "obfuscat", "hidden", "malicious"]) || + exists("log.actionResult")) +', '2026-03-02 16:10:25.187528', true, true, 'origin', null, '[]', '["log.cs4","origin.host"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/kaspersky-security/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/kaspersky-security/utm_group_rules_data_type.sql new file mode 100644 index 000000000..de17a1a18 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/kaspersky-security/utm_group_rules_data_type.sql @@ -0,0 +1,19 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1160, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1161, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1162, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1163, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1164, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1165, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1166, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1167, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1149, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1150, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1151, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1152, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1153, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1154, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1155, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1156, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1157, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1158, 28, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1159, 28, null); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/windows/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260302/windows/utm_correlation_rules.sql new file mode 100644 index 000000000..c1e2ed130 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/windows/utm_correlation_rules.sql @@ -0,0 +1,2314 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (879, 'Windows: Unusual Process Network Connection', 3, 3, 2, 'Defense Evasion', 'Trusted Developer Utilities Proxy Execution', 'Identifies network activity from unexpected system applications. This may indicate adversarial activity as these applications are often leveraged by adversaries to execute code and evade detection.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1127/"]', 'regexMatch("log.eventDataProcessName", "(Microsoft.Workflow.Compiler.exe|bginfo.exe|cdb.exe|cmstp.exe|csi.exe|dnx.exe|fsi.exe|ieexec.exe|iexpress.exe|odbcconf.exe|rcsi.exe|xwizard.exe)")', '2026-03-02 13:21:40.452980', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataProcessID","operator":"filter_term","value":"{{.log.eventDataProcessID}}"}],"or":null,"within":"now-5m","count":3}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (880, 'Windows: Suspicious Managed Code Hosting Process', 3, 3, 2, 'Defense Evasion', 'T1055 - Process Injection', 'Identifies a suspicious managed code hosting process which could indicate code injection or other form of suspicious code execution.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', 'regexMatch("log.eventDataProcessName", "(wscript.exe|cscript.exe|mshta.exe|wmic.exe|regsvr32.exe|svchost.exe|dllhost.exe|cmstp.exe)")', '2026-03-02 13:21:41.849658', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataProcessName","operator":"filter_term","value":"{{.log.eventDataProcessName}}"},{"field":"log.eventdataProcessID","operator":"filter_term","value":"{{.log.eventdataProcessID}}"}],"or":null,"within":"now-5m","count":3}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (881, 'Windows: Potential Credential Access via Renamed COM+ Services DLL', 3, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies suspicious renamed COMSVCS.DLL Image Load, which exports the MiniDump function that can be used to dump a process memory. This may indicate an attempt to dump LSASS memory while bypassing command-line based detection in preparation for credential access.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/001/"]', 'contains("log.eventCategory", "process") && contains("log.eventProcessName", "rundll32.exe") && regexMatch("log.eventDataset", "(windows.sysmon_operational)") && equals("log.eventId", "7") && !equals("log.fileName", "COMSVCS.DLL") && (regexMatch("log.filePeOriginalFileName", "(COMSVCS.DLL)") || regexMatch("log.filePeImphash", "(EADBCCBB324829ACB5F2BBE87E5549A8)"))', '2026-03-02 13:21:43.155628', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.processEntityId","operator":"filter_term","value":"{{.log.processEntityId}}"},{"field":"log.eventCategory","operator":"filter_term","value":"process"}],"or":null,"within":"now-5m","count":3}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (882, 'Windows: Multiple Vault Web Credentials Read', 2, 3, 2, 'Credential Access', 'T1555.004 - Credentials from Password Stores: Windows Credential Manager', 'Windows Credential Manager allows you to create, view, or delete saved credentials for signing into websites, connected applications, and networks. An adversary may abuse this to list or dump credentials stored in the Credential Manager for saved usernames and passwords. This may also be performed in preparation of lateral movement.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1555/004/"]', 'equals("log.eventCode", 5382) && !equals("log.eventDataSubjectLogonId", "0x3e7") && (contains("log.eventDataSchemaFriendlyName", "Windows Web Password Credential") || contains("log.eventDataResource", "http"))', '2026-03-02 13:21:44.517513', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataProcessPid","operator":"filter_term","value":"{{.log.eventDataProcessPid}}"},{"field":"log.eventCode","operator":"filter_term","value":"5382"},{"field":"log.eventDataSubjectLogonId","operator":"filter_not_match","value":"0x3e7"}],"or":[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataSchemaFriendlyName","operator":"filter_term","value":"Windows Web Password Credential"},{"field":"log.eventDataResource","operator":"filter_term","value":"http"}],"or":null,"within":"now-60s","count":1}],"within":"now-60s","count":1}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (883, 'Windows: Remote Logon followed by Scheduled Task Creation', 3, 3, 2, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies a remote logon followed by a scheduled task creation on the target host. This could be indicative of adversary lateral movement.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', 'equals("log.action", "logged-in") && equals("actionResult", "success") && !contains("log.UserName", "ANONYMOUS LOGON") && !contains("log.UserDomain", "NT AUTHORITY")', '2026-03-02 13:21:45.774870', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.hostId","operator":"filter_term","value":"{{.log.hostId}}"},{"field":"log.eventDataSubjectLogonId","operator":"filter_term","value":"scheduled-task-created"}],"or":null,"within":"now-60s","count":3}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (884, 'Windows: Possible ransomware attack detected. Unusual File Extensions.', 3, 3, 3, 'Impact', 'T1486 - Data Encrypted for Impact', 'Ransomware, is a type of malware that prevents users from accessing their system or personal files and requires payment of a ransom in order to gain access to them again. Identifies ransomware attempts. Files with unusual file extensions have been detected, potentially indicating encrypted files created by ransomware.', '["https://attack.mitre.org/tactics/TA0040/"]', 'equals("log.eventCode", 4663) && regexMatch("log.eventDataFileName", "\\.(7z\\.encrypted|aaa|abcd|abtc|acc|aes256|aes_ni|aes256ctr|aes256encrypted|aes_gcm|ajp|alcatraz_locked|alfa|amnesia[1-9]?|amsi|apocalypse666|armadillo|arrow|asasin|asi|atom128|auditor|aurora|autoit|avastvirusinfo|avenger|av666|azero|barak|barrax|bart|beef|beetle|bip|bit|bitcoin|bl2r|blackblock|blackmail|blast|blind|bmw|boot|braincrypt[3-8]?|broken|btcware|budak|bull|buydecryptor|cakl|calipso|calum|carote|cats|cbf|cccmn|ccrypt|cerber[3-9]?|chifrator|chimera|ciphered|crypted|clover|cobra|codnat3|combo|comrade|conficker|coot|cpt|crash|crimson|cry|crypt\\d{3,4}|crypt38|crypt72|crypt888|cryptinfinite|cryptolocker|cryptowall|cryptxxx|crypz|csfr|csone|ctb[1-4]|ctb-locker|ctrsa|cryptowin|cube|dcrpt|ddtf|dr2|dragon|dried|druid|ducky|ecrpt|eeta|etr|ee|f[0-9]{3,4}|flock|grt|grt[0-9]+|grtlock|gwz|h3ll|hades|hakunamatata|hallucinating|happy|harmful|harrow|havoc|headdesk|helpdecrypt|hermes|hidden|hideous|hijack|hilda|hitler|hjg|hmpl|hrosas|hsdf|hushed|hwrm|ihsdj|ikarus|ikasir|ikayed|ill|imbtc|img|encrypted|improved|indrik|injected|innocent|insane|interesante|jungle|kaos|karl|katana|kimcilware|kin|kiratos|kiss|kjh|locked|locky|lokf|losers|lukitus|m3g4c0rtx|m4n1fest0|m4s4g3|maas|madmax|mafi|magic|maktub|malware|manamecrypt|mandelbrot|manic|matrix|max|md5|medusa|mega|melme|merry|mesmerize|metropolitan|mikey|mikibackup|milarepa.lotos@aol.com|mirror|mmnn|mole|monro|mosk|muslat|n1n1n1|nabr|napoleon|narrow|nasoh|nataniel|neitrino|neras|nlah|nosu|novasof|nozelesn|nuclear|nwa|nymaim|obelisk|off|offwhite|ogdo|omega|omerta|onion|ooss|opencode|openme|opqz|osiris|otx|p3rf0rm4|pabluk|pack14|packagetrackr@india.com|packrat|pahd|panda|pandemic|pandora|pansy|paradise|paris|paym3|paymer|payms|pcap|pclock|peet|pelikan|penis|petya|pewcrypt|phoenix|photominr|phobos|phps|pirated|pluto|po1|point|poop|potato|pr0tect|preppy|princesa|princess|prosper|prosperity|prq|pshy|pumas|pumax|pure|purple|purpler|pwnd|pysa|q9q9|qbtex|qiuu|qkkd|qscx|qtyop|quimera|r2d2|r5a|rabit|radman|raid10|rainbow|rakhni|rambo|ramses|rat|rcrypted|react|reactor|realtek|reaper|redlion|redmat|redrum|rekt|remk|removal|remsec|remy|renaming|revenge|rezuc|rhino|ribd|rich|rip|rire|rizonesoft@protonmail.ch|rk|rmdir|robinhood|rocke|rogue|roldat|rolin|ronzware|rosenquist|rotten|roza|rpcminer|rsalive|rumba|run|rxx|uak|udjvu|unlock92|unlckr|upd|urcp|usam|usbc|v8|vag|vandt|varasto|vault|vauw|vb|ve|vendetta|venom|veracrypt|versiegelt|veton|vhd|vindows|violate|virus|vivin|vk_677|vma|vmx|volcano|vorasto|vorphal|vos|vscrypt|vxl|w4b|wakanda|wannacash|wannacry|wanted|war|wasted|wcry|weapologize|webmafia|weird|weui|whatthefuck|whistler|white|whitenoise|whiterabbit|whorus|why!decryptor|wicked|wildfire|windows10|windows7|windows8|windowsupdate|winlock|wipe|wisconsin|wizard|wlu|woolger|worm|wormfubuki|wow|wpencrypt|wq!decryptor|wrui|wtg|x1881|x3m|xampug|xdata|xencrypt|xfiles|xhelper|xlr|xman|xmd|xmd|xtbl|xtbl|xtr|xtt|xtz|xyz|yakuza|yatron|ybn|year|yellow|yheq|yty|yuke|yxo|yyto|z3|zatrov|zax|zbot|zbt|zbt|zeppelin|zerber|zet|zet|zfj|zfj|zimbra|zip|zix|zlz|zobm|zoh|zorro|zphs)$")', '2026-03-02 13:21:47.135287', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"4663"},{"field":"origin.user","operator":"filter_term","value":"{{.origin.user}}"}],"or":null,"within":"now-5m","count":20}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (885, 'Windows: Possible ransomware attack detected. Ransomware Note Creation.', 3, 3, 2, 'Impact', 'T1486 - Data Encrypted for Impact', 'Ransomware, is a type of malware that prevents users from accessing their system or personal files and requires payment of a ransom in order to gain access to them again. Identifies ransomware attempts. A known ransomware note file has been detected, potentially indicating an active ransomware infection.', '["https://attack.mitre.org/tactics/TA0040/"]', 'equals("log.eventCode", 4663) && regexMatch("log.EventDataFileName", "(README_TO_RESTORE_FILES|INSTRUCTION_TO_GET_FILES_BACK|HOW_TO_DECRYPT_FILES|DECRYPT_INSTRUCTION|RECOVER_INSTRUCTION|RESTORE_FILES|READ_ME_NOW|YOUR_FILES_ARE_ENCRYPTED|IMPORTANT_INSTRUCTIONS|NOTICE|DECRYPT_YOUR_FILES|HOW_TO_RESTORE_FILES|HELP_DECRYPT|RECOVERY_FILE|RECOVER-FILES|INSTRUCTION)\\.(txt|html|php)$")', '2026-03-02 13:21:48.438769', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"{{.log.eventCode}}"},{"field":"log.EventDataFileName","operator":"filter_term","value":"{{.log.EventDataFileName}}"}],"or":null,"within":"now-60s","count":5}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (886, 'Windows: Possible ransomware attack detected. Multiple File Deletion.', 1, 3, 2, 'Impact', 'T1486 - Data Encrypted for Impact', 'Detects potential ransomware activity by monitoring multiple file write/modification events (Event ID 4663) with write access masks in user directories within a short timeframe. Modern ransomware typically encrypts files in-place rather than deleting them, making write access monitoring more effective than deletion monitoring alone.', '["https://attack.mitre.org/tactics/TA0040/"]', e'equals("log.eventCode", 4663) && +oneOf("log.eventDataAccessMask", ["0x2", "0x4", "0x6"]) && +!(regexMatch("log.eventDataProcessName", "(?i).*(trustedinstaller|svchost|wuauclt|msiexec|windows10upgrade|setuphost|tiworker|dism).*")) && +regexMatch("log.eventDataObjectName", "(?i).*\\\\\\\\(users|documents|desktop|downloads|pictures|videos|music)\\\\\\\\.*") && +!(regexMatch("log.eventDataObjectName", "(?i).*(\\\\\\\\windows\\\\\\\\|\\\\\\\\program files|\\\\\\\\programdata\\\\\\\\|\\\\\\\\temp\\\\\\\\|\\\\\\\\appdata\\\\\\\\local\\\\\\\\temp|\\\\\\\\softwaredistribution\\\\\\\\|\\\\\\\\winsxs\\\\\\\\|\\\\\\\\logs\\\\\\\\|\\\\\\\\prefetch\\\\\\\\).*")) && +!(regexMatch("log.eventDataObjectName", "(?i).*\\\\.(tmp|log|etl|dmp|pf|evtx|cache|dat|bak)$")) && +regexMatch("log.eventDataObjectName", "(?i).*\\\\.(doc[x]?|xls[x]?|ppt[x]?|pdf|txt|jpg|jpeg|png|gif|bmp|mp4|avi|mp3|zip|rar|7z)$") +', '2026-03-02 13:21:49.800468', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"4663"},{"field":"origin.user","operator":"filter_term","value":"{{.origin.user}}"}],"or":null,"within":"now-5m","count":50}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (887, 'Windows: Probable Password guessing', 2, 2, 3, 'Credential Access', 'T1110.001 - Brute Force: Password Guessing', 'Adversaries with no prior knowledge of legitimate credentials within the system or environment may guess passwords to attempt access to accounts. Without knowledge of the password for an account, an adversary may opt to systematically guess the password using a repetitive or iterative mechanism. An adversary may guess login credentials without prior knowledge of system or environment passwords during an operation by using a list of common passwords. Password guessing may or may not take into account the target''s policies on password complexity or use policies that may lock accounts out after a number of failed attempts.', '["https://attack.mitre.org/tactics/TA0006","https://attack.mitre.org/techniques/T1110/001/"]', 'oneOf("log.eventCode", [4625, 529, 530, 531, 532, 533, 534, 535, 536, 537, 539])', '2026-03-02 13:21:51.099967', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"{{.log.eventCode}}"},{"field":"target.user","operator":"filter_term","value":"{{.target.user}}"}],"or":null,"within":"now-5m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (888, 'Windows: Multiple TS Gateway login failures', 3, 3, 2, 'Credential Access', 'T1110 - Brute Force', 'Adversaries may use brute force techniques to gain access to accounts when passwords are unknown or when password hashes are obtained.', '["https://attack.mitre.org/techniques/T1110/"]', 'equals("log.eventCode", 1001) && contains("log.message", "Microsoft-Windows-TerminalServices")', '2026-03-02 13:21:52.466461', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-15m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (889, 'Windows: Multiple remote access login failures', 3, 2, 2, 'Credential Access', 'T1110 - Brute Force', 'Adversaries may use brute force techniques to gain access to accounts when passwords are unknown or when password hashes are obtained.', '["https://attack.mitre.org/techniques/T1110/"]', e'oneOf("log.eventCode", [20187, 20014, 20078, 20050, 20049, 2018]) && +regexMatch("log.message", "(?i)(authentication failed|login failed|access denied|authentication error|invalid credentials)") +', '2026-03-02 13:21:53.827075', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"}],"or":null,"within":"now-15m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (890, 'Windows: Multiple failed attempts to perform a privileged operation by the same user', 1, 2, 3, 'Privilege Escalation', 'T1110 - Brute Force', 'Adversaries may use brute force techniques to gain access to accounts when passwords are unknown or when password hashes are obtained.', '["https://attack.mitre.org/techniques/T1110/"]', 'equals("log.eventCode", 577) || equals("log.eventCode", 4673)', '2026-03-02 13:21:54.875323', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"target.user","operator":"filter_term","value":"{{.target.user}}"}],"or":null,"within":"now-10m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (891, 'Windows: Potential Credential Access via Trusted Developer Utility', 2, 2, 3, 'Credential Access', 'T1003 - OS Credential Dumping', 'An instance of MSBuild, the Microsoft Build Engine, loaded DLLs (dynamically linked libraries) responsible for Windows credential management. This technique is sometimes used for credential dumping.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'regexMatch("log.eventDataProcessName", "(MSBuild.exe|msbuild.exe)") && regexMatch("log.message", "(vaultcli.dll|SAMLib.DLL)")', '2026-03-02 13:21:56.049626', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataProcessId","operator":"filter_term","value":"{{.log.eventDataProcessId}}"}],"or":null,"within":"now-1m","count":1}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (892, 'Windows: Multiple Logon Failure Followed by Logon Success', 2, 2, 3, 'Credential Access', 'T1110 - Brute Force', 'This rule is triggered when a sequence of multiple failed login attempts followed immediately by a successful login from the same IP address or source is detected. This unusual sequence of events may indicate a possible unauthorized access attempt using a brute force or password guessing technique. The purpose of this rule is to identify suspicious patterns of login activity and alert you to potential unauthorized access attempts.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1110/"]', 'equals("log.eventCode", 4624)', '2026-03-02 13:21:57.308717', true, true, 'origin', '["origin.ip","target.user"]', '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":4625},{"field":"target.user","operator":"filter_term","value":"{{.target.user}}"}],"or":null,"within":"now-5m","count":10}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (893, 'Windows: Possible Brute Force Attack', 2, 2, 3, 'Credential Access', 'T1110 - Brute Force', 'This rule is triggered when a pattern of repeated and rapid login attempts from the same IP address or source is detected. These login attempts may target specific user accounts or services in an attempt to crack passwords through automated brute force. The purpose of this rule is to identify possible malicious unauthorized access attempts and prevent a brute force attack against the system.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1110/"]', 'equals("log.eventCode", 4625)', '2026-03-02 13:21:58.672154', true, true, 'origin', '["origin.host","target.user"]', '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"{{.log.eventCode}}"},{"field":"target.user","operator":"filter_term","value":"{{.target.user}}"}],"or":null,"within":"now-5m","count":10}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (894, 'Windows: Signed Proxy Execution via MS Work Folders', 1, 2, 3, 'Defense Evasion', 'T1218 - System Binary Proxy Execution', 'Identifies the use of Windows Work Folders to execute a potentially masqueraded control.exe file in the current working directory. Misuse of Windows Work Folders could indicate malicious activity.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1218/"]', 'contains("log.eventDataProcessName", "control.exe") && contains("log.eventDataParentProcessName", "workfolders.exe") && !regexMatch("log.eventDataProcessName", "(:\\Windows\\(System32|SysWOW64)\\control.exe)")', '2026-03-02 13:21:59.853571', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (895, 'Windows: Wireless Credential Dumping using Netsh Command', 3, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies attempts to dump Wireless saved access keys in clear text using the Windows built-in utility Netsh.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'contains("log.message", "wlan") && regexMatch("log.message", "(key(.+)clear)") && contains("log.eventDataProcessName", "netsh.exe")', '2026-03-02 13:22:01.032912', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (896, 'Windows: Unusual Child Process of dns.exe', 1, 3, 2, 'Initial Access', 'T1133 - External Remote Services', 'Identifies an unexpected process spawning from dns.exe, the process responsible for Windows DNS server services, which may indicate activity related to remote code execution or other forms of exploitation.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1133/"]', 'contains("log.eventDataProcessName", "dns.exe") && !contains("log.eventDataParentProcessName", "conhost.exe")', '2026-03-02 13:22:02.298793', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (897, 'Windows: System Shells via Services', 1, 3, 2, 'Persistence', 'T1543.003 - Create or Modify System Process: Windows Service', 'Windows services typically run as SYSTEM and can be used as a privilege escalation opportunity. Malware or penetration testers may run a shell as a service to gain SYSTEM permissions.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1543/003/"]', e'regexMatch("log.eventDataProcessName", "(cmd.exe|powershell.exe|pwsh.exe|powershell_ise.exe)") && + contains("log.eventDataParentProcessName", "services.exe") && + !(regexMatch("log.message", "(NVDisplay.ContainerLocalSystem)")) +', '2026-03-02 13:22:03.482260', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (898, 'Windows: Symbolic Link to Shadow Copy Created', 1, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Detects creation of a symbolic link to a volume shadow copy. Adversaries may use this technique to access and exfiltrate sensitive data such as NTDS.dit or SAM database from shadow copies.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'regexMatch("log.eventDataProcessName", "(cmd.exe|powershell.exe|pwsh.exe|powershell_ise.exe)") && regexMatch("log.message", "(?i)(mklink|New-Item.*SymbolicLink)") && contains("log.message", "HarddiskVolumeShadowCopy")', '2026-03-02 13:22:04.836960', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (899, 'Windows: Suspicious Execution via Scheduled Task', 1, 3, 2, 'Persistence', 'T1053.005 - Scheduled Task', 'Identifies execution of a suspicious program via scheduled tasks by looking at process lineage and command line usage.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1053/005/"]', e'equals("log.eventDataEventType", "start") && contains("log.eventDataProcessParentName", "svchost.exe") && contains("log.eventDataProcessParentArgs", "Schedule") && +regexMatch("log.eventDataOriginalFileName", "(cscript.exe|wscript.exe|PowerShell.EXE|Cmd.Exe|MSHTA.EXE|RUNDLL32.EXE|REGSVR32.EXE|MSBuild.exe|InstallUtil.exe|RegAsm.exe|RegSvcs.exe|msxsl.exe|CONTROL.EXE|EXPLORER.EXE|Microsoft.Workflow.Compiler.exe|msiexec.exe)") && +regexMatch("log.eventDataProcessArgs", "(C:\\\\\\\\Users\\\\\\\\|C:\\\\\\\\ProgramData\\\\\\\\|C:\\\\\\\\Windows\\\\\\\\Temp\\\\\\\\|C:\\\\\\\\Windows\\\\\\\\Tasks\\\\\\\\|C:\\\\\\\\PerfLogs\\\\\\\\|C:\\\\\\\\Intel\\\\\\\\|C:\\\\\\\\Windows\\\\\\\\Debug\\\\\\\\|C:\\\\\\\\HP\\\\\\\\)") && +!regexMatch("log.eventDataProcessName", "(cmd.exe|cscript.exe|powershell.exe|msiexec.exe)") && +!regexMatch("log.eventDataProcessArgs", "(:\\\\\\\\(.+).bat|:\\\\\\\\Windows\\\\\\\\system32\\\\\\\\calluxxprovider.vbs|-File|-PSConsoleFile)") && +!regexMatch("log.eventDataUserId", "(S-1-5-18)") && !regexMatch("log.eventDataWorkingDirectory", "(:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\)") +', '2026-03-02 13:22:05.931261', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (900, 'Windows: Suspicious RDP ActiveX Client Loaded', 1, 3, 2, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies suspicious Image Loading of the Remote Desktop Services ActiveX Client (mstscax), this may indicate the presence of RDP lateral movement capability.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', e'!(regexMatch("log.eventDataProcessName", "(C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\mstsc\\\\.exe|C:\\\\\\\\Windows\\\\\\\\SysWOW64\\\\\\\\mstsc\\\\.exe)")) && +regexMatch("log.eventDataProcessName", "(C:\\\\\\\\Windows\\\\\\\\|C:\\\\\\\\Users\\\\\\\\Public\\\\\\\\|C:\\\\\\\\Users\\\\\\\\Default\\\\\\\\|C:\\\\\\\\Intel\\\\\\\\|C:\\\\\\\\PerfLogs\\\\\\\\|C:\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Device\\\\\\\\Mup\\\\\\\\|\\\\\\\\\\\\\\\\)") && +contains("log.message", "mstscax.dll") +', '2026-03-02 13:22:07.049460', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (901, 'Windows: Suspicious Process Execution via Renamed PsExec Executable', 1, 3, 2, 'Execution', 'T1569 - System Services', 'Identifies suspicious psexec activity which is executing from the psexec service that has been renamed, possibly to vade detection.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1569/"]', 'equals("log.eventDataEventType", "start") && (contains("log.eventDataProcessName", "PSEXESVC.exe") || contains("log.eventDataOriginalFileName", "psexesvc.exe"))', '2026-03-02 13:22:08.108884', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (902, 'Windows: Suspicious Process Access via Direct System Call', 1, 3, 2, 'Defense Evasion', 'T1055 - Process Injection', 'Identifies suspicious process access events from an unknown memory region. Endpoint security solutions usually hook userland Windows APIs in order to decide if the code that is being executed is malicious or not. It''s possible to bypass hooked functions by writing malicious functions that call syscalls directly.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', e'equals("log.eventCode", 10) && !(regexMatch("log.eventDataCallTrace", "(:\\\\WINDOWS\\\\SYSTEM32\\\\ntdll.dll|:\\\\WINDOWS\\\\SysWOW64\\\\ntdll.dll|:\\\\Windows\\\\System32\\\\wow64cpu.dll|:\\\\WINDOWS\\\\System32\\\\wow64win.dll|:\\\\Windows\\\\System32\\\\win32u.dll)")) && +!(regexMatch("log.eventDataTargetImage", "(:\\\\WINDOWS\\\\system32\\\\lsass.exe|:\\\\Program Files (x86)\\\\Malwarebytes Anti-Exploit\\\\mbae-svc.exe|:\\\\Program Files\\\\Cisco\\\\AMP\\\\(.+)\\\\sfc.exe|:\\\\Program Files (x86)\\\\Microsoft\\\\EdgeWebView\\\\Application\\\\(.+)\\\\msedgewebview2.exe|:\\\\Program Files\\\\Adobe\\\\Acrobat DC\\\\Acrobat\\\\(.+)\\\\AcroCEF.exe)")) && +!(regexMatch("log.eventDataProcessName", "(:\\\\Program Files\\\\Adobe\\\\Acrobat DC\\\\Acrobat\\\\Acrobat.exe|:\\\\Program Files (x86)\\\\World of Warcraft\\\\_classic_\\\\WowClassic.exe)")) +', '2026-03-02 13:22:09.241708', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (903, 'Windows: Suspicious PowerShell Engine ImageLoad', 1, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', 'Identifies the PowerShell engine being invoked by unexpected processes. Rather than executing PowerShell functionality with powershell.exe, some attackers do this to operate more stealthily.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1059/"]', e'!(oneOf("log.eventDataProcessName", ["Altaro.SubAgent.exe", "AppV_Manage.exe", "azureadconnect.exe", "CcmExec.exe", "configsyncrun.exe", "choco.exe", "ctxappvservice.exe", "DVLS.Console.exe", "edgetransport.exe", "exsetup.exe", "forefrontactivedirectoryconnector.exe", "InstallUtil.exe", "JenkinsOnDesktop.exe", "Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe", "mmc.exe", "mscorsvw.exe", "msexchangedelivery.exe", "msexchangefrontendtransport.exe", "msexchangehmworker.exe", "msexchangesubmission.exe", "msiexec.exe", "MsiExec.exe", "noderunner.exe", "NServiceBus.Host.exe", "NServiceBus.Host32.exe", "NServiceBus.Hosting.Azure.HostProcess.exe", "OuiGui.WPF.exe", "powershell.exe", "powershell_ise.exe", "pwsh.exe", "SCCMCliCtrWPF.exe", "ScriptEditor.exe", "ScriptRunner.exe", "sdiagnhost.exe", "servermanager.exe", "setup100.exe", "ServiceHub.VSDetouredHost.exe", "SPCAF.Client.exe", "SPCAF.SettingsEditor.exe", "SQLPS.exe", "telemetryservice.exe", "UMWokerProcess.exe", "w3wp.exe", "wsmprovhost.exe"])) && +!(regexMatch("log.eventDataProcessName", "(C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe|C:\\\\Windows\\\\System32\\\\sdiagnhost.exe|C:\\\\Program Files( \\\\(x86\\\\))?\\\\(.+)\\\\.exe)")) && +oneOf("log.message", ["System.Management.Automation.ni.dll", "System.Management.Automation.dll"]) +', '2026-03-02 13:22:10.641905', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (904, 'Windows: Suspicious PDF Reader Child Process', 1, 1, 2, 'Execution', 'T1204 - User Execution', 'Identifies suspicious child processes of PDF reader applications. These child processes are often launched via exploitation of PDF applications or social engineering.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1204/"]', 'oneOf("log.eventDataParentProcessName", ["AcroRd32.exe", "Acrobat.exe", "FoxitPhantomPDF.exe", "FoxitReader.exe"]) && oneOf("log.eventDataProcessName", ["arp.exe", "dsquery.exe", "dsget.exe", "gpresult.exe", "hostname.exe", "ipconfig.exe", "nbtstat.exe", "net.exe", "net1.exe", "netsh.exe", "netstat.exe", "nltest.exe", "ping.exe", "qprocess.exe", "quser.exe", "qwinsta.exe", "reg.exe", "sc.exe", "systeminfo.exe", "tasklist.exe", "tracert.exe", "whoami.exe", "bginfo.exe", "cdb.exe", "cmstp.exe", "csi.exe", "dnx.exe", "fsi.exe", "ieexec.exe", "iexpress.exe", "installutil.exe", "Microsoft.Workflow.Compiler.exe", "msbuild.exe", "mshta.exe", "msxsl.exe", "odbcconf.exe", "rcsi.exe", "regsvr32.exe", "xwizard.exe", "atbroker.exe", "forfiles.exe", "schtasks.exe", "regasm.exe", "regsvcs.exe", "cmd.exe", "cscript.exe", "powershell.exe", "pwsh.exe", "wmic.exe", "wscript.exe", "bitsadmin.exe", "certutil.exe", "ftp.exe"])', '2026-03-02 13:22:11.904183', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (905, 'Windows: Suspicious MS Outlook Child Process', 1, 3, 2, 'Initial Access', 'T1566.001 - Phishing: Spearphishing Attachment', 'Identifies suspicious child processes of Microsoft Outlook. These child processes are often associated with spear phishing activity.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1566/001/"]', 'oneOf("log.eventDataProcessName", ["Microsoft.Workflow.Compiler.exe", "arp.exe", "atbroker.exe", "bginfo.exe", "bitsadmin.exe", "cdb.exe", "certutil.exe", "cmd.exe", "cmstp.exe", "cscript.exe", "csi.exe", "dnx.exe", "dsget.exe", "dsquery.exe", "forfiles.exe", "fsi.exe", "ftp.exe", "gpresult.exe", "hostname.exe", "ieexec.exe", "iexpress.exe", "installutil.exe", "ipconfig.exe", "mshta.exe", "msxsl.exe", "nbtstat.exe", "net.exe", "net1.exe", "netsh.exe", "netstat.exe", "nltest.exe", "odbcconf.exe", "ping.exe", "powershell.exe", "pwsh.exe", "qprocess.exe", "quser.exe", "qwinsta.exe", "rcsi.exe", "reg.exe", "regasm.exe", "regsvcs.exe", "regsvr32.exe", "sc.exe", "schtasks.exe", "systeminfo.exe", "tasklist.exe", "tracert.exe", "whoami.exe", "wmic.exe", "wscript.exe", "xwizard.exe"]) && contains("log.eventDataParentProcessName", "outlook.exe")', '2026-03-02 13:22:13.266068', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (906, 'Windows: Suspicious MS Office Child Process', 1, 2, 3, 'Initial Access', 'T1566.001 - Phishing: Spearphishing Attachment', 'Identifies suspicious child processes of frequently targeted Microsoft Office applications (Word, PowerPoint, Excel). These child processes are often launched during exploitation of Office applications or from documents with malicious macros.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1566/001/"]', 'oneOf("log.eventDataProcessName", ["Microsoft.Workflow.Compiler.exe", "arp.exe", "atbroker.exe", "bginfo.exe", "bitsadmin.exe", "cdb.exe", "certutil.exe", "cmd.exe", "cmstp.exe", "control.exe", "cscript.exe", "csi.exe", "dnx.exe", "dsget.exe", "dsquery.exe", "forfiles.exe", "fsi.exe", "ftp.exe", "gpresult.exe", "hostname.exe", "ieexec.exe", "iexpress.exe", "installutil.exe", "ipconfig.exe", "mshta.exe", "msxsl.exe", "nbtstat.exe", "net.exe", "net1.exe", "netsh.exe", "netstat.exe", "nltest.exe", "odbcconf.exe", "ping.exe", "powershell.exe", "pwsh.exe", "qprocess.exe", "quser.exe", "qwinsta.exe", "rcsi.exe", "reg.exe", "regasm.exe", "regsvcs.exe", "regsvr32.exe", "sc.exe", "schtasks.exe", "systeminfo.exe", "tasklist.exe", "tracert.exe", "whoami.exe", "wmic.exe", "wscript.exe", "xwizard.exe", "explorer.exe", "rundll32.exe", "hh.exe", "msdt.exe"]) && oneOf("log.eventDataParentProcessName", ["eqnedt32.exe", "excel.exe", "fltldr.exe", "msaccess.exe", "mspub.exe", "powerpnt.exe", "winword.exe", "outlook.exe"])', '2026-03-02 13:22:14.575639', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (907, 'Windows: Microsoft Exchange Worker Spawning Suspicious Processes', 2, 3, 2, 'Initial Access', 'T1190 - Exploit Public-Facing Application', 'Identifies suspicious processes being spawned by the Microsoft Exchange Server worker process (w3wp). This activity may indicate exploitation activity or access to an existing web shell backdoor.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1190/"]', 'contains("log.eventDataParentProcessName", "w3wp.exe") && regexMatch("log.message", "(MSExchange(.+)AppPool)") && regexMatch("log.eventDataProcessName", "(md.exe|powershell.exe|pwsh.dll|powershell_ise.exe)")', '2026-03-02 13:22:15.888276', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (908, 'Windows: Microsoft Exchange Worker Spawning Suspicious Processes', 2, 3, 2, 'Initial Access', 'T1190 - Exploit Public-Facing Application', 'Identifies suspicious processes being spawned by the Microsoft Exchange Server worker process (w3wp). This activity may indicate exploitation activity or access to an existing web shell backdoor.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1190/"]', 'regexMatch("log.eventDataParentProcessName", "(UMService.exe|UMWorkerProcess.exe)") && !(regexMatch("log.eventDataProcessName", "(:\\Windows\\System32\\werfault.exe|:\\Windows\\System32\\wermgr.exe|:\\Program Files\\Microsoft\\Exchange Server\\V(.+)\\Bin\\UMWorkerProcess.exe|D:\\Exchange 2016\\Bin\\UMWorkerProcess.exe|E:\\ExchangeServer\\Bin\\UMWorkerProcess.exe)"))', '2026-03-02 13:22:17.113022', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (909, 'Windows: Potential LSASS Memory Dump via PssCaptureSnapShot', 3, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies suspicious access to an LSASS handle via PssCaptureSnapShot where two successive process accesses are performed by the same process and target two different instances of LSASS. This may indicate an attempt to evade detection and dump LSASS memory for credential access.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'equals("log.eventId", "10") && regexMatch("log.eventDataTargetImage", "([Cc]:\\Windows\\[Ss]ystem32\\lsass.exe)")', '2026-03-02 13:22:18.376805', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (910, 'Windows: Potential Credential Access via LSASS Memory Dump', 3, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies suspicious access to LSASS handle from a call trace pointing to DBGHelp.dll or DBGCore.dll, which both export the MiniDumpWriteDump method that can be used to dump LSASS memory content in preparation for credential access.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'equals("log.eventId", "10") && regexMatch("log.eventDataTargetImage", "(C:\\\\WINDOWS\\\\system32\\\\lsass.exe)") && regexMatch("log.eventDataCallTrace", "(dbghelp|dbgcore)") && !regexMatch("log.eventDataProcessName", "(C:\\\\Windows\\\\System32\\\\WerFault.exe|C:\\\\Windows\\\\System32\\\\WerFaultSecure.exe)")', '2026-03-02 13:22:19.775611', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (911, 'Windows: Remote Computer Account DnsHostName Update', 3, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', 'Identifies the remote update to a computer account''s DnsHostName attribute. If the new value set is a valid domain controller DNS hostname and the subject computer name is not a domain controller, then it''s highly likely a preparation step to exploit CVE-2022-26923 in an attempt to elevate privileges from a standard domain user to domain admin privileges.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1068/"]', 'equals("log.action", "logged-in") && regexMatch("actionResult", "success") && !contains("log.userName", "ANONYMOUS LOGON") && !contains("log.eventDataSubjectUserName", "ANONYMOUS LOGON") && startsWith("log.eventDataSubjectUserName", "$") && !contains("log.userDomain", "NT AUTHORITY")', '2026-03-02 13:22:21.133002', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (912, 'Windows: Service Control Spawned via Script Interpreter', 2, 3, 2, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies Service Control (sc.exe) spawning from script interpreter processes to create, modify, or start services. This could be indicative of adversary lateral movement but will be noisy if commonly done by admins.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', 'oneOf("log.eventDataParentProcessName", ["cmd.exe", "wscript.exe", "rundll32.exe", "regsvr32.exe", "wmic.exe", "mshta.exe", "powershell.exe", "pwsh.exe"]) && oneOf("log.message", ["config", "create", "start", "delete", "stop", "pause"]) && !equals("log.eventDataSubjectUserName", "S-1-5-18") && contains("log.eventDataProcessName", "sc.exe")', '2026-03-02 13:22:22.441377', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (913, 'Windows: Sensitive Privilege SeEnableDelegationPrivilege assigned to a User', 3, 3, 1, 'Credential Access', 'T1212 - Exploitation for Credential Access', 'Identifies the assignment of the SeEnableDelegationPrivilege sensitive user right to a user. The SeEnableDelegationPrivilege user right enables computer and user accounts to be trusted for delegation. Attackers can abuse this right to compromise Active Directory accounts and elevate their privileges.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1212/"]', 'regexMatch("action", "([Aa]uthorization [Pp]olicy [Cc]hange)") && equals("log.eventCode", 4704) && contains("log.eventDataPrivilegeList", "SeEnableDelegationPrivilege")', '2026-03-02 13:22:23.793649', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (914, 'Windows: Security Software Discovery using WMIC', 3, 2, 1, 'Discovery', 'T1518.001 - Software Discovery: Security Software Discovery', 'Identifies the use of Windows Management Instrumentation Command (WMIC) to discover certain System Security Settings such as AntiVirus or Host Firewall details.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1518/001/"]', 'regexMatch("log.message", "(namespace:\\\\root\\SecurityCenter2)") && contains("log.message", "Get") && contains("log.eventDataProcessName", "wmic.exe")', '2026-03-02 13:22:25.092283', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (915, 'Windows: Potential Secure File Deletion via SDelete Utility', 3, 2, 2, 'Defense Evasion', 'T1070.004 - Indicator Removal: File Deletion', 'Detects file name patterns generated by the use of Sysinternals SDelete utility to securely delete a file via multiple file overwrite and rename operations.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1070/004/"]', 'equals("log.eventDataEventType", "change") && contains("log.eventDataFileName", "AAA.AAA")', '2026-03-02 13:22:26.331342', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (916, 'Windows Script Interpreter Executing Process via WMI', 2, 2, 2, 'Initial Access', 'T1566.001 - Phishing: Spearphishing Attachment', 'Identifies use of the built-in Windows script interpreters (cscript.exe or wscript.exe) being used to execute a process via Windows Management Instrumentation (WMI). This may be indicative of malicious activity.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1566/001/"]', 'oneOf("log.processName", ["wscript.exe", "cscript.exe"]) && contains("log.message", "wmiutils.dll")', '2026-03-02 13:22:27.599095', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (917, 'Windows Script Executing PowerShell', 2, 3, 2, 'Initial Access', 'T1566.001 - Phishing: Spearphishing Attachment', 'Identifies a PowerShell process launched by either cscript.exe or wscript.exe. Observing Windows scripting processes executing a PowerShell script, may be indicative of malicious activity.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1566/001/"]', 'equals("log.eventDataEventType", "start") && oneOf("log.eventDataParentProcessName", ["cscript.exe", "wscript.exe"]) && contains("log.eventDataProcessName", "powershell.exe")', '2026-03-02 13:22:28.773925', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (918, 'Windows: Remote Scheduled Task Creation', 2, 2, 2, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies remote scheduled task creations on a target host. This could be indicative of adversary lateral movement.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', 'contains("log.processName", "svchost.exe") && oneOf("log.eventDataSourceNetworkAddress", ["incoming", "ingress"]) && greaterOrEqual("origin.port", 49152) && greaterOrEqual("target.port", 49152) && !oneOf("origin.ip", ["127.0.0.1", "::1"])', '2026-03-02 13:22:30.128739', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (919, 'Windows: Outbound Scheduled Task Activity via PowerShell', 2, 3, 2, 'Execution', 'T1053.005 - Scheduled Task', 'Identifies the PowerShell process loading the Task Scheduler COM DLL followed by an outbound RPC network connection within a short time period. This may indicate lateral movement or remote discovery via scheduled tasks.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1053/005/"]', 'oneOf("log.eventDataProcessName", ["powershell.exe", "pwsh.exe", "powershell_ise.exe"]) && regexMatch("log.message", "(powershell.exe|pwsh.exe|powershell_ise.exe)")', '2026-03-02 13:22:31.388488', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (920, 'Windows: Searching for Saved Credentials via VaultCmd', 3, 2, 1, 'Credential Access', 'T1555 - Credentials from Password Stores', 'Windows Credential Manager allows you to create, view, or delete saved credentials for signing into websites, connected applications, and networks. An adversary may abuse this to list or dump credentials stored in the Credential Manager for saved usernames and passwords. This may also be performed in preparation of lateral movement.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1555/"]', 'contains("log.eventDataProcessName", "vaultcmd.exe") && contains("log.message", "/list")', '2026-03-02 13:22:32.623128', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (921, 'Windows: Potential Privileged Escalation via SamAccountName Spoofing', 2, 3, 1, 'Privilege Escalation', 'T1078 - Valid Accounts', 'Identifies a suspicious computer account name rename event, which may indicate an attempt to exploit CVE-2021-42278 to elevate privileges from a standard domain user to a user with domain admin privileges. CVE-2021-42278 is a security vulnerability that allows potential attackers to impersonate a domain controller via samAccountName attribute spoofing.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1078/"]', 'equals("action", "renamed-user-account") && endsWith("target.user", "$") && !endsWith("log.eventDataNewTargetUserName", "$")', '2026-03-02 13:22:33.799924', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (922, 'Windows: Execution of Persistent Suspicious Program', 2, 3, 2, 'Persistence', 'T1547 - Boot or Logon Autostart Execution', 'Identifies execution of suspicious persistent programs (scripts, rundll32, etc.) by looking at process lineage and command line usage', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1547/"]', 'contains("log.eventDataProcessName", "explorer.exe")', '2026-03-02 13:22:35.066139', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (923, 'Windows: Unusual Child Processes of RunDLL32', 2, 3, 2, 'Defense Evasion', 'T1218.011 - System Binary Proxy Execution: Rundll32', 'Identifies child processes of unusual instances of RunDLL32 where the command line parameters were suspicious. Misuse of RunDLL32 could indicate malicious activity', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1218/011/"]', 'contains("log.eventDataProcessName", "rundll32.exe")', '2026-03-02 13:22:36.151604', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (924, 'Windows: Remote System Discovery Commands', 3, 1, 2, 'Discovery', 'T1018 - Remote System Discovery', 'Discovery of remote system information using built-in commands, which may be used to move laterally.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1018/"]', 'regexMatch("log.message", "(-n|-s|-a)") && regexMatch("log.eventDataProcessName", "(nbtstat.exe|arp.exe)")', '2026-03-02 13:22:37.340444', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (925, 'Windows: Remote File Download via MpCmdRun', 2, 3, 1, 'Command and Control', 'T1105 - Ingress Tool Transfer', 'Identifies the Windows Defender configuration utility (MpCmdRun.exe) being used to download a remote file.', '["https://attack.mitre.org/tactics/TA0011/","https://attack.mitre.org/techniques/T1105/"]', 'contains("log.eventDataProcessName", "MpCmdRun.exe") && contains("log.message", "-url") && contains("log.message", "-DownloadFile") && contains("log.message", "-path")', '2026-03-02 13:22:38.562990', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (926, 'Windows: Remote File Copy to a Hidden Share', 3, 2, 1, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies a remote file copy attempt to a hidden network share. This may indicate lateral movement or data staging activity.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', 'oneOf("log.eventDataProcessName", ["cmd.exe", "powershell.exe", "robocopy.exe", "xcopy.exe"]) && contains("log.message", "$") && (contains("log.message", "copy") || contains("log.message", "move") || contains("log.message", " cp ") || contains("log.message", " mv "))', '2026-03-02 13:22:39.745107', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (927, 'Windows: Remote File Download via Desktopimgdownldr Utility', 2, 3, 1, 'Command and Control', 'T1105 - Ingress Tool Transfer', 'Identifies the desktopimgdownldr utility being used to download a remote file. An adversary may use desktopimgdownldr to download arbitrary files as an alternative to certutil.', '["https://attack.mitre.org/tactics/TA0011/","https://attack.mitre.org/techniques/T1105/"]', 'contains("log.eventDataProcessName", "desktopimgdownldr.exe")', '2026-03-02 13:22:41.055446', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (928, 'Windows: Suspicious Microsoft Diagnostics Wizard Execution with args IT_RebrowseForFile=, ms-msdt:/id, ms-msdt:-id or FromBase64', 2, 3, 1, 'Defense Evasion', 'T1218 - System Binary Proxy Execution', 'Identifies potential abuse of the Microsoft Diagnostics Troubleshooting Wizard (MSDT) to proxy malicious command or binary execution via malicious process arguments', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1218/"]', 'contains("log.eventDataProcessName", "msdt.exe") && regexMatch("log.message", "(IT_RebrowseForFile=|ms-msdt:/id|ms-msdt:-id|FromBase64)")', '2026-03-02 13:22:42.316588', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (929, 'Windows: Privilege Escalation via Rogue Named Pipe Impersonation', 1, 3, 2, 'Privilege Escalation', 'T1134 - Access Token Manipulation', 'Identifies a privilege escalation attempt via rogue named pipe impersonation. An adversary may abuse this technique by masquerading as a known named pipe and manipulating a privileged process to connect to it.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1134/"]', 'regexMatch("log.eventDataProcessName", "(\\(.+)\\Pipe\\)") && contains("log.action", "Pipe Created")', '2026-03-02 13:22:43.676556', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (930, 'Windows: Potential Modification of Accessibility Binaries', 2, 3, 1, 'Persistence', 'T1546.008 - Event Triggered Execution: Accessibility Features', 'Windows contains accessibility features that may be launched with a key combination before a user has logged in. An adversary can modify the way these programs are launched to get a command prompt or backdoor without logging in to the system.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1546/008/"]', e'!regexMatch("log.eventDataProcessName", "(osk.exe|sethc.exe|utilman2.exe|DisplaySwitch.exe|ATBroker.exe|ScreenMagnifier.exe|SR.exe|Narrator.exe|magnify.exe|MAGNIFY.EXE)") && + regexMatch("log.eventDataParentProcessName", "(Utilman.exe|on.exe)") && + contains("log.eventDataSubjectUserName", "SYSTEM") && + regexMatch("log.message", "(C:\\\\Windows\\\\System32\\\\osk.exe|C:\\\\Windows\\\\System32\\\\Magnify.exe|C:\\\\Windows\\\\System32\\\\Narrator.exe|C:\\\\Windows\\\\System32\\\\Sethc.exe|utilman.exe|ATBroker.exe|DisplaySwitch.exe|sethc.exe)") +', '2026-03-02 13:22:44.854436', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (931, 'Windows: Suspicious Print Spooler SPL File Created', 1, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', 'Detects attempts to exploit privilege escalation vulnerabilities related to the Print Spooler service including CVE-2020-1048 and CVE-2020-1337.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1068/"]', e'!regexMatch("log.eventDataProcessName", "(spoolsv.exe|printfilterpipelinesvc.exe|PrintIsolationHost.exe|splwow64.exe|msiexec.exe|poqexec.exe)") && regexMatch("log.eventDataProcessName", "(:\\\\Windows\\\\System32\\\\spool\\\\PRINTERS\\\\)") +', '2026-03-02 13:22:46.156806', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (932, 'Windows: Suspicious PrintSpooler Service Executable File Creation', 2, 3, 1, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', 'Detects attempts to exploit privilege escalation vulnerabilities related to the Print Spooler service. For more information refer to the following CVE''s - CVE-2020-1048, CVE-2020-1337 and CVE-2020-1300 and verify that the impacted system is patched', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1068/"]', e'!regexMatch("log.file.path", "(\\\\Windows\\\\System32\\\\spool\\\\|:\\\\Windows\\\\Temp\\\\|:\\\\Users\\\\)") && contains("log.eventDataProcessName", "spoolsv.exe") +', '2026-03-02 13:22:47.519189', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (933, 'Windows Firewall Disabled via PowerShell', 1, 2, 3, 'Defense Evasion', 'T1562.004 - Impair Defenses: Disable or Modify System Firewall', 'Identifies when the Windows Firewall is disabled using PowerShell cmdlets, which can help attackers evade network constraints, like internet and network lateral communication restrictions.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/004/"]', 'regexMatch("log.message", "(-All|Public|Domain|Private)") && contains("log.message", "False") && contains("log.message", "-Enabled") && contains("log.message", "Set-NetFirewallProfile") && regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)")', '2026-03-02 13:22:48.749324', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (934, 'Windows: User logged using Remote Desktop Connection from loopback address, possible exploit over reverse tunneling using stolen credentials', 3, 2, 1, 'Credential Access', 'T1021.001 - Remote Services: Remote Desktop Protocol', 'Adversaries may use Valid Accounts to log into a computer using the Remote Desktop Protocol (RDP). The adversary may then perform actions as the logged-on user.', '["https://attack.mitre.org/techniques/T1021/001/"]', 'equals("log.eventDataLogonType", "10") && oneOf("log.origin.ips", ["::1", "127.0.0.1"]) && oneOf("log.eventCode", [528, 540, 673, 4624, 4769])', '2026-03-02 13:22:50.137480', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (935, 'Windows: Persistence via WMI Event Subscription', 2, 3, 2, 'Persistence', 'T1546.003 - Event Triggered Execution: WMI Event Subscription', 'An adversary can use Windows Management Instrumentation (WMI) to install event filters, providers, consumers, and bindings that execute code when a defined event occurs. Adversaries may use the capabilities of WMI to subscribe to an event and execute arbitrary code when that event occurs, providing persistence on a system.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1546/003/"]', 'contains("log.message", "create") && regexMatch("log.message", "(ActiveScriptEventConsumer|CommandLineEventConsumer)") && contains("log.eventDataProcessName", "wmic.exe")', '2026-03-02 13:22:51.268994', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (936, 'Windows: Persistence via Update Orchestrator Service Hijack', 2, 3, 2, 'Persistence', 'T1543.003 - Create or Modify System Process: Windows Service', 'Identifies potential hijacking of the Microsoft Update Orchestrator Service to establish persistence with an integrity level of SYSTEM.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1543/003/"]', e'contains("log.message", "UsoSvc") && + regexMatch("log.eventDataParentProcessName", "C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\svchost\\\\.exe") && + !regexMatch("log.eventDataProcessName", "MoUsoCoreWorker\\\\.exe|OfficeC2RClient\\\\.exe") && + !regexMatch("log.eventDataProcessName", "ProgramData\\\\\\\\Microsoft\\\\\\\\Windows\\\\\\\\UUS\\\\\\\\Packages\\\\\\\\.*\\\\\\\\amd64\\\\\\\\MoUsoCoreWorker\\\\.exe|Windows\\\\\\\\System32\\\\\\\\UsoClient\\\\.exe|Windows\\\\\\\\System32\\\\\\\\MusNotification\\\\.exe|Windows\\\\\\\\System32\\\\\\\\MusNotificationUx\\\\.exe|Windows\\\\\\\\System32\\\\\\\\MusNotifyIcon\\\\.exe|Windows\\\\\\\\System32\\\\\\\\WerFault\\\\.exe|Windows\\\\\\\\System32\\\\\\\\WerMgr\\\\.exe|Windows\\\\\\\\UUS\\\\\\\\amd64\\\\\\\\MoUsoCoreWorker\\\\.exe|Windows\\\\\\\\System32\\\\\\\\MoUsoCoreWorker\\\\.exe|Windows\\\\\\\\UUS\\\\\\\\amd64\\\\\\\\UsoCoreWorker\\\\.exe|Windows\\\\\\\\System32\\\\\\\\UsoCoreWorker\\\\.exe|Program Files\\\\\\\\Common Files\\\\\\\\microsoft shared\\\\\\\\ClickToRun\\\\\\\\OfficeC2RClient\\\\.exe") +', '2026-03-02 13:22:52.636684', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (937, 'Windows: Persistence via TelemetryController Scheduled Task Hijack', 2, 3, 2, 'Persistence', 'T1053.005 - Scheduled Task', 'Detects the successful hijack of Microsoft Compatibility Appraiser scheduled task to establish persistence with an integrity level of system.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1053/005/"]', 'contains("log.message", "-cv") && contains("log.eventDataParentProcessName", "CompatTelRunner.exe") && !(regexMatch("log.eventDataProcessName", "(conhost.exe|DeviceCensus.exe|CompatTelRunner.exe|DismHost.exe|rundll32.exe|powershell.exe)"))', '2026-03-02 13:22:53.941140', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (938, 'Windows: Persistence via BITS Job Notify Cmdline', 2, 3, 2, 'Persistence', 'T1197 - BITS Jobs', 'An adversary can use the Background Intelligent Transfer Service (BITS) SetNotifyCmdLine method to execute a program that runs after a job finishes transferring data or after a job enters a specified state in order to persist on a system.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1197/"]', 'contains("log.message", "BITS") && contains("log.eventDataParentProcessName", "svchost.exe") && !(regexMatch("log.eventDataProcessName", "(:\\Windows\\System32\\WerFaultSecure.exe|:\\Windows\\System32\\WerFault.exe|:\\Windows\\System32\\wermgr.exe|:\\WINDOWS\\system32\\directxdatabaseupdater.exe)"))', '2026-03-02 13:22:55.295776', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (939, 'Windows: Privilege Escalation via Named Pipe Impersonation', 3, 3, 2, 'Privilege Escalation', 'T1134 - Access Token Manipulation', 'Identifies a privilege escalation attempt via named pipe impersonation. An adversary may abuse this technique by utilizing a framework such Metasploit''s meterpreter getsystem command.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1134/"]', 'regexMatch("log.eventDataProcessName", "(Cmd.Exe|PowerShell.EXE|powershell.exe|cmd.exe)") && contains("log.message", ">") && regexMatch("log.message", "(\\\\.\\pipe\\)")', '2026-03-02 13:22:56.549601', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (940, 'Windows: Mounting Hidden or WebDav Remote Shares', 3, 2, 1, 'Lateral Movement', 'T1021.002 - Remote Services: SMB/Windows Admin Shares', 'Identifies the use of net.exe to mount a WebDav or hidden remote share. This may indicate lateral movement or preparation for data exfiltration.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/002/"]', 'regexMatch("log.eventDataProcessName", "(net.exe|net1.exe)") && regexMatch("log.message", "(\\\\(.+)\\(.+)$|\\\\(.+)@SSL\\|http)") && contains("log.message", "/d") && contains("log.message", "use") && !(contains("log.eventDataParentProcessName", "net.exe"))', '2026-03-02 13:22:57.640797', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (941, 'Windows: Modification of Boot Configuration', 1, 3, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies use of bcdedit.exe to delete boot configuration data. This tactic is sometimes used as by malware or an attacker as a destructive technique.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'contains("log.eventDataProcessName", "bcdedit.exe") && regexMatch("log.message", "((ignoreallfailures(.+)bootstatuspolicy(.+)/set)|(ignoreallfailures(.+)/set(.+)bootstatuspolicy)|(/set(.+)bootstatuspolicy(.+)ignoreallfailures)|(/set(.+)ignoreallfailures(.+)bootstatuspolicy)|(bootstatuspolicy(.+)set(.+)ignoreallfailures)|(bootstatuspolicy(.+)ignoreallfailures(.+)/set)|(no(.+)recoveryenabled)|(recoveryenabled(.+)no))")', '2026-03-02 13:22:58.966800', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (942, 'Windows: Microsoft security essentials - Virus detected', 3, 3, 2, 'Privilege Escalation', 'T1055 - Process Injection', 'Detect the presence of a virus or malware on the system using Microsoft Security Essentials. The rule correlates different threat detection events, represented by various Event IDs, to identify virus detection on the system.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1055/"]', 'oneOf("log.eventCode", [1107, 1117, 1116, 1118, 1119]) && equals("log.providerName", "Microsoft Antimalware")', '2026-03-02 13:23:00.266586', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (943, 'Windows: LSASS Memory Dump Handle Access', 2, 3, 3, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', 'Identifies handle requests for the Local Security Authority Subsystem Service (LSASS) object access with specific access masks that many tools with a capability to dump memory to disk use (0x1fffff, 0x1010, 0x120089). This rule is tool agnostic as it has been validated against a host of various LSASS dump tools such as SharpDump, Procdump, Mimikatz, Comsvcs etc. It detects this behavior at a low level and does not depend on a specific tool or dump file name.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/","https://attack.mitre.org/techniques/T1003/001/"]', 'equals("log.eventCode", 4656) && regexMatch("log.eventDataObjectName", "(:\\Windows\\System32\\lsass.exe|\\Device\\HarddiskVolume[A-Za-z?:\\]([A-Za-z?])?\\Windows\\System32\\lsass.exe)") && !regexMatch("log.eventDataProcessName", "(:\\Program Files\\(.+).exe|:\\Program Files (x86)\\(.+).exe|:\\Windows\\system32\\wbem\\WmiPrvSE.exe|:\\Windows\\System32\\dllhost.exe|:\\Windows\\System32\\svchost.exe|:\\Windows\\System32\\msiexec.exe|:\\ProgramData\\Microsoft\\Windows Defender\\(.+).exe|:\\Windows\\explorer.exe)") && oneOf("log.eventDataAccessMask", ["0x1fffff", "0x1010", "0x120089", "0x1F3FFF"])', '2026-03-02 13:23:01.542431', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (944, 'Windows: IIS HTTP Logging Disabled', 3, 2, 3, 'Defense Evasion', 'T1562.002 - Impair Defenses: Disable Windows Event Logging', 'Identifies when Internet Information Services (IIS) HTTP Logging is disabled on a server. An attacker with IIS server access via a webshell or other mechanism can disable HTTP Logging as an effective anti-forensics measure.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/002/"]', 'contains("log.eventDataProcessName", "appcmd.exe") && regexMatch("log.message", "(/dontLog(.+):(.+)True)") && !(contains("log.eventDataParentProcessName", "iissetup.exe"))', '2026-03-02 13:23:02.680765', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (945, 'Windows: Microsoft IIS Connection Strings Decryption', 2, 3, 1, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies use of aspnet_regiis to decrypt Microsoft IIS connection strings. An attacker with Microsoft IIS web server access via a webshell or alike can decrypt and dump any hardcoded connection strings, such as the MSSQL service account password using aspnet_regiis command.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'regexMatch("log.eventDataProcessName", "aspnet_regiis.exe") && regexMatch("log.message", "(connectionStrings)") && regexMatch("log.message", "(-pdf)")', '2026-03-02 13:23:03.856518', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (946, 'Windows: Microsoft IIS Service Account Password Dumped', 3, 2, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies the Internet Information Services (IIS) command-line tool, AppCmd, being used to list passwords. An attacker with IIS web server access via a web shell can decrypt and dump the IIS AppPool service account password using AppCmd.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'regexMatch("log.eventDataProcessName", "appcmd.exe") && regexMatch("log.message", "(/list)") && regexMatch("log.message", "(/text(.+)password)")', '2026-03-02 13:23:05.170767', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (947, 'Windows: Possible sideloading of DLL via Microsoft antimalware service executable with MsMpEng process', 3, 3, 3, 'Defense Evasion', 'T1574 - Hijack Execution Flow', 'Identifies a Windows trusted program that is known to be vulnerable to DLL Search Order Hijacking starting after being renamed or from a non-standard path. This is uncommon behavior and may indicate an attempt to evade defenses via side-loading a malicious DLL within the memory space of one of those processes.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1574/"]', 'contains("log.eventDataProcessName", "MsMpEng.exe") && !regexMatch("log.eventDataProcessPath", "(:\\ProgramData\\Microsoft\\Windows Defender\\(.+).exe|:\\Program Files\\Windows Defender\\(.+).exe|:\\Program Files (x86)\\Windows Defender\\(.+).exe|:\\Program Files\\Microsoft Security Client\\(.+).exe|:\\Program Files (x86)\\Microsoft Security Client\\(.+).exe)")', '2026-03-02 13:23:06.299379', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (948, 'Windows: Execution via MSSQL xp_cmdshell Stored Procedure', 3, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', 'Identifies execution via MSSQL xp_cmdshell stored procedure. Malicious users may attempt to elevate their privileges by using xp_cmdshell, which is disabled by default, thus, it''s important to review the context of it''s use.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1059/"]', 'oneOf("log.message", ["diskfree", "rmdir", "mkdir", "dir", "del", "rename", "bcp", "XMLNAMESPACES"]) && contains("log.eventDataProcessName", "cmd.exe") && contains("log.eventDataParentProcessName", "sqlservr.exe")', '2026-03-02 13:23:07.566968', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (949, 'Windows: Process Activity via Compiled HTML File', 3, 3, 1, 'Execution', 'T1204.002 - User Execution: Malicious File', 'Compiled HTML files (.chm) are commonly distributed as part of the Microsoft HTML Help system. Adversaries may conceal malicious code in a CHM file and deliver it to a victim for execution. CHM content is loaded by the HTML Help executable program (hh.exe).', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1204/002/"]', 'regexMatch("log.eventDataProcessName", "(mshta.exe|cmd.exe|powershell.exe|pwsh.exe|powershell_ise.exe|cscript.exe|wscript.exe)") && regexMatch("log.eventDataParentProcessName", "hh.exe")', '2026-03-02 13:23:08.842441', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (950, 'Windows: Microsoft Build Engine Started an Unusual Process', 3, 3, 2, 'Defense Evasion', 'T1027 - Obfuscated Files or Information', 'An instance of MSBuild, the Microsoft Build Engine, started a PowerShell script or the Visual C# Command Line Compiler. This technique is sometimes used to deploy a malicious payload using the Build Engine.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1027/"]', 'regexMatch("log.eventDataProcessName", "(csc.exe|iexplore.exe|powershell.exe)") && regexMatch("log.eventDataParentProcessName", "MSBuild.exe")', '2026-03-02 13:23:10.028196', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (951, 'Windows: Microsoft Build Engine Started by a System Process', 3, 3, 2, 'Defense Evasion', 'T1127.001 - Trusted Developer Utilities Proxy Execution: MSBuild', 'An instance of MSBuild, the Microsoft Build Engine, was started by Explorer or the WMI (Windows Management Instrumentation) subsystem. This behavior is unusual and is sometimes used by malicious payloads.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1127/001/"]', 'regexMatch("log.eventDataProcessName", "MSBuild.exe") && regexMatch("log.eventDataParentProcessName", "(explorer.exe|wmiprvse.exe)")', '2026-03-02 13:23:11.157181', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (952, 'Windows: Microsoft Build Engine Started by a Script Process', 3, 3, 2, 'Defense Evasion', 'T1127.001 - Trusted Developer Utilities Proxy Execution: MSBuild', 'An instance of MSBuild, the Microsoft Build Engine, was started by a script or the Windows command interpreter. This behavior is unusual and is sometimes used by malicious payloads.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1127/001/"]', 'regexMatch("log.eventDataProcessName", "MSBuild.exe") && regexMatch("log.eventDataParentProcessName", "(cmd.exe|powershell.exe|wscript.exe|cscript.exe)")', '2026-03-02 13:23:12.468332', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (953, 'Windows: Microsoft Build Engine Started by an Office Application', 3, 3, 1, 'Defense Evasion', 'T1127.001 - Trusted Developer Utilities Proxy Execution: MSBuild', 'An instance of MSBuild, the Microsoft Build Engine, was started by Excel or Word. This is unusual behavior for the Build Engine and could have been caused by an Excel or Word document executing a malicious script payload.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1127/001/"]', 'regexMatch("log.eventDataProcessName", "MSBuild.exe") && regexMatch("log.eventDataParentProcessName", "(eqnedt32.exe|excel.exe|fltldr.exe|msaccess.exe|mspub.exe|outlook.exe|powerpnt.exe|winword.exe)")', '2026-03-02 13:23:13.606773', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (954, 'Windows: Remote Desktop Enabled in Windows Firewall by Netsh', 3, 3, 1, 'Defense Evasion', 'T1562.004 - Impair Defenses: Disable or Modify System Firewall', 'Identifies use of the network shell utility (netsh.exe) to enable inbound Remote Desktop Protocol (RDP) connections in the Windows Firewall.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/004/"]', 'regexMatch("log.eventDataProcessName", "netsh.exe") && regexMatch("log.message", "(action=allow|enable=Yes|enable)") && regexMatch("log.message", "(localport=3389|RemoteDesktop|group=(.+)remote desktop(.+))")', '2026-03-02 13:23:14.870837', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (955, 'Windows: Detection of Exchange mail exported through PowerShell', 3, 1, 1, 'Collection', 'T1005 - Data from Local System', 'This rule identifies the use of an Exchange PowerShell cmdlet, which is used to export the contents of a core file. mailbox or archive to a .pst file. Adversaries can target user email to collect sensitive information.', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1005/","https://attack.mitre.org/techniques/T1114/","https://attack.mitre.org/techniques/T1114/002/"]', 'regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)") && regexMatch("log.message", "(New-MailboxExportRequest)")', '2026-03-02 13:23:16.225231', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (956, 'Windows: Credential Acquisition via Registry Hive Dumping', 3, 1, 1, 'Credential Access', 'T1003.002 - OS Credential Dumping: Security Account Manager', 'Identifies attempts to export a registry hive which may contain credentials using the Windows reg.exe tool.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/","https://attack.mitre.org/techniques/T1003/002/"]', 'regexMatch("log.eventDataProcessName", "reg.exe") && regexMatch("log.message", "(save|export)") && regexMatch("log.message", "(hklm\\sam|hklm\\security)")', '2026-03-02 13:23:17.528615', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (957, 'Windows: Disabling Windows Defender Security Settings via PowerShell', 3, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', 'Identifies use of the Set-MpPreference PowerShell command to disable or weaken certain Windows Defender settings.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/001/"]', 'regexMatch("log.message", "(Set-MpPreference)") && regexMatch("log.message", "(-Disable|Disabled|NeverSend|-Exclusion)") && regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.dll|powershell_ise.exe)")', '2026-03-02 13:23:18.845833', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (958, 'Windows: Disable Windows Firewall Rules via Netsh', 2, 2, 3, 'Defense Evasion', 'T1562.004 - Impair Defenses: Disable or Modify System Firewall', 'Identifies use of netsh.exe to disable Windows Firewall rules or turn off the firewall entirely. Adversaries may disable the Windows Firewall to enable network connections for lateral movement or command and control.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/004/"]', 'regexMatch("log.message", "(disable(.+)firewall(.+)set|disable(.+)set(.+)firewall|firewall(.+)disable(.+)set|firewall(.+)set(.+)disable|set(.+)disable(.+)firewall|set(.+)firewall(.+)disable|state(.+)advfirewall(.+)off|state(.+)off(.+)advfirewall|advfirewall(.+)state(.+)off|advfirewall(.+)off(.+)state|off(.+)state(.+)advfirewall|off(.+)advfirewall(.+)state)") && regexMatch("log.eventDataProcessName", "netsh.exe")', '2026-03-02 13:23:20.152852', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (959, 'Windows: PowerShell Keylogging Script', 3, 2, 2, 'Collection', 'T1056.001 - Input Capture: Keylogging', 'Detects the use of Win32 API Functions that can be used to capture user keystrokes in PowerShell scripts. Attackers use this technique to capture user input, looking for credentials and/or other valuable data.', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1056/","https://attack.mitre.org/techniques/T1056/001/"]', 'regexMatch("log.message", "(GetAsyncKeyState|NtUserGetAsyncKeyState|GetKeyboardState|Get-Keystrokes|SetWindowsHookA|SetWindowsHookW|SetWindowsHookEx|SetWindowsHookExA|NtUserSetWindowsHookEx|GetForegroundWindow|GetWindowTextA|GetWindowTextW|WM_KEYBOARD_LL)") && regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)")', '2026-03-02 13:23:21.466419', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (960, 'Windows: Deleting Backup Catalogs with Wbadmin', 1, 2, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies use of the wbadmin.exe to delete the backup catalog. Ransomware and other malware may do this to prevent system recovery.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'regexMatch("log.message", "(delete(.+)catalog|catalog(.+)delete)") && regexMatch("log.eventDataProcessName", "(wbadmin.exe|WBADMIN.EXE)")', '2026-03-02 13:23:22.729718', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (961, 'Windows: Delete Volume USN Journal with Fsutil', 1, 2, 3, 'Defense Evasion', 'T1070.004 - Indicator Removal: File Deletion', 'Identifies use of the fsutil.exe to delete the volume USNJRNL. This technique is used by attackers to eliminate evidence of files created during post-exploitation activities.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1070/004/"]', 'regexMatch("log.message", "(deletejournal(.+)usn|usn(.+)deletejournal)") && regexMatch("log.eventDataProcessName", "fsutil.exe")', '2026-03-02 13:23:24.045793', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (962, 'Windows Defender Exclusions Added via PowerShell', 2, 2, 3, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies modifications to the Windows Defender configuration settings using PowerShell to add exclusions at the folder directory or process level.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/"]', 'regexMatch("log.message", "(-Exclusion(.+)(Add-MpPreference|Set-MpPreference)|(Add-MpPreference|Set-MpPreference)(.+)-Exclusion)") && regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)")', '2026-03-02 13:23:25.350856', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (963, 'Windows: NTDS or SAM Database File Copied', 3, 3, 3, 'Credential Access', 'T1003.002 - OS Credential Dumping: Security Account Manager', 'Identifies a copy operation of the Active Directory Domain Database or Security Account Manager (SAM) files. Those files contain sensitive information including hashed domain and local credentials.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/","https://attack.mitre.org/techniques/T1003/002/"]', 'regexMatch("log.eventDataProcessName", "(?i)(cmd\\.exe|powershell\\.exe|xcopy\\.exe|esentutl\\.exe)") && regexMatch("log.message", "(copy|xcopy|Copy-Item|move|cp|mv|/y|/vss|/d)") && regexMatch("log.message", "(\\ntds.dit|\\config\\SAM|\\(.+)\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy(.+)\\|/system32/config/SAM)")', '2026-03-02 13:23:26.709174', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (964, 'Clearing Windows Event Logs with wevtutil', 1, 2, 3, 'Defense Evasion', 'T1070.001 - Indicator Removal: Clear Windows Event Logs', 'Identifies attempts to clear or disable Windows event log stores using Windows wevetutil command. This is often done by attackers in an attempt to evade detection or destroy forensic evidence on a system.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1070/001/"]', 'regexMatch("log.message", "(/e:false|cl|clear-log|Clear-EventLog)") && regexMatch("log.eventDataLogonProcessName", "wevtutil.exe")', '2026-03-02 13:23:27.940941', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (965, 'Windows: Clearing Windows Console History', 1, 2, 3, 'Defense Evasion', 'T1070.003 - Indicator Removal: Clear Command History', 'Identifies when a user attempts to clear console history. An adversary may clear the command history of a compromised account to conceal the actions undertaken during an intrusion.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1070/003/"]', 'regexMatch("log.message", "(Clear-History|(Remove-Item|rm)(.+)(ConsoleHost_history.txt|\\(Get-PSReadlineOption\\)\\.HistorySavePath)|(ConsoleHost_history.txt|\\(Get-PSReadlineOption\\)\\.HistorySavePath)(.+)(Remove-Item|rm)|Set-PSReadlineOption(.+)SaveNothing|SaveNothing(.+)PSReadlineOption)") && equals("log.providerName", "PowerShell")', '2026-03-02 13:23:29.287886', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (966, 'Windows Defender: Antimalware engine found malware or other potentially unwanted software', 1, 2, 3, 'Execution', 'T1546 - Event Triggered Execution', 'This rule is triggered when the antimalware engine detects malware or potentially unwanted software on the system. This alert is critical to identify the presence of threats and unwanted software that may compromise system security and performance.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1546/"]', 'oneOf("log.eventCode", [1006, 1015, 1116]) && equals("log.providerName", "SecurityCenter")', '2026-03-02 13:23:30.591494', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (967, 'Windows Service Installed via an Unusual Client', 3, 3, 2, 'Privilege Escalation', 'T1543.003 - Create or Modify System Process: Windows Service', 'Identifies the creation of a Windows service by an unusual client process. Services may be created with administrator privileges but are executed under SYSTEM privileges, so an adversary may also use a service to escalate privileges from administrator to SYSTEM.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1543/003/"]', 'equals("action", "service-installed") && equals("clientProcessId", "0") && equals("parentProcessId", "0")', '2026-03-02 13:23:31.951538', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (968, 'Windows: Whoami Process Activity', 1, 1, 0, 'Discovery', 'T1033 - System Owner/User Discovery', 'Identifies suspicious use of whoami.exe which displays user, group, and privileges information for the user who is currently logged on to the local system.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1033/"]', 'contains("log.eventDataProcessName", "whoami.exe")', '2026-03-02 13:23:33.260121', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (969, 'Windows Web Shell Detection: Script Process Child of Common Web Processes', 1, 3, 2, 'Persistence', 'T1505.003 - Server Software Component: Web Shell', 'Identifies suspicious commands executed via a web server, which may suggest a vulnerability and remote shell access.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1505/003/"]', 'regexMatch("log.eventDataProcessName", "(cmd.exe|cscript.exe|powershell.exe|pwsh.exe|powershell_ise.exe|wmic.exe|wscript.exe)") && regexMatch("log.eventDataParentProcessName", "(w3wp.exe|httpd.exe|nginx.exe|php.exe|php-cgi.exe|tomcat.exe)")', '2026-03-02 13:23:34.618192', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (970, 'Windows: Volume Shadow Copy Deletion via WMIC', 1, 2, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies use of wmic.exe for shadow copy deletion on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'regexMatch("log.message", "(delete(.+)shadowcopy|shadowcopy(.+)delete)") && contains("log.eventDataProcessName", "WMIC.exe")', '2026-03-02 13:23:35.668424', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (971, 'Windows: Volume Shadow Copy Deletion via PowerShell', 1, 2, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies the use of the Win32_ShadowCopy class and related cmdlets to achieve shadow copy deletion. This commonly occurs in tandem with ransomware or other destructive attacks.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)") && regexMatch("log.message", "(Get-WmiObject|gwmi|Get-CimInstance|gcim)") && regexMatch("log.message", "(Win32_ShadowCopy)") && regexMatch("log.message", "(\\.Delete\\(\\)|Remove-WmiObject|rwmi|Remove-CimInstance|rcim)")', '2026-03-02 13:23:36.718635', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (972, 'Windows: Volume Shadow Copy Deleted or Resized via VssAdmin', 1, 2, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies use of vssadmin.exe for shadow copy deletion or resizing on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'regexMatch("log.message", "((delete|resize)(.+)shadows|shadows(.+)(delete|resize))") && contains("log.eventDataProcessName", "vssadmin.exe")', '2026-03-02 13:23:37.827087', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (973, 'Windows: Unusual Service Host Child Process - Childless Service', 1, 3, 2, 'Privilege Escalation', 'T1055 - Process Injection', 'Identifies unusual child processes of Service Host (svchost.exe) that traditionally do not spawn any child processes. This may indicate a code injection or an equivalent form of exploitation.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1055/"]', 'contains("log.eventDataParentProcessName", "svchost.exe") && regexMatch("log.message", "(WdiSystemHost|LicenseManager|StorSvc|CDPSvc|cdbhsvc|BthAvctpSvc|SstpSvc|WdiServiceHost|imgsvc|TrkWks|WpnService|IKEEXT|PolicyAgent|CryptSvc|netprofm|ProfSvc|StateRepository|camsvc|LanmanWorkstation|NlaSvc|EventLog|hidserv|DisplayEnhancementService|ShellHWDetection|AppHostSvc|fhsvc|CscService|PushToInstall)") && !regexMatch("log.eventDataProcessName", "(WerFault.exe|WerFaultSecure.exe|wermgr.exe|rundll32.exe)") && !regexMatch("log.eventDataProcessName", "(:\\Windows\\System32\\RelPost.exe|:\\Program Files\\|:\\Program Files (x86)\\|:\\Windows\\System32\\Kodak\\kds_i4x50\\lib\\lexexe.exe)") && !regexMatch("log.message", "(WdiSystemHost|WdiServiceHost|imgsvc)") && !regexMatch("log.message", "(:\\WINDOWS\\System32\\winethc.dll,ForceProxyDetectionOnNextRun)")', '2026-03-02 13:23:38.900423', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (974, 'Windows: Unusual Print Spooler Child Process', 1, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', 'Detects unusual Print Spooler service (spoolsv.exe) child processes. This may indicate an attempt to exploit privilege escalation vulnerabilities related to the Printing Service on Windows.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1068/"]', 'contains("log.eventDataParentProcessName", "spoolsv.exe") && !regexMatch("log.eventDataProcessName", "(splwow64.exe|PDFCreator.exe|acrodist.exe|spoolsv.exe|msiexec.exe|route.exe|WerFault.exe|net.exe|cmd.exe|powershell.exe|netsh.exe|regsvr32.exe)") && !regexMatch("log.message", "(\\WINDOWS\\system32\\spool\\DRIVERS|stop|start|.spl|\\program files(.+)route add|add portopening|rule name|PrintConfig.dll)") && equals("log.logName", "System")', '2026-03-02 13:23:40.035451', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (975, 'Windows: Unusual Network Connection via DllHost or via RunDLL32', 2, 3, 2, 'Defense Evasion', 'T1218 - System Binary Proxy Execution', 'Identifies unusual instances of dllhost.exe making outbound network connections. This may indicate adversarial Command and Control activity. Identifies unusual instances of rundll32.exe making outbound network connections. This may indicate adversarial Command and Control activity.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1218/"]', 'regexMatch("log.eventDataProcessName", "(dllhost.exe|rundll32.exe)") && regexMatch("log.origin.ips", "((10.0.0.0/8,127.0.0.0/8,169.254.0.0/16,172.16.0.0/12,192.0.0.0/24,192.0.0.0/29,192.0.0.8/32,192.0.0.9/32,192.0.0.10/32,192.0.0.170/32,192.0.0.171/32,192.0.2.0/24,192.31.196.0/24,192.52.193.0/24,192.168.0.0/16,192.88.99.0/24,224.0.0.0/4,100.64.0.0/10,192.175.48.0/24,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,240.0.0.0/4,::1,FE80::/10,FF00::/8)")', '2026-03-02 13:23:41.348436', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (976, 'Windows: Unusual File Modification by dns.exe', 1, 3, 2, 'Initial Access', 'T1133 - External Remote Services', 'Identifies an unexpected file being modified by dns.exe, the process responsible for Windows DNS Server services, which may indicate activity related to remote code execution or other forms of exploitation.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1133/"]', 'contains("log.eventDataProcessName", "dns.exe") && !contains("log.eventDataFileName", "dns.log")', '2026-03-02 13:23:42.439086', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (977, 'Windows: Unusual File Creation - Alternate Data Stream', 1, 3, 2, 'Defense Evasion', 'T1564 - Hide Artifacts', 'Identifies suspicious creation of Alternate Data Streams on highly targeted files. This is uncommon for legitimate files and sometimes done by adversaries to hide malware.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1564/"]', 'regexMatch("log.eventDataFileName", "(^C:\\(.+):(.+))") && !regexMatch("log.eventDataFileName", "(C:\\(.+):zone.identifier)") && regexMatch("log.message", "(pdf|dll|png|exe|dat|com|bat|cmd|sys|vbs|ps1|hta|txt|vbe|js|wsh|docx|doc|xlsx|xls|pptx|ppt|rtf|gif|jpg|png|bmp|img|iso)") && !regexMatch("log.eventDataProcessName", "(:\\windows\\System32\\svchost.exe|:\\Windows\\System32\\inetsrv\\w3wp.exe|:\\Windows\\explorer.exe|:\\Windows\\System32\\sihost.exe|:\\Windows\\System32\\PickerHost.exe|:\\Windows\\System32\\SearchProtocolHost.exe|:\\Program Files (x86)\\Dropbox\\Client\\Dropbox.exe|:\\Program Files\\Rivet Networks\\SmartByte\\SmartByteNetworkService.exe|:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe|:\\Program Files\\ExpressConnect\\ExpressConnectNetworkService.exe|:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe|:\\Program Files\\Google\\Chrome\\Application\\chrome.exe|:\\Program Files\\Mozilla Firefox\\firefox.exe)")', '2026-03-02 13:23:43.618034', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (978, 'Windows: UAC Bypass via Windows Firewall Snap-In Hijack', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies attempts to bypass User Account Control (UAC) by hijacking the Microsoft Management Console (MMC) Windows Firewall snap-in. Attackers bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'contains("log.eventDataProcessName", "mmc.exe") && !contains("log.message", "WerFault.exe") && contains("log.message", "WF.msc")', '2026-03-02 13:23:44.791393', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (979, 'Windows: Bypass UAC via Event Viewer', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies User Account Control (UAC) bypass via eventvwr.exe. Attackers bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'contains("log.eventDataParentProcessName", "eventvwr.exe") && !regexMatch("log.eventDataProcessName", "(:\\Windows\\SysWOW64\\mmc.exe|:\\Windows\\System32\\mmc.exe|:\\Windows\\SysWOW64\\WerFault.exe|:\\Windows\\System32\\WerFault.exe)")', '2026-03-02 13:23:46.023108', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (980, 'Windows: UAC Bypass Attempt via Privileged IFileOperation COM Interface', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies attempts to bypass User Account Control (UAC) via DLL side-loading. Attackers may attempt to bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'regexMatch("log.eventDataFileName", "(wow64log.dll|comctl32.dll|DismCore.dll|OskSupport.dll|duser.dll|Accessibility.ni.dll)") && contains("log.eventDataProcessName", "dllhost.exe") && !regexMatch("log.eventDataFileName", "(C:\\Windows\\SoftwareDistribution\\|C:\\Windows\\WinSxS\\)")', '2026-03-02 13:23:47.252476', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (981, 'Windows: UAC Bypass via ICMLuaUtil Elevated COM Interface', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies User Account Control (UAC) bypass attempts via the ICMLuaUtil Elevated COM interface. Attackers may attempt to bypass UAC to stealthily execute code with elevated permissions', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'contains("log.eventDataProcessName", "dllhost.exe") && !contains("log.message", "WerFault.exe") && regexMatch("log.message", "(/Processid:\\{3E5FC7F9-9A51-4367-9063-A120244FBEC7\\}|/Processid:\\{D2E7041B-2927-42FB-8E9F-7CE93B6DC937\\})")', '2026-03-02 13:23:48.644376', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (982, 'Windows: UAC Bypass Attempt via Elevated COM Internet Explorer Add-On Installer', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies User Account Control (UAC) bypass attempts by abusing an elevated COM Interface to launch a malicious program. Attackers may attempt to bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'regexMatch("log.message", "(C:\\(.+)\\AppData\\(.+)\\Temp\\IDC(.+).tmp\\(.+).exe)") && contains("log.processParentName", "ieinstall.exe") && regexMatch("log.message", "(-Embedding)")', '2026-03-02 13:23:49.992699', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (983, 'Windows: UAC Bypass Attempt with IEditionUpgradeManager Elevated COM Interface', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies attempts to bypass User Account Control (UAC) by abusing an elevated COM Interface to launch a rogue Windows ClipUp program. Attackers may attempt to bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'contains("log.eventDataProcessName", "Clipup.exe") && !regexMatch("log.message", "(C:\\Windows\\System32\\ClipUp.exe)") && contains("log.eventDataParentProcessName", "dllhost.exe") && regexMatch("log.message", "(/Processid:{BD54C901-076B-434E-B6C7-17C531F4AB41)")', '2026-03-02 13:23:51.350343', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (984, 'Windows: SeDebugPrivilege Enabled by a Suspicious Process', 1, 3, 2, 'Privilege Escalation', 'T1134 - Access Token Manipulation', 'Identifies the creation of a process running as SYSTEM and impersonating a Windows core binary privileges. Adversaries may create a new process with a different token to escalate privileges and bypass access controls.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1134/"]', 'regexMatch("log.action", "(Token Right Adjusted Events)") && regexMatch("log.eventProvider", "(Microsoft-Windows-Security-Auditing)") && regexMatch("log.eventDataEnabledPrivilegeList", "(SeDebugPrivilege)") && !(oneOf("log.eventDataSubjectUserSid", ["S-1-5-18", "S-1-5-19", "S-1-5-20"])) && !(regexMatch("log.eventDataProcessName", "(:\\Windows\\System32\\msiexec.exe|:\\Windows\\SysWOW64\\msiexec.exe|:\\Windows\\System32\\lsass.exe|:\\Windows\\WinSxS\\|:\\Program Files\\|:\\Program Files (x86)\\|:\\Windows\\System32\\MRT.exe|:\\Windows\\System32\\cleanmgr.exe|:\\Windows\\System32\\taskhostw.exe|:\\Windows\\System32\\mmc.exe|:\\Users\\(.+)\\AppData\\Local\\Temp\\(.+)-(.+)\\DismHost.exe|:\\Windows\\System32\\auditpol.exe|:\\Windows\\System32\\wbem\\WmiPrvSe.exe|:\\Windows\\SysWOW64\\wbem\\WmiPrvSe.exe)"))', '2026-03-02 13:23:52.661115', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (985, 'Windows: Suspicious WMIC XSL Script Execution', 2, 3, 2, 'Defense Evasion', 'T1220 - XSL Script Processing', 'Identifies WMIC allowlist bypass techniques by alerting on suspicious execution of scripts. When WMIC loads scripting libraries it may be indicative of an allowlist bypass.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1220/"]', 'regexMatch("log.message", "(?i)wmic.*format") && !contains("log.message", "/format:table") && regexMatch("log.message", "(?i)(jscript\\.dll|vbscript\\.dll)")', '2026-03-02 13:23:54.056297', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (986, 'Windows: Microsoft Exchange Server UM Writing Suspicious Files', 2, 3, 2, 'Initial Access', 'T1190 - Exploit Public-Facing Application', 'Identifies suspicious files being written by the Microsoft Exchange Server Unified Messaging (UM) service. This activity has been observed exploiting CVE-2021-26858.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1190/"]', 'regexMatch("log.eventDataProcessName", "(UMWorkerProcess.exe|umservice.exe)") && regexMatch("log.message", "(php|jsp|js|aspx|asmx|asax|cfm|shtml)") && regexMatch("log.message", "(:\\\\inetpub\\\\wwwroot\\\\aspnet_client\\\\|:\\\\(.+)\\\\Microsoft\\\\Exchange Server(.+)\\\\FrontEnd\\\\HttpProxy\\\\owa\\\\auth\\\\)") && !regexMatch("log.message", "(:\\\\(.+)\\\\Microsoft\\\\Exchange Server(.+)\\\\FrontEnd\\\\HttpProxy\\\\(owa|ecp)\\\\auth\\\\version\\\\)")', '2026-03-02 13:23:55.362289', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (987, 'Windows: Suspicious WMI Image Load from MS Office', 1, 2, 3, 'Execution', 'T1047 - Windows Management Instrumentation', 'Identifies a suspicious image load (wmiutils.dll) from Microsoft Office processes. This behavior may indicate adversarial activity where child processes are spawned via Windows Management Instrumentation (WMI). This technique can be used to execute code and evade traditional parent/child processes spawned from Microsoft Office products.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1047/"]', 'regexMatch("log.eventDataProcessName", "(WINWORD.EXE|EXCEL.EXE|POWERPNT.EXE|MSPUB.EXE|MSACCESS.EXE)") && contains("log.message", "wmiutils.dll")', '2026-03-02 13:23:56.668790', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (988, 'Windows: Suspicious Image Load (taskschd.dll) from MS Office', 1, 2, 3, 'Persistence', 'T1053 - Scheduled Task/Job', 'Identifies a suspicious image load (taskschd.dll) from Microsoft Office processes. This behavior may indicate adversarial activity where a scheduled task is configured via Windows Component Object Model (COM). This technique can be used to configure persistence and evade monitoring by avoiding the usage of the traditional Windows binary (schtasks.exe) used to manage scheduled tasks.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1053/"]', 'regexMatch("log.eventDataProcessName", "(WINWORD.EXE|EXCEL.EXE|POWERPNT.EXE|MSPUB.EXE|MSACCESS.EXE)") && contains("log.message", "taskschd.dll")', '2026-03-02 13:23:57.811936', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (989, 'Windows: Suspicious Execution from mounted device', 1, 2, 3, 'Defense Evasion', 'T1055 - Process Injection', 'Identifies suspicious process access events from an unknown memory region. Endpoint security solutions usually hook userland Windows APIs in order to decide if the code that is being executed is malicious or not. It''s possible to bypass hooked functions by writing malicious functions that call syscalls directly.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', 'equals("log.eventType", "start") && regexMatch("log.process.executable", "(^C:\\\\)") && regexMatch("log.processWorkingDirectory", "((^\\w:\\\\)") && !regexMatch("log.processWorkingDirectory", "(^C:\\\\)") && contains("log.processParentName", "explorer.exe") && oneOf("log.processName", ["rundll32.exe", "mshta.exe", "powershell.exe", "pwsh.exe", "cmd.exe", "regsvr32.exe", "cscript.exe", "wscript.exe"])', '2026-03-02 13:23:59.040429', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (990, 'Windows: New Windows Service Created to start from windows root path. Suspicious event as the binary may have been dropped using Windows Admin Shares', 1, 2, 3, 'Execution', 'T1021.002 - Remote Services: SMB/Windows Admin Shares', 'Adversaries may use Valid Accounts to interact with a remote network share using Server Message Block (SMB). The adversary may then perform actions as the logged-on user.', '["https://attack.mitre.org/techniques/T1021/002/"]', 'regexMatch("log.eventDataImagePath", "(^%systemroot%\\(.+)\\(.+).exe)") && equals("log.eventCode", 7045) && oneOf("log.logName", ["system", "System"])', '2026-03-02 13:24:00.306277', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (991, 'Windows: Suspicious Cmd Execution via WMI', 1, 2, 3, 'Execution', 'T1047 - Windows Management Instrumentation', 'Identifies suspicious command execution (cmd) via Windows Management Instrumentation (WMI) on a remote host. This could be indicative of adversary lateral movement.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1047/"]', 'contains("log.eventDataParentProcessName", "WmiPrvSE.exe") && contains("log.eventDataProcessName", "cmd.exe") && contains("log.message", "\\\\127.0.0.1\\") && regexMatch("log.message", "(2>&1|1>)")', '2026-03-02 13:24:01.617379', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (992, 'Windows: Suspicious CertUtil Commands', 3, 2, 1, 'Defense Evasion', 'T1140 - Deobfuscate/Decode Files or Information', 'Identifies suspicious commands being used with certutil.exe. CertUtil is a native Windows component which is part of Certificate Services. CertUtil is often abused by attackers to live off the land for stealthier command and control or data exfiltration.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1140/"]', 'regexMatch("log.message", "(decode|encode|urlcache|verifyctl|encodehex|decodehex|exportPFX)") && contains("log.eventDataProcessName", "certutil.exe")', '2026-03-02 13:24:03.011582', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (993, 'Windows: Detection of SUNBURST command and control activity', 3, 3, 2, 'Command and Control', 'T1195 - Supply Chain Compromise', 'This rule detects post-exploitation command and control activity of the SUNBURST backdoor.', '["https://attack.mitre.org/tactics/TA0011/","https://attack.mitre.org/techniques/T1195/"]', 'regexMatch("log.eventDataProcessName", "(ConfigurationWizard.exe|NetFlowService.exe|NetflowDatabaseMaintenance.exe|SolarWinds.Administration.exe|SolarWinds.BusinessLayerHost.exe|SolarWinds.BusinessLayerHostx64.exe|SolarWinds.Collector.Service.exe|SolarwindsDiagnostics.exe)") && regexMatch("log.message", "(/swip/Upload.ashx(.+)(POST|PUT)|(POST|PUT)(.+)/swip/Upload.ashx|/swip/SystemDescription(.+)(GET|HEAD)|(GET|HEAD)(.+)/swip/SystemDescription)")', '2026-03-02 13:24:04.375344', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (994, 'Windows: Startup Persistence by a Suspicious Process', 1, 2, 3, 'Persistence', 'T1547.001 - Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder', 'Identifies files written to or modified in the startup folder by commonly abused processes. Adversaries may use this technique to maintain persistence.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1547/001/"]', 'contains("log.message", "delet") && regexMatch("log.eventDataProcessName", "(cmd.exe|powershell.exe|wmic.exe|mshta.exe|pwsh.exe|cscript.exe|wscript.exe|regsvr32.exe|RegAsm.exe|rundll32.exe|EQNEDT32.EXE|WINWORD.EXE|EXCEL.EXE|POWERPNT.EXE|MSPUB.EXE|MSACCESS.EXE|iexplore.exe|InstallUtil.exe)") && regexMatch("log.message", "(C:\\Users\\(.+)\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\|C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp\\)") && !contains("target.domain", "NT AUTHORITY")', '2026-03-02 13:24:05.635651', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (995, 'Windows: User account exposed to Kerberoasting', 3, 3, 2, 'Credential Access', 'T1558.003 - Steal or Forge Kerberos Tickets: Kerberoasting', 'Detects when a user account has the servicePrincipalName attribute modified. Attackers can abuse write privileges over a user to configure Service Principle Names (SPNs) so that they can perform Kerberoasting. Administrators can also configure this for legitimate purposes, exposing the account to Kerberoasting.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1558/003/"]', 'regexMatch("log.action", "([Dd]irectory [Ss]ervice [Cc]hanges)") && equals("log.eventCode", 5136) && regexMatch("log.message", "(servicePrincipalName)")', '2026-03-02 13:24:06.774754', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (996, 'Windows: SIP Provider Modification', 1, 2, 3, 'Defense Evasion', 'T1553.003 - Subvert Trust Controls: SIP and Trust Provider Hijacking', 'Identifies modifications to the registered Subject Interface Package (SIP) providers. SIP providers are used by the Windows cryptographic system to validate file signatures on the system. This may be an attempt to bypass signature validation checks or inject code into critical processes.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1553/003/"]', 'equals("log.event.type", "change") && contains("log.registryDataStrings", ".dll") && regexMatch("log.registryPath", "(HKLM\\\\SOFTWARE(\\\\WOW6432Node)?\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\(.+)\\\\Dll|HKLM\\\\SOFTWARE(\\\\WOW6432Node)?\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\(.+)\\\\\\$Dll)")', '2026-03-02 13:24:07.824543', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (997, 'Windows: Execution via local SxS Shared Modules', 1, 2, 3, 'Execution', 'T1129 - Shared Modules', 'Identifies the creation, change, or deletion of a DLL module within a Windows SxS local folder. Adversaries may abuse shared modules to execute malicious payloads by instructing the Windows module loader to load DLLs from arbitrary local paths.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1129/"]', 'contains("log.file.extension", "dll") && regexMatch("log.file.path", "(C:\\\\(.+)\\\\.exe.local\\\\(.+).dll)")', '2026-03-02 13:24:09.001092', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (998, 'Windows: Potential Shadow Credentials added to AD Object', 3, 3, 2, 'Credential Access', 'T1556 - Modify Authentication Process', 'Identify the modification of the msDS-KeyCredentialLink attribute in an Active Directory Computer or User Object. Attackers can abuse control over the object and create a key pair, append to raw public key in the attribute, and obtain persistent and stealthy access to the target user or computer object.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1556/"]', 'regexMatch("log.action", "([Dd]irectory [Ss]ervice [Cc]hanges)") && equals("log.eventCode", 5136) && regexMatch("log.message", "(msDS-KeyCredentialLink)") && contains("log.message", ":828")', '2026-03-02 13:24:10.140157', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (999, 'Windows: Detection of a suspicious PowerShell Script with screenshot capabilities', 3, 2, 1, 'Collection', 'T1113 - Screen Capture', 'Detects PowerShell scripts that can take screenshots, which is a common feature in post-exploitation kits and remote access tools', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1113/"]', 'regexMatch("log.message", "(CopyFromScreen(.+)Drawing.Bitmap|Drawing.Bitmap(.+)CopyFromScreen)")', '2026-03-02 13:24:11.277803', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1000, 'Windows: Privilege Escalation via Windir Environment Variable', 2, 3, 2, 'Privilege Escalation', 'T1574.007 - Hijack Execution Flow: Path Interception by PATH Environment Variable', 'Identifies a privilege escalation attempt via a rogue Windows directory (Windir) environment variable. This is a known primitive that is often combined with other vulnerabilities to elevate privileges.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1574/007/"]', 'regexMatch("log.message", "(C:\\windows|%SystemRoot%)") && regexMatch("log.message", "(HKEY_USERS\\(.+)\\Environment\\windir|HKEY_USERS\\(.+)\\Environment\\systemroot)")', '2026-03-02 13:24:12.579516', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1001, 'Windows: Persistence via PowerShell profile', 2, 3, 1, 'Persistence', 'T1546.013 - Event Triggered Execution: PowerShell Profile', 'Identifies the creation or modification of a PowerShell profile. PowerShell profile is a script that is executed when PowerShell starts to customize the user environment, which can be abused by attackers to persist in a environment where PowerShell is common.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1098/002/"]', 'regexMatch("log.eventDataProcessName", "(:\\Users\\(.+)\\Documents\\WindowsPowerShell\\|:\\Users\\(.+)\\Documents\\PowerShell\\|:\\Windows\\System32\\WindowsPowerShell\\)") && regexMatch("log.eventDataProcessName", "(profile.ps1|Microsoft.Powershell_profile.ps1)")', '2026-03-02 13:24:13.722900', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1002, 'Windows: New ActiveSyncAllowedDeviceID Added via PowerShell', 3, 2, 1, 'Persistence', 'T1098.002 - Account Manipulation: Additional Email Delegate Permissions', 'Identifies the use of the Exchange PowerShell cmdlet, Set-CASMailbox, to add a new ActiveSync allowed device. Adversaries may target user email to collect sensitive information.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1098/002/"]', 'oneOf("log.eventDataProcessName", ["powershell.exe", "pwsh.exe", "powershell_ise.exe"]) && regexMatch("log.message", "(Set-CASMailbox(.+)ActiveSyncAllowedDeviceIDs)")', '2026-03-02 13:24:14.906311', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1003, 'Windows: Potential Credential Access via DuplicateHandle in LSASS', 3, 1, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies suspicious access to an LSASS handle via DuplicateHandle. This may indicate an attempt to bypass the NtOpenProcess API to evade detection and dump LSASS memory for credential access.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'equals("log.eventCode", 10) && contains("log.eventDataProcessName", "lsass.exe") && equals("log.eventDataGrantedAccess", "0x40") && regexMatch("log.eventDataCallTrace", "(UNKNOWN)")', '2026-03-02 13:24:16.167729', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1004, 'Windows: Potential DNS Tunneling via NsLookup', 3, 2, 1, 'Command and Control', 'T1071 - Application Layer Protocol', 'This rule identifies a large number of nslookup.exe executions with an explicit query type from the same host. This may indicate command and control activity utilizing the DNS protocol.', '["https://attack.mitre.org/tactics/TA0011/","https://attack.mitre.org/techniques/T1071/"]', 'contains("log.eventDataProcessName", "nslookup.exe") && regexMatch("log.message", "(-querytype=|-qt=|-q=|-type=)")', '2026-03-02 13:24:17.303228', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1005, 'Windows: Printer driver failed to load, possible remote code execution using PrinterNightmare exploit: CVE-2021-34527', 3, 2, 1, 'Lateral Movement', 'T1210 - Exploitation of Remote Services', 'Adversaries may exploit remote services to gain unauthorized access to internal systems once inside of a network. Exploitation of a software vulnerability occurs when an adversary takes advantage of a programming error in a program, service, or within the operating system software or kernel itself to execute adversary-controlled code. A common goal for post-compromise exploitation of remote services is for lateral movement to enable access to a remote system.', '["https://attack.mitre.org/techniques/T1210/"]', 'equals("log.eventCode", 808) && oneOf("log.severityLabel", ["Error", "error"])', '2026-03-02 13:24:18.699952', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1006, 'Windows: Possible addition of new item to Windows startup registry', 2, 3, 1, 'Persistence', 'T1547.001 - Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder', 'Adversaries may achieve persistence by adding a program to a startup folder or referencing it with a Registry run key. Adding an entry to the run keys in the Registry or startup folder will cause the program referenced to be executed when a user logs in. These programs will be executed under the context of the user and will have the account''s associated permissions level.', '["https://attack.mitre.org/techniques/T1547/001/"]', 'regexMatch("log.message", "(SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run)")', '2026-03-02 13:24:19.964043', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1007, 'Windows: PowerShell Script with Token Impersonation Capabilities', 3, 3, 2, 'Privilege Escalation', 'Token Impersonation/Theft', 'Detects scripts that contain PowerShell functions, structures, or Windows API functions related to token impersonation/theft. Attackers may duplicate then impersonate another user''s token to escalate privileges and bypass access controls.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1134/001/"]', e'oneOf("log.message", ["Invoke-TokenManipulation", "ImpersonateNamedPipeClient", "NtImpersonateThread"]) || +(regexMatch("log.message", "(UpdateProcThreadAttribute(.+)STARTUPINFOEX|STARTUPINFOEX(.+)UpdateProcThreadAttribute)") && +regexMatch("log.message", "(AdjustTokenPrivileges(.+)SeDebugPrivilege|SeDebugPrivilege(.+)AdjustTokenPrivileges)")) || +regexMatch("log.message", "((SetThreadToken|ImpersonateLoggedOnUser|CreateProcessWithTokenW|CreatePRocessAsUserW|CreateProcessAsUserA)(.+)(DuplicateToken|DuplicateTokenEx)|(DuplicateToken|DuplicateTokenEx)(.+)(SetThreadToken|ImpersonateLoggedOnUser|CreateProcessWithTokenW|CreatePRocessAsUserW|CreateProcessAsUserA))") +', '2026-03-02 13:24:21.279978', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1008, 'Windows: PowerShell Suspicious Discovery Related Windows API Functions', 2, 1, 3, 'Discovery', 'T1069 - Permission Groups Discovery', 'This rule detects the use of discovery-related Windows API functions in PowerShell Scripts. Attackers can use these functions to perform various situational awareness related activities, like enumerating users, shares, sessions, domain trusts, groups, etc.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1069/"]', e'contains("log.message", ["NetShareEnum", "NetWkstaUserEnum", "NetSessionEnum", "NetLocalGroupEnum", "NetLocalGroupGetMembers", "DsGetSiteName", "DsEnumerateDomainTrusts", "WTSEnumerateSessionsEx", "WTSQuerySessionInformation", "LsaGetLogonSessionData", "QueryServiceObjectSecurity"]) +', '2026-03-02 13:24:22.583987', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1009, 'Windows: PowerShell Kerberos Ticket Request', 3, 2, 1, 'Credential Access', 'T1059 - Command and Scripting Interpreter', 'Detects PowerShell scripts that have the capability of requesting kerberos tickets, which is a common step in Kerberoasting toolkits to crack service accounts.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1059/"]', 'regexMatch("log.message", "(KerberosRequestorSecurityToken)")', '2026-03-02 13:24:23.943734', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1010, 'Windows: PowerShell PSReflect Script', 2, 3, 1, 'Execution', 'T1059 - Command and Scripting Interpreter', 'Detects the use of PSReflect in PowerShell scripts. Attackers leverage PSReflect as a library that enables PowerShell to access win32 API functions.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1059/"]', 'regexMatch("log.message", "(New-InMemoryModule|Add-Win32Type|psenum|DefineDynamicAssembly|DefineDynamicModule|Reflection.TypeAttributes|Reflection.Emit.OpCodes|Reflection.Emit.CustomAttributeBuilder|Runtime.InteropServices.DllImportAttribute)")', '2026-03-02 13:24:25.245292', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1011, 'Windows: Potential Process Injection via PowerShell', 3, 3, 2, 'Defense Evasion', 'T1055 - Process Injection', 'Detects the use of Windows API functions that are commonly abused by malware and security tools to load malicious code or inject it into remote processes.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', 'regexMatch("log.message", "(WriteProcessMemory|CreateRemoteThread|NtCreateThreadEx|CreateThread|QueueUserAPC|SuspendThread|ResumeThread|GetDelegateForFunctionPointer)") && regexMatch("log.message", "(VirtualAlloc|VirtualAllocEx|VirtualProtect|LdrLoadDll|LoadLibrary|LoadLibraryA|LoadLibraryEx|GetProcAddress|OpenProcess|OpenProcessToken|AdjustTokenPrivileges)")', '2026-03-02 13:24:26.602880', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1012, 'Windows: Suspicious Portable Executable Encoded in Powershell Script', 2, 3, 1, 'Execution', 'T1059 - Command and Scripting Interpreter', 'Detects the presence of a portable executable (PE) in a PowerShell script by looking for its encoded header. Attackers embed PEs into PowerShell scripts to inject them into memory, avoiding defences by not writing to disk.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1059/"]', 'contains("log.message", "TVqQAAMAAAAEAAAA")', '2026-03-02 13:24:27.965317', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1013, 'Windows: PowerShell MiniDump Script', 3, 3, 2, 'Credential Access', 'T1059.001 - Command and Scripting Interpreter: PowerShell', 'This rule detects PowerShell scripts capable of dumping process memory using WindowsErrorReporting or Dbghelp.dll MiniDumpWriteDump. Attackers can use this tooling to dump LSASS and get access to credentials.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1059/001/"]', 'regexMatch("log.message", "(MiniDumpWriteDump|MiniDumpWithFullMemory|pmuDetirWpmuDiniM)")', '2026-03-02 13:24:29.271792', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1014, 'Windows: PowerShell Share Enumeration Script', 3, 2, 1, 'Discovery', 'T1135 - Network Share Discovery', 'Detects scripts that contain PowerShell functions, structures, or Windows API functions related to windows share enumeration activities. Attackers, mainly ransomware groups, commonly identify and inspect network shares, looking for critical information for encryption and/or exfiltration.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1135/"]', 'regexMatch("log.message", "(Invoke-ShareFinder|Invoke-ShareFinderThreaded|(shi1_netname(.+)shi1_remark)|shi1_remark(.+)shi1_netname|(NetShareEnum(.+)NetApiBufferFree)|(NetApiBufferFree(.+)NetShareEnum))")', '2026-03-02 13:24:30.360898', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1015, 'Windows: PowerShell Suspicious Payload Encoded and Compressed', 2, 3, 1, 'Defense Evasion', 'T1027 - Obfuscated Files or Information', 'Identifies the use of .NET functionality for decompression and base64 decoding combined in PowerShell scripts, which malware and security tools heavily use to deobfuscate payloads and load them directly in memory to bypass defenses.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1027/"]', 'regexMatch("log.message", "(FromBase64String)") && regexMatch("log.message", "(System.IO.Compression.DeflateStream|System.IO.Compression.GzipStream|IO.Compression.DeflateStream|IO.Compression.GzipStream)")', '2026-03-02 13:24:31.673759', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1016, 'Windows: Suspicious .NET Reflection via PowerShell', 3, 2, 1, 'Defense Evasion', 'T1055 - Process Injection', 'Detects the use of Reflection.Assembly to load PEs and DLLs in memory in PowerShell scripts. Attackers use this method to load executables and DLLs without writing to the disk, bypassing security solutions.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', 'regexMatch("log.message", "(\\[Reflection.Assembly\\]::Load)")', '2026-03-02 13:24:32.937200', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1017, 'Windows: Outlook add-in was loaded by powershell, possible use for email collection', 3, 2, 1, 'Execution', 'T1114.001 - Email Collection: Local Email Collection', 'Adversaries may target user email on local systems to collect sensitive information. Files containing email data can be acquired from a user is local system, such as Outlook storage or cache files.', '["https://attack.mitre.org/techniques/T1114/001/"]', 'contains("log.message", "Microsoft.Office.Interop.Outlook")', '2026-03-02 13:24:34.301901', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1018, 'Windows: Potential Invoke-Mimikatz PowerShell Script', 3, 3, 2, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', 'Mimikatz is a credential dumper capable of obtaining plaintext Windows account logins and passwords, along with many other features that make it useful for testing the security of networks. This rule detects Invoke-Mimikatz PowerShell script and alike.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/","https://attack.mitre.org/techniques/T1003/001/"]', 'regexMatch("log.message", "(DumpCreds(.+)DumpCerts|DumpCerts(.+)DumpCreds|sekurlsa::logonpasswords|crypto::certificates(.+)CERT_SYSTEM_STORE_LOCAL_MACHINE|CERT_SYSTEM_STORE_LOCAL_MACHINE(.+)crypto::certificates)")', '2026-03-02 13:24:35.609018', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1019, 'Windows: Kerberos Pre-authentication Disabled for User', 2, 2, 3, 'Credential Access', 'T1558.004 - Steal or Forge Kerberos Tickets: AS-REP Roasting', 'Identifies the modification of an accounts Kerberos pre-authentication options. An adversary with GenericWrite/GenericAll rights over the account can maliciously modify these settings to perform offline password cracking attacks such as AS-REP roasting', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1558/","https://attack.mitre.org/techniques/T1558/004/"]', 'regexMatch("log.message", "(('')?[Dd]on('')?t [Rr]equire [Pp]reauth('')?(\\s)?(-)?(\\s)?[Ee]nabled)") && equals("log.eventCode", 4738)', '2026-03-02 13:24:36.922121', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1020, 'Windows audit log was cleared', 1, 2, 3, 'Defense Evasion', 'T1070.001 - Indicator Removal: Clear Windows Event Logs', 'Detects when the Windows audit log (Security event log) has been cleared. Adversaries may clear event logs to remove evidence of an intrusion.', '["https://attack.mitre.org/techniques/T1070/001/"]', 'equals("log.eventCode", 1102)', '2026-03-02 13:24:38.237059', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1021, 'Windows Defender: Protection Disabled', 1, 2, 3, 'Defense Evasion', 'T1562 - Impair Defenses', 'This rule is triggered when it detects that Windows Defender protection has been turned off or disabled on the system. The alert is crucial to identify any unauthorized or malicious actions that may leave the system vulnerable to security threats.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/"]', 'oneOf("log.eventCode", [5001, 5012])', '2026-03-02 13:24:39.501400', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1022, 'Windows: An attempt was made to set the Directory Services Restore Mode', 1, 2, 3, 'Collection', 'T1005 - Data from Local System', 'This event generates on every attempt made to set the Directory Services Restore Mode. Is a function on Active Directory Domain Controllers to take the server offline for emergency maintenance, particularly restoring backups of AD objects. It is accessed on Windows Server via the advanced startup menu, similarly to safe mode.', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1005"]', 'equals("log.eventCode", 4794)', '2026-03-02 13:24:40.723515', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1023, 'Windows: Hiding files with attrib.exe', 1, 2, 3, 'Defense Evasion', 'T1564 - Hide Artifacts', 'This correlation rule detects events where the attrib.exe tool is used to hide files on the system. Cloaking files using this tool can be an indicator of suspicious or malicious activity, as it could be used by malicious actors to evade detection and hide their presence on the system.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1564/"]', 'regexMatch("log.message", "(attrib +h|attrib.exe +h)")', '2026-03-02 13:24:41.975963', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1024, 'Windows: Changes to AdminSDHolder', 1, 2, 3, 'Privilege Escalation', 'T1087 - Account Discovery', 'This rule detects changes to the AdminSDHolder object within the Active Directory. The AdminSDHolder object is critical to maintaining the ACLs of highly privileged accounts and groups in the domain. Any unexpected modification to this object could indicate a privilege escalation attempt or malicious action.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1069/"]', 'regexMatch("log.action", "Directory Service Changes") && equals("log.eventCode", 5136) && regexMatch("log.eventDataObjectName", "AdminSDHolder")', '2026-03-02 13:24:43.258258', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1074, 'Process Masquerading Detection', 2, 3, 2, 'Defense Evasion', 'T1036.005 - Masquerading: Match Legitimate Name or Location', e'Detects executables masquerading as legitimate Windows system processes but running from +incorrect locations. For example, svchost.exe should only run from C:\\Windows\\System32, +and explorer.exe should only run from C:\\Windows. Malware commonly uses legitimate process +names to avoid detection by analysts and automated tools. + +Next Steps: +1. Identify the actual file path of the masquerading process +2. Compare the file hash against known good versions of the legitimate binary +3. Check the digital signature of the suspicious executable +4. Analyze the executable in a sandbox environment +5. Review the parent process that launched the masquerading binary +6. Kill the suspicious process and quarantine the file +7. Search for other instances of the same file across the environment +', '["https://attack.mitre.org/techniques/T1036/005/","https://www.elastic.co/blog/how-hunt-masquerade-ball","https://redcanary.com/threat-detection-report/techniques/masquerading/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\svchost\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\(System32|SysWOW64)\\\\\\\\svchost\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\csrss\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\(System32|SysWOW64)\\\\\\\\csrss\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\lsass\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\lsass\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\services\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\services\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\smss\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\smss\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\wininit\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\wininit\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\explorer\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\explorer\\\\.exe$") + ) || + ( + regexMatch("log.eventDataProcessName", "(?i)\\\\\\\\svchost\\\\.exe$") && + !regexMatch("log.eventDataProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\(System32|SysWOW64)\\\\\\\\svchost\\\\.exe$") + ) || + ( + regexMatch("log.eventDataProcessName", "(?i)\\\\\\\\lsass\\\\.exe$") && + !regexMatch("log.eventDataProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\lsass\\\\.exe$") + ) +) +', '2026-03-02 13:27:45.750434', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1075, 'LSASS Memory Dump Alternatives', 3, 3, 1, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', e'Detects alternatives to Mimikatz for dumping LSASS process memory, including procdump.exe, +comsvcs.dll MiniDump via rundll32, Task Manager LSASS dump, and direct process access to LSASS. +These tools are commonly used by attackers who avoid Mimikatz to extract credentials from memory. + +Next Steps: +1. Isolate the affected host immediately to prevent lateral movement +2. Identify the user account and parent process that initiated the dump +3. Check if procdump or comsvcs.dll was used and from what directory +4. Review for any exfiltration of the dump file to external destinations +5. Reset all credentials that were potentially exposed on the affected host +6. Investigate how the attacker obtained the privileges needed for LSASS access +7. Search for evidence of credential reuse across the environment +', '["https://attack.mitre.org/techniques/T1003/001/","https://www.microsoft.com/en-us/security/blog/2022/10/05/detecting-and-preventing-lsass-credential-dumping-attacks/","https://redcanary.com/threat-detection-report/techniques/lsass-memory/"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + (regexMatch("log.eventDataCommandLine", "(?i)procdump.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)procdump.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)rundll32.*comsvcs\\\\.dll.*MiniDump") || + regexMatch("log.eventDataCommandLine", "(?i)rundll32.*comsvcs\\\\.dll.*MiniDump")) || + (regexMatch("log.eventDataCommandLine", "(?i)Out-Minidump.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)Out-Minidump.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)taskmgr.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)taskmgr.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)sqldumper\\\\.exe.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)sqldumper\\\\.exe.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)createdump\\\\.exe.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)createdump\\\\.exe.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)rdrleakdiag\\\\.exe.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)rdrleakdiag\\\\.exe.*lsass")) + ) +) || +( + equals("log.eventCode", "10") && + regexMatch("log.eventDataTargetImage", "(?i)lsass\\\\.exe$") && + oneOf("log.eventDataGrantedAccess", ["0x1010", "0x1038", "0x1fffff", "0x1410", "0x143a"]) +) +', '2026-03-02 13:27:47.013814', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1025, 'Suspicious Admin Share Access Detection', 3, 3, 2, 'Lateral Movement', 'T1021.002 - Remote Services: SMB/Windows Admin Shares', e'Detects suspicious access to Windows administrative shares (C$, ADMIN$, IPC$) which is +commonly used for lateral movement. While admin share access can be legitimate, attackers +use these shares to copy payloads, execute remote commands, and move laterally across the +network. The rule monitors Event ID 5140 (network share access) and Event ID 5145 (detailed +share access) for admin share connections from non-standard sources. + +Next Steps: +1. Identify the source IP and user account accessing the admin share +2. Verify if this is authorized administrative access or IT operations +3. Check what files were read or written to the admin share +4. Review if tools or malware were copied via the share +5. Check for service creation or scheduled task creation on the target +6. Correlate with authentication events from the same source IP +7. Block unauthorized admin share access via Group Policy +', '["https://attack.mitre.org/techniques/T1021/002/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=5140","https://www.sans.org/blog/detecting-lateral-movement-using-windows-admin-shares/"]', e'( + equals("log.eventCode", "5140") && + equals("log.channel", "Security") && + regexMatch("log.eventDataShareName", "(?i)\\\\\\\\\\\\*(C\\\\$|ADMIN\\\\$)$") && + exists("log.eventDataIpAddress") && + !equals("log.eventDataIpAddress", "::1") && + !equals("log.eventDataIpAddress", "127.0.0.1") && + !regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE|\\\\$)") && + !regexMatch("log.eventDataSubjectUserName", "(?i)\\\\$$") +) || +( + equals("log.eventCode", "5145") && + equals("log.channel", "Security") && + regexMatch("log.eventDataShareName", "(?i)\\\\\\\\\\\\*(C\\\\$|ADMIN\\\\$)$") && + regexMatch("log.eventDataRelativeTargetName", "(?i)\\\\.(exe|dll|bat|cmd|ps1|vbs|hta)$") && + exists("log.eventDataIpAddress") +) +', '2026-03-02 13:26:42.834233', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{.log.eventDataIpAddress}}"}],"or":null,"within":"now-30m","count":3}]', '["lastEvent.log.eventDataIpAddress","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1026, 'Windows Token Manipulation', 3, 3, 3, 'Defense Evasion, Privilege Escalation', 'Access Token Manipulation', 'Detects potential token manipulation attacks used for privilege escalation. Monitors for suspicious combinations of privileged service calls (4673) and operations on privileged objects (4674) along with special privilege assignments (4672) that include sensitive privileges like SeDebugPrivilege, SeImpersonatePrivilege, or SeTcbPrivilege commonly abused for token manipulation.', '["https://medium.com/palantir/windows-privilege-abuse-auditing-detection-and-defense-3078a403d74e","https://attack.mitre.org/techniques/T1134/"]', 'equals("log.eventCode", "4672") && exists("log.eventDataPrivilegeList") && (contains("log.eventDataPrivilegeList", "SeDebugPrivilege") || contains("log.eventDataPrivilegeList", "SeImpersonatePrivilege") || contains("log.eventDataPrivilegeList", "SeTcbPrivilege") || contains("log.eventDataPrivilegeList", "SeAssignPrimaryTokenPrivilege") || contains("log.eventDataPrivilegeList", "SeLoadDriverPrivilege") || contains("log.eventDataPrivilegeList", "SeRestorePrivilege"))', '2026-03-02 13:26:44.141961', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{.origin.host}}"},{"field":"log.eventDataTargetLogonId","operator":"filter_term","value":"{{.log.eventDataTargetLogonId}}"},{"field":"log.eventCode","operator":"should_terms","value":"4673,4674"}],"or":null,"within":"now-10m","count":3}]', '["lastEvent.log.eventDataTargetLogonId","lastEvent.target.user","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1027, 'Silver Ticket Attack Detection', 3, 3, 2, 'Credential Access', 'T1558.002 - Steal or Forge Kerberos Tickets: Silver Ticket', e'Detects Silver Ticket attacks where adversaries forge Kerberos TGS tickets using a service +account\'s NTLM hash, bypassing the KDC entirely. Unlike Golden Tickets, Silver Tickets target +specific services. The rule detects TGS tickets presented without corresponding TGT requests, +and service access events with anomalous Kerberos authentication patterns. + +Next Steps: +1. Identify the targeted service and its associated service account +2. Verify if the service account hash has been compromised via Kerberoasting +3. Reset the targeted service account password immediately +4. Review access logs for the targeted service for unauthorized activity +5. Check for prior Kerberoasting activity targeting the same service SPN +6. Investigate the source host for compromise indicators +7. Implement AES-only encryption for service accounts +8. Enable Kerberos PAC validation on the targeted services +', '["https://attack.mitre.org/techniques/T1558/002/","https://adsecurity.org/?p=2011","https://www.sans.org/blog/kerberos-in-the-crosshairs-golden-tickets-silver-tickets-mitm-and-more/"]', e'equals("log.eventCode", "4769") && +equals("log.channel", "Security") && +( + equals("log.eventDataTicketEncryptionType", "0x17") && + !regexMatch("log.eventDataServiceName", "(?i)(krbtgt|\\\\$$)") && + !oneOf("log.eventDataStatus", ["0x0", "0x6"]) && + exists("log.eventDataIpAddress") +) +', '2026-03-02 13:26:45.499847', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{.log.eventDataIpAddress}}"}],"or":null,"within":"now-15m","count":5}]', '["lastEvent.log.eventDataIpAddress","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1028, 'RDP Brute Force Attack', 3, 2, 2, 'Credential Access', 'T1110.001 - Brute Force: Password Guessing', e'Detects multiple failed RDP login attempts from the same source IP address, indicating a potential brute force attack. This rule monitors Windows Event ID 4625 (failed logon) with focus on network logon types (type 3) which are commonly used for RDP connections. The rule triggers when 10 or more failed attempts occur from the same IP within 15 minutes. + +Next Steps: +1. Investigate the source IP address for malicious indicators and geolocation +2. Check if the targeted user accounts are legitimate and active +3. Review successful logons from the same IP after failed attempts +4. Implement IP blocking or rate limiting for the source address +5. Enable account lockout policies if not already configured +6. Consider implementing multi-factor authentication for RDP access +7. Review RDP access logs for any successful connections during the attack timeframe +', '["https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4625","https://attack.mitre.org/techniques/T1110/001/"]', 'equals("log.eventCode", "4625") && equals("log.eventDataLogonType", "3") && exists("log.eventDataIpAddress") && !equals("log.eventDataIpAddress", "-") && !equals("log.eventDataIpAddress", "::1") && !equals("log.eventDataIpAddress", "127.0.0.1")', '2026-03-02 13:26:46.801170', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{.log.eventDataIpAddress}}"},{"field":"log.eventCode","operator":"filter_term","value":"4625"},{"field":"log.eventDataLogonType","operator":"filter_term","value":"3"}],"or":null,"within":"now-15m","count":10}]', '["adversary.ip","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1029, 'Process Injection Techniques Detection', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1055 - Process Injection', e'Detects various process injection techniques including CreateRemoteThread, SetWindowsHookEx, and other methods used by malware to inject code into legitimate processes. This rule monitors for suspicious cross-process activities targeting critical Windows processes and detects PowerShell/command line usage of injection APIs. + +Next Steps: +1. Investigate the source process and command line arguments for suspicious activity +2. Examine the target process for signs of compromise or abnormal behavior +3. Review process tree and parent-child relationships for the involved processes +4. Check for additional suspicious network connections or file system activities from the same host +5. Analyze memory dumps of target processes if available to confirm injection +6. Review system logs for privilege escalation attempts around the same timeframe +7. Correlate with threat intelligence to identify known malware families using similar techniques +8. Consider isolating the affected system if malicious activity is confirmed +', '["https://attack.mitre.org/techniques/T1055/","https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon","https://www.elastic.co/blog/ten-process-injection-techniques-technical-survey-common-and-trending-process"]', e'( + equals("log.eventCode", "8") && + ( + regexMatch("log.eventDataTargetImage", "(?i)(lsass\\\\.exe|csrss\\\\.exe|services\\\\.exe|on\\\\.exe|svchost\\\\.exe|explorer\\\\.exe)") || + ( + regexMatch("log.eventDataSourceImage", "(?i)(powershell\\\\.exe|cmd\\\\.exe|rundll32\\\\.exe|regsvr32\\\\.exe)") && + !regexMatch("log.eventDataTargetImage", "(?i)(conhost\\\\.exe)") + ) + ) +) || +( + equals("log.eventCode", "10") && + regexMatch("log.eventDataTargetImage", "(?i)(lsass\\\\.exe|csrss\\\\.exe|services\\\\.exe)") && + oneOf("log.eventDataGrantedAccess", ["0x1F0FFF", "0x1F1FFF", "0x1FFFFF", "0x1F3FFF"]) && + !regexMatch("log.eventDataSourceImage", "(?i)(taskmgr\\\\.exe|procexp\\\\.exe|procmon\\\\.exe|svchost\\\\.exe)") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataCommandLine", "(?i)VirtualAllocEx") || + regexMatch("log.eventDataCommandLine", "(?i)WriteProcessMemory") || + regexMatch("log.eventDataCommandLine", "(?i)CreateRemoteThread") || + regexMatch("log.eventDataCommandLine", "(?i)NtQueueApcThread") || + regexMatch("log.eventDataCommandLine", "(?i)SetWindowsHookEx") || + regexMatch("log.eventDataCommandLine", "(?i)RtlCreateUserThread") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[Kernel32\\\\]::(VirtualAllocEx|WriteProcessMemory|CreateRemoteThread)") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[ntdll\\\\]::(NtQueueApcThread|RtlCreateUserThread)") || + contains("log.eventDataScriptBlockText", "Invoke-ReflectivePEInjection") || + contains("log.eventDataScriptBlockText", "Invoke-ProcessHollowing") + ) +) +', '2026-03-02 13:26:48.067827', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{.origin.host}}"}],"or":null,"within":"now-15m","count":3}]', '["adversary.host","adversary.process"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1030, 'PetitPotam NTLM Relay Attack Detection', 3, 3, 2, 'Credential Access', 'T1187 - Forced Authentication', e'Detects PetitPotam NTLM relay attacks that abuse EFS RPC calls to coerce domain controller +authentication. The attack forces a DC to authenticate to an attacker-controlled host, enabling +NTLM relay to Active Directory Certificate Services (AD CS) for domain compromise. The rule +monitors Event ID 5145 (network share access) for IPC$ share access to specific EFS-related +named pipes used by PetitPotam. + +Next Steps: +1. Identify the source IP attempting the EFS pipe access +2. Check if the source is an authorized system or a potential attacker +3. Verify if AD CS is configured and potentially vulnerable to NTLM relay +4. Apply Microsoft patches for PetitPotam (KB5005413) +5. Enable Extended Protection for Authentication on AD CS +6. Disable NTLM authentication where possible +7. Monitor for certificate enrollment from the relayed authentication +8. Restrict access to EFS RPC endpoints via Windows Firewall rules +', '["https://attack.mitre.org/techniques/T1187/","https://github.com/topotam/PetitPotam","https://msrc.microsoft.com/update-guide/vulnerability/ADV210003"]', e'equals("log.eventCode", "5145") && +equals("log.channel", "Security") && +equals("log.eventDataShareName", "\\\\\\\\*\\\\IPC$") && +( + contains("log.eventDataRelativeTargetName", "efsrpc") || + contains("log.eventDataRelativeTargetName", "lsarpc") || + contains("log.eventDataRelativeTargetName", "efsr") || + contains("log.eventDataRelativeTargetName", "samr") +) +', '2026-03-02 13:26:49.391972', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{.log.eventDataIpAddress}}"}],"or":null,"within":"now-5m","count":3}]', '["lastEvent.log.eventDataIpAddress","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1031, 'Pass-the-Hash Attack Detection', 3, 3, 2, 'Lateral Movement', 'T1550.002 - Use Alternate Authentication Material: Pass the Hash', e'Detects Pass-the-Hash attacks by monitoring for NTLM authentication (Event ID 4624) with +LogonType 9 (NewCredentials) or LogonType 3 (Network) from unusual sources, combined with +the use of Seclogon service. Attackers use stolen NTLM hashes to authenticate without +knowing the plaintext password, commonly through tools like Mimikatz sekurlsa::pth, +Impacket, or CrackMapExec. + +Next Steps: +1. Identify the source IP and user account used for the NTLM authentication +2. Verify if the source host should be authenticating with NTLM to this target +3. Check for prior credential dumping activity on the source host +4. Review if the authentication was followed by lateral movement or data access +5. Reset the compromised account password and any related accounts +6. Implement NTLM restrictions via Group Policy where possible +7. Enable Windows Defender Credential Guard to protect NTLM hashes +', '["https://attack.mitre.org/techniques/T1550/002/","https://www.sans.org/blog/pass-the-hash-attack-detection/","https://stealthbits.com/blog/how-to-detect-pass-the-hash-attacks/"]', e'( + equals("log.eventCode", "4624") && + equals("log.channel", "Security") && + equals("log.eventDataLogonType", "9") && + equals("log.eventDataAuthenticationPackageName", "Negotiate") && + !regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE|ANONYMOUS LOGON|-|\\\\$)") && + exists("target.user") && + !regexMatch("target.user", "(?i)\\\\$$") +) || +( + equals("log.eventCode", "4624") && + equals("log.channel", "Security") && + equals("log.eventDataLogonType", "3") && + equals("log.eventDataLmPackageName", "NTLM V1") && + exists("log.eventDataIpAddress") && + !equals("log.eventDataIpAddress", "-") && + !equals("log.eventDataIpAddress", "::1") && + !equals("log.eventDataIpAddress", "127.0.0.1") +) +', '2026-03-02 13:26:50.707553', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{.log.eventDataIpAddress}}"},{"field":"log.eventCode","operator":"filter_term","value":"4624"}],"or":null,"within":"now-30m","count":3}]', '["lastEvent.log.eventDataIpAddress","adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1032, 'NTDS.dit Extraction Attempt', 3, 3, 1, 'Credential Access', 'T1003.003 - OS Credential Dumping: NTDS', e'Detects attempts to access or copy the Active Directory domain database (NTDS.dit) which contains password hashes for all domain users. This is a critical indicator of credential theft attempts and potential domain compromise. + +Next Steps: +1. Immediately isolate the affected system to prevent further compromise +2. Review all recent activity from the source host and user account +3. Check for signs of lateral movement from this system +4. Verify integrity of domain controllers and examine recent administrative actions +5. Look for evidence of credential harvesting tools (ntdsutil, vssadmin, mimikatz) +6. Review privileged account usage and consider forcing password resets +7. Examine network traffic for data exfiltration attempts +8. Check backup systems and shadow copies for unauthorized access +9. Coordinate with incident response team for full forensic analysis +', '["https://attack.mitre.org/techniques/T1003/003/","https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4663"]', e'oneOf("log.eventCode", ["4663", "4656"]) && +equals("log.channel", "Security") && +( + endsWith("log.eventDataObjectName", "\\\\ntds.dit") || + contains("log.eventDataObjectName", "\\\\NTDS\\\\") || + endsWith("log.eventDataProcessName", "\\\\ntdsutil.exe") || + endsWith("log.eventDataProcessName", "\\\\vssadmin.exe") +) && +!equals("log.eventDataAccessMask", "0x0") +', '2026-03-02 13:26:51.792644', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{.origin.host}}"}],"or":null,"within":"now-30m","count":2}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1033, 'Malicious PowerShell Execution', 3, 3, 2, 'Execution', 'T1059.001 - Command and Scripting Interpreter: PowerShell', e'Detects malicious PowerShell execution patterns including encoded commands, download cradles, +AMSI bypass attempts, execution policy bypasses, and common offensive PowerShell techniques. +These patterns are frequently used by attackers for initial access, lateral movement, and +payload delivery. + +Next Steps: +1. Isolate the affected host immediately +2. Decode any Base64-encoded command content for analysis +3. Check parent process - unexpected parents (e.g., Word, Excel) indicate macro-based attacks +4. Review network connections for download cradle destinations +5. Examine PowerShell transcription and script block logs for full command content +6. Search for persistence mechanisms created by the script +7. Check if AMSI was successfully bypassed and what payload was executed +8. Correlate with any recent phishing emails received by the user +', '["https://attack.mitre.org/techniques/T1059/001/","https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging","https://www.fireeye.com/blog/threat-research/2016/02/greater_visibility_t.html"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataNewProcessName", "(?i)powershell\\\\.exe$|pwsh\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)powershell\\\\.exe$|pwsh\\\\.exe$") + ) && + ( + regexMatch("log.eventDataCommandLine", "(?i)-[eE][nN][cC]\\\\s+[A-Za-z0-9+/=]{50,}") || + regexMatch("log.eventDataCommandLine", "(?i)(-nop|-noni|-w\\\\s+hidden|-ep\\\\s+bypass|-exec\\\\s+bypass)") || + regexMatch("log.eventDataCommandLine", "(?i)(DownloadString|DownloadFile|DownloadData|WebClient|Invoke-WebRequest|wget|curl|Start-BitsTransfer)") || + regexMatch("log.eventDataCommandLine", "(?i)(Invoke-Expression|IEX|Invoke-Command)\\\\s*[\\\\(\\\\{]") || + regexMatch("log.eventDataCommandLine", "(?i)(Net\\\\.WebClient|IO\\\\.MemoryStream|IO\\\\.StreamReader|IO\\\\.Compression)") || + regexMatch("log.eventDataCommandLine", "(?i)(FromBase64String|ToBase64String|\\\\[Convert\\\\]::)") || + regexMatch("log.eventDataCommandLine", "(?i)-[eE][nN][cC]\\\\s+[A-Za-z0-9+/=]{50,}") || + regexMatch("log.eventDataCommandLine", "(?i)(-nop|-noni|-w\\\\s+hidden|-ep\\\\s+bypass|-exec\\\\s+bypass)") || + regexMatch("log.eventDataCommandLine", "(?i)(DownloadString|DownloadFile|DownloadData|WebClient|Invoke-WebRequest)") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + contains("log.eventDataScriptBlockText", "AmsiInitFailed") || + contains("log.eventDataScriptBlockText", "amsiContext") || + contains("log.eventDataScriptBlockText", "AmsiUtils") || + contains("log.eventDataScriptBlockText", "amsi.dll") || + regexMatch("log.eventDataScriptBlockText", "(?i)(Invoke-Obfuscation|Invoke-CradleCrafter|Out-EncodedCommand)") || + regexMatch("log.eventDataScriptBlockText", "(?i)(New-Object\\\\s+Net\\\\.Sockets\\\\.TCPClient|Net\\\\.WebClient\\\\)?\\\\.DownloadString)") || + regexMatch("log.eventDataScriptBlockText", "(?i)(\\\\$env:COMSPEC|cmd\\\\.exe.*/c)") || + regexMatch("log.eventDataScriptBlockText", "(?i)(Add-Type.*DllImport|\\\\[DllImport)") || + contains("log.eventDataScriptBlockText", "Reflection.Assembly") || + contains("log.eventDataScriptBlockText", "Invoke-Shellcode") || + contains("log.eventDataScriptBlockText", "AmsiInitFailed") || + contains("log.eventDataScriptBlockText", "AmsiUtils") || + regexMatch("log.eventDataScriptBlockText", "(?i)(Invoke-Obfuscation|Invoke-CradleCrafter|Out-EncodedCommand)") + ) +) +', '2026-03-02 13:26:53.057252', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{.origin.host}}"}],"or":null,"within":"now-10m","count":2}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1034, 'Keylogger Activity Detection', 3, 2, 0, 'Collection', 'T1056.001 - Input Capture: Keylogging', e'Detects keylogger activity through Windows API hook installation, clipboard monitoring, +keyboard input capture, and known keylogger tool execution. Attackers use keyloggers +to capture user credentials, sensitive data, and communications. + +Next Steps: +1. Immediately isolate the affected host to stop credential capture +2. Identify the source process installing keyboard hooks and its origin +3. Check if the hooking process is a known legitimate application +4. Review what user accounts have been active on the host during the capture period +5. Force password resets for all accounts used on the compromised system +6. Check for data exfiltration - keylog data being sent externally +7. Examine the process tree to find how the keylogger was installed +8. Scan for persistence mechanisms associated with the keylogger +9. Review MFA tokens and session cookies that may have been captured +', '["https://attack.mitre.org/techniques/T1056/001/","https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw","https://www.sans.org/reading-room/whitepapers/detection/detecting-keyloggers-36062"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataCommandLine", "(?i)(GetAsyncKeyState|GetKeyState|GetKeyboardState|SetWindowsHookEx|SetWindowsHookExW|SetWindowsHookExA)") || + regexMatch("log.eventDataCommandLine", "(?i)(keylog|key.?log|key.?stroke|key.?capture|keystroke.?log)") || + regexMatch("log.eventDataCommandLine", "(?i)(Get-Clipboard|Set-Clipboard|clipboard.?monitor|ClipboardChanged)") || + regexMatch("log.eventDataCommandLine", "(?i)(GetAsyncKeyState|GetKeyState|SetWindowsHookEx)") || + regexMatch("log.eventDataCommandLine", "(?i)(keylog|key.?log|key.?stroke|key.?capture)") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + contains("log.eventDataScriptBlockText", "GetAsyncKeyState") || + contains("log.eventDataScriptBlockText", "GetKeyboardState") || + contains("log.eventDataScriptBlockText", "SetWindowsHookEx") || + contains("log.eventDataScriptBlockText", "WH_KEYBOARD_LL") || + contains("log.eventDataScriptBlockText", "WH_KEYBOARD") || + contains("log.eventDataScriptBlockText", "MapVirtualKey") || + contains("log.eventDataScriptBlockText", "Get-Keystrokes") || + contains("log.eventDataScriptBlockText", "Invoke-Keylogger") || + ( + contains("log.eventDataScriptBlockText", "user32.dll") && + ( + contains("log.eventDataScriptBlockText", "GetAsyncKeyState") || + contains("log.eventDataScriptBlockText", "GetForegroundWindow") || + contains("log.eventDataScriptBlockText", "GetWindowText") + ) + ) || + ( + contains("log.eventDataScriptBlockText", "DllImport") && + contains("log.eventDataScriptBlockText", "user32") && + contains("log.eventDataScriptBlockText", "KeyState") + ) || + contains("log.eventDataScriptBlockText", "OpenClipboard") || + contains("log.eventDataScriptBlockText", "GetClipboardData") || + contains("log.eventDataScriptBlockText", "GetAsyncKeyState") || + contains("log.eventDataScriptBlockText", "SetWindowsHookEx") || + contains("log.eventDataScriptBlockText", "WH_KEYBOARD_LL") + ) +) || +( + equals("log.eventCode", "8") && + contains("log.eventDataStartModule", "user32.dll") && + contains("log.eventDataStartFunction", "SetWindowsHookEx") +) +', '2026-03-02 13:26:54.099883', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{.origin.host}}"}],"or":null,"within":"now-15m","count":2}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1035, 'Kerberoasting Attack Detection', 3, 2, 1, 'Credential Access', 'T1558.003 - Steal or Forge Kerberos Tickets: Kerberoasting', e'Detects Kerberoasting attacks where adversaries request Kerberos TGS tickets encrypted with RC4 (0x17) for +service accounts in order to crack them offline and obtain plaintext credentials. This is the most common +Active Directory credential theft technique used in real-world compromises. The rule monitors Event ID 4769 +(Kerberos Service Ticket Operations) for RC4 encryption requests while excluding machine accounts (ending in $) +and legitimate system services. + +Next Steps: +1. Identify the requesting user account and verify if this is authorized security testing +2. Check which service account SPNs were targeted for TGS requests +3. Review if the requesting account has been compromised +4. Audit all service accounts with SPNs for weak passwords +5. Consider implementing AES-only Kerberos encryption policies +6. Rotate passwords for targeted service accounts immediately +7. Enable Group Managed Service Accounts (gMSA) where possible +8. Monitor for follow-up lateral movement using obtained credentials +', '["https://attack.mitre.org/techniques/T1558/003/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4769","https://adsecurity.org/?p=2293"]', e'equals("log.eventCode", "4769") && +equals("log.channel", "Security") && +equals("log.eventDataTicketEncryptionType", "0x17") && +!regexMatch("log.eventDataServiceName", "(?i)\\\\$$") && +!equals("log.eventDataServiceName", "krbtgt") && +!oneOf("log.eventDataTicketOptions", ["0x40810000", "0x40800000", "0x40810010"]) && +exists("log.eventDataServiceName") +', '2026-03-02 13:26:55.292640', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{.log.eventDataIpAddress}}"}],"or":null,"within":"now-15m","count":3}]', '["lastEvent.log.eventDataIpAddress","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1036, 'DCSync Attack Detection', 3, 3, 1, 'Credential Access', 'T1003.006 - OS Credential Dumping: DCSync', e'Detects DCSync attacks where attackers use directory replication services to retrieve password hashes from domain controllers. +This technique exploits legitimate Active Directory replication functionality to extract credentials without directly accessing the domain controller\'s files. +The rule monitors for specific replication GUIDs associated with credential access operations in Windows Event ID 4662. + +Next Steps: +- Immediately verify the legitimacy of the user account performing the replication operation +- Check if the source host is an authorized domain controller or backup system +- Review recent privilege escalation activities for the identified user account +- Examine network traffic for additional signs of credential harvesting +- Consider resetting passwords for high-privilege accounts if compromise is confirmed +- Review domain controller access logs for unauthorized administrative activities +', '["https://attack.mitre.org/techniques/T1003/006/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4662","https://www.elastic.co/guide/en/security/current/potential-credential-access-via-dcsync.html"]', e'equals("log.eventCode", "4662") && +equals("log.channel", "Security") && +equals("log.eventDataObjectServer", "DS") && +( + contains("log.eventDataProperties", "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2") || + contains("log.eventDataProperties", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2") || + contains("log.eventDataProperties", "89e95b76-444d-4c62-991a-0facbeda640c") || + contains("log.eventDataProperties", "19195a5b-6da0-11d0-afd3-00c04fd930c9") +) && +!regexMatch("log.eventDataSubjectUserName", ".*\\\\$$") && +!contains("origin.host", "DC") +', '2026-03-02 13:26:56.474938', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataSubjectUserName","operator":"filter_term","value":"{{.log.eventDataSubjectUserName}}"}],"or":null,"within":"now-1h","count":1}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1037, 'DCShadow Attack Detection', 3, 3, 2, 'Defense Evasion', 'T1207 - Rogue Domain Controller', e'Detects DCShadow attacks where attackers register a rogue domain controller to push malicious Active Directory changes. This technique allows adversaries to modify Active Directory objects by registering a rogue domain controller and triggering replication, effectively bypassing security controls and detection mechanisms. + +The rule monitors for: +- Computer account modifications with domain controller service principal names +- Access to sensitive Active Directory objects and properties +- Creation of server objects in the domain controller configuration + +Next Steps: +1. Immediately investigate the source host and user account involved in the activity +2. Check if the host is an authorized domain controller in your environment +3. Review recent Active Directory changes and replication logs +4. Examine authentication logs for the affected user account +5. Verify the legitimacy of any recent domain controller promotions +6. Check for signs of compromise on the source system +7. Consider isolating the affected host if unauthorized activity is confirmed +8. Review domain controller security policies and access controls +', '["https://attack.mitre.org/techniques/T1207/","https://www.dcshadow.com/","https://blog.alsid.eu/dcshadow-explained-4510f52fc19d"]', e'( + (equals("log.eventCode", "4742") && + equals("log.channel", "Security") && + contains("log.eventDataServicePrincipalNames", "GC/") && + contains("log.eventDataUserAccountControl", "SERVER_TRUST_ACCOUNT")) || + (equals("log.eventCode", "4662") && + equals("log.channel", "Security") && + equals("log.eventDataObjectType", "{bf967a92-0de6-11d0-a285-00aa003049e2}") && + contains("log.eventDataProperties", "1131f6ac-9c07-11d1-f79f-00c04fc2dcd2")) || + (equals("log.eventCode", "5137") && + equals("log.channel", "Security") && + equals("log.eventDataObjectClass", "server") && + contains("log.eventDataObjectDN", "CN=Servers,CN=")) +) && +!contains("origin.host", "DC") +', '2026-03-02 13:26:57.632855', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{.origin.host}}"}],"or":null,"within":"now-2h","count":2}]', '["lastEvent.log.eventDataSubjectUserName","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1038, 'Golden Ticket Attack Detection', 3, 3, 3, 'Credential Access', 'T1558.001 - Steal or Forge Kerberos Tickets: Golden Ticket', e'Detects Golden Ticket attacks where adversaries forge Kerberos TGTs using the KRBTGT account +hash, granting unlimited domain access. The rule detects anomalous TGT usage patterns including +TGS requests with unusual encryption types, tickets with abnormally long lifetimes, and Kerberos +authentication from non-domain-controller sources for the KRBTGT service. + +Next Steps: +1. Immediately verify if the KRBTGT account password has been compromised +2. Reset the KRBTGT password TWICE to invalidate all existing tickets +3. Identify the source host and investigate for full domain compromise +4. Review all domain admin activity from the suspected timeframe +5. Check for DCSync or NTDS.dit extraction as precursor activities +6. Audit all privileged account access across the domain +7. Consider rebuilding the domain if compromise is confirmed +8. Implement Kerberos armoring and constrained delegation +', '["https://attack.mitre.org/techniques/T1558/001/","https://adsecurity.org/?p=1640","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4769"]', e'( + equals("log.eventCode", "4769") && + equals("log.channel", "Security") && + equals("log.eventDataServiceName", "krbtgt") && + !equals("log.eventDataStatus", "0x0") && + exists("log.eventDataIpAddress") +) || +( + equals("log.eventCode", "4768") && + equals("log.channel", "Security") && + !oneOf("log.eventDataTicketEncryptionType", ["0x12", "0x11"]) && + exists("target.user") && + !regexMatch("target.user", "(?i)\\\\$$") +) || +( + equals("log.eventCode", "4672") && + equals("log.channel", "Security") && + contains("log.eventDataPrivilegeList", "SeTcbPrivilege") && + !regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE)$") && + !regexMatch("log.eventDataSubjectUserName", "(?i)\\\\$$") +) +', '2026-03-02 13:26:58.868113', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{.origin.host}}"}],"or":null,"within":"now-30m","count":3}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1039, 'BloodHound Reconnaissance Activity', 3, 2, 1, 'Discovery', 'T1087 - Account Discovery', e'Detects potential BloodHound Active Directory reconnaissance tool usage through LDAP queries, characteristic patterns, and AD enumeration activities. BloodHound is commonly used by attackers to map Active Directory relationships and identify privilege escalation paths. + +Next Steps: +1. Investigate the source host and user account involved in the activity +2. Review network logs for LDAP queries to domain controllers around the same timeframe +3. Check for other reconnaissance tools or suspicious PowerShell activity on the same host +4. Examine Active Directory audit logs for unusual object access patterns +5. Verify if the user account has legitimate reasons for AD enumeration activities +6. Look for signs of lateral movement or privilege escalation following this reconnaissance +7. Consider isolating the affected host if malicious activity is confirmed +', '["https://attack.mitre.org/techniques/T1087/","https://bloodhound.readthedocs.io/","https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataNewProcessName", "(?i)bloodhound") || + regexMatch("log.eventDataNewProcessName", "(?i)sharphound") || + regexMatch("log.eventDataCommandLine", "(?i)(bloodhound|sharphound)") || + regexMatch("log.eventDataCommandLine", "(?i)--CollectionMethod\\\\s+(All|Session|LoggedOn)") || + regexMatch("log.eventDataCommandLine", "(?i)(DCOnly|ComputerOnly|LocalGroup)") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)invoke-bloodhound") || + contains("log.eventDataScriptBlockText", "Get-BloodHoundData") || + contains("log.eventDataScriptBlockText", "Get-NetSession") || + contains("log.eventDataScriptBlockText", "Get-NetLoggedOn") || + contains("log.eventDataScriptBlockText", "Get-DomainTrust") + ) +) || +( + equals("log.eventCode", "4662") && + regexMatch("log.eventDataObjectType", "(?i)(bf967aba-0de6-11d0-a285-00aa003049e2|bf967a9c-0de6-11d0-a285-00aa003049e2)") && + oneOf("log.eventDataAccessMask", ["0x100", "0x10000"]) +) || +( + equals("log.eventCode", "5156") && + equals("log.eventDataDestinationPort", "389") && + equals("log.eventDataDirection", "%%14592") +) +', '2026-03-02 13:27:00.135533', true, true, 'origin', '["adversary.host","lastEvent.log.eventDataSubjectUserName"]', '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{.origin.host}}"}],"or":null,"within":"now-2h","count":10}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1040, 'AS-REP Roasting Attack Detection', 3, 2, 1, 'Credential Access', 'T1558.004 - Steal or Forge Kerberos Tickets: AS-REP Roasting', e'Detects AS-REP Roasting attacks targeting accounts with Kerberos pre-authentication disabled. +Attackers request AS-REP messages encrypted with RC4 (0x17) for accounts that do not require +pre-authentication, enabling offline password cracking. This is a companion technique to Kerberoasting +and targets a different set of vulnerable accounts. + +Next Steps: +1. Identify accounts with pre-authentication disabled and evaluate business justification +2. Enable Kerberos pre-authentication on all identified accounts +3. Verify the requesting source IP is not a known attack tool +4. Reset passwords for targeted accounts using strong, complex passwords +5. Audit Active Directory for accounts with DONT_REQUIRE_PREAUTH flag +6. Monitor for subsequent credential usage from the requesting IP +', '["https://attack.mitre.org/techniques/T1558/004/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4768","https://blog.harmj0y.net/activedirectory/roasting-as-reps/"]', e'equals("log.eventCode", "4768") && +equals("log.channel", "Security") && +equals("log.eventDataTicketEncryptionType", "0x17") && +equals("log.eventDataPreAuthType", "0") && +!regexMatch("target.user", "(?i)\\\\$$") && +exists("target.user") +', '2026-03-02 13:27:01.581886', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{.log.eventDataIpAddress}}"}],"or":null,"within":"now-15m","count":3}]', '["lastEvent.log.eventDataIpAddress","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1041, 'ADFS Authentication Anomalies', 3, 2, 2, 'Defense Evasion, Persistence, Privilege Escalation, Initial Access', 'T1078 - Valid Accounts', e'Detects anomalous authentication attempts against Active Directory Federation Services (ADFS) including multiple failed attempts that could indicate password spraying or brute force attacks. This rule monitors for authentication failures, token validation failures, and other ADFS security events that may indicate malicious activity. + +Next Steps: +1. Review the source IP address and determine if it\'s from a known/trusted location +2. Check for patterns of failed authentication attempts across multiple users +3. Examine ADFS audit logs for additional context around the authentication failures +4. Verify if the targeted user accounts are valid and active +5. Consider implementing IP-based blocking if malicious activity is confirmed +6. Review ADFS configuration for security hardening opportunities +7. Correlate with other authentication events across the domain +', '["https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/troubleshooting/ad-fs-tshoot-logging","https://attack.mitre.org/techniques/T1078/"]', 'equals("log.providerName", "AD FS") && (equals("log.eventId", "411") || equals("log.eventId", "342") || equals("log.eventId", "516")) && contains("log.message", "token validation failed")', '2026-03-02 13:27:02.890185', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{.log.eventDataIpAddress}}"}],"or":null,"within":"now-10m","count":10}]', '["adversary.ip","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1042, 'WMI Remote Command Execution Detection', 3, 3, 2, 'Lateral Movement', 'T1047 - Windows Management Instrumentation', e'Detects WMI-based remote command execution using wmic.exe /node: parameter or PowerShell +Invoke-WmiMethod / Invoke-CimMethod for lateral movement. Attackers use WMI to execute +commands on remote systems without dropping files to disk, making it a stealthy lateral +movement technique that leverages built-in Windows functionality. + +Next Steps: +1. Identify the target host specified in the /node: parameter +2. Verify if this is authorized administrative WMI usage +3. Check what process or command was executed on the remote host +4. Review the credentials used for the WMI connection +5. Search for WMI execution evidence on the target host +6. Check for additional lateral movement from the same source +7. Monitor for process creation events on the target system +', '["https://attack.mitre.org/techniques/T1047/","https://www.fireeye.com/content/dam/fireeye-www/global/en/current-threats/pdfs/wp-windows-management-instrumentation.pdf","https://www.sans.org/blog/wmi-for-detection-and-response/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)wmic(.exe)?\\\\s+/node:") || + regexMatch("log.eventDataCommandLine", "(?i)wmic(.exe)?\\\\s+/node:")) || + (regexMatch("log.eventDataCommandLine", "(?i)Invoke-WmiMethod.*-ComputerName") || + regexMatch("log.eventDataCommandLine", "(?i)Invoke-WmiMethod.*-ComputerName")) || + (regexMatch("log.eventDataCommandLine", "(?i)Invoke-CimMethod.*-ComputerName") || + regexMatch("log.eventDataCommandLine", "(?i)Invoke-CimMethod.*-ComputerName")) || + (regexMatch("log.eventDataCommandLine", "(?i)Get-WmiObject.*-ComputerName.*Win32_Process") || + regexMatch("log.eventDataCommandLine", "(?i)Get-WmiObject.*-ComputerName.*Win32_Process")) || + (regexMatch("log.eventDataCommandLine", "(?i)wmic(.exe)?.*process\\\\s+call\\\\s+create") || + regexMatch("log.eventDataCommandLine", "(?i)wmic(.exe)?.*process\\\\s+call\\\\s+create")) +) +', '2026-03-02 13:27:04.246580', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1043, 'WMI Event Subscription Persistence Detection', 2, 3, 2, 'Persistence', 'T1546.003 - Event Triggered Execution: Windows Management Instrumentation Event Subscription', e'Detects creation of WMI event subscriptions used for stealthy persistence. Attackers create +WMI event filters and consumers that execute commands when specific system events occur. +This is a highly effective persistence mechanism because it survives reboots and is difficult +to detect without specific monitoring. The rule monitors for wmic.exe commands creating +event subscriptions and PowerShell WMI subscription creation patterns. + +Next Steps: +1. Enumerate all WMI event subscriptions using Get-WMIObject or wmic +2. Examine the event filter conditions and consumer actions +3. Identify the command or script executed by the consumer +4. Remove malicious WMI subscriptions immediately +5. Check for related persistence mechanisms on the same host +6. Review the timeline to identify the initial compromise +7. Search for similar WMI persistence across other endpoints +', '["https://attack.mitre.org/techniques/T1546/003/","https://www.fireeye.com/blog/threat-research/2016/08/wmi_vs_wmi_monitor.html","https://pentestlab.blog/2020/01/21/persistence-wmi-event-subscription/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataCommandLine", "(?i)wmic.*(/namespace|__EventFilter|__EventConsumer|__FilterToConsumerBinding)") || + regexMatch("log.eventDataCommandLine", "(?i)wmic.*(/namespace|__EventFilter|__EventConsumer|__FilterToConsumerBinding)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)(Set-WmiInstance|New-CimInstance).*(__EventFilter|__EventConsumer|CommandLineEventConsumer|ActiveScriptEventConsumer)") || + regexMatch("log.eventDataCommandLine", "(?i)(Set-WmiInstance|New-CimInstance).*(__EventFilter|__EventConsumer|CommandLineEventConsumer|ActiveScriptEventConsumer)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)Register-WmiEvent") || + regexMatch("log.eventDataCommandLine", "(?i)Register-WmiEvent") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)mofcomp\\\\.exe") || + regexMatch("log.eventDataCommandLine", "(?i)mofcomp\\\\.exe") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)(__EventFilter|__EventConsumer|__FilterToConsumerBinding)") || + regexMatch("log.eventDataScriptBlockText", "(?i)(CommandLineEventConsumer|ActiveScriptEventConsumer)") || + contains("log.eventDataScriptBlockText", "Register-WmiEvent") + ) +) +', '2026-03-02 13:27:05.511230', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1044, 'Windows Script Host Suspicious Execution', 3, 3, 2, 'Execution', 'T1059.005 - Command and Scripting Interpreter: Visual Basic', e'Detects suspicious execution of wscript.exe and cscript.exe with potentially malicious script +files or command-line arguments. Attackers commonly use Windows Script Host to execute VBScript +and JScript payloads delivered via phishing emails, drive-by downloads, or as secondary +payloads during an intrusion. The rule focuses on scripts executed from suspicious locations +and with suspicious arguments. + +Next Steps: +1. Identify the script file being executed and analyze its contents +2. Check the parent process (e.g., Outlook, Word, browser) for phishing indicators +3. Review the script file location for legitimacy +4. Analyze any network connections made by the script +5. Check if the script downloads or executes additional payloads +6. Search for the same script across other endpoints +7. Block the script and remove any persistence mechanisms it created +', '["https://attack.mitre.org/techniques/T1059/005/","https://lolbas-project.github.io/lolbas/Binaries/Wscript/","https://lolbas-project.github.io/lolbas/Binaries/Cscript/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\(wscript|cscript)\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)\\\\\\\\(wscript|cscript)\\\\.exe$") +) && +( + (regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)")) || + (regexMatch("log.eventDataCommandLine", "(?i)//[eE]:(vbscript|jscript)") || + regexMatch("log.eventDataCommandLine", "(?i)//[eE]:(vbscript|jscript)")) || + (regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://|\\\\\\\\\\\\\\\\[0-9])") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://|\\\\\\\\\\\\\\\\[0-9])")) || + (regexMatch("log.eventDataCommandLine", "(?i)\\\\.(vbs|vbe|js|jse|wsf|wsh)\\\\s+(//B|//nologo)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\.(vbs|vbe|js|jse|wsf|wsh)\\\\s+(//B|//nologo)")) || + (regexMatch("log.eventDataCommandLine", "(?i)(CreateObject|WScript\\\\.Shell|Scripting\\\\.FileSystemObject|ADODB\\\\.Stream)") || + regexMatch("log.eventDataCommandLine", "(?i)(CreateObject|WScript\\\\.Shell|Scripting\\\\.FileSystemObject|ADODB\\\\.Stream)")) +) +', '2026-03-02 13:27:06.869053', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1045, 'Windows Remote Management (WinRM) Abuse', 3, 3, 2, 'Lateral Movement', 'T1021.006 - Remote Services: Windows Remote Management', e'Detects potential abuse of Windows Remote Management (WinRM) for lateral movement. Monitors for successful logon events (4624) with network logon type 3 combined with privilege escalation (4672) and WinRM-related process activity, indicating remote command execution via WinRM. + +Next Steps: +1. Investigate the source IP address and verify if it\'s an authorized administrative workstation +2. Review the target user account for any signs of compromise or unusual privilege usage +3. Examine recent PowerShell execution logs and command history on the target system +4. Check for concurrent suspicious activities from the same source IP across other systems +5. Verify if the WinRM connection aligns with scheduled maintenance or authorized administrative tasks +6. Review network traffic patterns between source and target systems for data exfiltration indicators +7. Validate the legitimacy of any processes spawned through the WinRM session +8. Consider implementing additional monitoring for WinRM usage if this represents unexpected activity +', '["https://jpcertcc.github.io/ToolAnalysisResultSheet/details/WinRM.htm","https://attack.mitre.org/techniques/T1021/006/"]', 'equals("log.eventCode", "4624") && equals("log.eventDataLogonType", "3") && exists("log.eventDataProcessName") && (contains("log.eventDataProcessName", "wsmprovhost.exe") || contains("log.eventDataProcessName", "winrshost.exe") || contains("log.eventDataProcessName", "powershell.exe"))', '2026-03-02 13:27:08.173569', true, true, 'origin', null, '[]', '["lastEvent.target.user","adversary.host","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1046, 'Windows Defender Tampering Detection', 2, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects attempts to disable or tamper with Windows Defender components through registry modifications, service stops, or exclusion additions. This includes registry changes to disable antivirus features, stopping Windows Defender services, or using PowerShell commands to modify protection settings. + +Next Steps: +1. Investigate the user account and process responsible for the tampering attempt +2. Check if this is part of authorized maintenance or if it\'s malicious activity +3. Review recent logon events and process execution history on the affected host +4. Verify Windows Defender configuration and ensure it\'s properly restored +5. Scan the system for malware that may have disabled protection +6. Check for other security tool tampering attempts across the environment +', '["https://attack.mitre.org/techniques/T1562/001/","https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-antivirus/troubleshoot-windows-defender-antivirus"]', e'(equals("log.eventId", "4657") && + contains("log.eventDataObjectName", "\\\\Windows Defender\\\\") && + (contains("log.eventDataObjectValueName", "DisableAntiSpyware") || + contains("log.eventDataObjectValueName", "DisableAntiVirus") || + contains("log.eventDataObjectValueName", "DisableRealtimeMonitoring"))) || +(equals("log.eventId", "7040") && + contains("log.eventDataServiceName", "WinDefend") && + equals("log.eventDataNewState", "disabled")) || +(equals("log.eventId", "4688") && + contains("log.eventDataCommandLine", "Set-MpPreference") && + (contains("log.eventDataCommandLine", "DisableRealtimeMonitoring") || + contains("log.eventDataCommandLine", "DisableIOAVProtection") || + contains("log.eventDataCommandLine", "DisableBehaviorMonitoring"))) +', '2026-03-02 13:27:09.488351', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1047, 'Vulnerable Driver Loading Detection (BYOVD)', 3, 3, 3, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', e'Detects Bring Your Own Vulnerable Driver (BYOVD) attacks where adversaries load known vulnerable +kernel drivers to disable security tools, escalate privileges, or gain kernel-level access. +This technique is increasingly used by ransomware groups and APTs to bypass endpoint protection. +The rule monitors for service installations loading known vulnerable drivers and suspicious +driver-related command patterns. + +Next Steps: +1. Identify the loaded driver and check against known vulnerable driver lists (loldrivers.io) +2. Examine the service installation that loaded the driver +3. Check if endpoint security tools were disabled after driver loading +4. Review the process that installed the vulnerable driver +5. Remove the vulnerable driver and restore security tools +6. Block the driver hash via WDAC or application control policies +7. Check for privilege escalation or security tool tampering after driver load +8. Search for the same driver across other endpoints +', '["https://attack.mitre.org/techniques/T1068/","https://www.loldrivers.io/","https://github.com/magicsword-io/LOLDrivers"]', e'( + equals("log.eventCode", "4697") && + equals("log.channel", "Security") && + ( + regexMatch("log.eventDataServiceFileName", "(?i)(dbutil_2_3|dbutildrv2|rtcore64|gdrv|asio|ene\\\\.sys|procexp|viragt64|aswarpot|hw64|cpuz|gmer64|mhyprot2|kdmapper|iqvw64e|echo_driver|winio|amifldrv64|elby|vboxdrv|gpu_temp|nicm|phymemx64|micio64|directio64|blacklotus|irec|zemana)") || + regexMatch("log.eventDataServiceFileName", "(?i)\\\\.sys\\\\s*$") + ) +) || +( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataCommandLine", "(?i)(sc\\\\.exe|sc)\\\\s+(create|start)\\\\s+.*type=\\\\s*kernel") || + regexMatch("log.eventDataCommandLine", "(?i)(sc\\\\.exe|sc)\\\\s+(create|start)\\\\s+.*type=\\\\s*kernel") || + regexMatch("log.eventDataCommandLine", "(?i)(sc\\\\.exe|sc)\\\\s+create\\\\s+.*binPath=.*\\\\.sys") || + regexMatch("log.eventDataCommandLine", "(?i)(sc\\\\.exe|sc)\\\\s+create\\\\s+.*binPath=.*\\\\.sys") + ) +) +', '2026-03-02 13:27:10.752364', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1048, 'Volume Shadow Copy Deletion', 2, 3, 3, 'Impact', 'T1490 - Inhibit System Recovery', e'Detects deletion of Volume Shadow Copies which is commonly performed by ransomware to prevent recovery of encrypted files. This rule identifies various methods attackers use to delete shadow copies including vssadmin, wmic, PowerShell, wbadmin, and bcdedit commands. + +Next Steps: +1. Immediately investigate the affected host for signs of ransomware activity +2. Check for recent file encryption or unusual file modifications +3. Review process execution timeline around the shadow copy deletion +4. Verify if this was legitimate administrative activity or malicious +5. Examine network connections and lateral movement indicators +6. Check backup integrity and consider isolating the system if ransomware is confirmed +7. Review user accounts and privileges involved in the activity +', '["https://attack.mitre.org/techniques/T1490/","https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/vssadmin"]', e'equals("log.eventId", "4688") && +((endsWith("log.eventDataProcessName", "vssadmin.exe") && + (contains("log.eventDataCommandLine", "delete shadows") || + (contains("log.eventDataCommandLine", "resize shadowstorage") && + contains("log.eventDataCommandLine", "maxsize=")))) || + (endsWith("log.eventDataProcessName", "wmic.exe") && + contains("log.eventDataCommandLine", "shadowcopy") && + contains("log.eventDataCommandLine", "delete")) || + (endsWith("log.eventDataProcessName", "powershell.exe") && + (contains("log.eventDataCommandLine", "Get-WmiObject") || + contains("log.eventDataCommandLine", "gwmi")) && + contains("log.eventDataCommandLine", "Win32_ShadowCopy") && + contains("log.eventDataCommandLine", "Delete()")) || + (endsWith("log.eventDataProcessName", "wbadmin.exe") && + contains("log.eventDataCommandLine", "delete") && + (contains("log.eventDataCommandLine", "catalog") || + contains("log.eventDataCommandLine", "backup"))) || + (endsWith("log.eventDataProcessName", "bcdedit.exe") && + contains("log.eventDataCommandLine", "recoveryenabled") && + contains("log.eventDataCommandLine", "no"))) +', '2026-03-02 13:27:12.068201', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataProcessName","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1049, 'UAC Bypass Attempt Detection', 3, 3, 2, 'Privilege Escalation, Defense Evasion', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', e'Detects potential UAC bypass attempts by monitoring for processes with elevated privileges that were not launched through the proper UAC consent mechanism. This rule identifies processes running with elevated token types (TokenElevationTypeVirtual) without proper UAC consent flow, which may indicate an attempt to bypass User Account Control security measures. + +Next Steps: +1. Investigate the process that triggered the alert - examine the process name, command line arguments, and parent process +2. Check if the process is known legitimate software that should have elevated privileges +3. Verify the user account that launched the process and their normal privilege level +4. Look for other suspicious activities on the same host around the same time +5. Check for known UAC bypass techniques such as DLL hijacking, registry manipulation, or abuse of auto-elevated processes +6. Examine the process execution chain to identify the attack vector +7. Consider isolating the affected system if malicious activity is confirmed +8. Review security policies and UAC settings to prevent similar bypasses +', '["https://attack.mitre.org/techniques/T1548/002/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=4688"]', e'equals("log.eventId", "4688") && +equals("log.eventDataTokenElevationType", "2") && +!contains("log.eventDataParentProcessName", "consent.exe") && +!contains("log.eventDataProcessName", "TrustedInstaller.exe") && +!contains("log.eventDataSubjectUserName", "SYSTEM") && +(contains("log.eventDataParentProcessName", "fodhelper.exe") || + contains("log.eventDataParentProcessName", "computerdefaults.exe") || + contains("log.eventDataParentProcessName", "sdclt.exe") || + contains("log.eventDataParentProcessName", "slui.exe") || + contains("log.eventDataParentProcessName", "eventvwr.exe") || + contains("log.eventDataParentProcessName", "cmstp.exe") || + contains("log.eventDataParentProcessName", "msconfig.exe") || + contains("log.eventDataParentProcessName", "dccw.exe") || + (contains("log.eventDataProcessName", "cmd.exe") || contains("log.eventDataProcessName", "powershell.exe") || contains("log.eventDataProcessName", "pwsh.exe"))) +', '2026-03-02 13:27:13.329683', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1050, 'Tunneling Service C2 Detection', 3, 2, 2, 'Command and Control', 'T1572 - Protocol Tunneling', e'Detects execution of tunneling services (ngrok, cloudflared, devtunnels, localtonet, bore, chisel) +that create reverse tunnels for command and control or to expose internal services to the internet. +These tools are increasingly abused by threat actors for persistent C2 channels that bypass +network security controls because traffic appears as legitimate HTTPS connections. + +Next Steps: +1. Identify the tunneling tool being used and its configuration +2. Determine what internal service or port is being exposed +3. Check if this is authorized usage (dev/test) or malicious +4. Review the tunnel endpoint for C2 activity +5. Block the tunneling tool and its associated domains at the firewall +6. Terminate the tunnel process and remove the tool +7. Check for data exfiltration through the tunnel +8. Hunt for the same tunneling tool across other endpoints +', '["https://attack.mitre.org/techniques/T1572/","https://www.microsoft.com/en-us/security/blog/2023/05/24/volt-typhoon-targets-us-critical-infrastructure-with-living-off-the-land-techniques/","https://ngrok.com/abuse"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)(ngrok|cloudflared|devtunnels|localtonet|bore|chisel|frpc|rathole)\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)(ngrok|cloudflared|devtunnels|localtonet|bore|chisel|frpc|rathole)\\\\.exe$") || + regexMatch("log.eventDataCommandLine", "(?i)ngrok\\\\s+(http|tcp|tls|start)") || + regexMatch("log.eventDataCommandLine", "(?i)ngrok\\\\s+(http|tcp|tls|start)") || + regexMatch("log.eventDataCommandLine", "(?i)cloudflared\\\\s+tunnel\\\\s+(run|--url)") || + regexMatch("log.eventDataCommandLine", "(?i)cloudflared\\\\s+tunnel\\\\s+(run|--url)") || + regexMatch("log.eventDataCommandLine", "(?i)devtunnels\\\\s+(host|create|port)") || + regexMatch("log.eventDataCommandLine", "(?i)devtunnels\\\\s+(host|create|port)") || + regexMatch("log.eventDataCommandLine", "(?i)chisel\\\\s+(client|server)") || + regexMatch("log.eventDataCommandLine", "(?i)chisel\\\\s+(client|server)") || + regexMatch("log.eventDataCommandLine", "(?i)frpc\\\\.exe.*-c") || + regexMatch("log.eventDataCommandLine", "(?i)frpc\\\\.exe.*-c") +) +', '2026-03-02 13:27:14.689098', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1051, 'File Timestomping Detection', 1, 3, 1, 'Defense Evasion', 'T1070.006 - Indicator Removal: Timestomp', e'Detects timestomping activities where attackers modify file creation or modification timestamps +to blend malicious files with legitimate system files. Common tools include PowerShell +Set-ItemProperty, timestomp.exe, NirCmd, and direct API calls to SetFileTime. This technique +is used to evade forensic timeline analysis and make malicious files appear older. + +Next Steps: +1. Identify which files had their timestamps modified +2. Check if the modified timestamps match legitimate system file dates (common pattern) +3. Analyze the files with modified timestamps for malicious content +4. Review the process and user that performed the timestamp modification +5. Investigate what the attacker was trying to hide with timestomping +6. Use $MFT analysis to detect original timestamps vs modified ones +7. Search for additional anti-forensic techniques on the same host +', '["https://attack.mitre.org/techniques/T1070/006/","https://www.inversecos.com/2022/04/defence-evasion-technique-timestomping.html","https://andreafortuna.org/2017/10/06/timestomping-what-it-is-and-how-to-detect-it/"]', e'( + equals("log.eventCode", "2") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + exists("log.eventDataPreviousCreationUtcTime") && + exists("log.eventDataCreationUtcTime") && + !regexMatch("log.eventDataImage", "(?i)(setup|install|update|patch|msiexec|TiWorker|TrustedInstaller|svchost)\\\\.exe$") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)Set-ItemProperty.*CreationTime|Set-ItemProperty.*LastWriteTime|Set-ItemProperty.*LastAccessTime") || + regexMatch("log.eventDataCommandLine", "(?i)Set-ItemProperty.*CreationTime|Set-ItemProperty.*LastWriteTime|Set-ItemProperty.*LastAccessTime") || + regexMatch("log.eventDataCommandLine", "(?i)timestomp|SetFileTime|NirCmd.*setfiletime") || + regexMatch("log.eventDataCommandLine", "(?i)timestomp|SetFileTime|NirCmd.*setfiletime") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\[IO\\\\.File\\\\]::SetCreationTime|\\\\[IO\\\\.File\\\\]::SetLastWriteTime") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\[IO\\\\.File\\\\]::SetCreationTime|\\\\[IO\\\\.File\\\\]::SetLastWriteTime") + ) +) +', '2026-03-02 13:27:15.999710', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1052, 'Sysmon Service Tampering Detection', 3, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects attempts to tamper with the Sysmon monitoring service including unloading the Sysmon +driver, stopping or deleting the Sysmon service, modifying Sysmon configuration, or renaming +the Sysmon binary. Attackers target Sysmon specifically because it provides rich endpoint +telemetry that enables threat detection. Disabling Sysmon is a critical defense evasion step. + +Next Steps: +1. Immediately investigate the process and user that tampered with Sysmon +2. Restore Sysmon service and verify it is running correctly +3. Check if the Sysmon driver is still loaded in the kernel +4. Review what malicious activity occurred during the Sysmon outage +5. Re-apply the Sysmon configuration from a known good backup +6. Investigate the full attack chain that led to Sysmon tampering +7. Consider implementing tamper protection for Sysmon +', '["https://attack.mitre.org/techniques/T1562/001/","https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon","https://undev.ninja/sysmon-evasion-techniques/"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + (regexMatch("log.eventDataCommandLine", "(?i)sysmon.*-u") || + regexMatch("log.eventDataCommandLine", "(?i)sysmon.*-u")) || + (regexMatch("log.eventDataCommandLine", "(?i)sc(.exe)?\\\\s+(stop|delete|config)\\\\s+sysmon") || + regexMatch("log.eventDataCommandLine", "(?i)sc(.exe)?\\\\s+(stop|delete|config)\\\\s+sysmon")) || + (regexMatch("log.eventDataCommandLine", "(?i)net(.exe)?\\\\s+stop\\\\s+sysmon") || + regexMatch("log.eventDataCommandLine", "(?i)net(.exe)?\\\\s+stop\\\\s+sysmon")) || + (regexMatch("log.eventDataCommandLine", "(?i)fltMC(.exe)?\\\\s+unload\\\\s+sysmon") || + regexMatch("log.eventDataCommandLine", "(?i)fltMC(.exe)?\\\\s+unload\\\\s+sysmon")) || + (regexMatch("log.eventDataCommandLine", "(?i)taskkill.*/f.*/im\\\\s+sysmon") || + regexMatch("log.eventDataCommandLine", "(?i)taskkill.*/f.*/im\\\\s+sysmon")) + ) +) || +( + equals("log.eventCode", "7045") && + regexMatch("log.eventDataServiceName", "(?i)sysmon") && + !equals("log.eventDataServiceType", "kernel mode driver") +) +', '2026-03-02 13:27:17.398400', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1053, 'Suspicious Service Installation Detection', 3, 3, 2, 'Persistence', 'T1543.003 - Create or Modify System Process: Windows Service', e'Detects installation of suspicious Windows services matching patterns from Cobalt Strike, Metasploit, +Impacket PSExec, and Meterpreter payloads. Attackers commonly install malicious services for persistence, +privilege escalation (getsystem), and lateral movement. The rule monitors Event ID 4697 for service +installations with characteristic names, binary paths, or command-line patterns used by offensive frameworks. + +Next Steps: +1. Examine the service binary path for malicious executables or scripts +2. Check if the service name matches known offensive tool patterns +3. Verify the installing user account and their authorization level +4. Review the service configuration for suspicious startup types +5. Stop and disable the suspicious service immediately +6. Analyze the service binary in a sandbox environment +7. Search for lateral movement indicators from the source host +8. Check for other persistence mechanisms installed by the same actor +', '["https://attack.mitre.org/techniques/T1543/003/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4697","https://www.sans.org/blog/red-team-tactics-hiding-windows-services/"]', e'equals("log.eventCode", "4697") && +equals("log.channel", "Security") && +( + regexMatch("log.eventDataServiceFileName", "(?i)(cmd\\\\.exe\\\\s*/c|powershell|pwsh|%COMSPEC%)") || + regexMatch("log.eventDataServiceFileName", "(?i)(\\\\\\\\ADMIN\\\\$|\\\\\\\\C\\\\$|127\\\\.0\\\\.0\\\\.1)") || + regexMatch("log.eventDataServiceFileName", "(?i)(rundll32|msiexec|regsvr32|mshta|certutil|bitsadmin)") || + regexMatch("log.eventDataServiceName", "(?i)^[a-zA-Z]{4,8}$") || + regexMatch("log.eventDataServiceFileName", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataServiceFileName", "(?i)\\\\.exe\\\\s+[a-zA-Z0-9+/=]{50,}") || + regexMatch("log.eventDataServiceName", "(?i)(BTOBTO|meterpreter|msf|payload|beacon|cobalt)") || + regexMatch("log.eventDataServiceFileName", "(?i)(comsvcs\\\\.dll|MiniDump|procdump|lsass)") +) +', '2026-03-02 13:27:18.887636', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1054, 'Startup Folder Persistence Detection', 2, 3, 2, 'Persistence', 'T1547.001 - Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder', e'Detects file creation or modification in Windows Startup folders, which execute programs +automatically on user login. Attackers drop malicious scripts, executables, or shortcut files +into Startup folders for persistence. The rule monitors both per-user and system-wide Startup +directories for suspicious file types. + +Next Steps: +1. Identify the file dropped into the Startup folder and analyze its contents +2. Check the parent process that created the file for suspicious origins +3. Verify if the file is a legitimate application shortcut or a malicious payload +4. Remove the suspicious file from the Startup folder +5. Analyze the executable or script for malicious behavior in a sandbox +6. Search for additional persistence mechanisms on the same host +7. Investigate the initial access vector +', '["https://attack.mitre.org/techniques/T1547/001/","https://docs.microsoft.com/en-us/windows/win32/shell/manage-program-startup","https://www.cybereason.com/blog/persistence-techniques-that-persist"]', e'( + equals("log.eventCode", "11") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + ( + regexMatch("log.eventDataTargetFilename", "(?i)\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup\\\\\\\\") || + regexMatch("log.eventDataTargetFilename", "(?i)\\\\\\\\ProgramData\\\\\\\\Microsoft\\\\\\\\Windows\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\StartUp\\\\\\\\") + ) && + regexMatch("log.eventDataTargetFilename", "(?i)\\\\.(exe|dll|bat|cmd|vbs|vbe|js|jse|wsf|wsh|ps1|hta|lnk|scr|pif)$") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + (regexMatch("log.eventDataCommandLine", "(?i)copy.*\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup") || + regexMatch("log.eventDataCommandLine", "(?i)copy.*\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup")) || + (regexMatch("log.eventDataCommandLine", "(?i)move.*\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup") || + regexMatch("log.eventDataCommandLine", "(?i)move.*\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup")) + ) +) +', '2026-03-02 13:27:20.019454', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1055, 'SMBv1 Usage Detection', 3, 2, 2, 'Lateral Movement', 'T1210 - Exploitation of Remote Services', e'Detects usage of the deprecated and vulnerable SMBv1 protocol which could be exploited for lateral movement or ransomware propagation. SMBv1 is susceptible to numerous security vulnerabilities including EternalBlue and should be disabled in favor of SMBv2/SMBv3. + +Next Steps: +1. Immediately investigate the source system using SMBv1 and identify which service or application is still dependent on this protocol +2. Review network traffic logs to determine if this is internal communication or external access attempts +3. Check for any signs of exploitation attempts or successful compromises on the affected system +4. Identify all systems in the environment that may still have SMBv1 enabled +5. Plan migration to SMBv2/SMBv3 and disable SMBv1 on all systems where possible +6. Monitor for any lateral movement patterns that may indicate ongoing compromise +7. Consider implementing network segmentation to limit exposure if SMBv1 cannot be immediately disabled +', '["https://learn.microsoft.com/en-us/windows-server/storage/file-server/troubleshoot/detect-enable-and-disable-smbv1-v2-v3","https://attack.mitre.org/techniques/T1210/"]', 'equals("log.eventId", "3000") && equals("log.providerName", "Microsoft-Windows-SMBServer") && contains("log.message", "SMB1")', '2026-03-02 13:27:21.193572', true, true, 'origin', null, '[]', '["adversary.host","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1056, 'Sliver C2 Framework Detection', 3, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', e'Detects Sliver C2 framework execution patterns and artifacts. Sliver is an increasingly popular +open-source C2 framework replacing Cobalt Strike in many threat actor toolkits. The rule monitors +for characteristic Sliver implant patterns including specific PowerShell stager patterns, +named pipe names, and executable naming conventions. + +Next Steps: +1. Identify the Sliver implant type (HTTP, HTTPS, mTLS, DNS, WireGuard) +2. Check for network beaconing to C2 infrastructure +3. Review the process tree for injection and post-exploitation activity +4. Memory scan affected processes for Sliver implant shellcode +5. Isolate the compromised host immediately +6. Block identified C2 domains and IPs at the network perimeter +7. Hunt for Sliver artifacts across all endpoints +8. Review initial access vector (phishing, exploitation, etc.) +', '["https://github.com/BishopFox/sliver","https://www.microsoft.com/en-us/security/blog/2022/08/24/looking-for-the-sliver-lining-hunting-for-emerging-command-and-control-frameworks/","https://www.cybereason.com/blog/sliver-c2-leveraged-by-many-threat-actors"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataCommandLine", "(?i)(sliver|sliverpb|silver-implant)") || + regexMatch("log.eventDataCommandLine", "(?i)(sliver|sliverpb|silver-implant)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\\\\\\\\\.\\\\\\\\pipe\\\\\\\\(sliverpb|sliver-)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\\\\\\\\\.\\\\\\\\pipe\\\\\\\\(sliverpb|sliver-)") || + regexMatch("log.eventDataNewProcessName", "(?i)(DECISIVE_|MANAGING_|IMPRESSED_|LITERARY_|ENORMOUS_)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\$s=New-Object\\\\s+IO\\\\.MemoryStream.*FromBase64String.*IO\\\\.Compression\\\\.GzipStream.*IO\\\\.Compression\\\\.CompressionMode.*ReadToEnd") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\$s=New-Object\\\\s+IO\\\\.MemoryStream.*FromBase64String.*IO\\\\.Compression\\\\.GzipStream") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\$s=New-Object\\\\s+IO\\\\.MemoryStream.*IO\\\\.Compression\\\\.GzipStream.*IO\\\\.Compression\\\\.CompressionMode.*ReadToEnd") || + contains("log.eventDataScriptBlockText", "sliverpb") || + regexMatch("log.eventDataScriptBlockText", "(?i)New-Object\\\\s+System\\\\.Net\\\\.Sockets\\\\.TCPClient.*IO\\\\.StreamReader.*IO\\\\.StreamWriter.*GetStream") + ) +) +', '2026-03-02 13:27:22.423356', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1057, 'SID History Injection Attempt', 3, 3, 1, 'Defense Evasion, Privilege Escalation', 'T1134.005 - Access Token Manipulation: SID-History Injection', e'Detects attempts to add SID History to an account, which can be used for privilege escalation. SID History injection allows attackers to inherit permissions from privileged accounts without being members of privileged groups. Both successful (4765) and failed (4766) attempts are monitored. + +Next Steps: +1. Immediately investigate the target user account and verify if SID History modification was legitimate +2. Check if the user performing the action has proper administrative privileges for this operation +3. Review the source SID being added to understand what permissions are being inherited +4. Examine recent authentication logs for the target account to identify potential unauthorized access +5. Verify Active Directory configuration and check for signs of domain controller compromise +6. Consider resetting the target account password and removing unauthorized SID History entries +7. Review domain administrator accounts and privileged group memberships for anomalies +', '["https://attack.mitre.org/techniques/T1134/005/","https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4765","https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4766","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=4765"]', e'oneOf("log.eventId", ["4765", "4766"]) && +equals("log.channel", "Security") +', '2026-03-02 13:27:23.721333', true, true, 'origin', null, '[]', '["adversary.user","target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1058, 'Shadow Credentials Attack Detection', 3, 3, 1, 'Credential Access', 'T1556 - Modify Authentication Process', e'Detects Shadow Credentials attacks where adversaries modify the msDS-KeyCredentialLink attribute +on Active Directory objects to add their own key credentials for passwordless authentication. +This enables attackers to authenticate as the target account without knowing the password, +effectively taking over the account. The rule monitors Event ID 5136 (directory service object +modification) and Event ID 4662 (object access) for msDS-KeyCredentialLink changes. + +Next Steps: +1. Identify the account whose msDS-KeyCredentialLink was modified +2. Check if the modification was done by an authorized admin or service +3. Examine the added key credential for malicious certificates +4. Remove unauthorized key credentials from the affected account +5. Reset the password for the affected account +6. Review who has write access to the msDS-KeyCredentialLink attribute +7. Audit all accounts for unauthorized key credential additions +8. Investigate for related tools like Whisker or pyWhisker +', '["https://attack.mitre.org/techniques/T1556/","https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab","https://github.com/eladshamir/Whisker"]', e'( + equals("log.eventCode", "5136") && + equals("log.channel", "Security") && + contains("log.eventDataAttributeLDAPDisplayName", "msDS-KeyCredentialLink") +) || +( + equals("log.eventCode", "4662") && + equals("log.channel", "Security") && + contains("log.eventDataProperties", "msDS-KeyCredentialLink") && + !regexMatch("log.eventDataSubjectUserName", "(?i)\\\\$$") +) +', '2026-03-02 13:27:24.871464', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1059, 'Suspicious Scheduled Task Persistence', 2, 3, 2, 'Persistence', 'T1053.005 - Scheduled Task/Job: Scheduled Task', e'Detects creation of scheduled tasks from suspicious locations or executing suspicious binaries, +which is one of the most common persistence mechanisms used by attackers. The rule monitors +Event ID 4698 (scheduled task creation) for tasks that reference suspicious paths such as +Temp, Downloads, AppData, or ProgramData directories, or execute known LOLBINs like +PowerShell, certutil, rundll32, mshta, regsvr32, or cmd.exe with suspicious arguments. + +Next Steps: +1. Examine the full TaskContent XML to understand what the scheduled task executes +2. Identify the user account that created the task and verify authorization +3. Check the task execution path for malicious payloads or scripts +4. Review the trigger schedule for persistence timing patterns +5. Remove the suspicious scheduled task immediately +6. Search for additional persistence mechanisms on the same host +7. Investigate the initial access vector that led to task creation +', '["https://attack.mitre.org/techniques/T1053/005/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4698","https://posts.specterops.io/abstracting-scheduled-task-creation-6d22febc27e7"]', e'equals("log.eventCode", "4698") && +equals("log.channel", "Security") && +( + regexMatch("log.eventDataTaskContent", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Public\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataTaskContent", "(?i)(powershell|pwsh|cmd\\\\.exe|certutil|mshta|rundll32|regsvr32|cscript|wscript|msiexec|bitsadmin)") || + regexMatch("log.eventDataTaskContent", "(?i)(-enc\\\\s|-encodedcommand|-nop|-w\\\\s+hidden|downloadstring|invoke-expression|iex)") || + regexMatch("log.eventDataTaskContent", "(?i)(http://|https://|ftp://|\\\\\\\\\\\\\\\\[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\\\\\)") +) && +!regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE)$") +', '2026-03-02 13:27:26.262107', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1060, 'SAM Database Access Attempt', 3, 3, 1, 'Credential Access', 'T1003.002 - OS Credential Dumping: Security Account Manager', e'Detects attempts to access the Security Account Manager (SAM) database, which contains local user account hashes. This activity may indicate credential dumping attempts by attackers trying to extract password hashes for offline cracking or lateral movement. + +Next Steps: +1. Immediately investigate the user account and process that accessed the SAM database +2. Check for any unusual processes running on the affected system +3. Review recent logon events and privilege escalation activities +4. Examine network connections from the affected host for lateral movement +5. Consider isolating the affected system if malicious activity is confirmed +6. Review security policies for SAM database access permissions +7. Check for presence of credential dumping tools or suspicious files +', '["https://attack.mitre.org/techniques/T1003/002/","https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4661"]', e'equals("log.eventCode", "4663") && +equals("log.channel", "Security") && +( + endsWith("log.eventDataObjectName", "\\\\SAM") || + endsWith("log.eventDataObjectName", "\\\\SECURITY") || + endsWith("log.eventDataObjectName", "\\\\SYSTEM") +) && +oneOf("log.eventDataAccessMask", ["0x20019", "0x1f01ff", "0x40", "0x20", "0x1"]) +', '2026-03-02 13:27:27.362742', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1061, 'Rundll32 Suspicious Abuse Detection', 2, 3, 2, 'Defense Evasion', 'T1218.011 - System Binary Proxy Execution: Rundll32', e'Detects suspicious rundll32.exe usage patterns including loading DLLs from non-system paths, +comsvcs.dll MiniDump for LSASS dumping, JavaScript/VBScript execution, and loading from Temp +or Downloads directories. Rundll32 is a signed Microsoft binary frequently abused for defense +evasion and code execution. + +Next Steps: +1. Examine the DLL path and export function being called +2. Verify the DLL is not loading from a suspicious location (Temp, Downloads, AppData) +3. Check the parent process for delivery mechanism indicators +4. Analyze the loaded DLL in a sandbox environment +5. Review network connections made by the rundll32 process +6. Check for process injection from the rundll32 process +7. Search for the same DLL hash across other endpoints +', '["https://attack.mitre.org/techniques/T1218/011/","https://lolbas-project.github.io/lolbas/Binaries/Rundll32/","https://redcanary.com/threat-detection-report/techniques/rundll32/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)rundll32\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)rundll32\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)comsvcs\\\\.dll.*(MiniDump|#24)") || + regexMatch("log.eventDataCommandLine", "(?i)(javascript:|vbscript:)") || + regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\).*\\\\.dll") || + regexMatch("log.eventDataCommandLine", "(?i)shell32\\\\.dll.*ShellExec_RunDLL") || + regexMatch("log.eventDataCommandLine", "(?i)advpack\\\\.dll.*RegisterOCX") || + regexMatch("log.eventDataCommandLine", "(?i)url\\\\.dll.*FileProtocolHandler") || + regexMatch("log.eventDataCommandLine", "(?i)zipfldr\\\\.dll.*RouteTheCall") || + regexMatch("log.eventDataCommandLine", "(?i)comsvcs\\\\.dll.*(MiniDump|#24)") || + regexMatch("log.eventDataCommandLine", "(?i)(javascript:|vbscript:)") || + regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\).*\\\\.dll") +) +', '2026-03-02 13:27:28.578959', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1062, 'Registry Run Key Persistence Detection', 2, 3, 2, 'Persistence', 'T1547.001 - Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder', e'Detects modifications to Windows Registry Run and RunOnce keys, which are the most fundamental +Windows persistence mechanism. Attackers add entries to these keys to execute malicious code +every time a user logs on or the system starts. The rule monitors Event ID 4657 (registry value +modifications) for changes to Run/RunOnce keys with suspicious values pointing to unusual +executable paths, scripts, or LOLBINs. + +Next Steps: +1. Examine the registry value data to identify the persistence payload +2. Verify if the modification was made by a legitimate software installer +3. Check the modifying process and user account for compromise indicators +4. Remove the malicious registry entry immediately +5. Analyze the payload binary or script referenced in the registry value +6. Search for additional persistence mechanisms on the same host +7. Review the timeline of events leading to the registry modification +', '["https://attack.mitre.org/techniques/T1547/001/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4657","https://docs.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys"]', e'equals("log.eventCode", "4657") && +equals("log.channel", "Security") && +regexMatch("log.eventDataObjectName", "(?i)(\\\\\\\\Run\\\\\\\\|\\\\\\\\RunOnce\\\\\\\\|\\\\\\\\RunOnceEx\\\\\\\\|\\\\\\\\RunServices\\\\\\\\|\\\\\\\\RunServicesOnce\\\\\\\\)") && +( + regexMatch("log.eventDataObjectValueName", "(?i)(powershell|cmd|mshta|rundll32|regsvr32|certutil|wscript|cscript|bitsadmin)") || + regexMatch("log.eventDataNewValue", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataNewValue", "(?i)(powershell|cmd\\\\.exe\\\\s*/c|mshta|rundll32|regsvr32|certutil|wscript|cscript)") || + regexMatch("log.eventDataNewValue", "(?i)(http://|https://|\\\\\\\\\\\\\\\\[0-9]+\\\\.[0-9]+)") || + regexMatch("log.eventDataNewValue", "(?i)(-enc\\\\s|-encodedcommand|-w\\\\s+hidden)") +) +', '2026-03-02 13:27:29.797982', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1063, 'RDP Session Hijacking Detection', 3, 3, 2, 'Lateral Movement', 'T1563.002 - Remote Service Session Hijacking: RDP Hijacking', e'Detects RDP session hijacking using tscon.exe, which allows a user with SYSTEM privileges +to connect to another user\'s RDP session without authentication. This is commonly used +after privilege escalation to gain access to active sessions of other users, potentially +including domain administrators. + +Next Steps: +1. Identify which user\'s session was hijacked via tscon +2. Verify the SYSTEM-level access method used (service, scheduled task, etc.) +3. Check what actions were performed in the hijacked session +4. Review if the target session had access to sensitive resources +5. Force logout the compromised session +6. Investigate how the attacker obtained SYSTEM privileges +7. Review all active RDP sessions on the affected server +', '["https://attack.mitre.org/techniques/T1563/002/","https://medium.com/@youraveragetechnoob/rdp-session-hijacking-55ef3f85feaa","https://www.keysight.com/blogs/tech/nwvs/2022/09/21/rdp-session-hijacking"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)tscon(.exe)?\\\\s+\\\\d+\\\\s+/dest:") || + regexMatch("log.eventDataCommandLine", "(?i)tscon(.exe)?\\\\s+\\\\d+\\\\s+/dest:")) || + (regexMatch("log.eventDataCommandLine", "(?i)query\\\\s+session.*tscon") || + regexMatch("log.eventDataCommandLine", "(?i)query\\\\s+session.*tscon")) || + (regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\tscon\\\\.exe$") && + regexMatch("log.eventDataCommandLine", "(?i)\\\\d+\\\\s+/dest:")) +) +', '2026-03-02 13:27:31.156268', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1064, 'PsExec Lateral Movement Detection', 3, 3, 2, 'Lateral Movement', 'T1569.002 - System Services: Service Execution', e'Detects PsExec-style lateral movement by monitoring for the creation of the PSEXESVC service +(Event ID 7045), PsExec named pipe creation, and process execution patterns characteristic +of PsExec and its variants (Impacket PSExec, Metasploit PSExec). PsExec is the most common +tool for lateral movement in enterprise environments. + +Next Steps: +1. Identify the source host that initiated the PsExec connection +2. Verify if this is authorized administrative activity +3. Check what command was executed via PsExec on the target host +4. Review the service account credentials used for PsExec authentication +5. Search for evidence of PsExec usage across other hosts in the environment +6. Check for credential theft that may have enabled the lateral movement +7. Block PsExec at the network level if not authorized +', '["https://attack.mitre.org/techniques/T1569/002/","https://jpcertcc.github.io/ToolAnalysisResultSheet/details/PsExec.htm","https://www.sans.org/blog/psexec-and-lateral-movement/"]', e'( + equals("log.eventCode", "7045") && + ( + regexMatch("log.eventDataServiceName", "(?i)^(PSEXESVC|psexec|PAExec|csexec|remcom)") || + regexMatch("log.eventDataServiceFileName", "(?i)(PSEXESVC|psexec|PAExec)\\\\.exe") || + (regexMatch("log.eventDataServiceFileName", "(?i)%SystemRoot%\\\\\\\\[a-zA-Z]{8}\\\\.exe") && + regexMatch("log.eventDataServiceName", "(?i)^[a-zA-Z]{8}$")) || + regexMatch("log.eventDataServiceFileName", "(?i)cmd\\\\.exe\\\\s+/c.*\\\\\\\\\\\\\\\\[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\\\\\") + ) +) || +( + equals("log.eventCode", "17") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + regexMatch("log.eventDataPipeName", "(?i)\\\\\\\\(psexesvc|paexec|csexec|remcom)") +) || +( + equals("log.eventCode", "4688") && + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\PSEXESVC\\\\.exe$") && + exists("log.eventDataParentProcessName") +) +', '2026-03-02 13:27:32.516639', true, true, 'origin', null, '[]', '["adversary.host","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1065, 'Print Spooler Exploitation Detection', 3, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', e'Detects potential PrintNightmare exploitation attempts through suspicious print spooler activity including DLL loading and driver installation. This rule identifies suspicious activity related to the Print Spooler service that could indicate CVE-2021-34527 (PrintNightmare) exploitation attempts. + +Next Steps: +1. Investigate the affected host for unauthorized driver installations +2. Check for recent DLL files placed in the print spooler drivers directory +3. Review print spooler service logs for unusual activity +4. Examine process execution context and parent processes +5. Verify if print driver installations were authorized +6. Check for lateral movement from the affected system +7. Consider isolating the affected host if exploitation is confirmed +', '["https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34527","https://attack.mitre.org/techniques/T1068/"]', '(equals("log.providerName", "Microsoft-Windows-PrintService") && equals("log.eventId", "316") && contains("log.message", "kernelbase.dll")) || (equals("log.eventDataProcessName", "spoolsv.exe") && contains("log.eventDataTargetFilename", "\\spool\\drivers\\x64\\") && endsWith("log.eventDataTargetFilename", ".dll"))', '2026-03-02 13:27:33.993741', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataTargetFilename","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1066, 'PowerShell Empire Detection', 3, 3, 2, 'Execution', 'T1059.001 - Command and Scripting Interpreter: PowerShell', e'Detects potential PowerShell Empire framework usage based on characteristic command patterns, obfuscation techniques, and encoded payloads commonly used by this post-exploitation framework. PowerShell Empire is a post-exploitation framework that uses PowerShell and Python agents to maintain persistence and execute commands on compromised systems. + +Next Steps: +1. Immediately isolate the affected host to prevent lateral movement +2. Analyze the complete PowerShell script block content for additional IOCs +3. Check for persistence mechanisms (scheduled tasks, registry entries, services) +4. Review network connections from the host for C2 communication +5. Examine process tree and parent processes that spawned PowerShell +6. Search for additional Empire artifacts across the environment +7. Reset credentials for any accounts used on the compromised system +8. Conduct memory analysis to identify injected code or payloads +9. Review recent user activity and file access patterns +10. Update endpoint detection rules based on specific Empire techniques observed +', '["https://attack.mitre.org/techniques/T1059/001/","https://www.powershellempire.com/","https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging"]', e'(equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && +equals("log.providerName", "Microsoft-Windows-PowerShell") && +( + contains("log.eventDataScriptBlockText", "System.Management.Automation.AmsiUtils") || + regexMatch("log.eventDataScriptBlockText", "(?i)(empire|invoke-empire|invoke-psempire)") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[System\\\\.Convert\\\\]::FromBase64String") || + regexMatch("log.eventDataScriptBlockText", "(?i)IEX\\\\s*\\\\(\\\\s*New-Object") || + regexMatch("log.eventDataScriptBlockText", "(?i)-enc\\\\s+[A-Za-z0-9+/=]{100,}") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\$DoIt\\\\s*=\\\\s*@") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[System\\\\.Text\\\\.Encoding\\\\]::Unicode\\\\.GetString") || + contains("log.eventDataScriptBlockText", "Invoke-Shellcode") || + contains("log.eventDataScriptBlockText", "Invoke-ReflectivePEInjection") || + contains("log.eventDataScriptBlockText", "Invoke-Mimikatz") +) +', '2026-03-02 13:27:35.265686', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1067, 'Pass-the-Ticket Attack Detection', 3, 3, 2, 'Lateral Movement', 'T1550.003 - Use Alternate Authentication Material: Pass the Ticket', e'Detects Pass-the-Ticket attacks where attackers steal and reuse Kerberos tickets from one +endpoint to authenticate on another. The rule monitors for Kerberos authentication events +(Event ID 4624 LogonType 3 with Kerberos package) combined with anomalous ticket usage +patterns and tools like Rubeus, Mimikatz kerberos::ptt, and Invoke-Mimikatz. + +Next Steps: +1. Identify the source of the Kerberos ticket and the target system +2. Check if the ticket was exported using Mimikatz or Rubeus +3. Verify if the original ticket owner\'s host is compromised +4. Review Kerberos TGS/TGT events from the source IP +5. Force Kerberos ticket renewal by resetting the user\'s password +6. Check for additional lateral movement from the authenticated session +7. Implement Kerberos constrained delegation and armoring +', '["https://attack.mitre.org/techniques/T1550/003/","https://adsecurity.org/?p=2011","https://www.sans.org/blog/kerberos-in-the-crosshairs-golden-tickets-silver-tickets-mitm-and-more/"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + (regexMatch("log.eventDataCommandLine", "(?i)kerberos::ptt") || + regexMatch("log.eventDataCommandLine", "(?i)kerberos::ptt")) || + (regexMatch("log.eventDataCommandLine", "(?i)Rubeus.*ptt") || + regexMatch("log.eventDataCommandLine", "(?i)Rubeus.*ptt")) || + (regexMatch("log.eventDataCommandLine", "(?i)Rubeus.*(asktgt|asktgs|renew|s4u|createnetonly)") || + regexMatch("log.eventDataCommandLine", "(?i)Rubeus.*(asktgt|asktgs|renew|s4u|createnetonly)")) || + (regexMatch("log.eventDataCommandLine", "(?i)Invoke-Mimikatz.*kerberos") || + regexMatch("log.eventDataCommandLine", "(?i)Invoke-Mimikatz.*kerberos")) || + (regexMatch("log.eventDataCommandLine", "(?i)klist.*purge|klist.*tickets") || + regexMatch("log.eventDataCommandLine", "(?i)klist.*purge|klist.*tickets")) + ) +) || +( + equals("log.eventCode", "4768") && + equals("log.channel", "Security") && + !equals("log.eventDataStatus", "0x0") && + oneOf("log.eventDataStatus", ["0x1f", "0x20", "0x25"]) && + exists("log.eventDataIpAddress") +) +', '2026-03-02 13:27:36.357874', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1068, 'NTLM Authentication Downgrade Attack', 3, 3, 1, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects NTLM authentication downgrade attacks via registry modifications to LMCompatibilityLevel, +NtlmMinClientSec, and NtlmMinServerSec. Attackers modify these registry values to weaken NTLM +authentication security, enabling credential interception, relay attacks, and offline cracking +of captured NTLM hashes. Downgrading to LM or NTLMv1 authentication makes credential theft +significantly easier. + +Next Steps: +1. Check the new registry value to determine the downgrade severity +2. LMCompatibilityLevel < 3 enables NTLMv1 which is trivially crackable +3. Identify the process and user that made the registry modification +4. Restore the registry values to enforce NTLMv2 (LMCompatibilityLevel = 5) +5. Review Group Policy for conflicting NTLM settings +6. Check for NTLM relay attacks following the downgrade +7. Audit network traffic for NTLMv1 authentication attempts +8. Consider disabling NTLM entirely where possible +', '["https://attack.mitre.org/techniques/T1562/001/","https://www.praetorian.com/blog/ntlm-relaying-attacks/","https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/network-security-lan-manager-authentication-level"]', e'equals("log.eventCode", "4657") && +equals("log.channel", "Security") && +( + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\LMCompatibilityLevel") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\MSV1_0\\\\\\\\NtlmMinClientSec") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\MSV1_0\\\\\\\\NtlmMinServerSec") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\MSV1_0\\\\\\\\RestrictSendingNTLMTraffic") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\MSV1_0\\\\\\\\AuditReceivingNTLMTraffic") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Services\\\\\\\\Netlogon\\\\\\\\Parameters\\\\\\\\RequireSignOrSeal") +) && +!regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|TrustedInstaller)$") +', '2026-03-02 13:27:37.625530', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1069, 'Network Sniffer and Packet Capture Detection', 3, 1, 0, 'Discovery', 'T1040 - Network Sniffing', e'Detects execution of network sniffing and packet capture tools that could be used to +intercept network traffic, capture credentials, or perform man-in-the-middle attacks. +Monitors for known sniffer binaries, raw socket creation, and promiscuous mode indicators. + +Next Steps: +1. Verify if the packet capture tool usage was authorized (IT/security staff) +2. Check which user account executed the tool and validate their role +3. Determine what network interfaces were targeted for capture +4. Review if any sensitive data (credentials, tokens) may have been captured +5. Check for data exfiltration - captured packets being sent to external destinations +6. Look for lateral movement from the host running the sniffer +7. Investigate if the sniffer was installed recently or is part of standard tooling +', '["https://attack.mitre.org/techniques/T1040/","https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4688","https://www.sans.org/reading-room/whitepapers/detection/detecting-network-sniffers-1180"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataNewProcessName", "(?i)(wireshark|tshark|dumpcap|windump|tcpdump|rawcap|netsh\\\\.exe|pktmon\\\\.exe)\\\\.exe$") || + regexMatch("log.eventDataNewProcessName", "(?i)(ettercap|NetworkMiner|Microsoft Network Monitor|nmcap)\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)(wireshark|tshark|dumpcap|windump|tcpdump|rawcap)\\\\.exe$") || + ( + regexMatch("log.eventDataNewProcessName", "(?i)netsh\\\\.exe$") && + regexMatch("log.eventDataCommandLine", "(?i)(trace\\\\s+start|capture\\\\s+start)") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)pktmon\\\\.exe$") && + contains("log.eventDataCommandLine", "start") + ) || + regexMatch("log.eventDataCommandLine", "(?i)(npcap|winpcap|pcap\\\\.dll|raw\\\\s+socket|promiscuous)") || + regexMatch("log.eventDataCommandLine", "(?i)(npcap|winpcap|pcap\\\\.dll|raw\\\\s+socket|promiscuous)") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + contains("log.eventDataScriptBlockText", "Net.Sockets.Socket") || + contains("log.eventDataScriptBlockText", "SocketType.Raw") || + contains("log.eventDataScriptBlockText", "ProtocolType.IP") || + contains("log.eventDataScriptBlockText", "IOControlCode") || + contains("log.eventDataScriptBlockText", "SIO_RCVALL") || + contains("log.eventDataScriptBlockText", "Invoke-PacketCapture") || + contains("log.eventDataScriptBlockText", "New-NetEventSession") + ) +) || +( + equals("log.eventCode", "7045") && + regexMatch("log.eventDataServiceName", "(?i)(npcap|winpcap|npf|rawcap)") +) +', '2026-03-02 13:27:39.021892', true, true, 'origin', '["adversary.host","adversary.user"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1070, 'Named Pipe Impersonation Attack', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1134.001 - Access Token Manipulation: Token Impersonation/Theft', e'Detects potential named pipe impersonation attacks used for privilege escalation. Monitors for suspicious process creation patterns including cmd.exe or powershell.exe with pipe-related commands, and processes creating named pipes with suspicious naming patterns commonly used by attack tools like Meterpreter and Cobalt Strike. + +Next Steps: +1. Verify the legitimacy of the process and command line parameters +2. Check for any privilege escalation activities following this event +3. Examine the process parent-child relationship and execution context +4. Review recent logon events (4672) for the affected host +5. Investigate any unusual network connections or file access patterns +6. Consider isolating the affected system if malicious activity is confirmed +', '["https://bherunda.medium.com/hunting-named-pipe-token-impersonation-abuse-573dcca36ae0","https://attack.mitre.org/techniques/T1134/001/"]', e'equals("log.eventCode", "4688") && +exists("log.eventDataProcessName") && +(contains("log.eventDataProcessName", "cmd.exe") || contains("log.eventDataProcessName", "powershell.exe")) && +exists("log.eventDataProcessCommandLine") && +(contains("log.eventDataProcessCommandLine", "\\\\\\\\.\\\\pipe\\\\") || + (contains("log.eventDataProcessCommandLine", "echo") && contains("log.eventDataProcessCommandLine", "pipe")) || + contains("log.eventDataProcessCommandLine", "CreateNamedPipe") || + contains("log.eventDataProcessCommandLine", "ImpersonateNamedPipeClient")) +', '2026-03-02 13:27:40.419829', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataProcessId","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1071, 'MSIExec Remote Payload Installation', 2, 3, 2, 'Defense Evasion', 'T1218.007 - System Binary Proxy Execution: Msiexec', e'Detects abuse of msiexec.exe for installing MSI packages from remote URLs, executing DLLs, +or running packages with quiet/silent flags to avoid user interaction. Attackers use msiexec +as a proxy to execute malicious code because it is a signed Microsoft binary that can download +and execute payloads from remote locations. + +Next Steps: +1. Examine the MSI package URL or path for malicious content +2. Review the quiet/silent installation flags for evasion intent +3. Check the parent process to determine the delivery mechanism +4. Analyze the MSI package contents in a sandbox +5. Review network connections to the MSI download source +6. Block the identified URL at the proxy/firewall +7. Search for the same MSI package hash across other endpoints +', '["https://attack.mitre.org/techniques/T1218/007/","https://lolbas-project.github.io/lolbas/Binaries/Msiexec/","https://www.trendmicro.com/en_us/research/19/b/msiexec-abuse.html"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)msiexec\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)msiexec\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)/i\\\\s+(http://|https://)") || + regexMatch("log.eventDataCommandLine", "(?i)/y\\\\s+") || + regexMatch("log.eventDataCommandLine", "(?i)/z\\\\s+(http://|https://)") || + ( + regexMatch("log.eventDataCommandLine", "(?i)(/q|/quiet|/passive)") && + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|\\\\\\\\\\\\\\\\)") + ) || + regexMatch("log.eventDataCommandLine", "(?i)/i\\\\s+(http://|https://)") || + regexMatch("log.eventDataCommandLine", "(?i)/y\\\\s+") || + ( + regexMatch("log.eventDataCommandLine", "(?i)(/q|/quiet|/passive)") && + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|\\\\\\\\\\\\\\\\)") + ) +) +', '2026-03-02 13:27:41.770652', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1072, 'MSHTA Suspicious Execution Detection', 2, 3, 2, 'Defense Evasion', 'T1218.005 - System Binary Proxy Execution: Mshta', e'Detects suspicious mshta.exe execution patterns including remote HTA file execution, inline +VBScript/JavaScript execution, and COM scriptlet execution. MSHTA is a signed Microsoft binary +that executes Microsoft HTML Applications (HTA) and is frequently abused for initial access, +defense evasion, and payload delivery because it bypasses application whitelisting. + +Next Steps: +1. Examine the full command line for remote URLs or inline script content +2. Identify the parent process to determine the initial delivery mechanism +3. Check for downloaded HTA files and analyze their contents +4. Review network connections made by the mshta.exe process +5. Block identified malicious URLs at the proxy/firewall +6. Search for similar mshta executions across other endpoints +7. Check for persistence mechanisms established after mshta execution +', '["https://attack.mitre.org/techniques/T1218/005/","https://lolbas-project.github.io/lolbas/Binaries/Mshta/","https://redcanary.com/threat-detection-report/techniques/mshta/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)mshta\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)mshta\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") || + regexMatch("log.eventDataCommandLine", "(?i)(vbscript:|javascript:)") || + regexMatch("log.eventDataCommandLine", "(?i)(GetObject|Execute|CreateObject|WScript\\\\.Shell)") || + regexMatch("log.eventDataCommandLine", "(?i)sct:") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\.hta\\\\s") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") || + regexMatch("log.eventDataCommandLine", "(?i)(vbscript:|javascript:)") || + regexMatch("log.eventDataCommandLine", "(?i)(GetObject|Execute|CreateObject|WScript\\\\.Shell)") +) +', '2026-03-02 13:27:43.046761', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1073, 'Mimikatz Tool Usage Detection', 3, 3, 1, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', e'Detects potential Mimikatz credential dumping tool usage through various indicators including characteristic command patterns, LSASS access, and known Mimikatz modules. Mimikatz is a well-known post-exploitation tool used to extract plaintext passwords, hash, PIN code and kerberos tickets from memory. + +Next Steps: +1. Immediately isolate the affected host to prevent lateral movement +2. Review the full command line and process execution details +3. Check for any credential theft or privilege escalation activities +4. Examine recent logon events and account usage patterns +5. Scan for additional persistence mechanisms or backdoors +6. Reset passwords for all potentially compromised accounts +7. Review security logs for signs of lateral movement to other systems +8. Conduct forensic analysis of memory dumps and system artifacts +', '["https://attack.mitre.org/techniques/T1003/001/","https://github.com/gentilkiwi/mimikatz","https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4688"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataNewProcessName", "(?i)mimikatz") || + regexMatch("log.eventDataCommandLine", "(?i)(sekurlsa|kerberos|crypto|vault|lsadump|dpapi)::") || + regexMatch("log.eventDataCommandLine", "(?i)(logonpasswords|pth|golden|silver|ticket)") || + regexMatch("log.eventDataCommandLine", "(?i)privilege::debug") || + contains("log.eventDataCommandLine", "coffee") || + contains("log.eventDataCommandLine", "kirbi") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)invoke-mimikatz") || + regexMatch("log.eventDataScriptBlockText", "(?i)mimikatz\\\\.ps1") || + regexMatch("log.eventDataScriptBlockText", "(?i)DumpCreds|DumpCerts") || + contains("log.eventDataScriptBlockText", "Win32_ShadowCopy") + ) +) || +( + equals("log.eventCode", "10") && + regexMatch("log.eventDataTargetImage", "(?i)lsass\\\\.exe") && + oneOf("log.eventDataGrantedAccess", ["0x1010", "0x1038", "0x1418", "0x1438"]) +) +', '2026-03-02 13:27:44.351658', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1076, 'LSASS Credential Dumping Detection', 3, 3, 1, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', e'Detects multiple LSASS credential dumping techniques beyond Mimikatz, including comsvcs.dll MiniDump, +procdump, nanodump, pypykatz, Task Manager dump, rundll32 with comsvcs.dll, and direct NT API calls. +LSASS holds credentials for all authenticated users and is a primary target for credential theft. +This rule complements the existing Mimikatz detection by covering alternative dumping methods. + +Next Steps: +1. Immediately isolate the affected host to prevent lateral movement +2. Identify the dumping technique used and the resulting dump file location +3. Check if the dump file has been exfiltrated to an external destination +4. Review the process tree to understand how the dumping tool was executed +5. Reset passwords for all accounts that were logged into the compromised system +6. Enable Credential Guard or RunAsPPL to protect LSASS +7. Search for the same dumping technique across other endpoints +8. Investigate the initial compromise vector +', '["https://attack.mitre.org/techniques/T1003/001/","https://www.microsoft.com/en-us/security/blog/2022/10/05/detecting-and-preventing-lsass-credential-dumping-attacks/","https://redcanary.com/threat-detection-report/techniques/lsass-memory/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataCommandLine", "(?i)comsvcs\\\\.dll[,\\\\s]+(MiniDump|#24)") || + regexMatch("log.eventDataCommandLine", "(?i)comsvcs\\\\.dll[,\\\\s]+(MiniDump|#24)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)procdump.*-ma\\\\s+(lsass|\\\\d+)") || + regexMatch("log.eventDataCommandLine", "(?i)procdump.*-ma\\\\s+(lsass|\\\\d+)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)rundll32\\\\.exe.*comsvcs") || + regexMatch("log.eventDataCommandLine", "(?i)rundll32\\\\.exe.*comsvcs") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)(nanodump|handlekatz|physmem2profit|dumpert)") || + regexMatch("log.eventDataCommandLine", "(?i)(nanodump|handlekatz|physmem2profit|dumpert)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)taskmgr.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)taskmgr.*lsass") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)(Out-Minidump|Get-Process.*lsass.*\\\\.DumpProcess)") || + regexMatch("log.eventDataCommandLine", "(?i)(Out-Minidump|Get-Process.*lsass.*\\\\.DumpProcess)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)sqldumper\\\\.exe.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)sqldumper\\\\.exe.*lsass") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)createdump\\\\.exe.*(-f|--full)") || + regexMatch("log.eventDataCommandLine", "(?i)createdump\\\\.exe.*(-f|--full)") + ) +) +', '2026-03-02 13:27:48.328270', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1077, 'Regsvr32 LOLBIN Abuse Detection', 3, 3, 2, 'Defense Evasion', 'T1218.010 - System Binary Proxy Execution: Regsvr32', e'Detects suspicious regsvr32.exe usage including Squiblydoo attacks (loading remote SCT files), +AppLocker bypass techniques, and execution of DLLs from suspicious locations. Regsvr32 is a +signed Microsoft binary that can be abused to proxy execution of malicious code, bypassing +application whitelisting controls. + +Next Steps: +1. Examine the DLL or SCT file being loaded by regsvr32 +2. Check if a remote URL was used (Squiblydoo attack indicator) +3. Analyze the parent process that launched regsvr32 +4. Review network connections made by the regsvr32 process +5. Check if the loaded DLL is from a suspicious location (Temp, AppData, etc.) +6. Block the remote URL if a network-based attack was used +7. Investigate what payload was delivered through the proxy execution +', '["https://attack.mitre.org/techniques/T1218/010/","https://pentestlab.blog/2017/05/11/applocker-bypass-regsvr32/","https://lolbas-project.github.io/lolbas/Binaries/Regsvr32/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + regexMatch("log.eventDataNewProcessName", "(?i)regsvr32\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)regsvr32\\\\.exe$") +) && +( + (regexMatch("log.eventDataCommandLine", "(?i)/s.*/u.*/i:") || + regexMatch("log.eventDataCommandLine", "(?i)/s.*/u.*/i:")) || + (regexMatch("log.eventDataCommandLine", "(?i)/i:(http|https|ftp)://") || + regexMatch("log.eventDataCommandLine", "(?i)/i:(http|https|ftp)://")) || + (regexMatch("log.eventDataCommandLine", "(?i)\\\\.sct") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\.sct")) || + (regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)")) || + (regexMatch("log.eventDataCommandLine", "(?i)scrobj\\\\.dll") || + regexMatch("log.eventDataCommandLine", "(?i)scrobj\\\\.dll")) +) +', '2026-03-02 13:27:49.634487', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1078, 'LaZagne Credential Harvester Detection', 3, 2, 1, 'Credential Access', 'T1555 - Credentials from Password Stores', e'Detects LaZagne credential harvesting tool execution. LaZagne is an open-source tool that +retrieves credentials stored on a local computer from 25+ sources including browsers, email +clients, databases, Wi-Fi passwords, Windows credentials, and more. The rule monitors for +both the executable name and characteristic command-line arguments used by LaZagne. + +Next Steps: +1. Immediately isolate the affected host to prevent credential abuse +2. Identify all credential stores that may have been accessed +3. Review the command-line arguments to determine which modules were used +4. Reset passwords for all accounts stored on the compromised system +5. Check browser credential stores, email clients, and Windows vaults +6. Review Wi-Fi profiles for exposed network credentials +7. Investigate the initial access vector and delivery mechanism +8. Search for LaZagne execution across other endpoints +', '["https://attack.mitre.org/techniques/T1555/","https://github.com/AlessandroZ/LaZagne","https://www.sans.org/blog/detecting-lazagne/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)lazagne\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)lazagne\\\\.exe$") || + regexMatch("log.eventDataCommandLine", "(?i)lazagne\\\\s+(all|browsers|chats|databases|games|git|mails|maven|memory|multimedia|php|svn|sysadmin|wifi|windows)") || + regexMatch("log.eventDataCommandLine", "(?i)lazagne\\\\s+(all|browsers|chats|databases|games|git|mails|maven|memory|multimedia|php|svn|sysadmin|wifi|windows)") || + regexMatch("log.eventDataCommandLine", "(?i)lazagne\\\\.exe") || + regexMatch("log.eventDataCommandLine", "(?i)lazagne\\\\.exe") +) +', '2026-03-02 13:27:50.871734', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1079, 'Impacket Lateral Movement Detection', 3, 3, 2, 'Lateral Movement', 'T1021.002 - Remote Services: SMB/Windows Admin Shares', e'Detects Impacket framework lateral movement patterns including wmiexec, smbexec, dcomexec, and atexec. +Impacket is the most commonly used lateral movement framework in real-world attacks. The characteristic +pattern involves cmd.exe /Q /c with output redirection to 127.0.0.1 via named pipes or ADMIN$ share, +as well as specific command-line patterns unique to each Impacket module. + +Next Steps: +1. Identify the source of the lateral movement and the compromised credentials used +2. Examine the full command line for data exfiltration or payload delivery +3. Check for Impacket artifacts in the ADMIN$ share or temp directories +4. Review authentication logs for the credential source +5. Isolate affected hosts and block lateral movement paths +6. Reset credentials for all accounts used in the lateral movement +7. Hunt for Impacket usage across all domain-joined systems +8. Review network logs for SMB traffic patterns consistent with Impacket +', '["https://attack.mitre.org/techniques/T1021/002/","https://github.com/fortra/impacket","https://www.13cubed.com/downloads/impacket_exec_commands_cheat_sheet.pdf"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c\\\\s+.+\\\\s+1>\\\\s*\\\\\\\\\\\\\\\\127\\\\.0\\\\.0\\\\.1\\\\\\\\") || + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c\\\\s+.+\\\\s+1>\\\\s*\\\\\\\\\\\\\\\\127\\\\.0\\\\.0\\\\.1\\\\\\\\") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c\\\\s+.+\\\\s+2>&1") || + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c\\\\s+.+\\\\s+2>&1") + ) || + ( + regexMatch("log.eventDataParentProcessName", "(?i)wmiprvse\\\\.exe$") && + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\__output\\\\s") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\__output\\\\s") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/C\\\\s+.+\\\\\\\\ADMIN\\\\$\\\\\\\\__\\\\d+\\\\.\\\\d+") || + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/C\\\\s+.+\\\\\\\\ADMIN\\\\$\\\\\\\\__\\\\d+\\\\.\\\\d+") + ) +) +', '2026-03-02 13:27:52.044424', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1080, 'Image File Execution Options Debugger Persistence', 3, 3, 2, 'Persistence', 'T1546.012 - Event Triggered Execution: Image File Execution Options Injection', e'Detects abuse of Image File Execution Options (IFEO) registry keys to establish persistence +or redirect program execution. Attackers set the Debugger value under IFEO to hijack +execution of legitimate programs (like accessibility tools sethc.exe, utilman.exe, narrator.exe) +and redirect them to cmd.exe or other malicious payloads. This technique is also used for +persistence through GlobalFlag and SilentProcessExit monitoring. + +Next Steps: +1. Check which program\'s IFEO key was modified and what debugger was set +2. Verify if accessibility tool hijacking was used (sethc, utilman, narrator, magnify, osk) +3. Remove the malicious Debugger registry value +4. Check for RDP access if accessibility tools were hijacked (sticky keys attack) +5. Investigate the user account that made the registry modification +6. Search for additional persistence mechanisms +7. Review recent RDP login activity on the affected host +', '["https://attack.mitre.org/techniques/T1546/012/","https://blog.malwarebytes.com/101/2015/12/an-introduction-to-image-file-execution-options/","https://oddvar.moe/2018/04/10/persistence-using-globalflags-in-image-file-execution-options/"]', e'( + equals("log.eventCode", "13") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + regexMatch("log.eventDataTargetObject", "(?i)\\\\\\\\Image File Execution Options\\\\\\\\.*\\\\\\\\(Debugger|GlobalFlag|MonitorProcess)$") && + !regexMatch("log.eventDataDetails", "(?i)(werfault|drwtsn32|vsjitdebugger|windbg)") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*Image File Execution Options.*Debugger") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*Image File Execution Options.*Debugger") + ) +) +', '2026-03-02 13:27:53.136703', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1081, 'GPO Tampering Detection', 2, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1484.001 - Domain Policy Modification: Group Policy Modification', e'Detects modifications to Group Policy Objects (GPOs) which could indicate an adversary attempting to escalate privileges, deploy malware across the domain, or establish persistence. GPO tampering is a common technique used by attackers to enforce malicious configurations or deploy payloads to multiple systems simultaneously. + +**Next Steps:** +1. Immediately review the specific GPO that was modified and identify what changes were made +2. Verify if the modification was authorized by checking with the responsible administrator +3. Examine the user account that made the changes for signs of compromise +4. Review recent authentication logs for the user account to identify potential lateral movement +5. Check domain controllers for additional suspicious activities around the same timeframe +6. If unauthorized, immediately revert the GPO changes and investigate the compromise vector +7. Consider temporarily disabling the affected user account pending investigation +', '["https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing","https://attack.mitre.org/techniques/T1484/001/"]', 'equals("log.eventId", "5136") && equals("log.providerName", "Microsoft-Windows-Security-Auditing") && contains("log.eventDataObjectDN", "CN=Policies,CN=System")', '2026-03-02 13:27:54.184266', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1082, 'Event Log Clearing Detection', 3, 3, 2, 'Defense Evasion', 'T1070.001 - Indicator Removal on Host: Clear Windows Event Logs', e'Detects when Windows event logs are cleared, which is often done by attackers to cover their tracks and remove evidence of malicious activities. This rule monitors for Event ID 1102 (Security log cleared), Event ID 104 (System log cleared), and command-line activities using wevtutil or PowerShell cmdlets to clear event logs. + +Next Steps: +1. Identify the user account that performed the log clearing operation +2. Review the timeline of events before the log clearing to identify potential malicious activities +3. Check for any remaining forensic artifacts in other log sources (network logs, endpoint logs, etc.) +4. Investigate if this was authorized maintenance or potential malicious activity +5. Review user privileges and access patterns for the account involved +6. Consider implementing additional logging and monitoring for critical systems +', '["https://attack.mitre.org/techniques/T1070/001/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=1102"]', e'equals("log.eventId", "1102") || +equals("log.eventCode", "1102") || +(equals("log.eventId", "104") && + contains("log.channel", "System")) || +(equals("log.eventId", "4688") && + ((contains("log.eventDataCommandLine", "wevtutil") && + contains("log.eventDataCommandLine", " cl ")) || + contains("log.eventDataCommandLine", "Clear-EventLog") || + contains("log.eventDataCommandLine", "Remove-EventLog") || + contains("log.eventDataCommandLine", "Limit-EventLog"))) +', '2026-03-02 13:27:55.373043', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1083, 'ETW Patching and Tampering Detection', 3, 3, 2, 'Defense Evasion', 'T1562.006 - Impair Defenses: Indicator Blocking', e'Detects attempts to tamper with Event Tracing for Windows (ETW) to blind security tools. +Attackers patch ETW functions in memory (NtTraceEvent, EtwEventWrite) or use PowerShell +Reflection to disable .NET ETW providers, preventing security tools from receiving telemetry. +This technique is frequently used before executing post-exploitation tools to evade detection. + +Next Steps: +1. Investigate the process that attempted ETW tampering +2. Check what malicious activity followed the ETW patching +3. Review PowerShell script block logs for ETW manipulation code +4. Examine if the .NET ETW provider was targeted (common for AMSI bypass chains) +5. Verify that ETW providers are functioning correctly after remediation +6. Search for additional evasion techniques on the same host +7. Isolate the host and investigate the full attack chain +', '["https://attack.mitre.org/techniques/T1562/006/","https://blog.xpnsec.com/hiding-your-dotnet-etw/","https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/"]', e'( + (equals("log.eventCode", "4104") || equals("log.eventCode", "4103")) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + contains("log.eventDataScriptBlockText", "EtwEventWrite") || + contains("log.eventDataScriptBlockText", "NtTraceEvent") || + contains("log.eventDataScriptBlockText", "NtTraceControl") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[Reflection\\\\.Assembly\\\\].*ETW") || + regexMatch("log.eventDataScriptBlockText", "(?i)Patch.*ETW|ETW.*Patch") || + contains("log.eventDataScriptBlockText", "EventProvider") || + contains("log.eventDataScriptBlockText", "EtwEventWrite") || + contains("log.eventDataScriptBlockText", "NtTraceEvent") + ) +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)logman.*stop.*EventLog") || + regexMatch("log.eventDataCommandLine", "(?i)logman.*stop.*EventLog") || + regexMatch("log.eventDataCommandLine", "(?i)auditpol.*/clear") || + regexMatch("log.eventDataCommandLine", "(?i)auditpol.*/clear") || + regexMatch("log.eventDataCommandLine", "(?i)Set-EtwTraceProvider.*0x0") || + regexMatch("log.eventDataCommandLine", "(?i)Set-EtwTraceProvider.*0x0") + ) +) +', '2026-03-02 13:27:56.633563', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1084, 'DPAPI Domain Backup Key Extraction', 3, 3, 1, 'Credential Access', 'T1003.004 - OS Credential Dumping: LSA Secrets', e'Detects extraction of the DPAPI domain backup key from a domain controller. The BCKUPKEY secret +object contains the domain\'s DPAPI backup key which can be used to decrypt any DPAPI-protected +data for all domain users, including passwords, certificates, and other sensitive data. This is +a high-severity credential access technique that indicates an attacker has domain admin access. + +Next Steps: +1. Verify the account accessing the BCKUPKEY object is authorized (should only be DC replication) +2. Check if the source host is a legitimate domain controller +3. Review the user account for signs of compromise +4. Investigate for related DCSync or NTDS.dit extraction attempts +5. Assume all DPAPI-protected secrets are compromised if unauthorized +6. Rotate the DPAPI domain backup key (requires careful planning) +7. Reset credentials for all privileged accounts +', '["https://attack.mitre.org/techniques/T1003/004/","https://www.dsinternals.com/en/retrieving-dpapi-backup-keys-from-active-directory/","https://adsecurity.org/?p=1785"]', e'equals("log.eventCode", "4662") && +equals("log.channel", "Security") && +contains("log.eventDataProperties", "BCKUPKEY") && +contains("log.eventDataObjectServer", "DS") && +!regexMatch("log.eventDataSubjectUserName", "(?i)\\\\$$") +', '2026-03-02 13:27:57.757884', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1085, 'Domain Trust Discovery Detection', 2, 1, 1, 'Discovery', 'T1482 - Domain Trust Discovery', e'Detects domain trust enumeration using nltest, dsquery, Get-ADTrust, and other Active +Directory discovery tools. Attackers enumerate domain trusts to identify additional targets +for lateral movement between trusted domains and forests. This is typically an early +reconnaissance step in Active Directory attacks. + +Next Steps: +1. Identify the user account performing domain trust enumeration +2. Verify if this is authorized security assessment or IT administration +3. Check for other discovery commands from the same user or host +4. Review if the user subsequently accessed resources in trusted domains +5. Correlate with other reconnaissance activities (BloodHound, SharpHound) +6. Monitor for lateral movement into discovered trusted domains +7. Review trust configurations for unnecessary or overly permissive trusts +', '["https://attack.mitre.org/techniques/T1482/","https://adsecurity.org/?p=1588","https://www.harmj0y.net/blog/redteaming/a-guide-to-attacking-domain-trusts/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)nltest(.exe)?\\\\s+/(domain_trusts|trusted_domains|dclist|dsgetdc)") || + regexMatch("log.eventDataCommandLine", "(?i)nltest(.exe)?\\\\s+/(domain_trusts|trusted_domains|dclist|dsgetdc)")) || + (regexMatch("log.eventDataCommandLine", "(?i)dsquery(.exe)?\\\\s+trust") || + regexMatch("log.eventDataCommandLine", "(?i)dsquery(.exe)?\\\\s+trust")) || + (regexMatch("log.eventDataCommandLine", "(?i)Get-ADTrust|Get-DomainTrust|Get-ForestTrust|Get-NetForestTrust") || + regexMatch("log.eventDataCommandLine", "(?i)Get-ADTrust|Get-DomainTrust|Get-ForestTrust|Get-NetForestTrust")) || + (regexMatch("log.eventDataCommandLine", "(?i)\\\\[System\\\\.DirectoryServices\\\\.ActiveDirectory\\\\.(Domain|Forest)\\\\]") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\[System\\\\.DirectoryServices\\\\.ActiveDirectory\\\\.(Domain|Forest)\\\\]")) || + (regexMatch("log.eventDataCommandLine", "(?i)adfind(.exe)?.*-f.*trustdirection") || + regexMatch("log.eventDataCommandLine", "(?i)adfind(.exe)?.*-f.*trustdirection")) +) +', '2026-03-02 13:27:58.951717', true, true, 'origin', '["adversary.host","adversary.user"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1086, 'DLL Sideloading Detection', 2, 3, 1, 'Defense Evasion', 'T1574.002 - Hijack Execution Flow: DLL Side-Loading', e'Detects DLL sideloading patterns where known vulnerable legitimate applications load malicious +DLLs from non-system paths. Attackers place a malicious DLL alongside a vulnerable application +that loads DLLs from its own directory rather than the system directory. This technique +leverages trusted application signatures to execute malicious code and evade security controls. + +Next Steps: +1. Verify the executable path - legitimate apps should be in Program Files, not user directories +2. Check the DLL loaded alongside the executable for unexpected modifications +3. Compare DLL hashes against known good versions +4. Examine the parent process to understand how the vulnerable app was launched +5. Review file creation timestamps for the executable and DLL pair +6. Analyze the suspicious DLL in a sandbox +7. Search for similar sideloading patterns across other endpoints +', '["https://attack.mitre.org/techniques/T1574/002/","https://www.mandiant.com/resources/blog/dll-side-loading-a-thorn-in-the-side-of-the-anti-virus-industry","https://hijacklibs.net/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataNewProcessName", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Desktop\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") && + regexMatch("log.eventDataNewProcessName", "(?i)(OneDriveStandaloneUpdater|colorcpl|consent|dxcap|eudcedit|eventvwr|isoburn|msconfig|msdt|mstsc|narrator|netplwiz|odbcad32|presentationhost|rstrui|sdclt|sethc|sigverif|utilman|write)\\\\.exe$") + ) || + ( + regexMatch("log.eventDataProcessName", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Desktop\\\\\\\\)") && + regexMatch("log.eventDataProcessName", "(?i)(OneDriveStandaloneUpdater|colorcpl|consent|dxcap|eudcedit|eventvwr|isoburn|msconfig|msdt|mstsc|narrator|netplwiz|odbcad32|presentationhost|rstrui|sdclt|sethc|sigverif|utilman|write)\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Desktop\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") && + regexMatch("log.eventDataNewProcessName", "(?i)(WerFault|SearchProtocolHost|SearchFilterHost|WmiPrvSE|backgroundTaskHost|RuntimeBroker|smartscreen|tabcal|winsat)\\\\.exe$") + ) +) +', '2026-03-02 13:28:00.829760', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1087, 'DCOM Lateral Movement Detection', 3, 3, 2, 'Lateral Movement', 'T1021.003 - Remote Services: Distributed Component Object Model', e'Detects potential DCOM lateral movement attempts by monitoring for suspicious process creation with DCOM-related command line parameters. Looks for processes with automation embedding flags and specific DCOM object CLSIDs commonly abused for lateral movement such as ShellWindows and MMC20.Application. + +Next Steps: +1. Investigate the source host and user account that initiated the DCOM activity +2. Review network connections between the source and target systems around the time of the alert +3. Check for additional lateral movement indicators on both source and target systems +4. Examine the specific DCOM objects and CLSIDs being accessed for known malicious usage +5. Verify if the DCOM activity aligns with legitimate business processes or scheduled tasks +6. Look for privilege escalation attempts following the lateral movement +7. Check for any data exfiltration or persistence mechanisms deployed post-compromise +', '["https://medium.com/@cY83rR0H1t/detecting-dcom-lateral-movement-ee2b461a8705","https://attack.mitre.org/techniques/T1021/003/"]', 'equals("log.eventCode", "4688") && exists("log.eventDataProcessCommandLine") && (contains("log.eventDataProcessCommandLine", "/automation -Embedding") || contains("log.eventDataProcessCommandLine", "9BA05972-F6A8-11CF-A442-00A0C90A8F39") || contains("log.eventDataProcessCommandLine", "c08afd90-f2a1-11d1-8455-00a0c91f3880") || contains("log.eventDataProcessCommandLine", "MMC20.Application") || contains("log.eventDataProcessCommandLine", "Document.Application.ShellExecute") || contains("log.eventDataProcessCommandLine", "GetTypeFromCLSID") || contains("log.eventDataProcessCommandLine", "GetTypeFromProgID"))', '2026-03-02 13:28:02.045113', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataProcessName","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1088, 'Credential Manager Access Detection', 3, 2, 1, 'Credential Access', 'T1555.004 - Credentials from Password Stores: Windows Credential Manager', e'Detects access to Windows Credential Manager using vaultcmd.exe, cmdkey.exe, or credential +enumeration via PowerShell. Attackers use these tools to extract stored credentials including +web passwords, Windows credentials, and RDP saved logins. This is a common post-exploitation +technique for credential harvesting. + +Next Steps: +1. Identify the user account and process executing credential manager commands +2. Verify if this is authorized administrative activity or penetration testing +3. Review what credentials are stored in the affected user\'s vault +4. Check for follow-up lateral movement using extracted credentials +5. Reset any credentials that may have been exposed +6. Review if remote desktop saved credentials were compromised +7. Investigate the initial access vector that led to credential access +', '["https://attack.mitre.org/techniques/T1555/004/","https://www.passcape.com/index.php?section=docsys&cmd=details&id=28","https://blog.malwarebytes.com/threat-analysis/2020/12/credential-stealing/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)vaultcmd(.exe)?\\\\s+/(list|listcreds|listproperties)") || + regexMatch("log.eventDataCommandLine", "(?i)vaultcmd(.exe)?\\\\s+/(list|listcreds|listproperties)")) || + (regexMatch("log.eventDataCommandLine", "(?i)cmdkey(.exe)?\\\\s+/list") || + regexMatch("log.eventDataCommandLine", "(?i)cmdkey(.exe)?\\\\s+/list")) || + (regexMatch("log.eventDataCommandLine", "(?i)(CredentialManager|Windows\\\\s+Vault|VaultSvc)") || + regexMatch("log.eventDataCommandLine", "(?i)(CredentialManager|Windows\\\\s+Vault|VaultSvc)")) || + (regexMatch("log.eventDataCommandLine", "(?i)Get-VaultCredential|Get-CachedGPPPassword") || + regexMatch("log.eventDataCommandLine", "(?i)Get-VaultCredential|Get-CachedGPPPassword")) +) +', '2026-03-02 13:28:03.316781', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1089, 'Credential Dump via Registry Export', 3, 2, 1, 'Credential Access', 'T1003.004 - OS Credential Dumping: LSA Secrets', e'Detects credential dumping via registry export of SAM, SYSTEM, and SECURITY hives using +reg.exe save commands. Attackers export these hives to extract password hashes offline using +tools like secretsdump.py or samdump2. This technique is commonly used after gaining local +administrator privileges. + +Next Steps: +1. Identify the user account executing the reg save commands +2. Check if SAM, SYSTEM, and SECURITY hives were all exported (indicates deliberate extraction) +3. Search for the output files on disk and check if they were exfiltrated +4. Review for follow-up pass-the-hash or credential reuse activity +5. Reset all local account passwords on the affected system +6. Investigate how the attacker obtained local administrator privileges +7. Check for domain credential exposure if SECURITY hive was exported +', '["https://attack.mitre.org/techniques/T1003/004/","https://www.sans.org/blog/protecting-privileged-domain-accounts-safeguarding-password-hashes/","https://pentestlab.blog/2018/07/04/dumping-domain-password-hashes/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SAM") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SAM")) || + (regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SYSTEM") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SYSTEM")) || + (regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SECURITY") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SECURITY")) || + (regexMatch("log.eventDataCommandLine", "(?i)esentutl.*/y.*/d.*ntds\\\\.dit") || + regexMatch("log.eventDataCommandLine", "(?i)esentutl.*/y.*/d.*ntds\\\\.dit")) +) +', '2026-03-02 13:28:04.757706', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1090, 'COM Hijacking Persistence Detection', 2, 3, 1, 'Persistence', 'T1546.015 - Event Triggered Execution: Component Object Model Hijacking', e'Detects COM (Component Object Model) hijacking used for persistence by modifying InProcServer32 +or LocalServer32 registry values. Attackers replace legitimate COM object DLLs with malicious ones +to achieve persistence, as the malicious DLL will be loaded whenever the COM object is instantiated. +This is a stealthy persistence mechanism that is difficult to detect without registry monitoring. + +Next Steps: +1. Examine the modified InProcServer32/LocalServer32 value to identify the malicious DLL +2. Verify if the new DLL path points to a legitimate or suspicious location +3. Check the original COM object registration for comparison +4. Analyze the replacement DLL in a sandbox +5. Identify the CLSID being hijacked and what software uses it +6. Restore the original COM registration +7. Search for similar COM hijacking across other endpoints +', '["https://attack.mitre.org/techniques/T1546/015/","https://pentestlab.blog/2020/05/20/persistence-com-hijacking/","https://bohops.com/2018/08/18/abusing-the-com-registry-structure-clsid-localserver32-inprocserver32/"]', e'equals("log.eventCode", "4657") && +equals("log.channel", "Security") && +regexMatch("log.eventDataObjectName", "(?i)(InProcServer32|LocalServer32)") && +( + regexMatch("log.eventDataNewValue", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\|\\\\\\\\Desktop\\\\\\\\)") || + regexMatch("log.eventDataNewValue", "(?i)(rundll32|regsvr32|mshta|powershell|cmd\\\\.exe|wscript|cscript)") || + regexMatch("log.eventDataNewValue", "(?i)scrobj\\\\.dll") || + regexMatch("log.eventDataNewValue", "(?i)(http://|https://)") || + !regexMatch("log.eventDataNewValue", "(?i)^(C:\\\\\\\\Windows\\\\\\\\|C:\\\\\\\\Program Files)") +) && +!regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|TrustedInstaller)$") +', '2026-03-02 13:28:06.114345', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1091, 'Cobalt Strike Process Behavior Detection', 3, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', e'Detects Cobalt Strike beacon process creation patterns that are distinct from normal system behavior. +These include rundll32.exe spawned without command-line arguments, dllhost.exe or runonce.exe spawning +cmd.exe or powershell, and characteristic post-exploitation process injection patterns. Cobalt Strike +is the most widely used commercial adversary simulation tool and is frequently found in real attacks. + +Next Steps: +1. Examine the parent-child process relationships for injection patterns +2. Check for rundll32 with no arguments (classic beacon default) +3. Review named pipe activity on the host for CS pipe names +4. Check for network beaconing behavior from the suspicious process +5. Memory scan the suspicious processes for CS beacon shellcode +6. Isolate the affected host immediately +7. Hunt for lateral movement from this host to other systems +8. Review the C2 infrastructure and block at network perimeter +', '["https://attack.mitre.org/software/S0154/","https://thedfirreport.com/2021/08/29/cobalt-strike-a-defenders-guide/","https://redcanary.com/threat-detection-report/threats/cobalt-strike/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataNewProcessName", "(?i)rundll32\\\\.exe$") && + ( + !exists("log.eventDataCommandLine") || + regexMatch("log.eventDataCommandLine", "(?i)^\\"?[A-Z]:\\\\\\\\Windows\\\\\\\\(System32|SysWOW64)\\\\\\\\rundll32\\\\.exe\\"?\\\\s*$") + ) + ) || + ( + regexMatch("log.eventDataParentProcessName", "(?i)(dllhost\\\\.exe|runonce\\\\.exe|searchprotocolhost\\\\.exe)$") && + regexMatch("log.eventDataNewProcessName", "(?i)(cmd\\\\.exe|powershell\\\\.exe|pwsh\\\\.exe)$") + ) || + ( + regexMatch("log.eventDataParentProcessName", "(?i)rundll32\\\\.exe$") && + regexMatch("log.eventDataNewProcessName", "(?i)(cmd\\\\.exe|powershell\\\\.exe|pwsh\\\\.exe)$") && + !exists("log.eventDataParentCommandLine") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)(MSSE-\\\\d+-server|status_\\\\d+|postex_\\\\d+|msagent_\\\\d+)") || + regexMatch("log.eventDataCommandLine", "(?i)(MSSE-\\\\d+-server|status_\\\\d+|postex_\\\\d+|msagent_\\\\d+)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\\\\\\\\\\\\\.\\\\\\\\.pipe\\\\\\\\(MSSE-|postex_|status_|msagent_)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\\\\\\\\\\\\\.\\\\\\\\.pipe\\\\\\\\(MSSE-|postex_|status_|msagent_)") + ) +) +', '2026-03-02 13:28:07.383219', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1092, 'CMSTP UAC Bypass Detection', 2, 3, 1, 'Defense Evasion', 'T1218.003 - System Binary Proxy Execution: CMSTP', e'Detects CMSTP.exe (Microsoft Connection Manager Profile Installer) being used to bypass UAC +and AppLocker restrictions. Attackers use CMSTP with specially crafted .inf files containing +malicious commands in the RunPreSetupCommandsSection to execute arbitrary code with elevated +privileges. This is a well-known UAC bypass technique. + +Next Steps: +1. Examine the .inf file referenced in the command line for malicious content +2. Check the RunPreSetupCommandsSection of the INF file for commands +3. Identify the parent process and delivery mechanism +4. Review if UAC was successfully bypassed +5. Check for post-exploitation activity with elevated privileges +6. Remove the malicious INF file and any created artifacts +7. Search for similar CMSTP abuse across other endpoints +', '["https://attack.mitre.org/techniques/T1218/003/","https://lolbas-project.github.io/lolbas/Binaries/Cmstp/","https://msitpros.com/?p=3960"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)cmstp\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)cmstp\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)/s\\\\s+.*\\\\.inf") || + regexMatch("log.eventDataCommandLine", "(?i)/ni\\\\s+/s\\\\s+") || + regexMatch("log.eventDataCommandLine", "(?i)/au\\\\s+") || + regexMatch("log.eventDataCommandLine", "(?i)/s\\\\s+.*\\\\.inf") || + regexMatch("log.eventDataCommandLine", "(?i)/ni\\\\s+/s\\\\s+") || + regexMatch("log.eventDataCommandLine", "(?i)/au\\\\s+") +) +', '2026-03-02 13:28:08.599372', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1093, 'Certutil LOLBIN Abuse Detection', 2, 3, 1, 'Defense Evasion', 'T1105 - Ingress Tool Transfer', e'Detects abuse of certutil.exe as a Living Off The Land Binary (LOLBIN) for downloading files from URLs, +encoding/decoding Base64 payloads, and NTLM coercion. Certutil is a legitimate Windows certificate +utility that is frequently abused by attackers for payload staging and defense evasion because it is +a signed Microsoft binary that bypasses application whitelisting. + +Next Steps: +1. Examine the full command line to identify downloaded URLs or encoded payloads +2. Check the destination file path for downloaded or decoded files +3. Analyze any downloaded files in a sandbox environment +4. Review the parent process to understand how certutil was invoked +5. Check for subsequent execution of downloaded payloads +6. Block the identified download URLs at the proxy/firewall level +7. Search for similar certutil abuse across other endpoints +', '["https://attack.mitre.org/techniques/T1105/","https://attack.mitre.org/techniques/T1140/","https://lolbas-project.github.io/lolbas/Binaries/Certutil/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)certutil\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)certutil\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)(-urlcache|-URL)") || + regexMatch("log.eventDataCommandLine", "(?i)(-encode|-decode)") || + regexMatch("log.eventDataCommandLine", "(?i)(-ping|-generateSSTFromWU)") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") || + regexMatch("log.eventDataCommandLine", "(?i)-verifyctl") || + regexMatch("log.eventDataCommandLine", "(?i)(-urlcache|-URL)") || + regexMatch("log.eventDataCommandLine", "(?i)(-encode|-decode)") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") +) +', '2026-03-02 13:28:09.949837', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1094, 'Certificate Services Abuse Detection', 3, 3, 1, 'Credential Access', 'T1558 - Steal or Forge Kerberos Tickets', e'Detects suspicious certificate requests and issuance that could indicate Golden Certificate attacks or unauthorized certificate generation for persistence. This rule monitors Windows Certificate Services events for potentially malicious certificate operations, particularly those involving machine accounts or anonymous logons that could be leveraged for persistence and privilege escalation. + +Next Steps: +1. Investigate the certificate request details including the requesting user/machine +2. Verify if the certificate request was legitimate and authorized +3. Check for any recent changes to Certificate Authority policies or templates +4. Review Certificate Authority logs for other suspicious certificate issuance +5. Examine the requesting host for signs of compromise +6. Consider revoking any suspicious certificates issued +7. Validate Certificate Authority security configurations and access controls +', '["https://www.splunk.com/en_us/blog/security/breaking-the-chain-defending-against-certificate-services-abuse.html","https://attack.mitre.org/techniques/T1558/"]', '(equals("log.eventId", "4886") || equals("log.eventId", "4887")) && equals("log.providerName", "Microsoft-Windows-Security-Auditing") && (contains("log.eventDataSubjectUserName", "$") || equals("log.eventDataSubjectUserName", "ANONYMOUS LOGON"))', '2026-03-02 13:28:11.218232', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataSubjectUserName","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1095, 'Boot and Logon Script Persistence Detection', 2, 3, 2, 'Persistence', 'T1037.001 - Boot or Logon Initialization Scripts: Logon Script (Windows)', e'Detects modifications to Windows logon script registry keys including UserInitMprLogonScript, +Userinit, and Shell values. Attackers modify these keys to execute malicious scripts or +binaries during user logon, providing persistent access that survives reboots. These keys +are commonly abused because they execute with the logged-on user\'s privileges. + +Next Steps: +1. Examine the registry value data to identify the script or binary being persisted +2. Analyze the referenced script or executable for malicious content +3. Verify the parent process that modified the registry key +4. Restore the original Userinit or Shell values +5. Remove any malicious scripts from the referenced paths +6. Search for additional persistence mechanisms on the same host +7. Investigate the initial compromise vector +', '["https://attack.mitre.org/techniques/T1037/001/","https://www.cybereason.com/blog/persistence-techniques-that-persist","https://pentestlab.blog/2020/01/14/persistence-logon-scripts/"]', e'( + equals("log.eventCode", "13") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + ( + regexMatch("log.eventDataTargetObject", "(?i)\\\\\\\\Windows\\\\\\\\CurrentVersion\\\\\\\\(on\\\\\\\\(Userinit|Shell)|Policies\\\\\\\\Explorer\\\\\\\\Run)") || + regexMatch("log.eventDataTargetObject", "(?i)UserInitMprLogonScript$") || + regexMatch("log.eventDataTargetObject", "(?i)\\\\\\\\Environment\\\\\\\\UserInitMprLogonScript$") + ) && + !regexMatch("log.eventDataDetails", "(?i)^C:\\\\\\\\Windows\\\\\\\\system32\\\\\\\\userinit\\\\.exe,?\\\\s*$") && + !regexMatch("log.eventDataDetails", "(?i)^explorer\\\\.exe\\\\s*$") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*on.*(Userinit|Shell)") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*on.*(Userinit|Shell)") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*UserInitMprLogonScript") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*UserInitMprLogonScript") + ) +) +', '2026-03-02 13:28:12.540759', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1096, 'BITSAdmin Abuse Detection', 2, 3, 1, 'Defense Evasion', 'T1197 - BITS Jobs', e'Detects abuse of bitsadmin.exe for downloading files from remote URLs, creating persistent BITS +jobs, and transferring payloads. BITS (Background Intelligent Transfer Service) is a Windows +service commonly abused by attackers for both download and persistence because BITS jobs survive +reboots and can be configured to execute commands upon completion. + +Next Steps: +1. Examine the download URL and destination path in the command line +2. Review the BITS job for any notification command (persistence mechanism) +3. Check the downloaded file for malicious content +4. List all BITS jobs on the system using \'bitsadmin /list /allusers /verbose\' +5. Remove suspicious BITS jobs and quarantine downloaded files +6. Block the download URL at the proxy/firewall level +7. Search for similar BITS abuse across other endpoints +', '["https://attack.mitre.org/techniques/T1197/","https://lolbas-project.github.io/lolbas/Binaries/Bitsadmin/","https://isc.sans.edu/diary/Investigating+Microsoft+BITS+Activity/23281"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)bitsadmin\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)bitsadmin\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)(/transfer|/addfile|/resume|/create|/setnotifycmdline|/setnotifyflags)") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") || + regexMatch("log.eventDataCommandLine", "(?i)/SetMinRetryDelay") || + regexMatch("log.eventDataCommandLine", "(?i)(/transfer|/addfile|/resume|/create|/setnotifycmdline)") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") +) +', '2026-03-02 13:28:13.935398', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1097, 'AppInit DLLs Persistence Detection', 3, 3, 2, 'Persistence', 'T1546.010 - Event Triggered Execution: AppInit DLLs', e'Detects modifications to the AppInit_DLLs registry key, which causes specified DLLs to be +loaded into every process that loads User32.dll. Attackers use this technique for persistence +and DLL injection, as the malicious DLL will be loaded into virtually every user-mode process. +On modern Windows with Secure Boot, this requires LoadAppInit_DLLs to also be enabled. + +Next Steps: +1. Identify the DLL path being added to the AppInit_DLLs registry value +2. Analyze the referenced DLL for malicious functionality +3. Check if LoadAppInit_DLLs was also enabled (required on modern Windows) +4. Remove the malicious DLL path from the registry value +5. Delete the malicious DLL file from disk +6. Reboot the system to stop the DLL from being loaded into new processes +7. Investigate the initial compromise that led to this persistence +', '["https://attack.mitre.org/techniques/T1546/010/","https://docs.microsoft.com/en-us/windows/win32/dlls/secure-boot-and-appinit-dlls","https://pentestlab.blog/2020/01/07/persistence-appinit-dlls/"]', e'( + equals("log.eventCode", "13") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + regexMatch("log.eventDataTargetObject", "(?i)\\\\\\\\Windows\\\\\\\\CurrentVersion\\\\\\\\Windows\\\\\\\\(AppInit_DLLs|LoadAppInit_DLLs)$") && + exists("log.eventDataDetails") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*AppInit_DLLs") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*AppInit_DLLs") + ) +) +', '2026-03-02 13:28:15.154832', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1098, 'AMSI Bypass Detection', 3, 3, 2, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects attempts to bypass the Antimalware Scan Interface (AMSI) through PowerShell commands, DLL hijacking, or memory patching techniques. AMSI bypass is commonly used by attackers to evade detection when executing malicious PowerShell scripts or other code. + +Next Steps: +1. Immediately isolate the affected host to prevent lateral movement +2. Examine the full PowerShell script block text in Event ID 4104 for malicious content +3. Review command line arguments in Event ID 4688 for suspicious PowerShell execution +4. Check for additional indicators of compromise on the host +5. Verify if legitimate administrative tools are being used or if this is malicious activity +6. Review recent file modifications and process execution history +7. Check for persistence mechanisms that may have been installed +8. Consider reimaging the system if compromise is confirmed +', '["https://attack.mitre.org/techniques/T1562/001/","https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal"]', e'(equals("log.eventId", "4104") && + ((contains("log.eventDataScriptBlockText", "[Ref].Assembly.GetType") && + contains("log.eventDataScriptBlockText", "amsi") && + contains("log.eventDataScriptBlockText", "SetValue")) || + contains("log.eventDataScriptBlockText", "AmsiUtils") || + contains("log.eventDataScriptBlockText", "amsiInitFailed") || + contains("log.eventDataScriptBlockText", "Bypass.AMSI") || + contains("log.eventDataScriptBlockText", "AmsiScanBuffer"))) || +(equals("log.eventId", "4688") && + contains("log.eventDataCommandLine", "powershell") && + (contains("log.eventDataCommandLine", "amsi.dll") || + contains("log.eventDataCommandLine", "AmsiScanBuffer") || + contains("log.eventDataCommandLine", "amsiInitFailed"))) +', '2026-03-02 13:28:16.457110', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1099, 'AdminSDHolder Abuse Detection', 3, 3, 2, 'Persistence, Privilege Escalation', 'T1098 - Account Manipulation', e'Detects modifications to the AdminSDHolder object which can be used for persistence by granting elevated privileges. The SDProp process propagates these permissions to protected groups every 60 minutes, making this a critical security event. + +Next Steps: +1. Immediately review the user account that performed the modification +2. Check if the modification was authorized and part of legitimate administrative activities +3. Examine the specific permissions that were changed on the AdminSDHolder object +4. Monitor for privilege escalation activities in the next 60 minutes (SDProp cycle) +5. Review all members of protected groups for unauthorized additions +6. Audit recent administrative activities by the same user account +7. Consider temporarily disabling the user account if unauthorized activity is suspected +', '["https://attack.mitre.org/techniques/T1098/","https://adsecurity.org/?p=1906","https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-c--protected-accounts-and-groups-in-active-directory"]', e'oneOf("log.eventCode", ["4662", "5136", "4670"]) && +equals("log.channel", "Security") && +( + contains("log.eventDataObjectName", "CN=AdminSDHolder,CN=System") || + contains("log.eventDataObjectDN", "CN=AdminSDHolder,CN=System") +) && +( + oneOf("log.eventDataOperationType", ["Object Access", "Write Property"]) || + oneOf("log.eventDataAccessMask", ["0x20000", "0x40000", "0x80000"]) || + regexMatch("log.action", ".*Permissions.*changed.*") +) && +!equals("log.eventDataSubjectUserName", "SYSTEM") +', '2026-03-02 13:28:17.767394', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataObjectName","lastEvent.log.eventDataSubjectUserName"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260302/windows/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260302/windows/utm_group_rules_data_type.sql new file mode 100644 index 000000000..190a7afe0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260302/windows/utm_group_rules_data_type.sql @@ -0,0 +1,221 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (879, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (880, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (881, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (882, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (883, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (884, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (885, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (886, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (887, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (888, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (889, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (890, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (891, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (892, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (893, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (894, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (895, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (896, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (897, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (898, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (899, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (900, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (901, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (902, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (903, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (904, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (905, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (906, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (907, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (908, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (909, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (910, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (911, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (912, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (913, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (914, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (915, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (916, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (917, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (918, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (919, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (920, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (921, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (922, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (923, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (924, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (925, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (926, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (927, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (928, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (929, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (930, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (931, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (932, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (933, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (934, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (935, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (936, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (937, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (938, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (939, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1025, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1026, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1027, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1028, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1029, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1030, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1031, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1032, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1033, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1034, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1035, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1036, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1037, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1038, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1039, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1040, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1041, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1042, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1043, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1044, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1045, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1046, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1047, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1048, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1049, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1050, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1051, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1052, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (940, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (941, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (942, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (943, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (944, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (945, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (946, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (947, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (948, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (949, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (950, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (951, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (952, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (953, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (954, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (955, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (956, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (957, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (958, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (959, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (960, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (961, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (962, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (963, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (964, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (965, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (966, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (967, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (968, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (969, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (970, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (971, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (972, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (973, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (974, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (975, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (976, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (977, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (978, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (979, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (980, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (981, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (982, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (983, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (984, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (985, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (986, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (987, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (988, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1053, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1054, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1055, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1056, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1057, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1058, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1059, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1060, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1061, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1062, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1063, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1064, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (989, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (990, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (991, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (992, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (993, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (994, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (995, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (996, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (997, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (998, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (999, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1000, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1001, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1002, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1003, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1004, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1005, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1006, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1007, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1008, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1009, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1010, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1011, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1012, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1013, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1014, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1015, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1016, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1017, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1018, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1019, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1020, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1021, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1022, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1023, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1024, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1065, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1066, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1067, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1068, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1069, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1070, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1071, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1072, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1073, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1074, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1075, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1076, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1077, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1078, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1079, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1080, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1081, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1082, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1083, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1084, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1085, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1086, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1087, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1088, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1089, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1090, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1091, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1092, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1093, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1094, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1095, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1096, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1097, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1098, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1099, 1, null); \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index a1ba2df2f..ddba358f1 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -411,6 +411,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/filters/antivirus/bitdefender_gz.yml b/filters/antivirus/bitdefender_gz.yml index e8605dc75..fa430da73 100644 --- a/filters/antivirus/bitdefender_gz.yml +++ b/filters/antivirus/bitdefender_gz.yml @@ -183,16 +183,31 @@ pipeline: - log.src to: origin.ip + - rename: + from: + - log.deviceIps + to: origin.ip + + - rename: + from: + - log.dvchost + to: target.host + - rename: from: - log.sproc - to: origin.path + to: target.path - rename: from: - log.filePath to: origin.path + - rename: + from: + - log.act + to: action + # Removing unnecessary characters - trim: function: prefix diff --git a/filters/filebeat/system_linux_module.yml b/filters/filebeat/system_linux_module.yml index ba96d3e21..5bbbac822 100644 --- a/filters/filebeat/system_linux_module.yml +++ b/filters/filebeat/system_linux_module.yml @@ -1,79 +1,374 @@ -# System linux filter, version 3.0.0 -# Fields based on https://www.elastic.co/guide/en/beats/filebeat/7.13/filebeat-module-system.html -# and filebeat fields.yml version 7.13.4 oss -# As the docs says this module work with one event per line, filebeat must ensure to send one event per line. - -# Filter Input requirements -> fileset: datatype -# syslog: plain text +# System Linux filter version 4.0.0 +# Support for systemd/journald JSON format from filebeat/journald +# Converts SCREAMINGSNAKECASE and snakecase to camelCase +# Maps to UTMStack Standard Event Schema +# Optimized: Direct mapping to standard schema (no intermediate steps) + pipeline: - dataTypes: - linux steps: + # ======================================== + # PHASE 1: EXTRACTION + # ======================================== + + # Parse JSON from systemd/journald - json: source: raw + where: 'startsWith("raw", "{")' + + # ======================================== + # PHASE 2: FIELD NORMALIZATION (camelCase conversion) + # ======================================== + + # Convert SCREAMINGSNAKECASE to camelCase - rename: from: - - log.url - to: origin.url + - log.MESSAGE + to: log.message + where: exists("log.MESSAGE") + - rename: from: - - log.log.file.path - to: origin.file + - log.PRIORITY + to: log.priority + where: exists("log.PRIORITY") + - rename: from: - - log.host.ip - to: log.local.ips + - log.SYSLOGIDENTIFIER + to: log.syslogIdentifier + where: exists("log.SYSLOGIDENTIFIER") + - rename: from: - - log.host.mac - to: log.local.macs + - log.SYSLOGTIMESTAMP + to: log.syslogTimestamp + where: exists("log.SYSLOGTIMESTAMP") + - rename: from: - - log.host.hostname - to: origin.host + - log.SYSLOGFACILITY + to: log.syslogFacility + where: exists("log.SYSLOGFACILITY") + - rename: from: - - log.host.name - to: origin.user + - log.SYSLOGPID + to: log.syslogPid + where: exists("log.SYSLOGPID") + + # Convert snakecase to camelCase (only for fields staying in log.*) + - rename: + from: + - log.PID + to: log.pid + where: exists("log.PID") + + - rename: + from: + - log.UID + to: log.uid + where: exists("log.UID") + + - rename: + from: + - log.GID + to: log.gid + where: exists("log.GID") + + - rename: + from: + - log.TID + to: log.tid + where: exists("log.TID") + + - rename: + from: + - log.EXE + to: log.exe + where: exists("log.EXE") + + - rename: + from: + - log.UNIT + to: log.unit + where: exists("log.UNIT") + + - rename: + from: + - log.SYSTEMDUNIT + to: log.systemdUnit + where: exists("log.SYSTEMDUNIT") + + - rename: + from: + - log.SYSTEMDSLICE + to: log.systemdSlice + where: exists("log.SYSTEMDSLICE") + + - rename: + from: + - log.SYSTEMDUSERSLICE + to: log.systemdUserSlice + where: exists("log.SYSTEMDUSERSLICE") + + - rename: + from: + - log.SYSTEMDSESSION + to: log.systemdSession + where: exists("log.SYSTEMDSESSION") + + - rename: + from: + - log.SESSIONID + to: log.sessionId + where: exists("log.SESSIONID") + + - rename: + from: + - log.LEADER + to: log.leader + where: exists("log.LEADER") + + - rename: + from: + - log.SYSTEMDOWNERUID + to: log.systemdOwnerUid + where: exists("log.SYSTEMDOWNERUID") + + - rename: + from: + - log.SYSTEMDCGROUP + to: log.systemdCgroup + where: exists("log.SYSTEMDCGROUP") + + - rename: + from: + - log.BOOTID + to: log.bootId + where: exists("log.BOOTID") + + - rename: + from: + - log.MACHINEID + to: log.machineId + where: exists("log.MACHINEID") + + - rename: + from: + - log.TRANSPORT + to: log.transport + where: exists("log.TRANSPORT") + + - rename: + from: + - log.SELINUXCONTEXT + to: log.selinuxContext + where: exists("log.SELINUXCONTEXT") + - rename: from: - - log.host.id - to: log.hostId + - log.AUDITSESSION + to: log.auditSession + where: exists("log.AUDITSESSION") + - rename: from: - - log.event.dataset - to: action + - log.AUDITLOGINUID + to: log.auditLoginUid + where: exists("log.AUDITLOGINUID") + - rename: from: - - log.agent.version - to: log.agentVersion + - log.CAPEFFECTIVE + to: log.capEffective + where: exists("log.CAPEFFECTIVE") + - rename: from: - - log.host.os.kernel - to: log.osVersion + - log.REALTIMETIMESTAMP + to: log.realtimeTimestamp + where: exists("log.REALTIMETIMESTAMP") + + - rename: + from: + - log.SOURCEREALTIMETIMESTAMP + to: log.sourceRealtimeTimestamp + where: exists("log.SOURCEREALTIMETIMESTAMP") + + - rename: + from: + - log.MONOTONICTIMESTAMP + to: log.monotonicTimestamp + where: exists("log.MONOTONICTIMESTAMP") + + - rename: + from: + - log.CURSOR + to: log.cursor + where: exists("log.CURSOR") + + - rename: + from: + - log.SEQNUM + to: log.seqnum + where: exists("log.SEQNUM") + + - rename: + from: + - log.SEQNUMID + to: log.seqnumId + where: exists("log.SEQNUMID") + + - rename: + from: + - log.RUNTIMESCOPE + to: log.runtimeScope + where: exists("log.RUNTIMESCOPE") + + - rename: + from: + - log.STREAMID + to: log.streamId + where: exists("log.STREAMID") + + - rename: + from: + - log.SYSTEMDINVOCATIONID + to: log.systemdInvocationId + where: exists("log.SYSTEMDINVOCATIONID") + + - rename: + from: + - log.CODEFILE + to: log.codeFile + where: exists("log.CODEFILE") + + - rename: + from: + - log.CODELINE + to: log.codeLine + where: exists("log.CODELINE") + + - rename: + from: + - log.CODEFUNC + to: log.codeFunc + where: exists("log.CODEFUNC") + + - rename: + from: + - log.INVOCATIONID + to: log.invocationId + where: exists("log.INVOCATIONID") + + - rename: + from: + - log.JOBID + to: log.jobId + where: exists("log.JOBID") + + - rename: + from: + - log.JOBRESULT + to: actionResult + where: exists("log.JOBRESULT") + + - rename: + from: + - log.JOBTYPE + to: log.jobType + where: exists("log.JOBTYPE") + + - rename: + from: + - log.MESSAGEID + to: log.messageId + where: exists("log.MESSAGEID") + + # ======================================== + # PHASE 3: STANDARD SCHEMA MAPPING + # ======================================== + + # Map directly to Standard Event Schema (no intermediate camelCase step) + - rename: + from: + - log.HOSTNAME + to: origin.host + where: exists("log.HOSTNAME") + + - rename: + from: + - log.USERID + to: origin.user + where: exists("log.USERID") + - rename: from: - - log.host.os.type - to: log.osType + - log.COMM + to: origin.process + where: exists("log.COMM") + - rename: from: - - log.host.architecture - to: log.cpuArchitecture - - cast: - to: '[]string' - fields: - - log.local.ips - - cast: - to: '[]string' - fields: - - log.local.macs - - delete: - fields: - - log.service - - log.metadata - - log.agent - - log.host - - log.event - - log.ecs - - log.log - - log.fileset \ No newline at end of file + - log.CMDLINE + to: origin.command + where: exists("log.CMDLINE") + + # Map syslog priority (0-7) to severity labels + - add: + function: string + params: + key: severity + value: "emergency" + where: 'equals("log.priority", "0")' + + - add: + function: string + params: + key: severity + value: "alert" + where: 'equals("log.priority", "1")' + + - add: + function: string + params: + key: severity + value: "critical" + where: 'equals("log.priority", "2")' + + - add: + function: string + params: + key: severity + value: "error" + where: 'equals("log.priority", "3")' + + - add: + function: string + params: + key: severity + value: "warning" + where: 'equals("log.priority", "4")' + + - add: + function: string + params: + key: severity + value: "notice" + where: 'equals("log.priority", "5")' + + - add: + function: string + params: + key: severity + value: "info" + where: 'equals("log.priority", "6")' + + - add: + function: string + params: + key: severity + value: "debug" + where: 'equals("log.priority", "7")' \ No newline at end of file diff --git a/filters/fortinet/fortinet.yml b/filters/fortinet/fortinet.yml index b973d3c23..aedde27a9 100644 --- a/filters/fortinet/fortinet.yml +++ b/filters/fortinet/fortinet.yml @@ -1,4 +1,4 @@ -# Fortinet firewall module filter, version 3.0.1 +# Fortinet firewall module filter, version 3.0.3 # Based in docs and samples provided # # Documentations @@ -72,7 +72,26 @@ pipeline: from: - log.srcport to: origin.port - + - rename: + from: + - log.mastersrcmac + to: log.masterSourceMac + - rename: + from: + - log.osname + to: log.osName + - rename: + from: + - log.unauthusersource + to: log.unauthUserSource + - rename: + from: + - log.srchwvendor + to: log.sourceVendor + - rename: + from: + - log.srcmac + to: origin.mac - rename: from: - log.dest_ip @@ -89,6 +108,22 @@ pipeline: from: - log.src_port to: origin.port + - rename: + from: + - log.rcvdbyte + to: origin.bytesReceived + - rename: + from: + - log.rcvdpkt + to: origin.packagesReceived + - rename: + from: + - log.sentbyte + to: origin.bytesSent + - rename: + from: + - log.sentpkt + to: origin.packagesSent # Removing unused caracters - trim: @@ -101,6 +136,92 @@ pipeline: substring: '>' fields: - log.priority + - trim: + function: prefix + substring: '"' + fields: + - log.devname + - log.devid + - log.logid + - log.type + - log.subtype + - log.eventtype + - log.level + - log.devid + - log.vd + - log.srccountry + - log.dstcountry + - log.srcintf + - log.srcintfrole + - log.dstintf + - log.dstintfrole + - log.direction + - log.poluuid + - log.policytype + - action + - log.appcat + - log.app + - log.hostname + - log.url + - log.apprisk + - log.scertcname + - log.scertissuer + - log.appact + - log.applist + - log.masterSourceMac + - log.osName + - log.service + - log.trandisp + - log.tz + - log.srcswversion + - log.unauthUserSource + - origin.mac + - log.unauthuser + - log.srcname + - log.sourceVendor + - trim: + function: suffix + substring: '"' + fields: + - log.devname + - log.devid + - log.logid + - log.type + - log.subtype + - log.eventtype + - log.level + - log.devid + - log.vd + - log.srccountry + - log.dstcountry + - log.srcintf + - log.srcintfrole + - log.dstintf + - log.dstintfrole + - log.direction + - log.poluuid + - log.policytype + - action + - log.appcat + - log.app + - log.hostname + - log.url + - log.apprisk + - log.scertcname + - log.scertissuer + - log.appact + - log.applist + - log.masterSourceMac + - log.osName + - log.service + - log.trandisp + - log.tz + - log.srcswversion + - log.unauthUserSource + - origin.mac + - log.unauthuser + - log.srcname + - log.sourceVendor # Adding geolocation to origin.ip - dynamic: @@ -118,7 +239,868 @@ pipeline: destination: target.geolocation where: exists("target.ip") + # Adding protocol field based on IANA protocol numbers + - add: + function: "string" + params: + key: protocol + value: "HOPOPT" + where: equals("log.proto", "0") + - add: + function: "string" + params: + key: protocol + value: "ICMP" + where: equals("log.proto", "1") + - add: + function: "string" + params: + key: protocol + value: "IGMP" + where: equals("log.proto", "2") + - add: + function: "string" + params: + key: protocol + value: "GGP" + where: equals("log.proto", "3") + - add: + function: "string" + params: + key: protocol + value: "IP-in-IP" + where: equals("log.proto", "4") + - add: + function: "string" + params: + key: protocol + value: "ST" + where: equals("log.proto", "5") + - add: + function: "string" + params: + key: protocol + value: "TCP" + where: equals("log.proto", "6") + - add: + function: "string" + params: + key: protocol + value: "CBT" + where: equals("log.proto", "7") + - add: + function: "string" + params: + key: protocol + value: "EGP" + where: equals("log.proto", "8") + - add: + function: "string" + params: + key: protocol + value: "IGP" + where: equals("log.proto", "9") + - add: + function: "string" + params: + key: protocol + value: "BBN-RCC-MON" + where: equals("log.proto", "10") + - add: + function: "string" + params: + key: protocol + value: "NVP-II" + where: equals("log.proto", "11") + - add: + function: "string" + params: + key: protocol + value: "PUP" + where: equals("log.proto", "12") + - add: + function: "string" + params: + key: protocol + value: "ARGUS" + where: equals("log.proto", "13") + - add: + function: "string" + params: + key: protocol + value: "EMCON" + where: equals("log.proto", "14") + - add: + function: "string" + params: + key: protocol + value: "XNET" + where: equals("log.proto", "15") + - add: + function: "string" + params: + key: protocol + value: "CHAOS" + where: equals("log.proto", "16") + - add: + function: "string" + params: + key: protocol + value: "UDP" + where: equals("log.proto", "17") + - add: + function: "string" + params: + key: protocol + value: "MUX" + where: equals("log.proto", "18") + - add: + function: "string" + params: + key: protocol + value: "DCN-MEAS" + where: equals("log.proto", "19") + - add: + function: "string" + params: + key: protocol + value: "HMP" + where: equals("log.proto", "20") + - add: + function: "string" + params: + key: protocol + value: "PRM" + where: equals("log.proto", "21") + - add: + function: "string" + params: + key: protocol + value: "XNS-IDP" + where: equals("log.proto", "22") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-1" + where: equals("log.proto", "23") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-2" + where: equals("log.proto", "24") + - add: + function: "string" + params: + key: protocol + value: "LEAF-1" + where: equals("log.proto", "25") + - add: + function: "string" + params: + key: protocol + value: "LEAF-2" + where: equals("log.proto", "26") + - add: + function: "string" + params: + key: protocol + value: "RDP" + where: equals("log.proto", "27") + - add: + function: "string" + params: + key: protocol + value: "IRTP" + where: equals("log.proto", "28") + - add: + function: "string" + params: + key: protocol + value: "ISO-TP4" + where: equals("log.proto", "29") + - add: + function: "string" + params: + key: protocol + value: "NETBLT" + where: equals("log.proto", "30") + - add: + function: "string" + params: + key: protocol + value: "MFE-NSP" + where: equals("log.proto", "31") + - add: + function: "string" + params: + key: protocol + value: "MERIT-INP" + where: equals("log.proto", "32") + - add: + function: "string" + params: + key: protocol + value: "DCCP" + where: equals("log.proto", "33") + - add: + function: "string" + params: + key: protocol + value: "3PC" + where: equals("log.proto", "34") + - add: + function: "string" + params: + key: protocol + value: "IDPR" + where: equals("log.proto", "35") + - add: + function: "string" + params: + key: protocol + value: "XTP" + where: equals("log.proto", "36") + - add: + function: "string" + params: + key: protocol + value: "DDP" + where: equals("log.proto", "37") + - add: + function: "string" + params: + key: protocol + value: "IDPR-CMTP" + where: equals("log.proto", "38") + - add: + function: "string" + params: + key: protocol + value: "TP++" + where: equals("log.proto", "39") + - add: + function: "string" + params: + key: protocol + value: "IL" + where: equals("log.proto", "40") + - add: + function: "string" + params: + key: protocol + value: "IPv6" + where: equals("log.proto", "41") + - add: + function: "string" + params: + key: protocol + value: "SDRP" + where: equals("log.proto", "42") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Route" + where: equals("log.proto", "43") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Frag" + where: equals("log.proto", "44") + - add: + function: "string" + params: + key: protocol + value: "IDRP" + where: equals("log.proto", "45") + - add: + function: "string" + params: + key: protocol + value: "RSVP" + where: equals("log.proto", "46") + - add: + function: "string" + params: + key: protocol + value: "GRE" + where: equals("log.proto", "47") + - add: + function: "string" + params: + key: protocol + value: "DSR" + where: equals("log.proto", "48") + - add: + function: "string" + params: + key: protocol + value: "BNA" + where: equals("log.proto", "49") + - add: + function: "string" + params: + key: protocol + value: "ESP" + where: equals("log.proto", "50") + - add: + function: "string" + params: + key: protocol + value: "AH" + where: equals("log.proto", "51") + - add: + function: "string" + params: + key: protocol + value: "I-NLSP" + where: equals("log.proto", "52") + - add: + function: "string" + params: + key: protocol + value: "SwIPe" + where: equals("log.proto", "53") + - add: + function: "string" + params: + key: protocol + value: "NARP" + where: equals("log.proto", "54") + - add: + function: "string" + params: + key: protocol + value: "MOBILE" + where: equals("log.proto", "55") + - add: + function: "string" + params: + key: protocol + value: "TLSP" + where: equals("log.proto", "56") + - add: + function: "string" + params: + key: protocol + value: "SKIP" + where: equals("log.proto", "57") + - add: + function: "string" + params: + key: protocol + value: "IPv6-ICMP" + where: equals("log.proto", "58") + - add: + function: "string" + params: + key: protocol + value: "IPv6-NoNxt" + where: equals("log.proto", "59") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Opts" + where: equals("log.proto", "60") + - add: + function: "string" + params: + key: protocol + value: "CFTP" + where: equals("log.proto", "62") + - add: + function: "string" + params: + key: protocol + value: "SAT-EXPAK" + where: equals("log.proto", "64") + - add: + function: "string" + params: + key: protocol + value: "KRYPTOLAN" + where: equals("log.proto", "65") + - add: + function: "string" + params: + key: protocol + value: "RVD" + where: equals("log.proto", "66") + - add: + function: "string" + params: + key: protocol + value: "IPPC" + where: equals("log.proto", "67") + - add: + function: "string" + params: + key: protocol + value: "SAT-MON" + where: equals("log.proto", "69") + - add: + function: "string" + params: + key: protocol + value: "VISA" + where: equals("log.proto", "70") + - add: + function: "string" + params: + key: protocol + value: "IPCU" + where: equals("log.proto", "71") + - add: + function: "string" + params: + key: protocol + value: "CPNX" + where: equals("log.proto", "72") + - add: + function: "string" + params: + key: protocol + value: "CPHB" + where: equals("log.proto", "73") + - add: + function: "string" + params: + key: protocol + value: "WSN" + where: equals("log.proto", "74") + - add: + function: "string" + params: + key: protocol + value: "PVP" + where: equals("log.proto", "75") + - add: + function: "string" + params: + key: protocol + value: "BR-SAT-MON" + where: equals("log.proto", "76") + - add: + function: "string" + params: + key: protocol + value: "SUN-ND" + where: equals("log.proto", "77") + - add: + function: "string" + params: + key: protocol + value: "WB-MON" + where: equals("log.proto", "78") + - add: + function: "string" + params: + key: protocol + value: "WB-EXPAK" + where: equals("log.proto", "79") + - add: + function: "string" + params: + key: protocol + value: "ISO-IP" + where: equals("log.proto", "80") + - add: + function: "string" + params: + key: protocol + value: "VMTP" + where: equals("log.proto", "81") + - add: + function: "string" + params: + key: protocol + value: "SECURE-VMTP" + where: equals("log.proto", "82") + - add: + function: "string" + params: + key: protocol + value: "VINES" + where: equals("log.proto", "83") + - add: + function: "string" + params: + key: protocol + value: "IPTM" + where: equals("log.proto", "84") + - add: + function: "string" + params: + key: protocol + value: "NSFNET-IGP" + where: equals("log.proto", "85") + - add: + function: "string" + params: + key: protocol + value: "DGP" + where: equals("log.proto", "86") + - add: + function: "string" + params: + key: protocol + value: "TCF" + where: equals("log.proto", "87") + - add: + function: "string" + params: + key: protocol + value: "EIGRP" + where: equals("log.proto", "88") + - add: + function: "string" + params: + key: protocol + value: "OSPF" + where: equals("log.proto", "89") + - add: + function: "string" + params: + key: protocol + value: "Sprite-RPC" + where: equals("log.proto", "90") + - add: + function: "string" + params: + key: protocol + value: "LARP" + where: equals("log.proto", "91") + - add: + function: "string" + params: + key: protocol + value: "MTP" + where: equals("log.proto", "92") + - add: + function: "string" + params: + key: protocol + value: "AX.25" + where: equals("log.proto", "93") + - add: + function: "string" + params: + key: protocol + value: "OS" + where: equals("log.proto", "94") + - add: + function: "string" + params: + key: protocol + value: "MICP" + where: equals("log.proto", "95") + - add: + function: "string" + params: + key: protocol + value: "SCC-SP" + where: equals("log.proto", "96") + - add: + function: "string" + params: + key: protocol + value: "ETHERIP" + where: equals("log.proto", "97") + - add: + function: "string" + params: + key: protocol + value: "ENCAP" + where: equals("log.proto", "98") + - add: + function: "string" + params: + key: protocol + value: "GMTP" + where: equals("log.proto", "100") + - add: + function: "string" + params: + key: protocol + value: "IFMP" + where: equals("log.proto", "101") + - add: + function: "string" + params: + key: protocol + value: "PNNI" + where: equals("log.proto", "102") + - add: + function: "string" + params: + key: protocol + value: "PIM" + where: equals("log.proto", "103") + - add: + function: "string" + params: + key: protocol + value: "ARIS" + where: equals("log.proto", "104") + - add: + function: "string" + params: + key: protocol + value: "SCPS" + where: equals("log.proto", "105") + - add: + function: "string" + params: + key: protocol + value: "QNX" + where: equals("log.proto", "106") + - add: + function: "string" + params: + key: protocol + value: "A/N" + where: equals("log.proto", "107") + - add: + function: "string" + params: + key: protocol + value: "IPComp" + where: equals("log.proto", "108") + - add: + function: "string" + params: + key: protocol + value: "SNP" + where: equals("log.proto", "109") + - add: + function: "string" + params: + key: protocol + value: "Compaq-Peer" + where: equals("log.proto", "110") + - add: + function: "string" + params: + key: protocol + value: "IPX-in-IP" + where: equals("log.proto", "111") + - add: + function: "string" + params: + key: protocol + value: "VRRP" + where: equals("log.proto", "112") + - add: + function: "string" + params: + key: protocol + value: "PGM" + where: equals("log.proto", "113") + - add: + function: "string" + params: + key: protocol + value: "L2TP" + where: equals("log.proto", "115") + - add: + function: "string" + params: + key: protocol + value: "DDX" + where: equals("log.proto", "116") + - add: + function: "string" + params: + key: protocol + value: "IATP" + where: equals("log.proto", "117") + - add: + function: "string" + params: + key: protocol + value: "STP" + where: equals("log.proto", "118") + - add: + function: "string" + params: + key: protocol + value: "SRP" + where: equals("log.proto", "119") + - add: + function: "string" + params: + key: protocol + value: "UTI" + where: equals("log.proto", "120") + - add: + function: "string" + params: + key: protocol + value: "SMP" + where: equals("log.proto", "121") + - add: + function: "string" + params: + key: protocol + value: "SM" + where: equals("log.proto", "122") + - add: + function: "string" + params: + key: protocol + value: "PTP" + where: equals("log.proto", "123") + - add: + function: "string" + params: + key: protocol + value: "IS-IS" + where: equals("log.proto", "124") + - add: + function: "string" + params: + key: protocol + value: "FIRE" + where: equals("log.proto", "125") + - add: + function: "string" + params: + key: protocol + value: "CRTP" + where: equals("log.proto", "126") + - add: + function: "string" + params: + key: protocol + value: "CRUDP" + where: equals("log.proto", "127") + - add: + function: "string" + params: + key: protocol + value: "SSCOPMCE" + where: equals("log.proto", "128") + - add: + function: "string" + params: + key: protocol + value: "IPLT" + where: equals("log.proto", "129") + - add: + function: "string" + params: + key: protocol + value: "SPS" + where: equals("log.proto", "130") + - add: + function: "string" + params: + key: protocol + value: "PIPE" + where: equals("log.proto", "131") + - add: + function: "string" + params: + key: protocol + value: "SCTP" + where: equals("log.proto", "132") + - add: + function: "string" + params: + key: protocol + value: "FC" + where: equals("log.proto", "133") + - add: + function: "string" + params: + key: protocol + value: "RSVP-E2E-IGNORE" + where: equals("log.proto", "134") + - add: + function: "string" + params: + key: protocol + value: "Mobility-Header" + where: equals("log.proto", "135") + - add: + function: "string" + params: + key: protocol + value: "UDPLite" + where: equals("log.proto", "136") + - add: + function: "string" + params: + key: protocol + value: "MPLS-in-IP" + where: equals("log.proto", "137") + - add: + function: "string" + params: + key: protocol + value: "manet" + where: equals("log.proto", "138") + - add: + function: "string" + params: + key: protocol + value: "HIP" + where: equals("log.proto", "139") + - add: + function: "string" + params: + key: protocol + value: "Shim6" + where: equals("log.proto", "140") + - add: + function: "string" + params: + key: protocol + value: "WESP" + where: equals("log.proto", "141") + - add: + function: "string" + params: + key: protocol + value: "ROHC" + where: equals("log.proto", "142") + - add: + function: "string" + params: + key: protocol + value: "Ethernet" + where: equals("log.proto", "143") + - add: + function: "string" + params: + key: protocol + value: "AGGFRAG" + where: equals("log.proto", "144") + - add: + function: "string" + params: + key: protocol + value: "NSH" + where: equals("log.proto", "145") + - add: + function: "string" + params: + key: protocol + value: "Homa" + where: equals("log.proto", "146") + - add: + function: "string" + params: + key: protocol + value: "BIT-EMU" + where: equals("log.proto", "147") + # Removing unused fields - delete: fields: - - log.kvMessage \ No newline at end of file + - log.kvMessage + - log.proto \ No newline at end of file diff --git a/filters/macos/macos.yml b/filters/macos/macos.yml index 331ad2b38..1bdc6e3e9 100644 --- a/filters/macos/macos.yml +++ b/filters/macos/macos.yml @@ -1,7 +1,28 @@ -# Macos filter, version 3.0.0 +# Macos filter, version 3.0.1 pipeline: - dataTypes: - macos steps: - json: - source: raw \ No newline at end of file + source: raw + + # Renaming useful fields + - rename: + from: + - log.activityidentifier + to: log.activityIdentifier + + - rename: + from: + - log.processidentifier + to: log.processIdentifier + + - rename: + from: + - log.storecategory + to: log.storeCategory + + - rename: + from: + - log.threadidentifier + to: log.threadIdentifier \ No newline at end of file diff --git a/filters/netflow/netflow.yml b/filters/netflow/netflow.yml index a92c36581..402618f72 100644 --- a/filters/netflow/netflow.yml +++ b/filters/netflow/netflow.yml @@ -1,4 +1,4 @@ -# Netflow firewall module filter, version 3.1.0 +# Netflow firewall module filter, version 3.1.1 # Based in docs and Netflow Generator (Solarwinds) for send log # # Documentations @@ -80,8 +80,8 @@ pipeline: - grok: patterns: - fieldName: log.irrelevant4 - pattern: '{{.data}}proto="\[' - - fieldName: protocol + pattern: '{{.data}}proto="/\[' + - fieldName: log.protocol pattern: '{{.data}}\]"' source: raw @@ -158,31 +158,8 @@ pipeline: from: - log.dest_ip to: target.ip - - rename: - from: - - log.srcPortg - to: origin.port - - rename: - from: - - log.src_port - to: origin.port - - rename: - from: - - log.dstPortg - to: target.port - - rename: - from: - - log.dest_port - to: target.port - - # Fields conversions - - cast: - to: 'int' - fields: - - origin.port - - target.port - # Removing unused caracters + # Remove unused caracters and estandarize fields - trim: function: prefix substring: '"' @@ -198,6 +175,7 @@ pipeline: - log.version - target.ip - origin.ip + - trim: function: suffix substring: '"' @@ -222,15 +200,62 @@ pipeline: - log.totalPackets - log.inEthernet - log.outEthernet - - protocol - - origin.port - - target.port + - log.protocol + - log.srcPortg + - log.src_port + - log.dstPortg + - log.dest_port - log.destMask - log.sourceMask - log.tcpFlgs - log.destAs - log.sourceAs + - trim: + function: prefix + substring: '0' + fields: + - log.srcPortg + - log.src_port + - log.dstPortg + - log.dest_port + + - trim: + function: substring + substring: " " + fields: + - log.srcPortg + - log.src_port + - log.dstPortg + - log.dest_port + + # Fields conversions + - cast: + to: 'int' + fields: + - log.srcPortg + - log.src_port + - log.dstPortg + - log.dest_port + + # Rename fields to fields base + - rename: + from: + - log.srcPortg + to: origin.port + - rename: + from: + - log.src_port + to: origin.port + - rename: + from: + - log.dstPortg + to: target.port + - rename: + from: + - log.dest_port + to: target.port + # Adding geolocation to origin.ip - dynamic: plugin: com.utmstack.geolocation @@ -246,6 +271,866 @@ pipeline: source: target.ip destination: target.geolocation where: exists("target.ip") + + # Adding protocol field based on IANA protocol numbers + - add: + function: "string" + params: + key: protocol + value: "HOPOPT" + where: equals("log.protocol", "0") + - add: + function: "string" + params: + key: protocol + value: "ICMP" + where: equals("log.protocol", "1") + - add: + function: "string" + params: + key: protocol + value: "IGMP" + where: equals("log.protocol", "2") + - add: + function: "string" + params: + key: protocol + value: "GGP" + where: equals("log.protocol", "3") + - add: + function: "string" + params: + key: protocol + value: "IP-in-IP" + where: equals("log.protocol", "4") + - add: + function: "string" + params: + key: protocol + value: "ST" + where: equals("log.protocol", "5") + - add: + function: "string" + params: + key: protocol + value: "TCP" + where: equals("log.protocol", "6") + - add: + function: "string" + params: + key: protocol + value: "CBT" + where: equals("log.protocol", "7") + - add: + function: "string" + params: + key: protocol + value: "EGP" + where: equals("log.protocol", "8") + - add: + function: "string" + params: + key: protocol + value: "IGP" + where: equals("log.protocol", "9") + - add: + function: "string" + params: + key: protocol + value: "BBN-RCC-MON" + where: equals("log.protocol", "10") + - add: + function: "string" + params: + key: protocol + value: "NVP-II" + where: equals("log.protocol", "11") + - add: + function: "string" + params: + key: protocol + value: "PUP" + where: equals("log.protocol", "12") + - add: + function: "string" + params: + key: protocol + value: "ARGUS" + where: equals("log.protocol", "13") + - add: + function: "string" + params: + key: protocol + value: "EMCON" + where: equals("log.protocol", "14") + - add: + function: "string" + params: + key: protocol + value: "XNET" + where: equals("log.protocol", "15") + - add: + function: "string" + params: + key: protocol + value: "CHAOS" + where: equals("log.protocol", "16") + - add: + function: "string" + params: + key: protocol + value: "UDP" + where: equals("log.protocol", "17") + - add: + function: "string" + params: + key: protocol + value: "MUX" + where: equals("log.protocol", "18") + - add: + function: "string" + params: + key: protocol + value: "DCN-MEAS" + where: equals("log.protocol", "19") + - add: + function: "string" + params: + key: protocol + value: "HMP" + where: equals("log.protocol", "20") + - add: + function: "string" + params: + key: protocol + value: "PRM" + where: equals("log.protocol", "21") + - add: + function: "string" + params: + key: protocol + value: "XNS-IDP" + where: equals("log.protocol", "22") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-1" + where: equals("log.protocol", "23") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-2" + where: equals("log.protocol", "24") + - add: + function: "string" + params: + key: protocol + value: "LEAF-1" + where: equals("log.protocol", "25") + - add: + function: "string" + params: + key: protocol + value: "LEAF-2" + where: equals("log.protocol", "26") + - add: + function: "string" + params: + key: protocol + value: "RDP" + where: equals("log.protocol", "27") + - add: + function: "string" + params: + key: protocol + value: "IRTP" + where: equals("log.protocol", "28") + - add: + function: "string" + params: + key: protocol + value: "ISO-TP4" + where: equals("log.protocol", "29") + - add: + function: "string" + params: + key: protocol + value: "NETBLT" + where: equals("log.protocol", "30") + - add: + function: "string" + params: + key: protocol + value: "MFE-NSP" + where: equals("log.protocol", "31") + - add: + function: "string" + params: + key: protocol + value: "MERIT-INP" + where: equals("log.protocol", "32") + - add: + function: "string" + params: + key: protocol + value: "DCCP" + where: equals("log.protocol", "33") + - add: + function: "string" + params: + key: protocol + value: "3PC" + where: equals("log.protocol", "34") + - add: + function: "string" + params: + key: protocol + value: "IDPR" + where: equals("log.protocol", "35") + - add: + function: "string" + params: + key: protocol + value: "XTP" + where: equals("log.protocol", "36") + - add: + function: "string" + params: + key: protocol + value: "DDP" + where: equals("log.protocol", "37") + - add: + function: "string" + params: + key: protocol + value: "IDPR-CMTP" + where: equals("log.protocol", "38") + - add: + function: "string" + params: + key: protocol + value: "TP++" + where: equals("log.protocol", "39") + - add: + function: "string" + params: + key: protocol + value: "IL" + where: equals("log.protocol", "40") + - add: + function: "string" + params: + key: protocol + value: "IPv6" + where: equals("log.protocol", "41") + - add: + function: "string" + params: + key: protocol + value: "SDRP" + where: equals("log.protocol", "42") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Route" + where: equals("log.protocol", "43") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Frag" + where: equals("log.protocol", "44") + - add: + function: "string" + params: + key: protocol + value: "IDRP" + where: equals("log.protocol", "45") + - add: + function: "string" + params: + key: protocol + value: "RSVP" + where: equals("log.protocol", "46") + - add: + function: "string" + params: + key: protocol + value: "GRE" + where: equals("log.protocol", "47") + - add: + function: "string" + params: + key: protocol + value: "DSR" + where: equals("log.protocol", "48") + - add: + function: "string" + params: + key: protocol + value: "BNA" + where: equals("log.protocol", "49") + - add: + function: "string" + params: + key: protocol + value: "ESP" + where: equals("log.protocol", "50") + - add: + function: "string" + params: + key: protocol + value: "AH" + where: equals("log.protocol", "51") + - add: + function: "string" + params: + key: protocol + value: "I-NLSP" + where: equals("log.protocol", "52") + - add: + function: "string" + params: + key: protocol + value: "SwIPe" + where: equals("log.protocol", "53") + - add: + function: "string" + params: + key: protocol + value: "NARP" + where: equals("log.protocol", "54") + - add: + function: "string" + params: + key: protocol + value: "MOBILE" + where: equals("log.protocol", "55") + - add: + function: "string" + params: + key: protocol + value: "TLSP" + where: equals("log.protocol", "56") + - add: + function: "string" + params: + key: protocol + value: "SKIP" + where: equals("log.protocol", "57") + - add: + function: "string" + params: + key: protocol + value: "IPv6-ICMP" + where: equals("log.protocol", "58") + - add: + function: "string" + params: + key: protocol + value: "IPv6-NoNxt" + where: equals("log.protocol", "59") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Opts" + where: equals("log.protocol", "60") + - add: + function: "string" + params: + key: protocol + value: "CFTP" + where: equals("log.protocol", "62") + - add: + function: "string" + params: + key: protocol + value: "SAT-EXPAK" + where: equals("log.protocol", "64") + - add: + function: "string" + params: + key: protocol + value: "KRYPTOLAN" + where: equals("log.protocol", "65") + - add: + function: "string" + params: + key: protocol + value: "RVD" + where: equals("log.protocol", "66") + - add: + function: "string" + params: + key: protocol + value: "IPPC" + where: equals("log.protocol", "67") + - add: + function: "string" + params: + key: protocol + value: "SAT-MON" + where: equals("log.protocol", "69") + - add: + function: "string" + params: + key: protocol + value: "VISA" + where: equals("log.protocol", "70") + - add: + function: "string" + params: + key: protocol + value: "IPCU" + where: equals("log.protocol", "71") + - add: + function: "string" + params: + key: protocol + value: "CPNX" + where: equals("log.protocol", "72") + - add: + function: "string" + params: + key: protocol + value: "CPHB" + where: equals("log.protocol", "73") + - add: + function: "string" + params: + key: protocol + value: "WSN" + where: equals("log.protocol", "74") + - add: + function: "string" + params: + key: protocol + value: "PVP" + where: equals("log.protocol", "75") + - add: + function: "string" + params: + key: protocol + value: "BR-SAT-MON" + where: equals("log.protocol", "76") + - add: + function: "string" + params: + key: protocol + value: "SUN-ND" + where: equals("log.protocol", "77") + - add: + function: "string" + params: + key: protocol + value: "WB-MON" + where: equals("log.protocol", "78") + - add: + function: "string" + params: + key: protocol + value: "WB-EXPAK" + where: equals("log.protocol", "79") + - add: + function: "string" + params: + key: protocol + value: "ISO-IP" + where: equals("log.protocol", "80") + - add: + function: "string" + params: + key: protocol + value: "VMTP" + where: equals("log.protocol", "81") + - add: + function: "string" + params: + key: protocol + value: "SECURE-VMTP" + where: equals("log.protocol", "82") + - add: + function: "string" + params: + key: protocol + value: "VINES" + where: equals("log.protocol", "83") + - add: + function: "string" + params: + key: protocol + value: "IPTM" + where: equals("log.protocol", "84") + - add: + function: "string" + params: + key: protocol + value: "NSFNET-IGP" + where: equals("log.protocol", "85") + - add: + function: "string" + params: + key: protocol + value: "DGP" + where: equals("log.protocol", "86") + - add: + function: "string" + params: + key: protocol + value: "TCF" + where: equals("log.protocol", "87") + - add: + function: "string" + params: + key: protocol + value: "EIGRP" + where: equals("log.protocol", "88") + - add: + function: "string" + params: + key: protocol + value: "OSPF" + where: equals("log.protocol", "89") + - add: + function: "string" + params: + key: protocol + value: "Sprite-RPC" + where: equals("log.protocol", "90") + - add: + function: "string" + params: + key: protocol + value: "LARP" + where: equals("log.protocol", "91") + - add: + function: "string" + params: + key: protocol + value: "MTP" + where: equals("log.protocol", "92") + - add: + function: "string" + params: + key: protocol + value: "AX.25" + where: equals("log.protocol", "93") + - add: + function: "string" + params: + key: protocol + value: "OS" + where: equals("log.protocol", "94") + - add: + function: "string" + params: + key: protocol + value: "MICP" + where: equals("log.protocol", "95") + - add: + function: "string" + params: + key: protocol + value: "SCC-SP" + where: equals("log.protocol", "96") + - add: + function: "string" + params: + key: protocol + value: "ETHERIP" + where: equals("log.protocol", "97") + - add: + function: "string" + params: + key: protocol + value: "ENCAP" + where: equals("log.protocol", "98") + - add: + function: "string" + params: + key: protocol + value: "GMTP" + where: equals("log.protocol", "100") + - add: + function: "string" + params: + key: protocol + value: "IFMP" + where: equals("log.protocol", "101") + - add: + function: "string" + params: + key: protocol + value: "PNNI" + where: equals("log.protocol", "102") + - add: + function: "string" + params: + key: protocol + value: "PIM" + where: equals("log.protocol", "103") + - add: + function: "string" + params: + key: protocol + value: "ARIS" + where: equals("log.protocol", "104") + - add: + function: "string" + params: + key: protocol + value: "SCPS" + where: equals("log.protocol", "105") + - add: + function: "string" + params: + key: protocol + value: "QNX" + where: equals("log.protocol", "106") + - add: + function: "string" + params: + key: protocol + value: "A/N" + where: equals("log.protocol", "107") + - add: + function: "string" + params: + key: protocol + value: "IPComp" + where: equals("log.protocol", "108") + - add: + function: "string" + params: + key: protocol + value: "SNP" + where: equals("log.protocol", "109") + - add: + function: "string" + params: + key: protocol + value: "Compaq-Peer" + where: equals("log.protocol", "110") + - add: + function: "string" + params: + key: protocol + value: "IPX-in-IP" + where: equals("log.protocol", "111") + - add: + function: "string" + params: + key: protocol + value: "VRRP" + where: equals("log.protocol", "112") + - add: + function: "string" + params: + key: protocol + value: "PGM" + where: equals("log.protocol", "113") + - add: + function: "string" + params: + key: protocol + value: "L2TP" + where: equals("log.protocol", "115") + - add: + function: "string" + params: + key: protocol + value: "DDX" + where: equals("log.protocol", "116") + - add: + function: "string" + params: + key: protocol + value: "IATP" + where: equals("log.protocol", "117") + - add: + function: "string" + params: + key: protocol + value: "STP" + where: equals("log.protocol", "118") + - add: + function: "string" + params: + key: protocol + value: "SRP" + where: equals("log.protocol", "119") + - add: + function: "string" + params: + key: protocol + value: "UTI" + where: equals("log.protocol", "120") + - add: + function: "string" + params: + key: protocol + value: "SMP" + where: equals("log.protocol", "121") + - add: + function: "string" + params: + key: protocol + value: "SM" + where: equals("log.protocol", "122") + - add: + function: "string" + params: + key: protocol + value: "PTP" + where: equals("log.protocol", "123") + - add: + function: "string" + params: + key: protocol + value: "IS-IS" + where: equals("log.protocol", "124") + - add: + function: "string" + params: + key: protocol + value: "FIRE" + where: equals("log.protocol", "125") + - add: + function: "string" + params: + key: protocol + value: "CRTP" + where: equals("log.protocol", "126") + - add: + function: "string" + params: + key: protocol + value: "CRUDP" + where: equals("log.protocol", "127") + - add: + function: "string" + params: + key: protocol + value: "SSCOPMCE" + where: equals("log.protocol", "128") + - add: + function: "string" + params: + key: protocol + value: "IPLT" + where: equals("log.protocol", "129") + - add: + function: "string" + params: + key: protocol + value: "SPS" + where: equals("log.protocol", "130") + - add: + function: "string" + params: + key: protocol + value: "PIPE" + where: equals("log.protocol", "131") + - add: + function: "string" + params: + key: protocol + value: "SCTP" + where: equals("log.protocol", "132") + - add: + function: "string" + params: + key: protocol + value: "FC" + where: equals("log.protocol", "133") + - add: + function: "string" + params: + key: protocol + value: "RSVP-E2E-IGNORE" + where: equals("log.protocol", "134") + - add: + function: "string" + params: + key: protocol + value: "Mobility-Header" + where: equals("log.protocol", "135") + - add: + function: "string" + params: + key: protocol + value: "UDPLite" + where: equals("log.protocol", "136") + - add: + function: "string" + params: + key: protocol + value: "MPLS-in-IP" + where: equals("log.protocol", "137") + - add: + function: "string" + params: + key: protocol + value: "manet" + where: equals("log.protocol", "138") + - add: + function: "string" + params: + key: protocol + value: "HIP" + where: equals("log.protocol", "139") + - add: + function: "string" + params: + key: protocol + value: "Shim6" + where: equals("log.protocol", "140") + - add: + function: "string" + params: + key: protocol + value: "WESP" + where: equals("log.protocol", "141") + - add: + function: "string" + params: + key: protocol + value: "ROHC" + where: equals("log.protocol", "142") + - add: + function: "string" + params: + key: protocol + value: "Ethernet" + where: equals("log.protocol", "143") + - add: + function: "string" + params: + key: protocol + value: "AGGFRAG" + where: equals("log.protocol", "144") + - add: + function: "string" + params: + key: protocol + value: "NSH" + where: equals("log.protocol", "145") + - add: + function: "string" + params: + key: protocol + value: "Homa" + where: equals("log.protocol", "146") + - add: + function: "string" + params: + key: protocol + value: "BIT-EMU" + where: equals("log.protocol", "147") # Removing unused fields - delete: @@ -261,6 +1146,8 @@ pipeline: - log.dstAs - log.srcAs - log.dstPort + - log.proto + - log.protocol - log.irrelevant - log.irrelevant1 - log.irrelevant2 diff --git a/filters/vmware/vmware-esxi.yml b/filters/vmware/vmware-esxi.yml index 7c533dd72..a415d2e1f 100644 --- a/filters/vmware/vmware-esxi.yml +++ b/filters/vmware/vmware-esxi.yml @@ -1,4 +1,4 @@ -# VMWare-ESXi, version 3.0.1 +# VMWare-ESXi, version 3.0.2 # # Based on docs and real logs provided # Support VMWare-ESXi log @@ -75,6 +75,14 @@ pipeline: - fieldName: log.subModuleIdentifier pattern: '{{.word}}\]' source: log.originIdComponent + + - grok: + patterns: + - fieldName: log.irrelevant2 + pattern: '{{.data}}level{{.space}}=' + - fieldName: log.level + pattern: '{{.integer}}' + source: log.message # Removing unused caracters - trim: diff --git a/filters/windows/windows-events.yml b/filters/windows/windows-events.yml index 2457dea08..458bf89e5 100644 --- a/filters/windows/windows-events.yml +++ b/filters/windows/windows-events.yml @@ -1,4 +1,4 @@ -# Windows_Agent filter, version 3.0.4 +# Windows_Agent filter, version 3.1.1 # Based on winlogbeat fields, reference [8.15] # See https://www.elastic.co/guide/en/beats/winlogbeat/current/exported-fields-winlog.html @@ -12,333 +12,277 @@ pipeline: # Renaming useful fields - rename: from: - - log.host.ip - to: log.origin.ips - - - rename: - from: - - log.host.mac - to: log.origin.macs - - - rename: - from: - - log.event.action - to: log.action - - - rename: - from: - - log.event.outcome - to: actionResult - - - rename: - from: - - log.host.hostname - to: origin.host - - - rename: - from: - - log.event.created - to: log.deviceTime - - - rename: - from: - - log.host.os.name - to: log.os - - - rename: - from: - - log.host.os.kernel - to: log.osVersion - - - rename: - from: - - log.host.architecture - to: log.cpuArchitecture - - - rename: - from: - - log.winlog.provider_guid + - log.providerguid to: log.providerGuid - rename: from: - - log.winlog.event_data.PrivilegeList - to: log.winlogEventDataPrivilegeList + - log.data.PrivilegeList + to: log.eventDataPrivilegeList - rename: from: - - log.winlog.event_data.ServiceName - to: log.winlogEventDataServiceName + - log.data.ServiceName + to: log.eventDataServiceName - rename: from: - - log.winlog.event_data.SubjectDomainName - to: log.winlogEventDataSubjectDomainName + - log.data.SubjectDomainName + to: log.eventDataSubjectDomainName - rename: from: - - log.winlog.event_data.SubjectLogonId - to: log.winlogEventDataSubjectLogonId + - log.data.SubjectLogonId + to: log.eventDataSubjectLogonId - rename: from: - - log.winlog.event_data.SubjectUserName - to: log.winlogEventDataSubjectUserName + - log.data.SubjectUserName + to: log.eventDataSubjectUserName - rename: from: - - log.winlog.event_data.SubjectUserSid - to: log.winlogEventDataSubjectUserSid + - log.data.SubjectUserSid + to: log.eventDataSubjectUserSid - rename: from: - - log.winlog.event_data.SubjectUserSid - to: log.winlogEventDataSubjectUserSid + - log.data.SubjectUserSid + to: log.eventDataSubjectUserSid - rename: from: - - log.winlog.event_data.PrivilegeList - to: log.winlogEventDataPrivilegeList + - log.data.PrivilegeList + to: log.eventDataPrivilegeList - rename: from: - - log.winlog.event_data.ClientProcessId - to: log.winlogEventDataClientProcessId + - log.data.ClientProcessId + to: log.eventDataClientProcessId - rename: from: - - log.winlog.event_data.Flags - to: log.winlogEventDataFlags + - log.data.Flags + to: log.eventDataFlags - rename: from: - - log.winlog.event_data.Identity - to: log.winlogEventDataIdentity + - log.data.Identity + to: log.eventDataIdentity - rename: from: - - log.winlog.event_data.ProcessCreationTime - to: log.winlogEventDataProcessCreationTime + - log.data.ProcessCreationTime + to: log.eventDataProcessCreationTime - rename: from: - - log.winlog.event_id - to: log.winlogEventId + - log.id + to: log.eventId - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.ReturnCode - to: log.winlogEventDataReturnCode + - log.data.ReturnCode + to: log.eventDataReturnCode - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.Schema - to: log.winlogEventDataSchema + - log.data.Schema + to: log.eventDataSchema - rename: from: - - log.winlog.event_data.SchemaFriendlyName - to: log.winlogEventDataSchemaFriendlyName + - log.data.SchemaFriendlyName + to: log.eventDataSchemaFriendlyName - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.AuthenticationPackageName - to: log.winlogEventDataAuthenticationPackageName + - log.data.AuthenticationPackageName + to: log.eventDataAuthenticationPackageName - rename: from: - - log.winlog.event_data.ElevatedToken - to: log.winlogEventDataElevatedToken + - log.data.ElevatedToken + to: log.eventDataElevatedToken - rename: from: - - log.winlog.event_data.ImpersonationLevel - to: log.winlogEventDataImpersonationLevel + - log.data.ImpersonationLevel + to: log.eventDataImpersonationLevel - rename: from: - - log.wineventlog.event_data.FailureReason - to: log.winlogEventDataFailureReason + - log.wineventlog.data.FailureReason + to: log.eventDataFailureReason - rename: from: - - log.winlog.event_data.IpAddress - to: log.winlogEventDataIpAddress + - log.data.IpAddress + to: origin.ip - rename: from: - - log.winlog.event_data.IpPort - to: log.winlogEventDataIpPort + - log.data.IpPort + to: log.eventDataIpPort - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.KeyLength - to: log.winlogEventDataKeyLength + - log.data.KeyLength + to: log.eventDataKeyLength - rename: from: - - log.winlog.event_data.LmPackageName - to: log.winlogEventDataLmPackageName + - log.data.LmPackageName + to: log.eventDataLmPackageName - rename: from: - - log.winlog.event_data.LogonProcessName - to: log.winlogEventDataLogonProcessName + - log.data.LogonProcessName + to: log.eventDataLogonProcessName - rename: from: - - log.winlog.event_data.LogonType - to: log.winlogEventDataLogonType - + - log.data.LogonType + to: log.eventDataLogonType - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.ProcessId - to: log.winlogEventDataProcessId + - log.data.ProcessId + to: log.eventDataProcessId - rename: from: - - log.winlog.event_data.ProcessName - to: log.winlogEventDataProcessName + - log.data.ProcessName + to: log.eventDataProcessName - rename: from: - - log.winlog.event_data.RestrictedAdminMode - to: log.winlogEventDataRestrictedAdminMode + - log.data.RestrictedAdminMode + to: log.eventDataRestrictedAdminMode - rename: from: - - log.winlog.event_data.TargetDomainName + - log.data.TargetDomainName to: target.domain - rename: from: - - log.winlog.event_data.TargetLinkedLogonId - to: log.winlogEventDataTargetLinkedLogonId + - log.data.TargetLinkedLogonId + to: log.eventDataTargetLinkedLogonId - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.TargetLogonId - to: log.winlogEventDataTargetLogonId + - log.data.TargetLogonId + to: log.eventDataTargetLogonId - rename: from: - - log.winlog.event_data.TargetOutboundDomainName - to: log.winlogEventDataTargetOutboundDomainName + - log.data.TargetOutboundDomainName + to: log.eventDataTargetOutboundDomainName - rename: from: - - log.winlog.event_data.TargetOutboundUserName - to: log.winlogEventDataTargetOutboundUserName + - log.data.TargetOutboundUserName + to: log.eventDataTargetOutboundUserName - rename: from: - - log.winlog.event_data.TargetUserName + - log.data.TargetUserName to: target.user - rename: from: - - log.winlog.event_data.TargetUserSid - to: log.winlogEventDataTargetUserSid + - log.data.TargetUserSid + to: log.eventDataTargetUserSid - rename: from: - - log.winlog.event_data.TransmittedServices - to: log.winlogEventDataTransmittedServices + - log.data.TransmittedServices + to: log.eventDataTransmittedServices - rename: from: - - log.winlog.event_data.VirtualAccount - to: log.winlogEventDataVirtualAccount + - log.data.VirtualAccount + to: log.eventDataVirtualAccount - rename: from: - - log.winlog.event_data.WorkstationName - to: log.winlogEventDataWorkstationName - - - rename: - from: - - log.winlog.event_data.FailureReason - to: log.winlogEventDataFailureReason - - - rename: - from: - - log.winlog.event_data.AccessMask - to: log.winlogEeventDataAccessMask + - log.data.WorkstationName + to: origin.host - rename: from: - - log.winlog.event_data.Status - to: log.winlogEventDataStatus + - log.data.FailureReason + to: log.eventDataFailureReason - rename: from: - - log.winlog.opcode - to: log.winlogOpcode + - log.data.AccessMask + to: log.eventDataAccessMask - rename: from: - - log.winlog.process.thread - to: log.winlogProcessThread + - log.data.Status + to: log.eventDataStatus - rename: from: - - log.winlog.task - to: log.winlogOpcode + - log.process.thread + to: log.processThread - rename: from: - - log.winlog.process.thread.id - to: log.winlogProcessThreadId + - log.execution.ThreadID + to: log.processThreadId - rename: from: - - log.winlog.process.pid - to: log.winlogProcessPid + - log.process.pid + to: log.processPid - rename: from: - - log.winlog.provider_name - to: log.winlogProviderName + - log.providername + to: log.providerName - rename: from: - - log.winlog.record_id - to: log.winlogRecordId + - log.record_id + to: log.recordId - rename: from: - - log.winlog.task - to: log.winlogTask + - log.task + to: log.task - rename: from: @@ -362,13 +306,13 @@ pipeline: - rename: from: - - log.winlog.api - to: log.winlogApi + - log.api + to: log.api - rename: from: - - log.winlog.channel - to: log.winlogChannel + - log.channel + to: log.channel - rename: from: @@ -377,23 +321,18 @@ pipeline: - rename: from: - - log.winlog.activity_id + - log.correlation.ActivityID to: log.activityId - rename: from: - - log.winlog.event_data.LogonGuid + - log.data.LogonGuid to: log.logonGuid - - cast: - to: "[]string" - fields: - - log.origin.ips - - - cast: - to: "[]string" - fields: - - log.origin.macs + - rename: + from: + - log.execution.ProcessID + to: log.executionProcessID - cast: to: "int" @@ -407,8 +346,8 @@ pipeline: - log.providerGuid - log.activityId - log.logonGuid - - log.winlogEventDataSchema - - log.winlogProcessThread + - log.eventDataSchema + - log.processThread - trim: function: suffix @@ -417,8 +356,8 @@ pipeline: - log.providerGuid - log.activityId - log.logonGuid - - log.winlogEventDataSchema - - log.winlogProcessThread + - log.eventDataSchema + - log.processThread # Drop unnecessary events - drop: @@ -2293,49 +2232,49 @@ pipeline: params: key: log.failureReasonDescription value: 'The specified user account has expired.' - where: equals("log.winlogEventDataFailureReason", "%%2305") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2305") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'The password for the specified account has expired' - where: equals("log.winlogEventDataFailureReason", "%%2309") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2309") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'Account currently disabled' - where: equals("log.winlogEventDataFailureReason", "%%2310") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2310") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'Account logon time restriction violation' - where: equals("log.winlogEventDataFailureReason", "%%2311") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2311") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'User not allowed to logon at this computer' - where: equals("log.winlogEventDataFailureReason", "%%2312") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2312") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'Unknown user name or bad password' - where: equals("log.winlogEventDataFailureReason", "%%2313") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2313") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'An Error occurred during Logon' - where: equals("log.winlogEventDataFailureReason", "%%2304") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2304") && equals("log.eventCode", 4625) # Decoding the "AccessMask" field when the event code is 4663 and adding the "accessType" and "accessDescription" field - add: @@ -2343,210 +2282,210 @@ pipeline: params: key: log.accessType value: 'read' - where: equals("log.winlogEeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to read the corresponding file data. For a directory object, the right to read the corresponding directory data.\n For a directory, the right to list the contents of the directory.\n For registry objects, this is, Query key value.' - where: equals("log.winlogEeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write' - where: equals("log.winlogEeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to write data to the file.\n For a directory object, the right to create a file in the directory.\n For registry objects, this is, Set key value.' - where: equals("log.winlogEeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'append' - where: equals("log.winlogEeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to append data to the file. (For local files, write operations will not overwrite existing data if this flag is specified without FILE_WRITE_DATA.)\n For a directory object, the right to create a subdirectory.\n For a named pipe, the right to create a pipe.' - where: equals("log.winlogEeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_extended_attributes' - where: equals("log.winlogEeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read extended file attributes.\n For registry objects, this is, Enumerate sub-keys.' - where: equals("log.winlogEeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_extended_attributes' - where: equals("log.winlogEeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to write extended file attributes.' - where: equals("log.winlogEeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'execute' - where: equals("log.winlogEeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a native code file, the right to execute the file. This access right given to scripts may cause the script to be executable, depending on the script interpreter.\n For a directory, the right to traverse the directory. By default, users are assigned the BYPASS_TRAVERSE_CHECKING privilege, which ignores the FILE_TRAVERSE access right.' - where: equals("log.winlogEeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'delete_child' - where: equals("log.winlogEeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a directory, the right to delete a directory and all the files it contains, including read-only files.' - where: equals("log.winlogEeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_attributes' - where: equals("log.winlogEeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read file attributes.' - where: equals("log.winlogEeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_attributes' - where: equals("log.winlogEeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to write file attributes.' - where: equals("log.winlogEeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'delete' - where: equals("log.winlogEeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to delete the object.' - where: equals("log.winlogEeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_control' - where: equals("log.winlogEeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read information in the security descriptor object, without including the information in the system access control list (SACL).' - where: equals("log.winlogEeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_dac' - where: equals("log.winlogEeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to modify the discretionary access control list (DACL) in the security descriptor object.' - where: equals("log.winlogEeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_owner' - where: equals("log.winlogEeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to change the owner in the security descriptor object' - where: equals("log.winlogEeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'synchronize' - where: equals("log.winlogEeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state. Some object types do not support this access right.' - where: equals("log.winlogEeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'access_sys_sec' - where: equals("log.winlogEeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The ACCESS_SYS_SEC access right controls the ability to get or set the SACL in an security descriptor object.' - where: equals("log.winlogEeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) # Decoding the "eventStatus" field - add: @@ -2554,91 +2493,91 @@ pipeline: params: key: log.statusDescription value: 'Account locked out' - where: equals("log.winlogEventDataStatus", "0xC0000234") + where: equals("log.eventDataStatus", "0xC0000234") - add: function: 'string' params: key: log.statusDescription value: 'Account expired' - where: equals("log.winlogEventDataStatus", "0xC0000193") + where: equals("log.eventDataStatus", "0xC0000193") - add: function: 'string' params: key: log.statusDescription value: 'Clocks out of sync' - where: equals("log.winlogEventDataStatus", "0xC0000133") + where: equals("log.eventDataStatus", "0xC0000133") - add: function: 'string' params: key: log.statusDescription value: 'Password change required' - where: equals("log.winlogEventDataStatus", "0xC0000224") + where: equals("log.eventDataStatus", "0xC0000224") - add: function: 'string' params: key: log.statusDescription value: 'User does not have logon right' - where: equals("log.winlogEventDataStatus", "0xc000015b") + where: equals("log.eventDataStatus", "0xc000015b") - add: function: 'string' params: key: log.statusDescription value: 'Logon failure' - where: equals("log.winlogEventDataStatus", "0xc000006d") + where: equals("log.eventDataStatus", "0xc000006d") - add: function: 'string' params: key: log.statusDescription value: 'Account restriction' - where: equals("log.winlogEventDataStatus", "0xc000006e") + where: equals("log.eventDataStatus", "0xc000006e") - add: function: 'string' params: key: log.statusDescription value: 'An error occurred during logon' - where: equals("log.winlogEventDataStatus", "0xc00002ee") + where: equals("log.eventDataStatus", "0xc00002ee") - add: function: 'string' params: key: log.statusDescription value: 'Password expired' - where: equals("log.winlogEventDataStatus", "0xC0000071") + where: equals("log.eventDataStatus", "0xC0000071") - add: function: 'string' params: key: log.statusDescription value: 'Account disabled' - where: equals("log.winlogEventDataStatus", "0xC0000072") + where: equals("log.eventDataStatus", "0xC0000072") - add: function: 'string' params: key: log.statusDescription value: 'Authentication firewall prohibits logon' - where: equals("log.winlogEventDataStatus", "0xC0000413") + where: equals("log.eventDataStatus", "0xC0000413") - add: function: 'string' params: key: log.statusDescription value: 'Incorrect password' - where: equals("log.winlogEventDataStatus", "0xc000006a") + where: equals("log.eventDataStatus", "0xc000006a") - add: function: 'string' params: key: log.statusDescription value: 'Account does not exist' - where: equals("log.winlogEventDataStatus", "0xc0000064") + where: equals("log.eventDataStatus", "0xc0000064") # Decoding the "eventStatus" field when the event code is 4771 and adding the "statusDescription" - add: @@ -2646,330 +2585,330 @@ pipeline: params: key: log.statusDescription value: 'Customer database entry has expired' - where: equals("log.winlogEventDataStatus", "0x1") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x1") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'The server database entry has expired' - where: equals("log.winlogEventDataStatus", "0x2") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Requested protocol version not supported' - where: equals("log.winlogEventDataStatus", "0x3") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x3") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Customer key encrypted in old master key' - where: equals("log.winlogEventDataStatus", "0x4") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x4") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Server key encrypted with old master key' - where: equals("log.winlogEventDataStatus", "0x5") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x5") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Client not found in Kerberos database' - where: equals("log.winlogEventDataStatus", "0x6") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x6") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Server not found in Kerberos database' - where: equals("log.winlogEventDataStatus", "0x7") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x7") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Multiple principal entries in database' - where: equals("log.winlogEventDataStatus", "0x8") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x8") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'The client or server has a null key' - where: equals("log.winlogEventDataStatus", "0x9") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x9") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Ticket not eligible for postdating' - where: equals("log.winlogEventDataStatus", "0xA") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xA") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Requested start time is later than end time' - where: equals("log.winlogEventDataStatus", "0xB") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xB") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC policy rejects request' - where: equals("log.winlogEventDataStatus", "0xC") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xC") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC cannot accommodate requested option' - where: equals("log.winlogEventDataStatus", "0xD") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xD") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC has no support for encryption type' - where: equals("log.winlogEventDataStatus", "0xE") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xE") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC has no support for checksum type' - where: equals("log.winlogEventDataStatus", "0xF") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xF") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC has no support for padata type' - where: equals("log.winlogEventDataStatus", "0x10") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x10") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC has no support for transited type' - where: equals("log.winlogEventDataStatus", "0x11") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x11") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Clients credentials have been revoked' - where: equals("log.winlogEventDataStatus", "0x12") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x12") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Credentials for server have been revoked' - where: equals("log.winlogEventDataStatus", "0x13") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x13") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'TGT has been revoked' - where: equals("log.winlogEventDataStatus", "0x14") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x14") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Client not yet valid - try again later' - where: equals("log.winlogEventDataStatus", "0x15") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x15") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Server not yet valid - try again later' - where: equals("log.winlogEventDataStatus", "0x16") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x16") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Password has expired' - where: equals("log.winlogEventDataStatus", "0x17") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x17") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Pre-authentication information was invalid' - where: equals("log.winlogEventDataStatus", "0x18") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x18") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Additional pre-authentication required' - where: equals("log.winlogEventDataStatus", "0x19") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x19") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Integrity check on decrypted field failed' - where: equals("log.winlogEventDataStatus", "0x1F") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x1F") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Ticket expired' - where: equals("log.winlogEventDataStatus", "0x20") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x20") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Ticket not yet valid' - where: equals("log.winlogEventDataStatus", "0x21") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x21") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Request is a replay' - where: equals("log.winlogEventDataStatus", "0x22") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x22") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'The ticket is not for us' - where: equals("log.winlogEventDataStatus", "0x23") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x23") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Ticket and authenticator is not match' - where: equals("log.winlogEventDataStatus", "0x24") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x24") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Clock skew too great' - where: equals("log.winlogEventDataStatus", "0x25") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x25") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Incorrect net address' - where: equals("log.winlogEventDataStatus", "0x26") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x26") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Protocol version mismatch' - where: equals("log.winlogEventDataStatus", "0x27") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x27") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Invalid msg type' - where: equals("log.winlogEventDataStatus", "0x28") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x28") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Message stream modified' - where: equals("log.winlogEventDataStatus", "0x29") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x29") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Message out of order' - where: equals("log.winlogEventDataStatus", "0x2A") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2A") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Specified version of key is not available' - where: equals("log.winlogEventDataStatus", "0x2C") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2C") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Service key not available' - where: equals("log.winlogEventDataStatus", "0x2D") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2D") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Mutual authentication failed' - where: equals("log.winlogEventDataStatus", "0x2E") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2E") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Incorrect message direction' - where: equals("log.winlogEventDataStatus", "0x2F") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2F") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Alternative authentication method required' - where: equals("log.winlogEventDataStatus", "0x30") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x30") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Incorrect sequence number in message' - where: equals("log.winlogEventDataStatus", "0x31") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x31") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Inappropriate type of checksum in message' - where: equals("log.winlogEventDataStatus", "0x32") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x32") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Generic error (description in e-text)' - where: equals("log.winlogEventDataStatus", "0x3C") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x3C") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Field is too long for this implementation' - where: equals("log.winlogEventDataStatus", "0x3D") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x3D") && equals("log.eventCode", 4771) - delete: fields: - log.agent - log.host - - log.winlog.computer_name - - log.winlog.event_data - - log.winlog.process + - log.computer_name + - log.data + - log.process - log.metadata - log.event - log.ecs diff --git a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.html b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.html index 094d1cc75..6bf13f5a0 100644 --- a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.html +++ b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.html @@ -182,7 +182,7 @@ diff --git a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts index 399158384..fca5fc706 100644 --- a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts +++ b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts @@ -1,12 +1,11 @@ import {ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core'; import {Subject} from 'rxjs'; -import {debounceTime, distinctUntilChanged, finalize, map, takeUntil, tap} from 'rxjs/operators'; +import {debounceTime, finalize, map, takeUntil, tap} from 'rxjs/operators'; import {ModalService} from '../../../core/modal/modal.service'; import {UtmToastService} from '../../../shared/alert/utm-toast.service'; import { ModalConfirmationComponent } from '../../../shared/components/utm/util/modal-confirmation/modal-confirmation.component'; -import {EncryptService} from '../../../shared/services/util/encrypt.service'; import {ModuleChangeStatusBehavior} from '../../shared/behavior/module-change-status.behavior'; import {IntCreateGroupComponent} from '../../shared/components/int-create-group/int-create-group.component'; import {GroupTypeEnum} from '../../shared/enum/group-type.enum'; @@ -46,10 +45,10 @@ export class IntGenericGroupConfigComponent implements OnInit, OnChanges, OnDest destroy$ = new Subject(); uniqueConfigNameConstrain = false; invalidDomainOrIp = false; + savingConfigs = new Map(); constructor(private utmModuleGroupService: UtmModuleGroupService, private toast: UtmToastService, - private encryptService: EncryptService, private utmModuleGroupConfService: UtmModuleGroupConfService, private modalService: ModalService, private moduleChangeStatusBehavior: ModuleChangeStatusBehavior, @@ -189,7 +188,7 @@ export class IntGenericGroupConfigComponent implements OnInit, OnChanges, OnDest } saveConfig(group: UtmModuleGroupType) { - this.savingConfig = true; + this.savingConfigs.set(group.id, true); const configs = this.changes.keys.filter(change => change.groupId === group.id); this.utmModuleGroupConfService.update({ @@ -197,7 +196,7 @@ export class IntGenericGroupConfigComponent implements OnInit, OnChanges, OnDest keys: configs }).pipe( finalize(() => { - this.savingConfig = false; + this.savingConfigs.set(group.id, false); this.cdr.detectChanges(); }) ).subscribe({ @@ -440,6 +439,7 @@ export class IntGenericGroupConfigComponent implements OnInit, OnChanges, OnDest this.addChange(integrationConfig); } + ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); diff --git a/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.html b/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.html index 6ccd7488b..e28193977 100644 --- a/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.html +++ b/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.html @@ -5,20 +5,14 @@

-

- The Linux agent captures system and application logs and sends them to a UTMStack master server or probe/proxy, - monitors system activity, and executes incident response commands. The UTMStack agents communicate over ports 9000, 9001 and 50051. - Please make sure these ports are open. -

- -
    +
    1. 1 Check pre-installation requirements

        -
      • The UTMStack Linux Agent relies on the local rsyslog service to collect and forward system logs.
      • +
      • The UTMStack agents communicate over ports 9000, 9001 and 50051. Please make sure these ports are open.
    2. @@ -30,7 +24,9 @@

      The following commands contains sensitive information, don't share it.
      - + +

diff --git a/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.ts b/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.ts index a143e9ed1..949b26fd3 100644 --- a/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.ts +++ b/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.ts @@ -13,7 +13,7 @@ export class GuideLinuxAgentComponent implements OnInit { @Input() serverId: number; @Input() version: string; token: string; - architectures = []; + platforms = []; constructor(private federationConnectionService: FederationConnectionService) { } @@ -21,7 +21,6 @@ export class GuideLinuxAgentComponent implements OnInit { this.getToken(); } - getToken() { this.federationConnectionService.getToken().subscribe(response => { if (response.body !== null && response.body !== '') { @@ -29,7 +28,7 @@ export class GuideLinuxAgentComponent implements OnInit { } else { this.token = ''; } - this.loadArchitectures(); + this.loadPlatforms(); }); } @@ -75,26 +74,35 @@ export class GuideLinuxAgentComponent implements OnInit { echo 'UTMStack Agent dependencies removed successfully.'"`; } - private loadArchitectures() { - this.architectures = [ + private loadPlatforms() { + const amd64 = 'utmstack_agent_service_linux_amd64'; + const arm64 = 'utmstack_agent_service_linux_arm64'; + + this.platforms = [ { - id: 1, name: 'Ubuntu / Debian', - install: this.getCommandUbuntu('utmstack_agent_service'), - uninstall: this.getUninstallCommand('utmstack_agent_service'), + id: 1, name: 'Ubuntu / Debian (AMD64)', + install: this.getCommandUbuntu(amd64), + uninstall: this.getUninstallCommand(amd64), shell: '' }, { - id: 2, name: 'Fedora / RedHat', - install: this.getCommandCentos7RedHat('utmstack_agent_service'), - uninstall: this.getUninstallCommand('utmstack_agent_service'), + id: 2, name: 'Ubuntu / Debian (ARM64)', + install: this.getCommandUbuntu(arm64), + uninstall: this.getUninstallCommand(arm64), shell: '' - }/*, + }, { - id: 3, name: 'Centos 8/AlmaLinux', - install: this.getCommandCentos8Almalinux('utmstack_agent_service'), - uninstall: this.getUninstallCommand('utmstack_agent_service'), + id: 3, name: 'Fedora / RedHat (AMD64)', + install: this.getCommandCentos7RedHat(amd64), + uninstall: this.getUninstallCommand(amd64), shell: '' - }*/ + }, + { + id: 4, name: 'Fedora / RedHat (ARM64)', + install: this.getCommandCentos7RedHat(arm64), + uninstall: this.getUninstallCommand(arm64), + shell: '' + } ]; } } diff --git a/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.html b/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.html index 6114adab4..6e1874cf8 100644 --- a/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.html +++ b/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.html @@ -1,33 +1,30 @@

- Installing MacOS agent + Installing macOS agent

-

- The MacOS agent captures system and application logs and sends them to a UTMStack master server or probe/proxy, monitors system activity, and executes incident response commands. - The UTMStack agents communicate over ports 9000, 9001 and 50051. Please make sure these ports are open. -

  1. 1 - Download the utmstack-macos-agent.pkg file. + Check pre-installation requirements

    +
      +
    • Compatible with macOS 11 (Big Sur) or higher on Apple Silicon (M1/M2/M3)
    • +
    • The UTMStack agents communicate over ports 9000, 9001 and 50051. Please make sure these ports are open.
    • +
  2. 2 - Run the utmstack-macos-agent.pkg file. This will download and install the required components for the UTMStack agent. + To install or uninstall the UTMStack agent, open a Terminal and run the following command:

    -
  3. -
  4. -

    - 3 - Use the following command according to the action you wish to perform (install or uninstall): -

    - +
    + The following command contains sensitive information, don't share it. +
    +
diff --git a/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.ts b/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.ts index 01ef208fb..64841d6ee 100644 --- a/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.ts +++ b/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.ts @@ -11,20 +11,18 @@ import { }) export class GuideMacosAgentComponent implements OnInit { @Input() integrationId: number; - @Input() filebeatModule: UtmModulesEnum; - @Input() filebeatModuleName: string; module = UtmModulesEnum; @Input() serverId: number; - architectures = []; + platforms = []; token: string; - constructor(private federationConnectionService: FederationConnectionService,) { } + + constructor(private federationConnectionService: FederationConnectionService) { } ngOnInit() { this.getToken(); } - getToken() { this.federationConnectionService.getToken().subscribe(response => { if (response.body !== null && response.body !== '') { @@ -32,35 +30,41 @@ export class GuideMacosAgentComponent implements OnInit { } else { this.token = ''; } - this.loadArchitectures(); + this.loadPlatforms(); }); } - getCommandARM(installerName: string): string { + getInstallCommand(installerName: string): string { const ip = window.location.host.includes(':') ? window.location.host.split(':')[0] : window.location.host; - return `sudo bash -c "/opt/utmstack/${installerName} ${ip} ${this.token} yes"`; + return `sudo bash -c "mkdir -p /opt/utmstack-macos-agent && \\ +curl -k -o /opt/utmstack-macos-agent/${installerName} \\ +https://${ip}:9001/private/dependencies/agent/${installerName} && \\ +chmod +x /opt/utmstack-macos-agent/${installerName} && \\ +/opt/utmstack-macos-agent/${installerName} install ${ip} ${this.token} yes"`; } - getUninstallCommand(installerName: string): string { - // tslint:disable-next-line:max-line-length - return `sudo bash -c "/opt/utmstack/${installerName}; launchctl bootout system /Library/LaunchDaemons/UTMStackAgent.plist 2>/dev/null; rm /Library/LaunchDaemons/UTMStackAgent.plist; rm -rf /opt/utmstack"`; + return `sudo bash -c "/opt/utmstack-macos-agent/${installerName} uninstall || true; \\ +launchctl bootout system /Library/LaunchDaemons/UTMStackAgent.plist 2>/dev/null || true; \\ +launchctl bootout system /Library/LaunchDaemons/UTMStackUpdater.plist 2>/dev/null || true; \\ +rm -f /Library/LaunchDaemons/UTMStackAgent.plist 2>/dev/null || true; \\ +rm -f /Library/LaunchDaemons/UTMStackUpdater.plist 2>/dev/null || true; \\ +echo 'Removing UTMStack Agent dependencies...' && sleep 10 && \\ +rm -rf /opt/utmstack-macos-agent 2>/dev/null || true; \\ +echo 'UTMStack Agent removed successfully.'"`; } - getDownloadUrl(): string { - const ip = window.location.host.includes(':') ? window.location.host.split(':')[0] : window.location.host; - return `https://${ip}:9001/private/dependencies/agent/utmstack-macos-agent.pkg`; - } + private loadPlatforms() { + const arm64 = 'utmstack_agent_service_darwin_arm64'; - private loadArchitectures() { - this.architectures = [ + this.platforms = [ { - id: 1, name: 'ARM64', - install: this.getCommandARM('utmstack_agent_service install'), - uninstall: this.getUninstallCommand('utmstack_agent_service uninstall'), + id: 1, name: 'ARM64 (Apple Silicon)', + install: this.getInstallCommand(arm64), + uninstall: this.getUninstallCommand(arm64), shell: '' - }, + } ]; } } diff --git a/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts b/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts index 0f855f2d7..6f032090a 100644 --- a/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts +++ b/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts @@ -16,15 +16,6 @@ export class GuideSyslogComponent implements OnInit { @Input() moduleEnum: UtmModulesEnum; @Input() dataType: string; module = UtmModulesEnum; - moduleImages: SyslogModuleImages[] = [ - {module: UtmModulesEnum.FORTIGATE, img: 'fortigate.png'}, - {module: UtmModulesEnum.UFW, img: 'ufw.png'}, - {module: UtmModulesEnum.MIKROTIK, img: 'mikrotik.png'}, - {module: UtmModulesEnum.PALO_ALTO, img: 'paloalto.png'}, - {module: UtmModulesEnum.SONIC_WALL, img: 'sonicwall.png'}, - {module: UtmModulesEnum.DECEPTIVE_BYTES, img: 'deceptivebytes.png'}, - {module: UtmModulesEnum.SOPHOS_XG, img: 'sophosxg.png'} - ]; syslogPorts: SyslogModulePorts[] = [ {module: UtmModulesEnum.FORTIGATE, port: '7005 TCP'}, diff --git a/frontend/src/app/app-module/guides/guide-winlogbeat/guide-winlogbeat.component.ts b/frontend/src/app/app-module/guides/guide-winlogbeat/guide-winlogbeat.component.ts index bca0c2878..114a38ff7 100644 --- a/frontend/src/app/app-module/guides/guide-winlogbeat/guide-winlogbeat.component.ts +++ b/frontend/src/app/app-module/guides/guide-winlogbeat/guide-winlogbeat.component.ts @@ -39,15 +39,15 @@ export class GuideWinlogbeatComponent implements OnInit { this.architectures = [ { id: 1, name: 'AMD64', - install: this.getCommand('utmstack_agent_service.exe'), - uninstall: this.getUninstallCommand('utmstack_agent_service.exe'), - shell: 'Windows Powershell terminal as “ADMINISTRATOR”' + install: this.getCommand('utmstack_agent_service_windows_amd64.exe'), + uninstall: this.getUninstallCommand('utmstack_agent_service_windows_amd64.exe'), + shell: 'Windows Powershell terminal as "ADMINISTRATOR"' }, { id: 2, name: 'ARM64', - install: this.getCommand('utmstack_agent_service_arm64.exe'), - uninstall: this.getUninstallCommand('utmstack_agent_service_arm64.exe'), - shell: 'Windows Powershell terminal as “ADMINISTRATOR”' + install: this.getCommand('utmstack_agent_service_windows_arm64.exe'), + uninstall: this.getUninstallCommand('utmstack_agent_service_windows_arm64.exe'), + shell: 'Windows Powershell terminal as "ADMINISTRATOR"' } ]; } diff --git a/frontend/src/app/app-module/guides/shared/components/agent-install-selector.component.ts b/frontend/src/app/app-module/guides/shared/components/agent-install-selector.component.ts index 6954ce64c..f1e6c50a7 100644 --- a/frontend/src/app/app-module/guides/shared/components/agent-install-selector.component.ts +++ b/frontend/src/app/app-module/guides/shared/components/agent-install-selector.component.ts @@ -11,7 +11,7 @@ import {UtmModulesEnum} from '../../../shared/enum/utm-module.enum';
diff --git a/frontend/src/app/app-module/guides/shared/constant.ts b/frontend/src/app/app-module/guides/shared/constant.ts index 468dd47fc..5a0a93036 100644 --- a/frontend/src/app/app-module/guides/shared/constant.ts +++ b/frontend/src/app/app-module/guides/shared/constant.ts @@ -22,54 +22,69 @@ function createPlatform( path?: string, restart?: string, extraCommands?: string[]): Platform { - return { id, name, command, shell, path, restart, extraCommands }; + return {id, name, command, shell, path, restart, extraCommands}; } export const createPlatforms = ( - windowsCommandAMD64: string, - windowsCommandARM64: string, - linuxCommand: string, - windowsPath?: string, - windowsRestart?: string, - linuxPath?: string, - linuxRestart?: string): Platform[] => [ - createPlatform( - 1, - 'WINDOWS (AMD64)', - windowsCommandAMD64, - WINDOWS_SHELL, - windowsPath, - windowsRestart,[ - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service.exe" ' + - '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + - '-NoNewWindow -Wait' - ] - ), - createPlatform( - 2, - 'WINDOWS (ARM64)', - windowsCommandARM64, - WINDOWS_SHELL, - windowsPath, - windowsRestart, - [ - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_arm64.exe" ' + - '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + - '-NoNewWindow -Wait' - ] - ), - createPlatform( - 3, - 'LINUX', - linuxCommand, - LINUX_SHELL, - linuxPath, - linuxRestart, - [ - `sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service load-tls-certs [YOUR_CERT_PATH] [YOUR_KEY_PATH]"` - ] - ) -]; + windowsCommandAMD64: string, + windowsCommandARM64: string, + linuxCommandAMD64: string, + linuxCommandARM64: string, + windowsPath?: string, + windowsRestart?: string, + linuxPath?: string, + linuxRestart?: string): Platform[] => [ + + createPlatform( + 1, + 'WINDOWS (AMD64)', + windowsCommandAMD64, + WINDOWS_SHELL, + windowsPath, + windowsRestart, [ + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_windows_amd64.exe" ' + + '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + + '-NoNewWindow -Wait' + ] + ), + + createPlatform( + 2, + 'WINDOWS (ARM64)', + windowsCommandARM64, + WINDOWS_SHELL, + windowsPath, + windowsRestart, + [ + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_windows_arm64.exe" ' + + '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + + '-NoNewWindow -Wait' + ] + ), + createPlatform( + 3, + 'LINUX (AMD64)', + linuxCommandAMD64, + LINUX_SHELL, + linuxPath, + linuxRestart, + [ + `sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service load-tls-certs [YOUR_CERT_PATH] [YOUR_KEY_PATH]"` + ] + ), + createPlatform( + 3, + 'LINUX (ARM64)', + linuxCommandARM64, + LINUX_SHELL, + linuxPath, + linuxRestart, + [ + `sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service load-tls-certs [YOUR_CERT_PATH] [YOUR_KEY_PATH]"` + ] + ) + ] +; export const createFileBeatsPlatforms = ( windowsCommand: string, @@ -97,9 +112,10 @@ export const createFileBeatsPlatforms = ( ]; export const PLATFORMS = createPlatforms( - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service.exe" -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\', \'TLS\' -NoNewWindow -Wait\n', - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_arm64.exe" -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\, \'TLS\' -NoNewWindow -Wait\n', - 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service ACTION AGENT_NAME PROTOCOL TLS"' + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_windows_amd64.exe " -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\', \'TLS\' -NoNewWindow -Wait\n', + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_windows_arm64.exe" -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\, \'TLS\' -NoNewWindow -Wait\n', + 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service_linux_amd64 ACTION AGENT_NAME PROTOCOL TLS"', + 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service_linux_arm64 ACTION AGENT_NAME PROTOCOL TLS"' ); export const FILEBEAT_PLATFORMS = createFileBeatsPlatforms( diff --git a/frontend/src/app/assets-discover/assets-view/assets-view.component.html b/frontend/src/app/assets-discover/assets-view/assets-view.component.html index 2823407bb..c2fc7bdd1 100644 --- a/frontend/src/app/assets-discover/assets-view/assets-view.component.html +++ b/frontend/src/app/assets-discover/assets-view/assets-view.component.html @@ -264,13 +264,13 @@
-
+
- Agent {{ agent }} detail + {{ assetSelected.isAgent ? 'Agent' : 'Data Source' }} — {{ assetName }} Details
-
+
+ [asset]="assetSelected" + [hostname]="assetName">
+ + +
+ Name:  + {{assetSelected.displayName}} +
+
+ MAC:  +
+ + {{card}} + +
+
+
+ Status:  + + + {{ !assetSelected.status ? 'ONLINE' : 'OFFLINE' }} + +
+
+ Type:  + + +
+
+ Group Source:  + + +
+
+ Comment:  + +
+
+ Last seen:  + {{ assetSelected.lastInput | date }} +
+
diff --git a/frontend/src/app/assets-discover/assets-view/assets-view.component.scss b/frontend/src/app/assets-discover/assets-view/assets-view.component.scss index e14e1b008..37338a59a 100644 --- a/frontend/src/app/assets-discover/assets-view/assets-view.component.scss +++ b/frontend/src/app/assets-discover/assets-view/assets-view.component.scss @@ -13,3 +13,7 @@ height: 100%; min-height: 0; } + +.min-w-70px { + min-width: 70px; +} diff --git a/frontend/src/app/assets-discover/assets-view/assets-view.component.ts b/frontend/src/app/assets-discover/assets-view/assets-view.component.ts index e5cd403d0..b66991669 100644 --- a/frontend/src/app/assets-discover/assets-view/assets-view.component.ts +++ b/frontend/src/app/assets-discover/assets-view/assets-view.component.ts @@ -19,7 +19,6 @@ import {SortEvent} from '../../shared/directives/sortable/type/sort-event'; import {ChartValueSeparator} from '../../shared/enums/chart-value-separator'; import {ElasticOperatorsEnum} from '../../shared/enums/elastic-operators.enum'; import {IncidentOriginTypeEnum} from '../../shared/enums/incident-response/incident-origin-type.enum'; -import {UtmDatePipe} from '../../shared/pipes/date.pipe'; import {IncidentCommandType} from '../../shared/types/incident/incident-command.type'; import {UtmFieldType} from '../../shared/types/table/utm-field.type'; import {TimeFilterType} from '../../shared/types/time-filter.type'; @@ -44,7 +43,7 @@ import {SourceDataTypeConfigComponent} from '../source-data-type-config/source-d changeDetection: ChangeDetectionStrategy.OnPush }) export class AssetsViewComponent implements OnInit, OnDestroy { - assets$: Observable; + assets: NetScanType[]; // defaultTime: ElasticFilterDefaultTime = new ElasticFilterDefaultTime('now-30d', 'now'); pageWidth = window.innerWidth; @@ -55,7 +54,7 @@ export class AssetsViewComponent implements OnInit, OnDestroy { page = 0; loading = false; itemsPerPage = ITEMS_PER_PAGE; - viewAssetDetail: NetScanType; + assetSelected: NetScanType; sortBy = AssetFieldEnum.ASSET_IS_ALIVE + ',DESC'; assetsFields: UtmFieldType[] = ASSETS_FIELDS; checkbox: any; @@ -83,7 +82,7 @@ export class AssetsViewComponent implements OnInit, OnDestroy { deleting: string[] = []; agentConsole: NetScanType; reasonRun: IncidentCommandType; - agent: string; + assetName: string; noData = false; destroy$ = new Subject(); @@ -280,7 +279,7 @@ export class AssetsViewComponent implements OnInit, OnDestroy { case AssetFieldEnum.ASSET_METRICS: break; default: - this.viewAssetDetail = asset; + this.assetSelected = asset; } } @@ -391,8 +390,12 @@ export class AssetsViewComponent implements OnInit, OnDestroy { getLastInput(asset: NetScanType) { if (asset.dataInputList.length > 0) { - const lastInput = asset.dataInputList[asset.dataInputList.length - 1].timestamp; + + const lastInputStatus = asset.dataInputList[asset.dataInputList.length - 1]; + const lastInput = lastInputStatus.timestamp; + asset.lastInputTimestamp = lastInput; + asset.status = lastInputStatus.down; return this.formatTimestampToDate(lastInput); } @@ -406,17 +409,17 @@ export class AssetsViewComponent implements OnInit, OnDestroy { } toggleAsset(asset: NetScanType) { - if (this.viewAssetDetail && this.viewAssetDetail.id === asset.id) { - this.viewAssetDetail = undefined; + if (this.assetSelected && this.assetSelected.id === asset.id) { + this.assetSelected = undefined; } else { - this.viewAssetDetail = asset; + this.assetSelected = asset; } } viwAgentDetail(event: Event, asset: NetScanType) { event.stopPropagation(); - this.viewAssetDetail = asset; - this.agent = asset.assetName; + this.assetSelected = asset; + this.assetName = asset.assetName && asset.assetName !== '' ? asset.assetName : asset.assetIp; } isSourceConnected(asset: NetScanType, source: UtmDataInputStatus): boolean { @@ -439,7 +442,7 @@ export class AssetsViewComponent implements OnInit, OnDestroy { } closeDetail() { - this.agent = undefined; + this.assetName = undefined; this.reasonRun.reason = ''; } diff --git a/frontend/src/app/assets-discover/shared/components/filters/asset-generic-filter/asset-generic-filter.component.html b/frontend/src/app/assets-discover/shared/components/filters/asset-generic-filter/asset-generic-filter.component.html index e37d85a4c..33b44a149 100644 --- a/frontend/src/app/assets-discover/shared/components/filters/asset-generic-filter/asset-generic-filter.component.html +++ b/frontend/src/app/assets-discover/shared/components/filters/asset-generic-filter/asset-generic-filter.component.html @@ -5,16 +5,12 @@
-
- -
diff --git a/frontend/src/app/assets-discover/shared/components/filters/asset-generic-filter/asset-generic-filter.component.ts b/frontend/src/app/assets-discover/shared/components/filters/asset-generic-filter/asset-generic-filter.component.ts index a83f7b326..59157aa0f 100644 --- a/frontend/src/app/assets-discover/shared/components/filters/asset-generic-filter/asset-generic-filter.component.ts +++ b/frontend/src/app/assets-discover/shared/components/filters/asset-generic-filter/asset-generic-filter.component.ts @@ -43,10 +43,12 @@ export class AssetGenericFilterComponent implements OnInit, AfterViewInit { } ngOnInit() { + this.requestParams = { ...this.requestParams, prop: this.fieldFilter.field }; + this.fieldValues$ = this.assetGenericFilterService.onRefresh$ .pipe( filter(filterData => !!filterData && filterData.refresh && filterData.fieldFilter === this.fieldFilter.field), diff --git a/frontend/src/app/assets-discover/shared/types/net-scan.type.ts b/frontend/src/app/assets-discover/shared/types/net-scan.type.ts index 42212b4e4..2a30d2588 100644 --- a/frontend/src/app/assets-discover/shared/types/net-scan.type.ts +++ b/frontend/src/app/assets-discover/shared/types/net-scan.type.ts @@ -39,4 +39,6 @@ export class NetScanType { sortKey?: string; lastInput: string; lastInputTimestamp: number; + status?: boolean; + isAgent?: boolean; } diff --git a/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html b/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html index f99dcb24d..5372cdeec 100644 --- a/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html +++ b/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html @@ -1,5 +1,5 @@
-
+
{{dashboard.name}}
@@ -17,9 +17,9 @@
{{dashboard.name}}
- + {{dashboard.name}}
-
{ - this.dashboard = this.layoutService.dashboard; - this.filters = this.dashboard && this.dashboard.filters ? JSON.parse(this.dashboard.filters) : []; - this.loadingVisualizations = false; - }), - map(data => data.response) + + this.layout$ = this.activatedRoute.data.pipe( + map(data => { + const response = data && data.response ? data.response : []; + + this.dashboard = this.layoutService.dashboard ? this.layoutService.dashboard : null; + + try { + this.filters = this.dashboard && this.dashboard.filters + ? JSON.parse(this.dashboard.filters) + : []; + } catch { + this.filters = []; + } + + this.loadingVisualizations = false; + + return response; + }) ); + this.dashboardBehavior.$filterDashboard.subscribe(dashboardFilter => { if (dashboardFilter) { mergeParams(dashboardFilter.filter, this.filtersValues).then(newFilters => { diff --git a/frontend/src/app/data-management/alert-management/shared/services/open-alerts.service.ts b/frontend/src/app/data-management/alert-management/shared/services/open-alerts.service.ts index 81afe4da0..880718277 100644 --- a/frontend/src/app/data-management/alert-management/shared/services/open-alerts.service.ts +++ b/frontend/src/app/data-management/alert-management/shared/services/open-alerts.service.ts @@ -32,10 +32,12 @@ export class OpenAlertsService implements OnDestroy { takeUntil(this.destroy$), switchMap(() => this.alertOpenStatusService.getOpenAlert()), tap((response) => { - this.openAlerts = response.body; - this.localStorage.store(OPEN_ALERTS_KEY, this.openAlerts); - if (this.openAlerts >= 0 && this.openAlerts !== this.openAlerts) { - this.openAlertsBehaviorSubject.next(this.openAlerts); + const newValue = response.body; + + if (newValue !== this.openAlerts) { + this.openAlerts = newValue; + this.openAlertsBehaviorSubject.next(newValue); + this.localStorage.store(OPEN_ALERTS_KEY, newValue); } }), catchError((err) => { diff --git a/frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.ts b/frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.ts index 05adf6938..67f760466 100644 --- a/frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.ts +++ b/frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.ts @@ -18,6 +18,7 @@ import {VisualizationService} from '../shared/services/visualization.service'; import {VisualizationCreateComponent} from '../visualization-create/visualization-create.component'; import {VisualizationDeleteComponent} from '../visualization-delete/visualization-delete.component'; import {VisualizationImportComponent} from '../visualization-import/visualization-import.component'; +import {UtmToastService} from "../../../shared/alert/utm-toast.service"; @Component({ selector: 'app-visualization-list', @@ -54,7 +55,8 @@ export class VisualizationListComponent implements OnInit { private visualizationService: VisualizationService, private spinner: NgxSpinnerService, private router: Router, - private activatedRoute: ActivatedRoute) { + private activatedRoute: ActivatedRoute, + private toastService: UtmToastService) { } ngOnInit() { @@ -118,7 +120,7 @@ export class VisualizationListComponent implements OnInit { getVisualizationList() { this.visualizationService.query(this.requestParams).subscribe( (res: HttpResponse) => this.onSuccess(res.body, res.headers), - (res: HttpResponse) => this.onError(res.body) + (res: HttpResponse) => this.onError(res) ); } @@ -200,7 +202,8 @@ export class VisualizationListComponent implements OnInit { } private onError(error) { - // this.alertService.error(error.error, error.message, null); + this.toastService.showError('Error', 'An error occurred while fetching visualizations'); + this.loading = false; } } diff --git a/frontend/src/app/incident-response/shared/component/agent-sidebar/agent-sidebar.component.ts b/frontend/src/app/incident-response/shared/component/agent-sidebar/agent-sidebar.component.ts index b1e39fd91..c3dac6220 100644 --- a/frontend/src/app/incident-response/shared/component/agent-sidebar/agent-sidebar.component.ts +++ b/frontend/src/app/incident-response/shared/component/agent-sidebar/agent-sidebar.component.ts @@ -24,10 +24,13 @@ export class AgentSidebarComponent implements OnInit { } searchAgent($event: string | number) { + const searchValue = $event.toString().trim(); + const searchQuery = searchValue ? `hostname.Contain=${searchValue}` : ''; + this.agentSidebarService.loadData({ ...this.request, page: 0, - searchQuery: $event.toString() + searchQuery }); } diff --git a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.html b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.html index acc79bedf..9b27fc43a 100644 --- a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.html +++ b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.html @@ -1,4 +1,4 @@ -
+
Read the logs filter documentation at
-
+
-
- - -
-
-
- - - -
- Data type is required. +
+ + +
+
+
+ + + +
+ Data type is required. +
-
-
- - -
-
- - -
+
+ +
+ + +
+
+ +
+ + +
diff --git a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss index 98651c800..070803090 100644 --- a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss +++ b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss @@ -1,21 +1,121 @@ -:host ::ng-deep json-editor, -:host ::ng-deep json-editor .jsoneditor, -:host ::ng-deep json-editor > div, -:host ::ng-deep json-editor jsoneditor-outer { - height: 90% !important; - border: none !important; +:host { + display: block; + height: auto; } +.template-editor { + height: 800px!important; +} + +.logstash-filter-wrapper { + display: flex; + flex-direction: column; + width: 100%; + height: auto; + padding: 0.5rem; + gap: 1rem; +} + +.logstash-filter-form { + display: flex; + flex-direction: column; + flex: 1; + gap: 1rem; +} + +.yaml-editor-group { + display: flex; + flex-direction: column; + flex: 1; + min-height: 600px; + margin: 0 !important; + + label { + margin-bottom: 0.5rem; + font-weight: 500; + } +} + +.yaml-editor-container { + border: 1px solid #d9d9d9; + border-radius: 4px; + overflow: hidden; + height: 600px; + width: 100%; + flex: 1; + + ::ng-deep { + .monaco-editor { + width: 100% !important; + height: 100% !important; + } + + .monaco-editor-background { + background-color: #f5f5f5 !important; + } + + .overflow-guard { + width: 100% !important; + height: 100% !important; + } + + .monaco-scrollable-element { + width: 100% !important; + height: 100% !important; + } + + .monaco-editor.readonly { + background-color: #f0f0f0 !important; + opacity: 0.8 !important; + } -::ng-deep { - .jsoneditor { - .jsoneditor-menu { - background-color: #232f3e !important; - border-bottom: 0 solid transparent !important; + .monaco-editor.readonly .monaco-editor-background { + background-color: #f0f0f0 !important; + } + + .mtk7 { + color: #2d7a1a !important; + font-weight: 600 !important; + } + + .mtk22 { + color: #0055cc !important; + font-weight: 600 !important; + } + + .mtk1, + .mtk5 { + color: #666666 !important; + } + + .mtk3 { + color: #c26e00 !important; } } +} - .pico-modal-header { - background-color: #232f3e !important; +.form-actions { + flex-shrink: 0; + margin-top: 1rem !important; +} + +@media screen and (max-height: 1000px) { + .yaml-editor-group { + min-height: 500px; + } + + .yaml-editor-container { + height: 500px; } } + +@media screen and (min-height: 1200px) { + .yaml-editor-group { + min-height: 700px; + } + + .yaml-editor-container { + height: 700px; + } +} + diff --git a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.ts b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.ts index 31b54d600..0d2529494 100644 --- a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.ts +++ b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.ts @@ -1,4 +1,4 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output, OnChanges, SimpleChanges, ViewChild, AfterViewInit} from '@angular/core'; import {Observable} from 'rxjs'; import {DataType} from '../../../rule-management/models/rule.model'; import {DataTypeService} from '../../../rule-management/services/data-type.service'; @@ -12,13 +12,15 @@ import {UtmPipeline} from '../../shared/types/logstash-stats.type'; templateUrl: './logstash-filter-create.component.html', styleUrls: ['./logstash-filter-create.component.scss'] }) -export class LogstashFilterCreateComponent implements OnInit { +export class LogstashFilterCreateComponent implements OnInit, OnChanges { @Output() filterEditClose = new EventEmitter(); @Output() close = new EventEmitter(); @Input() filter: LogstashFilterType; @Input() pipeline: UtmPipeline; @Input() dataType: DataType; @Input() disable = false; + @ViewChild('editorContainer') editorContainer: any; + types$: Observable; daTypeRequest: {page: number, size: number} = { page: -1, @@ -29,6 +31,24 @@ export class LogstashFilterCreateComponent implements OnInit { show = true; error = false; loadingDataTypes = false; + private editorInstance: any; + + editorOptions: any = { + theme: 'vs-light', + language: 'yaml', + automaticLayout: true, + fontSize: 13, + fontFamily: 'Courier New, Monaco, Menlo, monospace', + lineNumbers: 'on', + wordWrap: 'on', + minimap: { enabled: false }, + scrollBeyondLastLine: false, + formatOnPaste: true, + formatOnType: true, + tabSize: 2, + insertSpaces: true, + readOnly: false + }; constructor(private logstashFilterService: LogstashService, private utmToastService: UtmToastService, @@ -42,7 +62,33 @@ export class LogstashFilterCreateComponent implements OnInit { this.types$ = this.dataTypeService.type$; this.loadDataTypes(); + this.updateEditorOptions(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes && changes['disable']) { + this.updateEditorOptions(); + } + } + + private forceEditorLayout(): void { + try { + if (this.editorContainer) { + const editorInstance = (this.editorContainer as any).editor; + if (editorInstance && editorInstance.layout) { + editorInstance.layout(); + } + } + } catch (e) { + console.warn('Error al forzar layout del editor Monaco:', e); + } + } + private updateEditorOptions(): void { + this.editorOptions = { + ...this.editorOptions, + readOnly: this.disable + }; } @@ -94,4 +140,8 @@ export class LogstashFilterCreateComponent implements OnInit { trackByFn(type: DataType) { return type.id; } + + onEditorChange(value: string): void { + this.filter.logstashFilter = value; + } } diff --git a/frontend/src/app/logstash/logstash-filters/logstash-filters.component.html b/frontend/src/app/logstash/logstash-filters/logstash-filters.component.html index f2e37d578..aef7de0ea 100644 --- a/frontend/src/app/logstash/logstash-filters/logstash-filters.component.html +++ b/frontend/src/app/logstash/logstash-filters/logstash-filters.component.html @@ -20,9 +20,9 @@
Pipeline filters
-
+
-
+
-
-

+
+ +
+ + diff --git a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.scss b/frontend/src/app/rule-management/app-rule/components/rule-view/rule-view.component.scss similarity index 51% rename from frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.scss rename to frontend/src/app/rule-management/app-rule/components/rule-view/rule-view.component.scss index 2025c4be1..992acd4a6 100644 --- a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.scss +++ b/frontend/src/app/rule-management/app-rule/components/rule-view/rule-view.component.scss @@ -3,9 +3,6 @@ app-utm-modal-header { top: 0; z-index: 10; } -.rule-view-container { - max-height: 650px; -} .copy-btn { border: none; background: transparent; @@ -35,29 +32,6 @@ app-utm-modal-header { } } -.yaml-container { - width: 100%; - background: #ffffff; - padding: 16px; - border-radius: 6px; - border: 1px solid #e5e7eb; -} - -.yaml-preview { - white-space: pre-wrap; - font-family: "Fira Code", monospace; - font-size: 14px; -} - -::ng-deep .yaml-key { - color: #0277bd; - font-weight: 600; -} - -::ng-deep .yaml-value { - color: #FF6B6B; -} - .sub-header { background: transparent; border: none; @@ -110,3 +84,82 @@ app-utm-modal-header { } } +.rule-view-container { + border: 1px solid #d9d9d9; + border-radius: 4px; + overflow: hidden; + height: 650px; + width: 100%; + flex: 1; + + ::ng-deep { + .monaco-editor { + width: 100% !important; + height: 100% !important; + } + + .monaco-editor-background { + background-color: #f5f5f5 !important; + } + + .overflow-guard { + width: 100% !important; + height: 100% !important; + overflow: hidden !important; + } + + .monaco-scrollable-element { + width: 100% !important; + height: 100% !important; + overflow-y: auto !important; + overflow-x: hidden !important; + } + + .monaco-scrollable-element.vertical { + overflow-y: auto !important; + } + + + .scrollbar { + &.vertical { + .slider { + background: rgba(0, 0, 0, 0.2) !important; + border-radius: 4px !important; + + &:hover { + background: rgba(0, 0, 0, 0.4) !important; + } + } + } + } + + .monaco-editor.readonly { + background-color: #f0f0f0 !important; + opacity: 0.8 !important; + } + + .monaco-editor.readonly .monaco-editor-background { + background-color: #f0f0f0 !important; + } + + .mtk7 { + color: #2d7a1a !important; + font-weight: 600 !important; + } + + .mtk22 { + color: #0055cc !important; + font-weight: 600 !important; + } + + .mtk1, + .mtk5 { + color: #666666 !important; + } + + .mtk3 { + color: #c26e00 !important; + } + } +} + diff --git a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.ts b/frontend/src/app/rule-management/app-rule/components/rule-view/rule-view.component.ts similarity index 66% rename from frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.ts rename to frontend/src/app/rule-management/app-rule/components/rule-view/rule-view.component.ts index d9c0696ba..719bfd606 100644 --- a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.ts +++ b/frontend/src/app/rule-management/app-rule/components/rule-view/rule-view.component.ts @@ -1,4 +1,4 @@ -import {Component, Input, OnInit} from '@angular/core'; +import {Component, Input} from '@angular/core'; import * as yaml from 'js-yaml'; import {mapRuleToYaml} from '../../../../shared/components/utm/util/utm-file-upload/shared/rule-yaml.mapper'; import {Rule} from '../../../models/rule.model'; @@ -8,14 +8,34 @@ import {Rule} from '../../../models/rule.model'; templateUrl: './rule-view.component.html', styleUrls: ['./rule-view.component.scss'], }) -export class RuleViewComponent implements OnInit { +export class RuleViewComponent { @Input() rowDocument: Rule; copied = false; - - ngOnInit() { - console.log('Rule received:', this.rowDocument); - } + editorOptions: any = { + theme: 'vs-light', + language: 'yaml', + automaticLayout: true, + fontSize: 13, + lineHeight: 24, + fontFamily: 'Courier New, Monaco, Menlo, monospace', + lineNumbers: 'on', + wordWrap: 'on', + minimap: { enabled: false }, + scrollBeyondLastLine: false, + formatOnPaste: true, + formatOnType: true, + tabSize: 2, + insertSpaces: true, + readOnly: true, + scrollbar: { + vertical: 'auto', + horizontal: 'hidden', + useShadows: true, + verticalSliderSize: 12, + horizontalSliderSize: 0 + } + }; get yamlString(): string { try { @@ -33,14 +53,6 @@ export class RuleViewComponent implements OnInit { } } - get yamlHighlighted(): string { - return this.yamlString - .replace(/^(\s*)([a-zA-Z0-9_]+):/gm, '$1$2:') - .replace(/: (.*)/g, ': $1') - .replace(/-\s+(.*)/g, '- $1') - .replace(/^\s{2,}(.+)/gm, ' $1'); - } - exportYaml() { const yamlContent = this.yamlString; diff --git a/frontend/src/app/rule-management/rule-management.module.ts b/frontend/src/app/rule-management/rule-management.module.ts index 6d4b76f2b..02a2c17f8 100644 --- a/frontend/src/app/rule-management/rule-management.module.ts +++ b/frontend/src/app/rule-management/rule-management.module.ts @@ -37,7 +37,7 @@ import {AddReferenceComponent} from './app-rule/components/reference/add-referen import {RuleGenericFilterComponent} from './app-rule/components/rule-generic-filter/rule-generic-filter.component'; import {RuleFieldComponent} from './app-rule/components/rule-list/components/rule-field/rule-field.component'; import {RuleListComponent} from './app-rule/components/rule-list/rule-list.component'; -import {RuleViewComponent} from './app-rule/components/see-rule/rule-view.component' +import {RuleViewComponent} from './app-rule/components/rule-view/rule-view.component' import {RuleManagementRoutingModule} from './rule-management.routing.module'; import { DataTypeService } from './services/data-type.service'; import {FilterService} from './services/filter.service'; @@ -46,6 +46,7 @@ import {RuleService} from './services/rule.service'; import {RulesResolverService} from './services/rules.resolver.service'; import {GenericFilterComponent} from './share/generic-filter/generic-filter.component'; import {RuleDetailComponent} from "./app-rule/components/rule-list/components/rule-detail/rule-detail.component"; +import {MonacoEditorModule} from "ngx-monaco-editor"; @NgModule({ @@ -87,7 +88,8 @@ import {RuleDetailComponent} from "./app-rule/components/rule-list/components/ru NgSelectModule, InfiniteScrollModule, FileBrowserModule, - FormsModule + FormsModule, + MonacoEditorModule ], providers: [ RuleService, diff --git a/frontend/src/app/shared/components/utm/config/app-config-sections/app-config-sections.component.html b/frontend/src/app/shared/components/utm/config/app-config-sections/app-config-sections.component.html index 2ed339dbc..bc3288290 100644 --- a/frontend/src/app/shared/components/utm/config/app-config-sections/app-config-sections.component.html +++ b/frontend/src/app/shared/components/utm/config/app-config-sections/app-config-sections.component.html @@ -28,7 +28,7 @@
{{section.section | titlecase}}
class="mr-2" *ngIf="section.shortName === sectionType[sectionType.EMAIL]"> - c.confParamShort !== 'utmstack.tfa.enable'); + if (this.isOnline && this.section.id === this.sectionType.INSTANCE_REGISTRATION) { return this.configs.filter( c => c.confParamShort !== 'utmstack.instance.auth'); } else { @@ -328,10 +331,6 @@ export class AppConfigSectionsComponent implements OnInit, OnDestroy { } } - isEnabledTfa(): boolean { - return this.configs.some(conf => conf.confParamShort === 'utmstack.tfa.enable' && conf.confParamValue === 'true'); - } - serializeConfigValue(conf: SectionConfigParamType): string { if (conf.confParamDatatype === ConfigDataTypeEnum.Cron) { return JSON.stringify(conf.confParamValue); diff --git a/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts b/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts index dfdc55190..d6448b479 100644 --- a/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts +++ b/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts @@ -47,7 +47,7 @@ export class UtmAgentConnectComponent implements OnInit, OnChanges { ip: this.asset.assetIp, hostname: this.asset.assetName, os: this.asset.assetOs, - status: AgentStatusEnum.OFFLINE, + status: !this.asset.status ? AgentStatusEnum.ONLINE : AgentStatusEnum.OFFLINE, platform: this.asset.assetOsPlatform, version: this.asset.assetOsMinorVersion, agentKey: '', diff --git a/frontend/src/app/shared/components/utm/util/utm-file-upload/utm-file-upload.component.html b/frontend/src/app/shared/components/utm/util/utm-file-upload/utm-file-upload.component.html index e1e8b3a3b..d5ecfdfb4 100644 --- a/frontend/src/app/shared/components/utm/util/utm-file-upload/utm-file-upload.component.html +++ b/frontend/src/app/shared/components/utm/util/utm-file-upload/utm-file-upload.component.html @@ -6,7 +6,7 @@
Drag file or click here to select {{ hidden type="file"> - {{ 'You can upload up to ' + maxFiles + 'files only.'}} + {{ 'You can upload up to ' + maxFiles + ' files only.'}}
diff --git a/installer/docker/compose.go b/installer/docker/compose.go index 81cc43247..0e8077150 100644 --- a/installer/docker/compose.go +++ b/installer/docker/compose.go @@ -199,6 +199,28 @@ func (c *Compose) Populate(conf *config.Config, stack *StackConfig) error { backendMem := stack.ServiceResources["backend"].AssignedMemory backendMin := stack.ServiceResources["backend"].MinMemory + backendEnv := []string{ + "SERVER_NAME=" + conf.ServerName, + "DB_USER=postgres", + "DB_PASS=" + conf.Password, + "DB_HOST=postgres", + "DB_PORT=5432", + "DB_NAME=utmstack", + "ELASTICSEARCH_HOST=node1", + "ELASTICSEARCH_PORT=9200", + "INTERNAL_KEY=" + conf.InternalKey, + "ENCRYPTION_KEY=" + conf.InternalKey, + "GRPC_AGENT_MANAGER_HOST=agentmanager", + "GRPC_AGENT_MANAGER_PORT=9000", + "EVENT_PROCESSOR_HOST=event-processor-manager", + "EVENT_PROCESSOR_PORT=9002", + } + + // Disable TFA in dev and rc environments + if conf.Branch == "dev" || conf.Branch == "rc" { + backendEnv = append(backendEnv, "APP_TFA_ENABLED=false") + } + c.Services["backend"] = Service{ Image: utils.PointerOf[string]("ghcr.io/utmstack/utmstack/backend:${UTMSTACK_TAG}"), DependsOn: []string{ @@ -206,22 +228,7 @@ func (c *Compose) Populate(conf *config.Config, stack *StackConfig) error { "node1", "agentmanager", }, - Environment: []string{ - "SERVER_NAME=" + conf.ServerName, - "DB_USER=postgres", - "DB_PASS=" + conf.Password, - "DB_HOST=postgres", - "DB_PORT=5432", - "DB_NAME=utmstack", - "ELASTICSEARCH_HOST=node1", - "ELASTICSEARCH_PORT=9200", - "INTERNAL_KEY=" + conf.InternalKey, - "ENCRYPTION_KEY=" + conf.InternalKey, - "GRPC_AGENT_MANAGER_HOST=agentmanager", - "GRPC_AGENT_MANAGER_PORT=9000", - "EVENT_PROCESSOR_HOST=event-processor-manager", - "EVENT_PROCESSOR_PORT=9002", - }, + Environment: backendEnv, Volumes: []string{ stack.DataSources + ":/etc/utmstack", conf.UpdatesFolder + ":/updates", diff --git a/installer/go.mod b/installer/go.mod index 0a6f803e3..bfb952ff7 100644 --- a/installer/go.mod +++ b/installer/go.mod @@ -3,11 +3,11 @@ module github.com/utmstack/UTMStack/installer go 1.25.1 require ( - github.com/cloudfoundry/gosigar v1.3.112 + github.com/cloudfoundry/gosigar v1.3.116 github.com/docker/docker v28.5.2+incompatible github.com/kardianos/service v1.2.4 github.com/levigross/grequests v0.0.0-20250606031859-3f3c12e4e704 - github.com/lib/pq v1.10.9 + github.com/lib/pq v1.11.2 github.com/shirou/gopsutil/v3 v3.24.5 github.com/threatwinds/logger v1.2.3 github.com/utmstack/license-manager-sdk v0.1.0 @@ -77,7 +77,7 @@ require ( golang.org/x/arch v0.23.0 // indirect golang.org/x/crypto v0.47.0 // indirect golang.org/x/net v0.49.0 // indirect - golang.org/x/sys v0.40.0 // indirect + golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.41.0 // indirect diff --git a/installer/go.sum b/installer/go.sum index 5c0d8e6a2..1215bbd73 100644 --- a/installer/go.sum +++ b/installer/go.sum @@ -16,8 +16,8 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cloudfoundry/gosigar v1.3.112 h1:cGGZ2sj1GKyiwSxzouIR7ATNbgAkC4zqwWDxYQ2ObPc= -github.com/cloudfoundry/gosigar v1.3.112/go.mod h1:Ldc+tVw3dfqPwasZ9om1LT2aRwpjC1eFfbWKfv2WbDI= +github.com/cloudfoundry/gosigar v1.3.116 h1:CCl5yCoHaIC6K7TgERlPql7BerCpT5DPgdYYA5Hh09c= +github.com/cloudfoundry/gosigar v1.3.116/go.mod h1:qDRZlMlpxT/tmEjp0ote3QApxQ102Xn3qTImzxSjXyc= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -93,8 +93,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/levigross/grequests v0.0.0-20250606031859-3f3c12e4e704 h1:iH9CoV+Eoc3eeaCTt8qCU8/Qod7HdRKHlytTmOCIutw= github.com/levigross/grequests v0.0.0-20250606031859-3f3c12e4e704/go.mod h1:qnB03x2PLQFGb5PZv0m87zJrVuvrHUuvZMlHTUuZVnM= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -207,8 +207,8 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= diff --git a/installer/install.go b/installer/install.go index a70cdb02e..37a0ce2f4 100644 --- a/installer/install.go +++ b/installer/install.go @@ -34,7 +34,7 @@ func Install() error { return err } - pass, err := setup.Apply(version.Version) + pass, err := setup.Apply(version.Version, false) if err != nil { return fmt.Errorf("error applying setup: %v", err) } diff --git a/installer/setup/apply.go b/installer/setup/apply.go index 0e0cecef2..b23cff57b 100644 --- a/installer/setup/apply.go +++ b/installer/setup/apply.go @@ -12,7 +12,7 @@ import ( "github.com/utmstack/UTMStack/installer/utils" ) -func Apply(version string) (string, error) { +func Apply(version string, updating bool) (string, error) { cnf := config.GetConfig() fmt.Println("Generating Stack configuration...") @@ -256,10 +256,12 @@ func Apply(version string) (string, error) { fmt.Println(" [OK]") } - fmt.Print("Waiting for Backend to be ready. This may take a while.") + if !updating { + fmt.Print("Waiting for Backend to be ready. This may take a while.") - if err := services.Backend(); err != nil { - return "", err + if err := services.Backend(); err != nil { + return "", err + } } fmt.Println(" [OK]") diff --git a/installer/updater/service.go b/installer/updater/service.go index 926b04405..0195b2935 100644 --- a/installer/updater/service.go +++ b/installer/updater/service.go @@ -54,7 +54,7 @@ func (p *program) run() { config.Logger().Info("Applying pending update: %s-%s", pendingUpdate.Version, pendingUpdate.Edition) // Apply setup with the pending version - if _, err := setup.Apply(pendingUpdate.Version); err != nil { + if _, err := setup.Apply(pendingUpdate.Version, true); err != nil { config.Logger().ErrorF("error applying setup for version %s: %v", pendingUpdate.Version, err) } else { config.Logger().Info("Successfully applied update %s", pendingUpdate.Version) diff --git a/installer/utils/secret.go b/installer/utils/secret.go index 378c48513..03e24a7d6 100644 --- a/installer/utils/secret.go +++ b/installer/utils/secret.go @@ -1,23 +1,20 @@ package utils import ( - "crypto/rand" - "math/big" + "math/rand" ) func GenerateSecret(size int) string { var characters = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") - if size <= 0 { - return "" - } - result := make([]rune, size) - for i := range result { - num, err := rand.Int(rand.Reader, big.NewInt(int64(len(characters)))) - if err != nil { - panic(err) // Consider returning error if signature allows, but keeping panic for now as panic on CSPRNG fail is reasonable for secret gen + var s string + for { + if len(s) >= size { + break } - result[i] = characters[num.Int64()] + + s += string(characters[rand.Intn(len(characters))]) } - return string(result) + + return s } diff --git a/plugins/alerts/go.mod b/plugins/alerts/go.mod index 3ec1e0b45..37e0a8559 100644 --- a/plugins/alerts/go.mod +++ b/plugins/alerts/go.mod @@ -3,7 +3,7 @@ module github.com/utmstack/UTMStack/plugins/alerts go 1.25.5 require ( - github.com/threatwinds/go-sdk v1.1.13 + github.com/threatwinds/go-sdk v1.1.15 github.com/tidwall/gjson v1.18.0 google.golang.org/protobuf v1.36.11 ) diff --git a/plugins/alerts/go.sum b/plugins/alerts/go.sum index 123356719..fb814cb43 100644 --- a/plugins/alerts/go.sum +++ b/plugins/alerts/go.sum @@ -88,8 +88,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.13 h1:uOKaET5Pvxso+tYSevBiF8n6HP04oY5uf8K9uv+bYGk= -github.com/threatwinds/go-sdk v1.1.13/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/aws/go.mod b/plugins/aws/go.mod index d85c1dfa4..f76bb3dae 100644 --- a/plugins/aws/go.mod +++ b/plugins/aws/go.mod @@ -4,10 +4,10 @@ go 1.25.5 require ( github.com/aws/aws-sdk-go-v2 v1.41.1 - github.com/aws/aws-sdk-go-v2/config v1.32.7 - github.com/aws/aws-sdk-go-v2/credentials v1.19.7 + github.com/aws/aws-sdk-go-v2/config v1.32.8 + github.com/aws/aws-sdk-go-v2/credentials v1.19.8 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.9 + github.com/threatwinds/go-sdk v1.1.15 ) require ( @@ -33,20 +33,20 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -54,7 +54,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -66,9 +65,9 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/grpc v1.78.0 + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/plugins/aws/go.sum b/plugins/aws/go.sum index b0cc86430..24f0c5b67 100644 --- a/plugins/aws/go.sum +++ b/plugins/aws/go.sum @@ -6,10 +6,10 @@ github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6ce github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= -github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY= -github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY= -github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= -github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= +github.com/aws/aws-sdk-go-v2/config v1.32.8 h1:iu+64gwDKEoKnyTQskSku72dAwggKI5sV6rNvgSMpMs= +github.com/aws/aws-sdk-go-v2/config v1.32.8/go.mod h1:MI2XvA+qDi3i9AJxX1E2fu730syEBzp/jnXrjxuHwgI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.8 h1:Jp2JYH1lRT3KhX4mshHPvVYsR5qqRec3hGvEarNYoR0= +github.com/aws/aws-sdk-go-v2/credentials v1.19.8/go.mod h1:fZG9tuvyVfxknv1rKibIz3DobRaFw1Poe8IKtXB3XYY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= @@ -28,8 +28,8 @@ github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4 github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 h1:0jbJeuEHlwKJ9PfXtpSFc4MF+WIWORdhN1n30ITZGFM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= @@ -40,14 +40,16 @@ github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uS github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -70,8 +72,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -109,8 +111,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -118,13 +118,12 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -143,16 +142,16 @@ github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -174,12 +173,12 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plugins/azure/go.mod b/plugins/azure/go.mod index a2bf8c395..7af3ef831 100644 --- a/plugins/azure/go.mod +++ b/plugins/azure/go.mod @@ -3,17 +3,17 @@ module github.com/utmstack/UTMStack/plugins/azure go 1.25.5 require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/v2 v2.0.1 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.9 - google.golang.org/grpc v1.78.0 + github.com/threatwinds/go-sdk v1.1.15 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) require ( cel.dev/expr v0.25.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/go-amqp v1.5.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect @@ -21,7 +21,7 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -29,7 +29,7 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -41,7 +41,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -54,8 +53,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/plugins/azure/go.sum b/plugins/azure/go.sum index f07ebba16..8041018ed 100644 --- a/plugins/azure/go.sum +++ b/plugins/azure/go.sum @@ -26,6 +26,8 @@ github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uS github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= @@ -36,8 +38,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -64,8 +66,8 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -109,8 +111,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -118,13 +118,12 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -143,16 +142,16 @@ github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -174,12 +173,12 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plugins/azure/main.go b/plugins/azure/main.go index 1d53a0280..28712e7c6 100644 --- a/plugins/azure/main.go +++ b/plugins/azure/main.go @@ -419,67 +419,7 @@ func processPartition(ctx context.Context, pc *azeventhubs.ProcessorPartitionCli } for _, event := range events { - var logData map[string]any - if err := json.Unmarshal(event.Body, &logData); err != nil { - _ = catcher.Error("cannot parse event body", err, map[string]any{ - "group": groupName, - "partitionID": pc.PartitionID(), - "process": "plugin_com.utmstack.azure", - }) - continue - } - - if records, ok := logData["records"].([]any); ok && len(records) > 0 { - for _, record := range records { - recordMap, ok := record.(map[string]any) - if !ok { - _ = catcher.Error("invalid record format in records array", nil, map[string]any{ - "group": groupName, - "partitionID": pc.PartitionID(), - "process": "plugin_com.utmstack.azure", - }) - continue - } - - jsonLog, err := json.Marshal(recordMap) - if err != nil { - _ = catcher.Error("cannot encode record to JSON", err, map[string]any{ - "group": groupName, - "partitionID": pc.PartitionID(), - "process": "plugin_com.utmstack.azure", - }) - continue - } - - _ = plugins.EnqueueLog(&plugins.Log{ - Id: uuid.New().String(), - TenantId: defaultTenant, - DataType: "azure", - DataSource: groupName, - Timestamp: time.Now().UTC().Format(time.RFC3339Nano), - Raw: string(jsonLog), - }, "com.utmstack.azure") - } - } else { - jsonLog, err := json.Marshal(logData) - if err != nil { - _ = catcher.Error("cannot encode log to JSON", err, map[string]any{ - "group": groupName, - "partitionID": pc.PartitionID(), - "process": "plugin_com.utmstack.azure", - }) - continue - } - - _ = plugins.EnqueueLog(&plugins.Log{ - Id: uuid.New().String(), - TenantId: defaultTenant, - DataType: "azure", - DataSource: groupName, - Timestamp: time.Now().UTC().Format(time.RFC3339Nano), - Raw: string(jsonLog), - }, "com.utmstack.azure") - } + processEvent(event.Body, groupName) } if err := pc.UpdateCheckpoint(context.Background(), events[len(events)-1], nil); err != nil { @@ -518,6 +458,90 @@ func getAzureProcessor(group *config.ModuleGroup) AzureConfig { return azurePro } +func processEvent(eventBody []byte, groupName string) { + var firstByte byte + for _, b := range eventBody { + if b != ' ' && b != '\t' && b != '\n' && b != '\r' { + firstByte = b + break + } + } + + switch firstByte { + case '[': + processArrayEvent(eventBody, groupName) + case '{': + processObjectEvent(eventBody, groupName) + default: + _ = catcher.Error("invalid JSON format: expected array or object", nil, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + } +} + +func processArrayEvent(eventBody []byte, groupName string) { + var records []map[string]any + if err := json.Unmarshal(eventBody, &records); err != nil { + _ = catcher.Error("cannot parse event body as array", err, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + return + } + + for _, record := range records { + enqueueRecord(record, groupName) + } +} + +func processObjectEvent(eventBody []byte, groupName string) { + var logData map[string]any + if err := json.Unmarshal(eventBody, &logData); err != nil { + _ = catcher.Error("cannot parse event body as object", err, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + return + } + + if records, ok := logData["records"].([]any); ok && len(records) > 0 { + for _, record := range records { + recordMap, ok := record.(map[string]any) + if !ok { + _ = catcher.Error("invalid record format in records array", nil, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + continue + } + enqueueRecord(recordMap, groupName) + } + } else { + enqueueRecord(logData, groupName) + } +} + +func enqueueRecord(record map[string]any, groupName string) { + jsonLog, err := json.Marshal(record) + if err != nil { + _ = catcher.Error("cannot encode record to JSON", err, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + return + } + + _ = plugins.EnqueueLog(&plugins.Log{ + Id: uuid.New().String(), + TenantId: defaultTenant, + DataType: "azure", + DataSource: groupName, + Timestamp: time.Now().UTC().Format(time.RFC3339Nano), + Raw: string(jsonLog), + }, "com.utmstack.azure") +} + func connectionChecker(url string) error { checkConn := func() error { if err := checkConnection(url); err != nil { diff --git a/plugins/bitdefender/go.mod b/plugins/bitdefender/go.mod index 7dc65e835..f23ff74f3 100644 --- a/plugins/bitdefender/go.mod +++ b/plugins/bitdefender/go.mod @@ -5,8 +5,8 @@ go 1.25.5 require ( github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 - github.com/threatwinds/go-sdk v1.1.9 - google.golang.org/grpc v1.78.0 + github.com/threatwinds/go-sdk v1.1.15 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) @@ -17,7 +17,7 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -25,7 +25,7 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -37,7 +37,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -50,8 +49,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/plugins/bitdefender/go.sum b/plugins/bitdefender/go.sum index c26cade06..f5cf22703 100644 --- a/plugins/bitdefender/go.sum +++ b/plugins/bitdefender/go.sum @@ -8,14 +8,16 @@ github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uS github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -38,8 +40,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -79,8 +81,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -88,13 +88,12 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -113,16 +112,16 @@ github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -144,12 +143,12 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plugins/config/go.mod b/plugins/config/go.mod index db172777d..b94d8e1dd 100644 --- a/plugins/config/go.mod +++ b/plugins/config/go.mod @@ -3,8 +3,8 @@ module github.com/utmstack/UTMStack/plugins/config go 1.25.5 require ( - github.com/lib/pq v1.11.1 - github.com/threatwinds/go-sdk v1.1.10 + github.com/lib/pq v1.11.2 + github.com/threatwinds/go-sdk v1.1.15 gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/yaml v1.6.0 ) @@ -49,8 +49,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/plugins/config/go.sum b/plugins/config/go.sum index 98dd2873b..106bc7804 100644 --- a/plugins/config/go.sum +++ b/plugins/config/go.sum @@ -57,8 +57,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI= -github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -90,8 +90,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.10 h1:joqXa0K8DiiJ135isRi6eOzVTtlPBY2i2yAS2ia2wNU= -github.com/threatwinds/go-sdk v1.1.10/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -141,10 +141,10 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b h1:SGYyueaEovpqmWmtTvwtVgo638V/QFE2zlTCnRrR3jg= -google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b h1:GZxXGdFaHX27ZSMHudWc4FokdD+xl8BC2UJm1OVIEzs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/plugins/config/main.go b/plugins/config/main.go index 3db45fca5..5d596d59f 100644 --- a/plugins/config/main.go +++ b/plugins/config/main.go @@ -75,9 +75,13 @@ type ExpressionBackend struct { type ConfigState struct { AssetsLastUpdate time.Time + AssetsCount int RulesLastUpdate time.Time + RulesCount int FiltersLastUpdate time.Time + FiltersCount int PatternsLastUpdate time.Time + PatternsCount int } func (b *ExpressionBackend) ToExpression() Expression { @@ -431,26 +435,35 @@ func hasChanges(db *sql.DB, state *ConfigState) (bool, ConfigState, error) { changed := false queries := []struct { - query string - target *time.Time - old time.Time + timestampQuery string + countQuery string + targetTime *time.Time + targetCount *int + oldTime time.Time + oldCount int }{ - {"SELECT MAX(last_update) FROM utm_tenant_config", &newState.AssetsLastUpdate, state.AssetsLastUpdate}, - {"SELECT MAX(rule_last_update) FROM utm_correlation_rules", &newState.RulesLastUpdate, state.RulesLastUpdate}, - {"SELECT MAX(updated_at) FROM utm_logstash_filter", &newState.FiltersLastUpdate, state.FiltersLastUpdate}, - {"SELECT MAX(last_update) FROM utm_regex_pattern", &newState.PatternsLastUpdate, state.PatternsLastUpdate}, + {"SELECT MAX(last_update) FROM utm_tenant_config", "SELECT COUNT(*) FROM utm_tenant_config", &newState.AssetsLastUpdate, &newState.AssetsCount, state.AssetsLastUpdate, state.AssetsCount}, + {"SELECT MAX(rule_last_update) FROM utm_correlation_rules", "SELECT COUNT(*) FROM utm_correlation_rules WHERE rule_active = true", &newState.RulesLastUpdate, &newState.RulesCount, state.RulesLastUpdate, state.RulesCount}, + {"SELECT MAX(updated_at) FROM utm_logstash_filter", "SELECT COUNT(*) FROM utm_logstash_filter WHERE is_active = true", &newState.FiltersLastUpdate, &newState.FiltersCount, state.FiltersLastUpdate, state.FiltersCount}, + {"SELECT MAX(last_update) FROM utm_regex_pattern", "SELECT COUNT(*) FROM utm_regex_pattern", &newState.PatternsLastUpdate, &newState.PatternsCount, state.PatternsLastUpdate, state.PatternsCount}, } for _, q := range queries { var lastUpdate sql.NullTime - err := db.QueryRow(q.query).Scan(&lastUpdate) + err := db.QueryRow(q.timestampQuery).Scan(&lastUpdate) if err != nil { return false, newState, err } if lastUpdate.Valid { - *q.target = lastUpdate.Time + *q.targetTime = lastUpdate.Time } - if (*q.target).After(q.old) { + + err = db.QueryRow(q.countQuery).Scan(q.targetCount) + if err != nil { + return false, newState, err + } + + if (*q.targetTime).After(q.oldTime) || *q.targetCount != q.oldCount { changed = true } } diff --git a/plugins/crowdstrike/go.mod b/plugins/crowdstrike/go.mod index 2d7c073e3..81a249a21 100644 --- a/plugins/crowdstrike/go.mod +++ b/plugins/crowdstrike/go.mod @@ -5,8 +5,8 @@ go 1.25.5 require ( github.com/crowdstrike/gofalcon v0.19.0 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.9 - google.golang.org/grpc v1.78.0 + github.com/threatwinds/go-sdk v1.1.15 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) @@ -19,7 +19,7 @@ require ( github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -51,7 +51,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -65,7 +65,6 @@ require ( github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -86,8 +85,8 @@ require ( golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/plugins/crowdstrike/go.sum b/plugins/crowdstrike/go.sum index a5109a5ba..0ef3a0f64 100644 --- a/plugins/crowdstrike/go.sum +++ b/plugins/crowdstrike/go.sum @@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -95,8 +95,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -138,8 +138,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -147,13 +145,12 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -178,10 +175,10 @@ go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -209,12 +206,12 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plugins/events/go.mod b/plugins/events/go.mod index 9f5635523..b5edeb8c1 100644 --- a/plugins/events/go.mod +++ b/plugins/events/go.mod @@ -3,7 +3,7 @@ module github.com/utmstack/UTMStack/plugins/events go 1.25.5 require ( - github.com/threatwinds/go-sdk v1.1.13 + github.com/threatwinds/go-sdk v1.1.15 github.com/tidwall/gjson v1.18.0 ) diff --git a/plugins/events/go.sum b/plugins/events/go.sum index 123356719..fb814cb43 100644 --- a/plugins/events/go.sum +++ b/plugins/events/go.sum @@ -88,8 +88,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.13 h1:uOKaET5Pvxso+tYSevBiF8n6HP04oY5uf8K9uv+bYGk= -github.com/threatwinds/go-sdk v1.1.13/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/feeds/go.mod b/plugins/feeds/go.mod index 68b1d5bd7..96c2044a9 100644 --- a/plugins/feeds/go.mod +++ b/plugins/feeds/go.mod @@ -5,7 +5,7 @@ go 1.25.5 require ( github.com/AtlasInsideCorp/AtlasInsideAES v1.0.0 github.com/opensearch-project/opensearch-go/v2 v2.3.0 - github.com/threatwinds/go-sdk v1.1.9 + github.com/threatwinds/go-sdk v1.1.15 golang.org/x/sync v0.19.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -17,7 +17,7 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -25,7 +25,7 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -38,7 +38,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -51,8 +50,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/plugins/feeds/go.sum b/plugins/feeds/go.sum index 7154bbee2..a8c1262b1 100644 --- a/plugins/feeds/go.sum +++ b/plugins/feeds/go.sum @@ -29,8 +29,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -53,8 +53,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -98,8 +98,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -107,14 +105,13 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -197,10 +194,10 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/plugins/feeds/internal/client/cm_client.go b/plugins/feeds/internal/client/cm_client.go index ff41591a4..60b929487 100644 --- a/plugins/feeds/internal/client/cm_client.go +++ b/plugins/feeds/internal/client/cm_client.go @@ -66,6 +66,7 @@ func (c *CustomersManagerClient) RegisterUserReporter() (*RegistrationResponse, nil, http.MethodPost, headers, + false, ) if err != nil { diff --git a/plugins/gcp/go.mod b/plugins/gcp/go.mod index b08ac0228..f95db4dc9 100644 --- a/plugins/gcp/go.mod +++ b/plugins/gcp/go.mod @@ -5,9 +5,9 @@ go 1.25.5 require ( cloud.google.com/go/pubsub v1.50.1 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.9 - google.golang.org/api v0.263.0 - google.golang.org/grpc v1.78.0 + github.com/threatwinds/go-sdk v1.1.15 + google.golang.org/api v0.267.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) @@ -26,7 +26,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -37,10 +37,10 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect - github.com/googleapis/gax-go/v2 v2.16.0 // indirect + github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -52,7 +52,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -70,14 +69,14 @@ require ( golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/plugins/gcp/go.sum b/plugins/gcp/go.sum index 68097e872..02a528ce1 100644 --- a/plugins/gcp/go.sum +++ b/plugins/gcp/go.sum @@ -35,8 +35,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -44,16 +44,16 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= -github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -91,8 +91,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -109,8 +109,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= -github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= -github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= +github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= +github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -146,8 +146,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -160,8 +158,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -225,8 +223,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -253,8 +251,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.263.0 h1:UFs7qn8gInIdtk1ZA6eXRXp5JDAnS4x9VRsRVCeKdbk= -google.golang.org/api v0.263.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8= +google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE= +google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -262,17 +260,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/plugins/geolocation/go.mod b/plugins/geolocation/go.mod index 0ac2ba241..8cc022fe6 100644 --- a/plugins/geolocation/go.mod +++ b/plugins/geolocation/go.mod @@ -3,7 +3,7 @@ module github.com/utmstack/UTMStack/plugins/geolocation go 1.25.5 require ( - github.com/threatwinds/go-sdk v1.1.10 + github.com/threatwinds/go-sdk v1.1.15 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 ) @@ -47,8 +47,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/plugins/geolocation/go.sum b/plugins/geolocation/go.sum index f69c3240f..ad1333a84 100644 --- a/plugins/geolocation/go.sum +++ b/plugins/geolocation/go.sum @@ -88,8 +88,10 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.10 h1:joqXa0K8DiiJ135isRi6eOzVTtlPBY2i2yAS2ia2wNU= -github.com/threatwinds/go-sdk v1.1.10/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= +github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -111,14 +113,19 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -127,25 +134,43 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y= +golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b h1:SGYyueaEovpqmWmtTvwtVgo638V/QFE2zlTCnRrR3jg= -google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b h1:GZxXGdFaHX27ZSMHudWc4FokdD+xl8BC2UJm1OVIEzs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/api v0.0.0-20260223185530-2f722ef697dc h1:ULD+ToGXUIU6Pkzr1ARxdyvwfHbelw+agoFDRbLg4TU= +google.golang.org/genproto/googleapis/api v0.0.0-20260223185530-2f722ef697dc/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc h1:51Wupg8spF+5FC6D+iMKbOddFjMckETnNnEiZ+HX37s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plugins/inputs/go.mod b/plugins/inputs/go.mod index 479bb46bf..f9f500cce 100644 --- a/plugins/inputs/go.mod +++ b/plugins/inputs/go.mod @@ -5,8 +5,8 @@ go 1.25.5 require ( github.com/gin-gonic/gin v1.11.0 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.9 - google.golang.org/grpc v1.78.0 + github.com/threatwinds/go-sdk v1.1.15 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) @@ -17,14 +17,14 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -36,7 +36,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -49,8 +48,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/plugins/inputs/go.sum b/plugins/inputs/go.sum index 9978feb4f..72d534d36 100644 --- a/plugins/inputs/go.sum +++ b/plugins/inputs/go.sum @@ -8,14 +8,16 @@ github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uS github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -38,8 +40,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -77,8 +79,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -86,13 +86,12 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -111,16 +110,16 @@ github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -142,12 +141,12 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plugins/modules-config/config/config.go b/plugins/modules-config/config/config.go index 580326cc4..042a2994e 100644 --- a/plugins/modules-config/config/config.go +++ b/plugins/modules-config/config/config.go @@ -174,7 +174,7 @@ func (s *ConfigServer) SyncConfigs(backend string, internalKey string) { url := fmt.Sprintf("%s/api/utm-modules/module-details-decrypted?nameShort=%s&serverId=1", backend, name) for { - response, status, err := utils.DoReq[ConfigurationSection](url, nil, "GET", map[string]string{"Utm-Internal-Key": internalKey}) + response, status, err := utils.DoReq[ConfigurationSection](url, nil, "GET", map[string]string{"Utm-Internal-Key": internalKey}, true) if err == nil && status == http.StatusOK { s.mu.Lock() s.cache[t] = &response diff --git a/plugins/modules-config/go.mod b/plugins/modules-config/go.mod index fdc0111f5..c733f16ba 100644 --- a/plugins/modules-config/go.mod +++ b/plugins/modules-config/go.mod @@ -6,15 +6,15 @@ require ( cloud.google.com/go/pubsub v1.50.1 github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/v2 v2.0.1 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 - github.com/aws/aws-sdk-go-v2/config v1.32.7 - github.com/aws/aws-sdk-go-v2/credentials v1.19.7 + github.com/aws/aws-sdk-go-v2/config v1.32.8 + github.com/aws/aws-sdk-go-v2/credentials v1.19.8 github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.63.1 github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 github.com/crowdstrike/gofalcon v0.19.0 github.com/gin-gonic/gin v1.11.0 - github.com/threatwinds/go-sdk v1.1.9 - google.golang.org/api v0.263.0 - google.golang.org/grpc v1.78.0 + github.com/threatwinds/go-sdk v1.1.15 + google.golang.org/api v0.267.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) @@ -40,7 +40,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bytedance/gopkg v0.1.3 // indirect @@ -49,7 +49,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -80,11 +80,11 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect - github.com/googleapis/gax-go/v2 v2.16.0 // indirect + github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -98,7 +98,6 @@ require ( github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -118,14 +117,14 @@ require ( golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/plugins/modules-config/go.sum b/plugins/modules-config/go.sum index 0324da313..529be11de 100644 --- a/plugins/modules-config/go.sum +++ b/plugins/modules-config/go.sum @@ -44,10 +44,10 @@ github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6ce github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= -github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY= -github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY= -github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= -github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= +github.com/aws/aws-sdk-go-v2/config v1.32.8 h1:iu+64gwDKEoKnyTQskSku72dAwggKI5sV6rNvgSMpMs= +github.com/aws/aws-sdk-go-v2/config v1.32.8/go.mod h1:MI2XvA+qDi3i9AJxX1E2fu730syEBzp/jnXrjxuHwgI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.8 h1:Jp2JYH1lRT3KhX4mshHPvVYsR5qqRec3hGvEarNYoR0= +github.com/aws/aws-sdk-go-v2/credentials v1.19.8/go.mod h1:fZG9tuvyVfxknv1rKibIz3DobRaFw1Poe8IKtXB3XYY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= @@ -66,8 +66,8 @@ github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4 github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 h1:0jbJeuEHlwKJ9PfXtpSFc4MF+WIWORdhN1n30ITZGFM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= @@ -87,8 +87,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/crowdstrike/gofalcon v0.19.0 h1:pKvA8Az85wD6OR7aq/tvc+tORtR5CSyKp3+LDQXc4pc= @@ -100,18 +100,18 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= -github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -203,8 +203,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -221,8 +221,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= -github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= -github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= +github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= +github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -268,8 +268,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -282,8 +280,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -349,8 +347,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -377,8 +375,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.263.0 h1:UFs7qn8gInIdtk1ZA6eXRXp5JDAnS4x9VRsRVCeKdbk= -google.golang.org/api v0.263.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8= +google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE= +google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -386,17 +384,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/plugins/modules-config/validations/socai.go b/plugins/modules-config/validations/socai.go index 352640dcc..7367600fd 100644 --- a/plugins/modules-config/validations/socai.go +++ b/plugins/modules-config/validations/socai.go @@ -39,7 +39,7 @@ func ValidateSOCAIConfig(config *config.ModuleGroup) error { "Content-Type": "application/json", } - response, status, err := utils.DoReq[map[string]any](url, nil, "GET", headers) + response, status, err := utils.DoReq[map[string]any](url, nil, "GET", headers, false) if err != nil || status != http.StatusOK { if status == http.StatusRequestTimeout { return fmt.Errorf("SOC_AI connection timed out") diff --git a/plugins/o365/go.mod b/plugins/o365/go.mod index c755c130b..53c619ae0 100644 --- a/plugins/o365/go.mod +++ b/plugins/o365/go.mod @@ -4,8 +4,8 @@ go 1.25.5 require ( github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.9 - google.golang.org/grpc v1.78.0 + github.com/threatwinds/go-sdk v1.1.15 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) @@ -16,7 +16,7 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -24,7 +24,7 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -36,7 +36,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -49,8 +48,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/plugins/o365/go.sum b/plugins/o365/go.sum index 9978feb4f..72d534d36 100644 --- a/plugins/o365/go.sum +++ b/plugins/o365/go.sum @@ -8,14 +8,16 @@ github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uS github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -38,8 +40,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -77,8 +79,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -86,13 +86,12 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -111,16 +110,16 @@ github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -142,12 +141,12 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plugins/o365/main.go b/plugins/o365/main.go index 706e4c30f..5a2a00145 100644 --- a/plugins/o365/main.go +++ b/plugins/o365/main.go @@ -261,7 +261,7 @@ func (o *OfficeProcessor) GetAuth() error { var err error for retry := 0; retry < maxRetries; retry++ { - result, _, err = utils.DoReq[MicrosoftLoginResponse](requestUrl, dataBytes, http.MethodPost, headers) + result, _, err = utils.DoReq[MicrosoftLoginResponse](requestUrl, dataBytes, http.MethodPost, headers, false) if err == nil { o.Credentials = result return nil @@ -303,7 +303,7 @@ func (o *OfficeProcessor) StartSubscriptions() error { var err error for retry := 0; retry < maxRetries; retry++ { - _, _, err = utils.DoReq[StartSubscriptionResponse](link, []byte("{}"), http.MethodPost, headers) + _, _, err = utils.DoReq[StartSubscriptionResponse](link, []byte("{}"), http.MethodPost, headers, false) if err == nil { break } @@ -362,7 +362,7 @@ func (o *OfficeProcessor) GetContentList(subscription string, startTime time.Tim var err error for retry := 0; retry < maxRetries; retry++ { - respBody, status, err = utils.DoReq[[]ContentList](link, nil, http.MethodGet, headers) + respBody, status, err = utils.DoReq[[]ContentList](link, nil, http.MethodGet, headers, false) if err == nil && status == http.StatusOK { return respBody, nil } @@ -404,7 +404,7 @@ func (o *OfficeProcessor) GetContentDetails(url string) (ContentDetailsResponse, var err error for retry := 0; retry < maxRetries; retry++ { - respBody, status, err = utils.DoReq[ContentDetailsResponse](url, nil, http.MethodGet, headers) + respBody, status, err = utils.DoReq[ContentDetailsResponse](url, nil, http.MethodGet, headers, false) if err == nil { return respBody, nil } diff --git a/plugins/soc-ai/go.mod b/plugins/soc-ai/go.mod index 44b6744e3..2bbc877d7 100644 --- a/plugins/soc-ai/go.mod +++ b/plugins/soc-ai/go.mod @@ -20,14 +20,14 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -36,8 +36,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect - github.com/threatwinds/go-sdk v1.1.9 + github.com/threatwinds/go-sdk v1.1.14 github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -49,8 +48,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/plugins/soc-ai/go.sum b/plugins/soc-ai/go.sum index 9978feb4f..1abc5a7c4 100644 --- a/plugins/soc-ai/go.sum +++ b/plugins/soc-ai/go.sum @@ -14,8 +14,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -38,8 +38,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -77,8 +77,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -86,13 +84,12 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= +github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -142,10 +139,10 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/plugins/sophos/go.mod b/plugins/sophos/go.mod index fbd307c8c..4dfeb6264 100644 --- a/plugins/sophos/go.mod +++ b/plugins/sophos/go.mod @@ -4,8 +4,8 @@ go 1.25.5 require ( github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.9 - google.golang.org/grpc v1.78.0 + github.com/threatwinds/go-sdk v1.1.15 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) @@ -16,7 +16,7 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -24,7 +24,7 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -36,7 +36,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -49,8 +48,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/plugins/sophos/go.sum b/plugins/sophos/go.sum index 9978feb4f..72d534d36 100644 --- a/plugins/sophos/go.sum +++ b/plugins/sophos/go.sum @@ -8,14 +8,16 @@ github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uS github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -38,8 +40,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -77,8 +79,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -86,13 +86,12 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.9 h1:4i8UZczXyGbRJsUEHRgaS2oQ03VTRjh/DYyTtGdSfRA= -github.com/threatwinds/go-sdk v1.1.9/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -111,16 +110,16 @@ github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -142,12 +141,12 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plugins/sophos/main.go b/plugins/sophos/main.go index a5c8dbdd5..e0a03be46 100644 --- a/plugins/sophos/main.go +++ b/plugins/sophos/main.go @@ -155,7 +155,7 @@ func (p *SophosCentralProcessor) getAccessToken() (string, error) { var err error for retry := 0; retry < maxRetries; retry++ { - response, _, err = utils.DoReq[map[string]any](authURL, []byte(data.Encode()), http.MethodPost, headers) + response, _, err = utils.DoReq[map[string]any](authURL, []byte(data.Encode()), http.MethodPost, headers, false) if err == nil { accessToken, ok := response["access_token"].(string) if ok && accessToken != "" { @@ -230,7 +230,7 @@ func (p *SophosCentralProcessor) getTenantInfo(accessToken string) error { var err error for retry := 0; retry < maxRetries; retry++ { - response, _, err = utils.DoReq[WhoamiResponse](whoamiURL, nil, http.MethodGet, headers) + response, _, err = utils.DoReq[WhoamiResponse](whoamiURL, nil, http.MethodGet, headers, false) if err == nil { if response.ID != "" && response.ApiHosts.DataRegion != "" { p.TenantID = response.ID @@ -368,7 +368,7 @@ func (p *SophosCentralProcessor) getLogs(fromTime int64, nextKey string) ([]stri // Retry logic for getting logs var response EventAggregate for retry := 0; retry < maxRetries; retry++ { - response, _, err = utils.DoReq[EventAggregate](u.String(), nil, http.MethodGet, headers) + response, _, err = utils.DoReq[EventAggregate](u.String(), nil, http.MethodGet, headers, false) if err == nil { break } diff --git a/plugins/stats/go.mod b/plugins/stats/go.mod index acbcafcbf..18ca43b23 100644 --- a/plugins/stats/go.mod +++ b/plugins/stats/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.10 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/protobuf v1.36.11 ) @@ -47,8 +47,8 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/grpc v1.78.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/plugins/stats/go.sum b/plugins/stats/go.sum index 0c6ce469a..fb814cb43 100644 --- a/plugins/stats/go.sum +++ b/plugins/stats/go.sum @@ -88,8 +88,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.10 h1:joqXa0K8DiiJ135isRi6eOzVTtlPBY2i2yAS2ia2wNU= -github.com/threatwinds/go-sdk v1.1.10/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -139,10 +139,10 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b h1:SGYyueaEovpqmWmtTvwtVgo638V/QFE2zlTCnRrR3jg= -google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b h1:GZxXGdFaHX27ZSMHudWc4FokdD+xl8BC2UJm1OVIEzs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/shared/archive/zip.go b/shared/archive/zip.go new file mode 100644 index 000000000..84ce3ff12 --- /dev/null +++ b/shared/archive/zip.go @@ -0,0 +1,68 @@ +// Package archive provides utilities for working with archive files. +package archive + +import ( + "archive/zip" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +// Unzip extracts a zip archive to the specified destination directory. +func Unzip(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return fmt.Errorf("error opening zip file: %w", err) + } + defer r.Close() + + for _, f := range r.File { + if err := extractFile(f, dest); err != nil { + return err + } + } + return nil +} + +func extractFile(f *zip.File, dest string) error { + // Sanitize the file path to prevent zip slip attacks + filePath := filepath.Join(dest, f.Name) + if !strings.HasPrefix(filePath, filepath.Clean(dest)+string(os.PathSeparator)) { + return fmt.Errorf("invalid file path: %s", f.Name) + } + + if f.FileInfo().IsDir() { + if err := os.MkdirAll(filePath, os.ModePerm); err != nil { + return fmt.Errorf("error creating directory: %w", err) + } + return nil + } + + // Create parent directories if needed + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + return fmt.Errorf("error creating parent directory: %w", err) + } + + // Create the file + outFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return fmt.Errorf("error creating file: %w", err) + } + defer outFile.Close() + + // Open the file in the archive + rc, err := f.Open() + if err != nil { + return fmt.Errorf("error opening archived file: %w", err) + } + defer rc.Close() + + // Copy contents + if _, err = io.Copy(outFile, rc); err != nil { + return fmt.Errorf("error extracting file: %w", err) + } + + return nil +} diff --git a/shared/exec/exec.go b/shared/exec/exec.go new file mode 100644 index 000000000..22487ae65 --- /dev/null +++ b/shared/exec/exec.go @@ -0,0 +1,38 @@ +// Package exec provides utilities for executing external commands. +package exec + +import ( + "bytes" + "fmt" + "os/exec" +) + +// Run executes a command with the given arguments in the specified working directory. +// Returns an error if the command fails. +func Run(command, workDir string, args ...string) error { + cmd := exec.Command(command, args...) + cmd.Dir = workDir + + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + if stderr.Len() > 0 { + return fmt.Errorf("command failed: %s: %w", stderr.String(), err) + } + return fmt.Errorf("command failed: %w", err) + } + return nil +} + +// RunWithOutput executes a command and returns its combined stdout and stderr output. +func RunWithOutput(command, workDir string, args ...string) (string, error) { + cmd := exec.Command(command, args...) + cmd.Dir = workDir + + output, err := cmd.CombinedOutput() + if err != nil { + return string(output), fmt.Errorf("command failed: %w", err) + } + return string(output), nil +} diff --git a/shared/fs/fs.go b/shared/fs/fs.go new file mode 100644 index 000000000..afd42eb80 --- /dev/null +++ b/shared/fs/fs.go @@ -0,0 +1,108 @@ +// Package fs provides filesystem utility functions. +package fs + +import ( + "fmt" + "io" + "os" + "path/filepath" +) + +// GetExecutablePath returns the directory path of the current executable. +func GetExecutablePath() string { + ex, err := os.Executable() + if err != nil { + return "" + } + return filepath.Dir(ex) +} + +// Exists checks if a path exists. +func Exists(path string) bool { + _, err := os.Stat(path) + return !os.IsNotExist(err) +} + +// CreateDirIfNotExist creates a directory and all parent directories if they don't exist. +func CreateDirIfNotExist(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + if err := os.MkdirAll(path, 0755); err != nil { + return fmt.Errorf("error creating directory: %w", err) + } + } else if err != nil { + return fmt.Errorf("error checking path: %w", err) + } + return nil +} + +// Copy copies a file from src to dst. +func Copy(src, dst string) error { + sourceFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("error opening source file: %w", err) + } + defer sourceFile.Close() + + destFile, err := os.Create(dst) + if err != nil { + return fmt.Errorf("error creating destination file: %w", err) + } + defer destFile.Close() + + if _, err = io.Copy(destFile, sourceFile); err != nil { + return fmt.Errorf("error copying file: %w", err) + } + return nil +} + +// WriteString writes a string to a file, creating or truncating it. +func WriteString(path string, content string) error { + file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("error opening file: %w", err) + } + defer file.Close() + + if _, err = file.WriteString(content); err != nil { + return fmt.Errorf("error writing to file: %w", err) + } + return nil +} + +// ReadLines reads a file and returns its lines as a slice. +func ReadLines(path string) ([]string, error) { + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var lines []string + var line []byte + for _, b := range content { + if b == '\n' { + lines = append(lines, string(line)) + line = nil + } else if b != '\r' { + line = append(line, b) + } + } + if len(line) > 0 { + lines = append(lines, string(line)) + } + return lines, nil +} + +// IsEmpty checks if a directory is empty. +func IsEmpty(path string) (bool, error) { + f, err := os.Open(path) + if err != nil { + return false, err + } + defer f.Close() + + _, err = f.Readdirnames(1) + if err == io.EOF { + return true, nil + } + return false, err +} diff --git a/shared/fs/json.go b/shared/fs/json.go new file mode 100644 index 000000000..7bc39a1ba --- /dev/null +++ b/shared/fs/json.go @@ -0,0 +1,33 @@ +package fs + +import ( + "encoding/json" + "fmt" + "os" +) + +// ReadJSON reads a JSON file and unmarshals it into the provided data structure. +func ReadJSON(path string, data interface{}) error { + content, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("error reading file: %w", err) + } + + if err = json.Unmarshal(content, data); err != nil { + return fmt.Errorf("error unmarshaling JSON: %w", err) + } + return nil +} + +// WriteJSON marshals the data and writes it to a JSON file with indentation. +func WriteJSON(path string, data interface{}) error { + jsonData, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("error marshaling JSON: %w", err) + } + + if err = WriteString(path, string(jsonData)); err != nil { + return fmt.Errorf("error writing JSON file: %w", err) + } + return nil +} diff --git a/shared/fs/yaml.go b/shared/fs/yaml.go new file mode 100644 index 000000000..c54b3d36a --- /dev/null +++ b/shared/fs/yaml.go @@ -0,0 +1,47 @@ +package fs + +import ( + "fmt" + "os" + "reflect" + + "gopkg.in/yaml.v3" +) + +// ReadYAML reads a YAML file and unmarshals it into the provided data structure. +// The data parameter must be a non-nil pointer. +func ReadYAML(path string, data interface{}) error { + if data == nil { + return fmt.Errorf("data interface is nil") + } + + rv := reflect.ValueOf(data) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return fmt.Errorf("data must be a non-nil pointer") + } + + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("error opening file: %w", err) + } + defer file.Close() + + decoder := yaml.NewDecoder(file) + if err := decoder.Decode(data); err != nil { + return fmt.Errorf("error decoding YAML: %w", err) + } + return nil +} + +// WriteYAML marshals the data and writes it to a YAML file. +func WriteYAML(path string, data interface{}) error { + yamlData, err := yaml.Marshal(data) + if err != nil { + return fmt.Errorf("error marshaling YAML: %w", err) + } + + if err = WriteString(path, string(yamlData)); err != nil { + return fmt.Errorf("error writing YAML file: %w", err) + } + return nil +} diff --git a/shared/go.mod b/shared/go.mod new file mode 100644 index 000000000..51c63efb4 --- /dev/null +++ b/shared/go.mod @@ -0,0 +1,39 @@ +module github.com/utmstack/UTMStack/shared + +go 1.25.1 + +require ( + github.com/threatwinds/logger v1.2.3 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.14.1 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/gin-gonic/gin v1.10.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + golang.org/x/arch v0.21.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect +) diff --git a/shared/go.sum b/shared/go.sum new file mode 100644 index 000000000..5f0c38bc2 --- /dev/null +++ b/shared/go.sum @@ -0,0 +1,84 @@ +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w= +github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= +github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/threatwinds/logger v1.2.3 h1:V2SVAXzbq+/huCvIWOfqzMTH+WBHJxankyBgVG2hy1Y= +github.com/threatwinds/logger v1.2.3/go.mod h1:N+bJKvF4FQNJZLfQpVYWpr6D8iEAFnAQfHYqH5iR1TI= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= +golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/shared/http/download.go b/shared/http/download.go new file mode 100644 index 000000000..f116e14b3 --- /dev/null +++ b/shared/http/download.go @@ -0,0 +1,91 @@ +// Package http provides HTTP utility functions. +package http + +import ( + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "time" +) + +// DownloadOptions configures a download request. +type DownloadOptions struct { + // Headers to include in the request + Headers map[string]string + // SkipTLSVerify skips TLS certificate validation + SkipTLSVerify bool + // Timeout for the download (default: 5 minutes) + Timeout time.Duration +} + +// DefaultOptions returns default download options. +func DefaultOptions() DownloadOptions { + return DownloadOptions{ + Headers: make(map[string]string), + SkipTLSVerify: false, + Timeout: 5 * time.Minute, + } +} + +// Download downloads a file from the given URL to the specified destination. +func Download(url, destDir, filename string, opts DownloadOptions) error { + if opts.Timeout == 0 { + opts.Timeout = 5 * time.Minute + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: opts.SkipTLSVerify, + }, + } + + client := &http.Client{ + Timeout: opts.Timeout, + Transport: transport, + } + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return fmt.Errorf("error creating request: %w", err) + } + + for key, value := range opts.Headers { + req.Header.Set(key, value) + } + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("error downloading file: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("download failed with status: %s", resp.Status) + } + + destPath := filepath.Join(destDir, filename) + out, err := os.Create(destPath) + if err != nil { + return fmt.Errorf("error creating file: %w", err) + } + defer out.Close() + + if _, err = io.Copy(out, resp.Body); err != nil { + return fmt.Errorf("error writing file: %w", err) + } + + return nil +} + +// DownloadFile is a convenience function that downloads a file with common options. +func DownloadFile(url string, headers map[string]string, filename, destDir string, skipTLSVerify bool) error { + opts := DownloadOptions{ + Headers: headers, + SkipTLSVerify: skipTLSVerify, + Timeout: 5 * time.Minute, + } + return Download(url, destDir, filename, opts) +} diff --git a/shared/logger/logger.go b/shared/logger/logger.go new file mode 100644 index 000000000..666708179 --- /dev/null +++ b/shared/logger/logger.go @@ -0,0 +1,59 @@ +// Package logger provides a simple logging wrapper. +package logger + +import ( + "github.com/threatwinds/logger" +) + +// Log level constants +const ( + LevelDebug = 100 + LevelInfo = 200 + LevelNotice = 300 + LevelWarning = 400 + LevelError = 500 + LevelCritical = 502 + LevelAlert = 509 +) + +// Logger is the shared logger instance. +var Logger *logger.Logger + +// Init initializes the logger with the specified log file path and level. +func Init(logFile string, level int) { + Logger = logger.NewLogger(&logger.Config{ + Format: "text", + Level: level, + Output: logFile, + Retries: 3, + Wait: 5, + }) +} + +// Info logs an informational message. +func Info(format string, args ...any) { + if Logger != nil { + Logger.Info(format, args...) + } +} + +// Error logs an error message. +func Error(format string, args ...any) { + if Logger != nil { + Logger.ErrorF(format, args...) + } +} + +// Fatal logs a fatal message and exits. +func Fatal(format string, args ...any) { + if Logger != nil { + Logger.Fatal(format, args...) + } +} + +// Debug logs a debug message with the specified level. +func Debug(level int, format string, args ...any) { + if Logger != nil { + Logger.LogF(level, format, args...) + } +} diff --git a/shared/svc/svc.go b/shared/svc/svc.go new file mode 100644 index 000000000..e389c0f34 --- /dev/null +++ b/shared/svc/svc.go @@ -0,0 +1,9 @@ +// Package svc provides utilities for managing system services. +package svc + +// Service status constants +const ( + StatusRunning = "running" + StatusStopped = "stopped" + StatusUnknown = "unknown" +) diff --git a/shared/svc/svc_unix.go b/shared/svc/svc_unix.go new file mode 100644 index 000000000..ba2419ef1 --- /dev/null +++ b/shared/svc/svc_unix.go @@ -0,0 +1,64 @@ +//go:build linux || darwin +// +build linux darwin + +package svc + +import ( + "fmt" + "os/exec" + "strings" +) + +// Start starts a system service by name. +func Start(serviceName string) error { + cmd := exec.Command("systemctl", "start", serviceName) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to start service %s: %w", serviceName, err) + } + return nil +} + +// Stop stops a system service by name. +func Stop(serviceName string) error { + cmd := exec.Command("systemctl", "stop", serviceName) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to stop service %s: %w", serviceName, err) + } + return nil +} + +// Restart restarts a system service by name. +func Restart(serviceName string) error { + cmd := exec.Command("systemctl", "restart", serviceName) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to restart service %s: %w", serviceName, err) + } + return nil +} + +// IsActive checks if a system service is running. +func IsActive(serviceName string) (bool, error) { + cmd := exec.Command("systemctl", "is-active", serviceName) + output, err := cmd.Output() + if err != nil { + // is-active returns non-zero for inactive services + return false, nil + } + return strings.TrimSpace(string(output)) == "active", nil +} + +// Status returns the status of a system service. +func Status(serviceName string) (string, error) { + cmd := exec.Command("systemctl", "is-active", serviceName) + output, _ := cmd.Output() + status := strings.TrimSpace(string(output)) + + switch status { + case "active": + return StatusRunning, nil + case "inactive", "failed": + return StatusStopped, nil + default: + return StatusUnknown, nil + } +} diff --git a/shared/svc/svc_windows.go b/shared/svc/svc_windows.go new file mode 100644 index 000000000..52d24b70a --- /dev/null +++ b/shared/svc/svc_windows.go @@ -0,0 +1,65 @@ +//go:build windows +// +build windows + +package svc + +import ( + "fmt" + "os/exec" + "strings" +) + +// Start starts a Windows service by name. +func Start(serviceName string) error { + cmd := exec.Command("sc", "start", serviceName) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to start service %s: %w", serviceName, err) + } + return nil +} + +// Stop stops a Windows service by name. +func Stop(serviceName string) error { + cmd := exec.Command("sc", "stop", serviceName) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to stop service %s: %w", serviceName, err) + } + return nil +} + +// Restart restarts a Windows service by stopping and starting it. +func Restart(serviceName string) error { + if err := Stop(serviceName); err != nil { + return err + } + return Start(serviceName) +} + +// IsActive checks if a Windows service is running. +func IsActive(serviceName string) (bool, error) { + cmd := exec.Command("sc", "query", serviceName) + output, err := cmd.Output() + if err != nil { + return false, fmt.Errorf("failed to query service %s: %w", serviceName, err) + } + return strings.Contains(string(output), "RUNNING"), nil +} + +// Status returns the status of a Windows service. +func Status(serviceName string) (string, error) { + cmd := exec.Command("sc", "query", serviceName) + output, err := cmd.Output() + if err != nil { + return StatusUnknown, fmt.Errorf("failed to query service %s: %w", serviceName, err) + } + + outputStr := string(output) + switch { + case strings.Contains(outputStr, "RUNNING"): + return StatusRunning, nil + case strings.Contains(outputStr, "STOPPED"): + return StatusStopped, nil + default: + return StatusUnknown, nil + } +} diff --git a/utmstack-collector/go.mod b/utmstack-collector/go.mod index 6757cf795..1260dd667 100644 --- a/utmstack-collector/go.mod +++ b/utmstack-collector/go.mod @@ -9,9 +9,9 @@ require ( github.com/glebarez/sqlite v1.11.0 github.com/google/uuid v1.6.0 github.com/kardianos/service v1.2.4 - github.com/threatwinds/go-sdk v1.1.7 + github.com/threatwinds/go-sdk v1.1.15 github.com/threatwinds/logger v1.2.3 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v2 v2.4.0 gorm.io/gorm v1.31.1 @@ -22,8 +22,8 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.14.2 // indirect - github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/containerd/errdefs v1.0.0 // indirect @@ -35,7 +35,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/go-windows v1.0.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect @@ -46,7 +46,7 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -70,7 +70,6 @@ require ( github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -90,8 +89,8 @@ require ( golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect diff --git a/utmstack-collector/go.sum b/utmstack-collector/go.sum index e817de2a8..51823e3b5 100644 --- a/utmstack-collector/go.sum +++ b/utmstack-collector/go.sum @@ -10,10 +10,10 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= -github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= -github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= -github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -46,8 +46,8 @@ github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -75,8 +75,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -149,8 +149,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -159,13 +157,12 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/threatwinds/go-sdk v1.1.7 h1:2IJAWTCxZU4BDFiavPjH8MqpA/mam1QyIsjySbZLlRo= -github.com/threatwinds/go-sdk v1.1.7/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/threatwinds/logger v1.2.3 h1:V2SVAXzbq+/huCvIWOfqzMTH+WBHJxankyBgVG2hy1Y= github.com/threatwinds/logger v1.2.3/go.mod h1:N+bJKvF4FQNJZLfQpVYWpr6D8iEAFnAQfHYqH5iR1TI= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= @@ -233,12 +230,12 @@ golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM= -google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=