Hacked By AnonymousFox
Current Path : /usr/share/cagefs/ |
|
Current File : //usr/share/cagefs/cagefs_without_lve_lib.py |
#!/opt/cloudlinux/venv/bin/python3 -bb
# -*- coding: utf-8 -*-
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2022 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
# pylint: disable=no-absolute-import
import os
import time
import subprocess
import signal
from typing import Optional, List, Dict
import traceback
import fcntl
import secureio
from pathlib import Path
import glob
from clcommon.lib import cledition
from clcommon.utils import run_command, ExternalProgramFailed, get_file_lines, write_file_lines
from cldetectlib import is_da
from secureio import write_file_via_tempfile, logging
_CAGEFS_MOUNT_BIN_FILE = '/usr/sbin/cagefs-mount'
_LSNS_BIN_FILE = '/usr/bin/lsns'
_MOUNT_BIN_FILE = '/bin/mount'
_UMOUNT_BIN_FILE = '/bin/umount'
_NSENTER_BIN = '/bin/nsenter'
_GREP_BIN = '/bin/grep'
_LOCK_FILE_NAME_PATTERN = '/var/cagefs/%s/%s.lock'
_CAGEFS_SKELETON_DIR = '/usr/share/cagefs-skeleton'
# /var/cagefs.uid/$PREFIX/$UID/ns.mnt
_NS_MNT_FILE_NAME_PATTERN = '/var/cagefs.uid/%s/%d/ns.mnt'
# /var/cagefs.uid/$PREFIX/$UID/ns.id
_NS_ID_FILE_NAME_PATTERN = '/var/cagefs.uid/%s/%d/ns.id'
class LockFailedException(Exception):
pass
class NsNotFoundException(Exception):
pass
class CagefsMountNotStartedException(Exception):
def __init__(self, msg=''):
self.msg = f'{msg}'
if cledition.is_container():
self.msg += '\nThe Virtuozzo host mounting limit may have been reached.\n' \
'Check for the presence of the kernel error "reached the limit on mounts" on VZ host.\n' \
'More info at https://docs.cloudlinux.com/cloudlinux_installation/#known-restrictions-and-issues'
def __str__(self):
return self.msg
def _find_command_in_ns(ns_id: str, cmd_to_search: str) -> bool:
"""
Find proposed command in proposed NS id
:param ns_id: NS id to search command
:param cmd_to_search: Command to search
:return: True - command found in NS, False - not found
"""
# Example:
# # lsns --type mnt --list --output pid,command <ns_id>
# PID COMMAND
# 1404 /bin/sh /usr/bin/mysqld_safe --basedir=/usr
# 1577 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql ....
try:
cmd = [_LSNS_BIN_FILE, '--type', 'mnt', '--list', '--output', 'command', ns_id]
stdout = run_command(cmd)
for line in stdout.split('\n'):
line = line.strip()
if line == '' or 'PID' in line:
# Skip header line and empty line
continue
# line example: '/bin/sh /usr/bin/mysqld_safe --basedir=/usr'
if line == cmd_to_search:
return True
except (ExternalProgramFailed, ):
pass
return False
def _find_save_ns_id_for_user(username: str, filename_to_write: str):
"""
Find user's NS id and write it to file
:param username: User name
:param filename_to_write: File name to write
"""
cmd_to_search = f'{_CAGEFS_MOUNT_BIN_FILE} {username}'
# 1. Get all user's NS id
# lsns --type mnt --list --output ns,command
cmd = [_LSNS_BIN_FILE, '--type', 'mnt', '--list', '--output', 'ns,command']
stdout = run_command(cmd)
ns_id_list = []
for line in stdout.split('\n'):
line = line.strip()
if line == '' or 'NS' in line:
# Skip header line and empty line
continue
line_parts = line.split(' ', 1)
# line_parts example:
# ['4026532195' '/usr/sbin/pdns_server --socket-dir=/run/pdns --guardian=no --daemon=no ...']
if line_parts[1].strip() == cmd_to_search:
# Command found, write NS id
write_file_via_tempfile(line_parts[0], filename_to_write, 0o600)
return
ns_id_list.append(line)
# Command not found in lsns output, search it in each NS
for ns_id in ns_id_list:
# try to find user's NS by process '/usr/sbin/cagefs-mount <username>'
if _find_command_in_ns(ns_id, cmd_to_search):
# Command found, write NS id
write_file_via_tempfile(ns_id, filename_to_write, 0x600)
return
raise NsNotFoundException(f"NS not found for user {username} when save NS id to file")
def _get_all_ns() -> Dict[str, str]:
"""
Get all NS with processes in system
:return: Dict: {'some_ns_id', 'some_pid_from_ns'}
"""
ns_dict = {}
try:
cmd = [_LSNS_BIN_FILE, '--type', 'mnt', '--list', '--output', 'ns,pid']
stdout = run_command(cmd)
for line in stdout.split('\n'):
line = line.strip()
if line == '' or 'PID' in line:
# Skip header line and empty line
continue
# line example: '4026532195 1582'
line_parts = line.split()
ns_dict[line_parts[0].strip()] = line_parts[1].strip()
except (ExternalProgramFailed, ):
pass
return ns_dict
def _get_pid_list_by_ns_id(ns_id: str, user_homedir: str) -> List[int]:
"""
Retrieves PID list for user in proposed NS id
:param ns_id: NS id to retrieve PID list
:param user_homedir: User homedir
:return: list PIDs in NS, [] - NS not found/has no processes
"""
# Get all NS id in system with some PID in NS as dict {ns_id: pid}
all_ns_id_dict = _get_all_ns()
if ns_id not in all_ns_id_dict:
return []
ns_pid = all_ns_id_dict[ns_id]
try:
# Check that NS with ns_id owned by user with proposed homedir
# /bin/nsenter -m -t PID /bin/grep /usr/share/cagefs-skeleton/$USERHOME /proc/mounts
user_home_is_skeleton = os.path.join(_CAGEFS_SKELETON_DIR, user_homedir)
proc = subprocess.run([_NSENTER_BIN, '-m', '-t', ns_pid, _GREP_BIN, user_home_is_skeleton, '/proc/mounts'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
if proc.returncode != 0:
# user home mount not found in NS
return []
pid_list = []
# NS valid, get PID list from it
cmd = [_LSNS_BIN_FILE, '--type', 'mnt', '--list', '--output', 'pid', ns_id]
stdout = run_command(cmd)
for line in stdout.split('\n'):
line = line.strip()
if line == '' or 'PID' in line:
# Skip header line and empty line
continue
pid_list.append(int(line))
return pid_list
except (ExternalProgramFailed, OSError, IOError, ):
pass
return []
def _get_pid_list_for_user(user_uid: int, cagefs_user_prefix: str, user_homedir: str) -> Optional[List[int]]:
"""
Retrieve pid list from user's NS
:param user_uid: User's uid
:param cagefs_user_prefix: User's cagefs prefix
:param user_homedir: User's homedir
:return: List of user's PIDs or None if user has no NS
"""
try:
# Read NS id from file
lines = get_file_lines(_NS_ID_FILE_NAME_PATTERN % (cagefs_user_prefix, user_uid))
ns_id = lines[0].strip()
return _get_pid_list_by_ns_id(ns_id, user_homedir)
except (OSError, IOError, IndexError, ):
# No NS for this user
pass
return None
def _kill_processes_in_list(pid_list: List[int]):
"""
Kill processes from list
:param pid_list: PID list to kill
"""
for pid in pid_list:
try:
os.kill(pid, signal.SIGKILL)
except (OSError, ):
pass
def _acquire_lock(filename: str) -> int:
"""
Creates a lock file and acquire lock on it
:return: File descriptor of created file
"""
try:
os.makedirs(os.path.dirname(filename), mode=0o700, exist_ok=True)
lock_fd = os.open(filename, os.O_CREAT, 0o600)
fcntl.flock(lock_fd, fcntl.LOCK_EX)
return lock_fd
except IOError:
raise LockFailedException("IO error happened while getting lock")
def _release_lock(lock_fd: int) -> None:
"""
Release lock and close lock file
:param lock_fd: Lock file descriptor
"""
fcntl.flock(lock_fd, fcntl.LOCK_UN)
os.close(lock_fd)
def _wait_for_pid_file(pid_filename: str) -> bool:
"""
Waits for cagefs-mount pid file appears up to 10 seconds
:param pid_filename: PID filename
:return: True - appears; False - Not
"""
for i in range(10000):
try:
os.stat(pid_filename)
return True
except (OSError, IOError):
# Error, PID file absent
time.sleep(0.001)
# Error, cagefs-mount was not started
return False
def _create_namespace_user(username: str) -> bool:
"""
Create namespace for single user
:param username: User name to create namespace
"""
from cagefsctl import get_user_prefix
error = False
background_processes = []
try:
cagefs_user_prefix = get_user_prefix(username)
user_uid = secureio.clpwd.get_uid(username)
# To create namespace for USER, use:
# if /var/cagefs.uid/$PREFIX/$UID/ns.mnt bind mount exists already - do nothing
# mkdir -p /var/cagefs.uid/$PREFIX/$UID
# create lockfile /var/cagefs/$PREFIX/$USER.lock, acquire lock
# touch /var/cagefs.uid/$PREFIX/$UID/ns.mnt
# /usr/sbin/cagefs-mount $USER
# mount --bind /proc/$PID/ns/mnt /var/cagefs.uid/$PREFIX/$UID/ns.mnt
# release lockfile /var/cagefs/$PREFIX/$USER.lock
# _NS_MNT_FILE_NAME_PATTERN = '/var/cagefs.uid/$PREFIX/$UID/ns.mnt'
# kill $PID (where $PID = pid of cagefs-mount process)
ns_mnt_filename = _NS_MNT_FILE_NAME_PATTERN % (cagefs_user_prefix, user_uid)
if os.path.exists(ns_mnt_filename):
return False
os.makedirs(os.path.dirname(ns_mnt_filename), mode=0o700, exist_ok=True)
# touch /var/cagefs.uid/$PREFIX/$UID/ns.mnt
Path(ns_mnt_filename).touch()
cagefs_pid_file = f'/var/cagefs.uid/{cagefs_user_prefix}/{user_uid}/cagefs-mount.pid'
try:
os.unlink(cagefs_pid_file)
except (OSError, IOError,):
pass
cmd = [_CAGEFS_MOUNT_BIN_FILE, username]
proc = subprocess.Popen(cmd, shell=False)
cagefs_mount_pid = proc.pid
background_processes.append(cagefs_mount_pid)
# We should wait while binary creates cagefs mounts
if not _wait_for_pid_file(cagefs_pid_file):
raise CagefsMountNotStartedException(f'{_CAGEFS_MOUNT_BIN_FILE} not started for user {username}')
# /bin/mount --bind /proc/$PID/ns/mnt /var/cagefs.uid/$PREFIX/$UID/ns.mnt
res = subprocess.run(
[_MOUNT_BIN_FILE, '--bind', f'/proc/{cagefs_mount_pid}/ns/mnt', ns_mnt_filename],
shell=False
)
if res.returncode != 0:
raise CagefsMountNotStartedException(f'Can\'t mount {ns_mnt_filename} for user {username}')
# Find user's NS id and save it to file /var/cagefs.uid/$PREFIX/$UID/ns.id
ns_id_filename = _NS_ID_FILE_NAME_PATTERN % (cagefs_user_prefix, user_uid)
_find_save_ns_id_for_user(username, ns_id_filename)
except KeyboardInterrupt:
raise
except CagefsMountNotStartedException as e:
error = True
logging(str(e))
except:
error = True
msg = traceback.format_exc()
logging(f"General error while attempting to create a namespace for user {username}. Error is: {msg}")
finally:
# Kill cagefs-mount process
_kill_processes_in_list(background_processes)
return error
def _delete_namespace_user(username: str) -> bool:
"""
Delete namespace for single user
:param username: User name to delete namespace
"""
from cagefsctl import get_user_prefix
lock_obj = None
error = False
try:
cagefs_user_prefix = get_user_prefix(username)
user_uid = secureio.clpwd.get_uid(username)
user_homedir = secureio.clpwd.get_homedir(username)
ns_mnt_filename = _NS_MNT_FILE_NAME_PATTERN % (cagefs_user_prefix, user_uid)
if not os.path.exists(ns_mnt_filename):
# User has no NS
return False
lock_obj = _acquire_lock(_LOCK_FILE_NAME_PATTERN % (cagefs_user_prefix, username))
if not os.path.exists(ns_mnt_filename):
# Check if User has no NS again after acquiring lock
return False
user_pids = _get_pid_list_for_user(user_uid, cagefs_user_prefix, user_homedir)
if user_pids:
_kill_processes_in_list(user_pids)
# umount /var/cagefs.uid/$PREFIX/$UID/ns.mnt
cmd = [_UMOUNT_BIN_FILE, ns_mnt_filename]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=False)
# rf -f /var/cagefs.uid/$PREFIX/$UID/ns.mnt
os.unlink(ns_mnt_filename)
except (LockFailedException, ) as e:
print(f"Can't acqure lock for user {username}. Error is: {str(e)}")
error = True
except:
msg = traceback.format_exc()
print(f"Error during delete namespace for user {username}. Error is: {msg}")
error = True
if lock_obj:
_release_lock(lock_obj)
return error
def create_namespace_user_list(username_list: List[str], verbose = False) -> bool:
"""
Create namespace for users from list
:param username_list: username list for prosess
"""
error = False
for username in username_list:
if verbose:
print("Creating NS for user:", username)
if _create_namespace_user(username):
error = True
return error
def delete_namespace_user_list(username_list: List[str], verbose = False) -> bool:
"""
Delete namespace for users from list
:param username_list: username list for prosess
:param verbose: If True, print messages to stdout
"""
error = False
for username in username_list:
if verbose:
print("Deleting NS for user:", username)
if _delete_namespace_user(username):
error = True
return error
def _get_httpd_php_fpm_service_override_files() -> Dict[str, str]:
"""
Get list of all php-fpm services override files on server
:return Dict. Example:
{'ea-php74-php-fpm.service': '/etc/systemd/system/ea-php74-php-fpm.service.d/override.conf',
'ea-php56-php-fpm.service': '/etc/systemd/system/ea-php56-php-fpm.service.d/override.conf'
}
"""
systemd_dir = '/usr/lib/systemd/system'
# Scan available ea-php fpm services
mask_to_search = os.path.join(systemd_dir, 'ea-php*-fpm.service')
service_names = [os.path.basename(x) for x in glob.glob(mask_to_search)]
# Scan available alt-php fpm services
mask_to_search = os.path.join(systemd_dir, 'alt-php*-fpm.service')
service_names.extend([os.path.basename(x) for x in glob.glob(mask_to_search)])
# Add additional services - native php-fpm and httpd
# DA doesn't register service in /usr/lib/systemd/service, so we need to check /etc/systemd/system
add_services_names = ['php-fpm.service', 'httpd.service']
for service_name in add_services_names:
if os.path.exists(os.path.join(systemd_dir, service_name)) or (
is_da() and os.path.exists(os.path.join('/etc/systemd/system', service_name))
):
service_names.append(service_name)
# Create override configs list
override_file_dict = {service_name: '/etc/systemd/system/%s.d/zzz-cagefs.conf' % service_name
for service_name in service_names}
return override_file_dict
def fix_httpd_php_fpm_services():
"""
Reconfigure httpd and ea-php-fpm services to work in without LVE
Write to each systemd service file directives:
PrivateDevices=false
PrivateMounts=false
PrivateTmp=false
"""
try:
override_files_dict = _get_httpd_php_fpm_service_override_files()
lines_to_write = ['[Service]\n', 'PrivateDevices=false\n', 'PrivateMounts=false\n', 'PrivateTmp=false\n',
'ProtectSystem=false\n', 'ReadOnlyDirectories=\n', 'ReadWriteDirectories=\n',
'InaccessibleDirectories=\n', 'ProtectHome=false\n'
]
for override_file in override_files_dict.values():
# Create /etc/systemd/system/%s.d directory if need
os.makedirs(os.path.dirname(override_file), mode=0o700, exist_ok=True)
write_file_lines(override_file, lines_to_write, 'w')
os.system('/bin/systemctl daemon-reload 2> /dev/null')
# Restart all need services
for service_name in override_files_dict.keys():
os.system(f'/sbin/service {service_name} restart 2> /dev/null')
except (OSError, IOError,):
pass
def restore_httpd_php_fpm_services():
try:
override_files_dict = _get_httpd_php_fpm_service_override_files()
# override_files_dict example:
# {'ea-php74-php-fpm.service': '/etc/systemd/system/ea-php74-php-fpm.service.d/zzz-cagefs.conf',
# 'ea-php56-php-fpm.service': '/etc/systemd/system/ea-php56-php-fpm.service.d/zzz-cagefs.conf'}
for override_file in override_files_dict.values():
# Remove override file
try:
os.unlink(override_file)
except (OSError, IOError, ):
pass
# Remove override dir if it empty
try:
os.rmdir(os.path.dirname(override_file))
except (OSError, IOError, ):
pass
os.system('/bin/systemctl daemon-reload 2> /dev/null')
# Restart all need services
for service_name in override_files_dict.keys():
os.system(f'/sbin/service {service_name} restart 2> /dev/null')
except (OSError, IOError,):
pass
Hacked By AnonymousFox1.0, Coded By AnonymousFox