click-apparmor-0.2/0000775000000000000000000000000012310375410011143 5ustar click-apparmor-0.2/aa-clickhook0000775000000000000000000001366412310067251013431 0ustar #! /usr/bin/python3 # ------------------------------------------------------------------ # # Copyright (C) 2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ # FIXME: apparmor package from apparmor-utils is not a namespace package import apparmor from apparmor import click import optparse import os import sys # Where easyprof generated profiles are stored apparmor_profiles = '/var/lib/apparmor/profiles' # Where apparmor caches its profiles apparmor_cache = '/var/cache/apparmor' # Where the apparmor click hook registers its click entries to be stored apparmor_clicks = '/var/lib/apparmor/clicks' def generate_profiles(clicks, include=None): '''Generate profiles from click manifests''' if include is not None: if not os.path.exists(include): raise click.AppArmorException("Could not find '%s'" % include) else: warn("--include specified, including '%s' in all profiles" % include) files = [] for missing in clicks: try: click_manifest = click.ClickManifest(os.path.join(apparmor_clicks, missing)) except click.AppArmorExceptionClickFrameworkNotFound: error("Could not find framework for '%s'. Skipping" % missing, do_exit=False) continue except Exception: error("Could not parse click manifest. Skipping '%s'" % missing, do_exit=False) continue try: easyprof_manifest = apparmor.click.transform(click_manifest) except click.AppArmorExceptionClickInvalidPolicyVersion: error("Invalid policy version for '%s'. Skipping" % missing, do_exit=False) continue except Exception: error("Could not transform '%s' to AppArmor easyprof. Skipping" % missing, do_exit=False) continue try: # Generate the policy, but don't verify it. It will error on load # (and apps will correctly still not load). This saves a bit of # time, which is important when processing lots of files. files.extend(click.to_profiles(easyprof_manifest, apparmor_profiles, include, no_verify=True)) except Exception: error("Could not generate AppArmor profile for '%s'. Skipping" % missing, do_exit=False) continue return files def error(out, exit_code=1, do_exit=True): '''Print error message and exit''' try: sys.stderr.write("ERROR: %s\n" % (out)) except IOError: pass if do_exit: sys.exit(exit_code) def warn(out): '''Print warning message''' try: sys.stderr.write("WARN: %s\n" % (out)) except IOError: pass def main(): parser = optparse.OptionParser() parser.add_option("-f", "--force", "--force-regenerate", dest='force', help='force regeneration of all click profiles', action='store_true', default=False) parser.add_option("-d", "--debug", dest='debug', help='emit debugging information', action='store_true', default=False) parser.add_option("--include", dest='include', help='add \'#include "PATH"\' to generated profiles', metavar="PATH", default=None) (opt, args) = parser.parse_args() if not len(args) == 0: sys.exit(1) if not os.path.exists(apparmor_profiles): # FIXME log this os.makedirs(apparmor_profiles) if not os.path.exists(apparmor_cache): # FIXME log this os.makedirs(apparmor_cache) if opt.force: missing_profiles = os.listdir(apparmor_clicks) else: missing_profiles = click.get_missing_profiles(apparmor_clicks, apparmor_profiles) missing_clicks = click.get_missing_clickhooks(apparmor_clicks, apparmor_profiles) load_profiles = generate_profiles(missing_profiles, opt.include) # Don't try to load/unload profiles if apparmor isn't available, but be # sure to fail if there are problems when it is is_available = False try: click.apparmor_available() is_available = True except click.AppArmorException: warn("AppArmor not available when processing AppArmor hook") if is_available: click.load_profiles(load_profiles, args=['-r', '--write-cache', '--cache-loc=%s' % apparmor_cache]) # missing_clicks has the profile filename so we need to find the # profile name to unload from the kernel. # TODO: when click/application lifecycle guarantees the app is not # running, then we can remove the profile. For now leave the # profile in place since the app may still be running # removed_profiles = [] # for fn in missing_clicks: # p = click.AppName(profile_filename=fn).profile_name # removed_profiles.append(p) # click.unload_profiles(removed_profiles) for m in missing_clicks: try: os.remove(os.path.join(apparmor_profiles, m)) except Exception: error("Error removing '%s'" % os.path.join(apparmor_profiles, m), do_exit=False) return 0 if __name__ == "__main__": sys.exit(main()) click-apparmor-0.2/debian/0000775000000000000000000000000012310616705012372 5ustar click-apparmor-0.2/debian/compat0000664000000000000000000000000212307667275013606 0ustar 9 click-apparmor-0.2/debian/click-apparmor.install0000664000000000000000000000005012307667275016677 0ustar usr/bin/aa-click* usr/bin/aa-exec-click click-apparmor-0.2/debian/tests/0000775000000000000000000000000012310075305013527 5ustar click-apparmor-0.2/debian/tests/run_testsuite-as-root0000664000000000000000000000236212307667275017777 0ustar #!/bin/sh # ------------------------------------------------------------------ # # Copyright (C) 2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ set -e add_sysfs="no" cleanup() { mv -f /sbin/apparmor_parser.orig /sbin/apparmor_parser mv -f /usr/lib/python3/dist-packages/apparmor/click.py.orig /usr/lib/python3/dist-packages/apparmor/click.py } uid=`id -u` if [ "$uid" = "0" ]; then # divert apparmor_parser and substitute /bin/true so we don't need to load # policy into the kernel cp -f /sbin/apparmor_parser /sbin/apparmor_parser.orig cp -f /bin/true /sbin/apparmor_parser cp -f /usr/lib/python3/dist-packages/apparmor/click.py /usr/lib/python3/dist-packages/apparmor/click.py.orig sed -i 's/^mock_testenv = False/mock_testenv = True/' /usr/lib/python3/dist-packages/apparmor/click.py trap cleanup EXIT HUP INT QUIT TERM fi # copy the tests to another directory without PYTHONPATH so that it will use # the system imports cp -a ./test-clicktool.py $ADTTMP $ADTTMP/test-clicktool.py click-apparmor-0.2/debian/tests/test_aa-clickhook0000664000000000000000000001670612310073674017057 0ustar #!/bin/sh # ------------------------------------------------------------------ # # Copyright (C) 2013-2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ set -e rc="0" created_frameworks="" tested_frameworks="ubuntu-sdk-13.10 ubuntu-sdk-14.04-dev ubuntu-sdk-14.04-dev1 ubuntu-sdk-14.04-html-dev1 ubuntu-sdk-14.04-html ubuntu-sdk-14.04-papi-dev1 ubuntu-sdk-14.04-papi ubuntu-sdk-14.04-qml-dev3 ubuntu-sdk-14.04-qml" prefix="com.example.click-apparmor-test" user="$USER" if [ -n "$SUDO_USER" ]; then user="$SUDO_USER" fi if [ -z "$user" ]; then echo "Couldn't detect user" >&2 exit 1 fi click list cleanup() { mv -f /sbin/apparmor_parser.orig /sbin/apparmor_parser mv -f /usr/lib/python3/dist-packages/apparmor/click.py.orig /usr/lib/python3/dist-packages/apparmor/click.py for f in $created_frameworks ; do rm -f /usr/share/click/frameworks/${f}.framework done click list | grep "$prefix" | while read line ; do pkgname=`echo $line | awk '{print $1}'` pkgvers=`echo $line | awk '{print $2}'` click unregister --user=$user $pkgname $pkgvers echo "Removed: $pkgname $pkgvers" app_id="${p}_app1_${v}" rm -f "/var/cache/apparmor/click_${app_id}" rm -f "/var/lib/apparmor/clicks/${app_id}.json" rm -f "/var/lib/apparmor/profiles/click_${app_id}" done } trap cleanup EXIT HUP INT QUIT TERM unpack_dir=`pwd` cp -a ./debian/tests/data "$ADTTMP" get_policy_version() { aa-clickquery --click-framework="$1" -q policy_version } # # Main # # divert apparmor_parser and substitute /bin/true so we don't need to load # policy into the kernel cp -f /sbin/apparmor_parser /sbin/apparmor_parser.orig cp -f /bin/true /sbin/apparmor_parser cp -f /usr/lib/python3/dist-packages/apparmor/click.py /usr/lib/python3/dist-packages/apparmor/click.py.orig sed -i 's/^mock_testenv = False/mock_testenv = True/' /usr/lib/python3/dist-packages/apparmor/click.py # Good frameworks/manifests # For now, just create these. We may want to install the packages that creates # them, but for now, do this for i in $tested_frameworks ; do framework_file="/usr/share/click/frameworks/$i.framework" if [ -e "$framework_file" ]; then continue fi # FIXME: this won't work when we move to libclick (ie, we will need # metadata in these files) touch /usr/share/click/frameworks/$i.framework created_frameworks="$created_frameworks $i" done for fr in $tested_frameworks ; do # update framework pv=`get_policy_version "$fr"` for dir in `ls -1d ./debian/tests/data/${prefix}_*` ; do c=`basename $dir` cat "./debian/tests/data/$c/manifest.json" | sed "s/@@@FRAMEWORK@@@/$fr/" > "$ADTTMP/data/$c/manifest.json" cat "./debian/tests/data/$c/apparmor.json" | sed "s/@@@POLICYVERSION@@@/$pv/" > "$ADTTMP/data/$c/apparmor.json" cd "$ADTTMP" click build "$ADTTMP/data/$c" cd "$unpack_dir" click install --user=$user "$ADTTMP/${c}_all.click" echo -n "Installing: " p=`echo "$c" | cut -d '_' -f 1` v=`echo "$c" | cut -d '_' -f 2` echo -n "$p $c ($fr $pv): " # make sure click-apparmor did its job app_id="${p}_app1_${v}" this_rc="0" for f in /var/lib/apparmor/clicks/${app_id}.json /var/lib/apparmor/profiles/click_${app_id} ; do test -e "$f" || { rc="1" echo "'$f' does not exist" this_rc="1" } test -s "/var/lib/apparmor/clicks/${app_id}.json" || { rc="1" echo "'/var/lib/apparmor/clicks/${app_id}.json' is empty" this_rc="1" } done if [ "$this_rc" = "0" ]; then echo PASS else echo FAIL fi echo "Click manifest:" cat "$ADTTMP/data/$c/manifest.json" echo "Security manifest:" cat "$ADTTMP/data/$c/apparmor.json" echo "" # cleanup click unregister --user=$user "$p" "$v" done done # Bad frameworks/manifests fr="ubuntu-nonexistent-13.10" echo "Test ubuntu-nonexistent-13.10 (aa-clickquery, EXFAIL)" this_rc="0" get_policy_version "$fr" && this_rc="1" if [ "$this_rc" = "0" ]; then echo PASS else echo FAIL rc="1" fi for fr in ubuntu-nonexistent-13.10 ubuntu-sdk-13.10 ubuntu-sdk-14.04-qml ; do for pv in 0.1 1.0 1.1 ; do # these are valid, so skip them if [ "$fr" = "ubuntu-sdk-13.10" ] && [ "$pv" = "1.0" ]; then continue elif [ "$fr" = "ubuntu-sdk-14.04-qml" ] && [ "$pv" = "1.1" ]; then continue fi for dir in `ls -1d ./debian/tests/data/${prefix}_*` ; do c=`basename $dir` cat "./debian/tests/data/$c/manifest.json" | sed "s/@@@FRAMEWORK@@@/$fr/" > "$ADTTMP/data/$c/manifest.json" cat "./debian/tests/data/$c/apparmor.json" | sed "s/@@@POLICYVERSION@@@/$pv/" > "$ADTTMP/data/$c/apparmor.json" cd "$ADTTMP" click build "$ADTTMP/data/$c" cd "$unpack_dir" extra_args= if [ "$fr" = "ubuntu-nonexistent-13.10" ]; then extra_args="--force-missing-framework" fi click install $extra_args --user=$user "$ADTTMP/${c}_all.click" echo -n "Installing: " p=`echo "$c" | cut -d '_' -f 1` v=`echo "$c" | cut -d '_' -f 2` echo -n "$p $c (EXFAIL, $fr $pv): " # make sure click-apparmor did its job app_id="${p}_app1_${v}" this_rc="0" f="/var/lib/apparmor/clicks/${app_id}.json" test -e "$f" || { rc="1" echo "'$f' does not exist" this_rc="1" } test -s "/var/lib/apparmor/clicks/${app_id}.json" || { rc="1" echo "'/var/lib/apparmor/clicks/${app_id}.json' is empty" this_rc="1" } f="/var/lib/apparmor/profiles/click_${app_id}" test -e "$f" && { rc="1" echo "'$f' exists, but shouldn't" this_rc="1" } this_rc="0" if [ "$fr" = "ubuntu-nonexistent-13.10" ]; then aa-clickhook 2>&1 | grep -q "Could not find framework for '$app_id" || { rc="1" echo "Could not find \"Could not find framework for '$app_id\"" this_rc="1" } else aa-clickhook 2>&1 | grep -q "Invalid policy version for '$app_id" || { rc="1" echo "Could not find \"Invalid policy version for '$app_id\"" this_rc="1" } fi if [ "$this_rc" = "0" ]; then echo PASS else echo FAIL fi echo "Click manifest:" cat "$ADTTMP/data/$c/manifest.json" echo "Security manifest:" cat "$ADTTMP/data/$c/apparmor.json" echo "" # cleanup click unregister --user=$user "$p" "$v" done done done if [ "$rc" = "0" ]; then echo "PASS (all tests)" else echo "FAIL (one or more failed tests)" fi exit $rc click-apparmor-0.2/debian/tests/control0000664000000000000000000000021512307667321015143 0ustar Tests: run_testsuite-as-root test_aa-exec-click test_aa-clickhook test_aa-clickquery Restrictions: needs-root allow-stderr Depends: @, click click-apparmor-0.2/debian/tests/test_aa-clickquery0000664000000000000000000000246312307667321017263 0ustar #!/bin/sh # ------------------------------------------------------------------ # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ set -e rc="0" for framework in ubuntu-sdk-13.10 ubuntu-sdk-14.04 ubuntu-sdk-14.04-qml ubuntu-sdk-14.04-html ubuntu-sdk-14.04-papi ; do expected="" case "$framework" in ubuntu-sdk-13.10) expected="1.0" ;; ubuntu-sdk-14.04*) expected="1.1" ;; esac v=`aa-clickquery -q policy_version --click-framework=$framework` || { rc="1" echo "aa-clickquery exited with error" >&2 continue } if [ "$v" != "$expected" ]; then echo "Wrong version for '$framework' ($v != $expected)" >&2 rc="1" fi done for framework in ubuntu-sdk-13.04 nonexistent ubuntu-sdk-14.05-qml ; do if aa-clickquery -q policy_version --click-framework=$framework >/dev/null 2>&1 ; then echo "Unexpected return code '0' for '$framework'" >&2 rc="1" fi done if [ "$rc" = "0" ]; then echo PASS else echo FAIL fi exit $rc click-apparmor-0.2/debian/tests/data/0000775000000000000000000000000012307667275014463 5ustar click-apparmor-0.2/debian/tests/data/com.example.click-apparmor-test_0.1/0000775000000000000000000000000012307667275023131 5ustar click-apparmor-0.2/debian/tests/data/com.example.click-apparmor-test_0.1/apparmor.json0000664000000000000000000000014112307667275025641 0ustar { "policy_groups": [ "networking" ], "policy_version": @@@POLICYVERSION@@@ } click-apparmor-0.2/debian/tests/data/com.example.click-apparmor-test_0.1/manifest.json0000664000000000000000000000050612307667275025633 0ustar { "description": "some description", "framework": "@@@FRAMEWORK@@@", "hooks": { "app1": { "apparmor": "apparmor.json" } }, "maintainer": "Foo Bar ", "name": "com.example.click-apparmor-test", "title": "click-apparmor test click", "version": "0.1" } click-apparmor-0.2/debian/tests/test_aa-exec-click0000664000000000000000000000737712307734405017126 0ustar #!/bin/sh # ------------------------------------------------------------------ # # Copyright (C) 2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ set -e testdir="/tmp/testme" if [ -d "$ADTTMP" ]; then cat > "$ADTTMP/click" < "$ADTTMP/aa-exec" < $ADTTMP/out EOM chmod 755 "$ADTTMP/aa-exec" else echo "ADTTMP '$ADTTMP' does not exist" >&2 exit 1 fi uid=`id -u` if [ "$uid" = "0" ]; then # divert aa-exec and substitute /bin/true so we don't need to load # policy into the kernel cp -f /usr/sbin/aa-exec /usr/sbin/aa-exec.orig cp -f "$ADTTMP/aa-exec" /usr/sbin/aa-exec cp -f /usr/bin/click /usr/bin/click.orig cp -f "$ADTTMP/click" /usr/bin/click trap "mv -f /usr/sbin/aa-exec.orig /usr/sbin/aa-exec ; mv -f /usr/bin/click.orig /usr/bin/click" EXIT HUP INT QUIT TERM fi user=`id -un` aa-exec-click -x -p testme -- /bin/true rc="0" for i in XDG_CACHE_HOME XDG_CONFIG_HOME XDG_DATA_HOME XDG_RUNTIME_DIR XDG_DATA_DIRS PATH LD_LIBRARY_PATH QML2_IMPORT_PATH APP_ID UBUNTU_APPLICATION_ISOLATION TMPDIR __GL_SHADER_DISK_CACHE_PATH ; do case "$i" in XDG_CACHE_HOME) egrep -q "$i='/.*/.cache'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; XDG_CONFIG_HOME) egrep -q "$i='/.*/.config'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; XDG_DATA_HOME) egrep -q "$i='/.*/.local/share'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; XDG_RUNTIME_DIR) egrep -q "$i='/run/user/[0-9]+'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; XDG_DATA_DIRS) egrep -q "$i='/tmp/testme:/usr/share" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; PATH) egrep -q "$i='/tmp/testme/lib/.+/bin:/tmp/testme:" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; LD_LIBRARY_PATH) egrep -q "$i='/tmp/testme/lib/.+'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; QML2_IMPORT_PATH) egrep -q "$i='/tmp/testme/lib/.*'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; __GL_SHADER_DISK_CACHE_PATH) egrep -q "$i='/.*/.cache/testme'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; APP_ID) egrep -q "$i='testme'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; UBUNTU_APPLICATION_ISOLATION) egrep -q "$i='1'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; TMPDIR) egrep -q "$i='/run/user/[0-9]+/confined/testme'" "$ADTTMP/out" || { rc="1" echo "$i not properly set" >&2 } ;; esac done if [ "$rc" = "0" ]; then echo PASS else echo "" echo "Output:" cat "$ADTTMP/out" >&2 echo "" echo FAIL fi exit $rc click-apparmor-0.2/debian/click-apparmor.dirs0000664000000000000000000000003112307667275016171 0ustar /var/lib/apparmor/clicks click-apparmor-0.2/debian/source/0000775000000000000000000000000012307667275013710 5ustar click-apparmor-0.2/debian/source/format0000664000000000000000000000001512307667275015117 0ustar 3.0 (native) click-apparmor-0.2/debian/click-apparmor.manpages0000664000000000000000000000005712310100216016776 0ustar aa-exec-click.1 aa-clickquery.1 aa-clickhook.1 click-apparmor-0.2/debian/control0000664000000000000000000000240012310324467013772 0ustar Source: click-apparmor Section: admin Priority: optional Maintainer: Steve Beattie Build-Depends: debhelper (>= 9), python3 (>= 3.3), python3-all, python3-setuptools, python3-debian, pep8, pyflakes, click-dev, libnih-dbus1, apparmor-easyprof-ubuntu Standards-Version: 3.9.4 Vcs-Bzr: https://code.launchpad.net/~ubuntu-security/click-apparmor/trunk Vcs-Browser: http://bazaar.launchpad.net/~ubuntu-security/click-apparmor/trunk/files X-Auto-Uploader: no-rewrite-version X-Python3-Version: >= 3.3 XS-Testsuite: autopkgtest Package: click-apparmor Architecture: any Depends: ${misc:Depends}, ${python3:Depends}, python3-apparmor-click (= ${binary:Version}), click, apparmor (>= 2.8.0-0ubuntu15) Description: Click manifest to AppArmor easyprof conversion tools Provides a tool and a click hook for generating and managing AppArmor policies for Click packages. Package: python3-apparmor-click Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, apparmor-easyprof, apparmor-easyprof-ubuntu (>= 1.0.17), libnih-dbus1 Description: Click manifest to AppArmor easyprof conversion tools Provides a python library for converting Ubuntu Click package manifests to AppArmor easyprof manifest files to generate AppArmor policies. click-apparmor-0.2/debian/changelog0000664000000000000000000002171612310616571014254 0ustar click-apparmor (0.2) trusty; urgency=low * aa-exec-click: don't allow running under X by default - abort if running under X - add -x option to force running under X - add aa-exec-click.1 man page - debian/click-apparmor.manpages: install aa-exec-click.1 * debian/control: - drop unneeded Depends on python3-click - Build-Depends on pyflakes * add Makefile for tests * debian/rules: - don't also call dh_auto_build now that we have a Makefile - update to use Makefile for test commands * support alternate frameworks and subframeworks * add API for querying policy version given the click framework * add aa-clickquery tool to use query API with associated man page and autopkgtest * add aa-clickhook man page -- Jamie Strandboge Fri, 14 Mar 2014 10:17:11 -0500 click-apparmor (0.1.15.3) trusty; urgency=low [ Jamie Strandboge ] * regenerate policy if hook symlink is newer than the profile (LP: #1291549) * debian/control: update for CI Train - Set X-Auto-Uploader to no-rewrite-version - Set Vcs-Bzr to the new target branch -- Ubuntu daily release Thu, 13 Mar 2014 02:18:35 +0000 click-apparmor (0.1.14) trusty; urgency=medium * implement autopkgtests - add debian/tests/control - add preliminary tests to debian/tests - adjust debian/control for XS-Testsuite * test-clicktool.py: don't run unload profiles if ADTTMP is set * apparmor/click.py: adjust to add mock_testenv and honor it to skip checking apparmor_dirs when set -- Jamie Strandboge Wed, 05 Feb 2014 17:35:29 -0500 click-apparmor (0.1.13) trusty; urgency=low * prepend the click install directory to XDG_DATA_DIRS (LP: #1250546) * aa-exec-click: adjust to update LD_LIBRARY_PATH and QML2_IMPORT_PATH * debian/control: make click-apparmor Architecture: any so we can obtain the gnu triplet * debian/rules: adjust aa-exec-click for gnu triplet -- Jamie Strandboge Tue, 17 Dec 2013 10:18:55 -0600 click-apparmor (0.1.12) trusty; urgency=low [ Colin Watson ] * Treat manifests as UTF-8, even in non-UTF-8 locales (LP: #1245677). -- Jamie Strandboge Mon, 28 Oct 2013 19:33:23 -0700 click-apparmor (0.1.11) saucy; urgency=low * don't traceback on os.remove (LP: #1234908) -- Jamie Strandboge Thu, 03 Oct 2013 14:46:54 -0500 click-apparmor (0.1.10) saucy; urgency=low * work around lack of first boot postinst-style code in lxc-android-config and ship a click-apparmor upstart job. This checks to see if apparmor or apparmor-easyprof-ubuntu's dpkg md5sums changed, and if so, runs 'aa-clickhook -f'. This allows us to update policy for click packages on reboot after system-image updates. Note, the click system hooks job is not enough, because that correctly uses 'aa-clickhook' without arguments. 'aa-clickhook -f' isn't normally needed so this job completes quickly in typical reboots ('aa-clickhook -f' still only updates the click policy that is affected). (LP: #1229449) * don't verify the policy before load. It will error on load which is equivalent to erroring out before load. This allows us to avoid parsing policy twice which can save significant time when regenerating lots of profiles, which is important during first boot after system upgrade * generated policy should be readable by everyone (the click security manifests are not private) * fix default path to apparmor_parser (thus avoiding a needless 'which') * debian/rules: cleanup .coverage and apparmor/__pycache__ -- Jamie Strandboge Mon, 23 Sep 2013 14:11:03 -0500 click-apparmor (0.1.9) saucy; urgency=low * aa-exec-click: account for when /usr/sbin is not in the user's PATH -- Jamie Strandboge Thu, 05 Sep 2013 09:05:12 -0500 click-apparmor (0.1.8) saucy; urgency=low * apparmor/click.py: don't forget to delete temporary file when not updating policy * aa-clickhook, apparmor/click.py: add --include=PATH option for injecting #include "PATH" into profiles to support QA testing (eg, autopilot) -- Jamie Strandboge Wed, 04 Sep 2013 17:10:42 -0500 click-apparmor (0.1.7) saucy; urgency=low * README: update to describe how to test * add several new tests to complete coverage of apparmor/click.py * apparmor/click.py - don't overwrite existing file of policy is unchanged - fix couple of tracebacks with error reporting found by new tests * debian/control: Build-Depends on apparmor-easyprof-ubuntu since it is needed by the testsuite -- Jamie Strandboge Fri, 30 Aug 2013 07:09:02 -0500 click-apparmor (0.1.6) saucy; urgency=low * fix bashism in aa-exec-click -- Jamie Strandboge Tue, 27 Aug 2013 16:01:25 -0500 click-apparmor (0.1.5) saucy; urgency=low * add aa-exec-click * debian/control: Depends on apparmor that ships aa-exec * abstract out _unload_profile() * clarify clickname, profile_filename (formerly profilename) and raw_name * don't try to unload the profile from the kernel. Once there is a guarantee that the app is not running, then we can remove the profile -- Jamie Strandboge Mon, 26 Aug 2013 13:30:41 -0500 click-apparmor (0.1.4) saucy; urgency=low [ Steve Beattie ] * aa-clickhook: output to stdout instead of stderr if no output file is specified. [ Jamie Strandboge ] * add support for abstractions, read_paths and write_paths. These are not generally allowed in the Ubuntu app store, but will be handled via the review process. * fix a few error strings * policy_version is JSON Number, not string * add some more policy_version tests * Qt json outputs 1.0 as 1. Account for that in our transmogrification to easyprof (LP: #1214618) -- Jamie Strandboge Thu, 22 Aug 2013 12:15:02 -0500 click-apparmor (0.1.3) saucy; urgency=low * support different policy vendors since the click hook should be usable with alternative app stores -- Jamie Strandboge Fri, 16 Aug 2013 16:27:44 -0500 click-apparmor (0.1.2) saucy; urgency=low * apparmor/click.py: - add dbus_path() (uses libnih-dbus) to generate an APP_ID_DBUS template variable - rename APPNAME to APP_PKGNAME and APPVERSION to APP_VERSION to make policy clear wrt click documentation * aa-clickhook: report but don't trace back failures to generate the profile * update tests for above * debian/control: - Build-Depends on libnih-dbus1 (needed for tests) - python3-apparmor-click should Depends on libnih-dbus1 - python3-apparmor-click should Depends on apparmor-easyprof-ubuntu >= 1.0.17 (due to variable name changes) - tighten up other dependencies -- Jamie Strandboge Fri, 16 Aug 2013 12:22:42 -0500 click-apparmor (0.1.1) saucy; urgency=low * implement apparmor_available() to allow installing click packages when apparmor is not available. Obviously this is not generally recommended for normal use, but is valid when installing click packages in a chroot, as with live-build - apparmor/click.py: implement apparmor_available() - test-clicktool.py: add tests - aa-clickhook: adjust to use apparmor_available() -- Jamie Strandboge Tue, 13 Aug 2013 07:15:45 -0500 click-apparmor (0.1.0) saucy; urgency=low * some pep8/code/comment cleanups and add documentation * remove unused aa-clickprototype * debian/rules: update to run pep8 -- Jamie Strandboge Thu, 08 Aug 2013 15:44:26 -0500 click-apparmor (0.0.5) saucy; urgency=low [ Steve Beattie ] * debian/control: fix typo in maintainer email address * apparmor/click.py: - fix up pyflakes warnings (1 false positive remains) - fix up some pylint warnings - some click applications don't specify the policy_groups entry at all when using the 'unconfined' template; don't reject and instead automatically create an empty list - use a default value for policy_version as well, again to be lenient in what is missing from what we accept [ Jamie Strandboge ] * debian/rules: run testsuite * test-clicktool.py: pyflakes cleanups -- Steve Beattie Wed, 24 Jul 2013 15:42:26 -0700 click-apparmor (0.0.4) saucy; urgency=low * debian/rules: move dh_click to override_dh_auto_install -- Jamie Strandboge Wed, 24 Jul 2013 02:05:31 -0500 click-apparmor (0.0.3) saucy; urgency=low * split out python3-apparmor-click to only generate python3 versions of everything. -- Steve Beattie Tue, 23 Jul 2013 12:25:45 -0700 click-apparmor (0.0.2) saucy; urgency=low * Update to incorporate click packaging and hook changes -- Steve Beattie Tue, 23 Jul 2013 06:37:21 -0700 click-apparmor (0.0.1) UNRELEASED; urgency=low * Initial release. -- Steve Beattie Thu, 11 Jul 2013 10:20:01 -0700 click-apparmor-0.2/debian/rules0000775000000000000000000000275412307702127013462 0ustar #!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. # export DH_VERBOSE=1 PY3REQUESTED := $(shell py3versions -r) PY3DEFAULT := $(shell py3versions -d) # Run setup.py with the default python3 last so that the scripts use # #!/usr/bin/python3 and not #!/usr/bin/python3.X. PY3 := $(filter-out $(PY3DEFAULT),$(PY3REQUESTED)) python3 GNUTRIPLET := $(shell dpkg-architecture -qDEB_BUILD_MULTIARCH) %: dh $@ --with python3,click override_dh_auto_build: set -e; for python in $(PY3); do \ $$python setup.py build; \ done override_dh_auto_install: # setuptools likes to leave some debris around, which confuses # things. find build -name __pycache__ -print0 | xargs -0r rm -rf find build -name \*.egg-info -print0 | xargs -0r rm -rf dh_auto_install set -e; for python in $(PY3); do \ $$python setup.py install --force --root=$(CURDIR)/debian/tmp \ --no-compile --install-layout=deb; \ done # make location in /var to store symlinks mkdir -p $(CURDIR)/debian/tmp/var/lib/apparmor/clicks # copy aa-exec-click into place install -D -m 755 $(CURDIR)/aa-exec-click \ $(CURDIR)/debian/tmp/usr/bin/aa-exec-click # adjust aa-exec-click for multiarch sed -i "s/gnutriplet='###GNUTRIPLET###'/gnutriplet='$(GNUTRIPLET)'/" \ $(CURDIR)/debian/tmp/usr/bin/aa-exec-click override_dh_click: dh_click --name apparmor override_dh_auto_clean: dh_auto_clean override_dh_auto_test: #make pyflakes-check # needs locales setup make pep8-check LC_ALL=C make man-check make test click-apparmor-0.2/debian/click-apparmor.upstart0000664000000000000000000000155112307667275016742 0ustar description "Refresh Click Apparmor profiles if system policy changed" author "Jamie Strandboge " start on filesystem task script run= # If packages for system policy that affect click packages have been # updated since the last time we ran, run aa-clickhook -f for pkg in apparmor-easyprof-ubuntu apparmor ; do [ -f "/var/lib/dpkg/info/${pkg}.md5sums" ] || continue if ! diff -q "/var/lib/dpkg/info/${pkg}.md5sums" "/var/lib/apparmor/profiles/.${pkg}.md5sums" 2>/dev/null ; then # store md5sums in /var/lib/apparmor/profiles since # /var/cache/apparmor might be cleared by apparmor cp -f "/var/lib/dpkg/info/${pkg}.md5sums" "/var/lib/apparmor/profiles/.${pkg}.md5sums" run='yes' fi done if [ -n "$run" ]; then aa-clickhook -f fi end script click-apparmor-0.2/debian/python3-apparmor-click.install0000664000000000000000000000002012307667275020276 0ustar usr/lib/python3 click-apparmor-0.2/debian/click-apparmor.apparmor.click-hook0000664000000000000000000000012412307667275021076 0ustar Pattern: /var/lib/apparmor/clicks/${id}.json Exec: /usr/bin/aa-clickhook User: root click-apparmor-0.2/debian/copyright0000664000000000000000000000204212307667275014341 0ustar Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: click-apparmor Upstream-Contact: Steve Beattie Source: https://launchpad.net/ubuntu/+source/click-apparmor Files: * Copyright: 2013 Canonical Ltd. License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. . On Debian systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL-2'. click-apparmor-0.2/aa-clickquery.10000664000000000000000000000144712307736026013777 0ustar .TH aa-clickquery: "1" "" "March 2014" "March 2014" .SH NAME aa\-clickquery \- program for querying click\-apparmor .PP .SH DESCRIPTION This program is used to query click\-apparmor for information. .SH USAGE .TP aa\-clickquery \-\-click\-framework=FRAMEWORK \-\-query=QUERY .SH OPTIONS .TP \fB\-h\fR show program's help .TP \fB\-q\fR QUERY | \fB\-\-query\fR=QUERY The string to query. Specify 'help' for available query strings. .TP \fB\-\-click\-framework\fR=FRAMEWORK Specify the click framework to query. .SH EXAMPLES .PP Determine the policy version of a particular framework. $ aa\-clickquery \-\-click-framework=ubuntu\-sdk\-13.10 \-q policy_version 1.0 $ aa\-clickquery \-\-click\-framework=ubuntu\-sdk\-14.04\-qml \-q policy_version 1.1 .SH SEE ALSO .PP \fBapparmor\fR(7) \fBclick\fR(1) click-apparmor-0.2/aa-exec-click.10000664000000000000000000000176012307735750013634 0ustar .TH aa-exec-click: "1" "" "March 2014" "March 2014" .SH NAME aa\-exec\-click \- program for executing click packages under confinement .PP .SH DESCRIPTION This program is used to execute click package under AppArmor confinement. It is a thin wrapper around the \fBaa\-exec\fR command which sets up the environment that is needed to run a click application under confinement. .SH USAGE .TP aa\-exec\-click -p [ \-x ] [aa\-exec options] -- COMMAND .SH OPTIONS .TP \fB\-h\fR show program's help .TP \fB\-p\fR PROFILE NAME AppArmor profile the application should run under .TP \fB\-x\fR Force running application under X. Click packages are typically considered untrusted and are usually required to run under confinement. At this time, running untrusted click applications under X is not considered safe because of limitations in the X protocol. By default, aa\-exec\-click will refuse to launch applications if they are running under X. .SH SEE ALSO .PP \fBaa\-exec\fR(8), \fBapparmor\fR(7) click-apparmor-0.2/aa-exec-click0000755000000000000000000000766012307754364013505 0ustar #!/bin/bash # ------------------------------------------------------------------ # # Copyright (C) 2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ set -e # Wrapper around aa-exec to set various click variables: # https://wiki.ubuntu.com/SecurityTeam/Specifications/ApplicationConfinement#Launching_applications usage() { echo "`basename $0` -p -- ..." } profile="" use_insecure_x="" xpos="" while getopts hxp: f ; do case "$f" in p) profile="$OPTARG";; x) use_insecure_x="yes" xpos=$((OPTIND-1)) ;; h) usage; exit 0;; *) usage; exit 1;; esac done # strip -x from the list since we pass arguments straight to aa-exec if [ -z "$xpos" ]; then if [ "$1" = "-x" ]; then shift fi else set -- "${@:1:$((xpos-1))}" "${@:$((xpos+1)):$#}" fi if [ -z "$profile" ]; then usage exit 1 fi # Perhaps there is a better way to detect this, but for now, this works if [ -d "/tmp/.X11-unix" ]; then num_sockets=`ls -1 /tmp/.X11-unix | wc -l` if [ "$num_sockets" != "0" ] && [ "$use_insecure_x" != "yes" ]; then echo "Detected click app running under X! Aborting" exit 1 fi fi # gnutriplet should be updated during package build gnutriplet='###GNUTRIPLET###' pkgname=`echo "$profile" | cut -d '_' -f 1` # Make sure we have sane defaults based on the XDG spec if [ -z "$XDG_CACHE_HOME" ]; then export XDG_CACHE_HOME="$HOME/.cache" fi if [ -z "$XDG_CONFIG_HOME" ]; then export XDG_CONFIG_HOME="$HOME/.config" fi if [ -z "$XDG_DATA_HOME" ]; then export XDG_DATA_HOME="$HOME/.local/share" fi if [ -z "$XDG_RUNTIME_DIR" ]; then export XDG_RUNTIME_DIR="/run/user/$(id -ru)" # Ubuntu-specific fi # Set up various environment variables based on the click install directory # (click is guaranteed to be installed since click-apparmor Depends on it). # Also, while 'click pkgdir' should only return a path with the click package # name, and click package names follow Debian source package rules (see # (Debian policy 5.6.1), let's be extra careful and filter out any ':' in the # pkgdir pkgdir=`click pkgdir "$pkgname" | sed 's/://g'` && { if [ -n "$pkgdir" ]; then if [ -n "$XDG_DATA_DIRS" ]; then export XDG_DATA_DIRS="$pkgdir:$XDG_DATA_DIRS" else export XDG_DATA_DIRS="$pkgdir:/usr/share" fi if [ -n "$PATH" ]; then export PATH="$pkgdir:$PATH" else export PATH="$pkgdir:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" fi if [ "$gnutriplet" != "###GNUTRIPLET###" ]; then libdir="$pkgdir/lib/$gnutriplet" # We set PATH above, but if have compiled code, also prepend # $libdir/bin export PATH="$libdir/bin:$PATH" # LD_LIBRARY_PATH is searched forwards, so prepend if [ -n "$LD_LIBRARY_PATH" ]; then export LD_LIBRARY_PATH="$libdir:$LD_LIBRARY_PATH" else export LD_LIBRARY_PATH="$libdir" fi # QML2_IMPORT_PATH is search backwards, so append if [ -n "$QML2_IMPORT_PATH" ]; then export QML2_IMPORT_PATH="$QML2_IMPORT_PATH:$libdir" else export QML2_IMPORT_PATH="$libdir" fi fi fi } # This may be useful to apps export APP_ID="$profile" # Set application isolation environment export UBUNTU_APPLICATION_ISOLATION=1 export TMPDIR="$XDG_RUNTIME_DIR/confined/$pkgname" mkdir -p "$TMPDIR" || true export __GL_SHADER_DISK_CACHE_PATH="$XDG_CACHE_HOME/$pkgname" aa_exec="aa-exec" if ! which $aa_exec >/dev/null ; then aa_exec="/usr/sbin/aa-exec" fi exec $aa_exec "$@" click-apparmor-0.2/aa-clicktool0000775000000000000000000000432312307667275013460 0ustar #! /usr/bin/python3 # ------------------------------------------------------------------ # # Copyright (C) 2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ # tranforms a click manifest json file into a manifest consumable by the # aa-easyprof tool # # transformations to make: # click toplevel # # name + desktop_file + version -> profile name # ubuntu-13.10-framework -> policy_vendor = ubuntu from apparmor import click import json import optparse import os import sys def main(): def error(out, exit_code=1, do_exit=True): '''Print error message and exit''' try: sys.stderr.write("ERROR: %s\n" % (str(out))) except IOError: pass if do_exit: sys.exit(exit_code) def usage(): '''Return usage information''' return 'USAGE: %s [options] ' % \ os.path.basename(sys.argv[0]) parser = optparse.OptionParser() parser.add_option("-o", "--output", dest="output", help="output easyprof manifest to FILE", metavar="FILE") (opt, args) = parser.parse_args() if len(args) == 1: infile = args[0] else: error(usage()) try: clickjson = click.ClickManifest(infile) except IOError as e: error("unable to read manifest '%s': %s" % (infile, e)) except ValueError as e: error("manifest '%s' json error: %s" % (infile, e)) try: m = click.transform(clickjson) except click.AppArmorException as e: error("failed to convert manifest '%s':\n %s" % (infile, e)) # output json consistently for external testing try: if opt.output: outfile = open(opt.output, "w") else: outfile = sys.stdout json.dump(m, outfile, indent=2, separators=(',', ': '), sort_keys=True) except IOError as e: error("unable to write out manifest: %s" % (e)) if __name__ == "__main__": main() click-apparmor-0.2/Makefile0000664000000000000000000000275112307732434012621 0ustar TMPDIR = ./tmp PYFLAKES = $(TMPDIR)/pyflakes.out PEP8 = $(TMPDIR)/pep8.out ifndef $(PYTHON) export PYTHON=python3 endif PYFLAKES_EXE = pyflakes ifeq ($(PYTHON),python3) PYFLAKES_EXE = pyflakes3 endif all: # Use setup.py to install. See README for details exit 1 test: $(PYTHON) ./test-clicktool.py coverage: $(PYTHON) -m coverage run ./test-clicktool.py coverage-report: $(PYTHON) -m coverage report --show-missing --omit="*/apparmor/easyprof.py,*/pkg_resources.py" pyflakes-check: clean $(shell test -d $(TMPDIR) || mkdir $(TMPDIR)) $(shell $(PYFLAKES_EXE) apparmor/*py ./aa-clickhook ./aa-clicktool ./aa-clickquery ./test-clicktool.py 2>&1 | grep -v "undefined name '_'" > $(PYFLAKES)) cat "$(PYFLAKES)" test ! -s "$(PYFLAKES)" @echo "Syntax is ok" pep8-check: clean $(shell test -d $(TMPDIR) || mkdir $(TMPDIR)) $(shell pep8 apparmor/*py ./aa-clickhook ./aa-clicktool ./aa-clickquery ./test-clicktool.py 2>&1 > $(PEP8)) cat "$(PEP8)" test ! -s "$(PEP8)" @echo "PEP8 ok" man-check: clean $(shell mkdir $(TMPDIR) 2>/dev/null) for page in `ls *.1`; do \ manout=$(TMPDIR)/$$page.out; \ echo "Checking $$page for errors... "; \ PAGER=cat LANG='C' MANWIDTH=80 man --warnings -E ascii -l $$page >/dev/null 2> "$$manout"; \ cat "$$manout"; \ test ! -s "$$manout" || exit 1; \ echo "PASS"; \ done; \ @echo "Man pages are ok" check: pep8-check pyflakes-check man-check test clean: rm -rf $(TMPDIR) rm -f ./apparmor/*.pyc rm -rf ./apparmor/__pycache__ rm -rf ./.coverage click-apparmor-0.2/aa-clickquery0000664000000000000000000000426012307667321013635 0ustar #! /usr/bin/python3 # ------------------------------------------------------------------ # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ from apparmor import click import optparse import sys def error(out, exit_code=1, do_exit=True): '''Print error message and exit''' try: sys.stderr.write("ERROR: %s\n" % (out)) except IOError: pass if do_exit: sys.exit(exit_code) def warn(out): '''Print warning message''' try: sys.stderr.write("WARN: %s\n" % (out)) except IOError: pass def msg(out): '''Print message''' try: sys.stdout.write("%s\n" % (out)) except IOError: pass def main(): parser = optparse.OptionParser() parser.add_option("-q", "--query", dest='query', help="Query for given framework. Specify 'help' " + "for more information", metavar="QUERY", default=None) parser.add_option("--click-framework", dest='click_framework', help='Specify click framework to query', metavar="CLICKFRAMEWORK", default=None) (opt, args) = parser.parse_args() if not len(args) == 0: sys.exit(1) rc = 0 if opt.query == "help": m = '''Valid query strings: policy_version ''' elif opt.query is None or opt.click_framework is None: error("Not enough arguments") elif opt.query == "policy_version": try: v = click.get_policy_version_for_framework( opt.click_framework) except click.AppArmorException: error("Could not determine policy version for '%s'" % opt.click_framework) m = "%s" % str(v) else: error("Invalid query '%s'" % opt.query) msg(m) return rc if __name__ == "__main__": sys.exit(main()) click-apparmor-0.2/aa-clickhook.10000664000000000000000000000200512310100324013535 0ustar .TH aa-clickhook: "1" "" "March 2014" "March 2014" .SH NAME aa\-clickhook \- click system hook for AppArmor .PP .SH DESCRIPTION When a click package is installed, click will run system and user hooks. The click AppArmor system hook converts the security manifest in the click package into an AppArmor profile, then loads the profile into the kernel. On Ubuntu, the click AppArmor hook maps click frameworks to appropriate policy versions to ensure correct AppArmor policy is generated. .SH USAGE .TP aa\-clickhook [OPTIONS] .SH OPTIONS .TP \fB\-h\fR show program's help .TP \fB\-f\fR | \fB\-\-force\fR | \fB\-\-force\-regenerate\fR Force regeneration of all click profiles .TP \fB\-\-include\fR=PATH Add '#include "PATH"' to generated profiles .SH NOTES .PP aa\-clickhook will skip generating AppArmor policy if the framework is missing, if the specified policy version doesn't match the expected version for the framework, or otherwise improperly formatted click packages. .SH SEE ALSO .PP \fBapparmor\fR(7) \fBclick\fR(1) click-apparmor-0.2/test-clicktool.py0000775000000000000000000021534412310334737014500 0ustar #!/usr/bin/python3 # -*- coding: UTF-8 -*- # ------------------------------------------------------------------ # # Copyright (C) 2013-2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ # not needed for python3, but pyflakes is grumpy without it from __future__ import print_function, unicode_literals from apparmor import click from apparmor.click import AppName import apparmor.easyprof import json import os import shutil import sys import tempfile import time import unittest class ClickState(object): def __init__(self, rootdir): self.rootdir = rootdir # note: this contains a '_' to trip up the name parsing code # if not properly filtered off self.click_dir = os.path.join(self.rootdir, "click_apparmor") os.mkdir(self.click_dir) self.packages_tree = os.path.join(self.rootdir, "click.ubuntu.com") os.mkdir(self.packages_tree) self.profiles_dir = os.path.join(self.rootdir, "apparmor-profiles") os.mkdir(self.profiles_dir) def _stub_json(self, name, version): j = dict() j['name'] = name j['version'] = version j['hooks'] = dict() j['title'] = "Some silly demo app called %s" % (name) j['maintainer'] = 'Büggy McJerkerson ' return j def _add_app_to_json(self, app): j = self.manifest_json j['hooks'][app] = dict() j['hooks'][app]['apparmor'] = "apparmor/%s.json" % (app) with open(self.click_manifest, "w", encoding="UTF-8") as f: json.dump(j, f, indent=2, separators=(',', ': '), sort_keys=True, ensure_ascii=False) def add_package(self, name, version, framework=None): self.package = name self.version = version self.click_pkgdir = os.path.join(self.packages_tree, name, version, ".click", "info") self.pkg_dir = os.path.join(self.packages_tree, name, version) os.makedirs(self.click_pkgdir) self.manifest_json = self._stub_json(name, version) if framework: self.manifest_json['framework'] = framework self.click_manifest = os.path.join(self.click_pkgdir, "%s.manifest" % (name)) with open(self.click_manifest, "w+", encoding="UTF-8") as f: json.dump(self.manifest_json, f, indent=2, separators=(',', ': '), sort_keys=True, ensure_ascii=False) def add_app(self, appname, manifest=None): os.mkdir(os.path.join(self.click_pkgdir, "apparmor")) aa_json = os.path.join(self.click_pkgdir, "apparmor", "%s.json" % (appname)) self._add_app_to_json(appname) with open(aa_json, "w+", encoding="UTF-8") as f: if manifest: f.write(manifest) os.symlink(aa_json, os.path.join(self.click_dir, "%s_%s_%s.json" % (self.package, appname, self.version))) class T(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp(prefix="aa-click-manifest-") self.clickstate = ClickState(self.tmpdir) self.maxDiff = None def tearDown(self): if os.path.exists(self.tmpdir): shutil.rmtree(self.tmpdir) def test_manifest_name_parsing_1(self): '''Test simple acceptance for parsing click manifest name''' ex_app = "com.ubuntu.developer.username.myapp" ex_name = "myapp" ex_vers = "0.1" fname = "%s_%s_%s.json" % (ex_app, ex_name, ex_vers) (app, appname, version) = click.parse_manifest_name(fname) self.assertEquals(ex_app, app, "expected app %s, got %s" % (ex_app, app)) self.assertEquals(ex_name, appname, "expected appname %s, got %s" % (ex_name, appname)) self.assertEquals(ex_vers, version, "expected version %s, got %s" % (ex_vers, version)) def test_bad_manifest_name(self): '''Test manifest name with not enough elements''' ex_app = "com.ubuntu.developer.username.myapp" ex_name = "myapp" ex_vers = "0.1" fname = "%s_%s%s" % (ex_app, ex_name, ex_vers) try: (app, appname, version) = click.parse_manifest_name(fname) except click.AppArmorException: return except Exception: raise raise Exception("name %s should be invalid" % (fname)) def test_bad_manifest_name_2(self): '''Test manifest name with too many elements''' ex_app = "com.ubuntu.developer.username.myapp" ex_name = "myapp" ex_vers = "0.1" fname = "%s_%s_%s_err" % (ex_app, ex_name, ex_vers) try: (app, appname, version) = click.parse_manifest_name(fname) except click.AppArmorException: return except Exception: raise raise Exception("name %s should be invalid" % (fname)) def test_click_name_to_profile(self): '''Test click name conversion to profile name''' profile = "com.ubuntu.developer.username.myapp_myapp_0.3" expected = "%s.json" % (profile) orig = "click_%s" % (profile) app = click.AppName(profile_filename=orig) self.assertEquals(expected, app.click_name, "expected click name %s, got %s" % (expected, app.click_name)) def test_click_appname_no_args_to_init(self): '''Test AppName has args''' try: click.AppName() except click.AppArmorException: return except Exception: raise raise Exception("name unset should be invalid") def test_click_appname_bad_click_name(self): '''Test AppName with bad click_name''' n = "bad" try: click.AppName(click_name="bad") except click.AppArmorException: return except Exception: raise raise Exception("click_name '%s' should be invalid" % n) def test_click_appname_bad_profile_filename(self): '''Test AppName with bad profile_filename''' n = "bad" try: click.AppName(profile_filename="bad") except click.AppArmorException: return except Exception: raise raise Exception("profile_filename '%s' should be invalid" % n) def test_profile_name_to_click(self): '''Test profile name conversion to click name''' orig = "com.ubuntu.developer.username.myapp_myapp_0.3.json" expected = "click_com.ubuntu.developer.username.myapp_myapp_0.3" app = click.AppName(orig) self.assertEquals(expected, app.profile_filename, "expected profile filename %s, got %s" % (expected, app.profile_filename)) expected = "com.ubuntu.developer.username.myapp_myapp_0.3" self.assertEquals(expected, app.profile_name, "expected profile name %s, got %s" % (expected, app.profile_name)) def test_find_manifest_file(self): '''Test being given a symlink and finding the main package manifest''' c = self.clickstate c.add_package("package", "version") c.add_app("app") (result, pkg_dir) = apparmor.click.get_package_manifest( os.path.join(c.click_dir, "package_app_version.json"), "package") self.assertEquals(c.click_manifest, result, "Expected to get %s, got %s" % (c.click_manifest, result)) self.assertEquals(c.pkg_dir, pkg_dir, "Expected to get %s, got %s" % (c.pkg_dir, pkg_dir)) def test_parse_security_manifest(self): '''Test being given a symlink and parsing the manifests''' c = self.clickstate c.add_package("com.ubuntu.developer.username.myapp", "0.1") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json" nbase = n.strip(".json") cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path(nbase) expected = json.loads('''{ "profiles": { "com.ubuntu.developer.username.myapp_sample-app_0.1": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "com.ubuntu.developer.username.myapp", "APP_VERSION": "0.1", "CLICK_DIR": "%s" } } } }''' % (dbus_id, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) # verify we're testing the json structures deeply expected['profiles'][nbase]['template_variables']['BOGUS_VAR'] = \ 'bogus' self.assertNotEquals(expected, easyprof_manifest, "Expected %s and %s to differ" % (expected, easyprof_manifest)) def test_transform_nonexistent_framework(self): '''Test transform() with invalid framework''' c = self.clickstate c.add_package("com.ubuntu.developer.username.myapp", "0.1") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json" cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n)) cm.framework = "nonexistent" try: apparmor.click.transform(cm) except apparmor.click.AppArmorExceptionClickFrameworkNotFound as e: self.assertTrue("Unknown framework" in str(e)) return except Exception: raise raise Exception("Should have failed on nonexistent framework") def test_parse_security_manifest_without_required_field(self): '''Test click manifest without required field''' j = dict() j['version'] = "0.1" j['hooks'] = dict() j['title'] = "Some silly demo app" j['maintainer'] = 'Büggy McJerkerson ' self.click_manifest = os.path.join(self.tmpdir, "test-app.manifest") with open(self.click_manifest, "w+", encoding="UTF-8") as f: json.dump(j, f, indent=2, separators=(',', ': '), sort_keys=True, ensure_ascii=False) try: apparmor.click.read_click_manifest(self.click_manifest) except click.AppArmorException: return except Exception: raise raise Exception("Should have errored on missing name") # the top level of the security manifest is not *supposed* to # be a single dict entry of the app, but apparently this happens. # Accept it and move on def test_parse_security_manifest_lenient(self): '''Test being given a symlink and parsing the manifests, leniently''' c = self.clickstate c.add_package("com.ubuntu.developer.username.myapp", "0.3") security_json = '''{ "sample-app": { "policy_groups": [ "networking" ], "policy_version": 1.0 } }''' c.add_app('sample-app', manifest=security_json) n = "com.ubuntu.developer.username.myapp_sample-app_0.3.json" nbase = n.strip(".json") cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path(nbase) expected = json.loads('''{ "profiles": { "%s": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "com.ubuntu.developer.username.myapp", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (nbase, dbus_id, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_with_framework(self): '''Test being given a symlink and parsing the manifests w/framework setting''' c = self.clickstate c.add_package("com.ubuntu.developer.username.myapp", "0.1", framework='ubuntu-sdk-13.10') security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json" nbase = n.strip(".json") cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path(nbase) expected = json.loads('''{ "profiles": { "%s": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "com.ubuntu.developer.username.myapp", "APP_VERSION": "0.1", "CLICK_DIR": "%s" } } } }''' % (nbase, dbus_id, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_unknown_framework(self): '''Test unknown framework raises exception''' c = self.clickstate c.add_package("com.ubuntu.developer.username.myapp", "0.1", framework='unknown-sdk-13.10') security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json" try: cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n)) apparmor.click.transform(cm) except click.AppArmorException: return except Exception: raise raise Exception("Framework should be invalid") # the security policy is supposed to have a policy_groups entry even # if the list inside it is empty. However, I'm seeing click apps # without it so we'll accept it and move_on def test_parse_security_manifest_no_policy_groups(self): '''Test being given a symlink and parsing the manifests, leniently''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3") security_json = '{ "policy_version": 1.0 }' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package) expected = json.loads('''{ "profiles": { "%s_sample-app_0.3": { "policy_groups": [ ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (package, dbus_id, package, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_no_entries(self): '''Test being given a symlink and parsing the manifests, no policy whatsoever''' c = self.clickstate package = "com.ubuntu.newbie.username.yourapp" c.add_package(package, "0.3") security_json = '{ }' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package) expected = json.loads('''{ "profiles": { "%s_sample-app_0.3": { "policy_groups": [ ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (package, dbus_id, package, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_unconfined(self): '''Test being given a symlink and parsing the manifests for an unconfined app''' c = self.clickstate appname = "com.ubuntu.developer.trusteddev.terminal" c.add_package(appname, "0.99.9~123") security_json = '''{ "template": "unconfined", "policy_groups": [ ], "policy_version": 1.0 }''' c.add_app('0wnzered', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_0wnzered_0.99.9~123.json" % (appname))) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname) expected = json.loads('''{ "profiles": { "%s_0wnzered_0.99.9~123": { "policy_groups": [ ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "unconfined", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.99.9~123", "CLICK_DIR": "%s" } } } }''' % (appname, dbus_id, appname, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_alternative_policy_vendor(self): '''Test that an alternative vendor is ok''' c = self.clickstate appname = "com.ubuntu.developer.trusteddev.terminal" c.add_package(appname, "0.99.9~123") security_json = '''{ "template": "unconfined", "policy_groups": [ ], "policy_version": 1.0, "policy_vendor": "somevendor" }''' c.add_app('0wnzered', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_0wnzered_0.99.9~123.json" % (appname))) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname) expected = json.loads('''{ "profiles": { "%s_0wnzered_0.99.9~123": { "policy_groups": [ ], "policy_vendor": "somevendor", "policy_version": 1.0, "template": "unconfined", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.99.9~123", "CLICK_DIR": "%s" } } } }''' % (appname, dbus_id, appname, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_policy_version(self): '''Test numeric policy_version''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package) expected = json.loads('''{ "profiles": { "%s_sample-app_0.3": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (package, dbus_id, package, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_policy_version_invalid(self): '''Test numeric policy_version''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 0.1 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) try: apparmor.click.transform(cm) except apparmor.click.AppArmorExceptionClickInvalidPolicyVersion as e: self.assertTrue("Invalid policy version" in str(e)) return except Exception: raise raise Exception("Should have failed on invalid policy_version") def test_parse_security_manifest_policy_version_high(self): '''Test policy_version high''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3", framework='ubuntu-sdk-13.10') security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.1 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) try: apparmor.click.transform(cm) except apparmor.click.AppArmorExceptionClickInvalidPolicyVersion as e: self.assertTrue("Invalid policy version" in str(e)) return except Exception: raise raise Exception("Should have failed on invalid policy_version") def test_parse_security_manifest_policy_version_low(self): '''Test policy_version low''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3", framework='ubuntu-sdk-14.04') security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) try: apparmor.click.transform(cm) except apparmor.click.AppArmorExceptionClickInvalidPolicyVersion as e: self.assertTrue("Invalid policy version" in str(e)) return except Exception: raise raise Exception("Should have failed on invalid policy_version") def test_parse_security_manifest_policy_version_without_decimal(self): '''Test numeric policy_version without decimal (LP: # 1214618)''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package) expected = json.loads('''{ "profiles": { "%s_sample-app_0.3": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (package, dbus_id, package, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) # The security policy is supposed to have a JSON Number. The click hook # currently will convert "1.0" to 1.0 for easyprof. Test for that. def test_parse_security_manifest_policy_version_as_string(self): '''Test numeric policy_version as string''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": "1.0" }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package) expected = json.loads('''{ "profiles": { "%s_sample-app_0.3": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (package, dbus_id, package, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) # The security policy is supposed to have a JSON Number def test_parse_security_manifest_policy_version_is_number(self): '''Test numeric policy_version requires a number''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3") v = "abc" security_json = '''{ "policy_groups": [ "networking" ], "policy_version": "%s" }''' % v c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) try: apparmor.click.transform(cm) except ValueError: return except Exception: raise raise Exception("version %s should be invalid" % (v)) def test_parse_security_manifest_framework_default(self): '''Test manifest framework (default)''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3", framework='default') security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package) expected = json.loads('''{ "profiles": { "%s_sample-app_0.3": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (package, dbus_id, package, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_framework_1310(self): '''Test manifest framework (13.10)''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3", framework='ubuntu-sdk-13.10') security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package) expected = json.loads('''{ "profiles": { "%s_sample-app_0.3": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (package, dbus_id, package, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_framework_1404(self): '''Test manifest framework (14.04)''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3", framework='ubuntu-sdk-14.04') security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.1 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package) expected = json.loads('''{ "profiles": { "%s_sample-app_0.3": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.1, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (package, dbus_id, package, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_framework_1404_subframework(self): '''Test manifest framework 14.04 (subframeworks)''' for sub in ['-dev', '-dev1', '-html-dev1', '-html', '-papi-dev2', '-papi', '-qml-dev3', 'qml']: c = self.clickstate package = "com.ubuntu.developer.username.yourapp%s" % sub c.add_package(package, "0.3", framework='ubuntu-sdk-14.04%s' % sub) security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.1 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package) expected = json.loads('''{ "profiles": { "%s_sample-app_0.3": { "policy_groups": [ "networking" ], "policy_vendor": "ubuntu", "policy_version": 1.1, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.3", "CLICK_DIR": "%s" } } } }''' % (package, dbus_id, package, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_framework_1404_subframework_low(self): '''Test manifest framework 14.04 (subframework policy version low)''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3", framework='ubuntu-sdk-14.04-html') security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) try: apparmor.click.transform(cm) except apparmor.click.AppArmorExceptionClickInvalidPolicyVersion as e: self.assertTrue("Invalid policy version" in str(e)) return except Exception: raise raise Exception("Should have failed on invalid policy_version") def test_parse_security_manifest_framework_nonexistent(self): '''Test framework (nonexistent)''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" c.add_package(package, "0.3", framework='nonexistent') security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) try: cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_sample-app_0.3.json" % package)) apparmor.click.transform(cm) except apparmor.click.AppArmorExceptionClickFrameworkNotFound as e: self.assertTrue("Unknown framework" in str(e)) return except Exception: raise raise Exception("Should have failed on nonexistent framework") def test_parse_security_manifest_abstractions(self): '''Test we pass along abstractions to easyprof''' c = self.clickstate appname = "com.ubuntu.developer.trusteddev.terminal" c.add_package(appname, "0.99.9~123") security_json = '''{ "template": "ubuntu-sdk", "policy_groups": [ ], "policy_version": 1.0, "abstractions": [ "gnupg", "ssl_keys" ] }''' c.add_app('0wnzered', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_0wnzered_0.99.9~123.json" % (appname))) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname) expected = json.loads('''{ "profiles": { "%s_0wnzered_0.99.9~123": { "abstractions": [ "gnupg", "ssl_keys" ], "policy_groups": [ ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.99.9~123", "CLICK_DIR": "%s" } } } }''' % (appname, dbus_id, appname, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_read_paths(self): '''Test we pass along read_paths to easyprof''' c = self.clickstate appname = "com.ubuntu.developer.trusteddev.terminal" c.add_package(appname, "0.99.9~123") security_json = '''{ "template": "ubuntu-sdk", "policy_groups": [ ], "policy_version": 1.0, "read_path": [ "/tmp/foo_r", "/tmp/bar_r/" ] }''' c.add_app('0wnzered', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_0wnzered_0.99.9~123.json" % (appname))) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname) expected = json.loads('''{ "profiles": { "%s_0wnzered_0.99.9~123": { "policy_groups": [ ], "policy_vendor": "ubuntu", "policy_version": 1.0, "read_path": [ "/tmp/foo_r", "/tmp/bar_r/" ], "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.99.9~123", "CLICK_DIR": "%s" } } } }''' % (appname, dbus_id, appname, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_write_paths(self): '''Test we pass along write_paths to easyprof''' c = self.clickstate appname = "com.ubuntu.developer.trusteddev.terminal" c.add_package(appname, "0.99.9~123") security_json = '''{ "template": "ubuntu-sdk", "policy_groups": [ ], "policy_version": 1.0, "write_path": [ "/tmp/foo_w", "/tmp/bar_w/" ] }''' c.add_app('0wnzered', manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s_0wnzered_0.99.9~123.json" % (appname))) easyprof_manifest = apparmor.click.transform(cm) dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname) expected = json.loads('''{ "profiles": { "%s_0wnzered_0.99.9~123": { "policy_groups": [ ], "policy_vendor": "ubuntu", "policy_version": 1.0, "write_path": [ "/tmp/foo_w", "/tmp/bar_w/" ], "template": "ubuntu-sdk", "template_variables": { "APP_ID_DBUS": "%s", "APP_PKGNAME": "%s", "APP_VERSION": "0.99.9~123", "CLICK_DIR": "%s" } } } }''' % (appname, dbus_id, appname, c.packages_tree)) self.assertEquals(expected, easyprof_manifest, "Expected to get %s, got %s" % (expected, easyprof_manifest)) def test_parse_security_manifest_bad_symlink(self): '''Test being given a symlink and parsing the manifests''' c = self.clickstate c.add_package("com.ubuntu.developer.username.myapp", "0.1") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json" fn = os.path.join(c.click_dir, n) shutil.move(os.path.dirname(c.click_pkgdir), os.path.dirname(c.click_pkgdir) + '.new') try: apparmor.click.ClickManifest(fn) except apparmor.click.AppArmorException as e: self.assertTrue("from symlink" in str(e)) return except Exception: raise raise Exception("Should have not found click manifest from symlink") def test_get_click_dir(self): '''Test get_click_dir()''' c = self.clickstate c.add_package("com.ubuntu.developer.username.myapp", "0.1") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json" fn = os.path.join(c.click_dir, n) (package, app, version) = apparmor.click.parse_manifest_name(n) (m, pkg_dir) = apparmor.click.get_package_manifest(fn, package) click_dir = apparmor.click.get_click_dir(pkg_dir, package, version) self.assertTrue(os.path.basename(click_dir) == os.path.basename(c.packages_tree), "'%s' != " % os.path.basename(click_dir) + "'%s'" % os.path.basename(c.packages_tree)) def test_get_click_dir_bad_version(self): '''Test get_click_dir() with bad version''' c = self.clickstate c.add_package("com.ubuntu.developer.username.myapp", "0.1") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json" fn = os.path.join(c.click_dir, n) (package, app, version) = apparmor.click.parse_manifest_name(n) (m, pkg_dir) = apparmor.click.get_package_manifest(fn, package) click_dir = apparmor.click.get_click_dir(pkg_dir, package, version) self.assertTrue(os.path.basename(click_dir) == os.path.basename(c.packages_tree), "'%s' != " % os.path.basename(click_dir) + "'%s'" % os.path.basename(c.packages_tree)) try: apparmor.click.get_click_dir(pkg_dir, package, "0.2") except apparmor.click.AppArmorException as e: self.assertTrue("package version dir" in str(e)) return except Exception: raise raise Exception("Should not have found bad version '0.2'") def test_get_click_dir_bad_package(self): '''Test get_click_dir() with bad package''' c = self.clickstate c.add_package("com.ubuntu.developer.username.myapp", "0.1") security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app('sample-app', manifest=security_json) n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json" fn = os.path.join(c.click_dir, n) (package, app, version) = apparmor.click.parse_manifest_name(n) (m, pkg_dir) = apparmor.click.get_package_manifest(fn, package) click_dir = apparmor.click.get_click_dir(pkg_dir, package, version) self.assertTrue(os.path.basename(click_dir) == os.path.basename(c.packages_tree), "'%s' != " % os.path.basename(click_dir) + "'%s'" % os.path.basename(c.packages_tree)) try: apparmor.click.get_click_dir(pkg_dir, package + "-bad", version) except apparmor.click.AppArmorException as e: self.assertTrue("package dir" in str(e)) return except Exception: raise raise Exception("Should not have found bad version '%s'" % package + "-bad") def test_to_profiles(self): '''Test to_profiles()''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" appname = "sample-app" version = "0.3" c.add_package(package, version) security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app(appname, manifest=security_json) full_name = "%s_%s_%s" % (package, appname, version) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s.json" % full_name)) easyprof_manifest = apparmor.click.transform(cm) files = click.to_profiles(easyprof_manifest, self.tmpdir) self.assertTrue(len(files) == 1, "to_profiles didn't return one profile:\n%s" % files) self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" % files[0]) expected_fn = "click_%s" % (full_name) fn = os.path.basename(files[0]) self.assertTrue(fn == expected_fn, "'%s' != '%s'" % (fn, expected_fn)) def test_to_profiles_existing_profile_same(self): '''Test to_profiles() with existing profile that is the same''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" appname = "sample-app" version = "0.3" c.add_package(package, version) security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app(appname, manifest=security_json) full_name = "%s_%s_%s" % (package, appname, version) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s.json" % full_name)) easyprof_manifest = apparmor.click.transform(cm) files = click.to_profiles(easyprof_manifest, self.tmpdir) self.assertTrue(len(files) == 1, "to_profiles didn't return one profile:\n%s" % files) self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" % files[0]) # Test that we don't change the time stamp of the original file if # there are no changes mtime = os.stat(files[0]).st_mtime time.sleep(1) files = click.to_profiles(easyprof_manifest, self.tmpdir) self.assertTrue(len(files) == 1, "to_profiles didn't return one profile:\n%s" % files) self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" % files[0]) cur_mtime = os.stat(files[0]).st_mtime self.assertTrue(mtime == cur_mtime, 'mtime is different') def test_to_profiles_existing_profile_different(self): '''Test to_profiles() with existing profile that is different''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" appname = "sample-app" version = "0.3" c.add_package(package, version) security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app(appname, manifest=security_json) full_name = "%s_%s_%s" % (package, appname, version) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s.json" % full_name)) easyprof_manifest = apparmor.click.transform(cm) files = click.to_profiles(easyprof_manifest, self.tmpdir) self.assertTrue(len(files) == 1, "to_profiles didn't return one profile:\n%s" % files) self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" % files[0]) # Test that we do change the time stamp of the original file if # there are changes mtime = os.stat(files[0]).st_mtime time.sleep(1) c2 = self.clickstate shutil.rmtree(c2.click_pkgdir) os.unlink(os.path.join(c2.click_dir, "%s.json" % full_name)) c2.add_package(package, version) security_json = '''{ "policy_groups": [ ], "policy_version": 1.0 }''' c2.add_app(appname, manifest=security_json) cm = apparmor.click.ClickManifest(os.path.join(c2.click_dir, "%s.json" % full_name)) easyprof_manifest = apparmor.click.transform(cm) files = click.to_profiles(easyprof_manifest, self.tmpdir) self.assertTrue(len(files) == 1, "to_profiles didn't return one profile:\n%s" % files) expected_fn = "click_%s" % (full_name) fn = os.path.basename(files[0]) self.assertTrue(fn == expected_fn, "'%s' != '%s'" % (fn, expected_fn)) cur_mtime = os.stat(files[0]).st_mtime self.assertTrue(mtime != cur_mtime, 'mtime are the same') def test_to_profiles_nonexistent_dir(self): '''Test to_profiles() with non-existent directory''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" appname = "sample-app" version = "0.3" c.add_package(package, version) security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app(appname, manifest=security_json) full_name = "%s_%s_%s" % (package, appname, version) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s.json" % full_name)) easyprof_manifest = apparmor.click.transform(cm) didnt_exist_dir = os.path.join(self.tmpdir, "didnt_exist") files = [] files = click.to_profiles(easyprof_manifest, didnt_exist_dir) self.assertTrue(len(files) == 1, "to_profiles didn't return one profile:\n%s" % files) self.assertTrue(os.path.isdir(didnt_exist_dir), "Could not find " + "%s" % didnt_exist_dir) def test_to_profiles_with_include(self): '''Test to_profiles() with include file''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" appname = "sample-app" version = "0.3" c.add_package(package, version) security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app(appname, manifest=security_json) full_name = "%s_%s_%s" % (package, appname, version) include = os.path.join(self.tmpdir, "test-inject.include") with open(include, "w+") as f: f.write(''' / r,\n''') f.close() cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s.json" % full_name)) easyprof_manifest = apparmor.click.transform(cm) files = click.to_profiles(easyprof_manifest, self.tmpdir, include) self.assertTrue(len(files) == 1, "to_profiles didn't return one profile:\n%s" % files) self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" % files[0]) expected_fn = "click_%s" % (full_name) fn = os.path.basename(files[0]) self.assertTrue(fn == expected_fn, "'%s' != '%s'" % (fn, expected_fn)) with open(files[0], 'r') as f: contents = f.read() f.close() for s in ['# injected via click hook', 'test-inject.include']: self.assertTrue(s in contents, "Could not find '%s'" % s + "in:\n%s" % contents) def test_to_profiles_without_include(self): '''Test to_profiles() without include file''' c = self.clickstate package = "com.ubuntu.developer.username.yourapp" appname = "sample-app" version = "0.3" c.add_package(package, version) security_json = '''{ "policy_groups": [ "networking" ], "policy_version": 1.0 }''' c.add_app(appname, manifest=security_json) full_name = "%s_%s_%s" % (package, appname, version) cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, "%s.json" % full_name)) easyprof_manifest = apparmor.click.transform(cm) files = click.to_profiles(easyprof_manifest, self.tmpdir, include=None) self.assertTrue(len(files) == 1, "to_profiles didn't return one profile:\n%s" % files) self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" % files[0]) expected_fn = "click_%s" % (full_name) fn = os.path.basename(files[0]) self.assertTrue(fn == expected_fn, "'%s' != '%s'" % (fn, expected_fn)) with open(files[0], 'r') as f: contents = f.read() f.close() for s in ['# injected via click hook', 'test-inject.include']: self.assertFalse(s in contents, "Found '%s' in:\n%s" % ( s, contents)) def test_easyprof_profile_methods(self): '''Test EasyprofProfile methods''' newp = apparmor.click.EasyprofProfile("foo") s = "test-string" self.assertTrue(newp.template is None) newp.template = s self.assertTrue(newp.template == s) self.assertTrue(newp.policyvendor is None) newp.policyvendor = s self.assertTrue(newp.policyvendor == s) self.assertTrue(newp.policyversion is None) newp.policyversion = 1.0 self.assertTrue(newp.policyversion == 1.0) k = "test-key" self.assertTrue(k not in newp.profile['template_variables']) newp.add_variable(k, s) self.assertTrue(k in newp.profile['template_variables']) self.assertTrue(newp.profile['template_variables'][k] == s) e = "test-entry" self.assertTrue(e not in newp.profile['policy_groups']) newp.add_policygroup(e) self.assertTrue(e in newp.profile['policy_groups']) self.assertTrue('abstractions' not in newp.profile) newp.add_abstraction(e) self.assertTrue(e in newp.profile['abstractions']) self.assertTrue('read_path' not in newp.profile) newp.add_read_path(e) self.assertTrue(e in newp.profile['read_path']) self.assertTrue('write_path' not in newp.profile) newp.add_write_path(e) self.assertTrue(e in newp.profile['write_path']) def test_parse_manifest_name(self): '''Test parse_manifest_name()''' package = "com.ubuntu.developer.username.yourapp" appname = "sample-app" version = "0.3" manifest_name = "%s_%s_%s.json" % (package, appname, version) (p, a, v) = apparmor.click.parse_manifest_name(manifest_name) self.assertTrue(p == package, "'%s' != '%s'" % (p, package)) self.assertTrue(a == appname, "'%s' != '%s'" % (a, appname)) self.assertTrue(v == version, "'%s' != '%s'" % (v, version)) def test_parse_manifest_name_bad_no_json(self): '''Test parse_manifest_name() without .json''' package = "com.ubuntu.developer.username.yourapp" appname = "sample-app" version = "0.3" manifest_name = "%s_%s_%s" % (package, appname, version) try: apparmor.click.parse_manifest_name(manifest_name) except apparmor.click.AppArmorException: return except Exception: raise raise Exception("'%s' should be invalid" % (manifest_name)) def test_parse_manifest_name_bad_name(self): '''Test parse_manifest_name() bad name''' package = "com.ubuntu.developer.username.yourapp" manifest_name = "%s.json" % package try: apparmor.click.parse_manifest_name(manifest_name) except apparmor.click.AppArmorException: return except Exception: raise raise Exception("'%s' should be invalid" % (manifest_name)) def test_get_framework_base_version_subframework(self): '''Test get_framework_base_version() (subframework)''' for k in click.framework_transforms.keys(): if k == 'ubuntu-sdk-13.10': # ubuntu-sdk-13.10 doesn't have # subframeworks continue for sub in ['-dev', '-dev1', '-html-dev1', '-html', '-papi-dev2', '-papi', '-qml-dev3', 'qml']: framework = "%s%s" % (k, sub) framework_base = click.get_framework_base_version(framework) self.assertTrue(framework_base == k, "%s != %s" % (framework_base, k)) def test_get_framework_base_version_1310(self): '''Test get_framework_base_version() (13.10)''' framework = 'ubuntu-sdk-13.10' framework_base = click.get_framework_base_version(framework) self.assertTrue(framework_base == framework, "%s != %s" % (framework_base, framework)) def test_get_framework_base_version_nonexistent(self): '''Test get_framework_base_version() (nonexistent)''' framework = 'nonexistsent' try: click.get_framework_base_version(framework) except apparmor.click.AppArmorExceptionClickFrameworkNotFound as e: self.assertTrue("Unknown framework" in str(e)) return except Exception: raise raise Exception("Should have failed on nonexistent framework") def test_get_policy_version_for_framework(self): '''Test get_policy_version_for_framework() (subframework)''' for k in click.framework_transforms.keys(): if k == 'ubuntu-sdk-13.10': # ubuntu-sdk-13.10 doesn't have # subframeworks continue for sub in ['-dev', '-dev1', '-html-dev1', '-html', '-papi-dev2', '-papi', '-qml-dev3', 'qml']: framework = "%s%s" % (k, sub) policy_version = click.get_policy_version_for_framework( framework) recommended = click.framework_transforms[k][1] self.assertTrue(policy_version == recommended, "%s != %s" % (policy_version, recommended)) def test_get_policy_version_for_framework_1310(self): '''Test get_policy_version_for_framework() (13.10)''' framework = 'ubuntu-sdk-13.10' policy_version = click.get_policy_version_for_framework(framework) recommended = click.framework_transforms[framework][1] self.assertTrue(policy_version == recommended, "%s != %s" % (policy_version, recommended)) def test_get_policy_version_for_framework_nonexistent(self): '''Test get_policy_version_for_framework() (nonexistent)''' framework = 'nonexistsent' try: click.get_policy_version_for_framework(framework) except apparmor.click.AppArmorExceptionClickFrameworkNotFound as e: self.assertTrue("Unknown framework" in str(e)) return except Exception: raise raise Exception("Should have failed on nonexistent framework") class AppArmorManifestSynchronization(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp(prefix="aa-manifest-consistency-") self.clickstate = ClickState(self.tmpdir) self.maxDiff = None def tearDown(self): if os.path.exists(self.tmpdir): shutil.rmtree(self.tmpdir) def test_two_equal_directories(self): '''Test two equal directories''' c = self.clickstate clicks = ["alpha_beta_gamma", "click_click_version", "wat_no-really_wat"] for cname in clicks: with open(os.path.join(c.click_dir, '%s.json' % (cname)), 'w+') as f: f.write('invalid json here') with open(os.path.join(c.profiles_dir, 'click_%s' % (cname)), 'w+') as f: f.write('profile %s { }' % (cname)) expected = [] result = click.get_missing_profiles(c.click_dir, c.profiles_dir) self.assertEquals(expected, result, "Expected to get no profiles, got %s" % (result)) result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir) self.assertEquals(expected, result, "Expected to get no click hooks, got %s" % (result)) def test_two_empty_directories_for_profiles(self): '''Test two empty directories returns no new needed profiles''' c = self.clickstate expected = [] result = click.get_missing_profiles(c.click_dir, c.profiles_dir) self.assertEquals(expected, result, "Expected to get no profiles, got %s" % (result)) def test_two_empty_directories_for_clicks(self): '''Test two empty directories returns no removed click hooks''' c = self.clickstate expected = [] result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir) self.assertEquals(expected, result, "Expected to get no clickhooks, got %s" % (result)) def test_versus_empty_profiles_directory(self): '''Test against empty profiles directory''' c = self.clickstate clicks = ["alpha_beta_gamma.json", "click_click_version.json", "wat_no-really_wat.json"] for cname in clicks: with open(os.path.join(c.click_dir, cname), 'w+') as f: f.write('invalid json here') expected = set(clicks) result = set(click.get_missing_profiles(c.click_dir, c.profiles_dir)) self.assertEquals(expected, result, "Expected to get %s profiles, got %s" % (expected, result)) def test_versus_empty_clicks_directory(self): '''Test against empty clicks directory''' c = self.clickstate clicks = ["alpha_beta_gamma", "click_click_version", "wat_no-really_wat"] for cname in clicks: with open(os.path.join(c.profiles_dir, 'click_%s' % (cname)), 'w+') as f: f.write('profile %s { }' % (cname)) expected = set(["%s%s" % (AppName._CLICK_PREFIX, x) for x in clicks]) result = set(click.get_missing_clickhooks(c.click_dir, c.profiles_dir)) self.assertEquals(expected, result, "Expected to get %s hooks, got %s" % (expected, result)) def test_two_unequal_directories(self): '''Test two equal directories''' c = self.clickstate expected_clicks = ['missing_click_profile.json', 'another-missing_click_profile.json'] expected_profiles = ['%sremoved_click_package' % (AppName._CLICK_PREFIX)] clicks = ["alpha_beta_gamma.json", "click_click_version.json", "wat_no-really_wat.json"] profiles = ["%s%s" % (AppName._CLICK_PREFIX, x[:-len('.json')]) for x in clicks] clicks.extend(expected_clicks) profiles.extend(expected_profiles) for cname in clicks: with open(os.path.join(c.click_dir, cname), 'w+') as f: f.write('invalid json here') for pname in profiles: with open(os.path.join(c.profiles_dir, pname), 'w+') as f: f.write('profile %s { }' % (pname)) result = set(click.get_missing_profiles(c.click_dir, c.profiles_dir)) self.assertEquals(set(expected_clicks), result, "Expected to get '%s' missing profiles, got %s" % (set(expected_clicks), result)) result = set(click.get_missing_clickhooks(c.click_dir, c.profiles_dir)) self.assertEquals(set(expected_profiles), result, "Expected to get '%s' missing click hooks, got %s" % (set(expected_profiles), result)) def test_two_unequal_directories_bad_profile_filename(self): '''Test two unequal directories (bad profile_filename)''' c = self.clickstate clicks = ["alpha_beta_gamma", "click_click_version", "wat_no-really_wat"] for cname in clicks: with open(os.path.join(c.click_dir, '%s.json' % (cname)), 'w+') as f: f.write('invalid json here') with open(os.path.join(c.profiles_dir, 'badformat_%s' % (cname)), 'w+') as f: f.write('profile %s { }' % (cname)) expected = [] result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir) self.assertEquals(expected, result, "Expected to get no click hooks, got %s" % (result)) def test_two_equal_directories_new_symlink(self): '''Test two equal directories with new symlink (LP: #1291549)''' c = self.clickstate clicks = ["alpha_beta_gamma", "click_click_version", "wat_no-really_wat"] for cname in clicks: with open(os.path.join(c.click_dir, '%s.json' % (cname)), 'w+') as f: f.write('invalid json here') with open(os.path.join(c.profiles_dir, 'click_%s' % (cname)), 'w+') as f: f.write('profile %s { }' % (cname)) # Click hooks not updated yet, so everything should be the same expected = [] result = click.get_missing_profiles(c.click_dir, c.profiles_dir) self.assertEquals(expected, result, "Expected to get no profiles, got %s" % (result)) result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir) self.assertEquals(expected, result, "Expected to get no click hooks, got %s" % (result)) time.sleep(1) clicks = ["alpha_beta_gamma", "click_click_version"] for cname in clicks: with open(os.path.join(c.click_dir, '%s.json' % (cname)), 'w+') as f: f.write('invalid json here') expected = [] result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir) self.assertEquals(expected, result, "Expected to get no click hooks, got %s" % (result)) expected = len(clicks) result = click.get_missing_profiles(c.click_dir, c.profiles_dir) self.assertEquals(expected, len(result), "Expected to get %d profiles, got %s:\n" % (expected, len(result))) class AppArmorPolicyModificationTests(unittest.TestCase): def setUp(self): self.remove_profiles = [] def tearDown(self): if "ADTTMP" in os.environ: # skip unload if running under autopkgtest return apparmor_remove = "/sys/kernel/security/apparmor/.remove" for p in self.remove_profiles: with open(apparmor_remove, 'w') as f: f.write(p) def test_load_simple_policy(self): '''Test basic load policy function''' policy = "profile invalid_policy_dont_use { }" with tempfile.NamedTemporaryFile(prefix="aa-clicktest-", delete=False) as profile: profile.write(bytes(policy, 'utf-8')) name = profile.name try: rc, output = click.load_profile(name) except: raise self.remove_profiles.append('invalid_policy_dont_use') os.remove(name) def test_load_simple_policies(self): '''Test basic load policies function''' policy = "profile invalid_policy_dont_use { }" with tempfile.NamedTemporaryFile(prefix="aa-clicktest-", delete=False) as profile: profile.write(bytes(policy, 'utf-8')) name = profile.name try: rc, output = click.load_profiles([name]) except: raise self.remove_profiles.append('invalid_policy_dont_use') os.remove(name) def test_load_no_policies(self): '''Test load zero policies function''' try: rc, output = click.load_profiles([]) except: raise def test_unload_simple_policy(self): '''Test unload simple policy''' policy = "profile invalid_policy_dont_use { }" with tempfile.NamedTemporaryFile(prefix="aa-clicktest-", delete=False) as profile: profile.write(bytes(policy, 'utf-8')) name = profile.name try: rc, output = click.load_profile(name) except: raise click.unload_profile('invalid_policy_dont_use') os.remove(name) def test_unload_simple_policies(self): '''Test unload simple policies''' policies = ['more_invalid_policy_%s' % (suffix) for suffix in ['blart', 'blort', 'blat']] names = [] for p in policies: policy = "profile %s { }" % (p) with tempfile.NamedTemporaryFile(prefix="aa-clicktest-", delete=False) as profile: profile.write(bytes(policy, 'utf-8')) names.append(profile.name) try: rc, output = click.load_profiles(names) except: raise click.unload_profiles(policies) for name in names: os.remove(name) def test_unload_nonexistent_policy(self): '''Test unload nonexistent policy''' profile = 'invalid_policy_does_not_exist_dont_use' click.unload_profile(profile) def test_apparmor_available(self): '''Test apparmor_available()''' try: click.apparmor_available(parser="./aa-clickhook", apparmor_dirs=['./']) except click.AppArmorException: raise Exception("apparmor_available() should have passed") def test_apparmor_not_available(self): '''Test apparmor not available''' try: click.apparmor_available(parser="/nonexistent", apparmor_dirs=['/nonexistent.dir']) except click.AppArmorException as e: self.assertTrue("Could not find '/nonexistent.dir'" in str(e)) return except Exception: raise raise Exception("apparmor_available should have failed") if __name__ == '__main__': suite = unittest.TestSuite() suite.addTest(unittest.TestLoader().loadTestsFromTestCase(T)) if os.geteuid() == 0: suite.addTest(unittest.TestLoader().loadTestsFromTestCase( AppArmorPolicyModificationTests)) else: print('Not running as root, skipping tests that load/unload ' + 'AppArmor policy', file=sys.stderr) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( AppArmorManifestSynchronization)) rc = unittest.TextTestRunner(verbosity=2).run(suite) if not rc.wasSuccessful(): sys.exit(1) click-apparmor-0.2/README0000664000000000000000000000737312307667321012050 0ustar click-apparmor -------------- click-apparmor provides the AppArmor hook for click packaging. The hook is defined in /usr/share/click/hooks/apparmor.hook. Click packaging uses a declarative json manifest to define the AppArmor policy for the package and the click AppArmor hook is responsible for taking this json manifest and converting it into an AppArmor profile to load into the kernel. This happens in several steps: 1. click calls the AppArmor hook, aa-clickhook (as defined by 'Exec' in the click hook), and determines which manifests to operate on 2. for each manifest, aa-clickhook converts the manifest into an easyprof json file 3. for each easyprof manifest, aa-clickhook uses AppArmor easyprof to generate an AppArmor profile 4. for each AppArmor profile, aa-clickhook stores the profile on the system so it is persistant across reboots 5. for each AppArmor profile, aa-clickhook loads the profile into the kernel 6. if there are any profiles defined without a corresponding json manifest, aa-clickhook will remove them from the system Important locations: * /var/lib/apparmor/clicks - location of json manifests as defined by 'Pattern' in the click hook * /var/lib/apparmor/profiles - location of translated AppArmor profiles * /var/cache/apparmor - location of cached AppArmor profiles aa-clicktool is also provided for testing. To use: 1. Copy your click manifest and click security manifest somewhere in a manner that aa-clicktool can use (note, $name_$application_$version needs to match with what is in the manifests). Eg: $ mkdir -p /tmp/debug/com.ubuntu.developer.username.myapp/0.1/apparmor \ /tmp/debug/com.ubuntu.developer.username.myapp/0.1/.click/info $ cp apparmor/myapp.json \ /tmp/debug/com.ubuntu.developer.username.myapp/0.1/apparmor/ $ cp ./manifest.json \ /tmp/debug/com.ubuntu.developer.username.myapp/0.1/.click/info/com.ubuntu.developer.username.myapp_myapp_0.1.json $ ln -s /tmp/debug/com.ubuntu.developer.username.myapp/0.1/apparmor/myapp.json \ /tmp/debug/com.ubuntu.developer.username.myapp_myapp_0.1.json 2. Run aa-clicktool on it: $ aa-clicktool -o /tmp/debug/easyprof.json \ /tmp/debug/com.ubuntu.developer.username.myapp_myapp_0.1.json 3. Generate the apparmor profile with aa-easyprof: $ aa-easyprof -m /tmp/debug/easyprof.json > /tmp/debug/profile 4. Load the profile in the usual way: $ sudo apparmor_parser -r /tmp/debug/profile Development ----------- $ sudo apt-get build-dep click-apparmor Run tests with: $ ./test-clicktool.py or: $ make test Run with policy load tests: $ sudo ./test-clicktool.py or: $ sudo make test Run with coverage: $ sudo python3 -m coverage run ./test-clicktool.py $ python3 -m coverage report --show-missing apparmor/click.py or: $ sudo make coverage $ make coverage-report In addition to the above, before committing your changes, please run: $ make check If you are going to develop click-apparmor regularly, you might want to add a bzr hook to run the testsuite before committing. Eg, add something like this to ~/.bazaar/plugins/hooks/__init__.py: #!/usr/bin/python from bzrlib.branch import Branch def run_tests_click_apparmor(local, master, old_revno, old_revid, new_revno, new_revid, seven, eight): #print local, master, old_revno, old_revid, new_revno, new_revid, seven, eight if 'click-apparmor' in master.base: import subprocess print '' rc = subprocess.call(['make', 'check']) if rc != 0: import sys sys.exit(1) Branch.hooks.install_named_hook('pre_commit', run_tests_click_apparmor, 'click-apparmor tests') click-apparmor-0.2/apparmor/0000775000000000000000000000000012310375645012776 5ustar click-apparmor-0.2/apparmor/click.py0000664000000000000000000005431412310324467014440 0ustar # ------------------------------------------------------------------ # # Copyright (C) 2013-2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ # from __future__ import with_statement from apparmor import easyprof import apparmor import ctypes import errno import json import os import re import shutil import sys import tempfile # set to True if mocking test environment mock_testenv = False # from apparmor.easyprof import AppArmorException, error # or apparmor.common ? # # IMPORTANT: see end of this file for changing various default settings # # # TODO: move this out to the common library # #from apparmor import AppArmorException class AppArmorException(Exception): '''This class represents AppArmor exceptions''' def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class AppArmorExceptionClickInvalidPolicyVersion(AppArmorException): pass class AppArmorExceptionClickFrameworkNotFound(AppArmorException): pass # # End common # # # TODO: move these out to a utilities library # apparmor_fs = "/sys/kernel/security/apparmor" # IMPORTANT: only raise an AppArmorException if AppArmor is not available, # not on other errors def apparmor_available(parser="/sbin/apparmor_parser", apparmor_dirs=None): '''Is AppArmor available for use on this system''' dirs = apparmor_dirs if dirs is None: dirs = ['/sys/module/apparmor', apparmor_fs] for d in dirs: if mock_testenv and apparmor_dirs is None: # pragma: no cover break if not os.path.isdir(d): raise AppArmorException("Could not find '%s'" % d) if not os.path.exists(parser): # pragma: no cover rc, parser = easyprof.cmd(['which', 'apparmor_parser']) if rc != 0: raise AppArmorException("Could not find apparmor_parser") def error(out, exit_code=1, do_exit=True): '''Print error message and exit''' try: # pragma: no cover sys.stderr.write("ERROR: %s\n" % (out)) except IOError: # pragma: no cover pass if do_exit: # pragma: no cover sys.exit(exit_code) def load_profile(profile, parser="/sbin/apparmor_parser", args=['-r', '--write-cache']): '''Load individual profile into the kernel''' try: apparmor_available(parser) except AppArmorException as e: # pragma: nocover raise AppArmorException("%s. Skipping load" % e) command = [parser] command.extend(args) command.append(profile) rc, output = easyprof.cmd(command) if rc != 0: # pragma: nocover raise AppArmorException("policy load failed with exit status %d: %s" % (rc, output)) return (rc, output) def load_profiles(profiles, parser="/sbin/apparmor_parser", args=['-r', '--write-cache']): '''Load list of profiles into the kernel''' # it's possible we can be passed an empty list, be forgiving if len(profiles) == 0: return (0, []) try: apparmor_available(parser) except AppArmorException as e: # pragma: no cover raise AppArmorException("%s. Skipping load" % e) command = [parser] command.extend(args) command.extend(profiles) rc, output = easyprof.cmd(command) if rc != 0: # pragma: no cover raise AppArmorException("policy load failed with errno %d: %s" % (rc, output)) return (rc, output) def _unload_profile(profile): '''Unload a profile name (not a filename) from the kernel''' apparmor_remove = os.path.join(apparmor_fs, ".remove") try: with open(apparmor_remove, 'w') as f: f.write(profile) except (OSError, IOError) as e: if e.errno != errno.ENOENT: # pragma: no cover raise def unload_profile(profile): '''Unload a profile name from the kernel''' try: apparmor_available() except AppArmorException as e: # pragma: no cover raise AppArmorException("%s. Skipping unload" % e) _unload_profile(profile) def unload_profiles(profiles): '''Unload a list of profile names from the kernel''' try: apparmor_available() except AppArmorException as e: # pragma: no cover raise AppArmorException("%s. Skipping unload" % e) for profile in profiles: _unload_profile(profile) # # End utils # def output_policy(easyp, params, count, destdir, force=False, include=None): '''Output policy''' policy = easyp.gen_policy(**params) # Inject include if it is specified if include is not None: inject_s = ''' # injected via click hook #include "%s" ''' % include policy = re.sub(r'(\s}\s+)$', '\n %s\\1' % inject_s, policy) out_fn = None if not destdir: # pragma: no cover if count: sys.stdout.write('### aa-easyprof profile #%d ###\n' % count) sys.stdout.write('%s\n' % policy) return None else: if 'profile_name' in params: out_fn = AppName(raw_name=params['profile_name']) #elif 'binary' in params: # out_fn = params['binary'] else: # pragma: no cover raise AppArmorException("Could not determine output filename") # Generate an absolute path, converting any path delimiters to '.' out_fn = os.path.join(destdir, out_fn.profile_filename) if not os.path.exists(destdir): os.mkdir(destdir) if not os.path.isdir(destdir): # pragma: no cover raise AppArmorException("'%s' is not a directory" % destdir) f, fn = tempfile.mkstemp(prefix='aa-easyprof') if not isinstance(policy, bytes): policy = policy.encode('utf-8') os.write(f, policy) os.close(f) # Only update if the contents are different policy_orig = "".encode('utf-8') if not force and os.path.exists(out_fn): with open(out_fn) as orig: policy_orig = orig.read() if not isinstance(policy_orig, bytes): policy_orig = policy_orig.encode('utf-8') if force or policy_orig != policy: shutil.move(fn, out_fn) os.chmod(out_fn, 0o644) else: os.unlink(fn) return out_fn def walk_up(path): while True: yield path newpath = os.path.dirname(path) if newpath == path: # pragma: no cover return path = newpath def get_package_manifest(hooklink, pkg): '''Follow a click hook manifest symlink and return the main package manifest location. ''' json_dir = os.path.realpath(os.path.normpath(os.path.join( os.path.dirname(hooklink), os.readlink(hooklink)))) manifest_name = "%s.manifest" % pkg # Walk up the tree to find the .click directory, then grab # info/manifest from below that for path in walk_up(json_dir): info = os.path.join(path, ".click", "info") manifest = os.path.join(info, manifest_name) if os.path.isdir(info) and os.path.exists(manifest): return (manifest, path) raise AppArmorException("Could not find click manifest " + "'%s' from symlink '%s'" % (manifest_name, hooklink)) def get_framework_base_version(framework): '''Find the base version of a click framework''' # TODO: when libclick has the API, use it instead. for k in framework_transforms.keys(): if framework.startswith(k): return k raise AppArmorExceptionClickFrameworkNotFound("Unknown framework '%s'" % framework) def get_policy_version_for_framework(framework): '''Find the policy version for a given framework''' framework_base = get_framework_base_version(framework) return framework_transforms[framework_base][1] def get_click_dir(pkg_dir, pkg, ver): '''Find click dir''' (p, last) = os.path.split(pkg_dir) if not ver == last: raise AppArmorException("package version dir '%s' " % pkg_dir + "does not match expected '%s'" % ver) (result, last) = os.path.split(p) if not pkg == last: raise AppArmorException("package dir '%s' does not match " % p + "expected '%s'" % pkg) return result def read_click_manifest(manifest): '''Read click manifest''' f = open(manifest, "r", encoding="UTF-8") j = json.load(f) for field in required_click_fields: if not field in j: raise AppArmorException("could not find required field" + "'%s' in json" % (field)) return j def read_apparmor_manifest(manifest, app): '''Read AppArmor manifest from click package''' f = open(manifest, "r", encoding="UTF-8") j = json.load(f) if (len(j.keys()) == 1) and app in j: j = j[app] # No fields are actually required, the framework transformation # should set sensible defaults if any/all are missing return j class ClickManifest(object): def __init__(self, clickhook): (package, app, version) = parse_manifest_name(os.path.basename( clickhook)) (click_manifest, pkg_dir) = get_package_manifest(clickhook, package) click_json = read_click_manifest(click_manifest) profile_json = read_apparmor_manifest(clickhook, app) #if not package == click_json['name']: # raise AppArmorException("package name '%s' in json doesn't " + # "match " % (field)) self.app_pkgname = click_json['name'] self.app_version = click_json['version'] if 'framework' in click_json: self.framework = get_framework_base_version( click_json['framework']) else: self.framework = 'default' self.profiles = dict() self.profiles[app] = profile_json # FIXME: note that at some point in the future this may get # overridden by the package manifest entry, if defined self.click_dir = get_click_dir(pkg_dir, package, version) class EasyprofManifest(object): def __init__(self): self.manifest = dict() self.manifest['profiles'] = dict() def add_profile(self, name, profile): '''Add profile''' self.manifest['profiles'][name] = profile class AppName(object): _CLICK_PREFIX = "click_" _CLICK_SUFFIX = ".json" '''The AppName is used to keep three different views of the name in sync: - click_name: ie, filenames in the hooks directory - profile_filename: ie, filenames in the profiles directory - profile_name (aka raw_name): ie, the AppArmor profile name as loaded into the kernel ''' def __init__(self, click_name=None, profile_filename=None, raw_name=None): # Verify valid click tuple name? if click_name: self.click_name = click_name elif profile_filename: self.profile_filename = profile_filename elif raw_name: self._clickname = raw_name else: raise AppArmorException("AppName object needs an initial value") @property def click_name(self): return "%s%s" % (self._clickname, self._CLICK_SUFFIX) @click_name.setter def click_name(self, click_name): if not click_name.endswith(self._CLICK_SUFFIX): raise AppArmorException("invalid click manifest name '%s'" % (click_name)) # strip the suffix off self._clickname = click_name[:-len(self._CLICK_SUFFIX)] @property def profile_filename(self): return "%s%s" % (self._CLICK_PREFIX, self._clickname) @profile_filename.setter def profile_filename(self, profile_filename): if not profile_filename.startswith(self._CLICK_PREFIX): raise AppArmorException("invalid click profile name '%s'" % (profile_filename)) # Strip the click prefix off of the string self._clickname = profile_filename[len(self._CLICK_PREFIX):] @property def profile_name(self): return "%s" % self._clickname class EasyprofProfile(object): def __init__(self, name): self.profile = dict() self.profile['template_variables'] = dict() self.profile['policy_groups'] = list() self.name = name def add_variable(self, var_name, var_value): self.profile['template_variables'][var_name] = var_value def add_policygroup(self, policygroup): self.profile['policy_groups'].append(policygroup) def add_abstraction(self, abstraction): if not 'abstractions' in self.profile: self.profile['abstractions'] = list() self.profile['abstractions'].append(abstraction) def add_read_path(self, read_path): if not 'read_path' in self.profile: self.profile['read_path'] = list() self.profile['read_path'].append(read_path) def add_write_path(self, write_path): if not 'write_path' in self.profile: self.profile['write_path'] = list() self.profile['write_path'].append(write_path) @property def template(self): if 'template' in self.profile: return self.profile['template'] return None @template.setter def template(self, template): self.profile['template'] = template @property def policyvendor(self): if 'policy_vendor' in self.profile: return self.profile['policy_vendor'] return None @policyvendor.setter def policyvendor(self, vendor): self.profile['policy_vendor'] = vendor @property def policyversion(self): if 'policy_version' in self.profile: return self.profile['policy_version'] return None @policyversion.setter def policyversion(self, version): self.profile['policy_version'] = version # Do this setup only once libnih_dbus = ctypes.cdll.LoadLibrary("libnih-dbus.so.1") nih_dbus_path = libnih_dbus.nih_dbus_path nih_dbus_path.restype = ctypes.c_char_p def dbus_path(p): '''Convert a string to a DBus-compatible object path''' s = str(nih_dbus_path(None, "", p.encode(), None), 'ascii') if len(s) > 1: s = s[1:] return s def _ubuntu_transform(click_manifest, policy_version): '''Ubuntu SDK framework transformation function''' m = EasyprofManifest() for p in click_manifest.profiles: newp = EasyprofProfile(p) newname = "%s_%s_%s" % (click_manifest.app_pkgname, p, click_manifest.app_version) profile = click_manifest.profiles[p] for default in defaults_profile: if not default in profile: profile[default] = defaults_profile[default] if 'template' in profile: newp.template = profile['template'] else: newp.template = "ubuntu-sdk" # pragma: no cover if 'policy_vendor' in profile: newp.policyvendor = profile['policy_vendor'] else: newp.policyvendor = "ubuntu" # pragma: no cover if 'policy_version' in profile and \ float(profile['policy_version']) != policy_version: raise AppArmorExceptionClickInvalidPolicyVersion( "Invalid policy version '%s' " % profile['policy_version'] + "for framework '%s'" % click_manifest.framework) else: newp.policyversion = policy_version newp.add_variable("APP_PKGNAME", click_manifest.app_pkgname) newp.add_variable("APP_VERSION", click_manifest.app_version) newp.add_variable("APP_ID_DBUS", dbus_path(newname)) newp.add_variable("CLICK_DIR", click_manifest.click_dir) if 'policy_groups' in profile: for policygroup in profile['policy_groups']: newp.add_policygroup(policygroup) if 'abstractions' in profile: for abstraction in profile['abstractions']: newp.add_abstraction(abstraction) if 'read_path' in profile: for read_path in profile['read_path']: newp.add_read_path(read_path) if 'write_path' in profile: for write_path in profile['write_path']: newp.add_write_path(write_path) m.add_profile(newname, newp.profile) return m.manifest # The Ubuntu transform will enforce a particular policy version is used for a # particular framework. def ubuntu_1310_transform(click_manifest, policy_version): '''Ubuntu 13.10 SDK framework transformation function''' return _ubuntu_transform(click_manifest, policy_version) def ubuntu_1404_transform(click_manifest, policy_version): '''Ubuntu 14.04 SDK framework transformation function''' return _ubuntu_transform(click_manifest, policy_version) def parse_manifest_name(name): '''Parse a manifest name''' (n, ext) = os.path.splitext(name) if not ext == ".json": raise AppArmorException("unable to parse manifest name %s" % (name)) out = n.split("_") if len(out) != 3: raise AppArmorException("unable to parse manifest name %s" % (name)) return tuple(out) def transform(click_manifest): '''Transform click manifest into easyprof json''' if not click_manifest.framework in framework_transforms: raise AppArmorExceptionClickFrameworkNotFound( "Unknown framework '%s'" % (click_manifest.framework)) # TODO: when we support framework ranges, will need to adjust this to pick # the highest version that is supported in framework_transforms (ie, # supported on the system) (transform_function, policy_version) = \ framework_transforms[click_manifest.framework] return transform_function(click_manifest, policy_version) # to_profiles(easyprof_manifest, directory) # - directory is the output directory to place it into # - returns a list of paths written to def to_profiles(easyprof_manifest, directory, include=None, no_verify=False): '''Convert manifest to AppArmor profile''' (easy_opts, easy_args) = apparmor.easyprof.parse_args(args=[]) profiles = apparmor.easyprof.parse_manifest(json.dumps(easyprof_manifest), easy_opts) files = [] count = 0 for (binary, options) in profiles: count += 1 try: easyp = apparmor.easyprof.AppArmorEasyProfile(binary, options) except AppArmorException as e: # pragma: no cover error(e.value) except Exception: # pragma: no cover raise params = apparmor.easyprof.gen_policy_params(binary, options) params['no_verify'] = no_verify # FIXME: easyp.gen_policy is probably preferred. f = output_policy(easyp, params, count, directory, include=include) if directory: files.append(f) return files # get_missing_profiles(hooksdir, profilesdir) # - returns a list of click links that are missing correlated profiles def get_missing_profiles(hooksdir, profilesdir): '''Find click hooks that are missing profiles''' result = [] for hook in os.listdir(hooksdir): name = AppName(click_name=hook) profile = os.path.join(profilesdir, name.profile_filename) hook_full = os.path.join(hooksdir, hook) if not os.path.exists(profile): # If profile doesn't exist, we need to generate it result.append(name.click_name) elif os.lstat(hook_full).st_mtime > os.stat(profile).st_mtime: # If the profile exists, but the hook symlink is newer, we need to # regenerate it. Click may update the symlink from time to time, so # we need to handle this (LP: #1291549) result.append(name.click_name) return result # get_missing_clickhooks(hooksdir, profilesdir) # - returns a list of profiles that are missing correlated click links def get_missing_clickhooks(hooksdir, profilesdir): '''Find profiles that have had click packages removed''' result = [] for profile in os.listdir(profilesdir): try: name = AppName(profile_filename=profile) except apparmor.click.AppArmorException: continue if not os.path.exists(os.path.join(hooksdir, name.click_name)): result.append(name.profile_filename) return result # # Defaults # FIXME: make these configurable via a configuration file and consider removing # 'default' # # These define the transformation funtions given a specific framework # definition in the ClickManifest file. This may need to grow more # complex depending on how the framework definition grows in click # packages. # # Ubuntu requires a particular policy version for each framework base version, # so allow for this. We do it here rather than in the transform functions # so it is in one place and so we can more easily report the policy version # for a given framework. # # Format is: # framework_transforms = { # "": (, ), # } # Note: sometimes the framework_base_version is the same as the framework name, # but this is not always the case. Use framework_base_version instead so that # 'subframeworks' can more easily be assigned a particular policy version. Eg # "ubuntu-sdk-14.04-html" and "ubuntu-sdk-14.04-papi" should all get the same # policy version and they share the "ubuntu-sdk-14.04" framework base version. framework_transforms = { "default": (ubuntu_1310_transform, 1.0), "ubuntu-sdk-13.10": (ubuntu_1310_transform, 1.0), "ubuntu-sdk-14.04": (ubuntu_1404_transform, 1.1), } # The Ubuntu tranformation function takes a click manifest json # structure as input and returns an aa-easyprof json manifest defaults_profile = { 'template': 'ubuntu-sdk', 'policy_vendor': 'ubuntu', 'policy_groups': list(), } # Required fields in click manifest required_click_fields = ["name", "version"] # # End defaults # click-apparmor-0.2/apparmor/__init__.py0000664000000000000000000000014612307667321015111 0ustar # http://legacy.python.org/dev/peps/pep-0420/ __import__('pkg_resources').declare_namespace(__name__) click-apparmor-0.2/setup.py0000664000000000000000000000155112307732477012677 0ustar #! /usr/bin/env python3 from setuptools import setup import re # We probably ought to use debian.changelog, but let's avoid that # dependency for now. changelog_heading = re.compile(r"\w[-+0-9a-z.]* \(([^\(\) \t]+)\)") with open("debian/changelog") as changelog: line = changelog.readline() match = changelog_heading.match(line) if match is None: raise ValueError("Failed to parse first line of debian/changelog: " "'%s'" % line) version = match.group(1) setup( name="apparmor.click", namespace_packages=['apparmor'], version=version, description="AppArmor click utility", author="Steve Beattie", author_email="steve.beattie@canonical.com", license="GNU GPL-2", scripts=['aa-clicktool', 'aa-clickhook', 'aa-clickquery'], # packages=['apparmor'], py_modules=['apparmor.click'], )