Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mount fdescfs on jail start when mount_fdescfs is set #727

Merged
merged 21 commits into from
May 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
94738ba
BaseConfig setter and getter for mount_fdescfs
gronke May 7, 2019
2da77e8
mount fdescfs during jail start
gronke May 7, 2019
cb0ff20
remove unused methods and events
gronke May 7, 2019
9d0f944
test mount_devfs and mount_fdescfs
gronke May 8, 2019
8cf772d
do not pass ip jail params when vnet is enabled
gronke May 8, 2019
aa412bf
log unmount actions on jail teardown
gronke May 9, 2019
5cc696b
read ctypes.errno when jail_set fails
gronke May 9, 2019
6d45915
sysctl NODE type can have int values
gronke May 9, 2019
6c31208
BaseConfig.is_known_property is a public method
gronke May 9, 2019
c6d405f
map disabled, inherit and new values to NODE/INT jail params
gronke May 9, 2019
48ba185
JailLaunchFailed error can hava a reason
gronke May 9, 2019
5b32418
BaseConfig._get_* methods mark a config property as known
gronke May 9, 2019
b306db9
enhance mount_devfs and mount_fdescfs usability
gronke May 9, 2019
4e2bb1d
Jail handles params with sysctl NODE type
gronke May 9, 2019
f781dcc
mount /dev and /dev/fd from within the jail
gronke May 9, 2019
6f33a64
Resource.require_relative path() is a public method
gronke May 9, 2019
14fff55
fail unmount of insecure jail mountpoints on Storage teardown
gronke May 9, 2019
af5704d
pin Python developer requirement bandit to version 1.5.1
gronke May 9, 2019
21d1fdd
pass ruleset mount property when mounting devfs from outside of the jail
gronke May 9, 2019
6ce4434
decode nmount error message type calling MountFailed exception
gronke May 9, 2019
4b4a96f
normalize mount_fdescfs and mount_devfs values
gronke May 8, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ install-deps-dev: install-deps
install-dev: install-deps-dev install-python-requirements-dev
$(PYTHON) -m pip install -e .
install-travis:
python3.6 -m pip install flake8-mutable flake8-docstrings flake8-builtins flake8-mypy bandit bandit-high-entropy-string
python3.6 -m pip install -IU flake8-mutable flake8-docstrings flake8-builtins flake8-mypy bandit==1.5.1 bandit-high-entropy-string
uninstall:
$(PYTHON) -m pip uninstall -y ioc
@if [ -f /usr/local/etc/rc.d/ioc ]; then \
Expand Down
41 changes: 37 additions & 4 deletions libioc/Config/Jail/BaseConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,37 @@ def _get_tags(self) -> typing.List[str]:

return self.__unique_list(tags)

@property
def __has_mounts_enabled(self) -> bool:
prefix = "allow_mount_"
return any((self[x] == 1) for x in self.keys() if x.startswith(prefix))

def _get_enforce_statfs(self) -> int:
key = "enforce_statfs"
if key in self.data.keys():
return int(self.data[key])

if self.__has_mounts_enabled:
self.logger.verbose(
"setting enforce_statfs=1 to support allowed mounts"
)
return 1

raise KeyError(f"{key} unconfigured")

def _get_allow_mount(self) -> int:
key = "allow_mount"
if key in self.data.keys():
return int(self.data[key])

if self.__has_mounts_enabled:
self.logger.verbose(
"inheriting allow_mount=1 from allowed mounts"
)
return 1

raise KeyError(f"{key} unconfigured")

def __unique_list(self, seq: typing.List[str]) -> typing.List[str]:
seen: typing.Set[str] = set()
seen_add = seen.add
Expand Down Expand Up @@ -621,7 +652,7 @@ def __getitem__(self, key: str) -> typing.Any:
def unknown_config_parameters(self) -> typing.Iterator[str]:
"""Yield unknown config parameters already stored in the config."""
for key in self.data.keys():
if self._is_known_property(key, explicit=True) is False:
if self.is_known_property(key, explicit=True) is False:
yield key

def __delitem__(self, key: str) -> None:
Expand All @@ -636,7 +667,7 @@ def __setitem__( # noqa: T400
explicit: bool=True
) -> None:
"""Set a configuration value."""
if self._is_known_property(key, explicit=explicit) is False:
if self.is_known_property(key, explicit=explicit) is False:
if "jail" in dir(self):
_jail = self.jail # noqa: T484
else:
Expand Down Expand Up @@ -865,14 +896,16 @@ def is_user_property(key: str) -> bool:
"""Return whether the given key belongs to a custom user property."""
return (key == "user") or (key.startswith("user.") is True)

def _is_known_property(self, key: str, explicit: bool) -> bool:
def is_known_property(self, key: str, explicit: bool) -> bool:
"""Return True when the key is a known config property."""
if self._is_known_jail_param(key):
return True
if key in libioc.Config.Jail.Globals.DEFAULTS.keys():
return True # key is default
if f"_set_{key}" in dict.__dir__(self):
return True # key is setter
if f"_get_{key}" in dict.__dir__(self):
return True # key is getter
if key in libioc.Config.Jail.Properties.properties:
return True # key is special property
if self._key_is_mac_config(key, explicit=explicit) is True:
Expand All @@ -898,7 +931,7 @@ def _require_known_config_property(
key: str,
explicit: bool=True
) -> None:
if self._is_known_property(key, explicit=explicit) is False:
if self.is_known_property(key, explicit=explicit) is False:
raise libioc.errors.UnknownConfigProperty(
key=key,
logger=self.logger
Expand Down
6 changes: 3 additions & 3 deletions libioc/Config/Jail/File/Fstab.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ def path(self) -> str:
return self.file
else:
path = f"{self.jail.dataset.mountpoint}/{self.file}"
self.jail._require_relative_path(path)
self.jail.require_relative_path(path)
return path

def add_line(
Expand Down Expand Up @@ -692,7 +692,7 @@ def add_line(
self.jail.root_path,
line["destination"].strip("/")
]))
self.jail._require_relative_path(_destination)
self.jail.require_relative_path(_destination)
line["destination"] = _destination

libioc.helpers.require_no_symlink(str(line["destination"]))
Expand All @@ -707,7 +707,7 @@ def add_line(

if (auto_mount_jail and self.jail.running) is True:
destination = line["destination"]
self.jail._require_relative_path(destination)
self.jail.require_relative_path(destination)
self.logger.verbose(
f"auto-mount {destination}"
)
Expand Down
2 changes: 1 addition & 1 deletion libioc/Config/Jail/File/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,5 @@ def __init__(
def path(self) -> str:
"""Absolute path to the file."""
path = f"{self.resource.root_dataset.mountpoint}/{self.file}"
self.resource._require_relative_path(path)
self.resource.require_relative_path(path)
return os.path.abspath(path)
45 changes: 45 additions & 0 deletions libioc/Config/Jail/JailConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,51 @@ def _get_host_hostname(self) -> str:
def _get_legacy(self) -> bool:
return self.legacy

def _get_mount_fdescfs(self) -> int:
return self.__get_mount(key="mount_fdescfs")

def _set_mount_fdescfs(self, value: typing.Union[str, int, bool]) -> None:
self.__set_mount(key="mount_fdescfs", value=value)

def _get_mount_devfs(self) -> int:
return self.__get_mount(key="mount_devfs")

def _set_mount_devfs(self, value: typing.Union[str, int, bool]) -> None:
self.__set_mount(key="mount_devfs", value=value)

def __get_mount(self, key: str) -> int:
return 1 * ((int(self.data[key]) == 1) is True)

def __set_mount(
self,
key: str,
value: typing.Union[str, int, bool]
) -> None:

error_reason: typing.Optional[str] = None
if isinstance(value, bool) is True:
enabled = (value is True)
else:
try:
str_value = str(int(value))
except ValueError:
error_reason = "invalid input type (expected int, str or bool)"

if (str_value != "0") and (str_value != "1"):
error_reason = f"Boolean input expected, but got {str_value}"

enabled = (str_value == "1")

if error_reason is not None:
raise libioc.errors.InvalidJailConfigValue(
jail=self.jail,
property_name="mount_fdescfs",
reason=error_reason,
logger=self.logger
)

self.data[key] = ("1" if enabled else "0")

def __getitem__(self, key: str) -> typing.Any:
"""Get the value of a configuration argument or its default."""
try:
Expand Down
119 changes: 71 additions & 48 deletions libioc/Jail.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import libzfs
import freebsd_sysctl
import jail as libjail
import freebsd_sysctl.types

import libioc.Types
import libioc.errors
Expand All @@ -55,6 +56,10 @@
import libioc.ResourceSelector
import libioc.Config.Jail.File.Fstab

import ctypes.util
import errno
_dll = ctypes.CDLL(str(ctypes.util.find_library("c")), use_errno=True)


class JailResource(
libioc.LaunchableResource.LaunchableResource,
Expand Down Expand Up @@ -518,17 +523,23 @@ def _stop_failed_jail(

jailAttachEvent.add_rollback_step(_stop_failed_jail)
jiov = libjail.Jiov(self._launch_params)
jid = libjail.dll.jail_set(jiov.pointer, len(jiov), 1)
jid = _dll.jail_set(jiov.pointer, len(jiov), 1)

if jid > 0:
self.__jid = jid
yield jailAttachEvent.end()
else:
error_code = ctypes.get_errno()
if error_code > 0:
error_name = errno.errorcode[error_code]
error_text = f"{error_code} [{error_name}]"
else:
error_text = jiov.errmsg.value.decode("UTF-8")
error = libioc.errors.JailLaunchFailed(
jail=self,
reason=error_text,
logger=self.logger
)
error_text = jiov.errmsg.value.decode("UTF-8")
yield jailAttachEvent.fail(error_text)
raise error

Expand All @@ -538,6 +549,9 @@ def _stop_failed_jail(
# Mount Devfs
yield from self.__mount_devfs(jailStartEvent.scope)

# Mount Fdescfs
yield from self.__mount_fdescfs(jailStartEvent.scope)

# Setup Network
yield from self.__start_network(jailStartEvent.scope)

Expand Down Expand Up @@ -601,62 +615,70 @@ def __mount_devfs(
self,
event_scope: typing.Optional['libioc.events.Scope']=None
) -> typing.Generator['libioc.events.MountDevFS', None, None]:
yield from self.__mount_in_jail(
filesystem="devfs",
mountpoint="/dev",
event=libioc.events.MountDevFS,
event_scope=event_scope,
ruleset=self.config["devfs_ruleset"]
)

def __mount_fdescfs(
self,
igalic marked this conversation as resolved.
Show resolved Hide resolved
event_scope: typing.Optional['libioc.events.Scope']=None
) -> typing.Generator['libioc.events.MountFdescfs', None, None]:
yield from self.__mount_in_jail(
filesystem="fdescfs",
mountpoint="/dev/fd",
event=libioc.events.MountFdescfs,
event_scope=event_scope
)

event = libioc.events.MountDevFS(
def __mount_in_jail(
self,
filesystem: str,
mountpoint: str,
event: 'libioc.events.JailEvent',
event_scope: typing.Optional['libioc.events.Scope']=None,
**iov_data: str
) -> typing.Generator['libioc.events.MountFdescfs', None, None]:

_event = event(
jail=self,
scope=event_scope
)
yield event.begin()
yield _event.begin()

if self.config["mount_devfs"] is False:
yield event.skip("disabled")
if int(self.config[f"mount_{filesystem}"]) == 0:
yield _event.skip("disabled")
return

devpath = f"{self.root_path}/dev"

try:
if os.path.islink(devpath) is True:
_mountpoint = str(f"{self.root_path}{mountpoint}")
self.require_relative_path(_mountpoint)
if os.path.islink(_mountpoint) or os.path.isfile(_mountpoint):
raise libioc.errors.InsecureJailPath(
path=devpath,
path=_mountpoint,
logger=self.logger
)
libioc.helpers.mount(
destination=devpath,
fstype="devfs"
)
except Exception as e:
yield event.fail(e)
yield _event.fail(str(e))
raise e
if os.path.isdir(_mountpoint) is False:
os.makedirs(_mountpoint, mode=0o555)

yield event.end()

def __unmount_devfs(
self,
event_scope: typing.Optional['libioc.events.Scope']=None
) -> typing.Generator['libioc.events.UnmountDevFS', None, None]:

event = libioc.events.UnmountDevFS(
jail=self,
scope=event_scope
)
yield event.begin()

devpath = f"{self.root_path}/dev"

if os.path.ismount(devpath) is False:
yield event.skip()
return
try:
libioc.helpers.umount(
mountpoint=devpath,
libioc.helpers.mount(
destination=_mountpoint,
fstype=filesystem,
logger=self.logger,
frce=True
ruleset=4
)
except Exception as e:
yield event.fail(e)
yield _event.fail(str(e))
raise e

yield event.end()
yield _event.end()

@property
def _zfs_share_storage(
Expand Down Expand Up @@ -1544,21 +1566,13 @@ def devfs_ruleset(self) -> libioc.DevfsRules.DevfsRuleset:
ruleset_line_position = self.host.devfs.index(devfs_ruleset)
return self.host.devfs[ruleset_line_position].number

@staticmethod
def __get_launch_command(jail_args: typing.List[str]) -> typing.List[str]:
return ["/usr/sbin/jail", "-c"] + jail_args

@property
def _launch_params(self) -> libjail.Jiov:
config = self.config
vnet = (config["vnet"] is True)
value: libjail.RawIovecValue
jail_params: typing.Dict[str, libioc.JailParams.JailParam] = {}
for sysctl_name, sysctl in libioc.JailParams.JailParams().items():
if sysctl.ctl_type == freebsd_sysctl.types.NODE:
# skip NODE
continue

if sysctl_name == "security.jail.param.devfs_ruleset":
value = int(self.devfs_ruleset)
elif sysctl_name == "security.jail.param.path":
Expand All @@ -1585,17 +1599,26 @@ def _launch_params(self) -> libjail.Jiov:
value = []
for _, addresses in self.config["ip6_addr"].items():
value += [x.ip for x in addresses]
elif vnet and (sysctl_name.startswith("security.jail.param.ip")):
continue
else:
config_property_name = sysctl.iocage_name
if self.config._is_known_property(
if self.config.is_known_property(
config_property_name,
explicit=False
) is True:
value = config[config_property_name]
if sysctl.ctl_type in (
freebsd_sysctl.types.NODE,
freebsd_sysctl.types.INT,
):
sysctl_state_names = ["disable", "inherit", "new"]
if value in sysctl_state_names:
value = sysctl_state_names.index(value)
else:
continue

jail_params[sysctl.jail_arg_name] = value
jail_params[sysctl.jail_arg_name.rstrip(".")] = value

jail_params["persist"] = None
return jail_params
Expand Down
Loading