blob: af3cd65bf981af01e841c184129883dc3d36b085 [file] [log] [blame]
#!/usr/bin/python
#
# Copyright (C) 2010 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# pylint: disable=C0103
"""Tool to sanitize/randomize the configuration file.
"""
import sys
import os
import os.path
import optparse
from ganeti import constants
from ganeti import serializer
from ganeti import utils
from ganeti import pathutils
from ganeti import cli
from ganeti.cli import cli_option
OPTS = [
cli.VERBOSE_OPT,
cli_option("--path", help="Convert this configuration file"
" instead of '%s'" % pathutils.CLUSTER_CONF_FILE,
default=pathutils.CLUSTER_CONF_FILE, dest="CONFIG_DATA_PATH"),
cli_option("--sanitize-names", default="yes", type="bool",
help="Randomize the cluster, node and instance names [yes]"),
cli_option("--sanitize-ips", default="yes", type="bool",
help="Randomize the cluster, node and instance IPs [yes]"),
cli_option("--sanitize-lvs", default="no", type="bool",
help="Randomize the LV names (for old clusters) [no]"),
cli_option("--sanitize-os-names", default="yes", type="bool",
help="Randomize the OS names [yes]"),
cli_option("--no-randomization", default=False, action="store_true",
help="Disable all name randomization (only randomize secrets)"),
cli_option("--base-domain", default="example.com",
help="The base domain used for new names [example.com]"),
]
def Error(txt, *args):
"""Writes a message to standard error and exits.
"""
cli.ToStderr(txt, *args)
sys.exit(1)
def GenerateNameMap(opts, names, base):
"""For a given set of names, generate a list of sane new names.
"""
names = utils.NiceSort(names)
name_map = {}
for idx, old_name in enumerate(names):
new_name = "%s%d.%s" % (base, idx + 1, opts.base_domain)
if new_name in names:
Error("Name conflict for %s: %s already exists", base, new_name)
name_map[old_name] = new_name
return name_map
def SanitizeSecrets(opts, cfg): # pylint: disable=W0613
"""Cleanup configuration secrets.
"""
cfg["cluster"]["rsahostkeypub"] = ""
cfg["cluster"]["dsahostkeypub"] = ""
for instance in cfg["instances"].values():
for disk in instance["disks"]:
RandomizeDiskSecrets(disk)
def SanitizeCluster(opts, cfg):
"""Sanitize the cluster names.
"""
cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain
def SanitizeNodes(opts, cfg):
"""Sanitize node names.
"""
old_names = cfg["nodes"].keys()
old_map = GenerateNameMap(opts, old_names, "node")
# rename nodes
RenameDictKeys(cfg["nodes"], old_map, True)
# update master node
cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]]
# update instance configuration
for instance in cfg["instances"].values():
instance["primary_node"] = old_map[instance["primary_node"]]
for disk in instance["disks"]:
RenameDiskNodes(disk, old_map)
def SanitizeInstances(opts, cfg):
"""Sanitize instance names.
"""
old_names = cfg["instances"].keys()
old_map = GenerateNameMap(opts, old_names, "instance")
RenameDictKeys(cfg["instances"], old_map, True)
def SanitizeIps(opts, cfg): # pylint: disable=W0613
"""Sanitize the IP names.
@note: we're interested in obscuring the old IPs, not in generating
actually valid new IPs, so we chose to simply put IPv4
addresses, irrelevant of whether IPv6 or IPv4 addresses existed
before.
"""
def _Get(old):
if old in ip_map:
return ip_map[old]
idx = len(ip_map) + 1
rest, d_octet = divmod(idx, 256)
rest, c_octet = divmod(rest, 256)
rest, b_octet = divmod(rest, 256)
if rest > 0:
Error("Too many IPs!")
new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet)
ip_map[old] = new_ip
return new_ip
ip_map = {}
cfg["cluster"]["master_ip"] = _Get(cfg["cluster"]["master_ip"])
for node in cfg["nodes"].values():
node["primary_ip"] = _Get(node["primary_ip"])
node["secondary_ip"] = _Get(node["secondary_ip"])
for instance in cfg["instances"].values():
for nic in instance["nics"]:
if "ip" in nic and nic["ip"]:
nic["ip"] = _Get(nic["ip"])
def SanitizeOsNames(opts, cfg): # pylint: disable=W0613
"""Sanitize the OS names.
"""
def _Get(old):
if old in os_map:
return os_map[old]
os_map[old] = "ganeti-os%d" % (len(os_map) + 1)
return os_map[old]
os_map = {}
for instance in cfg["instances"].values():
instance["os"] = _Get(instance["os"])
if "os_hvp" in cfg["cluster"]:
for os_name in cfg["cluster"]["os_hvp"]:
# force population of the entire os map
_Get(os_name)
RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False)
def SanitizeDisks(opts, cfg): # pylint: disable=W0613
"""Cleanup disks disks.
"""
def _Get(old):
if old in lv_map:
return old
lv_map[old] = utils.NewUUID()
return lv_map[old]
def helper(disk):
if "children" in disk and disk["children"]:
for child in disk["children"]:
helper(child)
if disk["dev_type"] == constants.DT_PLAIN and opts.sanitize_lvs:
disk["logical_id"][1] = _Get(disk["logical_id"][1])
lv_map = {}
for instance in cfg["instances"].values():
for disk in instance["disks"]:
helper(disk)
def RandomizeDiskSecrets(disk):
"""Randomize a disks' secrets (if any).
"""
if "children" in disk and disk["children"]:
for child in disk["children"]:
RandomizeDiskSecrets(child)
# only disk type to contain secrets is the drbd one
if disk["dev_type"] == constants.DT_DRBD8:
disk["logical_id"][5] = utils.GenerateSecret()
def RenameDiskNodes(disk, node_map):
"""Rename nodes in the disk config.
"""
if "children" in disk and disk["children"]:
for child in disk["children"]:
RenameDiskNodes(child, node_map)
# only disk type to contain nodes is the drbd one
if disk["dev_type"] == constants.DT_DRBD8:
lid = disk["logical_id"]
lid[0] = node_map[lid[0]]
lid[1] = node_map[lid[1]]
def RenameDictKeys(a_dict, name_map, update_name):
"""Rename the dictionary keys based on a name map.
"""
for old_name in a_dict.keys():
new_name = name_map[old_name]
a_dict[new_name] = a_dict[old_name]
del a_dict[old_name]
if update_name:
a_dict[new_name]["name"] = new_name
def main():
"""Main program.
"""
# Option parsing
parser = optparse.OptionParser(usage="%prog [--verbose] output_file")
for o in OPTS:
parser.add_option(o)
(opts, args) = parser.parse_args()
if opts.no_randomization:
opts.sanitize_names = opts.sanitize_ips = opts.sanitize_os_names = \
opts.sanitize_lvs = False
# Option checking
if len(args) != 1:
Error("Usage: sanitize-config [options] {<output_file> | -}")
# Check whether it's a Ganeti configuration directory
if not os.path.isfile(opts.CONFIG_DATA_PATH):
Error("Cannot find Ganeti configuration file %s", opts.CONFIG_DATA_PATH)
config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH))
# Randomize LVM names
SanitizeDisks(opts, config_data)
SanitizeSecrets(opts, config_data)
if opts.sanitize_names:
SanitizeCluster(opts, config_data)
SanitizeNodes(opts, config_data)
SanitizeInstances(opts, config_data)
if opts.sanitize_ips:
SanitizeIps(opts, config_data)
if opts.sanitize_os_names:
SanitizeOsNames(opts, config_data)
data = serializer.DumpJson(config_data)
if args[0] == "-":
sys.stdout.write(data)
else:
utils.WriteFile(file_name=args[0],
data=data,
mode=0600,
backup=True)
if __name__ == "__main__":
main()