Statistics
| Branch: | Tag: | Revision:

root / tools / sanitize-config @ 18397489

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.LD_DRBD8:
203
      if "physical_id" in disk:
204
        del disk["physical_id"]
205

    
206
    if disk["dev_type"] == constants.LD_LV 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.LD_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.LD_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()