/usr/share/ganeti/sanitize-config is in ganeti 2.9.3-1.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | #! /usr/bin/python
#
# Copyright (C) 2010 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
# 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_DRBD8:
if "physical_id" in disk:
del disk["physical_id"]
if disk["dev_type"] == constants.DT_PLAIN and opts.sanitize_lvs:
disk["logical_id"][1] = _Get(disk["logical_id"][1])
disk["physical_id"][1] = 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))
# first, do some disk cleanup: remove DRBD physical_ids, since it
# contains both IPs (which we want changed) and the DRBD secret, and
# it's not needed for normal functioning, and 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()
|