CFEngine 3.17.0a1-termux - better Android Termux Support

Posted by Craig Comstock
August 26, 2020

As a follow up to my previous “personal policy” blog I have exciting news:

An improved CFEngine is available for Termux!

This provides a way to play with policy and implement policy on your non-rooted Android phone! Version 3.17.0a1-termux is an alpha release so understand it’s not heavily tested. That said, CFEngine for Termux is looking pretty awesome and useful. Highlights of features:

  • allow self-bootstrap to loopback since Android devices often change their IP address and bootstrapping locally seems to make some sense for a developer device and ability to play around, this is just as helpful on the desktop for that matter.
  • packages promises work with local masterfiles or with patched policy server masterfiles (pkg uses apt_get which CFE supports)
  • since Termux supports “real” versions of commands and doesn’t rely exclusively on busybox, CFEngine considers a Termux environment as a fairly full featured linux box in terms of commands and features
  • runs as un-privileged account, CFEngine for Termux does NOT require root
  • files promises work inside the /data/data/com.termux/files scope, not outside (unless possibly you have a rooted device, which is completely untested)
  • masterfiles policy framework works well, paths for common commands are modified to adjust to termux’s prefix $PREFIX being /data/data/com.termux/files/usr. Some common paths are setup for creating policy that works on Termux and other unices (etc_path, tmp_path, bin_path, var_path).

Not supported (yet):

  • Termux Services
  • user promises, on Android this doesn’t make much sense unless rooted and even then it is arguable
  • full CI/test suite passing
  • no allowances/testing of sleep states and termux wake lock affects on CFEngine
  • report collection for enterprise hub

Installation

The CFEngine package is available for Termux installations with Android 7.0 and higher (see this ticket about older Android support: https://github.com/termux/termux-packages/issues/4467).

pkg install cfengine
cf-agent --bootstrap 127.0.0.1 --inform | tee bootstrap.log
cf-agent -KIf update.cf
cf-agent -KI

At that point you should have the masterfiles policy framework in place at $PREFIX/var/lib/cfengine/inputs and changes made to $PREFIX/var/lib/cfengine/masterfiles can be updated with the following sequence which I made into an alias for ease-of-use while testing/developing:

alias cf='cf-agent -KIf update.cf && cf-agent -KI'

And since the masterfiles directory has a rather long path I created a symlink in my home dir:

cd ~
ln -s $PREFIX/var/lib/cfengine/masterfiles .

Now we can play around with some features. Let’s use autorun services by adding a def.json file at $PREFIX/var/lib/cfengine/masterfiles

{
  "classes":
  {
    "services_autorun": [ "any::" ],
    "cfengine_internal_purge_policies": [ "any::" ]
  }
}

So create a small service to show off some features, create at $PREFIX/var/lib/cfengine/masterfiles/services/autorun/termux_demo.cf

bundle agent termux_demo
{
  meta:
    "tags" slist => { "autorun" };

  classes:
    "in_termux" expression => "termux";
    "in_linux" expression => "linux";

  commands:
    _stdlib_path_exists_env::
      "$(paths.env)";

  files:
    "$(paths.etc_path)/ssh/sshd_config"
      changes => detect_all_change; # from masterfiles/lib/files.cf (stdlib)

  reports:
    "Hello from $(this.bundle)";
    "paths.bin_path is $(paths.bin_path)";
    termux::
      "Hello, only from termux platform agents";

  # users promises are not supported, this will generate an error
  # but not prevent policy from being evaluated in general
  users:
    "agent"
      policy => "present";
}

And run the update and agent run:

$ cf

error: Method 'personal' failed in some repairs
error: Users promise type is not supported on this OS
info: Executing 'no timeout' ... '/data/data/com.termux/files/usr/bin/env'
notice: Q: "...sr/bin/env": SHELL=/data/data/com.termux/files/usr/bin/bash
Q: "...sr/bin/env": PREFIX=/data/data/com.termux/files/usr
Q: "...sr/bin/env": PWD=/data/data/com.termux/files/home
Q: "...sr/bin/env": LOGNAME=u0_a138
Q: "...sr/bin/env": EXTERNAL_STORAGE=/sdcard
Q: "...sr/bin/env": LD_PRELOAD=/data/data/com.termux/files/usr/lib/libtermux-exec.so
Q: "...sr/bin/env": HOME=/data/data/com.termux/files/home
Q: "...sr/bin/env": TMPDIR=/data/data/com.termux/files/usr/tmp
Q: "...sr/bin/env": SSH_CONNECTION=127.0.0.1 42047 127.0.0.1 8022
Q: "...sr/bin/env": ANDROID_DATA=/data
Q: "...sr/bin/env": TERM=xterm-256color
Q: "...sr/bin/env": USER=u0_a138
Q: "...sr/bin/env": SHLVL=1
Q: "...sr/bin/env": ANDROID_ROOT=/system
Q: "...sr/bin/env": BOOTCLASSPATH=/system/framework/prcui-config.jar:/system/framework/core-oj.jar:/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/apache-xml.jar:/system/framework/org.apache.http.legacy.boot.jar:/system/framework/qcom.fmradio.jar:/system/framework/oem-services.jar:/system/framework/tcmiface.jar:/system/framework/telephony-ext.jar
Q: "...sr/bin/env": SSH_CLIENT=127.0.0.1 42047 8022
Q: "...sr/bin/env": PATH=/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/usr/bin/applets
Q: "...sr/bin/env": SSH_TTY=/dev/pts/1
Q: "...sr/bin/env": OLDPWD=/data/data/com.termux/files/usr/etc
Q: "...sr/bin/env": _=/data/data/com.termux/files/usr/bin/cf-agent
info: Last 20 quoted lines were generated by promiser '/data/data/com.termux/files/usr/bin/env'
info: Completed execution of '/data/data/com.termux/files/usr/bin/env'
R: Hello from termux_demo
R: paths.prefix is /data/data/com.termux/files/usr
R: Hello, only from termux platform agents
error: Method 'termux_demo' failed in some repairs

In order to support packages promise we must manually install python, because the package modules are written in python and we don’t typically have to bootstrap this installation as most distributions have python available.

pkg install python

MPF had to be adjusted as well so if you bootstrap to an existing server make sure you have the most recent masterfiles or `git cherry-pick` this commit into your policy. Create an auto run service to install some software at $PREFIX/var/lib/cfengine/masterfiles/services/autorun/software.cf

bundle agent software
{
  meta:
    "tags" slist => { "autorun" };

  packages:
    "pass" policy => "present";
}

And with an agent run I see that pass is installed!

     info: Successfully installed package 'pass'
$ pass --version
============================================
= pass: the standard unix password manager =
=                                          =
=                  v1.7.3                  =
=                                          =
=             Jason A. Donenfeld           =
=               Jason@zx2c4.com            =
=                                          =
=      http://www.passwordstore.org/       =
============================================

If you have any ideas on what you’d like to see with CFEngine on Termux, contribute your ideas and thoughts to the Termux Support Iteration 2 epic in our JIRA system.