Browse code

Move configuration to a separate object.

Xavier G authored on 11/04/2020 11:04:38
Showing 1 changed files

... ...
@@ -36,8 +36,23 @@ DEFAULT_KEY_MODE = 0o400
36 36
 DEFAULT_SENSITIVE_PATTERN = '/privkey.pem$'
37 37
 TIME_PROPS = ('st_atime', 'st_ctime', 'st_mtime')
38 38
 
39
-class CombinedFS(Operations):
40
-	def __init__(self, conf):
39
+def read_mode_setting(obj, key, default):
40
+	try:
41
+		return int(obj[key], 8)
42
+	except (KeyError, ValueError):
43
+		return default
44
+
45
+class CombinedFSConfiguration(object):
46
+	def __init__(self, conf_path):
47
+		self.read_conf(conf_path)
48
+
49
+	def read_conf(self, conf_path):
50
+		with open(conf_path) as conf_file:
51
+			conf = yaml.load(conf_file.read())
52
+			self.apply_conf(conf)
53
+			self.path = conf_path
54
+
55
+	def apply_conf(self, conf):
41 56
 		self.root = conf.get('letsencrypt_live', DEFAULT_ROOT)
42 57
 		self.filter = conf.get('cert_filter', DEFAULT_CERT_FILTER)
43 58
 		self.whitelist = conf.get('cert_whitelist', DEFAULT_WHITELIST)
... ...
@@ -46,27 +61,16 @@ class CombinedFS(Operations):
46 61
 		self.files = conf.get('files', {})
47 62
 		self.uid = int(conf.get('uid', DEFAULT_UID))
48 63
 		self.gid = int(conf.get('gid', DEFAULT_GID))
49
-		self.dir_mode = self.read_mode_setting(conf, 'dir_mode', DEFAULT_DIR_MODE)
50
-		self.reg_mode = self.read_mode_setting(conf, 'reg_mode', DEFAULT_REG_MODE)
51
-		self.key_mode = self.read_mode_setting(conf, 'key_mode', DEFAULT_KEY_MODE)
64
+		self.dir_mode = read_mode_setting(conf, 'dir_mode', DEFAULT_DIR_MODE)
65
+		self.reg_mode = read_mode_setting(conf, 'reg_mode', DEFAULT_REG_MODE)
66
+		self.key_mode = read_mode_setting(conf, 'key_mode', DEFAULT_KEY_MODE)
52 67
 		self.sensitive_pattern = conf.get('sensitive_pattern', DEFAULT_SENSITIVE_PATTERN)
53
-		# File descriptor management:
54
-		self.filedesc_lock = threading.Lock()
55
-		self.filedesc_index = 0
56
-		self.filedesc = {}
57 68
 		# Compile regexes:
58 69
 		self.sensitive_pattern_re = re.compile(self.sensitive_pattern)
59 70
 		if self.filter:
60 71
 			self.pattern_re = re.compile(self.pattern)
61 72
 
62 73
 	# Helpers:
63
-
64
-	def read_mode_setting(self, obj, key, default):
65
-		try:
66
-			return int(obj[key], 8)
67
-		except (KeyError, ValueError):
68
-			return default
69
-
70 74
 	def filter_cert(self, cert):
71 75
 		if not self.filter:
72 76
 			return True
... ...
@@ -117,11 +121,6 @@ class CombinedFS(Operations):
117 121
 				raise FuseOSError(errno.ENOENT)
118 122
 		return cert, filename, file_spec
119 123
 
120
-	def attributes(self, full_path):
121
-		st = os.lstat(full_path)
122
-		return dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime',
123
-		      'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid'))
124
-
125 124
 	def expand_path(self, cert, path):
126 125
 		expanded_path = path.replace('${cert}', cert)
127 126
 		if not expanded_path.startswith('/'):
... ...
@@ -141,6 +140,24 @@ class CombinedFS(Operations):
141 140
 	def is_sensitive_file(self, filepath):
142 141
 		return self.sensitive_pattern_re.search(filepath)
143 142
 
143
+class CombinedFS(Operations):
144
+	def __init__(self, conf_path):
145
+		# Configuration:
146
+		self.configuration = CombinedFSConfiguration(conf_path)
147
+		# File descriptor management:
148
+		self.filedesc_lock = threading.Lock()
149
+		self.filedesc_index = 0
150
+		self.filedesc = {}
151
+
152
+	# Helpers:
153
+	def attributes(self, full_path):
154
+		st = os.lstat(full_path)
155
+		return dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime',
156
+		      'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid'))
157
+
158
+	def get_conf(self):
159
+		return self.configuration
160
+
144 161
 	def iterate_paths(self, func, paths):
145 162
 		for filepath in paths:
146 163
 			try:
... ...
@@ -177,28 +194,29 @@ class CombinedFS(Operations):
177 194
 		raise FuseOSError(errno.ENOTSUP)
178 195
 
179 196
 	def getattr(self, path, fh=None):
180
-		cert, filename, file_spec = self.analyse_path(path)
197
+		conf = self.get_conf()
198
+		cert, filename, file_spec = conf.analyse_path(path)
181 199
 		if filename is None: # Directory
182
-			full_path = os.path.join(self.root, path.lstrip('/'))
200
+			full_path = os.path.join(conf.root, path.lstrip('/'))
183 201
 			dir_attrs = self.attributes(full_path)
184
-			dir_attrs['st_uid'] = self.uid
185
-			dir_attrs['st_gid'] = self.gid
186
-			dir_attrs['st_mode'] = stat.S_IFDIR | self.dir_mode
202
+			dir_attrs['st_uid'] = conf.uid
203
+			dir_attrs['st_gid'] = conf.gid
204
+			dir_attrs['st_mode'] = stat.S_IFDIR | conf.dir_mode
187 205
 			return dir_attrs
188 206
 		attrs = {
189 207
 			'st_nlink': 1,
190
-			'st_uid': file_spec.get('uid', self.uid),
191
-			'st_gid': file_spec.get('gid', self.gid),
208
+			'st_uid': file_spec.get('uid', conf.uid),
209
+			'st_gid': file_spec.get('gid', conf.gid),
192 210
 			'st_size': 0,
193 211
 		}
194
-		def_mode = self.reg_mode
195
-		paths = self.get_paths(cert, file_spec)
212
+		def_mode = conf.reg_mode
213
+		paths = conf.get_paths(cert, file_spec)
196 214
 		if not paths:
197 215
 			# Virtual empty file:
198
-			root_stats = os.stat(self.root)
216
+			root_stats = os.stat(conf.root)
199 217
 			for prop in TIME_PROPS:
200 218
 				attrs[prop] = getattr(root_stats, prop)
201
-			attrs['st_mode'] = stat.S_IFREG | self.read_mode_setting(file_spec, 'mode', def_mode)
219
+			attrs['st_mode'] = stat.S_IFREG | read_mode_setting(file_spec, 'mode', def_mode)
202 220
 			return attrs
203 221
 		stats = {}
204 222
 		def stat_file(path):
... ...
@@ -213,13 +231,14 @@ class CombinedFS(Operations):
213 231
 			# Add up sizes:
214 232
 			attrs['st_size'] += stat_obj.st_size
215 233
 			# Lower permissions if necessary:
216
-			if self.is_sensitive_file(filepath):
217
-				def_mode = self.key_mode
218
-		attrs['st_mode'] = stat.S_IFREG | self.read_mode_setting(file_spec, 'mode', def_mode)
234
+			if conf.is_sensitive_file(filepath):
235
+				def_mode = conf.key_mode
236
+		attrs['st_mode'] = stat.S_IFREG | read_mode_setting(file_spec, 'mode', def_mode)
219 237
 		return attrs
220 238
 
221 239
 	def readdir(self, path, fh):
222
-		cert, filename, _ = self.analyse_path(path)
240
+		conf = self.get_conf()
241
+		cert, filename, _ = conf.analyse_path(path)
223 242
 		# Deal only with directories:
224 243
 		if filename:
225 244
 			raise FuseOSError(errno.ENOTDIR)
... ...
@@ -231,24 +250,26 @@ class CombinedFS(Operations):
231 250
 		yield '..', dir_attrs, 0
232 251
 		if not cert:
233 252
 			# Top-level directory
234
-			flat_mode = self.separator != '/'
235
-			for cert in (d for d in os.listdir(self.root) if self.filter_cert(d)):
253
+			flat_mode = conf.separator != '/'
254
+			for cert in (d for d in os.listdir(conf.root) if conf.filter_cert(d)):
236 255
 				if flat_mode:
237
-					for filename in self.files:
238
-						yield cert + self.separator + filename, reg_attrs, 0
256
+					for filename in conf.files:
257
+						yield cert + conf.separator + filename, reg_attrs, 0
239 258
 				else:
240 259
 					yield cert, dir_attrs, 0
241 260
 		else:
242 261
 			# Second-level directory
243
-			for filename in self.files:
262
+			for filename in conf.files:
244 263
 				yield filename, reg_attrs, 0
245 264
 
246 265
 	def open(self, path, flags):
247
-		cert, filename, file_spec = self.analyse_path(path)
266
+		conf = self.get_conf()
267
+		cert, filename, file_spec = conf.analyse_path(path)
248 268
 		if not cert or not filename:
249 269
 			raise FuseOSError(errno.ENOENT)
250 270
 		# Being a read-only filesystem spares us the need to check most flags.
251 271
 		new_fd = {
272
+			'conf': conf,
252 273
 			'cert': cert,
253 274
 			'filename': filename,
254 275
 			'file_spec': file_spec,
... ...
@@ -261,11 +282,13 @@ class CombinedFS(Operations):
261 282
 
262 283
 	def read(self, path, length, offset, fh):
263 284
 		filedesc = self.filedesc.get(fh)
285
+		# Use the same configuration as open() when it created the file descriptor:
286
+		conf = filedesc['conf']
264 287
 		if filedesc is None:
265 288
 			raise FuseOSError(errno.EBADF)
266 289
 		data = filedesc.get('data')
267 290
 		if data is None:
268
-			paths = self.get_paths(filedesc['cert'], filedesc['file_spec'])
291
+			paths = conf.get_paths(filedesc['cert'], filedesc['file_spec'])
269 292
 			data = {'data': bytes() }
270 293
 			def concatenate(path):
271 294
 				data['data'] += open(path, 'rb').read()
... ...
@@ -282,7 +305,7 @@ class CombinedFS(Operations):
282 305
 		# pure Python.
283 306
 
284 307
 	def statfs(self, path):
285
-		stv = os.statvfs(self.root)
308
+		stv = os.statvfs(self.get_conf().root)
286 309
 		return dict((key, getattr(stv, key)) for key in ('f_bavail', 'f_bfree',
287 310
 			'f_blocks', 'f_bsize', 'f_favail', 'f_ffree', 'f_files', 'f_flag',
288 311
 			'f_frsize', 'f_namemax'))
... ...
@@ -293,10 +316,7 @@ class CombinedFS(Operations):
293 316
 		raise FuseOSError(errno.EINVAL)
294 317
 
295 318
 def main(conf_path, mountpoint, foreground):
296
-	conf = {}
297
-	with open(conf_path) as conf_file:
298
-		conf = yaml.load(conf_file.read())
299
-	FUSE(CombinedFS(conf), mountpoint, foreground=foreground, ro=True, default_permissions=True, allow_other=True)
319
+	FUSE(CombinedFS(conf_path), mountpoint, foreground=foreground, ro=True, default_permissions=True, allow_other=True)
300 320
 
301 321
 if __name__ == '__main__':
302 322
 	parser = argparse.ArgumentParser(description='Expose a transformed, version of Let\'s Encrypt / Certbot\'s "live" directory')