Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scoutsuite gcp #290

Merged
merged 11 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 2 additions & 43 deletions backend/app/integrations/markdown/bitdefender.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The connector uses the POST method to receive authenticated and secured messages
What we will be deploying is an HTTP endpoint that will receive the BitDefender logs and forward them to a syslog server. The HTTP endpoint is running on the server running CoPilot and will be listening on the port that you will define. This means that we must configure public DNS records to point to your edge firewall and open the port that you will define in the firewall.

Traffic flow will be as follows:
BitDefender Cloud Platform -> Your Edge Firewall -> CoPilot Server -> Graylog Server
BitDefender Cloud Platform _ Your Edge Firewall _ CoPilot Server \_ Graylog Server

## Configuration

Expand Down Expand Up @@ -59,45 +59,4 @@ You should now see the container running.

## Test the Connector

[Helpful Doc](https://support.netenrich.com/hc/en-us/articles/10833633251869-Bitdefender-Gravity-Zone-Cloud-integration#:~:text=155.173,Configure%20Chronicle%20Forwarder)

Use the following cURL command to send the test payload to the collector service you have just configured:

Replace `YOUR_AUTH_HEADER` with the base64 (https://www.blitter.se/utils/basic-authentication-header-generator/) encoded string of `username:password` and `REPLACE_WITH_YOUR_WEBSERVER` with the public DNS name you configured.

### NOTE: This only tests that your endpoint is reachable and that the logs are being sent to the endpoint. You will need to verify that the logs are being sent to the Graylog server.

```bash
curl -k -H 'Authorization: Basic YOUR_AUTH_HEADER' -H "Content-Type: application/json" -d
'{"cef": "0","events":
["CEF:0|Bitdefender|GravityZone|6.4.08|70000|Registration|3|BitdefenderGZModule=registrationd
vchost=TEST_ENDPOINTasdadBitdefenderGZComputerFQDN=test.example.com
dvc=192.168.1.2","CEF:0|Bitdefender|GravityZone|6.4.0-8|35|
Product ModulesStatus|5|BitdefenderGZModule=modules
dvchost=TEST_ENDPOINTasdadBitdefenderGZComputerFQDN=test.example.com
dvc=192.168.1.2","CEF:0|Bitdefender|GravityZone|6.4.0-8|35|
Product ModulesStatus|5|BitdefenderGZModule=modules
dvchost=TEST_ENDPOINTasdadBitdefenderGZComputerFQDN=test.example.com dvc=192.168.1.2"]}'
https://REPLACE_WITH_YOUR_WEBSERVER:3200/api
```

Now that the HTTPS collector service is running and listening for messages, we can test the service by sending a test message to the BitDefender service. Use the following cURL command to send the test payload to the collector service you have just configured:

Replace `YOUR_BITDEFENDER_API_KEY` with the BitDefender API key with the base64 encoded string of `API_KEY` followed by a colon `:`. For example, if the API key is `test`, the value I would base64 encode would be `test:`. Replace `REPLACE_WITH_YOUR_WEBSERVER` with the public DNS name you configured.

```bash
$ curl --tlsv1.2 -sS -k -X POST \
https://cloud.gravityzone.bitdefender.com/api/v1.0/jsonrpc/push \
-H 'authorization: Basic YOUR_BITDEFENDER_API_KEY' \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{"id":"1","jsonrpc":"2.0","method":"setPushEventSettings",
"params":{"serviceSettings":{"requireValidSslCertificate":false,"authorization":"Basic
dGVzdDp0ZXN0","url":"https://REPLACE_WITH_YOUR_WEBSERVER:3200/api"},"serviceType":"jsonRPC","status":1,
"subscribeToEventTypes":{"adcloudgz":true,"antiexploit":true,"aph":true,"av":true,"avc":true,"dp":true,
"endpoint-moved-in":true,"endpoint-moved-out":true,"exchange-malware":true,
"exchange-user-credentials":true,"fw":true,"hd":true,"hwid-change":true,"install":true,"modules":true,
"network-monitor":true,"network-sandboxing":true,"new-incident":true,"ransomware-mitigation":true,
"registration":true,"supa-update-status":true,"sva":true,"sva-load":true,"task-status":true,
"troubleshooting-activity":true,"uc":true,"uninstall":true}}}'
```
[Helpful Doc For Testing](https://support.netenrich.com/hc/en-us/articles/10833633251869-Bitdefender-Gravity-Zone-Cloud-integration#:~:text=155.173,Configure%20Chronicle%20Forwarder)
46 changes: 46 additions & 0 deletions backend/app/integrations/scoutsuite/routes/scoutsuite.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

from fastapi import APIRouter
from fastapi import BackgroundTasks
from fastapi import File
from fastapi import HTTPException
from fastapi import UploadFile
from loguru import logger

from app.integrations.scoutsuite.schema.scoutsuite import (
AvailableScoutSuiteReportsResponse,
)
from app.integrations.scoutsuite.schema.scoutsuite import AWSScoutSuiteReportRequest
from app.integrations.scoutsuite.schema.scoutsuite import AzureScoutSuiteReportRequest
from app.integrations.scoutsuite.schema.scoutsuite import GCPScoutSuiteReportRequest
from app.integrations.scoutsuite.schema.scoutsuite import ScoutSuiteReportOptions
from app.integrations.scoutsuite.schema.scoutsuite import (
ScoutSuiteReportOptionsResponse,
Expand All @@ -21,6 +24,12 @@
from app.integrations.scoutsuite.services.scoutsuite import (
generate_azure_report_background,
)
from app.integrations.scoutsuite.services.scoutsuite import (
generate_gcp_report_background,
)
from app.integrations.scoutsuite.services.scoutsuite import read_json_file
from app.integrations.scoutsuite.services.scoutsuite import save_file_to_directory
from app.integrations.scoutsuite.services.scoutsuite import validate_json_data

integration_scoutsuite_router = APIRouter()

Expand Down Expand Up @@ -122,6 +131,43 @@ async def generate_azure_report(
)


@integration_scoutsuite_router.post(
"/generate-gcp-report",
response_model=ScoutSuiteReportResponse,
)
async def generate_gcp_report(
background_tasks: BackgroundTasks,
file: UploadFile = File(...),
report_name: str = "gcp-report",
):
"""
Endpoint to generate a GCP ScoutSuite report.

Args:
background_tasks (BackgroundTasks): The background tasks object.
file (UploadFile): The uploaded JSON file.
"""
# Read the file contents
contents = await file.read()

# Read and validate the JSON file
data = await read_json_file(contents)
validate_json_data(data)

# Save the file to the scoutsuite-report directory
directory = os.path.join(os.getcwd(), "scoutsuite-report")
file_path = await save_file_to_directory(contents, directory, file.filename)

logger.info(f"File saved to: {file_path}")
request = GCPScoutSuiteReportRequest(report_name=report_name, file_path=file_path)
logger.info(f"Request: {request}")
background_tasks.add_task(generate_gcp_report_background, request)
return ScoutSuiteReportResponse(
success=True,
message="GCP ScoutSuite report generation started successfully. This will take a few minutes to complete. Check back in shortly.",
)


@integration_scoutsuite_router.delete(
"/delete-report/{report_name}",
response_model=ScoutSuiteReportResponse,
Expand Down
19 changes: 19 additions & 0 deletions backend/app/integrations/scoutsuite/schema/scoutsuite.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ def validate_report_type(cls, values):
return values


class GCPScoutSuiteReportRequest(BaseModel):
report_name: str = Field(..., description="The name of the report", example="gcp-report")
file_path: str = Field(..., description="The path to the GCP credentials file", example="gcp-credentials.json")


class GCPScoutSuiteJSON(BaseModel):
type: str
project_id: str
private_key_id: str
private_key: str
client_email: str
client_id: str
auth_uri: str
token_uri: str
auth_provider_x509_cert_url: str
client_x509_cert_url: str
universe_domain: str


class ScoutSuiteReportResponse(BaseModel):
success: bool
message: str
Expand Down
62 changes: 62 additions & 0 deletions backend/app/integrations/scoutsuite/services/scoutsuite.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import asyncio
import json
import os
import subprocess
from concurrent.futures import ThreadPoolExecutor

import aiofiles
from fastapi import HTTPException
from loguru import logger

from app.integrations.scoutsuite.schema.scoutsuite import AWSScoutSuiteReportRequest
from app.integrations.scoutsuite.schema.scoutsuite import AzureScoutSuiteReportRequest
from app.integrations.scoutsuite.schema.scoutsuite import GCPScoutSuiteJSON
from app.integrations.scoutsuite.schema.scoutsuite import GCPScoutSuiteReportRequest


async def generate_aws_report_background(request: AWSScoutSuiteReportRequest):
Expand Down Expand Up @@ -57,6 +63,34 @@ def construct_azure_command(request: AzureScoutSuiteReportRequest):
]


async def generate_gcp_report_background(request: GCPScoutSuiteReportRequest):
logger.info("Generating GCP ScoutSuite report in the background")

command = construct_gcp_command(request)
await run_command_in_background(command)

# Delete the file after the report is generated
try:
os.remove(request.file_path)
logger.info(f"Deleted GCP credentials file: {request.file_path}")
except Exception as e:
logger.error(f"Error deleting GCP credentials file: {e}")


def construct_gcp_command(request: GCPScoutSuiteReportRequest):
"""Construct the scout command."""
return [
"scout",
"gcp",
"--service-account",
request.file_path,
"--report-name",
request.report_name,
"--force",
"--no-browser",
]


def run_command(command):
"""Run the command and handle the output."""
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Expand All @@ -75,3 +109,31 @@ async def run_command_in_background(command):
with ThreadPoolExecutor() as executor:
loop = asyncio.get_event_loop()
await loop.run_in_executor(executor, lambda: run_command(command))


async def read_json_file(contents: bytes) -> dict:
"""Read and parse the JSON file."""
try:
return json.loads(contents)
except json.JSONDecodeError as e:
raise HTTPException(status_code=400, detail=f"Invalid JSON file - {str(e)}")


def validate_json_data(data: dict):
"""Validate the JSON data against the GCPScoutSuiteJSON model."""
try:
GCPScoutSuiteJSON(**data)
except Exception as e:
raise HTTPException(status_code=400, detail=f"JSON file does not have the correct format and fields - {str(e)}")


async def save_file_to_directory(contents: bytes, directory: str, filename: str) -> str:
"""Save the uploaded file to the specified directory."""
try:
os.makedirs(directory, exist_ok=True)
file_path = os.path.join(directory, filename)
async with aiofiles.open(file_path, "wb") as out_file:
await out_file.write(contents)
return file_path
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
Loading
Loading