Saltstack FreeBSD Jails — Part 2
After experimenting with Salt’s jails-aware functions for about 2 weeks, I opted for BastilleBSD for jails management and configuration.
I initially wanted to go for:
- Bastille for manual jail lifecycle management (create, update and upgrade mainly)
- Salt for jail configuration
But…
Salt service.running is actually NOT jails-aware!
This came as a big disappointment and it took me a while to come to the
realization. I see service.running as one of the main functions, with
file.managed and pkg.installed, for installing and configuring a service
with Salt.
The root cause is that, unlike salt/states/pkg.py, salt/states/service.py
does not pass **kwargs to os-specific implementations like
salt/modules/freebsdservice.py. And thus functions in
salt/modules/freebsdservice.py do not receive the jail keyword argument 😡
Draft for a fix
--- a/salt/states/service.py	2021-03-24 22:54:42.000000000 +0100
+++ b/salt/states/service.py	2023-03-22 23:54:52.130686730 +0100
@@ -134,7 +134,7 @@
     # is service available?
     try:
-        if not _available(name, ret):
+        if not _available(name, ret, **kwargs):
             return ret
     except CommandExecutionError as exc:
         ret["result"] = False
@@ -245,7 +245,7 @@
     # is service available?
     try:
-        if not _available(name, ret):
+        if not _available(name, ret, **kwargs):
             ret["result"] = True
             return ret
     except CommandExecutionError as exc:
@@ -344,15 +344,15 @@
     return ret
-def _available(name, ret):
+def _available(name, ret, **kwargs):
     """
     Check if the service is available
     """
     avail = False
     if "service.available" in __salt__:
-        avail = __salt__["service.available"](name)
+        avail = __salt__["service.available"](name, **kwargs)
     elif "service.get_all" in __salt__:
-        avail = name in __salt__["service.get_all"]()
+        avail = name in __salt__["service.get_all"](**kwargs)
     if not avail:
         ret["result"] = False
         ret["comment"] = "The named service {} is not available".format(name)
@@ -440,7 +440,7 @@
     # Check if the service is available
     try:
-        if not _available(name, ret):
+        if not _available(name, ret, **kwargs):
             if __opts__.get("test"):
                 ret["result"] = None
                 ret["comment"] = (
@@ -453,6 +453,7 @@
         ret["comment"] = exc.strerror
         return ret
+    # XXX uh what has systemd to do with generic service handling?!
     status_kwargs, warnings = _get_systemd_only(__salt__["service.status"], kwargs)
     if warnings:
         _add_warnings(ret, warnings)
@@ -635,7 +636,7 @@
     # Check if the service is available
     try:
-        if not _available(name, ret):
+        if not _available(name, ret, **kwargs):
             if __opts__.get("test"):
                 ret["result"] = None
                 ret["comment"] = (
--- a/salt/modules/freebsdservice.py	2021-03-24 22:54:42.000000000 +0100
+++ b/salt/modules/freebsdservice.py	2023-03-22 23:55:02.160776847 +0100
@@ -348,7 +348,7 @@
     return not enabled(name, **kwargs)
-def available(name, jail=None):
+def available(name, jail=None, **kwargs):
     """
     Check that the given service is available.
@@ -362,6 +362,9 @@
         salt '*' service.available sshd
     """
+    log.error(f"__kwargs={kwargs}")
+    jail = jail if jail is not None else kwargs.get("jail", "")
+    log.error(f"__jail={jail}")
     return name in get_all(jail)
@@ -384,7 +387,7 @@
     return name not in get_all(jail)
-def get_all(jail=None):
+def get_all(jail=None, **kwargs):
     """
     Return a list of all available services
@@ -398,8 +401,10 @@
         salt '*' service.get_all
     """
+    jail = jail if jail is not None else kwargs.get("jail", "")
     ret = []
     service = _cmd(jail)
+    log.warning(f"__service={service}")
     for srv in __salt__["cmd.run"]("{0} -l".format(service)).splitlines():
         if not srv.isupper():
             ret.append(srv)
@@ -478,7 +483,7 @@
     return not __salt__["cmd.retcode"](cmd, python_shell=False)
-def status(name, sig=None, jail=None):
+def status(name, sig=None, jail=None, **kwargs):
     """
     Return the status for a service.
     If the name contains globbing, a dict mapping service name to True/False
@@ -503,6 +508,8 @@
         salt '*' service.status <service name> [service signature]
     """
+    jail = jail if jail is not None else kwargs.get("jail", "")
+
     if sig:
         return bool(__salt__["status.pid"](sig))
Note I went as far as rewriting the definition of a couple services to make them jail-aware, that is making them applicable to host machines or jails.
Salt PR, a hazardous path?
The Salt project on Github currently displays 2.5k open issues, 600+ open PRs. This feels like a lot.
The Salt community on slack was reactive and welcoming, explaining that a PR will surely be accepted as long as I provide tests. I also learned there is a community call where I can directly talk to the core team ❤️.
That’s encouraging but there was another aspect that unsettled me a bit: the pertinence of my potential PR. Am I the only one interested in that feature? How do others use Salt for jails? How come I am seemingly the first user to spot the issue? It seems there are already very few FreeBSD sysadmins, much less managing Jails…
Why not rather use a trendy tool?
Nice first experience with Bastille
Actually creating Bastille
templates for
service packaging is pretty straight forward: Bastillefile feels like
Dockerfile. There are some less documented tricks, like the CONFIG keyword,
which calls bastille config under the hood.
Most service definitions will be covered with PKG, SYSRC,
SERVICE. MOUNT and OVERLAY will help with actual jail definitions. So I
have a template for each: jail definition and service definition. The former
INCLUDEing the latter.
I store and deploy templates with config files in Salt. Secrets are injected into config files with Salt pillars.
Pros:
- Simple install, simple maintenance (less jinja wrangling, easy to iterate).
- Flexibility. Fast, feature-rich, user-friendly tool.
- bastille templateis idempotent in my experience.
 
Cons:
- No periodic state checking (ala Salt/Puppet/Chef). More ansible-like.
- Tooling fragmentation. Specific tool for Jail management and
configuration. The divergence is slightly worsened by the fact that Bastille
uses a distinct jail.confper jail1.
- Services can only be deployed into jails.
Conclusion
Overall it feels like Bastille removes some complexity and seems to get some traction[Cit. needed]. So I’ll go and embrace the Bastille way. I’ll continue migrating the few remaining jails and package services still on the host.
- 
For ex. jail -c|-rdoes not work. At least for now: FreeBSD just introduced new default configuration files/etc/jail.conf.d/*.confand/etc/jail.*.conf. Bastille might take advantage of that in the future. ↩︎