dockerls 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.
Files changed (230) hide show
  1. dockerls/__init__.py +31 -0
  2. dockerls/application/__init__.py +0 -0
  3. dockerls/application/dto/__init__.py +3 -0
  4. dockerls/application/dto/analysis.py +257 -0
  5. dockerls/application/services/__init__.py +0 -0
  6. dockerls/application/services/alternatives_lookup.py +167 -0
  7. dockerls/application/services/composite_repository.py +106 -0
  8. dockerls/application/services/cross_validation.py +233 -0
  9. dockerls/application/services/ecosystems.py +350 -0
  10. dockerls/application/services/fallback_scanner.py +97 -0
  11. dockerls/application/services/hardening_analysis.py +174 -0
  12. dockerls/application/services/migration.py +297 -0
  13. dockerls/application/services/progress.py +56 -0
  14. dockerls/application/services/remediation.py +321 -0
  15. dockerls/application/services/scan_history_store.py +88 -0
  16. dockerls/application/services/scanner_factory.py +88 -0
  17. dockerls/application/services/source_registry.py +151 -0
  18. dockerls/application/services/tag_history_store.py +76 -0
  19. dockerls/application/services/teardown.py +50 -0
  20. dockerls/application/services/verdict.py +298 -0
  21. dockerls/application/services/version_discovery.py +108 -0
  22. dockerls/application/use_cases/__init__.py +0 -0
  23. dockerls/application/use_cases/analyze_dockerfile.py +103 -0
  24. dockerls/application/use_cases/analyze_image.py +173 -0
  25. dockerls/application/use_cases/build_image.py +1795 -0
  26. dockerls/application/use_cases/compare_images.py +91 -0
  27. dockerls/application/use_cases/fleet_scan.py +240 -0
  28. dockerls/application/use_cases/recommend_images.py +1078 -0
  29. dockerls/application/use_cases/registry_audit.py +133 -0
  30. dockerls/application/use_cases/search_images.py +23 -0
  31. dockerls/application/use_cases/upgrade_base.py +167 -0
  32. dockerls/cache/__init__.py +0 -0
  33. dockerls/cache/sqlite_cache.py +184 -0
  34. dockerls/cli/__init__.py +0 -0
  35. dockerls/cli/analysis_baseline.py +98 -0
  36. dockerls/cli/app.py +294 -0
  37. dockerls/cli/commands/__init__.py +0 -0
  38. dockerls/cli/commands/advisor.py +262 -0
  39. dockerls/cli/commands/alternatives.py +291 -0
  40. dockerls/cli/commands/analyze.py +429 -0
  41. dockerls/cli/commands/analyze_dockerfile.py +104 -0
  42. dockerls/cli/commands/base_cmd.py +244 -0
  43. dockerls/cli/commands/base_image.py +551 -0
  44. dockerls/cli/commands/build.py +1300 -0
  45. dockerls/cli/commands/cache_cmd.py +104 -0
  46. dockerls/cli/commands/compare.py +177 -0
  47. dockerls/cli/commands/controls.py +110 -0
  48. dockerls/cli/commands/doctor.py +566 -0
  49. dockerls/cli/commands/export.py +81 -0
  50. dockerls/cli/commands/fleet.py +159 -0
  51. dockerls/cli/commands/health.py +84 -0
  52. dockerls/cli/commands/login.py +53 -0
  53. dockerls/cli/commands/policy_cmd.py +111 -0
  54. dockerls/cli/commands/provenance_cmd.py +162 -0
  55. dockerls/cli/commands/recommend.py +761 -0
  56. dockerls/cli/commands/registry_audit_cmd.py +103 -0
  57. dockerls/cli/commands/sbom.py +144 -0
  58. dockerls/cli/commands/search.py +86 -0
  59. dockerls/cli/commands/verify.py +115 -0
  60. dockerls/cli/commands/version.py +12 -0
  61. dockerls/cli/commands/vex_cmd.py +117 -0
  62. dockerls/cli/dependencies.py +530 -0
  63. dockerls/cli/image_names.py +79 -0
  64. dockerls/cli/options.py +42 -0
  65. dockerls/cli/progress.py +145 -0
  66. dockerls/cli/publish_prompt.py +123 -0
  67. dockerls/cli/rendering.py +214 -0
  68. dockerls/cli/runtime.py +65 -0
  69. dockerls/cli/scan_failure.py +71 -0
  70. dockerls/cli/text.py +39 -0
  71. dockerls/cli/validators.py +35 -0
  72. dockerls/cli/vulnerability_view.py +154 -0
  73. dockerls/domain/__init__.py +0 -0
  74. dockerls/domain/entities/__init__.py +75 -0
  75. dockerls/domain/entities/declared_metadata.py +147 -0
  76. dockerls/domain/entities/dockerfile_analysis.py +318 -0
  77. dockerls/domain/entities/image.py +109 -0
  78. dockerls/domain/entities/image_facts.py +137 -0
  79. dockerls/domain/entities/recommendation.py +33 -0
  80. dockerls/domain/entities/scan_result.py +129 -0
  81. dockerls/domain/entities/vulnerability.py +232 -0
  82. dockerls/domain/interfaces/__init__.py +17 -0
  83. dockerls/domain/interfaces/cache_store.py +18 -0
  84. dockerls/domain/interfaces/dockerfile_validator.py +99 -0
  85. dockerls/domain/interfaces/eol_checker.py +11 -0
  86. dockerls/domain/interfaces/image_repository.py +15 -0
  87. dockerls/domain/interfaces/scanner.py +15 -0
  88. dockerls/domain/security_controls.py +362 -0
  89. dockerls/domain/value_objects/__init__.py +49 -0
  90. dockerls/domain/value_objects/attack_surface.py +198 -0
  91. dockerls/domain/value_objects/base_recipe.py +600 -0
  92. dockerls/domain/value_objects/base_upgrade.py +292 -0
  93. dockerls/domain/value_objects/build_labels.py +99 -0
  94. dockerls/domain/value_objects/build_policy.py +412 -0
  95. dockerls/domain/value_objects/confidence.py +156 -0
  96. dockerls/domain/value_objects/fleet.py +174 -0
  97. dockerls/domain/value_objects/gate.py +327 -0
  98. dockerls/domain/value_objects/hardening.py +303 -0
  99. dockerls/domain/value_objects/image_reference.py +90 -0
  100. dockerls/domain/value_objects/inheritance.py +352 -0
  101. dockerls/domain/value_objects/network_policy.py +280 -0
  102. dockerls/domain/value_objects/production_readiness.py +145 -0
  103. dockerls/domain/value_objects/provenance.py +211 -0
  104. dockerls/domain/value_objects/recipe_diff.py +188 -0
  105. dockerls/domain/value_objects/registry_audit.py +195 -0
  106. dockerls/domain/value_objects/registry_target.py +225 -0
  107. dockerls/domain/value_objects/remediation_score.py +62 -0
  108. dockerls/domain/value_objects/scan_history.py +183 -0
  109. dockerls/domain/value_objects/scan_plan.py +193 -0
  110. dockerls/domain/value_objects/scanner_db.py +143 -0
  111. dockerls/domain/value_objects/security_score.py +160 -0
  112. dockerls/domain/value_objects/security_tier.py +122 -0
  113. dockerls/domain/value_objects/tag_history.py +180 -0
  114. dockerls/domain/value_objects/tool_release.py +253 -0
  115. dockerls/domain/value_objects/tristate.py +47 -0
  116. dockerls/domain/value_objects/vex.py +249 -0
  117. dockerls/exit_codes.py +23 -0
  118. dockerls/exporters/__init__.py +0 -0
  119. dockerls/exporters/base.py +17 -0
  120. dockerls/exporters/csv_exporter.py +85 -0
  121. dockerls/exporters/factory.py +31 -0
  122. dockerls/exporters/html_exporter.py +105 -0
  123. dockerls/exporters/json_exporter.py +19 -0
  124. dockerls/exporters/markdown_exporter.py +84 -0
  125. dockerls/exporters/sarif_exporter.py +245 -0
  126. dockerls/infrastructure/__init__.py +0 -0
  127. dockerls/infrastructure/config/__init__.py +0 -0
  128. dockerls/infrastructure/config/policy_file.py +165 -0
  129. dockerls/infrastructure/config/settings.py +197 -0
  130. dockerls/infrastructure/database/__init__.py +0 -0
  131. dockerls/infrastructure/database/models.py +78 -0
  132. dockerls/infrastructure/dockerfile_validator.py +1899 -0
  133. dockerls/infrastructure/evidence.py +99 -0
  134. dockerls/infrastructure/hashing.py +165 -0
  135. dockerls/infrastructure/logging/__init__.py +0 -0
  136. dockerls/infrastructure/logging/setup.py +98 -0
  137. dockerls/infrastructure/network/__init__.py +0 -0
  138. dockerls/infrastructure/network/guarded_client.py +107 -0
  139. dockerls/infrastructure/network/host_guard.py +117 -0
  140. dockerls/infrastructure/redaction.py +135 -0
  141. dockerls/infrastructure/templates/hardening/alpine.dockerfile +44 -0
  142. dockerls/infrastructure/templates/hardening/debian.dockerfile +45 -0
  143. dockerls/infrastructure/templates/hardening/distroless.dockerfile +34 -0
  144. dockerls/infrastructure/templates/hardening/go-alpine.dockerfile +52 -0
  145. dockerls/infrastructure/templates/hardening/go-debian.dockerfile +54 -0
  146. dockerls/infrastructure/templates/hardening/go-distroless.dockerfile +43 -0
  147. dockerls/infrastructure/templates/hardening/go-scratch.dockerfile +48 -0
  148. dockerls/infrastructure/templates/hardening/go.dockerfile +50 -0
  149. dockerls/infrastructure/templates/hardening/gradle-alpine.dockerfile +51 -0
  150. dockerls/infrastructure/templates/hardening/gradle.dockerfile +52 -0
  151. dockerls/infrastructure/templates/hardening/java-alpine.dockerfile +50 -0
  152. dockerls/infrastructure/templates/hardening/java-debian.dockerfile +50 -0
  153. dockerls/infrastructure/templates/hardening/java-distroless.dockerfile +39 -0
  154. dockerls/infrastructure/templates/hardening/java-ubuntu.dockerfile +54 -0
  155. dockerls/infrastructure/templates/hardening/java.dockerfile +60 -0
  156. dockerls/infrastructure/templates/hardening/maven-alpine.dockerfile +56 -0
  157. dockerls/infrastructure/templates/hardening/maven.dockerfile +57 -0
  158. dockerls/infrastructure/templates/hardening/node-alpine.dockerfile +47 -0
  159. dockerls/infrastructure/templates/hardening/node-debian.dockerfile +54 -0
  160. dockerls/infrastructure/templates/hardening/node-distroless.dockerfile +46 -0
  161. dockerls/infrastructure/templates/hardening/node-ubuntu.dockerfile +63 -0
  162. dockerls/infrastructure/templates/hardening/node.dockerfile +61 -0
  163. dockerls/infrastructure/templates/hardening/php-alpine.dockerfile +45 -0
  164. dockerls/infrastructure/templates/hardening/php-debian.dockerfile +45 -0
  165. dockerls/infrastructure/templates/hardening/php-ubuntu.dockerfile +49 -0
  166. dockerls/infrastructure/templates/hardening/php.dockerfile +44 -0
  167. dockerls/infrastructure/templates/hardening/python-alpine.dockerfile +51 -0
  168. dockerls/infrastructure/templates/hardening/python-debian.dockerfile +54 -0
  169. dockerls/infrastructure/templates/hardening/python-distroless.dockerfile +51 -0
  170. dockerls/infrastructure/templates/hardening/python-ubuntu.dockerfile +60 -0
  171. dockerls/infrastructure/templates/hardening/python.dockerfile +58 -0
  172. dockerls/infrastructure/templates/hardening/ruby-alpine.dockerfile +48 -0
  173. dockerls/infrastructure/templates/hardening/ruby-debian.dockerfile +50 -0
  174. dockerls/infrastructure/templates/hardening/rust-alpine.dockerfile +50 -0
  175. dockerls/infrastructure/templates/hardening/rust-debian.dockerfile +48 -0
  176. dockerls/infrastructure/templates/hardening/rust-scratch.dockerfile +44 -0
  177. dockerls/infrastructure/templates/hardening/rust.dockerfile +54 -0
  178. dockerls/infrastructure/templates/hardening/ubuntu.dockerfile +49 -0
  179. dockerls/infrastructure/toolchain/__init__.py +0 -0
  180. dockerls/infrastructure/toolchain/db_metadata.py +115 -0
  181. dockerls/infrastructure/toolchain/installer.py +435 -0
  182. dockerls/integrations/__init__.py +0 -0
  183. dockerls/integrations/dhi/__init__.py +0 -0
  184. dockerls/integrations/dhi/catalog.py +457 -0
  185. dockerls/integrations/dhi/definition.py +151 -0
  186. dockerls/integrations/dhi/repository.py +238 -0
  187. dockerls/integrations/dockerhub/__init__.py +0 -0
  188. dockerls/integrations/dockerhub/client.py +318 -0
  189. dockerls/integrations/dockerhub/urls.py +75 -0
  190. dockerls/integrations/endoflife/__init__.py +0 -0
  191. dockerls/integrations/endoflife/checker.py +216 -0
  192. dockerls/integrations/engine/__init__.py +0 -0
  193. dockerls/integrations/engine/batch.py +197 -0
  194. dockerls/integrations/engine/client.py +330 -0
  195. dockerls/integrations/engine/locator.py +96 -0
  196. dockerls/integrations/exploitdb/__init__.py +0 -0
  197. dockerls/integrations/exploitdb/client.py +271 -0
  198. dockerls/integrations/grype/__init__.py +0 -0
  199. dockerls/integrations/grype/scanner.py +336 -0
  200. dockerls/integrations/registry/__init__.py +0 -0
  201. dockerls/integrations/registry/hardened.py +284 -0
  202. dockerls/integrations/registry/inspector.py +420 -0
  203. dockerls/integrations/registry/oci.py +259 -0
  204. dockerls/integrations/registry/private.py +79 -0
  205. dockerls/integrations/registry/urls.py +36 -0
  206. dockerls/integrations/scan_errors.py +67 -0
  207. dockerls/integrations/scan_target.py +57 -0
  208. dockerls/integrations/signing/__init__.py +0 -0
  209. dockerls/integrations/signing/cosign.py +484 -0
  210. dockerls/integrations/threat_intel/__init__.py +0 -0
  211. dockerls/integrations/threat_intel/client.py +290 -0
  212. dockerls/integrations/trivy/__init__.py +0 -0
  213. dockerls/integrations/trivy/cache_pool.py +176 -0
  214. dockerls/integrations/trivy/scanner.py +447 -0
  215. dockerls/utils/__init__.py +0 -0
  216. dockerls/utils/auth.py +108 -0
  217. dockerls/utils/executables.py +39 -0
  218. dockerls/utils/ignore_file.py +130 -0
  219. dockerls/utils/rate_limit.py +130 -0
  220. dockerls/utils/resources.py +183 -0
  221. dockerls/utils/retry.py +31 -0
  222. dockerls/utils/safe_yaml.py +166 -0
  223. dockerls/utils/subprocess_runner.py +216 -0
  224. dockerls/utils/validation.py +72 -0
  225. dockerls-1.0.0.dist-info/METADATA +563 -0
  226. dockerls-1.0.0.dist-info/RECORD +230 -0
  227. dockerls-1.0.0.dist-info/WHEEL +5 -0
  228. dockerls-1.0.0.dist-info/entry_points.txt +2 -0
  229. dockerls-1.0.0.dist-info/licenses/LICENSE +21 -0
  230. dockerls-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING
5
+
6
+ from dockerls.application.dto.analysis import ImageAnalysis
7
+ from dockerls.application.services.teardown import close_quietly, sources_of
8
+ from dockerls.application.services.verdict import apply_facts, finalize_verdict
9
+ from dockerls.application.use_cases.recommend_images import (
10
+ _enrich_with_threat_intel,
11
+ _eol_status,
12
+ )
13
+ from dockerls.domain.entities.image import DockerImage
14
+ from dockerls.domain.value_objects.image_reference import split_repository_and_tag
15
+ from dockerls.domain.value_objects.remediation_score import RemediationScore
16
+ from dockerls.domain.value_objects.security_score import SecurityScore
17
+ from dockerls.domain.value_objects.security_tier import SecurityTier
18
+ from dockerls.utils.ignore_file import active_ignored_cve_ids, load_ignore_rules
19
+
20
+ if TYPE_CHECKING:
21
+ from pathlib import Path
22
+
23
+ from dockerls.application.services.hardening_analysis import HardeningAnalyzer
24
+ from dockerls.application.services.scan_history_store import ScanHistoryStore
25
+ from dockerls.application.services.tag_history_store import TagHistoryStore
26
+ from dockerls.domain.interfaces.eol_checker import EOLCheckerInterface
27
+ from dockerls.domain.interfaces.image_repository import ImageRepositoryInterface
28
+ from dockerls.domain.interfaces.scanner import ScannerInterface
29
+ from dockerls.integrations.exploitdb.client import ExploitDBClient
30
+ from dockerls.integrations.threat_intel.client import ThreatIntelClient
31
+
32
+
33
+ class AnalyzeImageUseCase:
34
+ def __init__(
35
+ self,
36
+ repository: ImageRepositoryInterface,
37
+ scanner: ScannerInterface,
38
+ eol_checker: EOLCheckerInterface,
39
+ ignore_path: Path | None = None,
40
+ threat_intel: ThreatIntelClient | None = None,
41
+ hardening: HardeningAnalyzer | None = None,
42
+ exploitdb: ExploitDBClient | None = None,
43
+ tag_history: TagHistoryStore | None = None,
44
+ scan_history: ScanHistoryStore | None = None,
45
+ ):
46
+ self._repository = repository
47
+ self._scanner = scanner
48
+ self._eol_checker = eol_checker
49
+ self._ignored_cves = active_ignored_cve_ids(load_ignore_rules(ignore_path))
50
+ self._threat_intel = threat_intel
51
+ self._hardening = hardening
52
+ self._exploitdb = exploitdb
53
+ self._tag_history = tag_history
54
+ self._scan_history = scan_history
55
+
56
+ async def execute(self, image_reference: str) -> ImageAnalysis:
57
+ name, tag = self._parse_reference(image_reference)
58
+ image = await self._repository.get_image_metadata(name, tag)
59
+ if not image:
60
+ image = DockerImage(name=name, tag=tag)
61
+ if "@" in image_reference:
62
+ # Um digest é a identidade exata de um conjunto de bytes, e
63
+ # `name:tag` não o reproduz: reconstruir mandaria o scanner
64
+ # medir `node:latest` no lugar do digest que foi pedido --
65
+ # outra imagem, apresentada com o nome desta.
66
+ image.full_reference = image_reference
67
+
68
+ scan = await self._scanner.scan(image.full_reference)
69
+ if self._ignored_cves:
70
+ filtered = [
71
+ v for v in scan.vulnerabilities if v.cve_id.upper() not in self._ignored_cves
72
+ ]
73
+ if len(filtered) != len(scan.vulnerabilities):
74
+ scan = scan.model_copy(update={"vulnerabilities": filtered})
75
+ if self._threat_intel is not None:
76
+ scan = await _enrich_with_threat_intel(scan, self._threat_intel, self._exploitdb)
77
+
78
+ product = name.split("/")[-1]
79
+ match = re.match(r"^\d+(?:\.\d+){0,3}", tag)
80
+ version = match.group(0) if match else ""
81
+
82
+ eol_status = await _eol_status(self._eol_checker, product, version)
83
+ is_eol = eol_status.is_true
84
+ is_lts = await self._eol_checker.is_lts(product, version)
85
+
86
+ # `SecurityScore` requires a completed scan and raises on anything
87
+ # else. A failed scan is not scored at all here: the tier falls back
88
+ # to F (0.0), and `finalize_verdict` below reads `scan.is_verified`
89
+ # to keep that from ever being reported as a verdict rather than a
90
+ # measurement failure -- the same gate `recommend` applies before it
91
+ # will construct a `SecurityScore` in the first place.
92
+ security_score = 0.0
93
+ if scan.is_verified:
94
+ security_score = SecurityScore(image, scan, is_eol=is_eol, is_lts=is_lts).value
95
+ tier = SecurityTier(scan, security_score, is_eol=is_eol)
96
+ rem_score = RemediationScore(scan)
97
+
98
+ analysis = ImageAnalysis(
99
+ image=image,
100
+ scan=scan,
101
+ security_score=security_score,
102
+ tier=tier.tier.value,
103
+ remediation_score=rem_score.value,
104
+ is_eol=is_eol,
105
+ eol_status=eol_status,
106
+ is_lts=is_lts,
107
+ evidence_paths={scan.scanner: scan.evidence_path} if scan.evidence_path else {},
108
+ )
109
+
110
+ # The same evidence gathering `recommend` does for its finalists.
111
+ # Without it, `analyze` and `alternatives` would report the image
112
+ # they were asked about with every hardening fact unknown, while
113
+ # the candidates they are compared against carry measurements --
114
+ # and a comparison between a measurement and a blank is not one.
115
+ if self._hardening is not None:
116
+ digest, facts = await self._hardening.analyze(image, scan)
117
+ if digest and not image.digest:
118
+ image.digest = digest
119
+ apply_facts(analysis, facts)
120
+
121
+ # A digest is the identity of a fixed set of bytes; `name@sha256:...`
122
+ # was asked for exactly that byte set and has no *tag* to track a
123
+ # move for. Only a mutable `name:tag` reference has history worth
124
+ # keeping -- the same distinction `base` already draws for
125
+ # Dockerfile-pinned bases (`tag_history.py`).
126
+ if self._tag_history is not None and image.digest_known and "@" not in image_reference:
127
+ history = await self._tag_history.observe(f"{name}:{tag}", image.digest)
128
+ if history.moves:
129
+ analysis.tag_drift_note = history.explain()
130
+
131
+ # Unlike tag history, this is worth keeping for a digest reference
132
+ # too: a scanner's database learning about a new CVE can change the
133
+ # count for the exact same, unmoving digest between two runs.
134
+ if self._scan_history is not None and scan.is_verified and image.digest_known:
135
+ before = await self._scan_history.get(image.full_reference)
136
+ after = await self._scan_history.observe(
137
+ image.full_reference,
138
+ digest=image.digest,
139
+ critical=scan.critical_count,
140
+ high=scan.high_count,
141
+ medium=scan.medium_count,
142
+ low=scan.low_count,
143
+ total=scan.total_count,
144
+ )
145
+ if len(after.observations) > 1 and after.latest != before.latest:
146
+ analysis.vuln_trend_note = after.explain()
147
+
148
+ finalize_verdict(analysis, cross_validated=False)
149
+ return analysis
150
+
151
+ async def close(self) -> None:
152
+ """Release the scanner and the repository's connection pool.
153
+
154
+ Not done inside `execute`, because `CompareImagesUseCase` calls it
155
+ once per image: closing there would leave the second comparison
156
+ talking to a client that had already been shut down.
157
+ """
158
+ await close_quietly(self._scanner, self._hardening, *sources_of(self._repository))
159
+
160
+ def _parse_reference(self, reference: str) -> tuple[str, str]:
161
+ """Repositório e tag, sem confundir a porta do registry com uma tag.
162
+
163
+ O `rsplit(":", 1)` que morava aqui lia `registry.internal:5000/app`
164
+ como ("registry.internal", "5000/app") e `node@sha256:...` como
165
+ ("node@sha256", "..."). O alvo do scan sobrevivia por acidente --
166
+ `full_reference` reconstrói a string original a partir dos dois
167
+ pedaços errados --, mas o produto e a versão não: a consulta de
168
+ EOL/LTS recebia o produto "registry.internal" na versão "5000", e
169
+ `registry_host` perdia a porta. É a mesma regra que `search`,
170
+ `recommend`, `export`, `advisor` e `alternatives` já usam.
171
+ """
172
+ repository, tag = split_repository_and_tag(reference)
173
+ return repository, tag or "latest"