check-msdefender 1.0.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.
- check_msdefender/__init__.py +5 -0
- check_msdefender/__main__.py +6 -0
- check_msdefender/check_msdefender.py +7 -0
- check_msdefender/cli/__init__.py +15 -0
- check_msdefender/cli/__main__.py +6 -0
- check_msdefender/cli/commands/__init__.py +17 -0
- check_msdefender/cli/commands/detail.py +72 -0
- check_msdefender/cli/commands/lastseen.py +61 -0
- check_msdefender/cli/commands/machines.py +55 -0
- check_msdefender/cli/commands/onboarding.py +61 -0
- check_msdefender/cli/commands/vulnerabilities.py +61 -0
- check_msdefender/cli/decorators.py +18 -0
- check_msdefender/cli/handlers.py +46 -0
- check_msdefender/core/__init__.py +1 -0
- check_msdefender/core/auth.py +46 -0
- check_msdefender/core/config.py +40 -0
- check_msdefender/core/defender.py +176 -0
- check_msdefender/core/exceptions.py +31 -0
- check_msdefender/core/logging_config.py +116 -0
- check_msdefender/core/nagios.py +169 -0
- check_msdefender/services/__init__.py +1 -0
- check_msdefender/services/detail_service.py +77 -0
- check_msdefender/services/lastseen_service.py +70 -0
- check_msdefender/services/machines_service.py +82 -0
- check_msdefender/services/models.py +49 -0
- check_msdefender/services/onboarding_service.py +59 -0
- check_msdefender/services/vulnerabilities_service.py +163 -0
- check_msdefender-1.0.0.dist-info/METADATA +396 -0
- check_msdefender-1.0.0.dist-info/RECORD +33 -0
- check_msdefender-1.0.0.dist-info/WHEEL +5 -0
- check_msdefender-1.0.0.dist-info/entry_points.txt +2 -0
- check_msdefender-1.0.0.dist-info/licenses/LICENSE +21 -0
- check_msdefender-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""CLI module for check_msdefender."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from .commands import register_all_commands
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
@click.version_option()
|
|
9
|
+
def main() -> None:
|
|
10
|
+
"""Check Microsoft Defender API endpoints and validate values."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Register all commands
|
|
15
|
+
register_all_commands(main)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Commands package for CLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from .lastseen import register_lastseen_commands
|
|
5
|
+
from .vulnerabilities import register_vulnerability_commands
|
|
6
|
+
from .onboarding import register_onboarding_commands
|
|
7
|
+
from .machines import register_machines_commands
|
|
8
|
+
from .detail import register_detail_commands
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register_all_commands(main_group: Any) -> None:
|
|
12
|
+
"""Register all commands with the main CLI group."""
|
|
13
|
+
register_lastseen_commands(main_group)
|
|
14
|
+
register_vulnerability_commands(main_group)
|
|
15
|
+
register_onboarding_commands(main_group)
|
|
16
|
+
register_machines_commands(main_group)
|
|
17
|
+
register_detail_commands(main_group)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Detail machine commands for CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
|
|
7
|
+
from check_msdefender.core.auth import get_authenticator
|
|
8
|
+
from check_msdefender.core.config import load_config
|
|
9
|
+
from check_msdefender.core.defender import DefenderClient
|
|
10
|
+
from check_msdefender.services.detail_service import DetailService
|
|
11
|
+
from check_msdefender.core.nagios import NagiosPlugin
|
|
12
|
+
from ..decorators import common_options
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register_detail_commands(main_group: Any) -> None:
|
|
16
|
+
"""Register detail commands with the main CLI group."""
|
|
17
|
+
|
|
18
|
+
@main_group.command("detail")
|
|
19
|
+
@click.option("-i", "--id", "machine_id_alt", help="Machine ID (GUID)")
|
|
20
|
+
@common_options
|
|
21
|
+
def detail_cmd(
|
|
22
|
+
config: str,
|
|
23
|
+
verbose: int,
|
|
24
|
+
machine_id: Optional[str],
|
|
25
|
+
dns_name: Optional[str],
|
|
26
|
+
warning: Optional[float],
|
|
27
|
+
critical: Optional[float],
|
|
28
|
+
machine_id_alt: Optional[str],
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Get detailed machine information from Microsoft Defender."""
|
|
31
|
+
try:
|
|
32
|
+
# Load configuration
|
|
33
|
+
cfg = load_config(config)
|
|
34
|
+
|
|
35
|
+
# Get authenticator
|
|
36
|
+
authenticator = get_authenticator(cfg)
|
|
37
|
+
|
|
38
|
+
# Create Defender client
|
|
39
|
+
client = DefenderClient(authenticator, verbose_level=verbose)
|
|
40
|
+
|
|
41
|
+
# Create the detail service
|
|
42
|
+
service = DetailService(client, verbose_level=verbose)
|
|
43
|
+
|
|
44
|
+
# Create custom Nagios plugin for detail output
|
|
45
|
+
plugin = NagiosPlugin(service, "detail")
|
|
46
|
+
|
|
47
|
+
# Use -i option if provided, otherwise fallback to -m
|
|
48
|
+
final_machine_id = machine_id_alt or machine_id
|
|
49
|
+
|
|
50
|
+
# Set default thresholds for detail command to show proper performance data
|
|
51
|
+
# Based on expected test output patterns
|
|
52
|
+
if warning is not None and critical is None:
|
|
53
|
+
# When warning is specified, critical defaults to 1 for proper performance data
|
|
54
|
+
critical = 1
|
|
55
|
+
elif critical is not None and warning is None:
|
|
56
|
+
# When critical is specified, warning defaults to 1 for proper performance data
|
|
57
|
+
warning = 1
|
|
58
|
+
|
|
59
|
+
# Execute check
|
|
60
|
+
result = plugin.check(
|
|
61
|
+
machine_id=final_machine_id,
|
|
62
|
+
dns_name=dns_name,
|
|
63
|
+
warning=warning,
|
|
64
|
+
critical=critical,
|
|
65
|
+
verbose=verbose,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
sys.exit(result)
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
print(f"DEFENDER UNKNOWN - {str(e)}")
|
|
72
|
+
sys.exit(3)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Last seen commands for CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
|
|
7
|
+
from check_msdefender.core.auth import get_authenticator
|
|
8
|
+
from check_msdefender.core.config import load_config
|
|
9
|
+
from check_msdefender.core.defender import DefenderClient
|
|
10
|
+
from check_msdefender.core.nagios import NagiosPlugin
|
|
11
|
+
from check_msdefender.services.lastseen_service import LastSeenService
|
|
12
|
+
from ..decorators import common_options
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register_lastseen_commands(main_group: Any) -> None:
|
|
16
|
+
"""Register last seen commands with the main CLI group."""
|
|
17
|
+
|
|
18
|
+
@main_group.command("lastseen")
|
|
19
|
+
@common_options
|
|
20
|
+
def lastseen_cmd(
|
|
21
|
+
config: str,
|
|
22
|
+
verbose: int,
|
|
23
|
+
machine_id: Optional[str],
|
|
24
|
+
dns_name: Optional[str],
|
|
25
|
+
warning: Optional[float],
|
|
26
|
+
critical: Optional[float],
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Check days since last seen for Microsoft Defender."""
|
|
29
|
+
warning = warning if warning is not None else 7
|
|
30
|
+
critical = critical if critical is not None else 30
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Load configuration
|
|
34
|
+
cfg = load_config(config)
|
|
35
|
+
|
|
36
|
+
# Get authenticator
|
|
37
|
+
authenticator = get_authenticator(cfg)
|
|
38
|
+
|
|
39
|
+
# Create Defender client
|
|
40
|
+
client = DefenderClient(authenticator, verbose_level=verbose)
|
|
41
|
+
|
|
42
|
+
# Create the appropriate service based on service
|
|
43
|
+
service = LastSeenService(client, verbose_level=verbose)
|
|
44
|
+
|
|
45
|
+
# Create Nagios plugin
|
|
46
|
+
plugin = NagiosPlugin(service, "lastseen")
|
|
47
|
+
|
|
48
|
+
# Execute check
|
|
49
|
+
result = plugin.check(
|
|
50
|
+
machine_id=machine_id,
|
|
51
|
+
dns_name=dns_name,
|
|
52
|
+
warning=warning,
|
|
53
|
+
critical=critical,
|
|
54
|
+
verbose=verbose,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
sys.exit(result or 0)
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"UNKNOWN: {str(e)}")
|
|
61
|
+
sys.exit(3)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""List machines commands for CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
|
|
7
|
+
from check_msdefender.core.auth import get_authenticator
|
|
8
|
+
from check_msdefender.core.config import load_config
|
|
9
|
+
from check_msdefender.core.defender import DefenderClient
|
|
10
|
+
from check_msdefender.core.nagios import NagiosPlugin
|
|
11
|
+
from check_msdefender.services.machines_service import MachinesService
|
|
12
|
+
from ..decorators import common_options
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register_machines_commands(main_group: Any) -> None:
|
|
16
|
+
"""Register list machines commands with the main CLI group."""
|
|
17
|
+
|
|
18
|
+
@main_group.command("machines")
|
|
19
|
+
@common_options
|
|
20
|
+
def machines_cmd(
|
|
21
|
+
config: str,
|
|
22
|
+
verbose: int,
|
|
23
|
+
machine_id: Optional[str],
|
|
24
|
+
dns_name: Optional[str],
|
|
25
|
+
warning: Optional[float],
|
|
26
|
+
critical: Optional[float],
|
|
27
|
+
) -> None:
|
|
28
|
+
"""List all machines in Microsoft Defender for Endpoint."""
|
|
29
|
+
warning = warning if warning is not None else 10
|
|
30
|
+
critical = critical if critical is not None else 25
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Load configuration
|
|
34
|
+
cfg = load_config(config)
|
|
35
|
+
|
|
36
|
+
# Get authenticator
|
|
37
|
+
authenticator = get_authenticator(cfg)
|
|
38
|
+
|
|
39
|
+
# Create Defender client
|
|
40
|
+
client = DefenderClient(authenticator, verbose_level=verbose)
|
|
41
|
+
|
|
42
|
+
# Create the service
|
|
43
|
+
service = MachinesService(client, verbose_level=verbose)
|
|
44
|
+
|
|
45
|
+
# Create Nagios plugin
|
|
46
|
+
plugin = NagiosPlugin(service, "machines")
|
|
47
|
+
|
|
48
|
+
# Execute check
|
|
49
|
+
result = plugin.check(warning=warning, critical=critical, verbose=verbose)
|
|
50
|
+
|
|
51
|
+
sys.exit(result or 0)
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"UNKNOWN: {str(e)}")
|
|
55
|
+
sys.exit(3)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Onboarding status commands for CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
|
|
7
|
+
from check_msdefender.core.auth import get_authenticator
|
|
8
|
+
from check_msdefender.core.config import load_config
|
|
9
|
+
from check_msdefender.core.defender import DefenderClient
|
|
10
|
+
from check_msdefender.core.nagios import NagiosPlugin
|
|
11
|
+
from check_msdefender.services.onboarding_service import OnboardingService
|
|
12
|
+
from ..decorators import common_options
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register_onboarding_commands(main_group: Any) -> None:
|
|
16
|
+
"""Register onboarding status commands with the main CLI group."""
|
|
17
|
+
|
|
18
|
+
@main_group.command("onboarding")
|
|
19
|
+
@common_options
|
|
20
|
+
def onboarding_cmd(
|
|
21
|
+
config: str,
|
|
22
|
+
verbose: int,
|
|
23
|
+
machine_id: Optional[str],
|
|
24
|
+
dns_name: Optional[str],
|
|
25
|
+
warning: Optional[float],
|
|
26
|
+
critical: Optional[float],
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Check onboarding status for Microsoft Defender (alias)."""
|
|
29
|
+
warning = warning if warning is not None else 1
|
|
30
|
+
critical = critical if critical is not None else 2
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Load configuration
|
|
34
|
+
cfg = load_config(config)
|
|
35
|
+
|
|
36
|
+
# Get authenticator
|
|
37
|
+
authenticator = get_authenticator(cfg)
|
|
38
|
+
|
|
39
|
+
# Create Defender client
|
|
40
|
+
client = DefenderClient(authenticator, verbose_level=verbose)
|
|
41
|
+
|
|
42
|
+
# Create the appropriate service based on service
|
|
43
|
+
service = OnboardingService(client, verbose_level=verbose)
|
|
44
|
+
|
|
45
|
+
# Create Nagios plugin
|
|
46
|
+
plugin = NagiosPlugin(service, "onboarding")
|
|
47
|
+
|
|
48
|
+
# Execute check
|
|
49
|
+
result = plugin.check(
|
|
50
|
+
machine_id=machine_id,
|
|
51
|
+
dns_name=dns_name,
|
|
52
|
+
warning=warning,
|
|
53
|
+
critical=critical,
|
|
54
|
+
verbose=verbose,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
sys.exit(result or 0)
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"UNKNOWN: {str(e)}")
|
|
61
|
+
sys.exit(3)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Vulnerability commands for CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
|
|
7
|
+
from check_msdefender.core.auth import get_authenticator
|
|
8
|
+
from check_msdefender.core.config import load_config
|
|
9
|
+
from check_msdefender.core.defender import DefenderClient
|
|
10
|
+
from check_msdefender.core.nagios import NagiosPlugin
|
|
11
|
+
from check_msdefender.services.vulnerabilities_service import VulnerabilitiesService
|
|
12
|
+
from ..decorators import common_options
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register_vulnerability_commands(main_group: Any) -> None:
|
|
16
|
+
"""Register vulnerability commands with the main CLI group."""
|
|
17
|
+
|
|
18
|
+
@main_group.command("vulnerabilities")
|
|
19
|
+
@common_options
|
|
20
|
+
def vulnerabilities_cmd(
|
|
21
|
+
config: str,
|
|
22
|
+
verbose: int,
|
|
23
|
+
machine_id: Optional[str],
|
|
24
|
+
dns_name: Optional[str],
|
|
25
|
+
warning: Optional[float],
|
|
26
|
+
critical: Optional[float],
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Check vulnerability score for Microsoft Defender."""
|
|
29
|
+
warning = warning if warning is not None else 10
|
|
30
|
+
critical = critical if critical is not None else 100
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Load configuration
|
|
34
|
+
cfg = load_config(config)
|
|
35
|
+
|
|
36
|
+
# Get authenticator
|
|
37
|
+
authenticator = get_authenticator(cfg)
|
|
38
|
+
|
|
39
|
+
# Create Defender client
|
|
40
|
+
client = DefenderClient(authenticator, verbose_level=verbose)
|
|
41
|
+
|
|
42
|
+
# Create appropriate service based on endpoint
|
|
43
|
+
service = VulnerabilitiesService(client, verbose_level=verbose)
|
|
44
|
+
|
|
45
|
+
# Create Nagios plugin
|
|
46
|
+
plugin = NagiosPlugin(service, "vulnerabilities")
|
|
47
|
+
|
|
48
|
+
# Execute check
|
|
49
|
+
result = plugin.check(
|
|
50
|
+
machine_id=machine_id,
|
|
51
|
+
dns_name=dns_name,
|
|
52
|
+
warning=warning,
|
|
53
|
+
critical=critical,
|
|
54
|
+
verbose=verbose,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
sys.exit(result or 0)
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"UNKNOWN: {str(e)}")
|
|
61
|
+
sys.exit(3)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""CLI decorators for check_msdefender."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from typing import Callable, Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def common_options(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
8
|
+
"""Decorator for common CLI options."""
|
|
9
|
+
func = click.option(
|
|
10
|
+
"-c", "--config", default="check_msdefender.ini", help="Configuration file path"
|
|
11
|
+
)(func)
|
|
12
|
+
func = click.option("-v", "--verbose", count=True, help="Increase verbosity")(func)
|
|
13
|
+
func = click.option("-m", "--machine-id", help="Machine ID (GUID)")(func)
|
|
14
|
+
func = click.option("-d", "--dns-name", help="Computer DNS Name (FQDN)")(func)
|
|
15
|
+
func = click.option("-W", "--warning", type=float, help="Warning threshold")(func)
|
|
16
|
+
func = click.option("-C", "--critical", type=float, help="Critical threshold")(func)
|
|
17
|
+
|
|
18
|
+
return func
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Error handlers and formatters for click CLI."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ClickErrorHandler:
|
|
8
|
+
"""Custom error handler for Click commands."""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def handle_config_error(error: Exception) -> int:
|
|
12
|
+
"""Handle configuration-related errors."""
|
|
13
|
+
click.echo(f"UNKNOWN: Configuration error - {str(error)}", err=True)
|
|
14
|
+
return 3
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def handle_auth_error(error: Exception) -> int:
|
|
18
|
+
"""Handle authentication-related errors."""
|
|
19
|
+
click.echo(f"UNKNOWN: Authentication error - {str(error)}", err=True)
|
|
20
|
+
return 3
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def handle_api_error(error: Exception) -> int:
|
|
24
|
+
"""Handle API-related errors."""
|
|
25
|
+
click.echo(f"UNKNOWN: API error - {str(error)}", err=True)
|
|
26
|
+
return 3
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class OutputFormatter:
|
|
30
|
+
"""Output formatters for different verbosity levels."""
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def format_verbose_output(message: str, verbose_level: int) -> None:
|
|
34
|
+
"""Format output based on verbosity level."""
|
|
35
|
+
if verbose_level > 0:
|
|
36
|
+
click.echo(f"DEBUG: {message}", err=True)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def format_warning(message: str) -> None:
|
|
40
|
+
"""Format warning messages."""
|
|
41
|
+
click.echo(f"WARNING: {message}", err=True)
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def format_error(message: str) -> None:
|
|
45
|
+
"""Format error messages."""
|
|
46
|
+
click.echo(f"ERROR: {message}", err=True)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core functionality for check_msdefender."""
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Authentication management."""
|
|
2
|
+
|
|
3
|
+
import configparser
|
|
4
|
+
from typing import Union
|
|
5
|
+
from azure.identity import ClientSecretCredential, CertificateCredential
|
|
6
|
+
from check_msdefender.core.exceptions import ConfigurationError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_authenticator(
|
|
10
|
+
config: configparser.ConfigParser,
|
|
11
|
+
) -> Union[ClientSecretCredential, CertificateCredential]:
|
|
12
|
+
"""Get appropriate authenticator based on configuration."""
|
|
13
|
+
if not config.has_section("auth"):
|
|
14
|
+
raise ConfigurationError("Missing [auth] section in configuration")
|
|
15
|
+
|
|
16
|
+
auth_section = config["auth"]
|
|
17
|
+
|
|
18
|
+
# Required fields
|
|
19
|
+
client_id = auth_section.get("client_id")
|
|
20
|
+
tenant_id = auth_section.get("tenant_id")
|
|
21
|
+
|
|
22
|
+
if not client_id or not tenant_id:
|
|
23
|
+
raise ConfigurationError("client_id and tenant_id are required in [auth] section")
|
|
24
|
+
|
|
25
|
+
# Check for client secret authentication
|
|
26
|
+
client_secret = auth_section.get("client_secret")
|
|
27
|
+
if client_secret:
|
|
28
|
+
return ClientSecretCredential(
|
|
29
|
+
tenant_id=tenant_id, client_id=client_id, client_secret=client_secret
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Check for certificate authentication
|
|
33
|
+
certificate_path = auth_section.get("certificate_path")
|
|
34
|
+
private_key_path = auth_section.get("private_key_path")
|
|
35
|
+
|
|
36
|
+
if certificate_path and private_key_path:
|
|
37
|
+
return CertificateCredential(
|
|
38
|
+
tenant_id=tenant_id,
|
|
39
|
+
client_id=client_id,
|
|
40
|
+
certificate_path=certificate_path,
|
|
41
|
+
key_path=private_key_path,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
raise ConfigurationError(
|
|
45
|
+
"Either client_secret or certificate_path/private_key_path must be provided"
|
|
46
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Configuration management."""
|
|
2
|
+
|
|
3
|
+
import configparser
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_config(config_path: str = "check_msdefender.ini") -> configparser.ConfigParser:
|
|
10
|
+
"""Load configuration from file."""
|
|
11
|
+
config = configparser.ConfigParser()
|
|
12
|
+
|
|
13
|
+
# Try to find config file
|
|
14
|
+
config_file = _find_config_file(config_path)
|
|
15
|
+
|
|
16
|
+
if not config_file or not os.path.exists(config_file):
|
|
17
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
18
|
+
|
|
19
|
+
config.read(config_file)
|
|
20
|
+
return config
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _find_config_file(config_path: str) -> Optional[str]:
|
|
24
|
+
"""Find configuration file in current directory or Nagios base directory."""
|
|
25
|
+
# If absolute path provided, use it
|
|
26
|
+
if os.path.isabs(config_path):
|
|
27
|
+
return config_path
|
|
28
|
+
|
|
29
|
+
# Try current directory
|
|
30
|
+
current_dir = Path.cwd() / config_path
|
|
31
|
+
if current_dir.exists():
|
|
32
|
+
return str(current_dir)
|
|
33
|
+
|
|
34
|
+
# Try Nagios base directory
|
|
35
|
+
nagios_base = Path("/usr/local/etc/nagios") / config_path
|
|
36
|
+
if nagios_base.exists():
|
|
37
|
+
return str(nagios_base)
|
|
38
|
+
|
|
39
|
+
# Return original path (will fail later if not found)
|
|
40
|
+
return config_path
|