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 pathutils
37 from ganeti import cli
38 from ganeti.cli import cli_option
43 cli_option("--path", help="Convert this configuration file"
44 " instead of '%s'" % pathutils.CLUSTER_CONF_FILE,
45 default=pathutils.CLUSTER_CONF_FILE, dest="CONFIG_DATA_PATH"),
46 cli_option("--sanitize-names", default="yes", type="bool",
47 help="Randomize the cluster, node and instance names [yes]"),
48 cli_option("--sanitize-ips", default="yes", type="bool",
49 help="Randomize the cluster, node and instance IPs [yes]"),
50 cli_option("--sanitize-lvs", default="no", type="bool",
51 help="Randomize the LV names (for old clusters) [no]"),
52 cli_option("--sanitize-os-names", default="yes", type="bool",
53 help="Randomize the OS names [yes]"),
54 cli_option("--no-randomization", default=False, action="store_true",
55 help="Disable all name randomization (only randomize secrets)"),
56 cli_option("--base-domain", default="example.com",
57 help="The base domain used for new names [example.com]"),
61 def Error(txt, *args):
62 """Writes a message to standard error and exits.
65 cli.ToStderr(txt, *args)
69 def GenerateNameMap(opts, names, base):
70 """For a given set of names, generate a list of sane new names.
73 names = utils.NiceSort(names)
75 for idx, old_name in enumerate(names):
76 new_name = "%s%d.%s" % (base, idx + 1, opts.base_domain)
78 Error("Name conflict for %s: %s already exists", base, new_name)
79 name_map[old_name] = new_name
83 def SanitizeSecrets(opts, cfg): # pylint: disable=W0613
84 """Cleanup configuration secrets.
87 cfg["cluster"]["rsahostkeypub"] = ""
88 for instance in cfg["instances"].values():
89 for disk in instance["disks"]:
90 RandomizeDiskSecrets(disk)
93 def SanitizeCluster(opts, cfg):
94 """Sanitize the cluster names.
97 cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain
100 def SanitizeNodes(opts, cfg):
101 """Sanitize node names.
104 old_names = cfg["nodes"].keys()
105 old_map = GenerateNameMap(opts, old_names, "node")
108 RenameDictKeys(cfg["nodes"], old_map, True)
111 cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]]
113 # update instance configuration
114 for instance in cfg["instances"].values():
115 instance["primary_node"] = old_map[instance["primary_node"]]
116 for disk in instance["disks"]:
117 RenameDiskNodes(disk, old_map)
120 def SanitizeInstances(opts, cfg):
121 """Sanitize instance names.
124 old_names = cfg["instances"].keys()
125 old_map = GenerateNameMap(opts, old_names, "instance")
127 RenameDictKeys(cfg["instances"], old_map, True)
130 def SanitizeIps(opts, cfg): # pylint: disable=W0613
131 """Sanitize the IP names.
133 @note: we're interested in obscuring the old IPs, not in generating
134 actually valid new IPs, so we chose to simply put IPv4
135 addresses, irrelevant of whether IPv6 or IPv4 addresses existed
142 idx = len(ip_map) + 1
143 rest, d_octet = divmod(idx, 256)
144 rest, c_octet = divmod(rest, 256)
145 rest, b_octet = divmod(rest, 256)
147 Error("Too many IPs!")
148 new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet)
154 cfg["cluster"]["master_ip"] = _Get(cfg["cluster"]["master_ip"])
155 for node in cfg["nodes"].values():
156 node["primary_ip"] = _Get(node["primary_ip"])
157 node["secondary_ip"] = _Get(node["secondary_ip"])
159 for instance in cfg["instances"].values():
160 for nic in instance["nics"]:
161 if "ip" in nic and nic["ip"]:
162 nic["ip"] = _Get(nic["ip"])
165 def SanitizeOsNames(opts, cfg): # pylint: disable=W0613
166 """Sanitize the OS names.
172 os_map[old] = "ganeti-os%d" % (len(os_map) + 1)
176 for instance in cfg["instances"].values():
177 instance["os"] = _Get(instance["os"])
179 if "os_hvp" in cfg["cluster"]:
180 for os_name in cfg["cluster"]["os_hvp"]:
181 # force population of the entire os map
183 RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False)
186 def SanitizeDisks(opts, cfg): # pylint: disable=W0613
187 """Cleanup disks disks.
193 lv_map[old] = utils.NewUUID()
197 if "children" in disk and disk["children"]:
198 for child in disk["children"]:
201 if disk["dev_type"] == constants.LD_DRBD8:
202 if "physical_id" in disk:
203 del disk["physical_id"]
205 if disk["dev_type"] == constants.LD_LV and opts.sanitize_lvs:
206 disk["logical_id"][1] = _Get(disk["logical_id"][1])
207 disk["physical_id"][1] = disk["logical_id"][1]
211 for instance in cfg["instances"].values():
212 for disk in instance["disks"]:
216 def RandomizeDiskSecrets(disk):
217 """Randomize a disks' secrets (if any).
220 if "children" in disk and disk["children"]:
221 for child in disk["children"]:
222 RandomizeDiskSecrets(child)
224 # only disk type to contain secrets is the drbd one
225 if disk["dev_type"] == constants.LD_DRBD8:
226 disk["logical_id"][5] = utils.GenerateSecret()
229 def RenameDiskNodes(disk, node_map):
230 """Rename nodes in the disk config.
233 if "children" in disk and disk["children"]:
234 for child in disk["children"]:
235 RenameDiskNodes(child, node_map)
237 # only disk type to contain nodes is the drbd one
238 if disk["dev_type"] == constants.LD_DRBD8:
239 lid = disk["logical_id"]
240 lid[0] = node_map[lid[0]]
241 lid[1] = node_map[lid[1]]
244 def RenameDictKeys(a_dict, name_map, update_name):
245 """Rename the dictionary keys based on a name map.
248 for old_name in a_dict.keys():
249 new_name = name_map[old_name]
250 a_dict[new_name] = a_dict[old_name]
253 a_dict[new_name]["name"] = new_name
261 parser = optparse.OptionParser(usage="%prog [--verbose] output_file")
266 (opts, args) = parser.parse_args()
267 if opts.no_randomization:
268 opts.sanitize_names = opts.sanitize_ips = opts.sanitize_os_names = \
269 opts.sanitize_lvs = False
273 Error("Usage: sanitize-config [options] {<output_file> | -}")
275 # Check whether it's a Ganeti configuration directory
276 if not os.path.isfile(opts.CONFIG_DATA_PATH):
277 Error("Cannot find Ganeti configuration file %s", opts.CONFIG_DATA_PATH)
279 config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH))
281 # first, do some disk cleanup: remove DRBD physical_ids, since it
282 # contains both IPs (which we want changed) and the DRBD secret, and
283 # it's not needed for normal functioning, and randomize LVM names
284 SanitizeDisks(opts, config_data)
286 SanitizeSecrets(opts, config_data)
288 if opts.sanitize_names:
289 SanitizeCluster(opts, config_data)
290 SanitizeNodes(opts, config_data)
291 SanitizeInstances(opts, config_data)
293 if opts.sanitize_ips:
294 SanitizeIps(opts, config_data)
296 if opts.sanitize_os_names:
297 SanitizeOsNames(opts, config_data)
299 data = serializer.DumpJson(config_data)
301 sys.stdout.write(data)
303 utils.WriteFile(file_name=args[0],
308 if __name__ == "__main__":