root / tools / sanitize-config @ c1912a48
History | View | Annotate | Download (8.5 kB)
1 |
#!/usr/bin/python |
---|---|
2 |
# |
3 |
|
4 |
# Copyright (C) 2010 Google Inc. |
5 |
# |
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. |
10 |
# |
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. |
15 |
# |
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 |
19 |
# 02110-1301, USA. |
20 |
|
21 |
|
22 |
# pylint: disable=C0103 |
23 |
|
24 |
"""Tool to sanitize/randomize the configuration file. |
25 |
|
26 |
""" |
27 |
|
28 |
import sys |
29 |
import os |
30 |
import os.path |
31 |
import optparse |
32 |
|
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 |
39 |
|
40 |
|
41 |
OPTS = [ |
42 |
cli.VERBOSE_OPT, |
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]"), |
58 |
] |
59 |
|
60 |
|
61 |
def Error(txt, *args): |
62 |
"""Writes a message to standard error and exits. |
63 |
|
64 |
""" |
65 |
cli.ToStderr(txt, *args) |
66 |
sys.exit(1) |
67 |
|
68 |
|
69 |
def GenerateNameMap(opts, names, base): |
70 |
"""For a given set of names, generate a list of sane new names. |
71 |
|
72 |
""" |
73 |
names = utils.NiceSort(names) |
74 |
name_map = {} |
75 |
for idx, old_name in enumerate(names): |
76 |
new_name = "%s%d.%s" % (base, idx + 1, opts.base_domain) |
77 |
if new_name in names: |
78 |
Error("Name conflict for %s: %s already exists", base, new_name) |
79 |
name_map[old_name] = new_name |
80 |
return name_map |
81 |
|
82 |
|
83 |
def SanitizeSecrets(opts, cfg): # pylint: disable=W0613 |
84 |
"""Cleanup configuration secrets. |
85 |
|
86 |
""" |
87 |
cfg["cluster"]["rsahostkeypub"] = "" |
88 |
for instance in cfg["instances"].values(): |
89 |
for disk in instance["disks"]: |
90 |
RandomizeDiskSecrets(disk) |
91 |
|
92 |
|
93 |
def SanitizeCluster(opts, cfg): |
94 |
"""Sanitize the cluster names. |
95 |
|
96 |
""" |
97 |
cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain |
98 |
|
99 |
|
100 |
def SanitizeNodes(opts, cfg): |
101 |
"""Sanitize node names. |
102 |
|
103 |
""" |
104 |
old_names = cfg["nodes"].keys() |
105 |
old_map = GenerateNameMap(opts, old_names, "node") |
106 |
|
107 |
# rename nodes |
108 |
RenameDictKeys(cfg["nodes"], old_map, True) |
109 |
|
110 |
# update master node |
111 |
cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]] |
112 |
|
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) |
118 |
|
119 |
|
120 |
def SanitizeInstances(opts, cfg): |
121 |
"""Sanitize instance names. |
122 |
|
123 |
""" |
124 |
old_names = cfg["instances"].keys() |
125 |
old_map = GenerateNameMap(opts, old_names, "instance") |
126 |
|
127 |
RenameDictKeys(cfg["instances"], old_map, True) |
128 |
|
129 |
|
130 |
def SanitizeIps(opts, cfg): # pylint: disable=W0613 |
131 |
"""Sanitize the IP names. |
132 |
|
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 |
136 |
before. |
137 |
|
138 |
""" |
139 |
def _Get(old): |
140 |
if old in ip_map: |
141 |
return ip_map[old] |
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) |
146 |
if rest > 0: |
147 |
Error("Too many IPs!") |
148 |
new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet) |
149 |
ip_map[old] = new_ip |
150 |
return new_ip |
151 |
|
152 |
ip_map = {} |
153 |
|
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"]) |
158 |
|
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"]) |
163 |
|
164 |
|
165 |
def SanitizeOsNames(opts, cfg): # pylint: disable=W0613 |
166 |
"""Sanitize the OS names. |
167 |
|
168 |
""" |
169 |
def _Get(old): |
170 |
if old in os_map: |
171 |
return os_map[old] |
172 |
os_map[old] = "ganeti-os%d" % (len(os_map) + 1) |
173 |
return os_map[old] |
174 |
|
175 |
os_map = {} |
176 |
for instance in cfg["instances"].values(): |
177 |
instance["os"] = _Get(instance["os"]) |
178 |
|
179 |
if "os_hvp" in cfg["cluster"]: |
180 |
for os_name in cfg["cluster"]["os_hvp"]: |
181 |
# force population of the entire os map |
182 |
_Get(os_name) |
183 |
RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False) |
184 |
|
185 |
|
186 |
def SanitizeDisks(opts, cfg): # pylint: disable=W0613 |
187 |
"""Cleanup disks disks. |
188 |
|
189 |
""" |
190 |
def _Get(old): |
191 |
if old in lv_map: |
192 |
return old |
193 |
lv_map[old] = utils.NewUUID() |
194 |
return lv_map[old] |
195 |
|
196 |
def helper(disk): |
197 |
if "children" in disk and disk["children"]: |
198 |
for child in disk["children"]: |
199 |
helper(child) |
200 |
|
201 |
if disk["dev_type"] == constants.LD_DRBD8: |
202 |
if "physical_id" in disk: |
203 |
del disk["physical_id"] |
204 |
|
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] |
208 |
|
209 |
lv_map = {} |
210 |
|
211 |
for instance in cfg["instances"].values(): |
212 |
for disk in instance["disks"]: |
213 |
helper(disk) |
214 |
|
215 |
|
216 |
def RandomizeDiskSecrets(disk): |
217 |
"""Randomize a disks' secrets (if any). |
218 |
|
219 |
""" |
220 |
if "children" in disk and disk["children"]: |
221 |
for child in disk["children"]: |
222 |
RandomizeDiskSecrets(child) |
223 |
|
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() |
227 |
|
228 |
|
229 |
def RenameDiskNodes(disk, node_map): |
230 |
"""Rename nodes in the disk config. |
231 |
|
232 |
""" |
233 |
if "children" in disk and disk["children"]: |
234 |
for child in disk["children"]: |
235 |
RenameDiskNodes(child, node_map) |
236 |
|
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]] |
242 |
|
243 |
|
244 |
def RenameDictKeys(a_dict, name_map, update_name): |
245 |
"""Rename the dictionary keys based on a name map. |
246 |
|
247 |
""" |
248 |
for old_name in a_dict.keys(): |
249 |
new_name = name_map[old_name] |
250 |
a_dict[new_name] = a_dict[old_name] |
251 |
del a_dict[old_name] |
252 |
if update_name: |
253 |
a_dict[new_name]["name"] = new_name |
254 |
|
255 |
|
256 |
def main(): |
257 |
"""Main program. |
258 |
|
259 |
""" |
260 |
# Option parsing |
261 |
parser = optparse.OptionParser(usage="%prog [--verbose] output_file") |
262 |
|
263 |
for o in OPTS: |
264 |
parser.add_option(o) |
265 |
|
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 |
270 |
|
271 |
# Option checking |
272 |
if len(args) != 1: |
273 |
Error("Usage: sanitize-config [options] {<output_file> | -}") |
274 |
|
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) |
278 |
|
279 |
config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH)) |
280 |
|
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) |
285 |
|
286 |
SanitizeSecrets(opts, config_data) |
287 |
|
288 |
if opts.sanitize_names: |
289 |
SanitizeCluster(opts, config_data) |
290 |
SanitizeNodes(opts, config_data) |
291 |
SanitizeInstances(opts, config_data) |
292 |
|
293 |
if opts.sanitize_ips: |
294 |
SanitizeIps(opts, config_data) |
295 |
|
296 |
if opts.sanitize_os_names: |
297 |
SanitizeOsNames(opts, config_data) |
298 |
|
299 |
data = serializer.DumpJson(config_data) |
300 |
if args[0] == "-": |
301 |
sys.stdout.write(data) |
302 |
else: |
303 |
utils.WriteFile(file_name=args[0], |
304 |
data=data, |
305 |
mode=0600, |
306 |
backup=True) |
307 |
|
308 |
if __name__ == "__main__": |
309 |
main() |