Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b134731
Add endpoints to list Wazuh rules and rule files with comprehensive f…
taylorwalton Jul 4, 2025
934ea33
Add endpoint to retrieve Wazuh rule file content with structured and …
taylorwalton Jul 4, 2025
2d41784
Add endpoint to upload or update Wazuh rule files with validation and…
taylorwalton Jul 4, 2025
3449f3d
precommit fixes
taylorwalton Jul 4, 2025
89d7cbe
feat: add xml syntax check
Linko91 Jul 6, 2025
18e3e15
refactor: xml syntax check
Linko91 Jul 6, 2025
37897fa
refactor: xml syntax check
Linko91 Jul 6, 2025
a4305c2
refactor: xml syntax check
Linko91 Jul 6, 2025
1cfd929
refactor: xml syntax check
Linko91 Jul 6, 2025
4658a52
chore: update frontend dependencies
Linko91 Jul 6, 2025
ce56121
chore: update frontend dependencies
Linko91 Jul 6, 2025
5178605
chore: update frontend dependencies
Linko91 Jul 10, 2025
5022c0b
feat: add xml error popup
Linko91 Jul 10, 2025
6e9f6a3
refactor
Linko91 Jul 10, 2025
8cf2680
feat: add DetectionRules page
Linko91 Jul 11, 2025
c83c54c
fix: change default overwrite behavior to true in update_wazuh_rule_f…
taylorwalton Jul 11, 2025
4085f21
refactor: xml editor
Linko91 Jul 11, 2025
18559fa
Merge remote-tracking branch 'origin/wazuh-rules' into wazuh-rules
Linko91 Jul 11, 2025
90544b6
refactor: update execute_singul function to use connect method and cl…
taylorwalton Jul 11, 2025
377214a
lint
Linko91 Jul 11, 2025
2c01efb
fix: dependencies
Linko91 Jul 11, 2025
44e814d
Merge remote-tracking branch 'origin/wazuh-rules' into wazuh-rules
Linko91 Jul 11, 2025
bdad5aa
feat: add management routes for restarting Wazuh Manager service and …
taylorwalton Jul 11, 2025
355bd57
Merge remote-tracking branch 'origin/wazuh-rules' into wazuh-rules
taylorwalton Jul 11, 2025
b796c46
feat: add restart manager button
Linko91 Jul 11, 2025
e16aa24
precommit fixes
taylorwalton Jul 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 43 additions & 41 deletions backend/app/connectors/shuffle/services/singul.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,6 @@
from app.connectors.shuffle.utils.universal import get_shuffle_org_id
from app.connectors.shuffle.utils.universal import get_singul_client

# async def execute_singul(
# request: SingulRequest,
# ) -> dict:
# """
# Execute a Singul integration.

# Args:
# request (SingulRequest): The request object containing the workflow ID.

# Returns:
# dict: The response containing the execution ID.
# """
# logger.info("Executing Singul integration")

# # Get Singul client from database credentials
# singul = await get_singul_client()

# try:
# response = singul.communication.send_message(
# app=request.app,
# org_id=await get_shuffle_org_id(),
# fields=[
# {"key": "to", "value": "[email protected]"},
# {"key": "subject", "value": "Test Email from Singul"},
# {"key": "body", "value": "This is a test email sent from Singul."},
# ],
# )
# logger.info(f"Singul response: {response}")
# logger.info(f"Singul response success: {response.get('success', 'unknown')}")

# return {
# "executionId": response.get("id", "unknown"),
# "message": "Singul integration executed successfully",
# }
# except Exception as e:
# logger.error(f"Failed to execute Singul integration: {e}")
# return {"executionId": "unknown", "message": f"Singul integration failed: {e}", "success": False}


async def execute_singul(
request: SingulRequest,
Expand All @@ -61,10 +23,15 @@ async def execute_singul(
singul = await get_singul_client()

try:
response = singul.intel.get_ioc(
app="opencti_dcon",
response = singul.connect(
app=request.app,
action="send_message",
org_id=await get_shuffle_org_id(),
fields=[{"key": "ip", "value": "1.1.1.1"}],
fields=[
{"key": "to", "value": "[email protected]"},
{"key": "subject", "value": "Test Email from Singul"},
{"key": "body", "value": "This is a test email sent from Singul."},
],
)
logger.info(f"Singul response: {response}")
logger.info(f"Singul response success: {response.get('success', 'unknown')}")
Expand All @@ -76,3 +43,38 @@ async def execute_singul(
except Exception as e:
logger.error(f"Failed to execute Singul integration: {e}")
return {"executionId": "unknown", "message": f"Singul integration failed: {e}", "success": False}


# async def execute_singul(
# request: SingulRequest,
# ) -> dict:
# """
# Execute a Singul integration.

# Args:
# request (SingulRequest): The request object containing the workflow ID.

# Returns:
# dict: The response containing the execution ID.
# """
# logger.info("Executing Singul integration")

# # Get Singul client from database credentials
# singul = await get_singul_client()

# try:
# response = singul.intel.get_ioc(
# app="opencti_dcon",
# org_id=await get_shuffle_org_id(),
# fields=[{"key": "ip", "value": "1.1.1.1"}],
# )
# logger.info(f"Singul response: {response}")
# logger.info(f"Singul response success: {response.get('success', 'unknown')}")

# return {
# "executionId": response.get("id", "unknown"),
# "message": "Singul integration executed successfully",
# }
# except Exception as e:
# logger.error(f"Failed to execute Singul integration: {e}")
# return {"executionId": "unknown", "message": f"Singul integration failed: {e}", "success": False}
22 changes: 22 additions & 0 deletions backend/app/connectors/wazuh_manager/routes/management.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from fastapi import APIRouter
from fastapi import Security
from loguru import logger

from app.auth.routes.auth import AuthHandler
from app.connectors.wazuh_manager.utils.universal import restart_wazuh_manager_service

wazuh_manager_management_router = APIRouter()
auth_handler = AuthHandler()


@wazuh_manager_management_router.post(
"/restart",
description="Get all disabled rules",
dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
)
async def restart_wazuh_manager() -> dict:
"""
Restart the Wazuh Manager service.
"""
logger.info("Restarting Wazuh Manager service.")
return await restart_wazuh_manager_service()
216 changes: 216 additions & 0 deletions backend/app/connectors/wazuh_manager/routes/rules.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# App specific imports
from typing import List
from typing import Optional

from fastapi import APIRouter
from fastapi import Depends
from fastapi import File
from fastapi import HTTPException
from fastapi import Path
from fastapi import Query
from fastapi import Security
from fastapi import UploadFile
from loguru import logger
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
Expand All @@ -18,12 +25,20 @@
from app.connectors.wazuh_manager.schema.rules import RuleEnableResponse
from app.connectors.wazuh_manager.schema.rules import RuleExcludeRequest
from app.connectors.wazuh_manager.schema.rules import RuleExcludeResponse
from app.connectors.wazuh_manager.schema.rules import WazuhRuleFileContentResponse
from app.connectors.wazuh_manager.schema.rules import WazuhRuleFilesResponse
from app.connectors.wazuh_manager.schema.rules import WazuhRuleFileUploadResponse
from app.connectors.wazuh_manager.schema.rules import WazuhRulesResponse

# from app.connectors.wazuh_manager.schema.rules import RuleExclude
# from app.connectors.wazuh_manager.schema.rules import RuleExcludeResponse
from app.connectors.wazuh_manager.services.rules import disable_rule
from app.connectors.wazuh_manager.services.rules import enable_rule
from app.connectors.wazuh_manager.services.rules import get_wazuh_rule_file_content
from app.connectors.wazuh_manager.services.rules import get_wazuh_rule_files
from app.connectors.wazuh_manager.services.rules import get_wazuh_rules
from app.connectors.wazuh_manager.services.rules import post_to_copilot_ai_module
from app.connectors.wazuh_manager.services.rules import update_wazuh_rule_file

# from app.connectors.wazuh_manager.services.rules import exclude_rule
from app.db.db_session import get_db
Expand Down Expand Up @@ -164,3 +179,204 @@ async def enable_wazuh_rule(
)
async def exclude_wazuh_rule(request: RuleExcludeRequest) -> RuleExcludeResponse:
return await post_to_copilot_ai_module(data=request)


@wazuh_manager_rules_router.get(
"/rules",
response_model=WazuhRulesResponse,
description="List Wazuh rules",
dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
)
async def list_wazuh_rules(
rule_ids: Optional[List[int]] = Query(None, description="List of rule IDs"),
pretty: Optional[bool] = Query(False, description="Show results in human-readable format"),
wait_for_complete: Optional[bool] = Query(False, description="Disable timeout response"),
offset: Optional[int] = Query(0, ge=0, description="First element to return"),
limit: Optional[int] = Query(500, ge=1, le=100000, description="Maximum number of elements"),
select: Optional[List[str]] = Query(None, description="Fields to return"),
sort: Optional[str] = Query(None, description="Sort fields"),
search: Optional[str] = Query(None, description="Search text"),
q: Optional[str] = Query(None, description="Query filter"),
status: Optional[str] = Query(None, description="Rule status filter"),
group: Optional[str] = Query(None, description="Rule group filter"),
level: Optional[str] = Query(None, description="Rule level filter"),
filename: Optional[List[str]] = Query(None, description="Filename filter"),
relative_dirname: Optional[str] = Query(None, description="Directory filter"),
pci_dss: Optional[str] = Query(None, description="PCI DSS filter"),
gdpr: Optional[str] = Query(None, description="GDPR filter"),
gpg13: Optional[str] = Query(None, description="GPG13 filter"),
hipaa: Optional[str] = Query(None, description="HIPAA filter"),
nist_800_53: Optional[str] = Query(None, description="NIST 800-53 filter"),
tsc: Optional[str] = Query(None, description="TSC filter"),
mitre: Optional[str] = Query(None, description="MITRE filter"),
distinct: Optional[bool] = Query(False, description="Distinct values only"),
) -> WazuhRulesResponse:
"""
List Wazuh rules with comprehensive filtering options.

Returns a list of Wazuh rules from the Wazuh Manager API with support for
filtering by various criteria including compliance frameworks and MITRE ATT&CK.
"""
# Use **locals() to pass all parameters efficiently
params = {k: v for k, v in locals().items() if k not in ["auth_handler"]}
return await get_wazuh_rules(**params)


@wazuh_manager_rules_router.get(
"/rules/files",
response_model=WazuhRuleFilesResponse,
description="List Wazuh rule files",
dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
)
async def list_wazuh_rule_files(
pretty: Optional[bool] = Query(False, description="Show results in human-readable format"),
wait_for_complete: Optional[bool] = Query(False, description="Disable timeout response"),
offset: Optional[int] = Query(0, ge=0, description="First element to return in the collection"),
limit: Optional[int] = Query(500, ge=1, le=100000, description="Maximum number of elements to return"),
sort: Optional[str] = Query(None, description="Sort the collection by a field or fields"),
search: Optional[str] = Query(None, description="Look for elements containing the specified string"),
relative_dirname: Optional[str] = Query(None, description="Filter by relative directory name"),
filename: Optional[List[str]] = Query(None, description="Filter by filename of rule files"),
status: Optional[str] = Query(None, description="Filter by list status (enabled, disabled, all)"),
q: Optional[str] = Query(None, description="Query to filter results by"),
select: Optional[List[str]] = Query(None, description="Select which fields to return"),
distinct: Optional[bool] = Query(False, description="Look for distinct values"),
) -> WazuhRuleFilesResponse:
"""
Retrieve a list of Wazuh rule files from the Wazuh Manager.

This endpoint provides access to all rule files used to define Wazuh rules,
including their status and location within the ruleset directory structure.

Parameters:
- pretty: Format results for human readability
- wait_for_complete: Disable request timeout
- offset: Pagination offset (default: 0)
- limit: Maximum results per page (default: 500, max: 100000)
- sort: Fields to sort by (use +/- prefix for ascending/descending)
- search: Text search across file properties
- relative_dirname: Filter by relative directory path
- filename: Filter by specific rule filenames
- status: Filter by file status (enabled/disabled/all)
- q: Advanced query filter
- select: Comma-separated list of fields to return
- distinct: Return only distinct values

Returns:
- WazuhRuleFilesResponse: List of rule files with their status and location metadata.
"""
# Use locals() to capture all parameters, excluding non-parameter variables
params = {k: v for k, v in locals().items()}
return await get_wazuh_rule_files(**params)


@wazuh_manager_rules_router.get(
"/rules/files/{filename}",
response_model=WazuhRuleFileContentResponse,
description="Get Wazuh rule file content",
dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
)
async def get_wazuh_rule_file_content_endpoint(
filename: str = Path(..., description="Filename (rule or decoder) to get content for"),
pretty: Optional[bool] = Query(False, description="Show results in human-readable format"),
wait_for_complete: Optional[bool] = Query(False, description="Disable timeout response"),
raw: Optional[bool] = Query(True, description="Format response in plain text"),
relative_dirname: Optional[str] = Query(None, description="Filter by relative directory name"),
) -> WazuhRuleFileContentResponse:
"""
Get the content of a specified rule file in the ruleset.

This endpoint retrieves the full content of a Wazuh rule file, which can contain
multiple rule groups and individual rules with their configurations.

Parameters:
- filename: The name of the rule file to retrieve (required)
- pretty: Format results for human readability
- wait_for_complete: Disable request timeout
- raw: Return content as plain text instead of structured data
- relative_dirname: Filter by relative directory name

Returns:
- WazuhRuleFileContentResponse: The content of the rule file, either as structured
data (default) or as raw text (when raw=true).

Raises:
- 404: If the specified rule file is not found
- 500: If there's an error retrieving the file content
"""
# Use locals() to capture all parameters, excluding the filename path parameter
params = {k: v for k, v in locals().items() if k != "filename"}
return await get_wazuh_rule_file_content(filename, **params)


@wazuh_manager_rules_router.put(
"/rules/files/{filename}",
response_model=WazuhRuleFileUploadResponse,
description="Upload or update a Wazuh rule file",
dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
)
async def update_wazuh_rule_file_endpoint(
filename: str = Path(..., description="Name of the rule file to upload/update"),
file: UploadFile = File(..., description="Rule file content (XML format)"),
pretty: Optional[bool] = Query(False, description="Show results in human-readable format"),
wait_for_complete: Optional[bool] = Query(False, description="Disable timeout response"),
overwrite: Optional[bool] = Query(True, description="Whether to overwrite the file if it exists"),
relative_dirname: Optional[str] = Query(None, description="Relative directory name"),
) -> WazuhRuleFileUploadResponse:
"""
Upload or update a Wazuh rule file in the ruleset.

This endpoint allows you to upload a new rule file or update an existing one
in the Wazuh Manager ruleset. The file should be in XML format containing
valid Wazuh rule definitions.

Parameters:
- filename: The name of the rule file to upload/update (required)
- file: The rule file content as a binary upload (required, should be XML format)
- pretty: Format results for human readability
- wait_for_complete: Disable request timeout
- overwrite: Whether to overwrite the file if it already exists
- relative_dirname: Relative directory name where the file should be placed

Returns:
- WazuhRuleFileUploadResponse: Confirmation of successful upload/update with file details

Raises:
- 400: If the file format is invalid or parameters are incorrect
- 409: If the file already exists and overwrite is False
- 500: If there's an error uploading the file
"""
# Validate file content type (should be XML or octet-stream)
if file.content_type and not any(ct in file.content_type.lower() for ct in ["xml", "text", "octet-stream", "application/xml"]):
logger.warning(f"Unexpected content type: {file.content_type}")

try:
# Read file content
file_content = await file.read()

# Validate that we have content
if not file_content:
raise HTTPException(status_code=400, detail="File content is empty")

# Basic XML validation (check for XML tags)
file_content_str = file_content.decode("utf-8", errors="ignore")
if not file_content_str.strip().startswith("<"):
logger.warning("File does not appear to be XML format")

logger.info(f"Received file upload: {filename}, size: {len(file_content)} bytes")

# Call service function
return await update_wazuh_rule_file(
filename=filename,
file_content=file_content,
pretty=pretty,
wait_for_complete=wait_for_complete,
overwrite=overwrite,
relative_dirname=relative_dirname,
)

except UnicodeDecodeError:
raise HTTPException(status_code=400, detail="Invalid file encoding. File must be UTF-8 encoded XML.")
except Exception as e:
logger.error(f"Error processing file upload for {filename}: {e}")
raise HTTPException(status_code=500, detail=f"Error processing file upload: {str(e)}")
Loading