Skip to content

Commit d06d06f

Browse files
committed
mgr/cephadm: fix objects_by_names initialization + some improvements
This commit include the following changes: 1. Fix objects_by_names initialization as we was using a dict for all the case including global scoped objects which is not correct. For those cases an instance of an empty TLSObject must be used. 2. Add sanity checks to the load() method to avoid loading incorrect and malformed entries. 3. Add some helper functions to avoid code repetition Fixes: https://tracker.ceph.com/issues/73625 Signed-off-by: Redouane Kachach <[email protected]>
1 parent 8534d10 commit d06d06f

File tree

3 files changed

+73
-35
lines changed

3 files changed

+73
-35
lines changed

src/pybind/mgr/cephadm/tests/test_certmgr.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -773,13 +773,12 @@ def _fake_prefix_store(prefix):
773773
assert 'iscsi_ssl_key' in key_store
774774
assert isinstance(key_store['iscsi_ssl_key'], dict) and len(key_store['iscsi_ssl_key']) == 0
775775

776-
# Global-scoped key with bad JSON should also not be instantiated (no PrivKey object)
777-
# Depending on how you seed known names, it might be absent OR present but falsy.
778-
# Accept either: absent OR not a PrivKey instance.
779-
if 'mgmt_gateway_ssl_key' in key_store:
780-
assert not isinstance(key_store['mgmt_gateway_ssl_key'], PrivKey)
781-
else:
782-
assert 'mgmt_gateway_ssl_key' not in key_store
776+
# Global-scoped key with bad JSON should NOT be hydrated
777+
assert 'mgmt_gateway_ssl_key' in key_store
778+
obj = key_store.get('mgmt_gateway_ssl_key')
779+
# It should be a PrivKey (because globals are pre-seeded) but remain empty/falsy
780+
assert isinstance(obj, PrivKey)
781+
assert not bool(obj) # still the tombstone, not parsed content
783782

784783
messages = [r.getMessage() for r in caplog.records
785784
if r.levelno >= logging.WARNING and r.name == "cephadm.tlsobject_store"]

src/pybind/mgr/cephadm/tlsobject_store.py

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import (Any,
22
Dict,
3-
Union,
43
List,
54
Tuple,
65
Optional,
@@ -33,12 +32,22 @@ def __init__(self, mgr: 'CephadmOrchestrator',
3332
self.mgr: CephadmOrchestrator = mgr
3433
self.cephadm_signed_object_checker = cephadm_signed_obj_checker
3534
self.tlsobject_class = tlsobject_class
36-
all_known_objects_names = [item for sublist in known_objects_names.values() for item in sublist]
37-
self.objects_by_name: Dict[str, Any] = {key: {} for key in all_known_objects_names}
3835
self.service_scoped_objects = known_objects_names[TLSObjectScope.SERVICE]
3936
self.host_scoped_objects = known_objects_names[TLSObjectScope.HOST]
4037
self.global_scoped_objects = known_objects_names[TLSObjectScope.GLOBAL]
4138
self.store_prefix = f'{TLSOBJECT_STORE_PREFIX}{tlsobject_class.STORAGE_PREFIX}.'
39+
# initialize objects by name for the different scopes
40+
self.objects_by_name: Dict[str, Any] = {}
41+
for n in self.service_scoped_objects + self.host_scoped_objects:
42+
self.objects_by_name[n] = {}
43+
for n in self.global_scoped_objects:
44+
self.objects_by_name[n] = self.tlsobject_class()
45+
46+
def _kv_key(self, obj_name: str) -> str:
47+
return self.store_prefix + obj_name
48+
49+
def _set_store(self, obj_name: str, payload: Any) -> None:
50+
self.mgr.set_store(self._kv_key(obj_name), json.dumps(payload))
4251

4352
def register_object_name(self, obj_name: str, scope: TLSObjectScope) -> None:
4453
"""
@@ -105,18 +114,16 @@ def save_tlsobject(self, obj_name: str,
105114
self._validate_tlsobject_name(obj_name, service_name, host)
106115
tlsobject = self.tlsobject_class(tlsobject, user_made, editable)
107116
scope, target = self.get_tlsobject_scope_and_target(obj_name, service_name, host)
108-
j: Union[str, Dict[Any, Any], None] = None
109117
if scope in (TLSObjectScope.SERVICE, TLSObjectScope.HOST):
110118
self.objects_by_name[obj_name][target] = tlsobject
111-
j = {
119+
serialized_targets = {
112120
key: self.tlsobject_class.to_json(self.objects_by_name[obj_name][key])
113121
for key in self.objects_by_name[obj_name]
114122
}
115-
self.mgr.set_store(self.store_prefix + obj_name, json.dumps(j))
123+
self._set_store(obj_name, serialized_targets)
116124
elif scope == TLSObjectScope.GLOBAL:
117125
self.objects_by_name[obj_name] = tlsobject
118-
j = self.tlsobject_class.to_json(tlsobject)
119-
self.mgr.set_store(self.store_prefix + obj_name, json.dumps(j))
126+
self._set_store(obj_name, self.tlsobject_class.to_json(tlsobject))
120127
else:
121128
logger.error(f'Trying to save TLS object name {obj_name} with a not-supported/unknown TLSObjectScope scope {scope.value}')
122129

@@ -155,28 +162,27 @@ def rm_tlsobject(self, obj_name: str, service_name: Optional[str] = None, host:
155162
"""
156163
self._validate_tlsobject_name(obj_name, service_name, host)
157164
scope, target = self.get_tlsobject_scope_and_target(obj_name, service_name, host)
158-
j: Union[str, Dict[Any, Any], None] = None
159165
if scope in (TLSObjectScope.SERVICE, TLSObjectScope.HOST):
160166
if obj_name in self.objects_by_name and target in self.objects_by_name[obj_name]:
161167
del self.objects_by_name[obj_name][target]
162-
j = {
168+
serialized_targets = {
163169
key: self.tlsobject_class.to_json(self.objects_by_name[obj_name][key])
164170
for key in self.objects_by_name[obj_name]
165171
}
166-
self.mgr.set_store(self.store_prefix + obj_name, json.dumps(j))
172+
self._set_store(obj_name, serialized_targets)
167173
return True
168174
elif scope == TLSObjectScope.GLOBAL:
169175
self.objects_by_name[obj_name] = self.tlsobject_class()
170-
j = self.tlsobject_class.to_json(self.objects_by_name[obj_name])
171-
self.mgr.set_store(self.store_prefix + obj_name, json.dumps(j))
176+
serialized_obj = self.tlsobject_class.to_json(self.objects_by_name[obj_name])
177+
self._set_store(obj_name, serialized_obj)
172178
return True
173179
else:
174180
raise TLSObjectException(f'Attempted to remove {self.tlsobject_class.__name__.lower()} for unknown obj_name {obj_name}')
175181
return False
176182

177183
def _validate_tlsobject_name(self, obj_name: str, service_name: Optional[str] = None, host: Optional[str] = None) -> None:
178184
cred_type = self.tlsobject_class.__name__.lower()
179-
if obj_name not in self.objects_by_name.keys():
185+
if obj_name not in self.objects_by_name:
180186
raise TLSObjectException(f'Attempted to access {cred_type} for unknown TLS object name {obj_name}')
181187
if obj_name in self.host_scoped_objects and not host:
182188
raise TLSObjectException(f'Need host to access {cred_type} for TLS object {obj_name}')
@@ -205,39 +211,72 @@ def list_tlsobjects(self) -> List[Tuple[str, TLSObjectProtocol, Optional[str]]]:
205211
return tlsobjects
206212

207213
def load(self) -> None:
214+
215+
def _kv_preview(key: str, raw: str, n: int = 20) -> Tuple[str, int, str]:
216+
# Safe, short, escaped preview for logs
217+
return key, (len(raw) if raw else 0), (raw[:n] if raw else "")
218+
208219
for k, v in self.mgr.get_store_prefix(self.store_prefix).items():
209220
obj_name = k[len(self.store_prefix):]
210221
is_cephadm_signed_object = self.cephadm_signed_object_checker(obj_name)
211222
if not is_cephadm_signed_object and obj_name not in self.objects_by_name:
212-
logger.warning(f"TLSObjectStore: Discarding unknown obj_name '{obj_name}'")
223+
logger.warning("TLSObjectStore: Discarding unknown obj_name %r", obj_name)
213224
continue
214225

215226
try:
216227
tls_object_targets = json.loads(v)
228+
if not isinstance(tls_object_targets, dict):
229+
key_preview, vlen, vstart = _kv_preview(k, v)
230+
logger.error(
231+
"TLSObjectStore: Invalid data structure for object %r. "
232+
"Expected dict but got %s. key=%r, len=%d, startswith=%r",
233+
obj_name, type(tls_object_targets).__name__,
234+
key_preview, vlen, vstart,
235+
)
236+
continue
217237
except json.JSONDecodeError as e:
238+
key_preview, vlen, vstart = _kv_preview(k, v)
218239
logger.warning(
219-
f"TLSObjectStore: Cannot parse JSON for '{obj_name}': "
220-
f"key={k}, len={len(v) if v else 0}, startswith={v[:20]!r}, error={e}"
240+
"TLSObjectStore: Cannot parse JSON for %r: key=%r, len=%d, startswith=%r, error=%r",
241+
obj_name, key_preview, vlen, vstart, e,
221242
)
222243
continue
223244
except Exception as e:
245+
key_preview, vlen, vstart = _kv_preview(k, v)
224246
logger.error(
225-
f"TLSObjectStore: Unexpected error happened while trying to parse JSON for '{obj_name}': "
226-
f"key={k}, len={len(v) if v else 0}, startswith={v[:20]!r}, error={e}"
247+
"TLSObjectStore: Unexpected error while parsing %r: key=%r, len=%d, startswith=%r, error=%r",
248+
obj_name, key_preview, vlen, vstart, e,
249+
exc_info=True, # include traceback for unexpected errors
227250
)
228251
continue
229252

230253
if is_cephadm_signed_object or (obj_name in self.service_scoped_objects) or (obj_name in self.host_scoped_objects):
231254
if is_cephadm_signed_object and obj_name not in self.host_scoped_objects:
232255
self.host_scoped_objects.append(obj_name)
233256
self.objects_by_name[obj_name] = {}
234-
for target in tls_object_targets:
235-
tlsobject = self.tlsobject_class.from_json(tls_object_targets[target])
236-
if tlsobject:
237-
self.objects_by_name[obj_name][target] = tlsobject
257+
for target, payload in tls_object_targets.items():
258+
try:
259+
tlsobject = self.tlsobject_class.from_json(payload)
260+
if tlsobject: # skip tombstones
261+
self.objects_by_name[obj_name][target] = tlsobject
262+
else:
263+
logger.debug("TLSObjectStore: Skipping tombstone for %r (target %r)", obj_name, target)
264+
except Exception as e:
265+
key_preview, vlen, vstart = _kv_preview(k, str(payload))
266+
logger.warning(
267+
"TLSObjectStore: Failed to decode scoped TLS object %r (target %r): %r. "
268+
"key=%r, len=%d, startswith=%r",
269+
obj_name, target, e, key_preview, vlen, vstart
270+
)
238271
elif obj_name in self.global_scoped_objects:
239-
tlsobject = self.tlsobject_class.from_json(tls_object_targets)
240-
if tlsobject:
241-
self.objects_by_name[obj_name] = tlsobject
272+
try:
273+
self.objects_by_name[obj_name] = self.tlsobject_class.from_json(tls_object_targets)
274+
except Exception as e:
275+
key_preview, vlen, vstart = _kv_preview(k, v)
276+
logger.warning(
277+
"TLSObjectStore: Failed to decode global TLS object %r: %r. "
278+
"key=%r, len=%d, startswith=%r",
279+
obj_name, e, key_preview, vlen, vstart
280+
)
242281
else:
243-
logger.error(f"TLSObjectStore: Found a known TLS object name {obj_name} with unknown scope!")
282+
logger.error("TLSObjectStore: Found a known TLS object name %r with unknown scope!", obj_name)

src/pybind/mgr/cephadm/tlsobject_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,6 @@ def from_json(cls, data: Dict[str, str]) -> 'PrivKey':
156156
user_made = parse_bool(data.get('user_made', False))
157157
editable = parse_bool(data.get('editable', False))
158158
except ValueError as e:
159-
raise TLSObjectException(f'Expected field in Cert object to be bool but got another type: {e}')
159+
raise TLSObjectException(f'Expected field in PrivKey object to be bool but got another type: {e}')
160160

161161
return cls(key=key, user_made=user_made, editable=editable)

0 commit comments

Comments
 (0)