root / tools / sanitize-config @ fcad7225
History | View | Annotate | Download (8.4 kB)
1 | ea5fd476 | Iustin Pop | #!/usr/bin/python |
---|---|---|---|
2 | ea5fd476 | Iustin Pop | # |
3 | ea5fd476 | Iustin Pop | |
4 | ea5fd476 | Iustin Pop | # Copyright (C) 2010 Google Inc. |
5 | ea5fd476 | Iustin Pop | # |
6 | ea5fd476 | Iustin Pop | # This program is free software; you can redistribute it and/or modify |
7 | ea5fd476 | Iustin Pop | # it under the terms of the GNU General Public License as published by |
8 | ea5fd476 | Iustin Pop | # the Free Software Foundation; either version 2 of the License, or |
9 | ea5fd476 | Iustin Pop | # (at your option) any later version. |
10 | ea5fd476 | Iustin Pop | # |
11 | ea5fd476 | Iustin Pop | # This program is distributed in the hope that it will be useful, but |
12 | ea5fd476 | Iustin Pop | # WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | ea5fd476 | Iustin Pop | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | ea5fd476 | Iustin Pop | # General Public License for more details. |
15 | ea5fd476 | Iustin Pop | # |
16 | ea5fd476 | Iustin Pop | # You should have received a copy of the GNU General Public License |
17 | ea5fd476 | Iustin Pop | # along with this program; if not, write to the Free Software |
18 | ea5fd476 | Iustin Pop | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
19 | ea5fd476 | Iustin Pop | # 02110-1301, USA. |
20 | ea5fd476 | Iustin Pop | |
21 | ea5fd476 | Iustin Pop | |
22 | ea5fd476 | Iustin Pop | # pylint: disable-msg=C0103 |
23 | ea5fd476 | Iustin Pop | |
24 | ea5fd476 | Iustin Pop | """Tool to sanitize/randomize the configuration file. |
25 | ea5fd476 | Iustin Pop | |
26 | ea5fd476 | Iustin Pop | """ |
27 | ea5fd476 | Iustin Pop | |
28 | ea5fd476 | Iustin Pop | import sys |
29 | ea5fd476 | Iustin Pop | import os |
30 | ea5fd476 | Iustin Pop | import os.path |
31 | ea5fd476 | Iustin Pop | import optparse |
32 | ea5fd476 | Iustin Pop | |
33 | ea5fd476 | Iustin Pop | from ganeti import constants |
34 | ea5fd476 | Iustin Pop | from ganeti import serializer |
35 | ea5fd476 | Iustin Pop | from ganeti import utils |
36 | ea5fd476 | Iustin Pop | from ganeti import cli |
37 | ea5fd476 | Iustin Pop | from ganeti.cli import cli_option |
38 | ea5fd476 | Iustin Pop | |
39 | ea5fd476 | Iustin Pop | |
40 | ea5fd476 | Iustin Pop | OPTS = [ |
41 | ea5fd476 | Iustin Pop | cli.VERBOSE_OPT, |
42 | ea5fd476 | Iustin Pop | cli_option("--path", help="Convert this configuration file" |
43 | ea5fd476 | Iustin Pop | " instead of '%s'" % constants.CLUSTER_CONF_FILE, |
44 | ea5fd476 | Iustin Pop | default=constants.CLUSTER_CONF_FILE, dest="CONFIG_DATA_PATH"), |
45 | ea5fd476 | Iustin Pop | cli_option("--sanitize-names", default="yes", type="bool", |
46 | ea5fd476 | Iustin Pop | help="Randomize the cluster, node and instance names [yes]"), |
47 | ea5fd476 | Iustin Pop | cli_option("--sanitize-ips", default="yes", type="bool", |
48 | ea5fd476 | Iustin Pop | help="Randomize the cluster, node and instance IPs [yes]"), |
49 | ea5fd476 | Iustin Pop | cli_option("--sanitize-lvs", default="no", type="bool", |
50 | ea5fd476 | Iustin Pop | help="Randomize the LV names (for old clusters) [no]"), |
51 | ea5fd476 | Iustin Pop | cli_option("--sanitize-os-names", default="yes", type="bool", |
52 | ea5fd476 | Iustin Pop | help="Randomize the OS names [yes]"), |
53 | ea5fd476 | Iustin Pop | cli_option("--no-randomization", default=False, action="store_true", |
54 | ea5fd476 | Iustin Pop | help="Disable all name randomization (only randomize secrets)"), |
55 | ea5fd476 | Iustin Pop | cli_option("--base-domain", default="example.com", |
56 | ea5fd476 | Iustin Pop | help="The base domain used for new names [example.com]"), |
57 | ea5fd476 | Iustin Pop | ] |
58 | ea5fd476 | Iustin Pop | |
59 | ea5fd476 | Iustin Pop | |
60 | ea5fd476 | Iustin Pop | def Error(txt, *args): |
61 | ea5fd476 | Iustin Pop | """Writes a message to standard error and exits. |
62 | ea5fd476 | Iustin Pop | |
63 | ea5fd476 | Iustin Pop | """ |
64 | ea5fd476 | Iustin Pop | cli.ToStderr(txt, *args) |
65 | ea5fd476 | Iustin Pop | sys.exit(1) |
66 | ea5fd476 | Iustin Pop | |
67 | ea5fd476 | Iustin Pop | |
68 | ea5fd476 | Iustin Pop | def GenerateNameMap(opts, names, base): |
69 | ea5fd476 | Iustin Pop | """For a given set of names, generate a list of sane new names. |
70 | ea5fd476 | Iustin Pop | |
71 | ea5fd476 | Iustin Pop | """ |
72 | ea5fd476 | Iustin Pop | names = utils.NiceSort(names) |
73 | ea5fd476 | Iustin Pop | name_map = {} |
74 | ea5fd476 | Iustin Pop | for idx, old_name in enumerate(names): |
75 | ea5fd476 | Iustin Pop | new_name = "%s%d.%s" % (base, idx + 1, opts.base_domain) |
76 | ea5fd476 | Iustin Pop | if new_name in names: |
77 | ea5fd476 | Iustin Pop | Error("Name conflict for %s: %s already exists", base, new_name) |
78 | ea5fd476 | Iustin Pop | name_map[old_name] = new_name |
79 | ea5fd476 | Iustin Pop | return name_map |
80 | ea5fd476 | Iustin Pop | |
81 | ea5fd476 | Iustin Pop | |
82 | ea5fd476 | Iustin Pop | def SanitizeSecrets(opts, cfg): # pylint: disable-msg=W0613 |
83 | ea5fd476 | Iustin Pop | """Cleanup configuration secrets. |
84 | ea5fd476 | Iustin Pop | |
85 | ea5fd476 | Iustin Pop | """ |
86 | ea5fd476 | Iustin Pop | cfg["cluster"]["rsahostkeypub"] = "" |
87 | ea5fd476 | Iustin Pop | for instance in cfg["instances"].values(): |
88 | ea5fd476 | Iustin Pop | for disk in instance["disks"]: |
89 | ea5fd476 | Iustin Pop | RandomizeDiskSecrets(disk) |
90 | ea5fd476 | Iustin Pop | |
91 | ea5fd476 | Iustin Pop | |
92 | ea5fd476 | Iustin Pop | def SanitizeCluster(opts, cfg): |
93 | ea5fd476 | Iustin Pop | """Sanitize the cluster names. |
94 | ea5fd476 | Iustin Pop | |
95 | ea5fd476 | Iustin Pop | """ |
96 | ea5fd476 | Iustin Pop | cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain |
97 | ea5fd476 | Iustin Pop | |
98 | ea5fd476 | Iustin Pop | |
99 | ea5fd476 | Iustin Pop | def SanitizeNodes(opts, cfg): |
100 | ea5fd476 | Iustin Pop | """Sanitize node names. |
101 | ea5fd476 | Iustin Pop | |
102 | ea5fd476 | Iustin Pop | """ |
103 | ea5fd476 | Iustin Pop | old_names = cfg["nodes"].keys() |
104 | ea5fd476 | Iustin Pop | old_map = GenerateNameMap(opts, old_names, "node") |
105 | ea5fd476 | Iustin Pop | |
106 | ea5fd476 | Iustin Pop | # rename nodes |
107 | ea5fd476 | Iustin Pop | RenameDictKeys(cfg["nodes"], old_map, True) |
108 | ea5fd476 | Iustin Pop | |
109 | ea5fd476 | Iustin Pop | # update master node |
110 | ea5fd476 | Iustin Pop | cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]] |
111 | ea5fd476 | Iustin Pop | |
112 | ea5fd476 | Iustin Pop | # update instance configuration |
113 | ea5fd476 | Iustin Pop | for instance in cfg["instances"].values(): |
114 | ea5fd476 | Iustin Pop | instance["primary_node"] = old_map[instance["primary_node"]] |
115 | ea5fd476 | Iustin Pop | for disk in instance["disks"]: |
116 | ea5fd476 | Iustin Pop | RenameDiskNodes(disk, old_map) |
117 | ea5fd476 | Iustin Pop | |
118 | ea5fd476 | Iustin Pop | |
119 | ea5fd476 | Iustin Pop | def SanitizeInstances(opts, cfg): |
120 | ea5fd476 | Iustin Pop | """Sanitize instance names. |
121 | ea5fd476 | Iustin Pop | |
122 | ea5fd476 | Iustin Pop | """ |
123 | ea5fd476 | Iustin Pop | old_names = cfg["instances"].keys() |
124 | ea5fd476 | Iustin Pop | old_map = GenerateNameMap(opts, old_names, "instance") |
125 | ea5fd476 | Iustin Pop | |
126 | ea5fd476 | Iustin Pop | RenameDictKeys(cfg["instances"], old_map, True) |
127 | ea5fd476 | Iustin Pop | |
128 | ea5fd476 | Iustin Pop | |
129 | ea5fd476 | Iustin Pop | def SanitizeIps(opts, cfg): # pylint: disable-msg=W0613 |
130 | ea5fd476 | Iustin Pop | """Sanitize the IP names. |
131 | ea5fd476 | Iustin Pop | |
132 | ea5fd476 | Iustin Pop | @note: we're interested in obscuring the old IPs, not in generating |
133 | ea5fd476 | Iustin Pop | actually valid new IPs, so we chose to simply put IPv4 |
134 | ea5fd476 | Iustin Pop | addresses, irrelevant of whether IPv6 or IPv4 addresses existed |
135 | ea5fd476 | Iustin Pop | before. |
136 | ea5fd476 | Iustin Pop | |
137 | ea5fd476 | Iustin Pop | """ |
138 | ea5fd476 | Iustin Pop | def _Get(old): |
139 | ea5fd476 | Iustin Pop | if old in ip_map: |
140 | ea5fd476 | Iustin Pop | return ip_map[old] |
141 | ea5fd476 | Iustin Pop | idx = len(ip_map) + 1 |
142 | ea5fd476 | Iustin Pop | rest, d_octet = divmod(idx, 256) |
143 | ea5fd476 | Iustin Pop | rest, c_octet = divmod(rest, 256) |
144 | ea5fd476 | Iustin Pop | rest, b_octet = divmod(rest, 256) |
145 | ea5fd476 | Iustin Pop | if rest > 0: |
146 | ea5fd476 | Iustin Pop | Error("Too many IPs!") |
147 | ea5fd476 | Iustin Pop | new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet) |
148 | ea5fd476 | Iustin Pop | ip_map[old] = new_ip |
149 | ea5fd476 | Iustin Pop | return new_ip |
150 | ea5fd476 | Iustin Pop | |
151 | ea5fd476 | Iustin Pop | ip_map = {} |
152 | ea5fd476 | Iustin Pop | |
153 | ea5fd476 | Iustin Pop | cfg["cluster"]["master_ip"] = _Get(cfg["cluster"]["master_ip"]) |
154 | ea5fd476 | Iustin Pop | for node in cfg["nodes"].values(): |
155 | ea5fd476 | Iustin Pop | node["primary_ip"] = _Get(node["primary_ip"]) |
156 | ea5fd476 | Iustin Pop | node["secondary_ip"] = _Get(node["secondary_ip"]) |
157 | ea5fd476 | Iustin Pop | |
158 | ea5fd476 | Iustin Pop | for instance in cfg["instances"].values(): |
159 | ea5fd476 | Iustin Pop | for nic in instance["nics"]: |
160 | ea5fd476 | Iustin Pop | if "ip" in nic and nic["ip"]: |
161 | ea5fd476 | Iustin Pop | nic["ip"] = _Get(nic["ip"]) |
162 | ea5fd476 | Iustin Pop | |
163 | ea5fd476 | Iustin Pop | |
164 | ea5fd476 | Iustin Pop | def SanitizeOsNames(opts, cfg): # pylint: disable-msg=W0613 |
165 | ea5fd476 | Iustin Pop | """Sanitize the OS names. |
166 | ea5fd476 | Iustin Pop | |
167 | ea5fd476 | Iustin Pop | """ |
168 | ea5fd476 | Iustin Pop | def _Get(old): |
169 | ea5fd476 | Iustin Pop | if old in os_map: |
170 | ea5fd476 | Iustin Pop | return os_map[old] |
171 | ea5fd476 | Iustin Pop | os_map[old] = "ganeti-os%d" % (len(os_map) + 1) |
172 | ea5fd476 | Iustin Pop | return os_map[old] |
173 | ea5fd476 | Iustin Pop | |
174 | ea5fd476 | Iustin Pop | os_map = {} |
175 | ea5fd476 | Iustin Pop | for instance in cfg["instances"].values(): |
176 | ea5fd476 | Iustin Pop | instance["os"] = _Get(instance["os"]) |
177 | ea5fd476 | Iustin Pop | |
178 | ea5fd476 | Iustin Pop | if "os_hvp" in cfg["cluster"]: |
179 | ea5fd476 | Iustin Pop | for os_name in cfg["cluster"]["os_hvp"]: |
180 | ea5fd476 | Iustin Pop | # force population of the entire os map |
181 | ea5fd476 | Iustin Pop | _Get(os_name) |
182 | ea5fd476 | Iustin Pop | RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False) |
183 | ea5fd476 | Iustin Pop | |
184 | ea5fd476 | Iustin Pop | |
185 | ea5fd476 | Iustin Pop | def SanitizeDisks(opts, cfg): # pylint: disable-msg=W0613 |
186 | ea5fd476 | Iustin Pop | """Cleanup disks disks. |
187 | ea5fd476 | Iustin Pop | |
188 | ea5fd476 | Iustin Pop | """ |
189 | ea5fd476 | Iustin Pop | def _Get(old): |
190 | ea5fd476 | Iustin Pop | if old in lv_map: |
191 | ea5fd476 | Iustin Pop | return old |
192 | ea5fd476 | Iustin Pop | lv_map[old] = utils.NewUUID() |
193 | ea5fd476 | Iustin Pop | return lv_map[old] |
194 | ea5fd476 | Iustin Pop | |
195 | ea5fd476 | Iustin Pop | def helper(disk): |
196 | ea5fd476 | Iustin Pop | if "children" in disk and disk["children"]: |
197 | ea5fd476 | Iustin Pop | for child in disk["children"]: |
198 | ea5fd476 | Iustin Pop | helper(child) |
199 | ea5fd476 | Iustin Pop | |
200 | ea5fd476 | Iustin Pop | if disk["dev_type"] == constants.LD_DRBD8: |
201 | ea5fd476 | Iustin Pop | if "physical_id" in disk: |
202 | ea5fd476 | Iustin Pop | del disk["physical_id"] |
203 | ea5fd476 | Iustin Pop | |
204 | ea5fd476 | Iustin Pop | if disk["dev_type"] == constants.LD_LV and opts.sanitize_lvs: |
205 | ea5fd476 | Iustin Pop | disk["logical_id"][1] = _Get(disk["logical_id"][1]) |
206 | ea5fd476 | Iustin Pop | disk["physical_id"][1] = disk["logical_id"][1] |
207 | ea5fd476 | Iustin Pop | |
208 | ea5fd476 | Iustin Pop | lv_map = {} |
209 | ea5fd476 | Iustin Pop | |
210 | ea5fd476 | Iustin Pop | for instance in cfg["instances"].values(): |
211 | ea5fd476 | Iustin Pop | for disk in instance["disks"]: |
212 | ea5fd476 | Iustin Pop | helper(disk) |
213 | ea5fd476 | Iustin Pop | |
214 | ea5fd476 | Iustin Pop | |
215 | ea5fd476 | Iustin Pop | def RandomizeDiskSecrets(disk): |
216 | ea5fd476 | Iustin Pop | """Randomize a disks' secrets (if any). |
217 | ea5fd476 | Iustin Pop | |
218 | ea5fd476 | Iustin Pop | """ |
219 | ea5fd476 | Iustin Pop | if "children" in disk and disk["children"]: |
220 | ea5fd476 | Iustin Pop | for child in disk["children"]: |
221 | ea5fd476 | Iustin Pop | RandomizeDiskSecrets(child) |
222 | ea5fd476 | Iustin Pop | |
223 | ea5fd476 | Iustin Pop | # only disk type to contain secrets is the drbd one |
224 | ea5fd476 | Iustin Pop | if disk["dev_type"] == constants.LD_DRBD8: |
225 | ea5fd476 | Iustin Pop | disk["logical_id"][5] = utils.GenerateSecret() |
226 | ea5fd476 | Iustin Pop | |
227 | ea5fd476 | Iustin Pop | |
228 | ea5fd476 | Iustin Pop | def RenameDiskNodes(disk, node_map): |
229 | ea5fd476 | Iustin Pop | """Rename nodes in the disk config. |
230 | ea5fd476 | Iustin Pop | |
231 | ea5fd476 | Iustin Pop | """ |
232 | ea5fd476 | Iustin Pop | if "children" in disk and disk["children"]: |
233 | ea5fd476 | Iustin Pop | for child in disk["children"]: |
234 | ea5fd476 | Iustin Pop | RenameDiskNodes(child, node_map) |
235 | ea5fd476 | Iustin Pop | |
236 | ea5fd476 | Iustin Pop | # only disk type to contain nodes is the drbd one |
237 | ea5fd476 | Iustin Pop | if disk["dev_type"] == constants.LD_DRBD8: |
238 | ea5fd476 | Iustin Pop | lid = disk["logical_id"] |
239 | ea5fd476 | Iustin Pop | lid[0] = node_map[lid[0]] |
240 | ea5fd476 | Iustin Pop | lid[1] = node_map[lid[1]] |
241 | ea5fd476 | Iustin Pop | |
242 | ea5fd476 | Iustin Pop | |
243 | ea5fd476 | Iustin Pop | def RenameDictKeys(a_dict, name_map, update_name): |
244 | ea5fd476 | Iustin Pop | """Rename the dictionary keys based on a name map. |
245 | ea5fd476 | Iustin Pop | |
246 | ea5fd476 | Iustin Pop | """ |
247 | ea5fd476 | Iustin Pop | for old_name in a_dict.keys(): |
248 | ea5fd476 | Iustin Pop | new_name = name_map[old_name] |
249 | ea5fd476 | Iustin Pop | a_dict[new_name] = a_dict[old_name] |
250 | ea5fd476 | Iustin Pop | del a_dict[old_name] |
251 | ea5fd476 | Iustin Pop | if update_name: |
252 | ea5fd476 | Iustin Pop | a_dict[new_name]["name"] = new_name |
253 | ea5fd476 | Iustin Pop | |
254 | ea5fd476 | Iustin Pop | |
255 | ea5fd476 | Iustin Pop | def main(): |
256 | ea5fd476 | Iustin Pop | """Main program. |
257 | ea5fd476 | Iustin Pop | |
258 | ea5fd476 | Iustin Pop | """ |
259 | ea5fd476 | Iustin Pop | # Option parsing |
260 | ea5fd476 | Iustin Pop | parser = optparse.OptionParser(usage="%prog [--verbose] output_file") |
261 | ea5fd476 | Iustin Pop | |
262 | ea5fd476 | Iustin Pop | for o in OPTS: |
263 | ea5fd476 | Iustin Pop | parser.add_option(o) |
264 | ea5fd476 | Iustin Pop | |
265 | ea5fd476 | Iustin Pop | (opts, args) = parser.parse_args() |
266 | ea5fd476 | Iustin Pop | if opts.no_randomization: |
267 | ea5fd476 | Iustin Pop | opts.sanitize_names = opts.sanitize_ips = opts.sanitize_os_names = \ |
268 | ea5fd476 | Iustin Pop | opts.sanitize_lvs = False |
269 | ea5fd476 | Iustin Pop | |
270 | ea5fd476 | Iustin Pop | # Option checking |
271 | ea5fd476 | Iustin Pop | if len(args) != 1: |
272 | ea5fd476 | Iustin Pop | Error("Usage: sanitize-config [options] {<output_file> | -}") |
273 | ea5fd476 | Iustin Pop | |
274 | ea5fd476 | Iustin Pop | # Check whether it's a Ganeti configuration directory |
275 | ea5fd476 | Iustin Pop | if not os.path.isfile(opts.CONFIG_DATA_PATH): |
276 | ea5fd476 | Iustin Pop | Error("Cannot find Ganeti configuration file %s", opts.CONFIG_DATA_PATH) |
277 | ea5fd476 | Iustin Pop | |
278 | ea5fd476 | Iustin Pop | config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH)) |
279 | ea5fd476 | Iustin Pop | |
280 | ea5fd476 | Iustin Pop | # first, do some disk cleanup: remove DRBD physical_ids, since it |
281 | ea5fd476 | Iustin Pop | # contains both IPs (which we want changed) and the DRBD secret, and |
282 | ea5fd476 | Iustin Pop | # it's not needed for normal functioning, and randomize LVM names |
283 | ea5fd476 | Iustin Pop | SanitizeDisks(opts, config_data) |
284 | ea5fd476 | Iustin Pop | |
285 | ea5fd476 | Iustin Pop | SanitizeSecrets(opts, config_data) |
286 | ea5fd476 | Iustin Pop | |
287 | ea5fd476 | Iustin Pop | if opts.sanitize_names: |
288 | ea5fd476 | Iustin Pop | SanitizeCluster(opts, config_data) |
289 | ea5fd476 | Iustin Pop | SanitizeNodes(opts, config_data) |
290 | ea5fd476 | Iustin Pop | SanitizeInstances(opts, config_data) |
291 | ea5fd476 | Iustin Pop | |
292 | ea5fd476 | Iustin Pop | if opts.sanitize_ips: |
293 | ea5fd476 | Iustin Pop | SanitizeIps(opts, config_data) |
294 | ea5fd476 | Iustin Pop | |
295 | ea5fd476 | Iustin Pop | if opts.sanitize_os_names: |
296 | ea5fd476 | Iustin Pop | SanitizeOsNames(opts, config_data) |
297 | ea5fd476 | Iustin Pop | |
298 | ea5fd476 | Iustin Pop | data = serializer.DumpJson(config_data) |
299 | ea5fd476 | Iustin Pop | if args[0] == "-": |
300 | ea5fd476 | Iustin Pop | sys.stdout.write(data) |
301 | ea5fd476 | Iustin Pop | else: |
302 | ea5fd476 | Iustin Pop | utils.WriteFile(file_name=args[0], |
303 | ea5fd476 | Iustin Pop | data=data, |
304 | ea5fd476 | Iustin Pop | mode=0600, |
305 | ea5fd476 | Iustin Pop | backup=True) |
306 | ea5fd476 | Iustin Pop | |
307 | ea5fd476 | Iustin Pop | if __name__ == "__main__": |
308 | ea5fd476 | Iustin Pop | main() |