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.DT_PLAIN and opts.sanitize_lvs:
203 disk["logical_id"][1] = _Get(disk["logical_id"][1])
207 for instance in cfg["instances"].values():
208 for disk in instance["disks"]:
212 def RandomizeDiskSecrets(disk):
213 """Randomize a disks' secrets (if any).
216 if "children" in disk and disk["children"]:
217 for child in disk["children"]:
218 RandomizeDiskSecrets(child)
220 # only disk type to contain secrets is the drbd one
221 if disk["dev_type"] == constants.DT_DRBD8:
222 disk["logical_id"][5] = utils.GenerateSecret()
225 def RenameDiskNodes(disk, node_map):
226 """Rename nodes in the disk config.
229 if "children" in disk and disk["children"]:
230 for child in disk["children"]:
231 RenameDiskNodes(child, node_map)
233 # only disk type to contain nodes is the drbd one
234 if disk["dev_type"] == constants.DT_DRBD8:
235 lid = disk["logical_id"]
236 lid[0] = node_map[lid[0]]
237 lid[1] = node_map[lid[1]]
240 def RenameDictKeys(a_dict, name_map, update_name):
241 """Rename the dictionary keys based on a name map.
244 for old_name in a_dict.keys():
245 new_name = name_map[old_name]
246 a_dict[new_name] = a_dict[old_name]
249 a_dict[new_name]["name"] = new_name
257 parser = optparse.OptionParser(usage="%prog [--verbose] output_file")
262 (opts, args) = parser.parse_args()
263 if opts.no_randomization:
264 opts.sanitize_names = opts.sanitize_ips = opts.sanitize_os_names = \
265 opts.sanitize_lvs = False
269 Error("Usage: sanitize-config [options] {<output_file> | -}")
271 # Check whether it's a Ganeti configuration directory
272 if not os.path.isfile(opts.CONFIG_DATA_PATH):
273 Error("Cannot find Ganeti configuration file %s", opts.CONFIG_DATA_PATH)
275 config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH))
277 # Randomize LVM names
278 SanitizeDisks(opts, config_data)
280 SanitizeSecrets(opts, config_data)
282 if opts.sanitize_names:
283 SanitizeCluster(opts, config_data)
284 SanitizeNodes(opts, config_data)
285 SanitizeInstances(opts, config_data)
287 if opts.sanitize_ips:
288 SanitizeIps(opts, config_data)
290 if opts.sanitize_os_names:
291 SanitizeOsNames(opts, config_data)
293 data = serializer.DumpJson(config_data)
295 sys.stdout.write(data)
297 utils.WriteFile(file_name=args[0],
302 if __name__ == "__main__":