Statistics
| Branch: | Tag: | Revision:

root / tools / sanitize-config @ 72bb6b4e

History | View | Annotate | Download (8.4 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 cli
37
from ganeti.cli import cli_option
38

    
39

    
40
OPTS = [
41
  cli.VERBOSE_OPT,
42
  cli_option("--path", help="Convert this configuration file"
43
             " instead of '%s'" % constants.CLUSTER_CONF_FILE,
44
             default=constants.CLUSTER_CONF_FILE, dest="CONFIG_DATA_PATH"),
45
  cli_option("--sanitize-names", default="yes", type="bool",
46
             help="Randomize the cluster, node and instance names [yes]"),
47
  cli_option("--sanitize-ips", default="yes", type="bool",
48
             help="Randomize the cluster, node and instance IPs [yes]"),
49
  cli_option("--sanitize-lvs", default="no", type="bool",
50
             help="Randomize the LV names (for old clusters) [no]"),
51
  cli_option("--sanitize-os-names", default="yes", type="bool",
52
             help="Randomize the OS names [yes]"),
53
  cli_option("--no-randomization", default=False, action="store_true",
54
             help="Disable all name randomization (only randomize secrets)"),
55
  cli_option("--base-domain", default="example.com",
56
             help="The base domain used for new names [example.com]"),
57
  ]
58

    
59

    
60
def Error(txt, *args):
61
  """Writes a message to standard error and exits.
62

    
63
  """
64
  cli.ToStderr(txt, *args)
65
  sys.exit(1)
66

    
67

    
68
def GenerateNameMap(opts, names, base):
69
  """For a given set of names, generate a list of sane new names.
70

    
71
  """
72
  names = utils.NiceSort(names)
73
  name_map = {}
74
  for idx, old_name in enumerate(names):
75
    new_name = "%s%d.%s" % (base, idx + 1, opts.base_domain)
76
    if new_name in names:
77
      Error("Name conflict for %s: %s already exists", base, new_name)
78
    name_map[old_name] = new_name
79
  return name_map
80

    
81

    
82
def SanitizeSecrets(opts, cfg): # pylint: disable=W0613
83
  """Cleanup configuration secrets.
84

    
85
  """
86
  cfg["cluster"]["rsahostkeypub"] = ""
87
  for instance in cfg["instances"].values():
88
    for disk in instance["disks"]:
89
      RandomizeDiskSecrets(disk)
90

    
91

    
92
def SanitizeCluster(opts, cfg):
93
  """Sanitize the cluster names.
94

    
95
  """
96
  cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain
97

    
98

    
99
def SanitizeNodes(opts, cfg):
100
  """Sanitize node names.
101

    
102
  """
103
  old_names = cfg["nodes"].keys()
104
  old_map = GenerateNameMap(opts, old_names, "node")
105

    
106
  # rename nodes
107
  RenameDictKeys(cfg["nodes"], old_map, True)
108

    
109
  # update master node
110
  cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]]
111

    
112
  # update instance configuration
113
  for instance in cfg["instances"].values():
114
    instance["primary_node"] = old_map[instance["primary_node"]]
115
    for disk in instance["disks"]:
116
      RenameDiskNodes(disk, old_map)
117

    
118

    
119
def SanitizeInstances(opts, cfg):
120
  """Sanitize instance names.
121

    
122
  """
123
  old_names = cfg["instances"].keys()
124
  old_map = GenerateNameMap(opts, old_names, "instance")
125

    
126
  RenameDictKeys(cfg["instances"], old_map, True)
127

    
128

    
129
def SanitizeIps(opts, cfg): # pylint: disable=W0613
130
  """Sanitize the IP names.
131

    
132
  @note: we're interested in obscuring the old IPs, not in generating
133
      actually valid new IPs, so we chose to simply put IPv4
134
      addresses, irrelevant of whether IPv6 or IPv4 addresses existed
135
      before.
136

    
137
  """
138
  def _Get(old):
139
    if old in ip_map:
140
      return ip_map[old]
141
    idx = len(ip_map) + 1
142
    rest, d_octet = divmod(idx, 256)
143
    rest, c_octet = divmod(rest, 256)
144
    rest, b_octet = divmod(rest, 256)
145
    if rest > 0:
146
      Error("Too many IPs!")
147
    new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet)
148
    ip_map[old] = new_ip
149
    return new_ip
150

    
151
  ip_map = {}
152

    
153
  cfg["cluster"]["master_ip"] = _Get(cfg["cluster"]["master_ip"])
154
  for node in cfg["nodes"].values():
155
    node["primary_ip"] = _Get(node["primary_ip"])
156
    node["secondary_ip"] = _Get(node["secondary_ip"])
157

    
158
  for instance in cfg["instances"].values():
159
    for nic in instance["nics"]:
160
      if "ip" in nic and nic["ip"]:
161
        nic["ip"] = _Get(nic["ip"])
162

    
163

    
164
def SanitizeOsNames(opts, cfg): # pylint: disable=W0613
165
  """Sanitize the OS names.
166

    
167
  """
168
  def _Get(old):
169
    if old in os_map:
170
      return os_map[old]
171
    os_map[old] = "ganeti-os%d" % (len(os_map) + 1)
172
    return os_map[old]
173

    
174
  os_map = {}
175
  for instance in cfg["instances"].values():
176
    instance["os"] = _Get(instance["os"])
177

    
178
  if "os_hvp" in cfg["cluster"]:
179
    for os_name in cfg["cluster"]["os_hvp"]:
180
      # force population of the entire os map
181
      _Get(os_name)
182
    RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False)
183

    
184

    
185
def SanitizeDisks(opts, cfg): # pylint: disable=W0613
186
  """Cleanup disks disks.
187

    
188
  """
189
  def _Get(old):
190
    if old in lv_map:
191
      return old
192
    lv_map[old] = utils.NewUUID()
193
    return lv_map[old]
194

    
195
  def helper(disk):
196
    if "children" in disk and disk["children"]:
197
      for child in disk["children"]:
198
        helper(child)
199

    
200
    if disk["dev_type"] == constants.LD_DRBD8:
201
      if "physical_id" in disk:
202
        del disk["physical_id"]
203

    
204
    if disk["dev_type"] == constants.LD_LV and opts.sanitize_lvs:
205
      disk["logical_id"][1] = _Get(disk["logical_id"][1])
206
      disk["physical_id"][1] = disk["logical_id"][1]
207

    
208
  lv_map = {}
209

    
210
  for instance in cfg["instances"].values():
211
    for disk in instance["disks"]:
212
      helper(disk)
213

    
214

    
215
def RandomizeDiskSecrets(disk):
216
  """Randomize a disks' secrets (if any).
217

    
218
  """
219
  if "children" in disk and disk["children"]:
220
    for child in disk["children"]:
221
      RandomizeDiskSecrets(child)
222

    
223
  # only disk type to contain secrets is the drbd one
224
  if disk["dev_type"] == constants.LD_DRBD8:
225
    disk["logical_id"][5] = utils.GenerateSecret()
226

    
227

    
228
def RenameDiskNodes(disk, node_map):
229
  """Rename nodes in the disk config.
230

    
231
  """
232
  if "children" in disk and disk["children"]:
233
    for child in disk["children"]:
234
      RenameDiskNodes(child, node_map)
235

    
236
  # only disk type to contain nodes is the drbd one
237
  if disk["dev_type"] == constants.LD_DRBD8:
238
    lid = disk["logical_id"]
239
    lid[0] = node_map[lid[0]]
240
    lid[1] = node_map[lid[1]]
241

    
242

    
243
def RenameDictKeys(a_dict, name_map, update_name):
244
  """Rename the dictionary keys based on a name map.
245

    
246
  """
247
  for old_name in a_dict.keys():
248
    new_name = name_map[old_name]
249
    a_dict[new_name] = a_dict[old_name]
250
    del a_dict[old_name]
251
    if update_name:
252
      a_dict[new_name]["name"] = new_name
253

    
254

    
255
def main():
256
  """Main program.
257

    
258
  """
259
  # Option parsing
260
  parser = optparse.OptionParser(usage="%prog [--verbose] output_file")
261

    
262
  for o in OPTS:
263
    parser.add_option(o)
264

    
265
  (opts, args) = parser.parse_args()
266
  if opts.no_randomization:
267
    opts.sanitize_names = opts.sanitize_ips = opts.sanitize_os_names = \
268
        opts.sanitize_lvs = False
269

    
270
  # Option checking
271
  if len(args) != 1:
272
    Error("Usage: sanitize-config [options] {<output_file> | -}")
273

    
274
  # Check whether it's a Ganeti configuration directory
275
  if not os.path.isfile(opts.CONFIG_DATA_PATH):
276
    Error("Cannot find Ganeti configuration file %s", opts.CONFIG_DATA_PATH)
277

    
278
  config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH))
279

    
280
  # first, do some disk cleanup: remove DRBD physical_ids, since it
281
  # contains both IPs (which we want changed) and the DRBD secret, and
282
  # it's not needed for normal functioning, and randomize LVM names
283
  SanitizeDisks(opts, config_data)
284

    
285
  SanitizeSecrets(opts, config_data)
286

    
287
  if opts.sanitize_names:
288
    SanitizeCluster(opts, config_data)
289
    SanitizeNodes(opts, config_data)
290
    SanitizeInstances(opts, config_data)
291

    
292
  if opts.sanitize_ips:
293
    SanitizeIps(opts, config_data)
294

    
295
  if opts.sanitize_os_names:
296
    SanitizeOsNames(opts, config_data)
297

    
298
  data = serializer.DumpJson(config_data)
299
  if args[0] == "-":
300
    sys.stdout.write(data)
301
  else:
302
    utils.WriteFile(file_name=args[0],
303
                    data=data,
304
                    mode=0600,
305
                    backup=True)
306

    
307
if __name__ == "__main__":
308
  main()