regscale-cli 6.20.10.0__py3-none-any.whl → 6.21.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/application.py +12 -5
- regscale/core/app/internal/set_permissions.py +58 -27
- regscale/integrations/commercial/__init__.py +1 -2
- regscale/integrations/commercial/amazon/common.py +79 -2
- regscale/integrations/commercial/aws/cli.py +183 -9
- regscale/integrations/commercial/aws/scanner.py +544 -9
- regscale/integrations/commercial/cpe.py +18 -1
- regscale/integrations/commercial/nessus/scanner.py +2 -0
- regscale/integrations/commercial/sonarcloud.py +35 -36
- regscale/integrations/commercial/synqly/ticketing.py +51 -0
- regscale/integrations/commercial/tenablev2/jsonl_scanner.py +2 -1
- regscale/integrations/commercial/wizv2/async_client.py +10 -3
- regscale/integrations/commercial/wizv2/click.py +102 -26
- regscale/integrations/commercial/wizv2/constants.py +249 -1
- regscale/integrations/commercial/wizv2/issue.py +2 -2
- regscale/integrations/commercial/wizv2/parsers.py +3 -2
- regscale/integrations/commercial/wizv2/policy_compliance.py +1858 -0
- regscale/integrations/commercial/wizv2/scanner.py +15 -21
- regscale/integrations/commercial/wizv2/utils.py +258 -85
- regscale/integrations/commercial/wizv2/variables.py +4 -3
- regscale/integrations/compliance_integration.py +1455 -0
- regscale/integrations/integration_override.py +15 -6
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/markdown_parser.py +7 -1
- regscale/integrations/scanner_integration.py +193 -37
- regscale/models/app_models/__init__.py +1 -0
- regscale/models/integration_models/amazon_models/inspector_scan.py +32 -57
- regscale/models/integration_models/aqua.py +92 -78
- regscale/models/integration_models/cisa_kev_data.json +117 -5
- regscale/models/integration_models/defenderimport.py +64 -59
- regscale/models/integration_models/ecr_models/ecr.py +100 -147
- regscale/models/integration_models/flat_file_importer/__init__.py +52 -38
- regscale/models/integration_models/ibm.py +29 -47
- regscale/models/integration_models/nexpose.py +156 -68
- regscale/models/integration_models/prisma.py +46 -66
- regscale/models/integration_models/qualys.py +99 -93
- regscale/models/integration_models/snyk.py +229 -158
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/veracode.py +15 -20
- regscale/{integrations/commercial/wizv2/models.py → models/integration_models/wizv2.py} +4 -12
- regscale/models/integration_models/xray.py +276 -82
- regscale/models/regscale_models/control_implementation.py +14 -12
- regscale/models/regscale_models/file.py +4 -0
- regscale/models/regscale_models/issue.py +123 -0
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/rbac.py +22 -0
- regscale/models/regscale_models/regscale_model.py +4 -2
- regscale/models/regscale_models/security_plan.py +1 -1
- regscale/utils/graphql_client.py +3 -1
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/METADATA +9 -9
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/RECORD +64 -60
- tests/fixtures/test_fixture.py +58 -2
- tests/regscale/core/test_app.py +5 -3
- tests/regscale/core/test_version_regscale.py +5 -3
- tests/regscale/integrations/test_integration_mapping.py +522 -40
- tests/regscale/integrations/test_issue_due_date.py +1 -1
- tests/regscale/integrations/test_update_finding_dates.py +336 -0
- tests/regscale/integrations/test_wiz_policy_compliance_affected_controls.py +154 -0
- tests/regscale/models/test_asset.py +406 -50
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/top_level.txt +0 -0
regscale/_version.py
CHANGED
regscale/core/app/application.py
CHANGED
|
@@ -245,6 +245,7 @@ class Application(metaclass=Singleton):
|
|
|
245
245
|
"snowPassword": "<snowPassword>",
|
|
246
246
|
"snowUrl": "<mySnowUrl>",
|
|
247
247
|
"snowUserName": "<snowUserName>",
|
|
248
|
+
"sonarUrl": "https://sonarcloud.io",
|
|
248
249
|
"sonarToken": "<mySonarToken>",
|
|
249
250
|
"tenableAccessKey": "<tenableAccessKeyGoesHere>",
|
|
250
251
|
"tenableSecretKey": "<tenableSecretKeyGoesHere>",
|
|
@@ -295,7 +296,10 @@ class Application(metaclass=Singleton):
|
|
|
295
296
|
self.running_in_airflow = os.getenv("REGSCALE_AIRFLOW") == "true"
|
|
296
297
|
if isinstance(config, str):
|
|
297
298
|
config = self._read_config_from_str(config)
|
|
298
|
-
|
|
299
|
+
if self.running_in_airflow:
|
|
300
|
+
self.config = self._fetch_config_from_regscale(config)
|
|
301
|
+
else:
|
|
302
|
+
self.config = self._gen_config(config)
|
|
299
303
|
self.os = platform.system()
|
|
300
304
|
self.input_host = ""
|
|
301
305
|
# Ensure maxThreads is an integer for ThreadManager
|
|
@@ -536,10 +540,6 @@ class Application(metaclass=Singleton):
|
|
|
536
540
|
self.logger.debug("Successfully retrieved config from Click context.")
|
|
537
541
|
return click_config
|
|
538
542
|
|
|
539
|
-
if self.running_in_airflow:
|
|
540
|
-
if airflow_config := self._get_airflow_config(config):
|
|
541
|
-
self.logger.debug("Successfully retrieved config from Airflow.")
|
|
542
|
-
return airflow_config
|
|
543
543
|
try:
|
|
544
544
|
if config and self.local_config:
|
|
545
545
|
self.logger.debug(f"Config provided as :\n{type(config)}")
|
|
@@ -572,6 +572,13 @@ class Application(metaclass=Singleton):
|
|
|
572
572
|
return config
|
|
573
573
|
|
|
574
574
|
def _get_airflow_config(self, config: Optional[Union[dict, str]] = None) -> Optional[dict]:
|
|
575
|
+
"""
|
|
576
|
+
Get config from Airflow DAG config, or from the environment variables if not provided.
|
|
577
|
+
|
|
578
|
+
:param Optional[Union[dict, str]] config: Configuration dictionary, defaults to None
|
|
579
|
+
:return: Configuration dictionary
|
|
580
|
+
:rtype: Optional[dict]
|
|
581
|
+
"""
|
|
575
582
|
if config:
|
|
576
583
|
self.logger.debug(f"Received config from Airflow as: {type(config)}")
|
|
577
584
|
# check to see if config is a string because airflow can pass a string instead of a dict
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
import os
|
|
4
|
-
from
|
|
5
|
-
from openpyxl
|
|
6
|
-
from openpyxl.worksheet.worksheet import Worksheet
|
|
4
|
+
from rich.progress import track
|
|
5
|
+
from openpyxl import Workbook
|
|
7
6
|
from openpyxl.worksheet.datavalidation import DataValidation
|
|
8
|
-
from openpyxl import Workbook, load_workbook
|
|
9
7
|
from openpyxl.utils.dataframe import dataframe_to_rows
|
|
10
8
|
|
|
11
9
|
from regscale.core.app.logz import create_logger
|
|
@@ -118,38 +116,71 @@ def import_permissions_workbook(file: Path):
|
|
|
118
116
|
"""
|
|
119
117
|
# Read in the spreadsheet
|
|
120
118
|
records = get_records(file=file)
|
|
121
|
-
for
|
|
119
|
+
for index in track(
|
|
120
|
+
range(len(records)),
|
|
121
|
+
description="Processing rbacs...",
|
|
122
|
+
):
|
|
123
|
+
record = records[index]
|
|
124
|
+
# Check the records:
|
|
125
|
+
|
|
122
126
|
if not Group.get_object(record["group_id"]):
|
|
123
127
|
logger.error(f"Group {record['group_id']} doesn't exist in this instance. Skipping row")
|
|
124
128
|
continue
|
|
125
129
|
|
|
126
|
-
regscale_module_id = Modules.get_module_to_id(record["regscale_module"])
|
|
127
|
-
regscale_id = record["regscale_id"]
|
|
128
|
-
if record[READ_UPDATE] == "R":
|
|
129
|
-
permissions = 1
|
|
130
|
-
elif record[READ_UPDATE] == "RU":
|
|
131
|
-
permissions = 2
|
|
132
|
-
else:
|
|
133
|
-
permissions = 0
|
|
134
|
-
|
|
135
|
-
# Set the record to private
|
|
136
130
|
my_class = Modules.module_to_class(record["regscale_module"])
|
|
137
|
-
obj = my_class.get_object(regscale_id)
|
|
131
|
+
obj = my_class.get_object(record["regscale_id"])
|
|
138
132
|
if not obj:
|
|
139
|
-
logger.error(
|
|
133
|
+
logger.error(
|
|
134
|
+
f"RegScale {record['regscale_module']} record {record['regscale_id']} doesn't exist. Skipping row"
|
|
135
|
+
)
|
|
140
136
|
continue
|
|
141
137
|
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
138
|
+
# Set the permissions
|
|
139
|
+
set_permissions(record=record)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def set_permissions(record: dict):
|
|
143
|
+
"""
|
|
144
|
+
Sets the permissions for each record
|
|
145
|
+
|
|
146
|
+
:param dict record: permissions dictionary
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
if record[READ_UPDATE] == "R":
|
|
150
|
+
permissions = 1
|
|
151
|
+
elif record[READ_UPDATE] == "RU":
|
|
152
|
+
permissions = 2
|
|
153
|
+
else:
|
|
154
|
+
permissions = 0
|
|
155
|
+
|
|
156
|
+
# Add the permission
|
|
157
|
+
if not RBAC.add(
|
|
158
|
+
module_id=Modules.get_module_to_id(record["regscale_module"]),
|
|
159
|
+
parent_id=record["regscale_id"],
|
|
160
|
+
group_id=record["group_id"],
|
|
161
|
+
permission_type=permissions,
|
|
162
|
+
):
|
|
163
|
+
logger.warning(
|
|
164
|
+
f"Failed to set permissions for {record['regscale_module']} {record['regscale_id']} for group {record['group_id']}"
|
|
165
|
+
)
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
if not RBAC.public(
|
|
169
|
+
module_id=Modules.get_module_to_id(record["regscale_module"]),
|
|
170
|
+
parent_id=record["regscale_id"],
|
|
171
|
+
is_public=0 if record[PUBLIC_PRIVATE] == "private" else 1,
|
|
172
|
+
):
|
|
173
|
+
logger.warning(
|
|
174
|
+
f"Failed to set public/private for {record['regscale_module']} {record['regscale_id']} for group {record['group_id']}"
|
|
148
175
|
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
if not RBAC.reset(
|
|
179
|
+
module_id=Modules.get_module_to_id(record["regscale_module"]),
|
|
180
|
+
parent_id=record["regscale_id"],
|
|
181
|
+
):
|
|
182
|
+
logger.warning(
|
|
183
|
+
f"Failed to proliferate permissions for {record['regscale_module']} {record['regscale_id']} for group {record['group_id']}"
|
|
153
184
|
)
|
|
154
185
|
|
|
155
186
|
|
|
@@ -43,9 +43,8 @@ show_mapping(aqua, "aqua")
|
|
|
43
43
|
cls=LazyGroup,
|
|
44
44
|
lazy_subcommands={
|
|
45
45
|
"sync_assets": "regscale.integrations.commercial.aws.cli.sync_assets",
|
|
46
|
-
"
|
|
46
|
+
"sync_findings_and_assets": "regscale.integrations.commercial.aws.cli.sync_findings_and_assets",
|
|
47
47
|
"inspector": "regscale.integrations.commercial.aws.cli.inspector",
|
|
48
|
-
"inventory": "regscale.integrations.commercial.aws.cli.inventory",
|
|
49
48
|
},
|
|
50
49
|
name="aws",
|
|
51
50
|
)
|
|
@@ -11,6 +11,8 @@ from dateutil import parser
|
|
|
11
11
|
|
|
12
12
|
from regscale.core.app.utils.app_utils import create_logger
|
|
13
13
|
|
|
14
|
+
logger = create_logger()
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
def check_finding_severity(comment: Optional[str]) -> str:
|
|
16
18
|
"""Check the severity of the finding
|
|
@@ -93,6 +95,63 @@ def get_comments(finding: dict) -> str:
|
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
def fetch_aws_findings(aws_client: BaseClient) -> list:
|
|
98
|
+
"""Fetch AWS Findings with optimized rate limiting and pagination
|
|
99
|
+
|
|
100
|
+
:param BaseClient aws_client: AWS Security Hub Client
|
|
101
|
+
:return: AWS Findings
|
|
102
|
+
:rtype: list
|
|
103
|
+
"""
|
|
104
|
+
findings = []
|
|
105
|
+
try:
|
|
106
|
+
# Use optimized SecurityHubPuller for better performance
|
|
107
|
+
from regscale.integrations.commercial.aws.security_hub import SecurityHubPuller
|
|
108
|
+
|
|
109
|
+
# Extract credentials from the client to create SecurityHubPuller
|
|
110
|
+
session = aws_client._client_config.__dict__.get("_user_provided_options", {})
|
|
111
|
+
region = session.meta.region_name
|
|
112
|
+
|
|
113
|
+
# Create SecurityHubPuller with same credentials as the client
|
|
114
|
+
puller = SecurityHubPuller(region_name=region)
|
|
115
|
+
|
|
116
|
+
# Use existing client instead of creating new one to maintain credentials
|
|
117
|
+
puller.client = aws_client
|
|
118
|
+
|
|
119
|
+
# Fetch all findings with optimized pagination and rate limiting
|
|
120
|
+
logger.info("Using optimized SecurityHubPuller for findings retrieval...")
|
|
121
|
+
findings = puller.get_all_findings_with_retries()
|
|
122
|
+
|
|
123
|
+
logger.info(f"Successfully fetched {len(findings)} findings with rate limiting")
|
|
124
|
+
|
|
125
|
+
except ImportError:
|
|
126
|
+
# Fallback to original method if SecurityHubPuller not available
|
|
127
|
+
logger.warning("SecurityHubPuller not available, falling back to basic client")
|
|
128
|
+
findings = fallback_fetch_aws_findings(aws_client)
|
|
129
|
+
except ClientError as cex:
|
|
130
|
+
logger.error("Unexpected error: %s", cex)
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error("Error using SecurityHubPuller, falling back to basic client: %s", e)
|
|
133
|
+
findings = fallback_fetch_aws_findings(aws_client)
|
|
134
|
+
|
|
135
|
+
return findings
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def fallback_fetch_aws_findings(aws_client: BaseClient) -> list:
|
|
139
|
+
"""Fallback method to fetch AWS Findings without pagination
|
|
140
|
+
|
|
141
|
+
:param BaseClient aws_client: AWS Security Hub Client
|
|
142
|
+
:return: AWS Findings
|
|
143
|
+
:rtype: list
|
|
144
|
+
"""
|
|
145
|
+
findings = []
|
|
146
|
+
try:
|
|
147
|
+
response = aws_client.get_findings()
|
|
148
|
+
findings = response.get("Findings", [])
|
|
149
|
+
except ClientError as cex:
|
|
150
|
+
create_logger().error("Unexpected error when fetching resources from AWS: %s", cex)
|
|
151
|
+
return findings
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def fetch_aws_findings_v2(aws_client: BaseClient) -> list:
|
|
96
155
|
"""Fetch AWS Findings
|
|
97
156
|
|
|
98
157
|
:param BaseClient aws_client: AWS Security Hub Client
|
|
@@ -101,7 +160,25 @@ def fetch_aws_findings(aws_client: BaseClient) -> list:
|
|
|
101
160
|
"""
|
|
102
161
|
findings = []
|
|
103
162
|
try:
|
|
104
|
-
|
|
163
|
+
response = aws_client.get_findings_v2()
|
|
164
|
+
findings = response.get("Findings", [])
|
|
105
165
|
except ClientError as cex:
|
|
106
|
-
create_logger().error("Unexpected error: %s", cex)
|
|
166
|
+
create_logger().error("Unexpected error when fetching resources from AWS: %s", cex)
|
|
107
167
|
return findings
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def fetch_aws_resources(aws_client: BaseClient) -> list:
|
|
171
|
+
"""Fetch AWS Resources
|
|
172
|
+
|
|
173
|
+
:param BaseClient aws_client: AWS Security Hub Client
|
|
174
|
+
:return: AWS Resources
|
|
175
|
+
:rtype: list
|
|
176
|
+
"""
|
|
177
|
+
resources = []
|
|
178
|
+
try:
|
|
179
|
+
response = aws_client.get_resources_v2()
|
|
180
|
+
resources = response.get("Resources", [])
|
|
181
|
+
logger.info(f"Fetched {len(resources)} resources from Security Hub")
|
|
182
|
+
except ClientError as cex:
|
|
183
|
+
create_logger().error("Unexpected error when fetching resources from AWS: %s", cex)
|
|
184
|
+
return resources
|
|
@@ -265,13 +265,13 @@ def import_aws_scans(
|
|
|
265
265
|
"--region",
|
|
266
266
|
type=str,
|
|
267
267
|
default=os.environ.get("AWS_REGION", "us-east-1"),
|
|
268
|
-
help="AWS region to collect
|
|
268
|
+
help="AWS region to collect findings from",
|
|
269
269
|
)
|
|
270
270
|
@click.option(
|
|
271
271
|
"--regscale_id",
|
|
272
272
|
"--id",
|
|
273
273
|
type=click.INT,
|
|
274
|
-
help="RegScale will create and update
|
|
274
|
+
help="RegScale will create and update findings as children of this record.",
|
|
275
275
|
required=True,
|
|
276
276
|
)
|
|
277
277
|
@click.option(
|
|
@@ -286,7 +286,7 @@ def import_aws_scans(
|
|
|
286
286
|
type=str,
|
|
287
287
|
required=False,
|
|
288
288
|
help="AWS secret access key",
|
|
289
|
-
|
|
289
|
+
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
290
290
|
)
|
|
291
291
|
@click.option(
|
|
292
292
|
"--aws_session_token",
|
|
@@ -302,21 +302,195 @@ def sync_findings(
|
|
|
302
302
|
aws_secret_access_key: Optional[str] = None,
|
|
303
303
|
aws_session_token: Optional[str] = None,
|
|
304
304
|
) -> None:
|
|
305
|
-
"""
|
|
305
|
+
"""
|
|
306
|
+
Sync AWS Security Hub findings to RegScale.
|
|
307
|
+
|
|
308
|
+
This command fetches findings from AWS Security Hub and creates/updates
|
|
309
|
+
corresponding issues in RegScale.
|
|
310
|
+
"""
|
|
311
|
+
try:
|
|
312
|
+
logger.info("Starting AWS Security Hub findings sync to RegScale...")
|
|
313
|
+
from .scanner import AWSInventoryIntegration
|
|
314
|
+
|
|
315
|
+
scanner = AWSInventoryIntegration(plan_id=regscale_id)
|
|
316
|
+
findings_processed = scanner.sync_findings(
|
|
317
|
+
plan_id=regscale_id,
|
|
318
|
+
region=region,
|
|
319
|
+
aws_access_key_id=aws_access_key_id,
|
|
320
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
321
|
+
aws_session_token=aws_session_token,
|
|
322
|
+
)
|
|
323
|
+
logger.info(f"AWS Security Hub findings sync completed successfully. Processed {findings_processed} findings.")
|
|
324
|
+
except Exception as e:
|
|
325
|
+
logger.error(f"Error syncing AWS Security Hub findings: {e}", exc_info=True)
|
|
326
|
+
raise click.ClickException(str(e))
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@awsv2.command(name="sync_findings_and_assets")
|
|
330
|
+
@click.option(
|
|
331
|
+
"--region",
|
|
332
|
+
type=str,
|
|
333
|
+
default=os.environ.get("AWS_REGION", "us-east-1"),
|
|
334
|
+
help="AWS region to collect findings and assets from",
|
|
335
|
+
)
|
|
336
|
+
@click.option(
|
|
337
|
+
"--regscale_id",
|
|
338
|
+
"--id",
|
|
339
|
+
type=click.INT,
|
|
340
|
+
help="RegScale will create and update findings and assets as children of this record.",
|
|
341
|
+
required=True,
|
|
342
|
+
)
|
|
343
|
+
@click.option(
|
|
344
|
+
"--aws_access_key_id",
|
|
345
|
+
type=str,
|
|
346
|
+
required=False,
|
|
347
|
+
help="AWS access key ID",
|
|
348
|
+
envvar="AWS_ACCESS_KEY_ID",
|
|
349
|
+
)
|
|
350
|
+
@click.option(
|
|
351
|
+
"--aws_secret_access_key",
|
|
352
|
+
type=str,
|
|
353
|
+
required=False,
|
|
354
|
+
help="AWS secret access key",
|
|
355
|
+
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
356
|
+
)
|
|
357
|
+
@click.option(
|
|
358
|
+
"--aws_session_token",
|
|
359
|
+
type=click.STRING,
|
|
360
|
+
required=False,
|
|
361
|
+
help="AWS Session ID",
|
|
362
|
+
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
363
|
+
)
|
|
364
|
+
def sync_findings_and_assets(
|
|
365
|
+
region: str,
|
|
366
|
+
regscale_id: int,
|
|
367
|
+
aws_access_key_id: Optional[str] = None,
|
|
368
|
+
aws_secret_access_key: Optional[str] = None,
|
|
369
|
+
aws_session_token: Optional[str] = None,
|
|
370
|
+
) -> None:
|
|
371
|
+
"""
|
|
372
|
+
Sync AWS Security Hub findings and automatically discovered assets to RegScale.
|
|
373
|
+
|
|
374
|
+
This command fetches findings from AWS Security Hub, creates/updates corresponding
|
|
375
|
+
issues in RegScale, and also creates assets for the resources referenced in the findings.
|
|
376
|
+
This provides a comprehensive view by creating both the security findings and the
|
|
377
|
+
underlying AWS resources they reference.
|
|
378
|
+
"""
|
|
306
379
|
try:
|
|
307
|
-
logger.info("Starting AWS findings sync to RegScale...")
|
|
380
|
+
logger.info("Starting AWS Security Hub findings and assets sync to RegScale...")
|
|
308
381
|
from .scanner import AWSInventoryIntegration
|
|
309
382
|
|
|
310
383
|
scanner = AWSInventoryIntegration(plan_id=regscale_id)
|
|
311
|
-
scanner.
|
|
384
|
+
findings_processed, assets_processed = scanner.sync_findings_and_assets(
|
|
312
385
|
plan_id=regscale_id,
|
|
313
386
|
region=region,
|
|
314
387
|
aws_access_key_id=aws_access_key_id,
|
|
315
388
|
aws_secret_access_key=aws_secret_access_key,
|
|
316
389
|
aws_session_token=aws_session_token,
|
|
317
390
|
)
|
|
318
|
-
|
|
319
|
-
|
|
391
|
+
logger.info(
|
|
392
|
+
f"AWS Security Hub sync completed successfully. "
|
|
393
|
+
f"Processed {findings_processed} findings and {assets_processed} assets."
|
|
394
|
+
)
|
|
395
|
+
except Exception as e:
|
|
396
|
+
logger.error(f"Error syncing AWS Security Hub findings and assets: {e}", exc_info=True)
|
|
397
|
+
raise click.ClickException(str(e))
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@awsv2.group()
|
|
401
|
+
def findings():
|
|
402
|
+
"""AWS Security Hub findings commands."""
|
|
403
|
+
pass
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@findings.command(name="collect")
|
|
407
|
+
@click.option(
|
|
408
|
+
"--region",
|
|
409
|
+
type=str,
|
|
410
|
+
default=os.getenv("AWS_REGION", "us-east-1"),
|
|
411
|
+
help="AWS region to collect findings from. Default is us-east-1.",
|
|
412
|
+
)
|
|
413
|
+
@click.option(
|
|
414
|
+
"--aws_access_key_id",
|
|
415
|
+
type=str,
|
|
416
|
+
required=False,
|
|
417
|
+
help="AWS access key ID",
|
|
418
|
+
envvar="AWS_ACCESS_KEY_ID",
|
|
419
|
+
)
|
|
420
|
+
@click.option(
|
|
421
|
+
"--aws_secret_access_key",
|
|
422
|
+
type=str,
|
|
423
|
+
required=False,
|
|
424
|
+
help="AWS secret access key",
|
|
425
|
+
envvar="AWS_SECRET_ACCESS_KEY",
|
|
426
|
+
)
|
|
427
|
+
@click.option(
|
|
428
|
+
"--aws_session_token",
|
|
429
|
+
type=click.STRING,
|
|
430
|
+
required=False,
|
|
431
|
+
help="AWS Session ID",
|
|
432
|
+
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
433
|
+
)
|
|
434
|
+
@click.option(
|
|
435
|
+
"--output",
|
|
436
|
+
type=click.Path(dir_okay=False, writable=True),
|
|
437
|
+
help="Output file path (JSON format). Default: artifacts/aws/findings.json",
|
|
438
|
+
required=False,
|
|
439
|
+
)
|
|
440
|
+
def collect_findings(
|
|
441
|
+
region: str,
|
|
442
|
+
aws_access_key_id: Optional[str],
|
|
443
|
+
aws_secret_access_key: Optional[str],
|
|
444
|
+
aws_session_token: Optional[str],
|
|
445
|
+
output: Optional[str],
|
|
446
|
+
) -> None:
|
|
447
|
+
"""
|
|
448
|
+
Collect AWS Security Hub findings.
|
|
449
|
+
|
|
450
|
+
This command fetches findings from AWS Security Hub and displays them to stdout
|
|
451
|
+
or saves them to a JSON file. The findings include security issues, compliance
|
|
452
|
+
violations, and other security-related information from AWS Security Hub.
|
|
453
|
+
|
|
454
|
+
If no output file is specified, findings will be saved to artifacts/aws/findings.json
|
|
455
|
+
by default. Use --output - to display to stdout instead.
|
|
456
|
+
"""
|
|
457
|
+
try:
|
|
458
|
+
import boto3
|
|
459
|
+
from regscale.integrations.commercial.amazon.common import fetch_aws_findings
|
|
460
|
+
from regscale.models import DateTimeEncoder
|
|
461
|
+
|
|
462
|
+
logger.info("Collecting AWS Security Hub findings...")
|
|
463
|
+
|
|
464
|
+
# Create AWS session
|
|
465
|
+
session = boto3.Session(
|
|
466
|
+
region_name=region,
|
|
467
|
+
aws_access_key_id=aws_access_key_id,
|
|
468
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
469
|
+
aws_session_token=aws_session_token,
|
|
470
|
+
)
|
|
471
|
+
client = session.client("securityhub")
|
|
472
|
+
|
|
473
|
+
# Fetch findings
|
|
474
|
+
findings = fetch_aws_findings(aws_client=client)
|
|
475
|
+
|
|
476
|
+
logger.info(f"AWS Security Hub findings collected successfully. Found {len(findings)} finding(s).")
|
|
477
|
+
|
|
478
|
+
# Default output path
|
|
479
|
+
if output is None:
|
|
480
|
+
output = os.path.join("artifacts", "aws", "findings.json")
|
|
481
|
+
|
|
482
|
+
if output == "-":
|
|
483
|
+
# Output to stdout
|
|
484
|
+
click.echo(json.dumps(findings, indent=2, cls=DateTimeEncoder))
|
|
485
|
+
else:
|
|
486
|
+
# Save to file
|
|
487
|
+
# Ensure the artifacts directory exists
|
|
488
|
+
os.makedirs(os.path.dirname(output), exist_ok=True)
|
|
489
|
+
|
|
490
|
+
with open(output, "w", encoding="utf-8") as f:
|
|
491
|
+
json.dump(findings, f, indent=2, cls=DateTimeEncoder)
|
|
492
|
+
logger.info(f"Findings saved to {output}")
|
|
493
|
+
|
|
320
494
|
except Exception as e:
|
|
321
|
-
logger.error(f"Error
|
|
495
|
+
logger.error(f"Error collecting AWS Security Hub findings: {e}", exc_info=True)
|
|
322
496
|
raise click.ClickException(str(e))
|