Browse code

Introduce same-uid-as and same-gid-as.

Xavier G authored on 14/04/2020 20:30:16
Showing 2 changed files

... ...
@@ -67,6 +67,8 @@ class CombinedFSConfiguration(object):
67 67
 		self.files = conf.get('files', {})
68 68
 		self.uid = int(conf.get('uid', DEFAULT_UID))
69 69
 		self.gid = int(conf.get('gid', DEFAULT_GID))
70
+		self.same_uid_as = conf.get('same-uid-as', None)
71
+		self.same_gid_as = conf.get('same-gid-as', None)
70 72
 		self.dir_mode = read_mode_setting(conf, 'dir_mode', DEFAULT_DIR_MODE)
71 73
 		self.reg_mode = read_mode_setting(conf, 'reg_mode', DEFAULT_REG_MODE)
72 74
 		self.key_mode = read_mode_setting(conf, 'key_mode', DEFAULT_KEY_MODE)
... ...
@@ -171,6 +173,79 @@ class CombinedFS(Operations):
171 173
 	def get_conf(self):
172 174
 		return self.configuration
173 175
 
176
+	# uid/gid-related helpers; in the end, the xid (uid/gid) precedence is:
177
+	#  - filespec/same-xid-as
178
+	#  - filespec/xid
179
+	#  - conf/same-xid-as
180
+	#  - conf/xid
181
+	#  - DEFAULT_XID
182
+	def get_uid_gid(self, conf, filespec=None):
183
+		"""
184
+		Just-get-it-done wrapper around get_{uid,gid}_{global,for_filespec}.
185
+		"""
186
+		stats = {}
187
+		if filespec is None:
188
+			return self.get_uid_global(conf, stats), self.get_gid_global(conf, stats)
189
+		return self.get_uid_for_filespec(conf, filespec, stats), self.get_gid_for_filespec(conf, filespec, stats)
190
+
191
+	def get_uid_for_filespec(self, conf, filespec, stats):
192
+		"""
193
+		File-specific uid selection mechanism: attempt to use file-specific same-uid-as, falling back on
194
+		file-specific uid, falling back on global uid selection mechanism.
195
+		"""
196
+		uid = self.get_stat_attr(filespec.get('same-uid-as', None), 'st_uid', filespec.get('uid', None), stats)
197
+		if uid is None:
198
+			uid = self.get_uid_global(conf, stats)
199
+		return uid
200
+
201
+	def get_gid_for_filespec(self, conf, filespec, stats):
202
+		"""
203
+		File-specific gid selection mechanism: attempt to use file-specific same-gid-as, falling back on
204
+		file-specific gid, falling back on global gid selection mechanism.
205
+		"""
206
+		gid = self.get_stat_attr(filespec.get('same-gid-as', None), 'st_gid', filespec.get('gid', None), stats)
207
+		if gid is None:
208
+			gid = self.get_gid_global(conf, stats)
209
+		return gid
210
+
211
+	def get_uid_global(self, conf, stats):
212
+		"""
213
+		Global uid selection mechanism: attempt to use same-uid-as, falling back on uid.
214
+		"""
215
+		return self.get_stat_attr(conf.same_uid_as, 'st_uid', conf.uid, stats)
216
+
217
+	def get_gid_global(self, conf, stats):
218
+		"""
219
+		Global gid selection mechanism: attempt to use same-gid-as, falling back on gid.
220
+		"""
221
+		return self.get_stat_attr(conf.same_gid_as, 'st_gid', conf.gid, stats)
222
+
223
+	def get_stat_attr(self, path, attr, default, stats):
224
+		"""
225
+		Stat path and return the request attribute, or the default value if something goes wrong.
226
+		"""
227
+		if path is None:
228
+			return default
229
+		try:
230
+			return getattr(self.get_stat(path, stats), attr)
231
+		except:
232
+			return default
233
+
234
+	def get_stat(self, path, stats):
235
+		"""
236
+		Simple wrapper around os.stat() that uses a dict to implement some basic caching (for the sake of
237
+		uid/gid consistency, not actually for performance). Return either None or a stat structure.
238
+		Should throw no exceptions as long as stats is provided.
239
+		"""
240
+		stat = stats.get(path)
241
+		if stat is None:
242
+			try:
243
+				 stats[path] = stat = os.stat(path)
244
+			except:
245
+				pass
246
+		return stat
247
+	# End of uid/gid-related helpers
248
+
174 249
 	def iterate_paths(self, func, paths):
175 250
 		for filepath in paths:
176 251
 			try:
... ...
@@ -215,10 +290,11 @@ class CombinedFS(Operations):
215 290
 			return None
216 291
 
217 292
 	def handle_reload_getattr(self, conf, fh):
293
+		uid, gid = self.get_uid_gid(conf)
218 294
 		return {
219 295
 			'st_nlink': 1,
220
-			'st_uid': conf.uid,
221
-			'st_gid': conf.gid,
296
+			'st_uid': uid,
297
+			'st_gid': gid,
222 298
 			'st_size': RELOAD_FILESIZE,
223 299
 			'st_mode': stat.S_IFREG | conf.key_mode,
224 300
 		}
... ...
@@ -261,14 +337,16 @@ class CombinedFS(Operations):
261 337
 						dir_attrs[prop] = 0
262 338
 				else:
263 339
 					raise
264
-			dir_attrs['st_uid'] = conf.uid
265
-			dir_attrs['st_gid'] = conf.gid
340
+			uid, gid = self.get_uid_gid(conf)
341
+			dir_attrs['st_uid'] = uid
342
+			dir_attrs['st_gid'] = gid
266 343
 			dir_attrs['st_mode'] = stat.S_IFDIR | conf.dir_mode
267 344
 			return dir_attrs
345
+		uid, gid = self.get_uid_gid(conf, file_spec)
268 346
 		attrs = {
269 347
 			'st_nlink': 1,
270
-			'st_uid': file_spec.get('uid', conf.uid),
271
-			'st_gid': file_spec.get('gid', conf.gid),
348
+			'st_uid': uid,
349
+			'st_gid': gid,
272 350
 			'st_size': 0,
273 351
 		}
274 352
 		def_mode = conf.reg_mode
... ...
@@ -39,6 +39,15 @@ separator: '/'
39 39
 # These settings do NOT understand user and group names.
40 40
 uid: 1305
41 41
 gid: 25519
42
+# They default to 0.
43
+
44
+# However, hardcoding user/group ids can be a timebomb.
45
+# These settings provide the ability to reflect the same uid or gid as a given
46
+# file:
47
+same-uid-as: '/path/to/target/user/home/directory'
48
+same-gid-as: '/path/to/file/with/adequate/ownership'
49
+# If the mentioned paths cannot be stat()ed, combinedfs defaults to the values
50
+# set via uid and gid respectively.
42 51
 
43 52
 # Unix modes: base-8 notation in a string.
44 53
 # Directories:
... ...
@@ -64,7 +73,7 @@ files:
64 73
   #  content: ['chain.pem']
65 74
   fullchain.pem:
66 75
     content: ['fullchain.pem']
67
-    # Per-file permissions:
76
+    # Per-file permissions; same-uid-as and same-gid-as are available too:
68 77
     uid: 2223
69 78
     gid: 2226
70 79
     mode: '0440'