|
1 | 1 | from typing import (Any, |
2 | 2 | Dict, |
3 | | - Union, |
4 | 3 | List, |
5 | 4 | Tuple, |
6 | 5 | Optional, |
@@ -33,12 +32,22 @@ def __init__(self, mgr: 'CephadmOrchestrator', |
33 | 32 | self.mgr: CephadmOrchestrator = mgr |
34 | 33 | self.cephadm_signed_object_checker = cephadm_signed_obj_checker |
35 | 34 | 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} |
38 | 35 | self.service_scoped_objects = known_objects_names[TLSObjectScope.SERVICE] |
39 | 36 | self.host_scoped_objects = known_objects_names[TLSObjectScope.HOST] |
40 | 37 | self.global_scoped_objects = known_objects_names[TLSObjectScope.GLOBAL] |
41 | 38 | 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)) |
42 | 51 |
|
43 | 52 | def register_object_name(self, obj_name: str, scope: TLSObjectScope) -> None: |
44 | 53 | """ |
@@ -105,18 +114,16 @@ def save_tlsobject(self, obj_name: str, |
105 | 114 | self._validate_tlsobject_name(obj_name, service_name, host) |
106 | 115 | tlsobject = self.tlsobject_class(tlsobject, user_made, editable) |
107 | 116 | scope, target = self.get_tlsobject_scope_and_target(obj_name, service_name, host) |
108 | | - j: Union[str, Dict[Any, Any], None] = None |
109 | 117 | if scope in (TLSObjectScope.SERVICE, TLSObjectScope.HOST): |
110 | 118 | self.objects_by_name[obj_name][target] = tlsobject |
111 | | - j = { |
| 119 | + serialized_targets = { |
112 | 120 | key: self.tlsobject_class.to_json(self.objects_by_name[obj_name][key]) |
113 | 121 | for key in self.objects_by_name[obj_name] |
114 | 122 | } |
115 | | - self.mgr.set_store(self.store_prefix + obj_name, json.dumps(j)) |
| 123 | + self._set_store(obj_name, serialized_targets) |
116 | 124 | elif scope == TLSObjectScope.GLOBAL: |
117 | 125 | 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)) |
120 | 127 | else: |
121 | 128 | logger.error(f'Trying to save TLS object name {obj_name} with a not-supported/unknown TLSObjectScope scope {scope.value}') |
122 | 129 |
|
@@ -155,28 +162,27 @@ def rm_tlsobject(self, obj_name: str, service_name: Optional[str] = None, host: |
155 | 162 | """ |
156 | 163 | self._validate_tlsobject_name(obj_name, service_name, host) |
157 | 164 | scope, target = self.get_tlsobject_scope_and_target(obj_name, service_name, host) |
158 | | - j: Union[str, Dict[Any, Any], None] = None |
159 | 165 | if scope in (TLSObjectScope.SERVICE, TLSObjectScope.HOST): |
160 | 166 | if obj_name in self.objects_by_name and target in self.objects_by_name[obj_name]: |
161 | 167 | del self.objects_by_name[obj_name][target] |
162 | | - j = { |
| 168 | + serialized_targets = { |
163 | 169 | key: self.tlsobject_class.to_json(self.objects_by_name[obj_name][key]) |
164 | 170 | for key in self.objects_by_name[obj_name] |
165 | 171 | } |
166 | | - self.mgr.set_store(self.store_prefix + obj_name, json.dumps(j)) |
| 172 | + self._set_store(obj_name, serialized_targets) |
167 | 173 | return True |
168 | 174 | elif scope == TLSObjectScope.GLOBAL: |
169 | 175 | 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) |
172 | 178 | return True |
173 | 179 | else: |
174 | 180 | raise TLSObjectException(f'Attempted to remove {self.tlsobject_class.__name__.lower()} for unknown obj_name {obj_name}') |
175 | 181 | return False |
176 | 182 |
|
177 | 183 | def _validate_tlsobject_name(self, obj_name: str, service_name: Optional[str] = None, host: Optional[str] = None) -> None: |
178 | 184 | 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: |
180 | 186 | raise TLSObjectException(f'Attempted to access {cred_type} for unknown TLS object name {obj_name}') |
181 | 187 | if obj_name in self.host_scoped_objects and not host: |
182 | 188 | 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]]]: |
205 | 211 | return tlsobjects |
206 | 212 |
|
207 | 213 | 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 | + |
208 | 219 | for k, v in self.mgr.get_store_prefix(self.store_prefix).items(): |
209 | 220 | obj_name = k[len(self.store_prefix):] |
210 | 221 | is_cephadm_signed_object = self.cephadm_signed_object_checker(obj_name) |
211 | 222 | 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) |
213 | 224 | continue |
214 | 225 |
|
215 | 226 | try: |
216 | 227 | 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 |
217 | 237 | except json.JSONDecodeError as e: |
| 238 | + key_preview, vlen, vstart = _kv_preview(k, v) |
218 | 239 | 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, |
221 | 242 | ) |
222 | 243 | continue |
223 | 244 | except Exception as e: |
| 245 | + key_preview, vlen, vstart = _kv_preview(k, v) |
224 | 246 | 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 |
227 | 250 | ) |
228 | 251 | continue |
229 | 252 |
|
230 | 253 | if is_cephadm_signed_object or (obj_name in self.service_scoped_objects) or (obj_name in self.host_scoped_objects): |
231 | 254 | if is_cephadm_signed_object and obj_name not in self.host_scoped_objects: |
232 | 255 | self.host_scoped_objects.append(obj_name) |
233 | 256 | 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 | + ) |
238 | 271 | 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 | + ) |
242 | 281 | 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) |
0 commit comments