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 cfg["cluster"]["dsahostkeypub"] = ""
89 for instance in cfg["instances"].values():
90 for disk in instance["disks"]:
91 RandomizeDiskSecrets(disk)
94 def SanitizeCluster(opts, cfg):
95 """Sanitize the cluster names.
98 cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain
101 def SanitizeNodes(opts, cfg):
102 """Sanitize node names.
105 old_names = cfg["nodes"].keys()
106 old_map = GenerateNameMap(opts, old_names, "node")
109 RenameDictKeys(cfg["nodes"], old_map, True)
112 cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]]
114 # update instance configuration
115 for instance in cfg["instances"].values():
116 instance["primary_node"] = old_map[instance["primary_node"]]
117 for disk in instance["disks"]:
118 RenameDiskNodes(disk, old_map)
121 def SanitizeInstances(opts, cfg):
122 """Sanitize instance names.
125 old_names = cfg["instances"].keys()
126 old_map = GenerateNameMap(opts, old_names, "instance")
128 RenameDictKeys(cfg["instances"], old_map, True)
131 def SanitizeIps(opts, cfg): # pylint: disable=W0613
132 """Sanitize the IP names.
134 @note: we're interested in obscuring the old IPs, not in generating
135 actually valid new IPs, so we chose to simply put IPv4
136 addresses, irrelevant of whether IPv6 or IPv4 addresses existed
143 idx = len(ip_map) + 1
144 rest, d_octet = divmod(idx, 256)
145 rest, c_octet = divmod(rest, 256)
146 rest, b_octet = divmod(rest, 256)
148 Error("Too many IPs!")
149 new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet)
155 cfg["cluster"]["master_ip"] = _Get(cfg["cluster"]["master_ip"])
156 for node in cfg["nodes"].values():
157 node["primary_ip"] = _Get(node["primary_ip"])
158 node["secondary_ip"] = _Get(node["secondary_ip"])
160 for instance in cfg["instances"].values():
161 for nic in instance["nics"]:
162 if "ip" in nic and nic["ip"]:
163 nic["ip"] = _Get(nic["ip"])
166 def SanitizeOsNames(opts, cfg): # pylint: disable=W0613
167 """Sanitize the OS names.
173 os_map[old] = "ganeti-os%d" % (len(os_map) + 1)
177 for instance in cfg["instances"].values():
178 instance["os"] = _Get(instance["os"])
180 if "os_hvp" in cfg["cluster"]:
181 for os_name in cfg["cluster"]["os_hvp"]:
182 # force population of the entire os map
184 RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False)
187 def SanitizeDisks(opts, cfg): # pylint: disable=W0613
188 """Cleanup disks disks.
194 lv_map[old] = utils.NewUUID()
198 if "children" in disk and disk["children"]:
199 for child in disk["children"]:
202 if disk["dev_type"] == constants.LD_DRBD8:
203 if "physical_id" in disk:
204 del disk["physical_id"]
206 if disk["dev_type"] == constants.LD_LV and opts.sanitize_lvs:
207 disk["logical_id"][1] = _Get(disk["logical_id"][1])
208 disk["physical_id"][1] = disk["logical_id"][1]
212 for instance in cfg["instances"].values():
213 for disk in instance["disks"]:
217 def RandomizeDiskSecrets(disk):
218 """Randomize a disks' secrets (if any).
221 if "children" in disk and disk["children"]:
222 for child in disk["children"]:
223 RandomizeDiskSecrets(child)
225 # only disk type to contain secrets is the drbd one
226 if disk["dev_type"] == constants.LD_DRBD8:
227 disk["logical_id"][5] = utils.GenerateSecret()
230 def RenameDiskNodes(disk, node_map):
231 """Rename nodes in the disk config.
234 if "children" in disk and disk["children"]:
235 for child in disk["children"]:
236 RenameDiskNodes(child, node_map)
238 # only disk type to contain nodes is the drbd one
239 if disk["dev_type"] == constants.LD_DRBD8:
240 lid = disk["logical_id"]
241 lid[0] = node_map[lid[0]]
242 lid[1] = node_map[lid[1]]
245 def RenameDictKeys(a_dict, name_map, update_name):
246 """Rename the dictionary keys based on a name map.
249 for old_name in a_dict.keys():
250 new_name = name_map[old_name]
251 a_dict[new_name] = a_dict[old_name]
254 a_dict[new_name]["name"] = new_name
262 parser = optparse.OptionParser(usage="%prog [--verbose] output_file")
267 (opts, args) = parser.parse_args()
268 if opts.no_randomization:
269 opts.sanitize_names = opts.sanitize_ips = opts.sanitize_os_names = \
270 opts.sanitize_lvs = False
274 Error("Usage: sanitize-config [options] {<output_file> | -}")
276 # Check whether it's a Ganeti configuration directory
277 if not os.path.isfile(opts.CONFIG_DATA_PATH):
278 Error("Cannot find Ganeti configuration file %s", opts.CONFIG_DATA_PATH)
280 config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH))
282 # first, do some disk cleanup: remove DRBD physical_ids, since it
283 # contains both IPs (which we want changed) and the DRBD secret, and
284 # it's not needed for normal functioning, and randomize LVM names
285 SanitizeDisks(opts, config_data)
287 SanitizeSecrets(opts, config_data)
289 if opts.sanitize_names:
290 SanitizeCluster(opts, config_data)
291 SanitizeNodes(opts, config_data)
292 SanitizeInstances(opts, config_data)
294 if opts.sanitize_ips:
295 SanitizeIps(opts, config_data)
297 if opts.sanitize_os_names:
298 SanitizeOsNames(opts, config_data)
300 data = serializer.DumpJson(config_data)
302 sys.stdout.write(data)
304 utils.WriteFile(file_name=args[0],
309 if __name__ == "__main__":