ensure-dirs: Set correct permissions on ssconf files
[ganeti-local] / lib / tools / ensure_dirs.py
1 # Copyright (C) 2011 Google Inc.
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 # 02110-1301, USA.
17
18 """Script to ensure permissions on files/dirs are accurate.
19
20 """
21
22 import errno
23 import os
24 import os.path
25 import optparse
26 import sys
27 import stat
28
29 from ganeti import constants
30 from ganeti import errors
31 from ganeti import runtime
32 from ganeti import ssconf
33
34
35 (DIR, FILE) = range(2)
36 ALL_TYPES = frozenset([DIR, FILE])
37
38
39 class EnsureError(errors.GenericError):
40   """Top-level error class related to this script.
41
42   """
43
44
45 def EnsurePermission(path, mode, uid=-1, gid=-1, must_exist=True,
46                      _chmod_fn=os.chmod, _chown_fn=os.chown):
47   """Ensures that given path has given mode.
48
49   @param path: The path to the file
50   @param mode: The mode of the file
51   @param uid: The uid of the owner of this file
52   @param gid: The gid of the owner of this file
53   @param must_exist: Specifies if non-existance of path will be an error
54   @param _chmod_fn: chmod function to use (unittest only)
55   @param _chown_fn: chown function to use (unittest only)
56
57   """
58   try:
59     _chmod_fn(path, mode)
60
61     if max(uid, gid) > -1:
62       _chown_fn(path, uid, gid)
63   except EnvironmentError, err:
64     if err.errno == errno.ENOENT:
65       if must_exist:
66         raise EnsureError("Path %s does not exists, but should" % path)
67     else:
68       raise EnsureError("Error while changing permission on %s: %s" %
69                         (path, err))
70
71
72 def EnsureDir(path, mode, uid, gid, _stat_fn=os.lstat, _mkdir_fn=os.mkdir,
73               _ensure_fn=EnsurePermission):
74   """Ensures that given path is a dir and has given mode, uid and gid set.
75
76   @param path: The path to the file
77   @param mode: The mode of the file
78   @param uid: The uid of the owner of this file
79   @param gid: The gid of the owner of this file
80   @param _stat_fn: Stat function to use (unittest only)
81   @param _mkdir_fn: mkdir function to use (unittest only)
82   @param _ensure_fn: ensure function to use (unittest only)
83
84   """
85   try:
86     # We don't want to follow symlinks
87     st_mode = _stat_fn(path)[stat.ST_MODE]
88
89     if not stat.S_ISDIR(st_mode):
90       raise EnsureError("Path %s is expected to be a directory, but it's not" %
91                         path)
92   except EnvironmentError, err:
93     if err.errno == errno.ENOENT:
94       _mkdir_fn(path)
95     else:
96       raise EnsureError("Error while do a stat() on %s: %s" % (path, err))
97
98   _ensure_fn(path, mode, uid=uid, gid=gid)
99
100
101 def RecursiveEnsure(path, uid, gid, dir_perm, file_perm):
102   """Ensures permissions recursively down a directory.
103
104   This functions walks the path and sets permissions accordingly.
105
106   @param path: The absolute path to walk
107   @param uid: The uid used as owner
108   @param gid: The gid used as group
109   @param dir_perm: The permission bits set for directories
110   @param file_perm: The permission bits set for files
111
112   """
113   assert os.path.isabs(path), "Path %s is not absolute" % path
114   assert os.path.isdir(path), "Path %s is not a dir" % path
115
116   for root, dirs, files in os.walk(path):
117     for subdir in dirs:
118       EnsurePermission(os.path.join(root, subdir), dir_perm, uid=uid, gid=gid)
119
120     for filename in files:
121       EnsurePermission(os.path.join(root, filename), file_perm, uid=uid,
122                        gid=gid)
123
124
125 def ProcessPath(path):
126   """Processes a path component.
127
128   @param path: A tuple of the path component to process
129
130   """
131   (pathname, pathtype, mode, uid, gid) = path[0:5]
132
133   assert pathtype in ALL_TYPES
134
135   if pathtype == DIR:
136     # No additional parameters
137     assert len(path[5:]) == 0
138     EnsureDir(pathname, mode, uid, gid)
139   elif pathtype == FILE:
140     (must_exist, ) = path[5:]
141     EnsurePermission(pathname, mode, uid=uid, gid=gid, must_exist=must_exist)
142
143
144 def GetPaths():
145   """Returns a tuple of path objects to process.
146
147   """
148   getent = runtime.GetEnts()
149   masterd_log = constants.DAEMONS_LOGFILES[constants.MASTERD]
150   noded_log = constants.DAEMONS_LOGFILES[constants.NODED]
151   confd_log = constants.DAEMONS_LOGFILES[constants.CONFD]
152   rapi_log = constants.DAEMONS_LOGFILES[constants.RAPI]
153
154   rapi_dir = os.path.join(constants.DATA_DIR, "rapi")
155
156   paths = [
157     (constants.DATA_DIR, DIR, 0755, getent.masterd_uid,
158      getent.masterd_gid),
159     (constants.CLUSTER_DOMAIN_SECRET_FILE, FILE, 0640,
160      getent.masterd_uid, getent.masterd_gid, False),
161     (constants.CLUSTER_CONF_FILE, FILE, 0640, getent.masterd_uid,
162      getent.confd_gid, False),
163     (constants.CONFD_HMAC_KEY, FILE, 0440, getent.confd_uid,
164      getent.masterd_gid, False),
165     (constants.SSH_KNOWN_HOSTS_FILE, FILE, 0644, getent.masterd_uid,
166      getent.masterd_gid, False),
167     (constants.RAPI_CERT_FILE, FILE, 0440, getent.rapi_uid,
168      getent.masterd_gid, False),
169     (constants.NODED_CERT_FILE, FILE, 0440, getent.masterd_uid,
170      getent.masterd_gid, False),
171     ]
172
173   ss = ssconf.SimpleStore()
174   for ss_path in ss.GetFileList():
175     paths.append((ss_path, FILE, constants.SS_FILE_PERMS,
176                   getent.noded_uid, 0, False))
177
178   paths.extend([
179     (constants.QUEUE_DIR, DIR, 0700, getent.masterd_uid,
180      getent.masterd_gid),
181     (constants.JOB_QUEUE_SERIAL_FILE, FILE, 0600,
182      getent.masterd_uid, getent.masterd_gid, False),
183     (constants.JOB_QUEUE_ARCHIVE_DIR, DIR, 0700,
184      getent.masterd_uid, getent.masterd_gid),
185     (rapi_dir, DIR, 0750, getent.rapi_uid, getent.masterd_gid),
186     (constants.RAPI_USERS_FILE, FILE, 0640, getent.rapi_uid,
187      getent.masterd_gid, False),
188     (constants.RUN_GANETI_DIR, DIR, 0775, getent.masterd_uid,
189      getent.daemons_gid),
190     (constants.SOCKET_DIR, DIR, 0750, getent.masterd_uid,
191      getent.daemons_gid),
192     (constants.MASTER_SOCKET, FILE, 0770, getent.masterd_uid,
193      getent.daemons_gid, False),
194     (constants.BDEV_CACHE_DIR, DIR, 0755, getent.noded_uid,
195      getent.masterd_gid),
196     (constants.UIDPOOL_LOCKDIR, DIR, 0750, getent.noded_uid,
197      getent.masterd_gid),
198     (constants.DISK_LINKS_DIR, DIR, 0755, getent.noded_uid,
199      getent.masterd_gid),
200     (constants.CRYPTO_KEYS_DIR, DIR, 0700, getent.noded_uid,
201      getent.masterd_gid),
202     (constants.IMPORT_EXPORT_DIR, DIR, 0755, getent.noded_uid,
203      getent.masterd_gid),
204     (constants.LOG_DIR, DIR, 0770, getent.masterd_uid,
205      getent.daemons_gid),
206     (masterd_log, FILE, 0600, getent.masterd_uid, getent.masterd_gid,
207      False),
208     (confd_log, FILE, 0600, getent.confd_uid, getent.masterd_gid, False),
209     (noded_log, FILE, 0600, getent.noded_uid, getent.masterd_gid, False),
210     (rapi_log, FILE, 0600, getent.rapi_uid, getent.masterd_gid, False),
211     (constants.LOG_OS_DIR, DIR, 0750, getent.masterd_uid,
212      getent.daemons_gid),
213     ])
214
215   return tuple(paths)
216
217
218 def ParseOptions():
219   """Parses the options passed to the program.
220
221   @return: Options and arguments
222
223   """
224   program = os.path.basename(sys.argv[0])
225
226   parser = optparse.OptionParser(usage="%%prog [--full-run]",
227                                  prog=program)
228   parser.add_option("--full-run", "-f", dest="full_run", action="store_true",
229                     default=False, help=("Make a full run and collect"
230                                          " additional files (time consuming)"))
231
232   return parser.parse_args()
233
234
235 def Main():
236   """Main routine.
237
238   """
239   getent = runtime.GetEnts()
240   (opts, _) = ParseOptions()
241
242   try:
243     for path in GetPaths():
244       ProcessPath(path)
245
246     if opts.full_run:
247       RecursiveEnsure(constants.JOB_QUEUE_ARCHIVE_DIR, getent.masterd_uid,
248                       getent.masterd_gid, 0700, 0600)
249   except EnsureError, err:
250     print >> sys.stderr, "An error occurred while ensure permissions:", err
251     return constants.EXIT_FAILURE
252
253   return constants.EXIT_SUCCESS