root / tools / sanitize-config @ cd3b4ff4
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 |
cfg["cluster"]["dsahostkeypub"] = "" |
89 |
for instance in cfg["instances"].values(): |
90 |
for disk in instance["disks"]: |
91 |
RandomizeDiskSecrets(disk) |
92 |
|
93 |
|
94 |
def SanitizeCluster(opts, cfg): |
95 |
"""Sanitize the cluster names. |
96 |
|
97 |
""" |
98 |
cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain |
99 |
|
100 |
|
101 |
def SanitizeNodes(opts, cfg): |
102 |
"""Sanitize node names. |
103 |
|
104 |
""" |
105 |
old_names = cfg["nodes"].keys() |
106 |
old_map = GenerateNameMap(opts, old_names, "node") |
107 |
|
108 |
# rename nodes |
109 |
RenameDictKeys(cfg["nodes"], old_map, True) |
110 |
|
111 |
# update master node |
112 |
cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]] |
113 |
|
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) |
119 |
|
120 |
|
121 |
def SanitizeInstances(opts, cfg): |
122 |
"""Sanitize instance names. |
123 |
|
124 |
""" |
125 |
old_names = cfg["instances"].keys() |
126 |
old_map = GenerateNameMap(opts, old_names, "instance") |
127 |
|
128 |
RenameDictKeys(cfg["instances"], old_map, True) |
129 |
|
130 |
|
131 |
def SanitizeIps(opts, cfg): # pylint: disable=W0613 |
132 |
"""Sanitize the IP names. |
133 |
|
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 |
137 |
before. |
138 |
|
139 |
""" |
140 |
def _Get(old): |
141 |
if old in ip_map: |
142 |
return ip_map[old] |
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) |
147 |
if rest > 0: |
148 |
Error("Too many IPs!") |
149 |
new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet) |
150 |
ip_map[old] = new_ip |
151 |
return new_ip |
152 |
|
153 |
ip_map = {} |
154 |
|
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"]) |
159 |
|
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"]) |
164 |
|
165 |
|
166 |
def SanitizeOsNames(opts, cfg): # pylint: disable=W0613 |
167 |
"""Sanitize the OS names. |
168 |
|
169 |
""" |
170 |
def _Get(old): |
171 |
if old in os_map: |
172 |
return os_map[old] |
173 |
os_map[old] = "ganeti-os%d" % (len(os_map) + 1) |
174 |
return os_map[old] |
175 |
|
176 |
os_map = {} |
177 |
for instance in cfg["instances"].values(): |
178 |
instance["os"] = _Get(instance["os"]) |
179 |
|
180 |
if "os_hvp" in cfg["cluster"]: |
181 |
for os_name in cfg["cluster"]["os_hvp"]: |
182 |
# force population of the entire os map |
183 |
_Get(os_name) |
184 |
RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False) |
185 |
|
186 |
|
187 |
def SanitizeDisks(opts, cfg): # pylint: disable=W0613 |
188 |
"""Cleanup disks disks. |
189 |
|
190 |
""" |
191 |
def _Get(old): |
192 |
if old in lv_map: |
193 |
return old |
194 |
lv_map[old] = utils.NewUUID() |
195 |
return lv_map[old] |
196 |
|
197 |
def helper(disk): |
198 |
if "children" in disk and disk["children"]: |
199 |
for child in disk["children"]: |
200 |
helper(child) |
201 |
|
202 |
if disk["dev_type"] == constants.DT_DRBD8: |
203 |
if "physical_id" in disk: |
204 |
del disk["physical_id"] |
205 |
|
206 |
if disk["dev_type"] == constants.DT_PLAIN and opts.sanitize_lvs: |
207 |
disk["logical_id"][1] = _Get(disk["logical_id"][1]) |
208 |
disk["physical_id"][1] = disk["logical_id"][1] |
209 |
|
210 |
lv_map = {} |
211 |
|
212 |
for instance in cfg["instances"].values(): |
213 |
for disk in instance["disks"]: |
214 |
helper(disk) |
215 |
|
216 |
|
217 |
def RandomizeDiskSecrets(disk): |
218 |
"""Randomize a disks' secrets (if any). |
219 |
|
220 |
""" |
221 |
if "children" in disk and disk["children"]: |
222 |
for child in disk["children"]: |
223 |
RandomizeDiskSecrets(child) |
224 |
|
225 |
# only disk type to contain secrets is the drbd one |
226 |
if disk["dev_type"] == constants.DT_DRBD8: |
227 |
disk["logical_id"][5] = utils.GenerateSecret() |
228 |
|
229 |
|
230 |
def RenameDiskNodes(disk, node_map): |
231 |
"""Rename nodes in the disk config. |
232 |
|
233 |
""" |
234 |
if "children" in disk and disk["children"]: |
235 |
for child in disk["children"]: |
236 |
RenameDiskNodes(child, node_map) |
237 |
|
238 |
# only disk type to contain nodes is the drbd one |
239 |
if disk["dev_type"] == constants.DT_DRBD8: |
240 |
lid = disk["logical_id"] |
241 |
lid[0] = node_map[lid[0]] |
242 |
lid[1] = node_map[lid[1]] |
243 |
|
244 |
|
245 |
def RenameDictKeys(a_dict, name_map, update_name): |
246 |
"""Rename the dictionary keys based on a name map. |
247 |
|
248 |
""" |
249 |
for old_name in a_dict.keys(): |
250 |
new_name = name_map[old_name] |
251 |
a_dict[new_name] = a_dict[old_name] |
252 |
del a_dict[old_name] |
253 |
if update_name: |
254 |
a_dict[new_name]["name"] = new_name |
255 |
|
256 |
|
257 |
def main(): |
258 |
"""Main program. |
259 |
|
260 |
""" |
261 |
# Option parsing |
262 |
parser = optparse.OptionParser(usage="%prog [--verbose] output_file") |
263 |
|
264 |
for o in OPTS: |
265 |
parser.add_option(o) |
266 |
|
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 |
271 |
|
272 |
# Option checking |
273 |
if len(args) != 1: |
274 |
Error("Usage: sanitize-config [options] {<output_file> | -}") |
275 |
|
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) |
279 |
|
280 |
config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH)) |
281 |
|
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) |
286 |
|
287 |
SanitizeSecrets(opts, config_data) |
288 |
|
289 |
if opts.sanitize_names: |
290 |
SanitizeCluster(opts, config_data) |
291 |
SanitizeNodes(opts, config_data) |
292 |
SanitizeInstances(opts, config_data) |
293 |
|
294 |
if opts.sanitize_ips: |
295 |
SanitizeIps(opts, config_data) |
296 |
|
297 |
if opts.sanitize_os_names: |
298 |
SanitizeOsNames(opts, config_data) |
299 |
|
300 |
data = serializer.DumpJson(config_data) |
301 |
if args[0] == "-": |
302 |
sys.stdout.write(data) |
303 |
else: |
304 |
utils.WriteFile(file_name=args[0], |
305 |
data=data, |
306 |
mode=0600, |
307 |
backup=True) |
308 |
|
309 |
if __name__ == "__main__": |
310 |
main() |