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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ scanner-cli=src.cli:cli
3
+
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()