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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: certifyclouds
3
- Version: 1.2.0
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
- # cc-scanner
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 cc-scanner
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 cc-scanner
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.0
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 / invalid API key |
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 disable all network calls to the license server. The scan works fully offline.
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
- # cc-scanner
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 cc-scanner
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 cc-scanner
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.0
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 / invalid API key |
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 disable all network calls to the license server. The scan works fully offline.
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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "certifyclouds"
3
- version = "1.2.0"
3
+ version = "1.2.2"
4
4
  description = "Scan Azure Key Vaults for expiring secrets, certificates, and keys"
5
5
  readme = "README.md"
6
6
  license = { text = "Apache-2.0" }
@@ -1,3 +1,3 @@
1
1
  """CertifyClouds Scanner — scan Azure Key Vaults for expiring secrets, certificates, and keys."""
2
2
 
3
- __version__ = "1.2.0"
3
+ __version__ = "1.2.2"
@@ -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
- workers = workers or int(os.environ.get("CC_SCAN_WORKERS", "5"))
166
- timeout = timeout or int(os.environ.get("CC_SCAN_TIMEOUT", "300"))
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 cc-scanner[/dim]")
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("[red]Failed: could not reach license.certifyclouds.com[/red]")
82
- console.print(" Check your internet connection and try again.")
83
- console.print(" If you're behind a proxy, set HTTPS_PROXY in your environment.")
84
- return None
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 cc-scanner",
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
- with concurrent.futures.ThreadPoolExecutor(max_workers=worker_count) as executor:
528
- future_to_sub = {executor.submit(scan_subscription, sub): sub for sub in subs_to_scan}
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
- for future in concurrent.futures.as_completed(future_to_sub, timeout=timeout):
531
- try:
532
- sub_entry = future.result()
533
- results["subscriptions"].append(sub_entry)
534
- results["totalVaults"] += len(sub_entry["vaults"])
535
- for vault in sub_entry["vaults"]:
536
- results["totalSecrets"] += len(vault.get("secrets", []))
537
- results["totalCertificates"] += len(vault.get("certificates", []))
538
- results["totalKeys"] += len(vault.get("keys", []))
539
- if vault.get("error"):
540
- results["vaultsWithErrors"] += 1
541
- except Exception as exc:
542
- sub = future_to_sub[future]
543
- logger.error("Scan failed for subscription %s: %s", getattr(sub, "display_name", sub.subscription_id), exc)
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