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
« 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.
15"""
17IDMapShift is a tool that properly sets the ownership of a filesystem for use
18with linux user namespaces.
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.
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.
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"""
39import os
41from oslo_log import log as logging
43import nova.privsep
45LOG = logging.getLogger(__name__)
46NOBODY_ID = 65534
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
58 return memo[fsid]
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)
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)
75def shift_dir(fsdir, uid_mappings, gid_mappings, nobody):
76 uid_memo = dict()
77 gid_memo = dict()
79 def shift_path_short(p):
80 shift_path(p, uid_mappings, gid_mappings, nobody,
81 uid_memo=uid_memo, gid_memo=gid_memo)
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)
93def confirm_path(path, uid_ranges, gid_ranges, nobody):
94 stat = os.lstat(path)
95 uid = stat.st_uid
96 gid = stat.st_gid
98 uid_in_range = True if uid == nobody else False
99 gid_in_range = True if gid == nobody else False
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
107 for (start, end) in gid_ranges:
108 if start <= gid <= end:
109 gid_in_range = True
110 break
112 return uid_in_range and gid_in_range
115def get_ranges(maps):
116 return [(target, target + count - 1) for (start, target, count) in maps]
119def confirm_dir(fsdir, uid_mappings, gid_mappings, nobody):
120 uid_ranges = get_ranges(uid_mappings)
121 gid_ranges = get_ranges(gid_mappings)
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
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)