SnappyConfinement

Revision 28 as of 2015-05-11 19:20:36

Clear message

Introduction

Snappy confinement is an evolution of the security model for Ubuntu Touch. The basic concepts for confined applications and the AppStore model pertain to snappy applications as well. In short, applications are confined by default through the use of various technologies and this is achieved through a simple template-based system where policy is extended through the use of policy groups.

It will be most helpful if you are familiar with:

Implementation summary

Snappy uses a simple packaging format that is an evolution of click packaging. Snappy packages use a declarative yaml syntax and defaults to using default AppArmor template and the "network-client" policy group. Apps will be able to customize the default behavior in a number of ways via the yaml syntax.

Native snap format

Security policy

Security policy in snappy will be using a number of technologies including apparmor, seccomp, cgroups, etc. Internally, the security policy is generated based on what is in the snappy packaging yaml. Applications are tracked by the system by using the concept of an ApplicationId. The APP_ID is the composition of the package name, the service/binary name and package version. The APP_ID takes the form of <pkgname>_<appname>_<version>. For example, if this is in packaging yaml:

name: foo
version: 0.1
...
services:
  - name: bar
    start: bin/bar

Then the APP_ID for the 'bar' service is foo_bar_0.1. Notice, this is '<name>/<basename of binary/service name>'). The APP_ID is used throughout the system including in the enforcement of security policy.

A launcher will be implemented that will:

  • setup cgroups (device, network (future), memory (future)*)
  • setup iptables (for internal app access)
  • drop privileges to uid of service (future)
  • setup seccomp
  • setup various environment variables (eg, SNAP_APP_PATH, SNAP_APP_DATA_PATH, SNAP_APP_USER_DATA_PATH, SNAP_APP_TMPDIR, SNAP_OLD_PWD, SNAP_APP_ARCH and HOME).
  • chdir to SNAP_APP_PATH (the install directory)
  • exec the app under apparmor profile under default nice value

The launcher will be used when lauching both services and using CLI binaries. The default policy allows ELF executables, python, perl and shell (with selected corresponding utilities from /bin and /usr/bin), disallows capabilities(7) and enforces application isolation as per the snappy FHS.

AppArmor

Upon snap package install, the yaml is examined and apparmor profiles are generated for each service and binary and have names based on the APP_ID. apparmor profiles are template based and may be extended through policy groups, which are expressed in the yaml as caps.

  • If unspecified in the packaging yaml, snappy will choose the default template and network-client policy group (this may change as snappy involves)

  • Apps may choose to specify an alternate confinement for binaries and services by specifying caps and/or security-template in the yaml. Eg:

    ...
    services:
      - name: bar
        start: bin/bar
        caps:
          - network-client
      - name: baz
        start: bin/baz
        security-template: nondefault
        caps:
          - network-client
          - something-else

apparmor policy templates and policy groups are shipped on the snappy system and also via framework snaps. Apps that depend on a particular framework may reference the framework snap's policy templates and/or groups.

Apps may also optionally specify security-override to specify high level overrides to use when security-template’ and caps’ are not sufficient. The path specified by security-override is a custom security manifest. Use of this will trigger manual review in the Ubuntu store. Eg:

services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-overrides:
      apparmor: path/to/security override

Furthermore, apps may also specify security-policy instead of using the template based policy to use hand-crafted apparmor policy. Use of this will trigger a manual review in the Ubuntu store. Eg:

services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-policy:
      apparmor: meta/apparmor.profile

Seccomp

Like with apparmor (see above), on snap package install the yaml is examined and seccomp filter lists are generated for each service and binary. These seccomp filter lists are template based and may be extended through policy groups, which are expressed in the yaml as caps. For simplicity, the seccomp policy groups and templates will have the same names as the corresponding apparmor policy groups and templates such that:

  • If unspecified in the packaging yaml, snappy will choose the default filter template and network-client policy group (this may change as snappy involves)

  • Apps may choose to specify an alternate confinement for binaries and services by specifying caps and/or security-template

Apps may also optionally specify security-override to specify high level overrides to use when security-template and caps are not sufficient. The path specified by security-override is a custom seccomp filter manifest (this uses yaml-syntax and can be used to specify security-template, caps, syscalls, policy-vendor and policy-version). Use of this will trigger manual review in the Ubuntu store. Eg:

services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-overrides:
      seccomp: path/to/filter override

For example, if path/to/filter override that contains (yaml):

policy-vendor: ubuntu-core
policy-version: 15.04
security-template: default
caps:
  - network-client
syscalls:
  - clock_adjtime

then upon install a seccomp filter policy using the default seccomp template and the network-client cap will be generated with the clock_adjtime syscall added.

Furthermore, apps may also specify security-policy instead of using the template based policy to use hand-crafted seccomp filters. Use of this will trigger a manual review in the Ubuntu store. Eg:

services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-policy:
      seccomp: meta/seccomp-filter.list

The format of the seccomp filter is simply a list of syscalls specified one per line.

Cgroups

A device cgroup is setup by the launcher which adds several devices automatically, such as /dev/null, /dev/urandom, etc. Other devices may be added to the app's cgroup via the snappy hw-assign command. In the future a net cgroup will be added to tag network traffic and possibly a memory cgroup will be added.

Privilege dropping

TBD: a mechanism will be provided for apps to drop privileges

Transitional click format (old)

While snappy build uses only the package.yaml, on 15.04 under the hood a click compatibility manifest and click security json files for AppArmor are created (seccomp does not use click compatibility). This will be removed in future releases. This section describes the transition implementation.

Upon package install:

  • the AppArmor policy is generated (running any click hooks, such as aa-clickhook or aa-profile-hook)

apparmor hook

Under the hood, the security manifest is used to describe the apparmor confinement of the app. Most apps do not need to specify anything for confinement and snappy will create a security manifest like the following:

{
  "policy_vendor": "ubuntu-core",
  "policy_version": 15.04,
  "template": "default",
  "policy_groups": [
    "network-client"
  ]
}

The defaults provided may change as the confinement needs evolve. The template policy is found in /usr/share/apparmor/easyprof/templates/ubuntu-core/15.04/default and the network-client policy is found in /usr/share/apparmor/easyprof/policy-groups/ubuntu-core/15.04/network-client.

You may provide your own security json to extend security policy in various ways by using the security-override mechanism. Specifying security-override may trigger a manual review in the store, and is not needed for normal Snappy apps, but instead is provided for trusted snappy applications. Eg:

...
services:
  - name: bar
    start: bin/bar
    ports:
        required: 80/tcp
    security-override:
        apparmor: meta/bar.apparmor
        seccomp: meta/bar.seccomp

With the above yaml, you can now create meta/bar.apparmor that contains (json):

{
  "policy_vendor": "ubuntu-core",
  "policy_version": 15.04,
  "template": "default",
  "policy_groups": [
    "network-client"
  ],
  "write_path": [
    "/some/path"
  ]
}
  • upon snap install, the click security json is symlinked into /var/lib/apparmor/clicks

  • aa-clickhook is run on install and places the profile in /var/lib/apparmor/profiles (prefixed with 'click_')

  • aa-clickhook is shipped in click-apparmor

  • use of apparmor may trigger a manual review via the store (depending on the contents of the security json)

  • see the click manifest documentation for details

IMPORTANT: due to a limitation in click, if you install an already installed snap with the same version, the click hooks may not be run. If you are trying to adjust policy, you may need to run 'sudo aa-clickhook -f' to force regeneration of the profiles.

Note: you must always specify both apparmor and seccomp when using security-override (see above for seccomp override syntax).

apparmor-profile hook

In addition to the above, specialized, hand-crafted confinement may be done via security-policy. Specifying security-policy will trigger a manual review in the store, and is not needed for normal Snappy apps, but instead is provided for snappy framework and trusted snappy applications.

The aa-profile-hook works similarly to aa-clickhook:

  • The snappy packaging specifies to use 'security-policy' in its 'package.yaml' section, which specifies the profile. Eg:

    ...
    services:
      - name: bar
        start: bin/bar
        ports:
            required: 80/tcp
        security-policy:
            apparmor: meta/bar.profile
            filter: meta/bar.filter
  • the profile should reference the AppArmor variables for CLICK_DIR, APP_PKGNAME, APP_APPNAME, APP_VERSION and for profile attachment, just like the apparmor template does with click-apparmor

  • aa-profile-hook is run on install and places the profile in /var/lib/apparmor/profiles (prefixed with 'profile_')

  • If the binary is a service, snappy-systemd makes sure /etc/systemd/system/<service>.service launches the service using ubuntu-snapp-launch (snappy-systemd temporarily sets AppArmorProfile=<profilename> instead of using the launcher)

  • aa-profile-hook is shipped in click-apparmor

  • use of apparmor-profile requires manual review via the store.

To improve maintenance, the profile author can use AppArmor variables to avoid worrying about updating the profile name, the app name, where the app is installed or knowing the package version. Example profile:

#include <tunables/global>

# Specified profile variables
###VAR###

###PROFILEATTACH### (attach_disconnected) {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  # Read-only for the install directory
  @{CLICK_DIR}/@{APP_PKGNAME}/                   r,
  @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/    r,
  @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/**  mrklix,

  # Writable home area
  owner @{HOMEDIRS}/apps/@{APP_PKGNAME}/   rw,
  owner @{HOMEDIRS}/apps/@{APP_PKGNAME}/** mrwklix,

  # Read-only system area for other versions
  /var/lib/apps/@{APP_PKGNAME}/   r,
  /var/lib/apps/@{APP_PKGNAME}/** mrkix,

  # Writable system area only for this version.
  /var/lib/apps/@{APP_PKGNAME}/@{APP_VERSION}/   w,
  /var/lib/apps/@{APP_PKGNAME}/@{APP_VERSION}/** wl,

  ... specialized confinement ...

Example yaml to use the profile:

name: foo
version: 0.1
...
services:
  - name: bar
    start: bin/bar
    ports:
        required: 80/tcp
    security-policy:
        apparmor: meta/bar.profile
        filter: meta/bar.filter

Note: like with security-override, you must always specify both apparmor and seccomp when using security-policy (see above for seccomp policy syntax).

Normal usage

As stated, the snappy packaging yaml does not have to do anything to specify the default confinement. Eg, the following yaml:

name: foo
version: 0.1
...
services:
  - name: bar
    start: bin/bar
  - name: baz
    start: bin/baz
  - name: norf
    start: bin/norf

will create the following AppArmor profiles with default confinement:

  • foo_bar_0.1
  • foo_baz_0.1
  • foo_norf_0.1

Advanced usage

While the snappy packaging yaml is intentionally simple and straightforward for app developers, it can also quite flexible for those who need it. For example, consider the following yaml:

name: foo
version: 0.1
...
services:
  - name: normal-service
    start: bin/normal-service
  - name: extra-policy-group-service
    start: bin/extra-policy-group-service
    caps:
      - network-client
      - extra
  - name: non-default-template-service
    start: bin/non-default-template-service
    security-template: non-default
binaries:
  - name: bin/normal-binary
  - name: extra-policy-group-binary
    caps:
      - network-client
      - extra

Which in turn creates the following AppArmor profiles (using the specified security-template and/or caps:

  • foo_normal-service_0.1
  • foo_extra-policy-group-service_0.1
  • foo_non-default-template-service_0.1
  • foo_normal-binary_0.1
  • foo_extra-policy-group-binary_0.1

Debugging

To check to see if you have any denials:

$ sudo journalctl --no-pager -k | grep audit

An AppArmor denial will look something like:

apparmor="DENIED" operation="mkdir" profile="foo_bar_0.1" name="/var/lib/foo" pid=637 comm="bar" requested_mask="c" denied_mask="c" fsuid=0 ouid=0

If there are no AppArmor denials, AppArmor shouldn't be blocking the app.

If there are AppArmor denials, you can unblock yourself by:

  • modifying the profile in /var/lib/apparmor/profiles that corresponds to your app
  • reload the profile with:

    $ sudo apparmor_parser -r /var/lib/apparmor/profiles/<profile>

A seccomp denial will look something like:

audit: type=1326 audit(1430766107.122:16): auid=1000 uid=1000 gid=1000 ses=15 pid=1491 comm="env" exe="/bin/bash" sig=31 arch=40000028 syscall=983045 compat=0 ip=0xb6fb0bd6 code=0x0

The syscall=983045 can be resolved with the scmp_sys_resolver command (you may also use the sc-logresolve command). Eg:

$ scmp_sys_resolver 983045
set_tls

If there are no seccomp denials, it shouldn't be blocking the app. If there are seccomp denials, you can unblock yourself by modifying the seccomp file in /var/lib/snappy/seccomp/profiles, then launch your app like normal (the launcher will pick up the change on app invocation).

Do note that the local modification will not be preserved on package update. If you believe you have found a bug, please file a bug against: https://bugs.launchpad.net/ubuntu/+source/apparmor/+filebug

Helpful degugging commands

  • sudo sc-logresolve /var/log/syslog will show all seccomp denials.

  • sudo aa-clickhook -f will regenerate all the apparmor profiles in /var/lib/apparmor/profiles

  • sudo aa-profile-hook -f will regenerate all the apparmor-profile profiles in /var/lib/apparmor/profiles

  • sudo aa-status will show you the profiles loaded in the kernel and what processes are running under them

  • ps Z, ps Z <pid> and ps auxwwZ will show you normal ps output, but with the apparmor label the profile is running under

  • sudo systemctl stop <app>.service and sudo systemctl start <app>.service to stop and start services from /etc/systemd/system (ie, where snappy-systemd puts the service files)

  • This is often helpful when developing your app or policy for it:
    • In one terminal launch (tail kernel log for AppArmor denials):

      $ sudo journalctl -f -k | grep audit
    • In another (service name is the filename in /etc/systemd/system):

      $ sudo journalctl -f -u <service name>
    • Then launch the app with systemctl like above, or launch manually under confinement with:

      $ aa-exec -p <profile name> -- /apps/<pkgname>/<version>/...


CategorySpec