plexavo 0.2.0__tar.gz → 0.2.1__tar.gz

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.
Files changed (48) hide show
  1. {plexavo-0.2.0 → plexavo-0.2.1}/PKG-INFO +1 -1
  2. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/__init__.py +1 -1
  3. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/auth.py +9 -1
  4. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/interactive.py +42 -12
  5. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo.egg-info/PKG-INFO +1 -1
  6. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo.egg-info/SOURCES.txt +1 -0
  7. {plexavo-0.2.0 → plexavo-0.2.1}/pyproject.toml +1 -1
  8. plexavo-0.2.1/tests/test_auth_offline.py +91 -0
  9. {plexavo-0.2.0 → plexavo-0.2.1}/LICENSE +0 -0
  10. {plexavo-0.2.0 → plexavo-0.2.1}/README.md +0 -0
  11. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/aws_profile_setup.py +0 -0
  12. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/checks/__init__.py +0 -0
  13. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/checks/encryption.py +0 -0
  14. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/checks/iam.py +0 -0
  15. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/checks/iam_hygiene.py +0 -0
  16. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/checks/logging.py +0 -0
  17. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/checks/network.py +0 -0
  18. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/checks/storage.py +0 -0
  19. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/checks/usage.py +0 -0
  20. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/cli.py +0 -0
  21. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/findings.py +0 -0
  22. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/principals.py +0 -0
  23. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/__init__.py +0 -0
  24. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/ai_narration.py +0 -0
  25. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/fonts/DejaVuSans-Bold.ttf +0 -0
  26. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/fonts/DejaVuSans-BoldOblique.ttf +0 -0
  27. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/fonts/DejaVuSans-Oblique.ttf +0 -0
  28. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/fonts/DejaVuSans.ttf +0 -0
  29. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/fonts/GEIST-FONT-LICENSE.txt +0 -0
  30. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/html_report.py +0 -0
  31. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/pdf.py +0 -0
  32. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/report/templates/report.html.j2 +0 -0
  33. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo/scoring.py +0 -0
  34. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo.egg-info/dependency_links.txt +0 -0
  35. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo.egg-info/entry_points.txt +0 -0
  36. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo.egg-info/requires.txt +0 -0
  37. {plexavo-0.2.0 → plexavo-0.2.1}/plexavo.egg-info/top_level.txt +0 -0
  38. {plexavo-0.2.0 → plexavo-0.2.1}/setup.cfg +0 -0
  39. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_ai_narration_offline.py +0 -0
  40. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_encryption_offline.py +0 -0
  41. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_iam_hygiene_offline.py +0 -0
  42. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_iam_offline.py +0 -0
  43. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_logging_offline.py +0 -0
  44. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_network_offline.py +0 -0
  45. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_report_offline.py +0 -0
  46. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_scoring.py +0 -0
  47. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_storage_offline.py +0 -0
  48. {plexavo-0.2.0 → plexavo-0.2.1}/tests/test_usage_offline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexavo
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Open-source AWS misconfiguration scanner — runs with your own local AWS credentials, nothing sent to anyone else.
5
5
  Author: Kavee
6
6
  License: AGPL-3.0-or-later
@@ -4,4 +4,4 @@ Runs entirely with your own local AWS credentials. Nothing is sent to
4
4
  anyone else. See README.md for usage, or `plexavo scan --help`.
5
5
  """
6
6
 
7
- __version__ = "0.2.0"
7
+ __version__ = "0.2.1"
@@ -11,7 +11,7 @@ your own credentials, and nothing leaves your machine.
11
11
  from __future__ import annotations
12
12
 
13
13
  import boto3
14
- from botocore.exceptions import NoCredentialsError, ProfileNotFound
14
+ from botocore.exceptions import ClientError, NoCredentialsError, ProfileNotFound
15
15
 
16
16
 
17
17
  def get_local_session(profile_name: str | None = None, region: str | None = None) -> boto3.Session:
@@ -50,6 +50,14 @@ def get_local_session(profile_name: str | None = None, region: str | None = None
50
50
  f"{f'--profile {profile_name} ' if profile_name else ''}"
51
51
  "to set them up."
52
52
  ) from e
53
+ except ClientError as e:
54
+ code = e.response.get("Error", {}).get("Code", "Unknown")
55
+ raise RuntimeError(
56
+ f"AWS rejected these credentials ({code}). The access key may "
57
+ "be invalid, deactivated, or deleted — run `aws configure "
58
+ f"{f'--profile {profile_name} ' if profile_name else ''}"
59
+ "` to update them."
60
+ ) from e
53
61
 
54
62
  return boto3.Session(profile_name=profile_name, region_name=resolved_region)
55
63
 
@@ -16,10 +16,10 @@ import os
16
16
 
17
17
  import boto3
18
18
  from rich import box
19
- from rich.align import Align
20
- from rich.console import Console
19
+ from rich.console import Console, Group
21
20
  from rich.panel import Panel
22
21
  from rich.prompt import Confirm, IntPrompt, Prompt
22
+ from rich.table import Table
23
23
  from rich.text import Text
24
24
 
25
25
  from plexavo import __version__
@@ -42,27 +42,57 @@ FEATURES = [
42
42
  "100% local scanning — your AWS credentials never leave this machine",
43
43
  ]
44
44
 
45
- def _splash_panel() -> Panel:
46
- header = Text()
47
- header.append("PLEXAVO", style="bold white")
48
- header.append(" ")
49
- header.append(f"v{__version__}", style="grey62")
45
+ def _gradient_wordmark(word: str, min_shade: int = 90, max_shade: int = 255) -> Text:
46
+ """Letter-spaced word with a symmetric grey -> white -> grey shimmer,
47
+ one shade per letter (a smooth peak at the center, not a linear ramp),
48
+ e.g. "P L E X A V O" fading brightest at the middle letter."""
49
+ text = Text()
50
+ n = len(word)
51
+ for i, ch in enumerate(word):
52
+ t = 1 - abs((i / (n - 1)) * 2 - 1) if n > 1 else 1.0
53
+ shade = round(min_shade + t * (max_shade - min_shade))
54
+ text.append(ch, style=f"bold #{shade:02x}{shade:02x}{shade:02x}")
55
+ if i != n - 1:
56
+ text.append(" ")
57
+ return text
58
+
59
+
60
+ CLOUD_ART = (
61
+ " .--. \n"
62
+ " .-( ). \n"
63
+ " (___.__)__) "
64
+ )
65
+
66
+
67
+ def _header_row() -> Table:
68
+ """Wordmark pinned to the left corner, a small ASCII cloud floating
69
+ in the opposite corner — a quiet logomark, not a centerpiece."""
70
+ wordmark = _gradient_wordmark("PLEXAVO")
71
+ wordmark.append(" ")
72
+ wordmark.append(f"v{__version__}", style="grey62")
73
+
74
+ grid = Table.grid(expand=True)
75
+ grid.add_column(ratio=1)
76
+ grid.add_column(justify="right")
77
+ grid.add_row(wordmark, Text(CLOUD_ART, style="grey50"))
78
+ return grid
79
+
50
80
 
81
+ def _splash_panel() -> Panel:
51
82
  body = Text()
52
- body.append_text(header)
53
- body.append("\n")
54
83
  body.append("AWS Security Reimagined", style="italic grey74")
55
84
  body.append("\n")
56
85
  body.append(WEBSITE, style="underline grey62")
57
86
  body.append("\n\n")
58
- body.append("FEATURES\n", style="bold grey74")
87
+ body.append("─" * 50, style="grey42")
88
+ body.append("\n\n")
59
89
  for i, feature in enumerate(FEATURES):
60
- body.append(f" · {feature}", style="grey66")
90
+ body.append(f" ▸ {feature}", style="grey66")
61
91
  if i != len(FEATURES) - 1:
62
92
  body.append("\n")
63
93
 
64
94
  return Panel(
65
- Align.center(body),
95
+ Group(_header_row(), body),
66
96
  border_style="grey50",
67
97
  box=box.ROUNDED,
68
98
  padding=(1, 4),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexavo
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Open-source AWS misconfiguration scanner — runs with your own local AWS credentials, nothing sent to anyone else.
5
5
  Author: Kavee
6
6
  License: AGPL-3.0-or-later
@@ -34,6 +34,7 @@ plexavo/report/fonts/DejaVuSans.ttf
34
34
  plexavo/report/fonts/GEIST-FONT-LICENSE.txt
35
35
  plexavo/report/templates/report.html.j2
36
36
  tests/test_ai_narration_offline.py
37
+ tests/test_auth_offline.py
37
38
  tests/test_encryption_offline.py
38
39
  tests/test_iam_hygiene_offline.py
39
40
  tests/test_iam_offline.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plexavo"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Open-source AWS misconfiguration scanner — runs with your own local AWS credentials, nothing sent to anyone else."
9
9
  readme = "README.md"
10
10
  license = { text = "AGPL-3.0-or-later" }
@@ -0,0 +1,91 @@
1
+ """Offline tests for auth.py. No real AWS calls — boto3.Session.client is
2
+ monkeypatched to raise the errors AWS itself would raise, to prove
3
+ get_local_session converts them into a clear RuntimeError instead of
4
+ letting them crash the caller.
5
+
6
+ Run: python test_auth_offline.py
7
+ """
8
+
9
+ import boto3
10
+ from botocore.exceptions import ClientError, NoCredentialsError
11
+ from plexavo.auth import get_local_session
12
+
13
+ failures = 0
14
+
15
+
16
+ def assert_true(cond, msg):
17
+ global failures
18
+ status = "PASS" if cond else "FAIL"
19
+ print(f"[{status}] {msg}")
20
+ if not cond:
21
+ failures += 1
22
+
23
+
24
+ class _RaisingClient:
25
+ def __init__(self, exc):
26
+ self._exc = exc
27
+
28
+ def get_caller_identity(self):
29
+ raise self._exc
30
+
31
+
32
+ def _patch_client(monkeypatch_exc):
33
+ original_client = boto3.Session.client
34
+
35
+ def fake_client(self, service_name, *args, **kwargs):
36
+ if service_name == "sts":
37
+ return _RaisingClient(monkeypatch_exc)
38
+ return original_client(self, service_name, *args, **kwargs)
39
+
40
+ return original_client, fake_client
41
+
42
+
43
+ # --- InvalidClientTokenId (deactivated/deleted access key) surfaces as a clear RuntimeError ---
44
+ original_client, fake_client = _patch_client(
45
+ ClientError(
46
+ {"Error": {"Code": "InvalidClientTokenId", "Message": "The security token included in the request is invalid."}},
47
+ "GetCallerIdentity",
48
+ )
49
+ )
50
+ boto3.Session.region_name = property(lambda self: "us-east-1")
51
+ boto3.Session.client = fake_client
52
+ try:
53
+ raised = None
54
+ try:
55
+ # profile_name=None: boto3.Session() itself always succeeds
56
+ # regardless of what's in this machine's real ~/.aws config —
57
+ # only the (monkeypatched) sts call should fail.
58
+ get_local_session(profile_name=None)
59
+ except RuntimeError as e:
60
+ raised = e
61
+ except Exception as e:
62
+ raised = e
63
+
64
+ assert_true(isinstance(raised, RuntimeError), "ClientError (InvalidClientTokenId) is converted to RuntimeError, not left to crash")
65
+ assert_true(raised is not None and "InvalidClientTokenId" in str(raised), "RuntimeError message names the AWS error code")
66
+ finally:
67
+ boto3.Session.client = original_client
68
+
69
+
70
+ # --- NoCredentialsError still produces the pre-existing clear message ---
71
+ original_client, fake_client = _patch_client(NoCredentialsError())
72
+ boto3.Session.client = fake_client
73
+ try:
74
+ raised = None
75
+ try:
76
+ get_local_session(profile_name=None)
77
+ except RuntimeError as e:
78
+ raised = e
79
+ except Exception as e:
80
+ raised = e
81
+
82
+ assert_true(isinstance(raised, RuntimeError), "NoCredentialsError still converts to RuntimeError (regression check)")
83
+ assert_true(raised is not None and "No AWS credentials found" in str(raised), "NoCredentialsError message unchanged")
84
+ finally:
85
+ boto3.Session.client = original_client
86
+ del boto3.Session.region_name
87
+
88
+
89
+ print(f"\n{'ALL PASSED' if failures == 0 else f'{failures} FAILURE(S)'}")
90
+ import sys
91
+ sys.exit(1 if failures else 0)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes