3f7995f586
I can't see any evidence that anyone else uses our nova-idmapshift binary, and it adds a lot of complexity (flags we never call for example). Move the code we do actually use into the privsep directory and simplify our calls to it. Remove the extra binary from our install and documentation. Change-Id: Ibce28d20d166da154833376cf51f1877b829925e blueprint: hurrah-for-privsep
142 lines
4.5 KiB
Python
142 lines
4.5 KiB
Python
# Copyright 2014 Rackspace, Andrew Melton
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""
|
|
|
|
IDMapShift is a tool that properly sets the ownership of a filesystem for use
|
|
with linux user namespaces.
|
|
|
|
When using user namespaces with linux containers, the filesystem of the
|
|
container must be owned by the targeted user and group ids being applied
|
|
to that container. Otherwise, processes inside the container won't be able
|
|
to access the filesystem.
|
|
|
|
For example, when using the id map string '0:10000:2000', this means that
|
|
user ids inside the container between 0 and 1999 will map to user ids on
|
|
the host between 10000 and 11999. Root (0) becomes 10000, user 1 becomes
|
|
10001, user 50 becomes 10050 and user 1999 becomes 11999. This means that
|
|
files that are owned by root need to actually be owned by user 10000, and
|
|
files owned by 50 need to be owned by 10050, and so on.
|
|
|
|
IDMapShift will take the uid and gid strings used for user namespaces and
|
|
properly set up the filesystem for use by those users. Uids and gids outside
|
|
of provided ranges will be mapped to nobody (max uid/gid) so that they are
|
|
inaccessible inside the container.
|
|
"""
|
|
|
|
|
|
import os
|
|
|
|
from oslo_log import log as logging
|
|
|
|
import nova.privsep
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
NOBODY_ID = 65534
|
|
|
|
|
|
def find_target_id(fsid, mappings, nobody, memo):
|
|
if fsid not in memo:
|
|
for start, target, count in mappings:
|
|
if start <= fsid < start + count:
|
|
memo[fsid] = (fsid - start) + target
|
|
break
|
|
else:
|
|
memo[fsid] = nobody
|
|
|
|
return memo[fsid]
|
|
|
|
|
|
def print_chown(path, uid, gid, target_uid, target_gid):
|
|
LOG.debug('%s %s:%s -> %s:%s', path, uid, gid, target_uid, target_gid)
|
|
|
|
|
|
def shift_path(path, uid_mappings, gid_mappings, nobody, uid_memo, gid_memo):
|
|
stat = os.lstat(path)
|
|
uid = stat.st_uid
|
|
gid = stat.st_gid
|
|
target_uid = find_target_id(uid, uid_mappings, nobody, uid_memo)
|
|
target_gid = find_target_id(gid, gid_mappings, nobody, gid_memo)
|
|
print_chown(path, uid, gid, target_uid, target_gid)
|
|
os.lchown(path, target_uid, target_gid)
|
|
|
|
|
|
def shift_dir(fsdir, uid_mappings, gid_mappings, nobody):
|
|
uid_memo = dict()
|
|
gid_memo = dict()
|
|
|
|
def shift_path_short(p):
|
|
shift_path(p, uid_mappings, gid_mappings, nobody,
|
|
uid_memo=uid_memo, gid_memo=gid_memo)
|
|
|
|
shift_path_short(fsdir)
|
|
for root, dirs, files in os.walk(fsdir):
|
|
for d in dirs:
|
|
path = os.path.join(root, d)
|
|
shift_path_short(path)
|
|
for f in files:
|
|
path = os.path.join(root, f)
|
|
shift_path_short(path)
|
|
|
|
|
|
def confirm_path(path, uid_ranges, gid_ranges, nobody):
|
|
stat = os.lstat(path)
|
|
uid = stat.st_uid
|
|
gid = stat.st_gid
|
|
|
|
uid_in_range = True if uid == nobody else False
|
|
gid_in_range = True if gid == nobody else False
|
|
|
|
if not uid_in_range or not gid_in_range:
|
|
for (start, end) in uid_ranges:
|
|
if start <= uid <= end:
|
|
uid_in_range = True
|
|
break
|
|
|
|
for (start, end) in gid_ranges:
|
|
if start <= gid <= end:
|
|
gid_in_range = True
|
|
break
|
|
|
|
return uid_in_range and gid_in_range
|
|
|
|
|
|
def get_ranges(maps):
|
|
return [(target, target + count - 1) for (start, target, count) in maps]
|
|
|
|
|
|
def confirm_dir(fsdir, uid_mappings, gid_mappings, nobody):
|
|
uid_ranges = get_ranges(uid_mappings)
|
|
gid_ranges = get_ranges(gid_mappings)
|
|
|
|
if not confirm_path(fsdir, uid_ranges, gid_ranges, nobody):
|
|
return False
|
|
for root, dirs, files in os.walk(fsdir):
|
|
for d in dirs:
|
|
path = os.path.join(root, d)
|
|
if not confirm_path(path, uid_ranges, gid_ranges, nobody):
|
|
return False
|
|
for f in files:
|
|
path = os.path.join(root, f)
|
|
if not confirm_path(path, uid_ranges, gid_ranges, nobody):
|
|
return False
|
|
return True
|
|
|
|
|
|
@nova.privsep.sys_admin_pctxt.entrypoint
|
|
def shift(path, uid_map, gid_map):
|
|
if confirm_dir(uid_map, gid_map, path, NOBODY_ID):
|
|
return
|
|
shift_dir(path, uid_map, gid_map, NOBODY_ID)
|