certifyclouds 1.2.0__tar.gz → 1.2.2__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.
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/PKG-INFO +12 -9
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/README.md +11 -8
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/pyproject.toml +1 -1
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/__init__.py +1 -1
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/auth.py +7 -1
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/cli.py +24 -3
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/registration.py +5 -9
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/scanner.py +22 -17
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/.gitignore +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/LICENSE +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/config.py +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/filters.py +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/formatters/__init__.py +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/formatters/csv_fmt.py +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/formatters/html.py +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/formatters/json_fmt.py +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/formatters/table.py +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/security.py +0 -0
- {certifyclouds-1.2.0 → certifyclouds-1.2.2}/src/cc_scanner/thumbprint.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: certifyclouds
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Scan Azure Key Vaults for expiring secrets, certificates, and keys
|
|
5
5
|
Project-URL: Homepage, https://certifyclouds.com/scanner
|
|
6
6
|
Project-URL: Documentation, https://docs.certifyclouds.com/scanner
|
|
@@ -40,15 +40,15 @@ Requires-Dist: pytest-mock>=3.10; extra == 'dev'
|
|
|
40
40
|
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
42
|
|
|
43
|
-
#
|
|
43
|
+
# certifyclouds
|
|
44
44
|
|
|
45
45
|
**Do you know which Azure secrets expire this month?**
|
|
46
46
|
|
|
47
47
|
Scan all your Azure Key Vaults across every subscription in 30 seconds. Free, open source, works in Azure Cloud Shell.
|
|
48
48
|
|
|
49
49
|
```
|
|
50
|
-
pip install
|
|
51
|
-
cc-scan
|
|
50
|
+
pip install certifyclouds # or: pipx install certifyclouds
|
|
51
|
+
cc-scan # or: brew tap certifyclouds/certifyclouds && brew install certifyclouds
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
## What you get
|
|
@@ -62,8 +62,11 @@ cc-scan
|
|
|
62
62
|
## Quick Start
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
# 1. Install
|
|
66
|
-
pip install
|
|
65
|
+
# 1. Install (pick one)
|
|
66
|
+
pip install certifyclouds # pip
|
|
67
|
+
pipx install certifyclouds # pipx (isolated install)
|
|
68
|
+
brew tap certifyclouds/certifyclouds # Homebrew (macOS/Linux)
|
|
69
|
+
brew install certifyclouds
|
|
67
70
|
|
|
68
71
|
# 2. Authenticate (skip in Azure Cloud Shell)
|
|
69
72
|
az login
|
|
@@ -77,7 +80,7 @@ On first run, you'll be prompted to register with your email (free, takes 10 sec
|
|
|
77
80
|
## Output
|
|
78
81
|
|
|
79
82
|
```
|
|
80
|
-
CertifyClouds Scanner v1.2.
|
|
83
|
+
CertifyClouds Scanner v1.2.2
|
|
81
84
|
Authenticated as: you@company.com
|
|
82
85
|
|
|
83
86
|
Scanning Azure subscriptions...
|
|
@@ -170,7 +173,7 @@ AZURE_TENANT_ID=...
|
|
|
170
173
|
| 0 | Scan completed, no critical/high findings |
|
|
171
174
|
| 1 | Scan completed, critical or high findings detected |
|
|
172
175
|
| 2 | Scan failed (auth error, network error, timeout) |
|
|
173
|
-
| 3 | Registration required
|
|
176
|
+
| 3 | Registration required (no key found or registration failed) |
|
|
174
177
|
|
|
175
178
|
Non-zero exit codes make this CI/cron-friendly: `cc-scan || notify-team`.
|
|
176
179
|
|
|
@@ -215,7 +218,7 @@ CertifyClouds Scanner sends **aggregate usage statistics** to improve the produc
|
|
|
215
218
|
|
|
216
219
|
**Never sent:** Vault names or URIs, secret/certificate/key names, secret values, subscription IDs or names, Azure tenant IDs, resource group names, tags, certificate subjects or thumbprints.
|
|
217
220
|
|
|
218
|
-
Use `--offline` to
|
|
221
|
+
Use `--offline` to skip license server validation calls (key telemetry and version checks). Azure SDK calls are unaffected — the scanner still requires Azure connectivity to read Key Vault data.
|
|
219
222
|
|
|
220
223
|
Source code is open and auditable: [codeberg.org/hus/cc/scanner](https://codeberg.org/hus/cc)
|
|
221
224
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# certifyclouds
|
|
2
2
|
|
|
3
3
|
**Do you know which Azure secrets expire this month?**
|
|
4
4
|
|
|
5
5
|
Scan all your Azure Key Vaults across every subscription in 30 seconds. Free, open source, works in Azure Cloud Shell.
|
|
6
6
|
|
|
7
7
|
```
|
|
8
|
-
pip install
|
|
9
|
-
cc-scan
|
|
8
|
+
pip install certifyclouds # or: pipx install certifyclouds
|
|
9
|
+
cc-scan # or: brew tap certifyclouds/certifyclouds && brew install certifyclouds
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
## What you get
|
|
@@ -20,8 +20,11 @@ cc-scan
|
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
# 1. Install
|
|
24
|
-
pip install
|
|
23
|
+
# 1. Install (pick one)
|
|
24
|
+
pip install certifyclouds # pip
|
|
25
|
+
pipx install certifyclouds # pipx (isolated install)
|
|
26
|
+
brew tap certifyclouds/certifyclouds # Homebrew (macOS/Linux)
|
|
27
|
+
brew install certifyclouds
|
|
25
28
|
|
|
26
29
|
# 2. Authenticate (skip in Azure Cloud Shell)
|
|
27
30
|
az login
|
|
@@ -35,7 +38,7 @@ On first run, you'll be prompted to register with your email (free, takes 10 sec
|
|
|
35
38
|
## Output
|
|
36
39
|
|
|
37
40
|
```
|
|
38
|
-
CertifyClouds Scanner v1.2.
|
|
41
|
+
CertifyClouds Scanner v1.2.2
|
|
39
42
|
Authenticated as: you@company.com
|
|
40
43
|
|
|
41
44
|
Scanning Azure subscriptions...
|
|
@@ -128,7 +131,7 @@ AZURE_TENANT_ID=...
|
|
|
128
131
|
| 0 | Scan completed, no critical/high findings |
|
|
129
132
|
| 1 | Scan completed, critical or high findings detected |
|
|
130
133
|
| 2 | Scan failed (auth error, network error, timeout) |
|
|
131
|
-
| 3 | Registration required
|
|
134
|
+
| 3 | Registration required (no key found or registration failed) |
|
|
132
135
|
|
|
133
136
|
Non-zero exit codes make this CI/cron-friendly: `cc-scan || notify-team`.
|
|
134
137
|
|
|
@@ -173,7 +176,7 @@ CertifyClouds Scanner sends **aggregate usage statistics** to improve the produc
|
|
|
173
176
|
|
|
174
177
|
**Never sent:** Vault names or URIs, secret/certificate/key names, secret values, subscription IDs or names, Azure tenant IDs, resource group names, tags, certificate subjects or thumbprints.
|
|
175
178
|
|
|
176
|
-
Use `--offline` to
|
|
179
|
+
Use `--offline` to skip license server validation calls (key telemetry and version checks). Azure SDK calls are unaffected — the scanner still requires Azure connectivity to read Key Vault data.
|
|
177
180
|
|
|
178
181
|
Source code is open and auditable: [codeberg.org/hus/cc/scanner](https://codeberg.org/hus/cc)
|
|
179
182
|
|
|
@@ -22,11 +22,14 @@ REQUEST_TIMEOUT = 10.0
|
|
|
22
22
|
class ValidationResult:
|
|
23
23
|
"""Result of a key validation call."""
|
|
24
24
|
def __init__(self, valid: bool, error: str | None = None,
|
|
25
|
-
latest_version: str | None = None, offline: bool = False
|
|
25
|
+
latest_version: str | None = None, offline: bool = False,
|
|
26
|
+
release_notes: str | None = None, download_url: str | None = None):
|
|
26
27
|
self.valid = valid
|
|
27
28
|
self.error = error
|
|
28
29
|
self.latest_version = latest_version
|
|
29
30
|
self.offline = offline
|
|
31
|
+
self.release_notes = release_notes
|
|
32
|
+
self.download_url = download_url
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
def validate_key(key: str, stats: dict[str, Any] | None = None) -> ValidationResult:
|
|
@@ -72,9 +75,12 @@ def validate_key(key: str, stats: dict[str, Any] | None = None) -> ValidationRes
|
|
|
72
75
|
|
|
73
76
|
# Valid — update cache
|
|
74
77
|
cfg.update_last_validated()
|
|
78
|
+
version_data = data.get("latestVersionData") or {}
|
|
75
79
|
return ValidationResult(
|
|
76
80
|
valid=True,
|
|
77
81
|
latest_version=data.get("latestVersion"),
|
|
82
|
+
release_notes=version_data.get("release_notes") or None,
|
|
83
|
+
download_url=version_data.get("download_url") or None,
|
|
78
84
|
)
|
|
79
85
|
|
|
80
86
|
except (httpx.ConnectError, httpx.TimeoutException, httpx.HTTPError) as exc:
|
|
@@ -162,8 +162,20 @@ def main(
|
|
|
162
162
|
# Resolve settings: flag > env var > default
|
|
163
163
|
fmt = fmt or os.environ.get("CC_SCAN_FORMAT", "table")
|
|
164
164
|
auth_method = auth_method or os.environ.get("CC_SCAN_AUTH", "cli")
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
if not workers:
|
|
166
|
+
raw_workers = os.environ.get("CC_SCAN_WORKERS", "5")
|
|
167
|
+
try:
|
|
168
|
+
workers = int(raw_workers)
|
|
169
|
+
except ValueError:
|
|
170
|
+
print(f"Error: CC_SCAN_WORKERS must be an integer, got {raw_workers!r}")
|
|
171
|
+
sys.exit(2)
|
|
172
|
+
if not timeout:
|
|
173
|
+
raw_timeout = os.environ.get("CC_SCAN_TIMEOUT", "300")
|
|
174
|
+
try:
|
|
175
|
+
timeout = int(raw_timeout)
|
|
176
|
+
except ValueError:
|
|
177
|
+
print(f"Error: CC_SCAN_TIMEOUT must be an integer, got {raw_timeout!r}")
|
|
178
|
+
sys.exit(2)
|
|
167
179
|
offline = offline or os.environ.get("CC_SCAN_OFFLINE", "").lower() in ("true", "1")
|
|
168
180
|
workers = min(max(workers, 1), 20)
|
|
169
181
|
|
|
@@ -223,6 +235,11 @@ def main(
|
|
|
223
235
|
get_formatter("json")(results, [], output_file)
|
|
224
236
|
sys.exit(2)
|
|
225
237
|
|
|
238
|
+
if results.get("timed_out"):
|
|
239
|
+
if fmt == "table":
|
|
240
|
+
console.print(f"[yellow]Warning: {results['error']}. Results below are partial.[/yellow]")
|
|
241
|
+
console.print()
|
|
242
|
+
|
|
226
243
|
# Apply filters
|
|
227
244
|
apply_filters(results, expiring_days=expiring, expired_only=expired, asset_type=asset_type)
|
|
228
245
|
|
|
@@ -266,7 +283,9 @@ def main(
|
|
|
266
283
|
elif validation.offline and validation.error:
|
|
267
284
|
console.print(f"[dim]{validation.error}. Scanning in offline mode.[/dim]")
|
|
268
285
|
elif validation.latest_version and validation.latest_version != __version__:
|
|
269
|
-
console.print(f"[dim]Update available: cc-scan {__version__} → {validation.latest_version} — pip install --upgrade
|
|
286
|
+
console.print(f"[dim]Update available: cc-scan {__version__} → {validation.latest_version} — pip install --upgrade certifyclouds[/dim]")
|
|
287
|
+
if validation.release_notes:
|
|
288
|
+
console.print(f"[dim] What's new: {validation.release_notes}[/dim]")
|
|
270
289
|
|
|
271
290
|
# CTA (table format only, after version nudge per plan section 11.3)
|
|
272
291
|
if fmt == "table":
|
|
@@ -278,6 +297,8 @@ def main(
|
|
|
278
297
|
console.print()
|
|
279
298
|
|
|
280
299
|
# Exit code
|
|
300
|
+
if results.get("timed_out"):
|
|
301
|
+
sys.exit(2)
|
|
281
302
|
if has_critical_or_high(findings):
|
|
282
303
|
sys.exit(1)
|
|
283
304
|
sys.exit(0)
|
|
@@ -77,15 +77,11 @@ def register() -> str | None:
|
|
|
77
77
|
|
|
78
78
|
return key
|
|
79
79
|
|
|
80
|
-
except httpx.ConnectError:
|
|
81
|
-
console.print("[
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return
|
|
85
|
-
except httpx.TimeoutException:
|
|
86
|
-
console.print("[red]Failed: request timed out.[/red]")
|
|
87
|
-
console.print(" Check your internet connection and try again.")
|
|
88
|
-
return None
|
|
80
|
+
except (httpx.ConnectError, httpx.TimeoutException):
|
|
81
|
+
console.print("[yellow]Could not reach license server — continuing in offline mode.[/yellow]")
|
|
82
|
+
offline_key = f"offline-{email}"
|
|
83
|
+
cfg.save_registration(offline_key, email)
|
|
84
|
+
return offline_key
|
|
89
85
|
except Exception as exc:
|
|
90
86
|
console.print(f"[red]Failed: {exc}[/red]")
|
|
91
87
|
return None
|
|
@@ -164,7 +164,7 @@ def scan(
|
|
|
164
164
|
"totalCertificates": 0,
|
|
165
165
|
"totalKeys": 0,
|
|
166
166
|
"vaultsWithErrors": 0,
|
|
167
|
-
"error": "Azure SDK not installed. Run: pip install --force-reinstall
|
|
167
|
+
"error": "Azure SDK not installed. Run: pip install --force-reinstall certifyclouds",
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
try:
|
|
@@ -524,23 +524,28 @@ def scan(
|
|
|
524
524
|
# Execute subscription scans in parallel
|
|
525
525
|
worker_count = min(max_workers, len(subs_to_scan)) if subs_to_scan else 1
|
|
526
526
|
|
|
527
|
-
|
|
528
|
-
|
|
527
|
+
try:
|
|
528
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=worker_count) as executor:
|
|
529
|
+
future_to_sub = {executor.submit(scan_subscription, sub): sub for sub in subs_to_scan}
|
|
529
530
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
531
|
+
for future in concurrent.futures.as_completed(future_to_sub, timeout=timeout):
|
|
532
|
+
try:
|
|
533
|
+
sub_entry = future.result()
|
|
534
|
+
results["subscriptions"].append(sub_entry)
|
|
535
|
+
results["totalVaults"] += len(sub_entry["vaults"])
|
|
536
|
+
for vault in sub_entry["vaults"]:
|
|
537
|
+
results["totalSecrets"] += len(vault.get("secrets", []))
|
|
538
|
+
results["totalCertificates"] += len(vault.get("certificates", []))
|
|
539
|
+
results["totalKeys"] += len(vault.get("keys", []))
|
|
540
|
+
if vault.get("error"):
|
|
541
|
+
results["vaultsWithErrors"] += 1
|
|
542
|
+
except Exception as exc:
|
|
543
|
+
sub = future_to_sub[future]
|
|
544
|
+
logger.error("Scan failed for subscription %s: %s", getattr(sub, "display_name", sub.subscription_id), exc)
|
|
545
|
+
except concurrent.futures.TimeoutError:
|
|
546
|
+
logger.error("Scan timed out after %s seconds; returning partial results", timeout)
|
|
547
|
+
results["timed_out"] = True
|
|
548
|
+
results["error"] = f"Scan timed out after {timeout} seconds"
|
|
544
549
|
|
|
545
550
|
return results
|
|
546
551
|
|
|
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
|