oauth2
Taylor committed
Oct 9, 2023 at 21:28 UTC
4e215d633e3804be2d1d106e5c5b022bf03663fd
30 files changed
+421
-229
backend/app/agents/routes/agents.py
-10
@@ -1,9 +1,5 @@
1
-from typing import List
2
-
1
from fastapi import APIRouter
4
-from fastapi import Depends
2
from fastapi import HTTPException
6
-from fastapi import Security
3
from loguru import logger
4
from starlette.status import HTTP_401_UNAUTHORIZED
5
@@ -13,22 +9,16 @@ from app.agents.schema.agents import AgentUpdateCustomerCodeBody
9
from app.agents.schema.agents import AgentUpdateCustomerCodeResponse
10
from app.agents.schema.agents import OutdatedVelociraptorAgentsResponse
11
from app.agents.schema.agents import OutdatedWazuhAgentsResponse
16
-from app.agents.schema.agents import SyncedAgent
12
from app.agents.schema.agents import SyncedAgentsResponse
18
-from app.agents.services.modify import delete_agent_db
19
-from app.agents.services.modify import delete_agent_wazuh
13
from app.agents.services.modify import mark_agent_criticality
14
from app.agents.services.status import get_outdated_agents_velociraptor
15
from app.agents.services.status import get_outdated_agents_wazuh
16
from app.agents.services.sync import sync_agents
17
from app.agents.velociraptor.services.agents import delete_agent_velociraptor
25
-from app.agents.wazuh.schema.agents import WazuhAgent
26
-from app.agents.wazuh.schema.agents import WazuhAgentsList
18
from app.agents.wazuh.schema.agents import WazuhAgentVulnerabilitiesResponse
19
from app.agents.wazuh.services.vulnerabilities import collect_agent_vulnerabilities
20
21
# App specific imports
31
-from app.auth.routes.auth import auth_handler
22
from app.db.db_session import session
23
from app.db.universal_models import Agents
24
backend/app/agents/schema/agents.py
-4
@@ -1,8 +1,4 @@
1
-from typing import Any
2
-from typing import Dict
1
from typing import List
4
-from typing import Optional
5
-from typing import Union
2
3
from pydantic import BaseModel
4
from pydantic import Field
backend/app/agents/services/modify.py
-7
@@ -1,15 +1,8 @@
1
from typing import List
2
3
from fastapi import HTTPException
4
-from loguru import logger
4
6
-import app.agents.velociraptor.services.agents as velociraptor_services
5
import app.agents.wazuh.services.agents as wazuh_services
8
-from app.agents.schema.agents import SyncedAgent
9
-from app.agents.schema.agents import SyncedAgentsResponse
10
-from app.agents.velociraptor.schema.agents import VelociraptorAgent
11
-from app.agents.wazuh.schema.agents import WazuhAgent
12
-from app.agents.wazuh.schema.agents import WazuhAgentsList
6
from app.db.db_session import session
7
from app.db.universal_models import Agents
8
backend/app/agents/velociraptor/schema/agents.py
-3
@@ -1,7 +1,4 @@
1
from datetime import datetime
2
-from typing import Any
3
-from typing import Dict
4
-from typing import List
2
from typing import Optional
3
4
from pydantic import BaseModel
backend/app/agents/velociraptor/services/agents.py
-13
@@ -1,14 +1,5 @@
1
-import json
1
from datetime import datetime
3
-from typing import Any
4
-from typing import Dict
5
-from typing import List
6
-from typing import Optional
7
-from typing import Tuple
8
-from typing import Union
2
10
-import requests
11
-import xmltodict
3
from loguru import logger
4
5
from app.agents.schema.agents import AgentsResponse
@@ -16,10 +7,6 @@ from app.agents.velociraptor.schema.agents import VelociraptorAgent
7
from app.agents.velociraptor.utils.universal import parse_date
8
from app.connectors.velociraptor.services.artifacts import ArtifactsService
9
from app.connectors.velociraptor.utils.universal import UniversalService
19
-from app.connectors.wazuh_manager.schema.rules import RuleDisable
20
-from app.connectors.wazuh_manager.schema.rules import RuleDisableResponse
21
-from app.connectors.wazuh_manager.schema.rules import RuleEnable
22
-from app.connectors.wazuh_manager.schema.rules import RuleEnableResponse
10
11
12
def collect_velociraptor_agent(agent_name: str) -> VelociraptorAgent:
backend/app/agents/wazuh/schema/agents.py
-5
@@ -1,15 +1,10 @@
1
from datetime import datetime
2
-from typing import Any
3
-from typing import Dict
2
from typing import List
3
from typing import Optional
6
-from typing import Union
4
5
from pydantic import BaseModel
6
from pydantic import Field
7
11
-from app.db.universal_models import Agents
12
-
8
9
class WazuhAgent(BaseModel):
10
agent_id: str = Field(..., alias="agent_id")
backend/app/agents/wazuh/services/agents.py
-16
@@ -1,26 +1,10 @@
1
-import json
2
-from typing import Any
3
-from typing import Dict
4
-from typing import List
5
-from typing import Optional
6
-from typing import Tuple
7
-from typing import Union
8
-
9
-import requests
10
-import xmltodict
1
from loguru import logger
2
3
from app.agents.schema.agents import AgentModifyResponse
4
from app.agents.wazuh.schema.agents import WazuhAgent
5
from app.agents.wazuh.schema.agents import WazuhAgentsList
16
-from app.connectors.wazuh_manager.schema.rules import RuleDisable
17
-from app.connectors.wazuh_manager.schema.rules import RuleDisableResponse
18
-from app.connectors.wazuh_manager.schema.rules import RuleEnable
19
-from app.connectors.wazuh_manager.schema.rules import RuleEnableResponse
20
-from app.connectors.wazuh_manager.utils.universal import restart_service
6
from app.connectors.wazuh_manager.utils.universal import send_delete_request
7
from app.connectors.wazuh_manager.utils.universal import send_get_request
23
-from app.connectors.wazuh_manager.utils.universal import send_put_request
8
9
10
def collect_wazuh_agents() -> WazuhAgentsList:
backend/app/agents/wazuh/services/vulnerabilities.py
-17
@@ -1,27 +1,10 @@
1
-import json
2
-from typing import Any
3
-from typing import Dict
1
from typing import List
5
-from typing import Optional
6
-from typing import Tuple
7
-from typing import Union
2
9
-import requests
10
-import xmltodict
3
from loguru import logger
4
13
-from app.agents.schema.agents import AgentsResponse
14
-from app.agents.wazuh.schema.agents import WazuhAgent
15
-from app.agents.wazuh.schema.agents import WazuhAgentsList
5
from app.agents.wazuh.schema.agents import WazuhAgentVulnerabilities
6
from app.agents.wazuh.schema.agents import WazuhAgentVulnerabilitiesResponse
18
-from app.connectors.wazuh_manager.schema.rules import RuleDisable
19
-from app.connectors.wazuh_manager.schema.rules import RuleDisableResponse
20
-from app.connectors.wazuh_manager.schema.rules import RuleEnable
21
-from app.connectors.wazuh_manager.schema.rules import RuleEnableResponse
22
-from app.connectors.wazuh_manager.utils.universal import restart_service
7
from app.connectors.wazuh_manager.utils.universal import send_get_request
24
-from app.connectors.wazuh_manager.utils.universal import send_put_request
8
9
10
def collect_agent_vulnerabilities(agent_id: str):
backend/app/auth/models/users.py
+25
-10
@@ -1,4 +1,5 @@
1
import datetime
2
+from enum import Enum
3
from typing import Optional
4
5
from pydantic import EmailStr
@@ -8,29 +9,43 @@ from sqlmodel import Relationship
9
from sqlmodel import SQLModel
10
11
12
+class Role(SQLModel, table=True):
13
+ id: Optional[int] = Field(primary_key=True)
14
+ name: str = Field(max_length=256)
15
+ description: str = Field(max_length=256)
16
+
17
+ user: Optional["User"] = Relationship(back_populates="role")
18
+
19
+
20
class User(SQLModel, table=True):
21
id: Optional[int] = Field(primary_key=True)
22
username: str = Field(index=True)
23
password: str = Field(max_length=256, min_length=6)
24
email: EmailStr
25
created_at: datetime.datetime = datetime.datetime.now()
17
- is_admin: bool = False
26
+ role_id: Optional[int] = Field(foreign_key="role.id")
27
28
smtp: "SMTP" = Relationship(back_populates="user")
29
+ role: Optional["Role"] = Relationship(back_populates="user")
30
+
31
+
32
+# Enum class for role_id 1,2,3
33
+class RoleEnum(int, Enum):
34
+ admin = 1
35
+ analyst = 2
36
+ customer = 3
37
38
39
class UserInput(SQLModel):
40
username: str
24
- password: str = Field(max_length=256, min_length=6)
25
- password2: str
41
+ password: str = Field(
42
+ max_length=256,
43
+ min_length=8,
44
+ regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$",
45
+ description="Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, and one number",
46
+ )
47
email: EmailStr
27
- is_admin: bool = False
28
-
29
- @validator("password2")
30
- def password_match(cls, v, values, **kwargs):
31
- if "password" in values and v != values["password"]:
32
- raise ValueError("passwords don't match")
33
- return v
48
+ role_id: RoleEnum = Field(RoleEnum.analyst, description="Role ID 1: admin, 2: analyst, 3: customer", foreign_key="role.id")
49
50
51
class UserLogin(SQLModel):
backend/app/auth/routes/auth.py
+25
-7
@@ -1,13 +1,15 @@
1
+from datetime import timedelta
2
+
3
from fastapi import APIRouter
4
from fastapi import Depends
5
from fastapi import HTTPException
4
-from fastapi import Security
5
-from fastapi import security
6
-from fastapi.security import HTTPAuthorizationCredentials
6
+from fastapi import status
7
+from fastapi.security import OAuth2PasswordRequestForm
8
9
from app.auth.models.users import User
10
from app.auth.models.users import UserInput
11
from app.auth.models.users import UserLogin
12
+from app.auth.schema.auth import Token
13
from app.auth.schema.auth import UserLoginResponse
14
from app.auth.schema.auth import UserResponse
15
from app.auth.services.universal import find_user
@@ -15,17 +17,33 @@ from app.auth.services.universal import select_all_users
17
from app.auth.utils import AuthHandler
18
from app.db.db_session import session
19
20
+ACCESS_TOKEN_EXPIRE_MINUTES = 60
21
+
22
user_router = APIRouter()
23
auth_handler = AuthHandler()
24
25
26
+@user_router.post("/token", response_model=Token)
27
+async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
28
+ user = auth_handler.authenticate_user(form_data.username, form_data.password)
29
+ if not user:
30
+ raise HTTPException(
31
+ status_code=status.HTTP_401_UNAUTHORIZED,
32
+ detail="Incorrect username or password",
33
+ headers={"WWW-Authenticate": "Bearer"},
34
+ )
35
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
36
+ access_token = auth_handler.encode_token(user.username) # replace with actual scopes
37
+ return {"access_token": access_token, "token_type": "bearer"}
38
+
39
+
40
@user_router.post("/register", response_model=UserResponse, status_code=201, description="Register new user")
41
def register(user: UserInput):
42
users = select_all_users()
43
if any(x.username == user.username for x in users):
44
raise HTTPException(status_code=400, detail="Username is taken")
45
hashed_pwd = auth_handler.get_password_hash(user.password)
28
- u = User(username=user.username, password=hashed_pwd, email=user.email, is_admin=user.is_admin)
46
+ u = User(username=user.username, password=hashed_pwd, email=user.email, role_id=user.role_id)
47
session.add(u)
48
session.commit()
49
return {"message": "User created successfully", "success": True}
@@ -43,6 +61,6 @@ def login(user: UserLogin):
61
return {"token": token, "success": True, "message": "Login successful"}
62
63
46
-@user_router.get("/users/me", description="Get current user")
47
-def get_current_user(user: User = Depends(auth_handler.get_current_user)):
48
- return user
64
+# @user_router.get("/users/me", description="Get current user")
65
+# def get_current_user(user: User = Depends(auth_handler.get_current_user)):
66
+# return user
backend/app/auth/schema/auth.py
+9
@@ -10,3 +10,12 @@ class UserLoginResponse(BaseModel):
10
token: str
11
message: str
12
success: bool
13
+
14
+
15
+class Token(BaseModel):
16
+ access_token: str
17
+ token_type: str
18
+
19
+
20
+class TokenData(BaseModel):
21
+ username: str | None = None
backend/app/auth/services/universal.py
+11
@@ -1,6 +1,7 @@
1
from sqlmodel import Session
2
from sqlmodel import select
3
4
+from app.auth.models.users import Role
5
from app.auth.models.users import User
6
from app.db.db_session import engine
7
@@ -16,3 +17,13 @@ def find_user(name):
17
with Session(engine) as session:
18
statement = select(User).where(User.username == name)
19
return session.exec(statement).first()
20
+
21
+
22
+def get_role(name):
23
+ with Session(engine) as session:
24
+ statement = select(User).where(User.username == name)
25
+ res = session.exec(statement).first()
26
+ # Get the role name
27
+ statement = select(Role).where(Role.id == res.role_id)
28
+ role = session.exec(statement).first()
29
+ return role.name
backend/app/auth/utils.py
+86
-18
@@ -1,49 +1,117 @@
1
-import datetime
1
+from datetime import datetime
2
+from datetime import timedelta
3
4
import jwt
5
+from fastapi import Depends
6
from fastapi import HTTPException
5
-from fastapi import Security
6
-from fastapi.security import HTTPAuthorizationCredentials
7
-from fastapi.security import HTTPBearer
7
+from fastapi.security import OAuth2PasswordBearer
8
+from fastapi.security import SecurityScopes
9
from passlib.context import CryptContext
9
-from starlette import status
10
11
from app.auth.services.universal import find_user
12
+from app.auth.services.universal import get_role
13
14
15
class AuthHandler:
15
- security = HTTPBearer()
16
+ security = OAuth2PasswordBearer(
17
+ tokenUrl="auth/token",
18
+ scopes={"admin": "Admin users", "analyst": "SOC Analysts", "customer": "Customers"},
19
+ )
20
pwd_context = CryptContext(schemes=["bcrypt"])
21
secret = "supersecret"
22
23
def get_password_hash(self, password):
24
return self.pwd_context.hash(password)
25
22
- def verify_password(self, pwd, hashed_pwd):
23
- return self.pwd_context.verify(pwd, hashed_pwd)
26
+ def verify_password(self, plain_password, hashed_password):
27
+ return self.pwd_context.verify(plain_password, hashed_password)
28
25
- def encode_token(self, user_id):
26
- payload = {"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=8), "iat": datetime.datetime.utcnow(), "sub": user_id}
29
+ def authenticate_user(self, username: str, password: str):
30
+ user = find_user(username)
31
+ if not user or not self.verify_password(password, user.password):
32
+ return False
33
+ return user
34
+
35
+ def encode_token(self, username: str):
36
+ payload = {"exp": datetime.utcnow() + timedelta(hours=8), "iat": datetime.utcnow(), "sub": username, "scopes": [get_role(username)]}
37
return jwt.encode(payload, self.secret, algorithm="HS256")
38
39
def decode_token(self, token):
40
try:
41
payload = jwt.decode(token, self.secret, algorithms=["HS256"])
32
- return payload["sub"]
42
+ return payload["sub"], payload.get("scopes", [])
43
except jwt.ExpiredSignatureError:
44
raise HTTPException(status_code=401, detail="Expired signature")
45
except jwt.InvalidTokenError:
46
raise HTTPException(status_code=401, detail="Invalid token")
47
38
- def auth_wrapper(self, auth: HTTPAuthorizationCredentials = Security(security)):
39
- return self.decode_token(auth.credentials)
48
+ def get_current_user(self, security_scopes: SecurityScopes, token: str = Depends(security)):
49
+ if security_scopes.scopes:
50
+ authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
51
+ else:
52
+ authenticate_value = "Bearer"
53
+
54
+ credentials_exception = HTTPException(
55
+ status_code=401,
56
+ detail="Could not validate credentials",
57
+ headers={"WWW-Authenticate": authenticate_value},
58
+ )
59
+
60
+ try:
61
+ username, token_scopes = self.decode_token(token)
62
+ except Exception as e:
63
+ raise HTTPException(
64
+ status_code=401,
65
+ detail=f"Could not decode token: {e}",
66
+ headers={"WWW-Authenticate": authenticate_value},
67
+ )
68
41
- def get_current_user(self, auth: HTTPAuthorizationCredentials = Security(security)):
42
- credentials_exception = HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
43
- username = self.decode_token(auth.credentials)
69
if username is None:
45
- raise credentials_exception
70
+ raise HTTPException(
71
+ status_code=401,
72
+ detail="Username not found in token",
73
+ headers={"WWW-Authenticate": authenticate_value},
74
+ )
75
user = find_user(username)
76
if user is None:
48
- raise credentials_exception
77
+ raise HTTPException(
78
+ status_code=401,
79
+ detail="User not found",
80
+ headers={"WWW-Authenticate": authenticate_value},
81
+ )
82
+
83
+ for scope in security_scopes.scopes:
84
+ if scope not in token_scopes:
85
+ raise HTTPException(
86
+ status_code=401,
87
+ detail="Not enough permissions",
88
+ headers={"WWW-Authenticate": authenticate_value},
89
+ )
90
+
91
return user
92
+
93
+ def return_username_for_logging(self, token: str = Depends(security)):
94
+ username, token_scopes = self.decode_token(token)
95
+ return username
96
+
97
+ def require_any_scope(self, *required_scopes: str):
98
+ async def _require_any_scope(token: str = Depends(self.security)):
99
+ if not token:
100
+ raise HTTPException(
101
+ status_code=401,
102
+ detail="Not authenticated",
103
+ headers={"WWW-Authenticate": "Bearer"},
104
+ )
105
+
106
+ username, token_scopes = self.decode_token(token)
107
+
108
+ if not any(scope in token_scopes for scope in required_scopes):
109
+ raise HTTPException(
110
+ status_code=401,
111
+ detail="Not enough permissions, you don't have any of the required scopes.",
112
+ headers={"WWW-Authenticate": "Bearer"},
113
+ )
114
+
115
+ return username
116
+
117
+ return _require_any_scope
backend/app/auth/utils_backup.py
new
+52
@@ -0,0 +1,52 @@
1
+import datetime
2
+
3
+import jwt
4
+from fastapi import HTTPException
5
+from fastapi import Security
6
+from fastapi.security import HTTPAuthorizationCredentials, OAuth2PasswordBearer
7
+from fastapi.security import HTTPBearer
8
+from passlib.context import CryptContext
9
+from typing import Optional
10
+from starlette import status
11
+
12
+from app.auth.services.universal import find_user
13
+from app.auth.models.users import User
14
+
15
+
16
+class AuthHandler:
17
+ security = HTTPBearer()
18
+ pwd_context = CryptContext(schemes=["bcrypt"])
19
+ secret = "supersecret"
20
+
21
+ def get_password_hash(self, password):
22
+ return self.pwd_context.hash(password)
23
+
24
+ def verify_password(self, pwd, hashed_pwd):
25
+ return self.pwd_context.verify(pwd, hashed_pwd)
26
+
27
+ def encode_token(self, user_id):
28
+ payload = {"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=8), "iat": datetime.datetime.utcnow(), "sub": user_id}
29
+ return jwt.encode(payload, self.secret, algorithm="HS256")
30
+
31
+ def decode_token(self, token):
32
+ try:
33
+ payload = jwt.decode(token, self.secret, algorithms=["HS256"])
34
+ return payload["sub"]
35
+ except jwt.ExpiredSignatureError:
36
+ raise HTTPException(status_code=401, detail="Expired signature")
37
+ except jwt.InvalidTokenError:
38
+ raise HTTPException(status_code=401, detail="Invalid token")
39
+
40
+ def auth_wrapper(self, auth: HTTPAuthorizationCredentials = Security(security)):
41
+ return self.decode_token(auth.credentials)
42
+
43
+ def get_current_user(self, auth: HTTPAuthorizationCredentials = Security(security)):
44
+ credentials_exception = HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
45
+ username = self.decode_token(auth.credentials)
46
+ if username is None:
47
+ raise credentials_exception
48
+ user = find_user(username)
49
+ if user is None:
50
+ raise credentials_exception
51
+ return user
52
+
backend/app/connectors/cortex/routes/analyzers.py
+3
-9
@@ -1,19 +1,10 @@
1
-from datetime import timedelta
2
-from typing import Dict
1
from typing import List
4
-from typing import Optional
5
-from typing import Union
2
3
from fastapi import APIRouter
4
from fastapi import Depends
5
from fastapi import HTTPException
10
-from fastapi import Security
6
from loguru import logger
12
-from starlette.status import HTTP_401_UNAUTHORIZED
7
14
-# App specific imports
15
-from app.auth.routes.auth import auth_handler
16
-from app.connectors.cortex.schema.analyzers import AnalyzerJobData
8
from app.connectors.cortex.schema.analyzers import AnalyzersResponse
9
from app.connectors.cortex.schema.analyzers import RunAnalyzerBody
10
from app.connectors.cortex.schema.analyzers import RunAnalyzerResponse
@@ -21,6 +12,9 @@ from app.connectors.cortex.services.analyzers import get_analyzers
12
from app.connectors.cortex.services.analyzers import run_analyzer
13
from app.db.db_session import session
14
15
+# App specific imports
16
+
17
+
18
cortex_analyzer_router = APIRouter()
19
20
backend/app/connectors/cortex/services/analyzers.py
-7
@@ -1,14 +1,7 @@
1
# analyzers.py
2
3
-import json
4
-import time
5
-from datetime import datetime
6
-from typing import Any
7
-from typing import Callable
3
from typing import Dict
4
from typing import List
10
-from typing import Optional
11
-from typing import Tuple
5
from typing import Union
6
7
from cortex4py.api import Api
backend/app/connectors/routes.py
+21
-27
@@ -1,3 +1,7 @@
1
+from functools import wraps
2
+from http import HTTPStatus
3
+from typing import Callable
4
+from typing import List
5
from typing import Union
6
7
## Auth Things
@@ -5,11 +9,16 @@ from fastapi import APIRouter
9
from fastapi import Depends
10
from fastapi import File
11
from fastapi import HTTPException
12
+from fastapi import Request
13
+from fastapi import Security
14
from fastapi import UploadFile
15
+from fastapi.security import HTTPAuthorizationCredentials
16
+from fastapi.security import HTTPBearer
17
from loguru import logger
18
from starlette.status import HTTP_401_UNAUTHORIZED
19
20
from app.auth.routes.auth import auth_handler
21
+from app.auth.utils import AuthHandler
22
from app.connectors.schema import ConnectorListResponse
23
from app.connectors.schema import ConnectorResponse
24
from app.connectors.schema import ConnectorsListResponse
@@ -19,33 +28,13 @@ from app.connectors.services import ConnectorServices
28
29
connector_router = APIRouter()
30
22
-# ! ROUTE WITH AUTH
23
-# @connector_router.get("", response_model=ConnectorsListResponse, description="Fetch all available connectors")
24
-# async def get_connectors(user=Depends(auth_handler.get_current_user)) -> ConnectorListResponse:
25
-# """
26
-# Fetch all available connectors from the database.
31
28
-# This endpoint retrieves all the connectors stored in the database and returns them
29
-# along with a success status and message.
30
-
31
-# Returns:
32
-# ConnectorListResponse: A Pydantic model containing a list of connectors and additional metadata.
33
-
34
-# Raises:
35
-# HTTPException: An exception with a 404 status code is raised if no connectors are found.
36
-# """
37
-# logger.info(f"Fetching all connectors for user: {user.username}")
38
-# if not user.is_admin:
39
-# raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Unauthorized")
40
-
41
-# connectors = ConnectorServices.fetch_all_connectors()
42
-# if connectors:
43
-# return {"connectors": connectors, "success": True, "message": "Connectors fetched successfully"}
44
-# else:
45
-# raise HTTPException(status_code=404, detail="No connectors found")
46
-
47
-
48
-@connector_router.get("", response_model=ConnectorsListResponse, description="Fetch all available connectors")
32
+@connector_router.get(
33
+ "",
34
+ response_model=ConnectorsListResponse,
35
+ description="Fetch all available connectors",
36
+ dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
37
+)
38
async def get_connectors() -> ConnectorListResponse:
39
"""
40
Fetch all available connectors from the database.
@@ -67,7 +56,12 @@ async def get_connectors() -> ConnectorListResponse:
56
raise HTTPException(status_code=404, detail="No connectors found")
57
58
70
-@connector_router.get("/{connector_id}", response_model=ConnectorListResponse, description="Fetch a specific connector")
59
+@connector_router.get(
60
+ "/{connector_id}",
61
+ response_model=ConnectorListResponse,
62
+ description="Fetch a specific connector",
63
+ dependencies=[Security(AuthHandler().require_any_scope("admin", "test"))],
64
+)
65
async def get_connector(connector_id: int) -> Union[ConnectorResponse, HTTPException]:
66
"""
67
Fetch a specific connector by its ID.
backend/app/connectors/wazuh_indexer/schema/alerts.py
-1
@@ -2,7 +2,6 @@ from typing import Any
2
from typing import Dict
3
from typing import List
4
from typing import Optional
5
-from typing import Union
5
6
from pydantic import BaseModel
7
from pydantic import Field
backend/app/connectors/wazuh_indexer/schema/indices.py
-4
@@ -1,8 +1,4 @@
1
-from typing import Any
1
from typing import Dict
3
-from typing import List
4
-from typing import Optional
5
-from typing import Union
2
3
from pydantic import BaseModel
4
from pydantic import Field
backend/app/connectors/wazuh_manager/routes/rules.py
+40
-25
@@ -1,50 +1,50 @@
1
-from typing import List
2
-
1
from fastapi import APIRouter
2
from fastapi import Depends
3
from fastapi import HTTPException
4
from fastapi import Security
7
-from loguru import logger
8
-from starlette.status import HTTP_401_UNAUTHORIZED
5
6
# App specific imports
11
-from app.auth.routes.auth import auth_handler
7
+from app.auth.routes.auth import AuthHandler
8
from app.connectors.wazuh_manager.models.rules import DisabledRule
9
from app.connectors.wazuh_manager.schema.rules import AllDisabledRuleResponse
10
from app.connectors.wazuh_manager.schema.rules import RuleDisable
11
from app.connectors.wazuh_manager.schema.rules import RuleDisableResponse
12
from app.connectors.wazuh_manager.schema.rules import RuleEnable
13
from app.connectors.wazuh_manager.schema.rules import RuleEnableResponse
14
+from app.connectors.wazuh_manager.schema.rules import RuleExclude
15
+from app.connectors.wazuh_manager.schema.rules import RuleExcludeResponse
16
from app.connectors.wazuh_manager.services.rules import disable_rule
17
from app.connectors.wazuh_manager.services.rules import enable_rule
18
+from app.connectors.wazuh_manager.services.rules import exclude_rule
19
from app.db.db_session import session
20
21
NEW_LEVEL = "1"
22
wazuh_manager_router = APIRouter()
24
-
25
-
26
-def verify_admin(user):
27
- if not user.is_admin:
28
- raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Unauthorized")
23
+auth_handler = AuthHandler()
24
25
26
def query_disabled_rule(rule_id: str):
27
return session.query(DisabledRule).filter(DisabledRule.rule_id == rule_id).first()
28
29
35
-@wazuh_manager_router.get("/rule/disabled", response_model=AllDisabledRuleResponse, description="Get all disabled rules")
36
-async def get_disabled_rules(user=Depends(auth_handler.get_current_user)) -> AllDisabledRuleResponse:
37
- logger.info(f"Fetching all disabled rules for user: {user.username}")
38
- verify_admin(user)
30
+@wazuh_manager_router.get(
31
+ "/rule/disabled",
32
+ response_model=AllDisabledRuleResponse,
33
+ description="Get all disabled rules",
34
+ dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
35
+)
36
+async def get_disabled_rules() -> AllDisabledRuleResponse:
37
disabled_rules = session.query(DisabledRule).all()
38
return AllDisabledRuleResponse(disabled_rules=disabled_rules, success=True, message="Successfully fetched all disabled rules")
39
40
43
-@wazuh_manager_router.post("/rule/disable", response_model=RuleDisableResponse, description="Disable a Wazuh Rule")
44
-async def disable_wazuh_rule(rule: RuleDisable, user=Depends(auth_handler.get_current_user)) -> RuleDisableResponse:
45
- logger.info(f"Disabling rule for user: {user.username}")
46
- verify_admin(user)
47
-
41
+@wazuh_manager_router.post(
42
+ "/rule/disable",
43
+ response_model=RuleDisableResponse,
44
+ description="Disable a Wazuh Rule",
45
+ dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
46
+)
47
+async def disable_wazuh_rule(rule: RuleDisable, username: str = Depends(auth_handler.get_current_user)) -> RuleDisableResponse:
48
if query_disabled_rule(rule.rule_id):
49
raise HTTPException(status_code=404, detail="Rule is already disabled")
50
@@ -56,7 +56,7 @@ async def disable_wazuh_rule(rule: RuleDisable, user=Depends(auth_handler.get_cu
56
new_level=NEW_LEVEL,
57
reason_for_disabling=rule.reason_for_disabling,
58
length_of_time=rule.length_of_time,
59
- disabled_by=user.username,
59
+ disabled_by=username.username,
60
)
61
session.add(new_disabled_rule)
62
session.commit()
@@ -65,11 +65,13 @@ async def disable_wazuh_rule(rule: RuleDisable, user=Depends(auth_handler.get_cu
65
raise HTTPException(status_code=404, detail="Was not able to disable rule")
66
67
68
-@wazuh_manager_router.post("/rule/enable", response_model=RuleEnableResponse, description="Enable a Wazuh Rule")
69
-async def enable_wazuh_rule(rule: RuleEnable, user=Depends(auth_handler.get_current_user)) -> RuleEnableResponse:
70
- logger.info(f"Enabling rule for user: {user.username}")
71
- verify_admin(user)
72
-
68
+@wazuh_manager_router.post(
69
+ "/rule/enable",
70
+ response_model=RuleEnableResponse,
71
+ description="Enable a Wazuh Rule",
72
+ dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
73
+)
74
+async def enable_wazuh_rule(rule: RuleEnable) -> RuleEnableResponse:
75
disabled_rule = query_disabled_rule(rule.rule_id)
76
if not disabled_rule:
77
raise HTTPException(status_code=404, detail="Rule is already enabled")
@@ -83,3 +85,16 @@ async def enable_wazuh_rule(rule: RuleEnable, user=Depends(auth_handler.get_curr
85
return rule_enabled
86
else:
87
raise HTTPException(status_code=404, detail="Was not able to enable rule")
88
+
89
+
90
+@wazuh_manager_router.post(
91
+ "/rule/exclude",
92
+ response_model=RuleExcludeResponse,
93
+ description="Retrieve recommended exclusion for a Wazuh Rule",
94
+)
95
+async def exclude_wazuh_rule(rule: RuleExclude) -> RuleExcludeResponse:
96
+ recommended_exclusion = exclude_rule(rule)
97
+ if recommended_exclusion:
98
+ return recommended_exclusion
99
+ else:
100
+ raise HTTPException(status_code=404, detail="Was not able to exclude rule")
backend/app/connectors/wazuh_manager/schema/rules.py
+24
@@ -2,6 +2,7 @@ from typing import List
2
from typing import Optional
3
4
from pydantic import BaseModel
5
+from pydantic import Field
6
7
8
class RuleDisable(BaseModel):
@@ -40,3 +41,26 @@ class AllDisabledRuleResponse(BaseModel):
41
disabled_rules: List[AllDisabledRule]
42
success: bool
43
message: str
44
+
45
+
46
+class RuleExclude(BaseModel):
47
+ rule_value: str = Field(
48
+ ...,
49
+ description="The value of the field trying to be exclude",
50
+ example="C:\\Windows\\ServiceState\\EventLog\\Data\\lastalive1.dat",
51
+ )
52
+ input_value: str = Field(
53
+ ...,
54
+ description="The proposed value of the field trying to be exclude that would result in an exclusiong",
55
+ example="C:\\\\Windows\\\\ServiceState\\\\EventLog\\\\Data\\\\lastalive1\.dat",
56
+ )
57
+
58
+
59
+class RuleExcludeResponse(BaseModel):
60
+ success: bool
61
+ message: str
62
+ recommended_exclusion: str = Field(
63
+ ...,
64
+ description="The recommended exclusion for the rule",
65
+ example="C:\\\\Windows\\\\ServiceState\\\\EventLog\\\\Data\\\\lastalive1\.dat",
66
+ )
backend/app/connectors/wazuh_manager/services/rules.py
+45
@@ -1,15 +1,20 @@
1
+import re
2
from typing import Any
3
from typing import Dict
4
from typing import List
5
from typing import Tuple
6
from typing import Union
7
8
+import pcre2
9
import xmltodict
10
+from loguru import logger
11
12
from app.connectors.wazuh_manager.schema.rules import RuleDisable
13
from app.connectors.wazuh_manager.schema.rules import RuleDisableResponse
14
from app.connectors.wazuh_manager.schema.rules import RuleEnable
15
from app.connectors.wazuh_manager.schema.rules import RuleEnableResponse
16
+from app.connectors.wazuh_manager.schema.rules import RuleExclude
17
+from app.connectors.wazuh_manager.schema.rules import RuleExcludeResponse
18
from app.connectors.wazuh_manager.utils.universal import restart_service
19
from app.connectors.wazuh_manager.utils.universal import send_get_request
20
from app.connectors.wazuh_manager.utils.universal import send_put_request
@@ -96,3 +101,43 @@ def disable_rule(rule: RuleDisable) -> RuleDisableResponse:
101
102
def enable_rule(rule: RuleEnable, previous_level: str) -> RuleEnableResponse:
103
return process_rule(rule, lambda fc, rid: set_rule_level(fc, rid, previous_level), RuleEnableResponse)
104
+
105
+
106
+################# ! EXCLUDE RULE ! #################
107
+
108
+
109
+def make_pcre2_compatible(input_string: str) -> str:
110
+ """
111
+ Convert the input string to a PCRE2 compatible regex pattern.
112
+
113
+ Parameters:
114
+ - input_string (str): The input string to convert.
115
+
116
+ Returns:
117
+ - str: The PCRE2 compatible regex pattern.
118
+ """
119
+ # PCRE2 uses \\ to escape a backslash
120
+ return input_string.replace("\\", "\\\\")
121
+
122
+
123
+def exclude_rule(rule: RuleExclude) -> RuleExcludeResponse:
124
+ try:
125
+ # Convert rule_value to a PCRE2 compatible regex pattern
126
+ pcre2_pattern = make_pcre2_compatible(rule.rule_value)
127
+
128
+ compiled_pattern = pcre2.compile(pcre2_pattern)
129
+ print(f"Compiled Pattern: {compiled_pattern}") # Debugging line
130
+
131
+ print(f"Input Value: {rule.input_value}") # Debugging line
132
+
133
+ match_data = compiled_pattern.match(rule.input_value)
134
+
135
+ if match_data:
136
+ return RuleExcludeResponse(success=True, message="Successfully excluded rule", recommended_exclusion=rule.input_value)
137
+ else:
138
+ return RuleExcludeResponse(success=False, message="Failed to exclude rule", recommended_exclusion="")
139
+
140
+ except Exception as e:
141
+ print(f"Exception: {e}") # Debugging line
142
+ logger.error(f"Failed to exclude rule: {e}")
143
+ return RuleExcludeResponse(success=False, message=f"Failed to exclude rule: {e}", recommended_exclusion="")
backend/app/db/all_models.py
+3
-1
@@ -3,4 +3,6 @@ from app.auth.models.users import User
3
from app.connectors.models import Connectors
4
from app.connectors.sublime.models.alerts import SublimeAlerts
5
from app.connectors.wazuh_manager.models.rules import DisabledRule
6
-from app.db.universal_models import Customers, CustomersMeta, Agents
6
+from app.db.universal_models import Agents
7
+from app.db.universal_models import Customers
8
+from app.db.universal_models import CustomersMeta
backend/app/db/db_populate.py
+32
@@ -1,6 +1,7 @@
1
from loguru import logger
2
from sqlmodel import Session
3
4
+from app.auth.models.users import Role
5
from app.connectors.models import Connectors
6
7
@@ -141,3 +142,34 @@ def add_connectors_if_not_exist(session: Session):
142
143
# Commit the changes if any new connectors were added
144
session.commit()
145
+
146
+
147
+def add_roles_if_not_exist(session: Session):
148
+ # List of roles to add
149
+ role_list = [
150
+ {
151
+ "name": "admin",
152
+ "description": "Administrator",
153
+ },
154
+ {
155
+ "name": "analyst",
156
+ "description": "SOC Analyst",
157
+ },
158
+ {
159
+ "name": "customer",
160
+ "description": "Customer",
161
+ },
162
+ ]
163
+
164
+ for role_data in role_list:
165
+ # Check if role already exists in the database
166
+ existing_role = session.query(Role).filter_by(name=role_data["name"]).first()
167
+
168
+ if existing_role is None:
169
+ # If role does not exist, create new role entry
170
+ new_role = Role(**role_data)
171
+ session.add(new_role)
172
+ logger.info(f"Added new role: {role_data['name']}")
173
+
174
+ # Commit the changes if any new roles were added
175
+ session.commit()
backend/app/db/db_setup.py
+2
-7
@@ -4,14 +4,8 @@ from sqlmodel import Session
4
from sqlmodel import SQLModel
5
6
# from app.db.all_models import *
7
-from app.auth.models.users import User
8
-from app.connectors.models import Connectors
9
-from app.connectors.sublime.models.alerts import SublimeAlerts
10
-from app.connectors.wazuh_manager.models.rules import DisabledRule
7
from app.db.db_populate import add_connectors_if_not_exist
12
-from app.db.universal_models import Agents
13
-from app.db.universal_models import Customers
14
-from app.db.universal_models import CustomersMeta
8
+from app.db.db_populate import add_roles_if_not_exist
9
10
11
def create_tables(engine):
@@ -33,4 +27,5 @@ def create_tables(engine):
27
# After creating all tables, add connectors if they don't exist
28
with Session(engine) as session:
29
add_connectors_if_not_exist(session)
30
+ add_roles_if_not_exist(session)
31
session.commit()
backend/app/integrations/dnstwist/routes/analyze.py
-1
@@ -4,7 +4,6 @@ from fastapi import Depends
4
from fastapi import HTTPException
5
from loguru import logger
6
7
-from app.db.db_session import session
7
from app.integrations.dnstwist.schema.analyze import DomainAnalysisResponse
8
from app.integrations.dnstwist.schema.analyze import DomainRequestBody
9
from app.integrations.dnstwist.services.analyze import analyze_domain
backend/requirements.in
+3
@@ -12,6 +12,8 @@ marshmallow-sqlalchemy
12
matplotlib
13
mitreattack-python
14
openai
15
+passlib[bcrypt]
16
+pcre2
17
pika
18
psycopg2-binary
19
python-multipart
@@ -20,6 +22,7 @@ python-magic
22
passlib
23
PyJWT
24
pydantic[email]
25
+python-jose[cryptography]
26
pyvelociraptor~=0.1
27
regex
28
uvicorn
package.json
+1
-1
@@ -158,4 +158,4 @@
158
"engines": {
159
"node": ">=16.0.0 <20.5.0"
160
}
161
-}
\ No newline at end of file
161
+}
src/api/connectors.ts
+4
-1
@@ -13,6 +13,9 @@ export default {
13
return HttpClient.put<FlaskBaseResponse & { connectors: Connector[] }>(`/connectors/${connectorId}`, payload)
14
},
15
upload(connectorId: string | number, formData: FormData) {
16
- return HttpClient.post<FlaskBaseResponse & { connectors: Connector[] }>(`/connectors/upload/${connectorId}`, formData)
16
+ return HttpClient.post<FlaskBaseResponse & { connectors: Connector[] }>(
17
+ `/connectors/upload/${connectorId}`,
18
+ formData
19
+ )
20
}
21
}
unplugin.components.d.ts
+35
-35
@@ -5,39 +5,39 @@
5
// Read more: https://github.com/vuejs/core/pull/3399
6
export {}
7
8
-declare module 'vue' {
9
- export interface GlobalComponents {
10
- CardActions: typeof import('./src/components/cards/CardActions.vue')['default']
11
- CardBasic1: typeof import('./src/components/cards/basic/CardBasic1.vue')['default']
12
- CardBasic2: typeof import('./src/components/cards/basic/CardBasic2.vue')['default']
13
- CardBasic3: typeof import('./src/components/cards/basic/CardBasic3.vue')['default']
14
- CardBasic4: typeof import('./src/components/cards/basic/CardBasic4.vue')['default']
15
- CardBasic5: typeof import('./src/components/cards/basic/CardBasic5.vue')['default']
16
- CardBasic6: typeof import('./src/components/cards/basic/CardBasic6.vue')['default']
17
- CardCodeExample: typeof import('./src/components/cards/CardCodeExample.vue')['default']
18
- CardCombo1: typeof import('./src/components/cards/combo/CardCombo1.vue')['default']
19
- CardCombo2: typeof import('./src/components/cards/combo/CardCombo2.vue')['default']
20
- CardCombo3: typeof import('./src/components/cards/combo/CardCombo3.vue')['default']
21
- CardCombo4: typeof import('./src/components/cards/combo/CardCombo4.vue')['default']
22
- CardCombo5: typeof import('./src/components/cards/combo/CardCombo5.vue')['default']
23
- CardCombo6: typeof import('./src/components/cards/combo/CardCombo6.vue')['default']
24
- CardCombo7: typeof import('./src/components/cards/combo/CardCombo7.vue')['default']
25
- CardCombo8: typeof import('./src/components/cards/combo/CardCombo8.vue')['default']
26
- CardComboIcon: typeof import('./src/components/cards/combo/CardComboIcon.vue')['default']
27
- CardEcommerce1: typeof import('./src/components/cards/ecommerce/CardEcommerce1.vue')['default']
28
- CardEcommerce2: typeof import('./src/components/cards/ecommerce/CardEcommerce2.vue')['default']
29
- CardEcommerce3: typeof import('./src/components/cards/ecommerce/CardEcommerce3.vue')['default']
30
- CardEcommerce4: typeof import('./src/components/cards/ecommerce/CardEcommerce4.vue')['default']
31
- CardExtra1: typeof import('./src/components/cards/extra/CardExtra1.vue')['default']
32
- CardExtra2: typeof import('./src/components/cards/extra/CardExtra2.vue')['default']
33
- CardExtra3: typeof import('./src/components/cards/extra/CardExtra3.vue')['default']
34
- CardExtra4: typeof import('./src/components/cards/extra/CardExtra4.vue')['default']
35
- CardExtra5: typeof import('./src/components/cards/extra/CardExtra5.vue')['default']
36
- CardExtra6: typeof import('./src/components/cards/extra/CardExtra6.vue')['default']
37
- CardExtra7: typeof import('./src/components/cards/extra/CardExtra7.vue')['default']
38
- CardSocial1: typeof import('./src/components/cards/social/CardSocial1.vue')['default']
39
- CardWrapper: typeof import('./src/components/cards/CardWrapper.vue')['default']
40
- RouterLink: typeof import('vue-router')['RouterLink']
41
- RouterView: typeof import('vue-router')['RouterView']
42
- }
8
+declare module "vue" {
9
+ export interface GlobalComponents {
10
+ CardActions: typeof import("./src/components/cards/CardActions.vue")["default"]
11
+ CardBasic1: typeof import("./src/components/cards/basic/CardBasic1.vue")["default"]
12
+ CardBasic2: typeof import("./src/components/cards/basic/CardBasic2.vue")["default"]
13
+ CardBasic3: typeof import("./src/components/cards/basic/CardBasic3.vue")["default"]
14
+ CardBasic4: typeof import("./src/components/cards/basic/CardBasic4.vue")["default"]
15
+ CardBasic5: typeof import("./src/components/cards/basic/CardBasic5.vue")["default"]
16
+ CardBasic6: typeof import("./src/components/cards/basic/CardBasic6.vue")["default"]
17
+ CardCodeExample: typeof import("./src/components/cards/CardCodeExample.vue")["default"]
18
+ CardCombo1: typeof import("./src/components/cards/combo/CardCombo1.vue")["default"]
19
+ CardCombo2: typeof import("./src/components/cards/combo/CardCombo2.vue")["default"]
20
+ CardCombo3: typeof import("./src/components/cards/combo/CardCombo3.vue")["default"]
21
+ CardCombo4: typeof import("./src/components/cards/combo/CardCombo4.vue")["default"]
22
+ CardCombo5: typeof import("./src/components/cards/combo/CardCombo5.vue")["default"]
23
+ CardCombo6: typeof import("./src/components/cards/combo/CardCombo6.vue")["default"]
24
+ CardCombo7: typeof import("./src/components/cards/combo/CardCombo7.vue")["default"]
25
+ CardCombo8: typeof import("./src/components/cards/combo/CardCombo8.vue")["default"]
26
+ CardComboIcon: typeof import("./src/components/cards/combo/CardComboIcon.vue")["default"]
27
+ CardEcommerce1: typeof import("./src/components/cards/ecommerce/CardEcommerce1.vue")["default"]
28
+ CardEcommerce2: typeof import("./src/components/cards/ecommerce/CardEcommerce2.vue")["default"]
29
+ CardEcommerce3: typeof import("./src/components/cards/ecommerce/CardEcommerce3.vue")["default"]
30
+ CardEcommerce4: typeof import("./src/components/cards/ecommerce/CardEcommerce4.vue")["default"]
31
+ CardExtra1: typeof import("./src/components/cards/extra/CardExtra1.vue")["default"]
32
+ CardExtra2: typeof import("./src/components/cards/extra/CardExtra2.vue")["default"]
33
+ CardExtra3: typeof import("./src/components/cards/extra/CardExtra3.vue")["default"]
34
+ CardExtra4: typeof import("./src/components/cards/extra/CardExtra4.vue")["default"]
35
+ CardExtra5: typeof import("./src/components/cards/extra/CardExtra5.vue")["default"]
36
+ CardExtra6: typeof import("./src/components/cards/extra/CardExtra6.vue")["default"]
37
+ CardExtra7: typeof import("./src/components/cards/extra/CardExtra7.vue")["default"]
38
+ CardSocial1: typeof import("./src/components/cards/social/CardSocial1.vue")["default"]
39
+ CardWrapper: typeof import("./src/components/cards/CardWrapper.vue")["default"]
40
+ RouterLink: typeof import("vue-router")["RouterLink"]
41
+ RouterView: typeof import("vue-router")["RouterView"]
42
+ }
43
}