root / tools / sanitize-config @ fcad7225
History | View | Annotate | Download (8.4 kB)
1 |
#!/usr/bin/python |
---|---|
2 |
# |
3 |
|
4 |
# Copyright (C) 2010 Google Inc. |
5 |
# |
6 |
# This program is free software; you can redistribute it and/or modify |
7 |
# it under the terms of the GNU General Public License as published by |
8 |
# the Free Software Foundation; either version 2 of the License, or |
9 |
# (at your option) any later version. |
10 |
# |
11 |
# This program is distributed in the hope that it will be useful, but |
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 |
# General Public License for more details. |
15 |
# |
16 |
# You should have received a copy of the GNU General Public License |
17 |
# along with this program; if not, write to the Free Software |
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
19 |
# 02110-1301, USA. |
20 |
|
21 |
|
22 |
# pylint: disable-msg=C0103 |
23 |
|
24 |
"""Tool to sanitize/randomize the configuration file. |
25 |
|
26 |
""" |
27 |
|
28 |
import sys |
29 |
import os |
30 |
import os.path |
31 |
import optparse |
32 |
|
33 |
from ganeti import constants |
34 |
from ganeti import serializer |
35 |
from ganeti import utils |
36 |
from ganeti import cli |
37 |
from ganeti.cli import cli_option |
38 |
|
39 |
|
40 |
OPTS = [ |
41 |
cli.VERBOSE_OPT, |
42 |
cli_option("--path", help="Convert this configuration file" |
43 |
" instead of '%s'" % constants.CLUSTER_CONF_FILE, |
44 |
default=constants.CLUSTER_CONF_FILE, dest="CONFIG_DATA_PATH"), |
45 |
cli_option("--sanitize-names", default="yes", type="bool", |
46 |
help="Randomize the cluster, node and instance names [yes]"), |
47 |
cli_option("--sanitize-ips", default="yes", type="bool", |
48 |
help="Randomize the cluster, node and instance IPs [yes]"), |
49 |
cli_option("--sanitize-lvs", default="no", type="bool", |
50 |
help="Randomize the LV names (for old clusters) [no]"), |
51 |
cli_option("--sanitize-os-names", default="yes", type="bool", |
52 |
help="Randomize the OS names [yes]"), |
53 |
cli_option("--no-randomization", default=False, action="store_true", |
54 |
help="Disable all name randomization (only randomize secrets)"), |
55 |
cli_option("--base-domain", default="example.com", |
56 |
help="The base domain used for new names [example.com]"), |
57 |
] |
58 |
|
59 |
|
60 |
def Error(txt, *args): |
61 |
"""Writes a message to standard error and exits. |
62 |
|
63 |
""" |
64 |
cli.ToStderr(txt, *args) |
65 |
sys.exit(1) |
66 |
|
67 |
|
68 |
def GenerateNameMap(opts, names, base): |
69 |
"""For a given set of names, generate a list of sane new names. |
70 |
|
71 |
""" |
72 |
names = utils.NiceSort(names) |
73 |
name_map = {} |
74 |
for idx, old_name in enumerate(names): |
75 |
new_name = "%s%d.%s" % (base, idx + 1, opts.base_domain) |
76 |
if new_name in names: |
77 |
Error("Name conflict for %s: %s already exists", base, new_name) |
78 |
name_map[old_name] = new_name |
79 |
return name_map |
80 |
|
81 |
|
82 |
def SanitizeSecrets(opts, cfg): # pylint: disable-msg=W0613 |
83 |
"""Cleanup configuration secrets. |
84 |
|
85 |
""" |
86 |
cfg["cluster"]["rsahostkeypub"] = "" |
87 |
for instance in cfg["instances"].values(): |
88 |
for disk in instance["disks"]: |
89 |
RandomizeDiskSecrets(disk) |
90 |
|
91 |
|
92 |
def SanitizeCluster(opts, cfg): |
93 |
"""Sanitize the cluster names. |
94 |
|
95 |
""" |
96 |
cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain |
97 |
|
98 |
|
99 |
def SanitizeNodes(opts, cfg): |
100 |
"""Sanitize node names. |
101 |
|
102 |
""" |
103 |
old_names = cfg["nodes"].keys() |
104 |
old_map = GenerateNameMap(opts, old_names, "node") |
105 |
|
106 |
# rename nodes |
107 |
RenameDictKeys(cfg["nodes"], old_map, True) |
108 |
|
109 |
# update master node |
110 |
cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]] |
111 |
|
112 |
# update instance configuration |
113 |
for instance in cfg["instances"].values(): |
114 |
instance["primary_node"] = old_map[instance["primary_node"]] |
115 |
for disk in instance["disks"]: |
116 |
RenameDiskNodes(disk, old_map) |
117 |
|
118 |
|
119 |
def SanitizeInstances(opts, cfg): |
120 |
"""Sanitize instance names. |
121 |
|
122 |
""" |
123 |
old_names = cfg["instances"].keys() |
124 |
old_map = GenerateNameMap(opts, old_names, "instance") |
125 |
|
126 |
RenameDictKeys(cfg["instances"], old_map, True) |
127 |
|
128 |
|
129 |
def SanitizeIps(opts, cfg): # pylint: disable-msg=W0613 |
130 |
"""Sanitize the IP names. |
131 |
|
132 |
@note: we're interested in obscuring the old IPs, not in generating |
133 |
actually valid new IPs, so we chose to simply put IPv4 |
134 |
addresses, irrelevant of whether IPv6 or IPv4 addresses existed |
135 |
before. |
136 |
|
137 |
""" |
138 |
def _Get(old): |
139 |
if old in ip_map: |
140 |
return ip_map[old] |
141 |
idx = len(ip_map) + 1 |
142 |
rest, d_octet = divmod(idx, 256) |
143 |
rest, c_octet = divmod(rest, 256) |
144 |
rest, b_octet = divmod(rest, 256) |
145 |
if rest > 0: |
146 |
Error("Too many IPs!") |
147 |
new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet) |
148 |
ip_map[old] = new_ip |
149 |
return new_ip |
150 |
|
151 |
ip_map = {} |
152 |
|
153 |
cfg["cluster"]["master_ip"] = _Get(cfg["cluster"]["master_ip"]) |
154 |
for node in cfg["nodes"].values(): |
155 |
node["primary_ip"] = _Get(node["primary_ip"]) |
156 |
node["secondary_ip"] = _Get(node["secondary_ip"]) |
157 |
|
158 |
for instance in cfg["instances"].values(): |
159 |
for nic in instance["nics"]: |
160 |
if "ip" in nic and nic["ip"]: |
161 |
nic["ip"] = _Get(nic["ip"]) |
162 |
|
163 |
|
164 |
def SanitizeOsNames(opts, cfg): # pylint: disable-msg=W0613 |
165 |
"""Sanitize the OS names. |
166 |
|
167 |
""" |
168 |
def _Get(old): |
169 |
if old in os_map: |
170 |
return os_map[old] |
171 |
os_map[old] = "ganeti-os%d" % (len(os_map) + 1) |
172 |
return os_map[old] |
173 |
|
174 |
os_map = {} |
175 |
for instance in cfg["instances"].values(): |
176 |
instance["os"] = _Get(instance["os"]) |
177 |
|
178 |
if "os_hvp" in cfg["cluster"]: |
179 |
for os_name in cfg["cluster"]["os_hvp"]: |
180 |
# force population of the entire os map |
181 |
_Get(os_name) |
182 |
RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False) |
183 |
|
184 |
|
185 |
def SanitizeDisks(opts, cfg): # pylint: disable-msg=W0613 |
186 |
"""Cleanup disks disks. |
187 |
|
188 |
""" |
189 |
def _Get(old): |
190 |
if old in lv_map: |
191 |
return old |
192 |
lv_map[old] = utils.NewUUID() |
193 |
return lv_map[old] |
194 |
|
195 |
def helper(disk): |
196 |
if "children" in disk and disk["children"]: |
197 |
for child in disk["children"]: |
198 |
helper(child) |
199 |
|
200 |
if disk["dev_type"] == constants.LD_DRBD8: |
201 |
if "physical_id" in disk: |
202 |
del disk["physical_id"] |
203 |
|
204 |
if disk["dev_type"] == constants.LD_LV and opts.sanitize_lvs: |
205 |
disk["logical_id"][1] = _Get(disk["logical_id"][1]) |
206 |
disk["physical_id"][1] = disk["logical_id"][1] |
207 |
|
208 |
lv_map = {} |
209 |
|
210 |
for instance in cfg["instances"].values(): |
211 |
for disk in instance["disks"]: |
212 |
helper(disk) |
213 |
|
214 |
|
215 |
def RandomizeDiskSecrets(disk): |
216 |
"""Randomize a disks' secrets (if any). |
217 |
|
218 |
""" |
219 |
if "children" in disk and disk["children"]: |
220 |
for child in disk["children"]: |
221 |
RandomizeDiskSecrets(child) |
222 |
|
223 |
# only disk type to contain secrets is the drbd one |
224 |
if disk["dev_type"] == constants.LD_DRBD8: |
225 |
disk["logical_id"][5] = utils.GenerateSecret() |
226 |
|
227 |
|
228 |
def RenameDiskNodes(disk, node_map): |
229 |
"""Rename nodes in the disk config. |
230 |
|
231 |
""" |
232 |
if "children" in disk and disk["children"]: |
233 |
for child in disk["children"]: |
234 |
RenameDiskNodes(child, node_map) |
235 |
|
236 |
# only disk type to contain nodes is the drbd one |
237 |
if disk["dev_type"] == constants.LD_DRBD8: |
238 |
lid = disk["logical_id"] |
239 |
lid[0] = node_map[lid[0]] |
240 |
lid[1] = node_map[lid[1]] |
241 |
|
242 |
|
243 |
def RenameDictKeys(a_dict, name_map, update_name): |
244 |
"""Rename the dictionary keys based on a name map. |
245 |
|
246 |
""" |
247 |
for old_name in a_dict.keys(): |
248 |
new_name = name_map[old_name] |
249 |
a_dict[new_name] = a_dict[old_name] |
250 |
del a_dict[old_name] |
251 |
if update_name: |
252 |
a_dict[new_name]["name"] = new_name |
253 |
|
254 |
|
255 |
def main(): |
256 |
"""Main program. |
257 |
|
258 |
""" |
259 |
# Option parsing |
260 |
parser = optparse.OptionParser(usage="%prog [--verbose] output_file") |
261 |
|
262 |
for o in OPTS: |
263 |
parser.add_option(o) |
264 |
|
265 |
(opts, args) = parser.parse_args() |
266 |
if opts.no_randomization: |
267 |
opts.sanitize_names = opts.sanitize_ips = opts.sanitize_os_names = \ |
268 |
opts.sanitize_lvs = False |
269 |
|
270 |
# Option checking |
271 |
if len(args) != 1: |
272 |
Error("Usage: sanitize-config [options] {<output_file> | -}") |
273 |
|
274 |
# Check whether it's a Ganeti configuration directory |
275 |
if not os.path.isfile(opts.CONFIG_DATA_PATH): |
276 |
Error("Cannot find Ganeti configuration file %s", opts.CONFIG_DATA_PATH) |
277 |
|
278 |
config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH)) |
279 |
|
280 |
# first, do some disk cleanup: remove DRBD physical_ids, since it |
281 |
# contains both IPs (which we want changed) and the DRBD secret, and |
282 |
# it's not needed for normal functioning, and randomize LVM names |
283 |
SanitizeDisks(opts, config_data) |
284 |
|
285 |
SanitizeSecrets(opts, config_data) |
286 |
|
287 |
if opts.sanitize_names: |
288 |
SanitizeCluster(opts, config_data) |
289 |
SanitizeNodes(opts, config_data) |
290 |
SanitizeInstances(opts, config_data) |
291 |
|
292 |
if opts.sanitize_ips: |
293 |
SanitizeIps(opts, config_data) |
294 |
|
295 |
if opts.sanitize_os_names: |
296 |
SanitizeOsNames(opts, config_data) |
297 |
|
298 |
data = serializer.DumpJson(config_data) |
299 |
if args[0] == "-": |
300 |
sys.stdout.write(data) |
301 |
else: |
302 |
utils.WriteFile(file_name=args[0], |
303 |
data=data, |
304 |
mode=0600, |
305 |
backup=True) |
306 |
|
307 |
if __name__ == "__main__": |
308 |
main() |