Skip to content

Commit adaf30d

Browse files
authored
Merge pull request #142 from awslabs/v3/feat/add-aws-plugin-module
feat(metrics collection): added metrics collection and reworked AshAggregatedResults scanner results content for usability
2 parents b51449b + cbd2e9e commit adaf30d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1049
-329
lines changed

.ash/.ash.yaml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# yaml-language-server: $schema=../automated_security_helper/schemas/AshConfig.json
22
project_name: automated-security-helper
33
fail_on_findings: true
4+
ash_plugin_modules:
5+
- automated_security_helper.plugin_modules.ash_aws_plugins
46
global_settings:
57
severity_threshold: MEDIUM
68
ignore_paths:
@@ -22,11 +24,11 @@ build:
2224
reporters:
2325
asff:
2426
enabled: false
25-
# cloudwatch-logs:
26-
# enabled: true
27-
# options:
28-
# aws_region: us-west-2
29-
# log_group_name: !ENV ASH_LOG_GROUP_NAME
27+
cloudwatch-logs:
28+
enabled: true
29+
options:
30+
aws_region: !ENV ${AWS_REGION:None}
31+
log_group_name: !ENV ${ASH_CLOUDWATCH_LOG_GROUP:None}
3032
csv:
3133
enabled: true
3234
cyclonedx:
@@ -70,8 +72,7 @@ scanners:
7072
options:
7173
frameworks:
7274
- all
73-
additional_formats:
74-
- cyclonedx_json
75+
additional_formats: []
7576
skip_path: []
7677
config_file: .ash/.checkov.yaml
7778
detect-secrets:

.vscode/launch.json

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,27 @@
99
},
1010
{
1111
"args": [
12-
"run",
13-
"--source-dir",
14-
"${workspaceFolder}",
15-
"--output-dir",
16-
"${workspaceFolder}/ash_output",
17-
"--config",
18-
"${workspaceFolder}/.ash.yaml",
12+
"scan",
1913
"--verbose",
2014
"--no-progress",
21-
"--no-cleanup"
15+
"--",
16+
"--scanners=\"npm-audit\"",
17+
"--exclude-scanners=\"cdk-nag\""
2218
],
2319
"console": "integratedTerminal",
2420
"env": {
2521
"PATH": "${workspaceFolder}/.venv/bin:${env.PATH}"
2622
},
2723
"name": "ASH: Test CLI",
28-
"program": "${workspaceFolder}/src/automated_security_helper/cli/main.py",
24+
"program": "${workspaceFolder}/.venv/bin/ash",
2925
"request": "launch",
3026
"type": "debugpy"
3127
},
3228
{
3329
"args": [],
3430
"console": "integratedTerminal",
3531
"name": "ASH: Test CDK Nag Headless Wrapper",
36-
"program": "./src/automated_security_helper/utils/cdk_nag_wrapper.py",
32+
"program": "./automated_security_helper/utils/cdk_nag_wrapper.py",
3733
"request": "launch",
3834
"type": "debugpy"
3935
},

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#checkov:skip=CKV_DOCKER_7: Base image is using a non-latest version tag by default, Checkov is unable to parse due to the use of ARG
1+
#checkov:skip=CKV_DOCKER_7:Base image is using a non-latest version tag by default, Checkov is unable to parse due to the use of ARG
22
ARG BASE_IMAGE=public.ecr.aws/docker/library/python:3.10-bullseye
33

44
# First stage: Build poetry requirements

automated_security_helper/base/converter_plugin.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@
22

33
from abc import abstractmethod
44
from pathlib import Path
5-
from typing import Generic, List, TypeVar
5+
from typing import Annotated, Generic, List, TypeVar
66
from typing_extensions import Self
77

8-
from pydantic import model_validator
8+
from pydantic import Field, model_validator
99

10+
from automated_security_helper.base.options import ConverterOptionsBase
1011
from automated_security_helper.base.plugin_base import PluginBase
1112
from automated_security_helper.base.plugin_config import PluginConfigBase
1213
from automated_security_helper.core.exceptions import ScannerError
1314
from automated_security_helper.utils.log import ASH_LOGGER
1415

1516

1617
class ConverterPluginConfigBase(PluginConfigBase):
17-
pass
18+
options: Annotated[ConverterOptionsBase, Field(description="Converter options")] = (
19+
ConverterOptionsBase()
20+
)
1821

1922

2023
T = TypeVar("T", bound=ConverterPluginConfigBase)

automated_security_helper/base/options.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ class BuilderOptionsBase(BaseModel):
1010
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
1111

1212

13-
class ConverterOptionsBase(BaseModel):
14-
"""Base class for converter options."""
13+
class PluginOptionsBase(BaseModel):
14+
"""Base class for plugin options."""
1515

1616
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
1717

1818

19-
class ScannerOptionsBase(BaseModel):
20-
"""Base class for scanner options."""
19+
class ConverterOptionsBase(PluginOptionsBase):
20+
"""Base class for converter options."""
2121

22-
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
22+
23+
class ScannerOptionsBase(PluginOptionsBase):
24+
"""Base class for scanner options."""
2325

2426
severity_threshold: Annotated[
2527
Literal["ALL", "LOW", "MEDIUM", "HIGH", "CRITICAL"] | None,
@@ -29,7 +31,5 @@ class ScannerOptionsBase(BaseModel):
2931
] = None
3032

3133

32-
class ReporterOptionsBase(BaseModel):
34+
class ReporterOptionsBase(PluginOptionsBase):
3335
"""Base class for reporter options."""
34-
35-
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)

automated_security_helper/base/plugin_config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from automated_security_helper.base.options import ScannerOptionsBase
1+
from automated_security_helper.base.options import PluginOptionsBase
22
from pydantic import BaseModel, ConfigDict, Field
33
from typing import Annotated
44

@@ -23,6 +23,6 @@ class PluginConfigBase(BaseModel):
2323
enabled: Annotated[bool, Field(description="Whether the component is enabled")] = (
2424
True
2525
)
26-
options: Annotated[ScannerOptionsBase, Field(description="Scanner options")] = (
27-
ScannerOptionsBase()
26+
options: Annotated[PluginOptionsBase, Field(description="Scanner options")] = (
27+
PluginOptionsBase()
2828
)

automated_security_helper/base/reporter_plugin.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
from abc import abstractmethod
44
from datetime import datetime, timezone
5-
from typing import Generic, TypeVar
5+
from typing import Annotated, Generic, TypeVar
66
from typing_extensions import Self
77

8-
from pydantic import model_validator
8+
from pydantic import Field, model_validator
99

10+
from automated_security_helper.base.options import ReporterOptionsBase
1011
from automated_security_helper.base.plugin_base import PluginBase
1112
from automated_security_helper.base.plugin_config import PluginConfigBase
1213
from automated_security_helper.core.exceptions import ScannerError
@@ -15,6 +16,9 @@
1516

1617

1718
class ReporterPluginConfigBase(PluginConfigBase):
19+
options: Annotated[ReporterOptionsBase, Field(description="Reporter options")] = (
20+
ReporterOptionsBase()
21+
)
1822
extension: str | None = None
1923

2024

automated_security_helper/base/scanner_plugin.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import datetime, timezone
22
import logging
3+
from automated_security_helper.base.options import ScannerOptionsBase
34
from automated_security_helper.base.plugin_base import PluginBase
45
from automated_security_helper.base.plugin_config import PluginConfigBase
56
from automated_security_helper.core.enums import ScannerToolType
@@ -16,7 +17,9 @@
1617

1718

1819
class ScannerPluginConfigBase(PluginConfigBase):
19-
pass
20+
options: Annotated[ScannerOptionsBase, Field(description="Scanner options")] = (
21+
ScannerOptionsBase()
22+
)
2023

2124

2225
T = TypeVar("T", bound=ScannerPluginConfigBase)
@@ -221,6 +224,8 @@ def _post_scan(
221224
options: Optional scanner-specific options
222225
"""
223226
self.end_time = datetime.now(timezone.utc)
227+
if not self.start_time:
228+
self.start_time = self.end_time
224229

225230
# ec_color = "bold green" if self.exit_code == 0 else "bold red"
226231
self._plugin_log(

automated_security_helper/cli/config.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4+
import logging
45
import os
56
from pathlib import Path
67
import sys
@@ -12,6 +13,7 @@
1213
from automated_security_helper.config.resolve_config import resolve_config
1314
from automated_security_helper.core.constants import ASH_CONFIG_FILE_NAMES
1415
from automated_security_helper.core.exceptions import ASHConfigValidationError
16+
from automated_security_helper.utils.log import get_logger
1517

1618
config_app = typer.Typer(
1719
name="config",
@@ -42,7 +44,15 @@ def init(
4244
help="Overwrite the config file if it already exists at the target path.",
4345
),
4446
] = False,
47+
verbose: Annotated[bool, typer.Option(help="Enable verbose logging")] = False,
48+
debug: Annotated[bool, typer.Option(help="Enable debug logging")] = False,
49+
color: Annotated[bool, typer.Option(help="Enable/disable colorized output")] = True,
4550
):
51+
get_logger(
52+
level=(logging.DEBUG if debug else 15 if verbose else logging.INFO),
53+
show_progress=False,
54+
use_color=color,
55+
)
4656
config_path_path = Path(config_path)
4757
if config_path_path.absolute().exists() and not force:
4858
typer.secho(
@@ -73,7 +83,15 @@ def get(
7383
help=f"The name of the config file to get. By default, ASH looks for the following config file names in the source directory of a scan: {ASH_CONFIG_FILE_NAMES}. If a different filename is specified, it must be provided when running ASH via the `--config` option or by setting the `ASH_CONFIG` environment variable.",
7484
),
7585
] = None,
86+
verbose: Annotated[bool, typer.Option(help="Enable verbose logging")] = False,
87+
debug: Annotated[bool, typer.Option(help="Enable debug logging")] = False,
88+
color: Annotated[bool, typer.Option(help="Enable/disable colorized output")] = True,
7689
):
90+
get_logger(
91+
level=(logging.DEBUG if debug else 15 if verbose else logging.INFO),
92+
show_progress=False,
93+
use_color=color,
94+
)
7795
if config_path is not None and not Path(config_path).exists():
7896
typer.secho(f"Config file does not exist at {config_path}", fg=typer.colors.RED)
7997
raise typer.Exit(1)
@@ -100,7 +118,15 @@ def validate(
100118
help=f"The name of the config file to create. By default, ASH looks for the following config file names in the source directory of a scan: {ASH_CONFIG_FILE_NAMES}. If a different filename is specified, it must be provided when running ASH via the `--config` option or by setting the `ASH_CONFIG` environment variable.",
101119
),
102120
] = None,
121+
verbose: Annotated[bool, typer.Option(help="Enable verbose logging")] = False,
122+
debug: Annotated[bool, typer.Option(help="Enable debug logging")] = False,
123+
color: Annotated[bool, typer.Option(help="Enable/disable colorized output")] = True,
103124
):
125+
get_logger(
126+
level=(logging.DEBUG if debug else 15 if verbose else logging.INFO),
127+
show_progress=False,
128+
use_color=color,
129+
)
104130
if config_path is not None and not Path(config_path).exists():
105131
typer.secho(f"Config file does not exist at {config_path}", fg=typer.colors.RED)
106132
raise typer.Exit(1)

automated_security_helper/cli/dependencies.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import platform
88
import sys
99
from pathlib import Path
10-
from typing import List, Optional
10+
from typing import Annotated, List, Optional
1111

1212
import typer
1313
from rich import print
@@ -19,7 +19,7 @@
1919
from automated_security_helper.config.resolve_config import resolve_config
2020
from automated_security_helper.core.constants import ASH_BIN_PATH, ASH_WORK_DIR_NAME
2121
from automated_security_helper.plugins import ash_plugin_manager
22-
from automated_security_helper.utils.log import ASH_LOGGER
22+
from automated_security_helper.utils.log import get_logger
2323

2424
dependencies_app = typer.Typer(
2525
name="dependencies",
@@ -69,16 +69,17 @@ def install_dependencies(
6969
"--bin-path",
7070
"-b",
7171
help="Path to install binaries to.",
72+
envvar="ASH_BIN_PATH",
7273
),
7374
plugin_types: List[str] = typer.Option(
7475
["converter", "scanner", "reporter"],
7576
"--plugin-type",
7677
"-t",
7778
help="Plugin types to install dependencies for",
7879
),
79-
verbose: bool = typer.Option(
80-
False, "--verbose", "-v", help="Enable verbose output"
81-
),
80+
verbose: Annotated[bool, typer.Option(help="Enable verbose logging")] = False,
81+
debug: Annotated[bool, typer.Option(help="Enable debug logging")] = False,
82+
color: Annotated[bool, typer.Option(help="Enable/disable colorized output")] = True,
8283
) -> int:
8384
"""Install dependencies for ASH plugins.
8485
@@ -87,22 +88,20 @@ def install_dependencies(
8788
8889
Binary tools will be installed to the specified bin path (defaults to ~/.ash/bin).
8990
"""
91+
# Set the ASH_BIN_PATH environment variable to override the default
92+
import os
93+
9094
# Set up logging
91-
if verbose:
92-
ASH_LOGGER.setLevel(logging.DEBUG)
93-
handler = logging.StreamHandler()
94-
handler.setLevel(logging.DEBUG)
95-
ASH_LOGGER.addHandler(handler)
95+
get_logger(
96+
level=(logging.DEBUG if debug else 15 if verbose else logging.INFO),
97+
show_progress=False,
98+
use_color=color,
99+
)
96100

97-
# Use provided bin path or default
101+
# Use provided bin path or default if bin_path was null
98102
target_bin_path = bin_path or ASH_BIN_PATH
99-
100-
# Create bin directory if it doesn't exist
103+
# Create target_bin_path directory if it doesn't exist
101104
target_bin_path.mkdir(parents=True, exist_ok=True)
102-
103-
# Set the ASH_BIN_PATH environment variable to override the default
104-
import os
105-
106105
os.environ["ASH_BIN_PATH"] = str(target_bin_path)
107106

108107
console.print(

automated_security_helper/cli/plugin.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import logging
1010
import os
1111
from pathlib import Path
12-
from typing import Annotated
12+
from typing import Annotated, List
1313
import typer
1414
from rich.console import Console
1515
from rich.table import Table
@@ -49,6 +49,13 @@ def list_plugins(
4949
bool,
5050
typer.Option(help="Whether to include the plugin config in the response table"),
5151
] = False,
52+
ash_plugin_modules: Annotated[
53+
List[str],
54+
typer.Option(
55+
help="List of Python modules to import containing ASH plugins and/or event subscribers. These are loaded in addition to the default modules.",
56+
envvar="ASH_PLUGIN_MODULES",
57+
),
58+
] = [],
5259
verbose: Annotated[bool, typer.Option(help="Enable verbose logging")] = False,
5360
debug: Annotated[bool, typer.Option(help="Enable debug logging")] = False,
5461
color: Annotated[bool, typer.Option(help="Enable/disable colorized output")] = True,
@@ -83,10 +90,12 @@ def list_plugins(
8390

8491
try:
8592
console = Console()
93+
ash_config = resolve_config(config_path=config)
94+
ash_config.ash_plugin_modules.extend(ash_plugin_modules)
8695
plugin_context = PluginContext(
8796
source_dir=Path.cwd(),
8897
output_dir=Path.cwd().joinpath(".ash", "ash_output"),
89-
config=resolve_config(config_path=config),
98+
config=ash_config,
9099
)
91100

92101
# Load all plugins

0 commit comments

Comments
 (0)