scanner-cli 0.1.0rc3__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.
@@ -0,0 +1,65 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: scanner-cli
|
3
|
+
Version: 0.1.0rc3
|
4
|
+
Summary: Python command-line interface for Scanner API
|
5
|
+
Author: Scanner Inc.
|
6
|
+
Author-email: support@scanner.dev
|
7
|
+
Requires-Python: >=3.10,<4.0
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
12
|
+
Requires-Dist: click (>=8.1.7,<9.0.0)
|
13
|
+
Requires-Dist: scanner-client (>=0.1.0rc3,<0.2.0)
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
|
16
|
+
# scanner-cli
|
17
|
+
|
18
|
+
This is a Python CLI for the Scanner API.
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
To install the CLI, run
|
23
|
+
|
24
|
+
```
|
25
|
+
pip install scanner-cli
|
26
|
+
```
|
27
|
+
|
28
|
+
You will need to provide the API URL of your Scanner instance and an API key. Go
|
29
|
+
to *Settings > API Keys* to find your API URL and API key.
|
30
|
+
|
31
|
+
You can either set these values as environment variables:
|
32
|
+
|
33
|
+
```
|
34
|
+
export SCANNER_API_URL=<your API URL>
|
35
|
+
export SCANNER_API_KEY=<your API key>
|
36
|
+
```
|
37
|
+
|
38
|
+
or provide them as arguments to the CLI:
|
39
|
+
|
40
|
+
```
|
41
|
+
scanner-cli <command> --api-url=<your API url> --api-key=<your API key>
|
42
|
+
```
|
43
|
+
|
44
|
+
### Commands
|
45
|
+
|
46
|
+
Available commands are
|
47
|
+
- `run-tests` - run tests on detection rules as code
|
48
|
+
- `validate` - validate detection rules as code
|
49
|
+
|
50
|
+
To validate or run tests on files
|
51
|
+
|
52
|
+
```
|
53
|
+
scanner-cli <command> -f detections/src/errors.yaml -f detections/src/unauthorized_logins.yaml
|
54
|
+
```
|
55
|
+
|
56
|
+
To validate or run tests on directories
|
57
|
+
|
58
|
+
```
|
59
|
+
scanner-cli <command> -d detections/src
|
60
|
+
```
|
61
|
+
|
62
|
+
This will validate or run tests on all YAML files in the directory that have the correct schema header.
|
63
|
+
|
64
|
+
A file or directory must be provided. Multiple files and/or directories can be provided.
|
65
|
+
|
@@ -0,0 +1,6 @@
|
|
1
|
+
src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
src/cli.py,sha256=Hv5WA0LfmqgkT-9BRqcyUz2h8uTPEeeLkiPl3iy7saw,4398
|
3
|
+
scanner_cli-0.1.0rc3.dist-info/METADATA,sha256=w4zNdQEMF6cJHXEi4bZvVqUNmgAp8O0e5o9mzKxg6x4,1624
|
4
|
+
scanner_cli-0.1.0rc3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
5
|
+
scanner_cli-0.1.0rc3.dist-info/entry_points.txt,sha256=vqXMrIG6N6pY66bNf0y-gbUxbU8v5dXvuL3mV832Fh8,43
|
6
|
+
scanner_cli-0.1.0rc3.dist-info/RECORD,,
|
src/__init__.py
ADDED
File without changes
|
src/cli.py
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
""" Contains code for Python CLI """
|
2
|
+
|
3
|
+
import os
|
4
|
+
from typing import Any, Callable
|
5
|
+
|
6
|
+
import click
|
7
|
+
|
8
|
+
from scanner_client import Scanner
|
9
|
+
from scanner_client.detection_rule_yaml import validate_and_read_file
|
10
|
+
|
11
|
+
_CLICK_OPTIONS = [
|
12
|
+
click.option(
|
13
|
+
"--api-url",
|
14
|
+
envvar="SCANNER_API_URL",
|
15
|
+
help="The API URL of your Scanner instance. Go to Settings > API Keys in Scanner to find your API URL.",
|
16
|
+
),
|
17
|
+
click.option(
|
18
|
+
"--api-key",
|
19
|
+
envvar="SCANNER_API_KEY",
|
20
|
+
help="Scanner API key. Go to Settings > API Keys in Scanner to find your API keys or to create a new API key.",
|
21
|
+
),
|
22
|
+
click.option(
|
23
|
+
"-f",
|
24
|
+
"--file",
|
25
|
+
"file_paths",
|
26
|
+
help="File to validate. This must be .yml or .yaml file with the correct schema header.",
|
27
|
+
multiple=True,
|
28
|
+
),
|
29
|
+
click.option(
|
30
|
+
"-d",
|
31
|
+
"--dir",
|
32
|
+
"directories",
|
33
|
+
help="Directory to validate. Only .yml or .yaml files with the correct schema header will be validated.",
|
34
|
+
multiple=True,
|
35
|
+
),
|
36
|
+
]
|
37
|
+
|
38
|
+
|
39
|
+
def _click_options(func) -> Callable[..., Any]:
|
40
|
+
for option in reversed(_CLICK_OPTIONS):
|
41
|
+
func = option(func)
|
42
|
+
|
43
|
+
return func
|
44
|
+
|
45
|
+
|
46
|
+
def _is_valid_file(file_path: str) -> bool:
|
47
|
+
try:
|
48
|
+
validate_and_read_file(file_path)
|
49
|
+
return True
|
50
|
+
except:
|
51
|
+
return False
|
52
|
+
|
53
|
+
|
54
|
+
def _get_valid_files_in_directory(directory: str) -> list[str]:
|
55
|
+
if not os.path.exists(directory):
|
56
|
+
raise click.exceptions.ClickException(
|
57
|
+
message=(
|
58
|
+
f"Directory {directory} not found."
|
59
|
+
)
|
60
|
+
)
|
61
|
+
|
62
|
+
return [f"{directory}/{f}" for f in os.listdir(directory) if _is_valid_file(f"{directory}/{f}")]
|
63
|
+
|
64
|
+
|
65
|
+
def _get_valid_files(file_paths: str, directories: str) -> list[str]:
|
66
|
+
files = [f for f in file_paths if _is_valid_file(f)]
|
67
|
+
|
68
|
+
for d in directories:
|
69
|
+
files.extend(_get_valid_files_in_directory(d))
|
70
|
+
|
71
|
+
return files
|
72
|
+
|
73
|
+
|
74
|
+
def _validate_shared_options(api_url: str, api_key: str, file_paths: str, directories: str) -> None:
|
75
|
+
if api_url is None:
|
76
|
+
raise click.exceptions.UsageError(
|
77
|
+
message=(
|
78
|
+
"Pass --api-url option or set `SCANNER_API_URL` environment variable."
|
79
|
+
)
|
80
|
+
)
|
81
|
+
|
82
|
+
if api_key is None:
|
83
|
+
raise click.exceptions.UsageError(
|
84
|
+
message=(
|
85
|
+
"Pass --api-key option or set `SCANNER_API_KEY` environment variable."
|
86
|
+
)
|
87
|
+
)
|
88
|
+
|
89
|
+
if not file_paths and not directories:
|
90
|
+
raise click.exceptions.UsageError(
|
91
|
+
message=(
|
92
|
+
"Either --file or --dir must be provided."
|
93
|
+
)
|
94
|
+
)
|
95
|
+
|
96
|
+
|
97
|
+
@click.group()
|
98
|
+
def cli():
|
99
|
+
""" Python CLI for Scanner API """
|
100
|
+
|
101
|
+
|
102
|
+
@cli.command()
|
103
|
+
@_click_options
|
104
|
+
def validate(api_url: str, api_key: str, file_paths: str, directories: str):
|
105
|
+
""" Validate detection rules """
|
106
|
+
_validate_shared_options(api_url, api_key, file_paths, directories)
|
107
|
+
|
108
|
+
scanner_client = Scanner(api_url, api_key)
|
109
|
+
|
110
|
+
files = _get_valid_files(file_paths, directories)
|
111
|
+
click.echo(f'Validating {len(files)} {"file" if len(files) == 1 else "files"}')
|
112
|
+
|
113
|
+
for file in files:
|
114
|
+
result = scanner_client.detection_rule_yaml.validate(file)
|
115
|
+
|
116
|
+
if result.is_valid:
|
117
|
+
click.echo(f"{file}: " + click.style("Valid", fg="green"))
|
118
|
+
else:
|
119
|
+
click.echo(f"{file}: " + click.style(f"{result.error}", fg="red"))
|
120
|
+
|
121
|
+
|
122
|
+
@cli.command()
|
123
|
+
@_click_options
|
124
|
+
def run_tests(api_url: str, api_key: str, file_paths: str, directories: str):
|
125
|
+
""" Run detection rule tests """
|
126
|
+
_validate_shared_options(api_url, api_key, file_paths, directories)
|
127
|
+
|
128
|
+
scanner_client = Scanner(api_url, api_key)
|
129
|
+
|
130
|
+
files = _get_valid_files(file_paths, directories)
|
131
|
+
click.echo(f'Running tests on {len(files)} {"file" if len(files) == 1 else "files"}')
|
132
|
+
|
133
|
+
for file in files:
|
134
|
+
response = scanner_client.detection_rule_yaml.run_tests(file)
|
135
|
+
results = response.results.to_dict()
|
136
|
+
|
137
|
+
click.secho(f"{file}", bold=True)
|
138
|
+
if len(results) == 0:
|
139
|
+
click.secho("No tests found", fg="yellow")
|
140
|
+
else:
|
141
|
+
for name, status in response.results.to_dict().items():
|
142
|
+
if status == "Passed":
|
143
|
+
click.echo(f"{name}: " + click.style("Passed", fg="green"))
|
144
|
+
else:
|
145
|
+
click.echo(f"{name}: " + click.style("Failed", fg="red"))
|
146
|
+
|
147
|
+
click.echo("")
|
148
|
+
|
149
|
+
|
150
|
+
if __name__ == "__main__":
|
151
|
+
cli()
|