Created by darix and powered by reveal.js
CC BY-NC-ND 4.0
"The permissions are fine why isn't this working"
"OMG apparmor again"
"Who turned that on here"
systemctl stop apparmor;
systemctl disable apparmor
"Problem is solved"
Are we really as secure as before?
auditd
$EDITOR
/var/log/audit/audit.log
/sys/kernel/security/apparmor/profiles
ps aufxZ
$ ps axZ | grep redis
redis.replica (complain) [snip] /usr/sbin/redis-server ...
redis.primary (enforce) [snip] /usr/sbin/redis-server ...
unconfined [snip] grep --color=auto redis
$ less /var/log/audit/audit.log
$ $EDITOR /etc/apparmor.d/profilename
$ rcapparmor reload
file access, exec, capabilities, (limited) network support, mount, signals, dbus, ptrace, rlimits
Depending on the kernel/userland. This list is from Tumbleweed 20181224
man 5 apparmor.d
# /etc/apparmor.d/hello-world-go
# Profile for hello-world-go.go
# sets some global variables used by
# abstractions/profiles to better fit our distro
#
abi <abi/3.0>,
include <tunables/global>
# path to executable, can be a profile name,
# but more on that later
profile hellow-world-go /usr/local/bin/hello-world-go {
# nothing to see here. this is a static binary
}
# /etc/apparmor.d/hello-world-c
# Profile for hello-world-c.c
abi <abi/3.0>,
include <tunables/global>
profile hello-world-c /usr/local/bin/hello-world-c {
}
$ hello-world-c
hello-world-c: error while loading shared libraries:
libc.so.6: cannot open shared object file:
No such file or directory
$ ldd =hello-world-c
linux-vdso.so.1 (0x00007fffce3fd000)
libc.so.6 => /lib64/libc.so.6 (0x00007f5ae28d8000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5ae2c98000)
$ grep 'DENIED.*hello-world' /var/log/audit/audit.log
type=AVC msg=audit(1547563748.737:437):
apparmor="DENIED" operation="open" profile="hello-world-c"
name="/etc/ld.so.cache" pid=4156 comm="hello-world-c"
requested_mask="r" denied_mask="r" fsuid=0 ouid=0
type=AVC msg=audit(1547563748.737:438):
apparmor="DENIED" operation="open" profile="hello-world-c"
name="/lib64/libc-2.27.so" pid=4156 comm="hello-world-c"
requested_mask="r" denied_mask="r" fsuid=0 ouid=0
type=AVC msg=audit(1547563748.737:438):
apparmor="DENIED" operation="open" profile="hello-world-c"
name="/lib64/libc-2.27.so" pid=4156 comm="hello-world-c"
requested_mask="r" denied_mask="r" fsuid=0 ouid=0
# /etc/apparmor.d/hello-world-c
# Profile for hello-world-c.c
abi <abi/3.0>,
include <tunables/global>
profile hello-world-c /usr/local/bin/hello-world-c {
/etc/ld.so.cache r,
/lib64/libc-2.27.so r,
}
$ hello-world-c
hello-world-c: error while loading shared libraries:
libc.so.6: failed to map segment from shared object
type=AVC msg=audit(1547564894.993:588): apparmor="DENIED"
operation="file_mmap" profile="hello-world-c"
name="/lib64/libc-2.27.so" pid=5121 comm="hello-world-c"
requested_mask="m" denied_mask="m" fsuid=0 ouid=0
# /etc/apparmor.d/hello-world-c
# Profile for hello-world-c.c
abi <abi/3.0>,
include <tunables/global>
profile hello-world-c /usr/local/bin/hello-world-c {
/etc/ld.so.cache r,
/lib64/libc-2.27.so rm,
}
/etc/apparmor.d/abstractions/
Reusable code blocks to solve common problems in a central place
Over the time some of the abstractions got quite broad, might need rework. But nonetheless they are a good starting point.
Let us check out abstractions/base ...
# /etc/apparmor.d/hello-world-c
# Profile for hello-world-c.c
abi <abi/3.0>,
include <tunables/global>
profile hello-world-c /usr/local/bin/hello-world-c {
include <abstractions/base>
}
# /etc/apparmor.d/hello-world.pl
# Profile for hello-world.pl
abi <abi/3.0>,
include <tunables/global>
profile hello-world-pl /usr/local/bin/hello-world.pl {
include <abstractions/base>
/usr/local/bin/hello-world.pl r,
}
We don't need an x rule for /usr/bin/perl because the kernel internally already called the script with "/usr/bin/perl /usr/local/bin/hello-world.pl" but within the scope of our profile
profile bin /path/to/bin {
include <abstractions/base>
deny / w, # not sure why it tries to open it rw,
# read will still be allowed via base
owner /tmp/** rw,
}
abi <abi/3.0>,
include <tunables/global>
@{RAILS_ROOT}=/srv/www/vhosts/discourse
profile /discourse/appserver {
owner @{RAILS_ROOT}/public/uploads/** rw,
}
# from tunables/home
@{HOMEDIRS}=/home/ /srv/home/
Example: exec-go.go
$ exec-go
panic: permission denied
goroutine 1 [running]:
main.main()
.../exec-go.go:37 +0x125
type=AVC msg=audit(1547576818.255:740):
apparmor="DENIED" operation="exec" profile="exec-go"
name="hello-world-c" pid=8300 comm="exec-go"
requested_mask="x" denied_mask="x" fsuid=0 ouid=0
Executed binary will inherit the permissions from the current profile
Has the disadvantage you merge permission scopes
Allow execution if binary has a profile
We can actually specify into which profile we should jump, sadly those explicit transitions are limited to 12 in a profile atm.
profile binary /path/to/binary {
/path/to/otherbinary Px -> usethisprofile,
}
Similar to Px/px transition
Target profile has to be a child profile of the current profile (either defined locally or via include)
profile binary /path/to/binary {
/path/to/bin1 Cx -> somechild,
/path/to/bin2 Px -> specialprofile,
/path/to/bin3 Px -> binary//somechild,
/path/to/bin4 Cx -> specialprofile, # wrong.
profile somechild {}
}
profile specialprofile {}
Switch executed binary into an unconfined scope
Use with caution. Actually hardly ever.
[pPcC][iUu]x
Those combinations will try to jump to the profile as above but will fallback to ix/Ux if that fails. Mostly useful in conjuction with pam_apparmor
Unless you really need to inherit the environment from the parent use the upper case version of the rule.
Forking always works. It does not need an exec rule.
# /etc/apparmor.d/exec-go
abi <abi/3.0>,
include <tunables/global>
profile exec-go /usr/local/bin/exec-go {
# nothing to see here. this is a static binary
/usr/local/bin/hello-world-c Px,
}
To make ld happy we need to allow the target profile to read its own binary
# /etc/apparmor.d/hello-world-c
abi <abi/3.0>,
include <tunables/global>
profile hello-world-c /usr/local/bin/hello-world-c {
include <abstractions/base>
/usr/local/bin/hello-world-c rm,
}
ptrace trace peer=libvirt-*,
signal send set=(term, kill) peer=/bin/foo,
capability setuid,
network inet stream,
network inet6 stream,
type=AVC msg=audit(1547580403.396:1266): apparmor="DENIED"
operation="signal" profile="exec-go" pid=12084 comm="zsh"
requested_mask="receive" denied_mask="receive" signal=term
peer="unconfined"
This wouldn't happen for the C process as the base abstraction allows receiving signals from unconfined processes. The go binary does not need it abstraction and lacks the permission for this. Something to keep in mind.
profile ping /{usr,}/bin/ping {}
profile something {}
profile foobar /usr/bin/foobar {
/var/lib/foobar/ r,
/var/lib/foobar/** rw,
owner /tmp/** rwlk,
/usr/lib{64,}/erlang/erts-*/bin/epmd px,
}
Either directly in the service file or via
$ systemctl edit something.service
Useful if your service has a generic binary for
launching like "bundle exec" or "perl"
[Service]
AppArmorProfile=something
Snippet from redis@.service[Service] ExecStart=/usr/sbin/redis-server /etc/redis/%i.conf
We call it with "systemctl start redis@primary". If we want to assign an apparmor profile we can do: $ systemctl edit redis@.service[Service] AppArmorProfile=redis.%i
Now we can actually protect the redis instances from each other.
You can find abstractions and everything for redis in obs://home:darix:apps/redis-apparmor
man 2 change_hat
Two common flags
We should allow the user to have local modifications to the profile without touching our files
# /etc/apparmor.d/hello-world.pl
# Profile for hello-world.pl
abi <abi/3.0>,
include <tunables/global>
profile hello-world.pl /usr/local/bin/hello-world.pl {
include <abstractions/base>
/usr/local/bin/hello-world.pl r,
include if exists <local/hello-world.pl>
}
For the spec file:
# code 15 or newer. not earlier
%bcond_without apparmor_reload
%if 0%{?suse_version} <= 1315
BuildRequires: apparmor-profiles
Requires: apparmor-profiles
%else
BuildRequires: apparmor-abstractions
Requires: apparmor-abstractions
%endif
%if %{with apparmor_reload}
BuildRequires: apparmor-rpm-macros
%endif
%post
[snip]
%if %{with apparmor_reload}
%apparmor_reload /etc/apparmor.d/hello-world.pl
%endif
%files
%config /etc/apparmor.d/hello-world.pl
%config(noreplace) /etc/apparmor.d/local/hello-world.pl
aa-audit aa-cleanprof aa-decode aa-easyprof aa-enforce aa-genprof aa-mergeprof aa-remove-unknown aa-teardown aa-autodep aa-complain aa-disable aa-enabled aa-exec aa-logprof aa-notify aa-status aa-unconfined
Terminal 1: $ aa-genprof /path/to/program Terminal 2: $ /path/to/program
We assume the violation was logged already: $ aa-logprof /path/to/program
$ aa-complain /path/to/program $ aa-enforce /etc/apparmor.d/path.to.program
$ aa-remove-unknown Especially useful if you see a lot of null profiles. But be warned it will remove all profiles not in /etc/apparmor.d/, This means it can wipe autogenerated profiles from docker/lxc/libvirt.
profile hello_world_in_perl {
include <abstractions/base>
/usr/local/bin/hello-world-pl r,
}
$ aa-exec -p hello_world_in_perl /usr/local/bin/hello-world-pl
Actually no... there is more
AppArmor is constantly improving. My most waited features are ...
... better network support, allowing iptables to know if the process runs within apparmor and which profile.
... more transitions for Px
Created by darix and powered by reveal.js
CC BY-NC-ND 4.0