@cryptotaxi247 / CoPilot / commits / 4e215d63

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 }