4 # Copyright (C) 2010 Google Inc.
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.
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.
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
22 # pylint: disable=C0103
24 """Tool to sanitize/randomize the configuration file.
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
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]"),
60 def Error(txt, *args):
61 """Writes a message to standard error and exits.
64 cli.ToStderr(txt, *args)
68 def GenerateNameMap(opts, names, base):
69 """For a given set of names, generate a list of sane new names.
72 names = utils.NiceSort(names)
74 for idx, old_name in enumerate(names):
75 new_name = "%s%d.%s" % (base, idx + 1, opts.base_domain)
77 Error("Name conflict for %s: %s already exists", base, new_name)
78 name_map[old_name] = new_name
82 def SanitizeSecrets(opts, cfg): # pylint: disable=W0613
83 """Cleanup configuration secrets.
86 cfg["cluster"]["rsahostkeypub"] = ""
87 for instance in cfg["instances"].values():
88 for disk in instance["disks"]:
89 RandomizeDiskSecrets(disk)
92 def SanitizeCluster(opts, cfg):
93 """Sanitize the cluster names.
96 cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain
99 def SanitizeNodes(opts, cfg):
100 """Sanitize node names.
103 old_names = cfg["nodes"].keys()
104 old_map = GenerateNameMap(opts, old_names, "node")
107 RenameDictKeys(cfg["nodes"], old_map, True)
110 cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]]
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)
119 def SanitizeInstances(opts, cfg):
120 """Sanitize instance names.
123 old_names = cfg["instances"].keys()
124 old_map = GenerateNameMap(opts, old_names, "instance")
126 RenameDictKeys(cfg["instances"], old_map, True)
129 def SanitizeIps(opts, cfg): # pylint: disable=W0613
130 """Sanitize the IP names.
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
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)
146 Error("Too many IPs!")
147 new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet)
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"])
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"])
164 def SanitizeOsNames(opts, cfg): # pylint: disable=W0613
165 """Sanitize the OS names.
171 os_map[old] = "ganeti-os%d" % (len(os_map) + 1)
175 for instance in cfg["instances"].values():
176 instance["os"] = _Get(instance["os"])
178 if "os_hvp" in cfg["cluster"]:
179 for os_name in cfg["cluster"]["os_hvp"]:
180 # force population of the entire os map
182 RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False)
185 def SanitizeDisks(opts, cfg): # pylint: disable=W0613
186 """Cleanup disks disks.
192 lv_map[old] = utils.NewUUID()
196 if "children" in disk and disk["children"]:
197 for child in disk["children"]:
200 if disk["dev_type"] == constants.LD_DRBD8:
201 if "physical_id" in disk:
202 del disk["physical_id"]
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]
210 for instance in cfg["instances"].values():
211 for disk in instance["disks"]:
215 def RandomizeDiskSecrets(disk):
216 """Randomize a disks' secrets (if any).
219 if "children" in disk and disk["children"]:
220 for child in disk["children"]:
221 RandomizeDiskSecrets(child)
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()
228 def RenameDiskNodes(disk, node_map):
229 """Rename nodes in the disk config.
232 if "children" in disk and disk["children"]:
233 for child in disk["children"]:
234 RenameDiskNodes(child, node_map)
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]]
243 def RenameDictKeys(a_dict, name_map, update_name):
244 """Rename the dictionary keys based on a name map.
247 for old_name in a_dict.keys():
248 new_name = name_map[old_name]
249 a_dict[new_name] = a_dict[old_name]
252 a_dict[new_name]["name"] = new_name
260 parser = optparse.OptionParser(usage="%prog [--verbose] output_file")
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
272 Error("Usage: sanitize-config [options] {<output_file> | -}")
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)
278 config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH))
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)
285 SanitizeSecrets(opts, config_data)
287 if opts.sanitize_names:
288 SanitizeCluster(opts, config_data)
289 SanitizeNodes(opts, config_data)
290 SanitizeInstances(opts, config_data)
292 if opts.sanitize_ips:
293 SanitizeIps(opts, config_data)
295 if opts.sanitize_os_names:
296 SanitizeOsNames(opts, config_data)
298 data = serializer.DumpJson(config_data)
300 sys.stdout.write(data)
302 utils.WriteFile(file_name=args[0],
307 if __name__ == "__main__":