1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 """Serializer abstraction module
31
32 This module introduces a simple abstraction over the serialization
33 backend (currently json).
34
35 """
36
37
38
39
40
41 import re
42
43
44
45
46
47
48 import simplejson
49
50 from ganeti import errors
51 from ganeti import utils
52 from ganeti import constants
53
54 _RE_EOLSP = re.compile("[ \t]+$", re.MULTILINE)
55
56
57 -def DumpJson(data, private_encoder=None):
58 """Serialize a given object.
59
60 @param data: the data to serialize
61 @return: the string representation of data
62 @param private_encoder: specify L{serializer.EncodeWithPrivateFields} if you
63 require the produced JSON to also contain private
64 parameters. Otherwise, they will encode to null.
65
66 """
67 if private_encoder is None:
68
69 private_encoder = EncodeWithoutPrivateFields
70 encoded = simplejson.dumps(data, default=private_encoder)
71
72 txt = _RE_EOLSP.sub("", encoded)
73 if not txt.endswith("\n"):
74 txt += "\n"
75
76 return txt
77
78
80 """Unserialize data from a string.
81
82 @param txt: the json-encoded form
83 @return: the original data
84 @raise JSONDecodeError: if L{txt} is not a valid JSON document
85
86 """
87 values = simplejson.loads(txt)
88
89
90 WrapPrivateValues(values)
91
92 return values
93
94
96 """Crawl a JSON decoded structure for private values and wrap them.
97
98 @param json: the json-decoded value to protect.
99
100 """
101
102
103 todo = [json]
104
105 while todo:
106 data = todo.pop()
107
108 if isinstance(data, list):
109 for item in data:
110 todo.append(item)
111 elif isinstance(data, dict):
112
113
114
115
116
117
118 for field in data:
119 value = data[field]
120 if field in constants.PRIVATE_PARAMETERS_BLACKLIST:
121 if not field.endswith("_cluster"):
122 data[field] = PrivateDict(value)
123 elif data[field] is not None:
124 for os in data[field]:
125 value[os] = PrivateDict(value[os])
126 else:
127 todo.append(value)
128 else:
129 pass
130
131
132 -def DumpSignedJson(data, key, salt=None, key_selector=None,
133 private_encoder=None):
134 """Serialize a given object and authenticate it.
135
136 @param data: the data to serialize
137 @param key: shared hmac key
138 @param key_selector: name/id that identifies the key (in case there are
139 multiple keys in use, e.g. in a multi-cluster environment)
140 @param private_encoder: see L{DumpJson}
141 @return: the string representation of data signed by the hmac key
142
143 """
144 txt = DumpJson(data, private_encoder=private_encoder)
145 if salt is None:
146 salt = ""
147 signed_dict = {
148 "msg": txt,
149 "salt": salt,
150 }
151
152 if key_selector:
153 signed_dict["key_selector"] = key_selector
154 else:
155 key_selector = ""
156
157 signed_dict["hmac"] = utils.Sha1Hmac(key, txt, salt=salt + key_selector)
158
159 return DumpJson(signed_dict)
160
161
163 """Verify that a given message was signed with the given key, and load it.
164
165 @param txt: json-encoded hmac-signed message
166 @param key: the shared hmac key or a callable taking one argument (the key
167 selector), which returns the hmac key belonging to the key selector.
168 Typical usage is to pass a reference to the get method of a dict.
169 @rtype: tuple of original data, string
170 @return: original data, salt
171 @raises errors.SignatureError: if the message signature doesn't verify
172
173 """
174 signed_dict = LoadJson(txt)
175
176 WrapPrivateValues(signed_dict)
177
178 if not isinstance(signed_dict, dict):
179 raise errors.SignatureError("Invalid external message")
180 try:
181 msg = signed_dict["msg"]
182 salt = signed_dict["salt"]
183 hmac_sign = signed_dict["hmac"]
184 except KeyError:
185 raise errors.SignatureError("Invalid external message")
186
187 if callable(key):
188
189 key_selector = signed_dict.get("key_selector", None)
190 hmac_key = key(key_selector)
191 if not hmac_key:
192 raise errors.SignatureError("No key with key selector '%s' found" %
193 key_selector)
194 else:
195 key_selector = ""
196 hmac_key = key
197
198 if not utils.VerifySha1Hmac(hmac_key, msg, hmac_sign,
199 salt=salt + key_selector):
200 raise errors.SignatureError("Invalid Signature")
201
202 return LoadJson(msg), salt
203
204
206 """Parses and verifies JSON data.
207
208 @type raw: string
209 @param raw: Input data in JSON format
210 @type verify_fn: callable
211 @param verify_fn: Verification function, usually from L{ht}
212 @return: De-serialized data
213
214 """
215 try:
216 data = LoadJson(raw)
217 except Exception, err:
218 raise errors.ParseError("Can't parse input data: %s" % err)
219
220 if not verify_fn(data):
221 raise errors.ParseError("Data does not match expected format: %s" %
222 verify_fn)
223
224 return data
225
226
227 Dump = DumpJson
228 Load = LoadJson
229 DumpSigned = DumpSignedJson
230 LoadSigned = LoadSignedJson
231
232
234 """Wrap a value so it is hard to leak it accidentally.
235
236 >>> x = Private("foo")
237 >>> print "Value: %s" % x
238 Value: <redacted>
239 >>> print "Value: {0}".format(x)
240 Value: <redacted>
241 >>> x.upper() == "FOO"
242 True
243
244 """
245 - def __init__(self, item, descr="redacted"):
246 if isinstance(item, Private):
247 raise ValueError("Attempted to nest Private values.")
248 self._item = item
249 self._descr = descr
250
252 "Return the wrapped value."
253 return self._item
254
256 return "<%s>" % (self._descr, )
257
259 return "Private(?, descr=%r)" % (self._descr, )
260
261
262
263
264
266 if isinstance(other, Private):
267 return self._item == other._item
268 else:
269 return self._item == other
270
272 return hash(self._item)
273
276
278 return Private(getattr(self._item, attr),
279 descr="%s.%s" % (self._descr, attr))
280
282 return Private(self._item(*args, **kwargs),
283 descr="%s()" % (self._descr, ))
284
285
286
287
290
292 return bool(self._item)
293
294
295
296 __slots__ = ["_item", "_descr"]
297
298
300 """A dictionary that turns its values to private fields.
301
302 >>> PrivateDict()
303 {}
304 >>> supersekkrit = PrivateDict({"password": "foobar"})
305 >>> print supersekkrit["password"]
306 <password>
307 >>> supersekkrit["password"].Get()
308 'foobar'
309 >>> supersekkrit.GetPrivate("password")
310 'foobar'
311 >>> supersekkrit["user"] = "eggspam"
312 >>> supersekkrit.Unprivate()
313 {'password': 'foobar', 'user': 'eggspam'}
314
315 """
319
321 if not isinstance(value, Private):
322 if not isinstance(item, dict):
323 value = Private(value, descr=item)
324 else:
325 value = PrivateDict(value)
326 dict.__setitem__(self, item, value)
327
328
329
330
331
332 - def update(self, other=None, **kwargs):
333
334 if other is None:
335 pass
336 elif hasattr(other, 'iteritems'):
337 for k, v in other.iteritems():
338 self[k] = v
339 elif hasattr(other, 'keys'):
340 for k in other.keys():
341 self[k] = other[k]
342 else:
343 for k, v in other:
344 self[k] = v
345 if kwargs:
346 self.update(kwargs)
347
349 """Like dict.get, but extracting the value in the process.
350
351 Arguments are semantically equivalent to ``dict.get``
352
353 >>> PrivateDict({"foo": "bar"}).GetPrivate("foo")
354 'bar'
355 >>> PrivateDict({"foo": "bar"}).GetPrivate("baz", "spam")
356 'spam'
357
358 """
359 if len(args) == 1:
360 key, = args
361 return self[key].Get()
362 elif len(args) == 2:
363 key, default = args
364 if key not in self:
365 return default
366 else:
367 return self[key].Get()
368 else:
369 raise TypeError("GetPrivate() takes 2 arguments (%d given)" % len(args))
370
372 """Turn this dict of Private() values to a dict of values.
373
374 >>> PrivateDict({"foo": "bar"}).Unprivate()
375 {'foo': 'bar'}
376
377 @rtype: dict
378
379 """
380 returndict = {}
381 for key in self:
382 returndict[key] = self[key].Get()
383 return returndict
384
385
387 if isinstance(obj, Private):
388 return None
389 raise TypeError(repr(obj) + " is not JSON serializable")
390
391
393 if isinstance(obj, Private):
394 return obj.Get()
395 raise TypeError(repr(obj) + " is not JSON serializable")
396