Skip to content

Commit feee484

Browse files
nemarjancursoragent
authored andcommitted
[env_op_images] Use CRI-O Trying to access lines for image origin verification
CRI-O reports the canonical source registry in Pulled image lines even when fetching from a mirror, making node_verified_image_origin unreliable. Use Trying to access log lines instead, which show the actual registry contacted. Changes: - log_evidence_uri renamed to image_fetched_from - New image_canonical_name field (canonical name reported to k8s) - New verification_reason field explaining evidence chain - pull_failed origin when Trying lines exist without a Pulled image - Falls back to Pulled image when no Trying lines exist Tests added for mirror-override, fallback, and pull_failed scenarios. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: nemarjan <nemarjan@redhat.com>
1 parent 5de0766 commit feee484

2 files changed

Lines changed: 432 additions & 52 deletions

File tree

plugins/modules/verify_pulled_report_crio.py

Lines changed: 93 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@
2727
2828
description:
2929
- Reads the YAML produced by the env_op_images pulled-images report role task.
30-
- "Parses CRI-O journal lines for C(msg=\"Pulled image: ...@sha256:...\")."
30+
- "Parses CRI-O journal lines for both C(msg=\"Trying to access\") and
31+
C(msg=\"Pulled image: ...@sha256:...\") entries."
32+
- "CRI-O always reports the canonical source registry in C(Pulled image)
33+
lines even when the image was fetched from a mirror. The
34+
C(Trying to access) lines show which registries CRI-O actually
35+
contacted. This module uses the last C(Trying to access) URI for
36+
a given digest as the authoritative pull-source evidence."
3137
- Adds per-row verification fields using trusted mirror domains from
3238
C(summary.mirror_rules).
3339
- When images carry a C(node) field, evidence is matched against the
@@ -105,7 +111,8 @@
105111
returned: always
106112
nodes_with_evidence:
107113
description: >-
108-
Node names that had at least one C(Pulled image) log entry.
114+
Node names that had at least one C(Trying to access) or
115+
C(Pulled image) log entry.
109116
type: list
110117
elements: str
111118
returned: always
@@ -118,8 +125,11 @@
118125
import yaml
119126
from ansible.module_utils.basic import AnsibleModule
120127

121-
LOG_PATTERN = re.compile(
122-
r'msg="Pulled image: (?P<actual_uri>[^@\s]+)@(?P<id>sha256:[a-f0-9]+)"'
128+
PULLED_PATTERN = re.compile(
129+
r'msg="Pulled image: (?P<pulled_uri>[^@\s]+)@(?P<id>sha256:[a-f0-9]+)"'
130+
)
131+
TRYING_PATTERN = re.compile(
132+
r'msg="Trying to access \\"(?P<tried_uri>[^@\s]+)@(?P<id>sha256:[a-f0-9]+)\\"'
123133
)
124134
SHA256_PATTERN = re.compile(r"sha256:[a-f0-9]+")
125135

@@ -138,24 +148,58 @@ def _domain_from_uri(uri):
138148
return uri.split("/")[0].strip()
139149

140150

141-
def _apply_evidence(img, actual_uri, evidence_node, trusted_mirrors):
142-
"""Set common verification fields on an image row that has log evidence."""
143-
actual_domain = _domain_from_uri(actual_uri)
144-
img["node_verified_image_origin"] = (
145-
"mirror" if actual_domain in trusted_mirrors else "source"
146-
)
147-
img["log_evidence_uri"] = actual_uri
151+
def _apply_evidence(img, evidence, evidence_node, trusted_mirrors):
152+
"""Set verification fields on an image row that has log evidence.
153+
154+
The *evidence* dict carries ``tried_uri`` (from "Trying to access") and/or
155+
``pulled_uri`` (from "Pulled image"). The tried URI is authoritative for
156+
determining origin because CRI-O always reports the canonical source name
157+
in "Pulled image" even when it fetched from a mirror.
158+
159+
When only ``tried_uri`` exists without a ``pulled_uri``, the pull was
160+
attempted but never confirmed successful -- origin is ``pull_failed``.
161+
"""
162+
tried_uri = evidence.get("tried_uri")
163+
pulled_uri = evidence.get("pulled_uri")
164+
165+
if tried_uri and not pulled_uri:
166+
img["node_verified_image_origin"] = "pull_failed"
167+
img["verification_reason"] = "CRI-O tried registries but pull never succeeded"
168+
img["image_fetched_from"] = None
169+
img["image_canonical_name"] = None
170+
img["log_evidence_node"] = evidence_node
171+
return None
172+
173+
authoritative_uri = tried_uri or pulled_uri
174+
actual_domain = _domain_from_uri(authoritative_uri)
175+
origin = "mirror" if actual_domain in trusted_mirrors else "source"
176+
img["node_verified_image_origin"] = origin
177+
178+
if tried_uri:
179+
img["verification_reason"] = "CRI-O contacted {0} and pull succeeded".format(
180+
origin
181+
)
182+
else:
183+
img["verification_reason"] = (
184+
"Pull confirmed via Pulled image log only"
185+
" (no registry contact attempt logged)"
186+
)
187+
188+
img["image_fetched_from"] = tried_uri
189+
img["image_canonical_name"] = pulled_uri
148190
img["log_evidence_node"] = evidence_node
149-
return actual_domain
150191

151192

152193
def _collect_log_evidence(paths, module):
153194
"""Parse CRI-O logs into per-node and global evidence dicts.
154195
196+
Each digest maps to ``{"tried_uri": ..., "pulled_uri": ...}``.
197+
"Trying to access" is the authoritative source for which registry was
198+
contacted; "Pulled image" always carries the canonical name.
199+
155200
Returns:
156-
per_node: ``{node_name: {sha256_digest: pull_uri}}``
157-
global_evidence: ``{sha256_digest: (pull_uri, node_name)}``
158-
(last writer wins across nodes for the global dict)
201+
per_node: ``{node_name: {digest: {"tried_uri": str|None, "pulled_uri": str|None}}}``
202+
global_evidence: ``{digest: (evidence_dict, node_name)}``
159203
"""
160204
per_node = {}
161205
global_evidence = {}
@@ -165,12 +209,33 @@ def _collect_log_evidence(paths, module):
165209
try:
166210
with open(path, "r") as f:
167211
for line in f:
168-
match = LOG_PATTERN.search(line)
169-
if match:
170-
digest = match.group("id")
171-
uri = match.group("actual_uri")
172-
node_ev[digest] = uri
173-
global_evidence[digest] = (uri, node)
212+
trying = TRYING_PATTERN.search(line)
213+
if trying:
214+
digest = trying.group("id")
215+
node_entry = node_ev.setdefault(
216+
digest, {"tried_uri": None, "pulled_uri": None}
217+
)
218+
node_entry["tried_uri"] = trying.group("tried_uri")
219+
global_entry = global_evidence.setdefault(
220+
digest, [{"tried_uri": None, "pulled_uri": None}, node]
221+
)
222+
global_entry[0]["tried_uri"] = trying.group("tried_uri")
223+
global_entry[1] = node
224+
continue
225+
226+
pulled = PULLED_PATTERN.search(line)
227+
if pulled:
228+
digest = pulled.group("id")
229+
node_entry = node_ev.setdefault(
230+
digest, {"tried_uri": None, "pulled_uri": None}
231+
)
232+
node_entry["pulled_uri"] = pulled.group("pulled_uri")
233+
global_entry = global_evidence.setdefault(
234+
digest, [{"tried_uri": None, "pulled_uri": None}, node]
235+
)
236+
global_entry[0]["pulled_uri"] = pulled.group("pulled_uri")
237+
if global_entry[1] is None:
238+
global_entry[1] = node
174239
except IOError as exc:
175240
module.fail_json(
176241
msg="Cannot read CRI-O log file {0}: {1}".format(path, str(exc))
@@ -251,16 +316,18 @@ def run_module():
251316
)
252317

253318
if node_local_hit:
254-
actual_uri = per_node_evidence[img_node][img_sha]
255-
_apply_evidence(img, actual_uri, img_node, trusted_mirrors)
319+
evidence = per_node_evidence[img_node][img_sha]
320+
_apply_evidence(img, evidence, img_node, trusted_mirrors)
256321
elif img_sha in global_evidence:
257-
actual_uri, evidence_node = global_evidence[img_sha]
258-
_apply_evidence(img, actual_uri, evidence_node, trusted_mirrors)
322+
evidence, evidence_node = global_evidence[img_sha]
323+
_apply_evidence(img, evidence, evidence_node, trusted_mirrors)
259324
if img_node:
260325
cross_node_entries += 1
261326
else:
262327
img["node_verified_image_origin"] = "cached/unknown"
263-
img["log_evidence_uri"] = None
328+
img["verification_reason"] = "No CRI-O log evidence found for this digest"
329+
img["image_fetched_from"] = None
330+
img["image_canonical_name"] = None
264331
img["log_evidence_node"] = None
265332

266333
nodes_with_evidence = sorted(n for n, ev in per_node_evidence.items() if ev)

0 commit comments

Comments
 (0)