Coverage for nova/privsep/idmapshift.py: 95%

74 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright 2014 Rackspace, Andrew Melton 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15""" 

16 

17IDMapShift is a tool that properly sets the ownership of a filesystem for use 

18with linux user namespaces. 

19 

20When using user namespaces with linux containers, the filesystem of the 

21container must be owned by the targeted user and group ids being applied 

22to that container. Otherwise, processes inside the container won't be able 

23to access the filesystem. 

24 

25For example, when using the id map string '0:10000:2000', this means that 

26user ids inside the container between 0 and 1999 will map to user ids on 

27the host between 10000 and 11999. Root (0) becomes 10000, user 1 becomes 

2810001, user 50 becomes 10050 and user 1999 becomes 11999. This means that 

29files that are owned by root need to actually be owned by user 10000, and 

30files owned by 50 need to be owned by 10050, and so on. 

31 

32IDMapShift will take the uid and gid strings used for user namespaces and 

33properly set up the filesystem for use by those users. Uids and gids outside 

34of provided ranges will be mapped to nobody (max uid/gid) so that they are 

35inaccessible inside the container. 

36""" 

37 

38 

39import os 

40 

41from oslo_log import log as logging 

42 

43import nova.privsep 

44 

45LOG = logging.getLogger(__name__) 

46NOBODY_ID = 65534 

47 

48 

49def find_target_id(fsid, mappings, nobody, memo): 

50 if fsid not in memo: 

51 for start, target, count in mappings: 

52 if start <= fsid < start + count: 

53 memo[fsid] = (fsid - start) + target 

54 break 

55 else: 

56 memo[fsid] = nobody 

57 

58 return memo[fsid] 

59 

60 

61def print_chown(path, uid, gid, target_uid, target_gid): 

62 LOG.debug('%s %s:%s -> %s:%s', path, uid, gid, target_uid, target_gid) 

63 

64 

65def shift_path(path, uid_mappings, gid_mappings, nobody, uid_memo, gid_memo): 

66 stat = os.lstat(path) 

67 uid = stat.st_uid 

68 gid = stat.st_gid 

69 target_uid = find_target_id(uid, uid_mappings, nobody, uid_memo) 

70 target_gid = find_target_id(gid, gid_mappings, nobody, gid_memo) 

71 print_chown(path, uid, gid, target_uid, target_gid) 

72 os.lchown(path, target_uid, target_gid) 

73 

74 

75def shift_dir(fsdir, uid_mappings, gid_mappings, nobody): 

76 uid_memo = dict() 

77 gid_memo = dict() 

78 

79 def shift_path_short(p): 

80 shift_path(p, uid_mappings, gid_mappings, nobody, 

81 uid_memo=uid_memo, gid_memo=gid_memo) 

82 

83 shift_path_short(fsdir) 

84 for root, dirs, files in os.walk(fsdir): 

85 for d in dirs: 

86 path = os.path.join(root, d) 

87 shift_path_short(path) 

88 for f in files: 

89 path = os.path.join(root, f) 

90 shift_path_short(path) 

91 

92 

93def confirm_path(path, uid_ranges, gid_ranges, nobody): 

94 stat = os.lstat(path) 

95 uid = stat.st_uid 

96 gid = stat.st_gid 

97 

98 uid_in_range = True if uid == nobody else False 

99 gid_in_range = True if gid == nobody else False 

100 

101 if not uid_in_range or not gid_in_range: 

102 for (start, end) in uid_ranges: 

103 if start <= uid <= end: 

104 uid_in_range = True 

105 break 

106 

107 for (start, end) in gid_ranges: 

108 if start <= gid <= end: 

109 gid_in_range = True 

110 break 

111 

112 return uid_in_range and gid_in_range 

113 

114 

115def get_ranges(maps): 

116 return [(target, target + count - 1) for (start, target, count) in maps] 

117 

118 

119def confirm_dir(fsdir, uid_mappings, gid_mappings, nobody): 

120 uid_ranges = get_ranges(uid_mappings) 

121 gid_ranges = get_ranges(gid_mappings) 

122 

123 if not confirm_path(fsdir, uid_ranges, gid_ranges, nobody): 

124 return False 

125 for root, dirs, files in os.walk(fsdir): 

126 for d in dirs: 

127 path = os.path.join(root, d) 

128 if not confirm_path(path, uid_ranges, gid_ranges, nobody): 

129 return False 

130 for f in files: 

131 path = os.path.join(root, f) 

132 if not confirm_path(path, uid_ranges, gid_ranges, nobody): 

133 return False 

134 return True 

135 

136 

137@nova.privsep.sys_admin_pctxt.entrypoint 

138def shift(path, uid_map, gid_map): 

139 if confirm_dir(uid_map, gid_map, path, NOBODY_ID): 

140 return 

141 shift_dir(path, uid_map, gid_map, NOBODY_ID)