Skip to content

Commit

Permalink
Merge pull request #6 from stat-kwon/master
Browse files Browse the repository at this point in the history
Add prowler service for Google Cloud
  • Loading branch information
stat-kwon authored Jan 17, 2024
2 parents 3a87ebe + 56de5c2 commit 4d3541c
Show file tree
Hide file tree
Showing 8 changed files with 616 additions and 37 deletions.
42 changes: 7 additions & 35 deletions src/cloudforet/plugin/connector/aws_prowler_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from spaceone.core import utils
from spaceone.core.connector import BaseConnector
from cloudforet.plugin.lib import ProfileManager
from cloudforet.plugin.error.custom import *
from cloudforet.plugin.model.prowler.collector import COMPLIANCE_FRAMEWORKS

Expand All @@ -19,47 +20,19 @@
_AWS_PROFILE_DIR = _AWS_PROFILE_PATH.rsplit("/", 1)[0]


class AWSProfileManager:
def __init__(self, credentials: dict):
self._profile_name = utils.random_string()
self._source_profile_name = None
self._credentials = credentials

@property
def profile_name(self) -> str:
return self._profile_name

@property
def source_profile_name(self) -> str:
return self._source_profile_name

@source_profile_name.setter
def source_profile_name(self, value: str):
self._source_profile_name = value

@property
def credentials(self) -> dict:
return self._credentials

def __enter__(self) -> "AWSProfileManager":
self._add_aws_profile()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self._remove_aws_profile()

def _add_aws_profile(self):
class AWSProfileManager(ProfileManager):
def _add_profile(self):
_LOGGER.debug(f"[_AWSProfileManager] add aws profile: {self._profile_name}")

aws_profile = configparser.ConfigParser()

if os.path.exists(_AWS_PROFILE_PATH) is False:
self._create_aws_profile_file(aws_profile)
self._create_profile_file(aws_profile)

aws_profile.read(_AWS_PROFILE_PATH)

if self.profile_name in aws_profile.sections():
self._remove_aws_profile(aws_profile)
self._remove_profile(aws_profile)

aws_profile.add_section(self.profile_name)

Expand Down Expand Up @@ -104,14 +77,13 @@ def _add_aws_profile(self):
with open(_AWS_PROFILE_PATH, "w") as f:
aws_profile.write(f)

@staticmethod
def _create_aws_profile_file(aws_profile: configparser.ConfigParser):
def _create_profile_file(self, aws_profile: configparser.ConfigParser):
os.makedirs(_AWS_PROFILE_DIR, exist_ok=True)
aws_profile["default"] = {}
with open(_AWS_PROFILE_PATH, "w") as f:
aws_profile.write(f)

def _remove_aws_profile(self, aws_profile: configparser.ConfigParser = None):
def _remove_profile(self, aws_profile: configparser.ConfigParser = None):
_LOGGER.debug(f"[_AWSProfileManager] remove aws profile: {self._profile_name}")

if aws_profile is None:
Expand Down
139 changes: 139 additions & 0 deletions src/cloudforet/plugin/connector/google_prowler_connector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import os
import json
import logging
import tempfile
import configparser
import subprocess
from typing import List

from spaceone.core import utils
from spaceone.core.connector import BaseConnector
from cloudforet.plugin.lib import ProfileManager
from cloudforet.plugin.error.custom import *
from cloudforet.plugin.model.prowler.collector import COMPLIANCE_FRAMEWORKS

__all__ = ["GoogleProwlerConnector"]
REQUIRED_SECRET_KEYS = [
"type",
"project_id",
"private_key_id",
"private_key",
"client_email",
"client_id",
"auth_uri",
"token_uri",
"auth_provider_x509_cert_url",
"client_x509_cert_url",
"universe_domain",
]

_LOGGER = logging.getLogger(__name__)
_GOOGLE_CLOUD_PROFILE_PATH = os.environ.get(
"GOOGLE_CLOUD_SHARED_CREDENTIALS_FILE",
os.path.expanduser("~/.google_cloud/credentials"),
)
_GOOGLE_CLOUD_PROFILE_DIR = _GOOGLE_CLOUD_PROFILE_PATH.rsplit("/", 1)[0]


class GoogleProfileManager(ProfileManager):
def _add_profile(self):
_LOGGER.debug(
f"[_GoogleProfileManager] add google profile: {self._profile_name}"
)
json_file_path = os.path.join(
_GOOGLE_CLOUD_PROFILE_DIR, f"{self._profile_name}.json"
)

self.source_profile_path = json_file_path

if os.path.exists(_GOOGLE_CLOUD_PROFILE_PATH) is False:
self._create_profile_file()
else:
if not os.path.exists(json_file_path):
self._create_profile_file()

def _create_profile_file(self, **kwargs):
os.makedirs(_GOOGLE_CLOUD_PROFILE_DIR, exist_ok=True)

secret_data_json = json.dumps(self._credentials)
with open(self.source_profile_path, "w") as f:
f.write(secret_data_json)

def _remove_profile(self, json_file_path=None):
_LOGGER.debug(
f"[_GoogleProfileManager] remove google cloud profile: {self._profile_name}"
)

if json_file_path:
os.remove(json_file_path)


class GoogleProwlerConnector(BaseConnector):
provider = "google_cloud"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._temp_dir = None

def verify_client(self, options: dict, secret_data: dict, schema: str):
self._check_secret_data(secret_data)

with tempfile.TemporaryDirectory():
with GoogleProfileManager(secret_data) as google_profile:
cmd = self._command_prefix(google_profile.source_profile_path)
cmd += ["-l"]
_LOGGER.debug(f"[verify_client] command: {cmd}")
response = subprocess.run(
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE
)
if response.returncode != 0:
raise ERROR_PROWLER_EXECUTION_FAILED(
reason=response.stderr.decode("utf-8")
)

def check(self, options: dict, secret_data: dict, schema: str):
self._check_secret_data(secret_data)

compliance_framework = COMPLIANCE_FRAMEWORKS["google_cloud"].get(
options["compliance_framework"]
)

with tempfile.TemporaryDirectory() as temp_dir:
with GoogleProfileManager(secret_data) as google_profile:
cmd = self._command_prefix(google_profile.source_profile_path)

cmd += ["-M", "json", "-o", temp_dir, "-F", "output", "-z"]
cmd += ["--compliance", compliance_framework]

_LOGGER.debug(f"[check] command: {cmd}")

response = subprocess.run(
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE
)
if response.returncode != 0:
raise ERROR_PROWLER_EXECUTION_FAILED(
reason=response.stderr.decode("utf-8")
)

output_json_file = os.path.join(temp_dir, "output.json")
check_results = utils.load_json_from_file(output_json_file)
return check_results

@staticmethod
def _check_secret_data(secret_data):
missing_keys = [key for key in REQUIRED_SECRET_KEYS if key not in secret_data]
if missing_keys:
for key in missing_keys:
raise ERROR_REQUIRED_PARAMETER(key=f"secret_data.{key}")

@staticmethod
def _command_prefix(source_profile_path) -> List[str]:
return [
"python3",
"-m",
"prowler",
"gcp",
"--credentials-file",
source_profile_path,
"-b",
]
1 change: 1 addition & 0 deletions src/cloudforet/plugin/lib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from cloudforet.plugin.lib.profile_manager import ProfileManager
57 changes: 57 additions & 0 deletions src/cloudforet/plugin/lib/profile_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import logging
import abc

from spaceone.core import utils

_LOGGER = logging.getLogger(__name__)


class ProfileManager:
def __init__(self, credentials: dict):
self._profile_name = utils.random_string()
self._source_profile_name = None
self._source_profile_path = None
self._credentials = credentials

@property
def profile_name(self) -> str:
return self._profile_name

@property
def source_profile_name(self) -> str:
return self._source_profile_name

@source_profile_name.setter
def source_profile_name(self, value: str):
self._source_profile_name = value

@property
def source_profile_path(self) -> str:
return self._source_profile_path

@source_profile_path.setter
def source_profile_path(self, value: str):
self._source_profile_path = value

@property
def credentials(self) -> dict:
return self._credentials

def __enter__(self) -> "ProfileManager":
self._add_profile()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self._remove_profile()

@abc.abstractmethod
def _add_profile(self):
raise NotImplemented("Please implement _add_profile method")

@abc.abstractmethod
def _create_profile_file(self, profile):
raise NotImplemented("Please implement _create_profile_file method")

@abc.abstractmethod
def _remove_profile(self, profile=None):
raise NotImplemented("Please implement _remove_profile method")
1 change: 1 addition & 0 deletions src/cloudforet/plugin/manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from cloudforet.plugin.manager.aws_prowler_manager import AWSProwlerManager
from cloudforet.plugin.manager.azure_prowler_manager import AzureProwlerManager
from cloudforet.plugin.manager.google_prowler_manager import GoogleProwlerManager
Loading

0 comments on commit 4d3541c

Please sign in to comment.