diff --git a/api/grpc/events/v1/security_violation.pb.go b/api/grpc/events/v1/security_violation.pb.go new file mode 100644 index 0000000000..704c2ddd9e --- /dev/null +++ b/api/grpc/events/v1/security_violation.pb.go @@ -0,0 +1,737 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: events/v1/security_violation.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// SecurityViolationEvent represents the structured NGINX App Protect security violation data +type SecurityViolationEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Name of the security policy + PolicyName string `protobuf:"bytes,1,opt,name=policy_name,json=policyName,proto3" json:"policy_name,omitempty"` + // Unique support ID for the violation + SupportId string `protobuf:"bytes,2,opt,name=support_id,json=supportId,proto3" json:"support_id,omitempty"` + // Outcome of the request (e.g., REJECTED, PASSED) + Outcome string `protobuf:"bytes,3,opt,name=outcome,proto3" json:"outcome,omitempty"` + // Reason for the outcome + OutcomeReason string `protobuf:"bytes,4,opt,name=outcome_reason,json=outcomeReason,proto3" json:"outcome_reason,omitempty"` + // Reason for blocking exception if applicable + BlockingExceptionReason string `protobuf:"bytes,5,opt,name=blocking_exception_reason,json=blockingExceptionReason,proto3" json:"blocking_exception_reason,omitempty"` + // HTTP method used + Method string `protobuf:"bytes,6,opt,name=method,proto3" json:"method,omitempty"` + // Protocol used (e.g., HTTP/1.1) + Protocol string `protobuf:"bytes,7,opt,name=protocol,proto3" json:"protocol,omitempty"` + // X-Forwarded-For header value + XffHeaderValue string `protobuf:"bytes,8,opt,name=xff_header_value,json=xffHeaderValue,proto3" json:"xff_header_value,omitempty"` + // Request URI + Uri string `protobuf:"bytes,9,opt,name=uri,proto3" json:"uri,omitempty"` + // Full request + Request string `protobuf:"bytes,10,opt,name=request,proto3" json:"request,omitempty"` + // Indicates if the request was truncated + IsTruncated string `protobuf:"bytes,11,opt,name=is_truncated,json=isTruncated,proto3" json:"is_truncated,omitempty"` + // Status of the request + RequestStatus string `protobuf:"bytes,12,opt,name=request_status,json=requestStatus,proto3" json:"request_status,omitempty"` + // HTTP response code + ResponseCode string `protobuf:"bytes,13,opt,name=response_code,json=responseCode,proto3" json:"response_code,omitempty"` + // Server address + ServerAddr string `protobuf:"bytes,14,opt,name=server_addr,json=serverAddr,proto3" json:"server_addr,omitempty"` + // Virtual server name + VsName string `protobuf:"bytes,15,opt,name=vs_name,json=vsName,proto3" json:"vs_name,omitempty"` + // Remote address of the client + RemoteAddr string `protobuf:"bytes,16,opt,name=remote_addr,json=remoteAddr,proto3" json:"remote_addr,omitempty"` + // Destination port + DestinationPort string `protobuf:"bytes,17,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"` + // Server port + ServerPort string `protobuf:"bytes,18,opt,name=server_port,json=serverPort,proto3" json:"server_port,omitempty"` + // List of violations + Violations string `protobuf:"bytes,19,opt,name=violations,proto3" json:"violations,omitempty"` + // List of sub-violations + SubViolations string `protobuf:"bytes,20,opt,name=sub_violations,json=subViolations,proto3" json:"sub_violations,omitempty"` + // Violation rating + ViolationRating string `protobuf:"bytes,21,opt,name=violation_rating,json=violationRating,proto3" json:"violation_rating,omitempty"` + // Signature set names + SigSetNames string `protobuf:"bytes,22,opt,name=sig_set_names,json=sigSetNames,proto3" json:"sig_set_names,omitempty"` + // Signature CVEs + SigCves string `protobuf:"bytes,23,opt,name=sig_cves,json=sigCves,proto3" json:"sig_cves,omitempty"` + // Client class + ClientClass string `protobuf:"bytes,24,opt,name=client_class,json=clientClass,proto3" json:"client_class,omitempty"` + // Client application + ClientApplication string `protobuf:"bytes,25,opt,name=client_application,json=clientApplication,proto3" json:"client_application,omitempty"` + // Client application version + ClientApplicationVersion string `protobuf:"bytes,26,opt,name=client_application_version,json=clientApplicationVersion,proto3" json:"client_application_version,omitempty"` + // Severity of the violation + Severity string `protobuf:"bytes,27,opt,name=severity,proto3" json:"severity,omitempty"` + // Threat campaign names + ThreatCampaignNames string `protobuf:"bytes,28,opt,name=threat_campaign_names,json=threatCampaignNames,proto3" json:"threat_campaign_names,omitempty"` + // Bot anomalies detected + BotAnomalies string `protobuf:"bytes,29,opt,name=bot_anomalies,json=botAnomalies,proto3" json:"bot_anomalies,omitempty"` + // Bot category + BotCategory string `protobuf:"bytes,30,opt,name=bot_category,json=botCategory,proto3" json:"bot_category,omitempty"` + // Enforced bot anomalies + EnforcedBotAnomalies string `protobuf:"bytes,31,opt,name=enforced_bot_anomalies,json=enforcedBotAnomalies,proto3" json:"enforced_bot_anomalies,omitempty"` + // Bot signature name + BotSignatureName string `protobuf:"bytes,32,opt,name=bot_signature_name,json=botSignatureName,proto3" json:"bot_signature_name,omitempty"` + // System ID + SystemId string `protobuf:"bytes,33,opt,name=system_id,json=systemId,proto3" json:"system_id,omitempty"` + // Instance tags + InstanceTags string `protobuf:"bytes,34,opt,name=instance_tags,json=instanceTags,proto3" json:"instance_tags,omitempty"` + // Instance group + InstanceGroup string `protobuf:"bytes,35,opt,name=instance_group,json=instanceGroup,proto3" json:"instance_group,omitempty"` + // Parent hostname + ParentHostname string `protobuf:"bytes,36,opt,name=parent_hostname,json=parentHostname,proto3" json:"parent_hostname,omitempty"` + // Display name + DisplayName string `protobuf:"bytes,37,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + // Detailed violation data + ViolationsData []*ViolationData `protobuf:"bytes,38,rep,name=violations_data,json=violationsData,proto3" json:"violations_data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SecurityViolationEvent) Reset() { + *x = SecurityViolationEvent{} + mi := &file_events_v1_security_violation_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SecurityViolationEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecurityViolationEvent) ProtoMessage() {} + +func (x *SecurityViolationEvent) ProtoReflect() protoreflect.Message { + mi := &file_events_v1_security_violation_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecurityViolationEvent.ProtoReflect.Descriptor instead. +func (*SecurityViolationEvent) Descriptor() ([]byte, []int) { + return file_events_v1_security_violation_proto_rawDescGZIP(), []int{0} +} + +func (x *SecurityViolationEvent) GetPolicyName() string { + if x != nil { + return x.PolicyName + } + return "" +} + +func (x *SecurityViolationEvent) GetSupportId() string { + if x != nil { + return x.SupportId + } + return "" +} + +func (x *SecurityViolationEvent) GetOutcome() string { + if x != nil { + return x.Outcome + } + return "" +} + +func (x *SecurityViolationEvent) GetOutcomeReason() string { + if x != nil { + return x.OutcomeReason + } + return "" +} + +func (x *SecurityViolationEvent) GetBlockingExceptionReason() string { + if x != nil { + return x.BlockingExceptionReason + } + return "" +} + +func (x *SecurityViolationEvent) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *SecurityViolationEvent) GetProtocol() string { + if x != nil { + return x.Protocol + } + return "" +} + +func (x *SecurityViolationEvent) GetXffHeaderValue() string { + if x != nil { + return x.XffHeaderValue + } + return "" +} + +func (x *SecurityViolationEvent) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +func (x *SecurityViolationEvent) GetRequest() string { + if x != nil { + return x.Request + } + return "" +} + +func (x *SecurityViolationEvent) GetIsTruncated() string { + if x != nil { + return x.IsTruncated + } + return "" +} + +func (x *SecurityViolationEvent) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *SecurityViolationEvent) GetResponseCode() string { + if x != nil { + return x.ResponseCode + } + return "" +} + +func (x *SecurityViolationEvent) GetServerAddr() string { + if x != nil { + return x.ServerAddr + } + return "" +} + +func (x *SecurityViolationEvent) GetVsName() string { + if x != nil { + return x.VsName + } + return "" +} + +func (x *SecurityViolationEvent) GetRemoteAddr() string { + if x != nil { + return x.RemoteAddr + } + return "" +} + +func (x *SecurityViolationEvent) GetDestinationPort() string { + if x != nil { + return x.DestinationPort + } + return "" +} + +func (x *SecurityViolationEvent) GetServerPort() string { + if x != nil { + return x.ServerPort + } + return "" +} + +func (x *SecurityViolationEvent) GetViolations() string { + if x != nil { + return x.Violations + } + return "" +} + +func (x *SecurityViolationEvent) GetSubViolations() string { + if x != nil { + return x.SubViolations + } + return "" +} + +func (x *SecurityViolationEvent) GetViolationRating() string { + if x != nil { + return x.ViolationRating + } + return "" +} + +func (x *SecurityViolationEvent) GetSigSetNames() string { + if x != nil { + return x.SigSetNames + } + return "" +} + +func (x *SecurityViolationEvent) GetSigCves() string { + if x != nil { + return x.SigCves + } + return "" +} + +func (x *SecurityViolationEvent) GetClientClass() string { + if x != nil { + return x.ClientClass + } + return "" +} + +func (x *SecurityViolationEvent) GetClientApplication() string { + if x != nil { + return x.ClientApplication + } + return "" +} + +func (x *SecurityViolationEvent) GetClientApplicationVersion() string { + if x != nil { + return x.ClientApplicationVersion + } + return "" +} + +func (x *SecurityViolationEvent) GetSeverity() string { + if x != nil { + return x.Severity + } + return "" +} + +func (x *SecurityViolationEvent) GetThreatCampaignNames() string { + if x != nil { + return x.ThreatCampaignNames + } + return "" +} + +func (x *SecurityViolationEvent) GetBotAnomalies() string { + if x != nil { + return x.BotAnomalies + } + return "" +} + +func (x *SecurityViolationEvent) GetBotCategory() string { + if x != nil { + return x.BotCategory + } + return "" +} + +func (x *SecurityViolationEvent) GetEnforcedBotAnomalies() string { + if x != nil { + return x.EnforcedBotAnomalies + } + return "" +} + +func (x *SecurityViolationEvent) GetBotSignatureName() string { + if x != nil { + return x.BotSignatureName + } + return "" +} + +func (x *SecurityViolationEvent) GetSystemId() string { + if x != nil { + return x.SystemId + } + return "" +} + +func (x *SecurityViolationEvent) GetInstanceTags() string { + if x != nil { + return x.InstanceTags + } + return "" +} + +func (x *SecurityViolationEvent) GetInstanceGroup() string { + if x != nil { + return x.InstanceGroup + } + return "" +} + +func (x *SecurityViolationEvent) GetParentHostname() string { + if x != nil { + return x.ParentHostname + } + return "" +} + +func (x *SecurityViolationEvent) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *SecurityViolationEvent) GetViolationsData() []*ViolationData { + if x != nil { + return x.ViolationsData + } + return nil +} + +// ViolationData represents individual violation details +type ViolationData struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Name of the violation + ViolationDataName string `protobuf:"bytes,1,opt,name=violation_data_name,json=violationDataName,proto3" json:"violation_data_name,omitempty"` + // Context of the violation + ViolationDataContext string `protobuf:"bytes,2,opt,name=violation_data_context,json=violationDataContext,proto3" json:"violation_data_context,omitempty"` + // Context data associated with the violation + ViolationDataContextData *ContextData `protobuf:"bytes,3,opt,name=violation_data_context_data,json=violationDataContextData,proto3" json:"violation_data_context_data,omitempty"` + // Signature data for the violation + ViolationDataSignatures []*SignatureData `protobuf:"bytes,4,rep,name=violation_data_signatures,json=violationDataSignatures,proto3" json:"violation_data_signatures,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ViolationData) Reset() { + *x = ViolationData{} + mi := &file_events_v1_security_violation_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ViolationData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ViolationData) ProtoMessage() {} + +func (x *ViolationData) ProtoReflect() protoreflect.Message { + mi := &file_events_v1_security_violation_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ViolationData.ProtoReflect.Descriptor instead. +func (*ViolationData) Descriptor() ([]byte, []int) { + return file_events_v1_security_violation_proto_rawDescGZIP(), []int{1} +} + +func (x *ViolationData) GetViolationDataName() string { + if x != nil { + return x.ViolationDataName + } + return "" +} + +func (x *ViolationData) GetViolationDataContext() string { + if x != nil { + return x.ViolationDataContext + } + return "" +} + +func (x *ViolationData) GetViolationDataContextData() *ContextData { + if x != nil { + return x.ViolationDataContextData + } + return nil +} + +func (x *ViolationData) GetViolationDataSignatures() []*SignatureData { + if x != nil { + return x.ViolationDataSignatures + } + return nil +} + +// SignatureData represents signature data contained within each violation +type SignatureData struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Signature ID + SigDataId string `protobuf:"bytes,1,opt,name=sig_data_id,json=sigDataId,proto3" json:"sig_data_id,omitempty"` + // Blocking mask + SigDataBlockingMask string `protobuf:"bytes,2,opt,name=sig_data_blocking_mask,json=sigDataBlockingMask,proto3" json:"sig_data_blocking_mask,omitempty"` + // Buffer information + SigDataBuffer string `protobuf:"bytes,3,opt,name=sig_data_buffer,json=sigDataBuffer,proto3" json:"sig_data_buffer,omitempty"` + // Offset in the buffer + SigDataOffset string `protobuf:"bytes,4,opt,name=sig_data_offset,json=sigDataOffset,proto3" json:"sig_data_offset,omitempty"` + // Length of the signature match + SigDataLength string `protobuf:"bytes,5,opt,name=sig_data_length,json=sigDataLength,proto3" json:"sig_data_length,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SignatureData) Reset() { + *x = SignatureData{} + mi := &file_events_v1_security_violation_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SignatureData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignatureData) ProtoMessage() {} + +func (x *SignatureData) ProtoReflect() protoreflect.Message { + mi := &file_events_v1_security_violation_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignatureData.ProtoReflect.Descriptor instead. +func (*SignatureData) Descriptor() ([]byte, []int) { + return file_events_v1_security_violation_proto_rawDescGZIP(), []int{2} +} + +func (x *SignatureData) GetSigDataId() string { + if x != nil { + return x.SigDataId + } + return "" +} + +func (x *SignatureData) GetSigDataBlockingMask() string { + if x != nil { + return x.SigDataBlockingMask + } + return "" +} + +func (x *SignatureData) GetSigDataBuffer() string { + if x != nil { + return x.SigDataBuffer + } + return "" +} + +func (x *SignatureData) GetSigDataOffset() string { + if x != nil { + return x.SigDataOffset + } + return "" +} + +func (x *SignatureData) GetSigDataLength() string { + if x != nil { + return x.SigDataLength + } + return "" +} + +// ContextData represents the context data of the violation +type ContextData struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Name of the context + ContextDataName string `protobuf:"bytes,1,opt,name=context_data_name,json=contextDataName,proto3" json:"context_data_name,omitempty"` + // Value of the context + ContextDataValue string `protobuf:"bytes,2,opt,name=context_data_value,json=contextDataValue,proto3" json:"context_data_value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ContextData) Reset() { + *x = ContextData{} + mi := &file_events_v1_security_violation_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ContextData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContextData) ProtoMessage() {} + +func (x *ContextData) ProtoReflect() protoreflect.Message { + mi := &file_events_v1_security_violation_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContextData.ProtoReflect.Descriptor instead. +func (*ContextData) Descriptor() ([]byte, []int) { + return file_events_v1_security_violation_proto_rawDescGZIP(), []int{3} +} + +func (x *ContextData) GetContextDataName() string { + if x != nil { + return x.ContextDataName + } + return "" +} + +func (x *ContextData) GetContextDataValue() string { + if x != nil { + return x.ContextDataValue + } + return "" +} + +var File_events_v1_security_violation_proto protoreflect.FileDescriptor + +const file_events_v1_security_violation_proto_rawDesc = "" + + "\n" + + "\"events/v1/security_violation.proto\x12\tevents.v1\"\xaa\v\n" + + "\x16SecurityViolationEvent\x12\x1f\n" + + "\vpolicy_name\x18\x01 \x01(\tR\n" + + "policyName\x12\x1d\n" + + "\n" + + "support_id\x18\x02 \x01(\tR\tsupportId\x12\x18\n" + + "\aoutcome\x18\x03 \x01(\tR\aoutcome\x12%\n" + + "\x0eoutcome_reason\x18\x04 \x01(\tR\routcomeReason\x12:\n" + + "\x19blocking_exception_reason\x18\x05 \x01(\tR\x17blockingExceptionReason\x12\x16\n" + + "\x06method\x18\x06 \x01(\tR\x06method\x12\x1a\n" + + "\bprotocol\x18\a \x01(\tR\bprotocol\x12(\n" + + "\x10xff_header_value\x18\b \x01(\tR\x0exffHeaderValue\x12\x10\n" + + "\x03uri\x18\t \x01(\tR\x03uri\x12\x18\n" + + "\arequest\x18\n" + + " \x01(\tR\arequest\x12!\n" + + "\fis_truncated\x18\v \x01(\tR\visTruncated\x12%\n" + + "\x0erequest_status\x18\f \x01(\tR\rrequestStatus\x12#\n" + + "\rresponse_code\x18\r \x01(\tR\fresponseCode\x12\x1f\n" + + "\vserver_addr\x18\x0e \x01(\tR\n" + + "serverAddr\x12\x17\n" + + "\avs_name\x18\x0f \x01(\tR\x06vsName\x12\x1f\n" + + "\vremote_addr\x18\x10 \x01(\tR\n" + + "remoteAddr\x12)\n" + + "\x10destination_port\x18\x11 \x01(\tR\x0fdestinationPort\x12\x1f\n" + + "\vserver_port\x18\x12 \x01(\tR\n" + + "serverPort\x12\x1e\n" + + "\n" + + "violations\x18\x13 \x01(\tR\n" + + "violations\x12%\n" + + "\x0esub_violations\x18\x14 \x01(\tR\rsubViolations\x12)\n" + + "\x10violation_rating\x18\x15 \x01(\tR\x0fviolationRating\x12\"\n" + + "\rsig_set_names\x18\x16 \x01(\tR\vsigSetNames\x12\x19\n" + + "\bsig_cves\x18\x17 \x01(\tR\asigCves\x12!\n" + + "\fclient_class\x18\x18 \x01(\tR\vclientClass\x12-\n" + + "\x12client_application\x18\x19 \x01(\tR\x11clientApplication\x12<\n" + + "\x1aclient_application_version\x18\x1a \x01(\tR\x18clientApplicationVersion\x12\x1a\n" + + "\bseverity\x18\x1b \x01(\tR\bseverity\x122\n" + + "\x15threat_campaign_names\x18\x1c \x01(\tR\x13threatCampaignNames\x12#\n" + + "\rbot_anomalies\x18\x1d \x01(\tR\fbotAnomalies\x12!\n" + + "\fbot_category\x18\x1e \x01(\tR\vbotCategory\x124\n" + + "\x16enforced_bot_anomalies\x18\x1f \x01(\tR\x14enforcedBotAnomalies\x12,\n" + + "\x12bot_signature_name\x18 \x01(\tR\x10botSignatureName\x12\x1b\n" + + "\tsystem_id\x18! \x01(\tR\bsystemId\x12#\n" + + "\rinstance_tags\x18\" \x01(\tR\finstanceTags\x12%\n" + + "\x0einstance_group\x18# \x01(\tR\rinstanceGroup\x12'\n" + + "\x0fparent_hostname\x18$ \x01(\tR\x0eparentHostname\x12!\n" + + "\fdisplay_name\x18% \x01(\tR\vdisplayName\x12A\n" + + "\x0fviolations_data\x18& \x03(\v2\x18.events.v1.ViolationDataR\x0eviolationsData\"\xa2\x02\n" + + "\rViolationData\x12.\n" + + "\x13violation_data_name\x18\x01 \x01(\tR\x11violationDataName\x124\n" + + "\x16violation_data_context\x18\x02 \x01(\tR\x14violationDataContext\x12U\n" + + "\x1bviolation_data_context_data\x18\x03 \x01(\v2\x16.events.v1.ContextDataR\x18violationDataContextData\x12T\n" + + "\x19violation_data_signatures\x18\x04 \x03(\v2\x18.events.v1.SignatureDataR\x17violationDataSignatures\"\xdc\x01\n" + + "\rSignatureData\x12\x1e\n" + + "\vsig_data_id\x18\x01 \x01(\tR\tsigDataId\x123\n" + + "\x16sig_data_blocking_mask\x18\x02 \x01(\tR\x13sigDataBlockingMask\x12&\n" + + "\x0fsig_data_buffer\x18\x03 \x01(\tR\rsigDataBuffer\x12&\n" + + "\x0fsig_data_offset\x18\x04 \x01(\tR\rsigDataOffset\x12&\n" + + "\x0fsig_data_length\x18\x05 \x01(\tR\rsigDataLength\"g\n" + + "\vContextData\x12*\n" + + "\x11context_data_name\x18\x01 \x01(\tR\x0fcontextDataName\x12,\n" + + "\x12context_data_value\x18\x02 \x01(\tR\x10contextDataValueB\vZ\tevents/v1b\x06proto3" + +var ( + file_events_v1_security_violation_proto_rawDescOnce sync.Once + file_events_v1_security_violation_proto_rawDescData []byte +) + +func file_events_v1_security_violation_proto_rawDescGZIP() []byte { + file_events_v1_security_violation_proto_rawDescOnce.Do(func() { + file_events_v1_security_violation_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_events_v1_security_violation_proto_rawDesc), len(file_events_v1_security_violation_proto_rawDesc))) + }) + return file_events_v1_security_violation_proto_rawDescData +} + +var file_events_v1_security_violation_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_events_v1_security_violation_proto_goTypes = []any{ + (*SecurityViolationEvent)(nil), // 0: events.v1.SecurityViolationEvent + (*ViolationData)(nil), // 1: events.v1.ViolationData + (*SignatureData)(nil), // 2: events.v1.SignatureData + (*ContextData)(nil), // 3: events.v1.ContextData +} +var file_events_v1_security_violation_proto_depIdxs = []int32{ + 1, // 0: events.v1.SecurityViolationEvent.violations_data:type_name -> events.v1.ViolationData + 3, // 1: events.v1.ViolationData.violation_data_context_data:type_name -> events.v1.ContextData + 2, // 2: events.v1.ViolationData.violation_data_signatures:type_name -> events.v1.SignatureData + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_events_v1_security_violation_proto_init() } +func file_events_v1_security_violation_proto_init() { + if File_events_v1_security_violation_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_events_v1_security_violation_proto_rawDesc), len(file_events_v1_security_violation_proto_rawDesc)), + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_events_v1_security_violation_proto_goTypes, + DependencyIndexes: file_events_v1_security_violation_proto_depIdxs, + MessageInfos: file_events_v1_security_violation_proto_msgTypes, + }.Build() + File_events_v1_security_violation_proto = out.File + file_events_v1_security_violation_proto_goTypes = nil + file_events_v1_security_violation_proto_depIdxs = nil +} diff --git a/api/grpc/events/v1/security_violation.pb.validate.go b/api/grpc/events/v1/security_violation.pb.validate.go new file mode 100644 index 0000000000..2b0a868bad --- /dev/null +++ b/api/grpc/events/v1/security_violation.pb.validate.go @@ -0,0 +1,626 @@ +// Code generated by protoc-gen-validate. DO NOT EDIT. +// source: events/v1/security_violation.proto + +package v1 + +import ( + "bytes" + "errors" + "fmt" + "net" + "net/mail" + "net/url" + "regexp" + "sort" + "strings" + "time" + "unicode/utf8" + + "google.golang.org/protobuf/types/known/anypb" +) + +// ensure the imports are used +var ( + _ = bytes.MinRead + _ = errors.New("") + _ = fmt.Print + _ = utf8.UTFMax + _ = (*regexp.Regexp)(nil) + _ = (*strings.Reader)(nil) + _ = net.IPv4len + _ = time.Duration(0) + _ = (*url.URL)(nil) + _ = (*mail.Address)(nil) + _ = anypb.Any{} + _ = sort.Sort +) + +// Validate checks the field values on SecurityViolationEvent with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *SecurityViolationEvent) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on SecurityViolationEvent with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// SecurityViolationEventMultiError, or nil if none found. +func (m *SecurityViolationEvent) ValidateAll() error { + return m.validate(true) +} + +func (m *SecurityViolationEvent) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for PolicyName + + // no validation rules for SupportId + + // no validation rules for Outcome + + // no validation rules for OutcomeReason + + // no validation rules for BlockingExceptionReason + + // no validation rules for Method + + // no validation rules for Protocol + + // no validation rules for XffHeaderValue + + // no validation rules for Uri + + // no validation rules for Request + + // no validation rules for IsTruncated + + // no validation rules for RequestStatus + + // no validation rules for ResponseCode + + // no validation rules for ServerAddr + + // no validation rules for VsName + + // no validation rules for RemoteAddr + + // no validation rules for DestinationPort + + // no validation rules for ServerPort + + // no validation rules for Violations + + // no validation rules for SubViolations + + // no validation rules for ViolationRating + + // no validation rules for SigSetNames + + // no validation rules for SigCves + + // no validation rules for ClientClass + + // no validation rules for ClientApplication + + // no validation rules for ClientApplicationVersion + + // no validation rules for Severity + + // no validation rules for ThreatCampaignNames + + // no validation rules for BotAnomalies + + // no validation rules for BotCategory + + // no validation rules for EnforcedBotAnomalies + + // no validation rules for BotSignatureName + + // no validation rules for SystemId + + // no validation rules for InstanceTags + + // no validation rules for InstanceGroup + + // no validation rules for ParentHostname + + // no validation rules for DisplayName + + for idx, item := range m.GetViolationsData() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, SecurityViolationEventValidationError{ + field: fmt.Sprintf("ViolationsData[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, SecurityViolationEventValidationError{ + field: fmt.Sprintf("ViolationsData[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return SecurityViolationEventValidationError{ + field: fmt.Sprintf("ViolationsData[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return SecurityViolationEventMultiError(errors) + } + + return nil +} + +// SecurityViolationEventMultiError is an error wrapping multiple validation +// errors returned by SecurityViolationEvent.ValidateAll() if the designated +// constraints aren't met. +type SecurityViolationEventMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m SecurityViolationEventMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m SecurityViolationEventMultiError) AllErrors() []error { return m } + +// SecurityViolationEventValidationError is the validation error returned by +// SecurityViolationEvent.Validate if the designated constraints aren't met. +type SecurityViolationEventValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e SecurityViolationEventValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e SecurityViolationEventValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e SecurityViolationEventValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e SecurityViolationEventValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e SecurityViolationEventValidationError) ErrorName() string { + return "SecurityViolationEventValidationError" +} + +// Error satisfies the builtin error interface +func (e SecurityViolationEventValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sSecurityViolationEvent.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = SecurityViolationEventValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = SecurityViolationEventValidationError{} + +// Validate checks the field values on ViolationData with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *ViolationData) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ViolationData with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in ViolationDataMultiError, or +// nil if none found. +func (m *ViolationData) ValidateAll() error { + return m.validate(true) +} + +func (m *ViolationData) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for ViolationDataName + + // no validation rules for ViolationDataContext + + if all { + switch v := interface{}(m.GetViolationDataContextData()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ViolationDataValidationError{ + field: "ViolationDataContextData", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ViolationDataValidationError{ + field: "ViolationDataContextData", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetViolationDataContextData()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ViolationDataValidationError{ + field: "ViolationDataContextData", + reason: "embedded message failed validation", + cause: err, + } + } + } + + for idx, item := range m.GetViolationDataSignatures() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ViolationDataValidationError{ + field: fmt.Sprintf("ViolationDataSignatures[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ViolationDataValidationError{ + field: fmt.Sprintf("ViolationDataSignatures[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ViolationDataValidationError{ + field: fmt.Sprintf("ViolationDataSignatures[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return ViolationDataMultiError(errors) + } + + return nil +} + +// ViolationDataMultiError is an error wrapping multiple validation errors +// returned by ViolationData.ValidateAll() if the designated constraints +// aren't met. +type ViolationDataMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ViolationDataMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ViolationDataMultiError) AllErrors() []error { return m } + +// ViolationDataValidationError is the validation error returned by +// ViolationData.Validate if the designated constraints aren't met. +type ViolationDataValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ViolationDataValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ViolationDataValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ViolationDataValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ViolationDataValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ViolationDataValidationError) ErrorName() string { return "ViolationDataValidationError" } + +// Error satisfies the builtin error interface +func (e ViolationDataValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sViolationData.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ViolationDataValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ViolationDataValidationError{} + +// Validate checks the field values on SignatureData with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *SignatureData) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on SignatureData with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in SignatureDataMultiError, or +// nil if none found. +func (m *SignatureData) ValidateAll() error { + return m.validate(true) +} + +func (m *SignatureData) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for SigDataId + + // no validation rules for SigDataBlockingMask + + // no validation rules for SigDataBuffer + + // no validation rules for SigDataOffset + + // no validation rules for SigDataLength + + if len(errors) > 0 { + return SignatureDataMultiError(errors) + } + + return nil +} + +// SignatureDataMultiError is an error wrapping multiple validation errors +// returned by SignatureData.ValidateAll() if the designated constraints +// aren't met. +type SignatureDataMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m SignatureDataMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m SignatureDataMultiError) AllErrors() []error { return m } + +// SignatureDataValidationError is the validation error returned by +// SignatureData.Validate if the designated constraints aren't met. +type SignatureDataValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e SignatureDataValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e SignatureDataValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e SignatureDataValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e SignatureDataValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e SignatureDataValidationError) ErrorName() string { return "SignatureDataValidationError" } + +// Error satisfies the builtin error interface +func (e SignatureDataValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sSignatureData.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = SignatureDataValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = SignatureDataValidationError{} + +// Validate checks the field values on ContextData with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *ContextData) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ContextData with the rules defined in +// the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in ContextDataMultiError, or +// nil if none found. +func (m *ContextData) ValidateAll() error { + return m.validate(true) +} + +func (m *ContextData) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for ContextDataName + + // no validation rules for ContextDataValue + + if len(errors) > 0 { + return ContextDataMultiError(errors) + } + + return nil +} + +// ContextDataMultiError is an error wrapping multiple validation errors +// returned by ContextData.ValidateAll() if the designated constraints aren't met. +type ContextDataMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ContextDataMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ContextDataMultiError) AllErrors() []error { return m } + +// ContextDataValidationError is the validation error returned by +// ContextData.Validate if the designated constraints aren't met. +type ContextDataValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ContextDataValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ContextDataValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ContextDataValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ContextDataValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ContextDataValidationError) ErrorName() string { return "ContextDataValidationError" } + +// Error satisfies the builtin error interface +func (e ContextDataValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sContextData.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ContextDataValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ContextDataValidationError{} diff --git a/api/grpc/events/v1/security_violation.proto b/api/grpc/events/v1/security_violation.proto new file mode 100644 index 0000000000..674a0f1e4d --- /dev/null +++ b/api/grpc/events/v1/security_violation.proto @@ -0,0 +1,122 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. +syntax = "proto3"; +package events.v1; + +option go_package = "events/v1"; + +// SecurityViolationEvent represents the structured NGINX App Protect security violation data +message SecurityViolationEvent { + // Name of the security policy + string policy_name = 1; + // Unique support ID for the violation + string support_id = 2; + // Outcome of the request (e.g., REJECTED, PASSED) + string outcome = 3; + // Reason for the outcome + string outcome_reason = 4; + // Reason for blocking exception if applicable + string blocking_exception_reason = 5; + // HTTP method used + string method = 6; + // Protocol used (e.g., HTTP/1.1) + string protocol = 7; + // X-Forwarded-For header value + string xff_header_value = 8; + // Request URI + string uri = 9; + // Full request + string request = 10; + // Indicates if the request was truncated + string is_truncated = 11; + // Status of the request + string request_status = 12; + // HTTP response code + string response_code = 13; + // Server address + string server_addr = 14; + // Virtual server name + string vs_name = 15; + // Remote address of the client + string remote_addr = 16; + // Destination port + string destination_port = 17; + // Server port + string server_port = 18; + // List of violations + string violations = 19; + // List of sub-violations + string sub_violations = 20; + // Violation rating + string violation_rating = 21; + // Signature set names + string sig_set_names = 22; + // Signature CVEs + string sig_cves = 23; + // Client class + string client_class = 24; + // Client application + string client_application = 25; + // Client application version + string client_application_version = 26; + // Severity of the violation + string severity = 27; + // Threat campaign names + string threat_campaign_names = 28; + // Bot anomalies detected + string bot_anomalies = 29; + // Bot category + string bot_category = 30; + // Enforced bot anomalies + string enforced_bot_anomalies = 31; + // Bot signature name + string bot_signature_name = 32; + // System ID + string system_id = 33; + // Instance tags + string instance_tags = 34; + // Instance group + string instance_group = 35; + // Parent hostname + string parent_hostname = 36; + // Display name + string display_name = 37; + // Detailed violation data + repeated ViolationData violations_data = 38; +} + +// ViolationData represents individual violation details +message ViolationData { + // Name of the violation + string violation_data_name = 1; + // Context of the violation + string violation_data_context = 2; + // Context data associated with the violation + ContextData violation_data_context_data = 3; + // Signature data for the violation + repeated SignatureData violation_data_signatures = 4; +} + +// SignatureData represents signature data contained within each violation +message SignatureData { + // Signature ID + string sig_data_id = 1; + // Blocking mask + string sig_data_blocking_mask = 2; + // Buffer information + string sig_data_buffer = 3; + // Offset in the buffer + string sig_data_offset = 4; + // Length of the signature match + string sig_data_length = 5; +} + +// ContextData represents the context data of the violation +message ContextData { + // Name of the context + string context_data_name = 1; + // Value of the context + string context_data_value = 2; +} diff --git a/docs/proto/protos.md b/docs/proto/protos.md index 18301abd50..1165ad1b9d 100644 --- a/docs/proto/protos.md +++ b/docs/proto/protos.md @@ -3,6 +3,12 @@ ## Table of Contents +- [events/v1/security_violation.proto](#events_v1_security_violation-proto) + - [ContextData](#events-v1-ContextData) + - [SecurityViolationEvent](#events-v1-SecurityViolationEvent) + - [SignatureData](#events-v1-SignatureData) + - [ViolationData](#events-v1-ViolationData) + - [mpi/v1/common.proto](#mpi_v1_common-proto) - [AuthSettings](#mpi-v1-AuthSettings) - [CommandResponse](#mpi-v1-CommandResponse) @@ -95,6 +101,130 @@ + +
+ +## events/v1/security_violation.proto +Copyright (c) F5, Inc. + +This source code is licensed under the Apache License, Version 2.0 license found in the +LICENSE file in the root directory of this source tree. + + + + +### ContextData +ContextData represents the context data of the violation + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| context_data_name | [string](#string) | | Name of the context | +| context_data_value | [string](#string) | | Value of the context | + + + + + + + + +### SecurityViolationEvent +SecurityViolationEvent represents the structured NGINX App Protect security violation data + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| policy_name | [string](#string) | | Name of the security policy | +| support_id | [string](#string) | | Unique support ID for the violation | +| outcome | [string](#string) | | Outcome of the request (e.g., REJECTED, PASSED) | +| outcome_reason | [string](#string) | | Reason for the outcome | +| blocking_exception_reason | [string](#string) | | Reason for blocking exception if applicable | +| method | [string](#string) | | HTTP method used | +| protocol | [string](#string) | | Protocol used (e.g., HTTP/1.1) | +| xff_header_value | [string](#string) | | X-Forwarded-For header value | +| uri | [string](#string) | | Request URI | +| request | [string](#string) | | Full request | +| is_truncated | [string](#string) | | Indicates if the request was truncated | +| request_status | [string](#string) | | Status of the request | +| response_code | [string](#string) | | HTTP response code | +| server_addr | [string](#string) | | Server address | +| vs_name | [string](#string) | | Virtual server name | +| remote_addr | [string](#string) | | Remote address of the client | +| destination_port | [string](#string) | | Destination port | +| server_port | [string](#string) | | Server port | +| violations | [string](#string) | | List of violations | +| sub_violations | [string](#string) | | List of sub-violations | +| violation_rating | [string](#string) | | Violation rating | +| sig_set_names | [string](#string) | | Signature set names | +| sig_cves | [string](#string) | | Signature CVEs | +| client_class | [string](#string) | | Client class | +| client_application | [string](#string) | | Client application | +| client_application_version | [string](#string) | | Client application version | +| severity | [string](#string) | | Severity of the violation | +| threat_campaign_names | [string](#string) | | Threat campaign names | +| bot_anomalies | [string](#string) | | Bot anomalies detected | +| bot_category | [string](#string) | | Bot category | +| enforced_bot_anomalies | [string](#string) | | Enforced bot anomalies | +| bot_signature_name | [string](#string) | | Bot signature name | +| system_id | [string](#string) | | System ID | +| instance_tags | [string](#string) | | Instance tags | +| instance_group | [string](#string) | | Instance group | +| parent_hostname | [string](#string) | | Parent hostname | +| display_name | [string](#string) | | Display name | +| violations_data | [ViolationData](#events-v1-ViolationData) | repeated | Detailed violation data | + + + + + + + + +### SignatureData +SignatureData represents signature data contained within each violation + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| sig_data_id | [string](#string) | | Signature ID | +| sig_data_blocking_mask | [string](#string) | | Blocking mask | +| sig_data_buffer | [string](#string) | | Buffer information | +| sig_data_offset | [string](#string) | | Offset in the buffer | +| sig_data_length | [string](#string) | | Length of the signature match | + + + + + + + + +### ViolationData +ViolationData represents individual violation details + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| violation_data_name | [string](#string) | | Name of the violation | +| violation_data_context | [string](#string) | | Context of the violation | +| violation_data_context_data | [ContextData](#events-v1-ContextData) | | Context data associated with the violation | +| violation_data_signatures | [SignatureData](#events-v1-SignatureData) | repeated | Signature data for the violation | + + + + + + + + + + + + + + + diff --git a/internal/collector/securityviolationsprocessor/README.md b/internal/collector/securityviolationsprocessor/README.md index 77a4d05801..8be5d67d33 100644 --- a/internal/collector/securityviolationsprocessor/README.md +++ b/internal/collector/securityviolationsprocessor/README.md @@ -1,5 +1,51 @@ -# SecurityViolations Processor +# Security Violations Processor -Internal component of the NGINX Agent that processes security violation syslog messages. Parses RFC3164 formatted syslog entries from log records and extracts structured attributes. Successfully parsed messages have their body replaced with the clean message content. +OpenTelemetry Collector processor that transforms NGINX App Protect security violation syslog messages into structured protobuf events. -Part of the NGINX Agent's log collection pipeline. \ No newline at end of file +## What It Does + +Processes NGINX App Protect WAF syslog messages and transforms them into `SecurityViolationEvent` protobuf messages: + +1. Parses RFC3164 syslog messages (best-effort mode) +2. Extracts CSV formatted data from NAP `secops_dashboard` log profile +3. Parses XML violation details with context extraction (parameter, header, cookie, uri, request) +4. Extracts attack signature details +5. Outputs structured protobuf events for downstream consumption + +## Implementation + +| File | Purpose | +|------|---------| +| [`processor.go`](processor.go) | Main processor implementation, RFC3164 parsing, orchestration | +| [`csv_parser.go`](csv_parser.go) | CSV parsing and field mapping | +| [`violations_parser.go`](violations_parser.go) | XML parsing, context extraction, signature parsing | +| [`xml_structs.go`](xml_structs.go) | XML structure definitions (BADMSG, violation contexts) | +| [`helpers.go`](helpers.go) | Utility functions | + +See individual files for implementation details. Protobuf schema defined in [`api/grpc/events/v1/security_violation.proto`](../../../api/grpc/events/v1/security_violation.proto). + +## Requirements + +- **Input**: NAP syslog messages with `secops_dashboard` log profile (33 CSV fields) +- **Output**: `SecurityViolationEvent` protobuf messages + +## Testing + +```bash +# Run all tests +go test ./internal/collector/securityviolationsprocessor -v + +# Check coverage +go test ./internal/collector/securityviolationsprocessor -coverprofile=coverage.out +go tool cover -html=coverage.out +``` + +Test coverage: CSV parsing, XML parsing (5 violation contexts), encoding edge cases, error handling. + +## Error Handling + +Implements graceful degradation: +- Malformed XML: Logs warning, continues processing +- Base64 decode errors: Falls back to raw data +- Missing fields: Uses empty strings +- Context inference: Derives from violation names when not explicit diff --git a/internal/collector/securityviolationsprocessor/csv_parser.go b/internal/collector/securityviolationsprocessor/csv_parser.go new file mode 100644 index 0000000000..6b13a9a10b --- /dev/null +++ b/internal/collector/securityviolationsprocessor/csv_parser.go @@ -0,0 +1,136 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package securityviolationsprocessor + +import ( + "strings" + + events "github.com/nginx/agent/v3/api/grpc/events/v1" +) + +// parseCSVLog parses comma-separated syslog messages where fields are in a +// order : blocking_exception_reason,dest_port,ip_client,is_truncated_bool,method,policy_name,protocol,request_status,response_code,severity,sig_cves,sig_set_names,src_port,sub_violations,support_id,threat_campaign_names,violation_rating,vs_name,x_forwarded_for_header_value,outcome,outcome_reason,violations,violation_details,bot_signature_name,bot_category,bot_anomalies,enforced_bot_anomalies,client_class,client_application,client_application_version,transport_protocol,uri,request (secops_dashboard-log profile format). +// versions when key-value logging isn't enabled. +// +//nolint:lll //long test string kept for log profile readability +func (p *securityViolationsProcessor) parseCSVLog(message string) map[string]string { + fieldValueMap := make(map[string]string) + + // Remove the "ASM:" prefix if present so we only process the values + message = strings.TrimPrefix(message, "ASM:") + + fields := strings.Split(message, ",") + + // Mapping of CSV field positions to their corresponding keys + fieldOrder := []string{ + "blocking_exception_reason", + "dest_port", + "ip_client", + "is_truncated_bool", + "method", + "policy_name", + "protocol", + "request_status", + "response_code", + "severity", + "sig_cves", + "sig_set_names", + "src_port", + "sub_violations", + "support_id", + "threat_campaign_names", + "violation_rating", + "vs_name", + "x_forwarded_for_header_value", + "outcome", + "outcome_reason", + "violations", + "violation_details", + "bot_signature_name", + "bot_category", + "bot_anomalies", + "enforced_bot_anomalies", + "client_class", + "client_application", + "client_application_version", + "transport_protocol", + "uri", + "request", + } + + for i, field := range fields { + if i >= len(fieldOrder) { + break + } + fieldValueMap[fieldOrder[i]] = strings.TrimSpace(field) + } + + // combine multiple values separated by '::' + if combined, ok := fieldValueMap["sig_cves"]; ok { + parts := strings.SplitN(combined, "::", maxSplitParts) + fieldValueMap["sig_ids"] = parts[0] + if len(parts) > 1 { + fieldValueMap["sig_names"] = parts[1] + } + } + + if combined, ok := fieldValueMap["sig_set_names"]; ok { + parts := strings.SplitN(combined, "::", maxSplitParts) + fieldValueMap["sig_set_names"] = parts[0] + if len(parts) > 1 { + fieldValueMap["sig_cves"] = parts[1] + } + } + + return fieldValueMap +} + +func (p *securityViolationsProcessor) mapKVToSecurityViolationEvent(log *events.SecurityViolationEvent, + kvMap map[string]string, +) { + log.PolicyName = kvMap["policy_name"] + log.SupportId = kvMap["support_id"] + log.Outcome = kvMap["outcome"] + log.OutcomeReason = kvMap["outcome_reason"] + log.BlockingExceptionReason = kvMap["blocking_exception_reason"] + log.Method = kvMap["method"] + log.Protocol = kvMap["protocol"] + log.XffHeaderValue = kvMap["x_forwarded_for_header_value"] + log.Uri = kvMap["uri"] + log.Request = kvMap["request"] + log.IsTruncated = kvMap["is_truncated_bool"] + log.RequestStatus = kvMap["request_status"] + log.ResponseCode = kvMap["response_code"] + log.ServerAddr = kvMap["server_addr"] + log.VsName = kvMap["vs_name"] + log.RemoteAddr = kvMap["ip_client"] + log.DestinationPort = kvMap["dest_port"] + log.ServerPort = kvMap["src_port"] + log.Violations = kvMap["violations"] + log.SubViolations = kvMap["sub_violations"] + log.ViolationRating = kvMap["violation_rating"] + log.SigSetNames = kvMap["sig_set_names"] + log.SigCves = kvMap["sig_cves"] + log.ClientClass = kvMap["client_class"] + log.ClientApplication = kvMap["client_application"] + log.ClientApplicationVersion = kvMap["client_application_version"] + log.Severity = kvMap["severity"] + log.ThreatCampaignNames = kvMap["threat_campaign_names"] + log.BotAnomalies = kvMap["bot_anomalies"] + log.BotCategory = kvMap["bot_category"] + log.EnforcedBotAnomalies = kvMap["enforced_bot_anomalies"] + log.BotSignatureName = kvMap["bot_signature_name"] + log.InstanceTags = kvMap["instance_tags"] + log.InstanceGroup = kvMap["instance_group"] + log.DisplayName = kvMap["display_name"] + + if log.GetRemoteAddr() == "" { + log.RemoteAddr = kvMap["remote_addr"] + } + if log.GetDestinationPort() == "" { + log.DestinationPort = kvMap["remote_port"] + } +} diff --git a/internal/collector/securityviolationsprocessor/helpers.go b/internal/collector/securityviolationsprocessor/helpers.go new file mode 100644 index 0000000000..0cddefe25f --- /dev/null +++ b/internal/collector/securityviolationsprocessor/helpers.go @@ -0,0 +1,69 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package securityviolationsprocessor + +import ( + "net" + "regexp" + "strings" + + events "github.com/nginx/agent/v3/api/grpc/events/v1" +) + +func splitAndTrim(value string) []string { + if strings.TrimSpace(value) == "" || value == notAvailable { + return nil + } + + parts := strings.Split(value, ",") + + var trimmedParts []string + for _, part := range parts { + trimmed := strings.TrimSpace(part) + if trimmed != "" { + trimmedParts = append(trimmedParts, trimmed) + } + } + + return trimmedParts +} + +func buildSignatures(ids, names []string, mask, offset, length string) []*events.SignatureData { + signatures := make([]*events.SignatureData, 0, len(ids)) + for i, id := range ids { + if id == "" || id == notAvailable { + continue + } + signature := &events.SignatureData{ + SigDataId: id, + SigDataBlockingMask: mask, + SigDataOffset: offset, + SigDataLength: length, + } + if i < len(names) { + signature.SigDataBuffer = names[i] + } + signatures = append(signatures, signature) + } + + return signatures +} + +func extractIPFromHostname(hostname string) string { + if ip := net.ParseIP(hostname); ip != nil { + return ip.String() + } + + re := regexp.MustCompile(`^ip-([0-9-]+)`) + if matches := re.FindStringSubmatch(hostname); len(matches) > 1 { + candidate := strings.ReplaceAll(matches[1], "-", ".") + if net.ParseIP(candidate) != nil { + return candidate + } + } + + return "" +} diff --git a/internal/collector/securityviolationsprocessor/model.go b/internal/collector/securityviolationsprocessor/model.go deleted file mode 100644 index 3ce92c8b8f..0000000000 --- a/internal/collector/securityviolationsprocessor/model.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) F5, Inc. -// -// This source code is licensed under the Apache License, Version 2.0 license found in the -// LICENSE file in the root directory of this source tree. - -package securityviolationsprocessor - -// SecurityViolationEvent represents the structured NGINX App Protect security violation data -type SecurityViolationEvent struct { - PolicyName string `json:"policy_name"` - SupportID string `json:"support_id"` - Outcome string `json:"outcome"` - OutcomeReason string `json:"outcome_reason"` - BlockingExceptionReason string `json:"blocking_exception_reason"` - Method string `json:"method"` - Protocol string `json:"protocol"` - XForwardedForHeaderValue string `json:"xff_header_value"` - URI string `json:"uri"` - Request string `json:"request"` - IsTruncated string `json:"is_truncated"` - RequestStatus string `json:"request_status"` - ResponseCode string `json:"response_code"` - ServerAddr string `json:"server_addr"` - VSName string `json:"vs_name"` - RemoteAddr string `json:"remote_addr"` - RemotePort string `json:"destination_port"` - ServerPort string `json:"server_port"` - Violations string `json:"violations"` - SubViolations string `json:"sub_violations"` - ViolationRating string `json:"violation_rating"` - SigSetNames string `json:"sig_set_names"` - SigCVEs string `json:"sig_cves"` - ClientClass string `json:"client_class"` - ClientApplication string `json:"client_application"` - ClientApplicationVersion string `json:"client_application_version"` - Severity string `json:"severity"` - ThreatCampaignNames string `json:"threat_campaign_names"` - BotAnomalies string `json:"bot_anomalies"` - BotCategory string `json:"bot_category"` - EnforcedBotAnomalies string `json:"enforced_bot_anomalies"` - BotSignatureName string `json:"bot_signature_name"` - SystemID string `json:"system_id"` - InstanceTags string `json:"instance_tags"` - InstanceGroup string `json:"instance_group"` - ParentHostname string `json:"parent_hostname"` - DisplayName string `json:"display_name"` - ViolationsData []ViolationData `json:"violations_data"` -} - -type ViolationData struct { - Name string `json:"violation_data_name"` - Context string `json:"violation_data_context"` - ContextData ContextData `json:"violation_data_context_data"` - Signatures []SignatureData `json:"violation_data_signatures"` -} - -// SignatureData represents signature data contained within each violation -type SignatureData struct { - ID string `json:"sig_data_id"` - BlockingMask string `json:"sig_data_blocking_mask"` - Buffer string `json:"sig_data_buffer"` - Offset string `json:"sig_data_offset"` - Length string `json:"sig_data_length"` -} - -// ContextData represents the context data of the violation -type ContextData struct { - Name string `json:"context_data_name"` - Value string `json:"context_data_value"` -} diff --git a/internal/collector/securityviolationsprocessor/processor.go b/internal/collector/securityviolationsprocessor/processor.go index a4dbdaedfa..03c5be6e2d 100644 --- a/internal/collector/securityviolationsprocessor/processor.go +++ b/internal/collector/securityviolationsprocessor/processor.go @@ -10,13 +10,11 @@ import ( "encoding/json" "errors" "fmt" - "net" - "regexp" - "strings" "time" syslog "github.com/leodido/go-syslog/v4" "github.com/leodido/go-syslog/v4/rfc3164" + events "github.com/nginx/agent/v3/api/grpc/events/v1" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/pdata/pcommon" @@ -165,16 +163,18 @@ func (p *securityViolationsProcessor) processAppProtectMessage(lr plog.LogRecord lr.Body().SetStr(string(jsonData)) attrs := lr.Attributes() - attrs.PutStr("app_protect.policy_name", appProtectLog.PolicyName) - attrs.PutStr("app_protect.support_id", appProtectLog.SupportID) - attrs.PutStr("app_protect.outcome", appProtectLog.Outcome) - attrs.PutStr("app_protect.remote_addr", appProtectLog.RemoteAddr) + attrs.PutStr("app_protect.policy_name", appProtectLog.GetPolicyName()) + attrs.PutStr("app_protect.support_id", appProtectLog.GetSupportId()) + attrs.PutStr("app_protect.outcome", appProtectLog.GetOutcome()) + attrs.PutStr("app_protect.remote_addr", appProtectLog.GetRemoteAddr()) return nil } -func (p *securityViolationsProcessor) parseAppProtectLog(message string, hostname *string) *SecurityViolationEvent { - log := &SecurityViolationEvent{} +func (p *securityViolationsProcessor) parseAppProtectLog( + message string, hostname *string, +) *events.SecurityViolationEvent { + log := &events.SecurityViolationEvent{} p.assignHostnames(log, hostname) @@ -182,7 +182,7 @@ func (p *securityViolationsProcessor) parseAppProtectLog(message string, hostnam p.mapKVToSecurityViolationEvent(log, kvMap) - if log.ServerAddr == "" && hostname != nil { + if log.GetServerAddr() == "" && hostname != nil { if ip := extractIPFromHostname(*hostname); ip != "" { log.ServerAddr = ip } @@ -194,279 +194,16 @@ func (p *securityViolationsProcessor) parseAppProtectLog(message string, hostnam return log } -func (p *securityViolationsProcessor) assignHostnames(log *SecurityViolationEvent, hostname *string) { +func (p *securityViolationsProcessor) assignHostnames(log *events.SecurityViolationEvent, hostname *string) { if hostname == nil { return } - log.SystemID = *hostname + log.SystemId = *hostname log.ParentHostname = *hostname - if log.ServerAddr == "" { + if log.GetServerAddr() == "" { if ip := extractIPFromHostname(*hostname); ip != "" { log.ServerAddr = ip } } } - -// parseCSVLog parses comma-separated syslog messages where fields are in a -// order : blocking_exception_reason,dest_port,ip_client,is_truncated_bool,method,policy_name,protocol,request_status,response_code,severity,sig_cves,sig_set_names,src_port,sub_violations,support_id,threat_campaign_names,violation_rating,vs_name,x_forwarded_for_header_value,outcome,outcome_reason,violations,violation_details,bot_signature_name,bot_category,bot_anomalies,enforced_bot_anomalies,client_class,client_application,client_application_version,transport_protocol,uri,request (secops_dashboard-log profile format). -// versions when key-value logging isn't enabled. -// -//nolint:lll //long test string kept for log profile readability -func (p *securityViolationsProcessor) parseCSVLog(message string) map[string]string { - fieldValueMap := make(map[string]string) - - // Remove the "ASM:" prefix if present so we only process the values - if idx := strings.Index(message, ":"); idx >= 0 { - message = message[idx+1:] - } - - fields := strings.Split(message, ",") - - // Mapping of CSV field positions to their corresponding keys - fieldOrder := []string{ - "blocking_exception_reason", - "dest_port", - "ip_client", - "is_truncated_bool", - "method", - "policy_name", - "protocol", - "request_status", - "response_code", - "severity", - "sig_cves", - "sig_set_names", - "src_port", - "sub_violations", - "support_id", - "threat_campaign_names", - "violation_rating", - "vs_name", - "x_forwarded_for_header_value", - "outcome", - "outcome_reason", - "violations", - "violation_details", - "bot_signature_name", - "bot_category", - "bot_anomalies", - "enforced_bot_anomalies", - "client_class", - "client_application", - "client_application_version", - "transport_protocol", - "uri", - "request", - } - - for i, field := range fields { - if i >= len(fieldOrder) { - break - } - fieldValueMap[fieldOrder[i]] = strings.TrimSpace(field) - } - - // combine multiple values separated by '::' - if combined, ok := fieldValueMap["sig_cves"]; ok { - parts := strings.SplitN(combined, "::", maxSplitParts) - fieldValueMap["sig_ids"] = parts[0] - if len(parts) > 1 { - fieldValueMap["sig_names"] = parts[1] - } - } - - if combined, ok := fieldValueMap["sig_set_names"]; ok { - parts := strings.SplitN(combined, "::", maxSplitParts) - fieldValueMap["sig_set_names"] = parts[0] - if len(parts) > 1 { - fieldValueMap["sig_cves"] = parts[1] - } - } - - return fieldValueMap -} - -func (p *securityViolationsProcessor) mapKVToSecurityViolationEvent(log *SecurityViolationEvent, - kvMap map[string]string, -) { - log.PolicyName = kvMap["policy_name"] - log.SupportID = kvMap["support_id"] - log.Outcome = kvMap["outcome"] - log.OutcomeReason = kvMap["outcome_reason"] - log.BlockingExceptionReason = kvMap["blocking_exception_reason"] - log.Method = kvMap["method"] - log.Protocol = kvMap["protocol"] - log.XForwardedForHeaderValue = kvMap["x_forwarded_for_header_value"] - log.URI = kvMap["uri"] - log.Request = kvMap["request"] - log.IsTruncated = kvMap["is_truncated_bool"] - log.RequestStatus = kvMap["request_status"] - log.ResponseCode = kvMap["response_code"] - log.ServerAddr = kvMap["server_addr"] - log.VSName = kvMap["vs_name"] - log.RemoteAddr = kvMap["ip_client"] - log.RemotePort = kvMap["dest_port"] - log.ServerPort = kvMap["src_port"] - log.Violations = kvMap["violations"] - log.SubViolations = kvMap["sub_violations"] - log.ViolationRating = kvMap["violation_rating"] - log.SigSetNames = kvMap["sig_set_names"] - log.SigCVEs = kvMap["sig_cves"] - log.ClientClass = kvMap["client_class"] - log.ClientApplication = kvMap["client_application"] - log.ClientApplicationVersion = kvMap["client_application_version"] - log.Severity = kvMap["severity"] - log.ThreatCampaignNames = kvMap["threat_campaign_names"] - log.BotAnomalies = kvMap["bot_anomalies"] - log.BotCategory = kvMap["bot_category"] - log.EnforcedBotAnomalies = kvMap["enforced_bot_anomalies"] - log.BotSignatureName = kvMap["bot_signature_name"] - log.InstanceTags = kvMap["instance_tags"] - log.InstanceGroup = kvMap["instance_group"] - log.DisplayName = kvMap["display_name"] - - if log.RemoteAddr == "" { - log.RemoteAddr = kvMap["remote_addr"] - } - if log.RemotePort == "" { - log.RemotePort = kvMap["remote_port"] - } -} - -// parseViolationsData extracts violation data from the syslog key-value map -func (p *securityViolationsProcessor) parseViolationsData(kvMap map[string]string) []ViolationData { - var violationsData []ViolationData - - // Extract violation name from violation_details XML - this is the only source - violationName := "" - if violationDetails := kvMap["violation_details"]; violationDetails != "" { - violNameRegex := regexp.MustCompile(`