前言:
APPARMor是一个Linux操作系统的内核安全模块,和selinux功能基本是一致的。
AppArmor(Application Armor)是Linux内核的一个安全模块,AppArmor允许系统管理员将每个程序与一个安全配置文件关联,从而限制程序的功能。简单的说,AppArmor是与SELinux类似的一个访问控制系统,通过它你可以指定程序可以读、写或运行哪些文件,是否可以打开网络端口等。作为对传统Unix的自主访问控制模块的补充,AppArmor提供了强制访问控制机制,它已经被整合到2.6版本的Linux内核中。
这里有一点需要特别强调,Ubuntu和openSUSE才有AppAromor,centos,Redhat这些操作系统才是使用SeLinux的。
因此,如果你对centos比较熟悉而对Ubuntu不太熟悉的情况下,可以简单的理解APPArmor为Ubuntu版的SeLinux
一,
APPArmor内核模块的停止和启用
AppArmor可以被禁用,其内核模块可以通过输入以下命令卸载:
sudo service apparmor stop
sudo update-rc.d -f apparmor remove
要重新启用AppArmor,输入:
sudo service apparmor start
sudo update-rc.d apparmor defaults
二,
APPArmor的两种工作模式
像 SELinux 一样,AppArmor 以两种模式运行。在 强制enforce 模式下,应用被赋予它们运行所需要的最小权限,但在 抱怨complain 模式下 AppArmor 允许一个应用执行受限的操作并将操作造成的“抱怨”记录到日志里(/var/log/kern.log
,/var/log/audit/audit.log
,和其它放在 /var/log/apparmor
中的日志)。
这两种模式意思enforce是强制应用,而complain是仅仅记录受限情况,如其名一样,仅仅抱怨一通而已,当然,两种工作模式日志都会记录。
Enforcement(强制模式) :在这种模式下,配置文件里列出的限制条件都会得到执行,并且对于违反这些限制条件的程序会进行日志记录。使用aa-enforce <程序名>开启
Complain(投诉模式):在这种模式下,配置文件里的限制条件不会得到执行,Apparmor只是对程序的行为进行记录。一般用于调试。使用aa-complain <程序名>
Disable: 此时对应配置文件不加载,没有对程序行为进行限制。aa-disable <程序名>
查询APPArmor的状态:
apparmor_status
apparmor module is loaded.
25 profiles are loaded.
23 profiles are in enforce mode.
/sbin/dhclient
/snap/snapd/17950/usr/lib/snapd/snap-confine
/snap/snapd/17950/usr/lib/snapd/snap-confine//mount-namespace-capture-helper
/usr/bin/docker
/usr/bin/lxc-start
/usr/bin/man
/usr/lib/NetworkManager/nm-dhcp-client.action
/usr/lib/NetworkManager/nm-dhcp-helper
/usr/lib/connman/scripts/dhclient-script
/usr/lib/snapd/snap-confine
/usr/lib/snapd/snap-confine//mount-namespace-capture-helper
/usr/local/bin/etcd
/usr/sbin/tcpdump
docker-default
lxc-container-default
lxc-container-default-cgns
lxc-container-default-with-mounting
lxc-container-default-with-nesting
man_filter
man_groff
snap-update-ns.http
snap.http.http
snap.http.man
2 profiles are in complain mode.
/bin/ping
/usr/sbin/sshd
27 processes have profiles defined.
24 processes are in enforce mode.
docker-default (29250)
docker-default (29270)
docker-default (29278)
docker-default (29411)
docker-default (29475)
docker-default (29554)
docker-default (29658)
docker-default (29748)
docker-default (29788)
docker-default (29833)
docker-default (29887)
docker-default (30114)
docker-default (30185)
docker-default (30186)
docker-default (30187)
docker-default (30350)
docker-default (30372)
docker-default (30569)
docker-default (30606)
docker-default (30607)
docker-default (30609)
docker-default (30998)
docker-default (31063)
docker-default (31064)
1 processes are in complain mode.
/usr/sbin/sshd (56029)
2 processes are unconfined but have a profile defined.
/usr/sbin/sshd (1409)
/usr/sbin/sshd (13170)
以上表示25个程序是在enforce模式,两个程序是在Complain模式,1个进程在Complain模式下运行,2个sshd进程虽然有配置文件定义过,但没有激活,24个进程都是docker在enforce模式下。
三,
APPArmor的配置文件
我们安装了一个可执行程序,如果想用AppArmor进行访问控制,就需要新建一个配置文件到/etc/apparmor.d/目录下,这个目录下的每个配置文件都是跟可执行程序绑定的,不要随便修改配置文件名或程序路径。
例如,MySQL的APPArmor的配置文件:
# cat usr.sbin.mysqld
# vim:syntax=apparmor
# Last Modified: Tue Jun 19 17:37:30 2007
#include <tunables/global>
/usr/sbin/mysqld {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/user-tmp>
#include <abstractions/mysql>
#include <abstractions/winbind>
capability dac_override,
capability sys_resource,
capability setgid,
capability setuid,
network tcp,
/etc/hosts.allow r,
/etc/hosts.deny r,
/etc/mysql/*.pem r,
/etc/mysql/conf.d/ r,
/etc/mysql/conf.d/* r,
/etc/mysql/*.cnf r,
/usr/lib/mysql/plugin/ r,
/usr/lib/mysql/plugin/*.so* mr,
/usr/sbin/mysqld mr,
/usr/share/mysql/** r,
/var/log/mysql.log rw,
/var/log/mysql.err rw,
/var/lib/mysql/ r,
/var/lib/mysql/** rwk,
/var/log/mysql/ r,
/var/log/mysql/* rw,
/var/run/mysqld/mysqld.pid rw,
/var/run/mysqld/mysqld.sock w,
/run/mysqld/mysqld.pid rw,
/run/mysqld/mysqld.sock w,
/sys/devices/system/cpu/ r,
# Site-specific additions and overrides. See local/README for details.
#include <local/usr.sbin.mysqld>
}
在配置文件中,注释总是以# 号开头。#include 行加载文件。
以下是配置文件中使用的不同类型的规则。
- 路径条目:这包含有关允许应用程序访问哪些文件的信息。
- 能力条目:确定一个受限进程被允许使用的权限。
- 网络条目:确定连接类型。例如:tcp。对于数据包分析器网络可以是原始的或数据包等。
在花括号 {} 中,我们还有其他包含语句,还包括访问权限/模式 [read(r)/write (w)/execute (x) (k) 锁(需要 r 或 w,AppArmor 2.1 及更高版本)]各种文件和目录,包括正则表达式,用花括号 {} 对 include 语句进行通配有助于加载 Novell AppArmor 配置文件的组件。
以上配置文件限定了MySQL程序,例如对于 /var/log/mysql这个目录/usr/sbin/mysqld 这个程序只有读权限,但此目录下/usr/sbin/mysqld这个程序具有读写权限,/usr/lib/mysql 这个目录下,/usr/sbin/mysqld 这个程序有读写锁定权限,那么,我们可以判断出这个配置文件是针对yum安装的MySQL服务了。
四,
APPArmor的配置文件示例:
cat >/etc/apparmor.d/k8s-apparmor-example-deny-write<<EOF
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
file,
# Deny all file writes.
deny /** w,
}
EOF
在此示例中,配置文件授予应用程序所有类型的访问权限,但写入整个文件系统除外。它包含两个规则:
file
: 允许对整个文件系统进行各种访问deny
/** w: 拒绝在根目录下写入任何文件/。该表达式/**转换为根目录下的任何文件,以及其子目录下的文件。
五,
将以上配置文件加载进APPArmor模块内:
cat /etc/apparmor.d/k8s-apparmor-example-deny-write |sudo apparmor_parser -a
查看APPArmor的状态:
可以看到新增的配置文件,并且该配置是enforce工作模式
root@k8s-master:~# apparmor_status
apparmor module is loaded.
25 profiles are loaded.
23 profiles are in enforce mode.
/sbin/dhclient
/snap/snapd/17950/usr/lib/snapd/snap-confine
/snap/snapd/17950/usr/lib/snapd/snap-confine//mount-namespace-capture-helper
/usr/bin/lxc-start
/usr/bin/man
/usr/lib/NetworkManager/nm-dhcp-client.action
/usr/lib/NetworkManager/nm-dhcp-helper
/usr/lib/connman/scripts/dhclient-script
/usr/lib/snapd/snap-confine
/usr/lib/snapd/snap-confine//mount-namespace-capture-helper
/usr/local/bin/etcd
/usr/sbin/tcpdump
docker-default
k8s-apparmor-example-deny-write
。。。。。。。。。。。。。。。略略略。。。。。。。。。。。。。。。。。。。。。。
六,
kubernetes的pod引用以上的APPArmor配置文件:
主要是注解container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write
注意,这个localhost前面是有空格的,hello是下面的pod部署文件里的container下的名字,k8s-apparmor-example-deny-write是上面新建的那个apparmor配置文件
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor
annotations:
# Tell Kubernetes to apply the AppArmor profile "k8s-apparmor-example-deny-write".
container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write
spec:
containers:
- name: hello
image: busybox
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
运行这个pod,但发现此pod状态是blocked
root@k8s-master:~# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-apparmor 0/1 Blocked 0 4m48s
查询具体原因为:
kubectl describe pod hello-apparmor
Message: Cannot enforce AppArmor: profile "k8s-apparmor-example-deny-write" is not loaded
root@k8s-master:~# kubectl describe pod hello-apparmor
Name: hello-apparmor
Namespace: default
Priority: 0
Node: k8s-node1/192.168.123.151
Start Time: Thu, 12 Jan 2023 21:34:57 +0800
Labels: <none>
Annotations: container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write
Status: Pending
Reason: AppArmor
Message: Cannot enforce AppArmor: profile "k8s-apparmor-example-deny-write" is not loaded
IP:
IPs: <none>
Containers:
hello:
Container ID:
Image: busybox
Image ID:
Port: <none>
Host Port: <none>
Command:
sh
-c
echo 'Hello AppArmor!' && sleep 1h
State: Waiting
Reason: Blocked
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-cjw7z (ro)
Conditions:
Type Status
Initialized True
Ready False
ContainersReady False
PodScheduled True
Volumes:
kube-api-access-cjw7z:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 5m8s default-scheduler Successfully assigned default/hello-apparmor to k8s-node1
OK,这个明明在master节点使用aaparmor_status 查看到了的啊,wait,这个pod是运行在node1节点的:
root@k8s-master:/etc/apparmor.d# kubectl get no -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-master Ready control-plane,master 400d v1.22.10 192.168.123.150 <none> Ubuntu 18.04.5 LTS 4.15.0-200-generic docker://20.10.0
k8s-node1 Ready <none> 30d v1.22.2 192.168.123.151 <none> Ubuntu 18.04.5 LTS 4.15.0-200-generic docker://20.10.0
k8s-node2 Ready <none> 30d v1.22.2 192.168.123.152 <none> Ubuntu 18.04.5 LTS 4.15.0-200-generic docker://20.10.0
原因总算清楚了,node1上并没有这个k8s-apparmor-example-deny-write配置文件,OK,拷贝此文件到node1节点并加载到内核中:
root@k8s-master:/etc/apparmor.d# scp k8s-apparmor-example-deny-write k8s-node1:/etc/apparmor.d/
The authenticity of host 'k8s-node1 (192.168.123.151)' can't be established.
ECDSA key fingerprint is SHA256:nG/f/jJzY0mQunN4HWMIaHLaElyZZjOHCG2XCYFA5YI.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'k8s-node1' (ECDSA) to the list of known hosts.
root@k8s-node1's password:
k8s-apparmor-example-deny-write 100% 120 106.0KB/s 00:00
在node1节点上加载k8s-apparmor-example-deny-write这个配置文件:
root@k8s-node1:~# apparmor_parser -a /etc/apparmor.d/k8s-apparmor-example-deny-write
重新app 以上pod,查看状态,可以发现正常的running了:
root@k8s-master:~# kubectl get po hello-apparmor
NAME READY STATUS RESTARTS AGE
hello-apparmor 1/1 Running 0 29m
七,
测试APPArmor配置文件效果:
进入容器后,执行创建文件touch命令无法写入,echo重定向也没有权限
root@k8s-master:~# kubectl exec -it hello-apparmor -- /bin/sh
/ # touch aaa
touch: aaa: Permission denied
/ # ls
bin dev etc home proc root sys tmp usr var
/ # cat etc/
group hostname hosts localtime mtab network/ passwd resolv.conf shadow
/ # cat etc/hostname
hello-apparmor
/ # echo "fuck apparmor" >test
/bin/sh: can't create test: Permission denied
OK,此前我们查询到了这个APPArmor配置文件是运行在enforce模式下的,现在登陆node1将它修改成Complain模式:
注意,如果没有aa-complain和aa-enforce命令,那么是需要安装apparmor-utils这个工具包的:
安装apparmor工具包
root@k8s-node1:~# apt-get install apparmor-utils
Reading package lists... Done
Building dependency tree
。。。。。。。。。。。略略略。。。。。。。。。
使用aa-complain命令直接指定apparmor的配置文件,切换到complain模式
root@k8s-node1:~# aa-complain /etc/apparmor.d/k8s-apparmor-example-deny-write
Setting /etc/apparmor.d/k8s-apparmor-example-deny-write to complain mode.
查看apparmor的状态:
可以看到k8s-apparmor-example-deny-write这个配置文件正确的加载了
root@k8s-node1:~# apparmor_status |grep k8s
k8s-apparmor-example-deny-write
k8s-apparmor-example-deny-write (117874)
root@k8s-node1:~# ps -ef |grep 117874
root 117874 117847 0 22:44 ? 00:00:00 sleep 1h
root 121613 64994 0 22:48 pts/0 00:00:00 grep --color=auto 117874
八,
自动生成apparmor配置文件
root@k8s-master:~# aa-genprof /usr/bin/passwd
Writing updated profile for /usr/bin/passwd.
Setting /usr/bin/passwd to complain mode.
Before you begin, you may wish to check if a
profile already exists for the application you
wish to confine. See the following wiki page for
more information:
http://wiki.apparmor.net/index.php/Profiles
Profiling: /usr/bin/passwd
Please start the application to be profiled in
another window and exercise its functionality now.
Once completed, select the "Scan" option below in
order to scan the system logs for AppArmor events.
For each AppArmor event, you will be given the
opportunity to choose whether the access should be
allowed or denied.
[(S)can system log for AppArmor events] / (F)inish
Profiling: /usr/bin/passwd
Please start the application to be profiled in
another window and exercise its functionality now.
Once completed, select the "Scan" option below in
order to scan the system logs for AppArmor events.
For each AppArmor event, you will be given the
opportunity to choose whether the access should be
allowed or denied.
[(S)can system log for AppArmor events] / (F)inish
多说一句,这个自动生成的配置文件还是在/etc/apparmor.d/目录下,但它是通过读取系统日志文件/var/log/syslog来进行的,因此,上面的提示说要新开一个窗口,运行一下passwd程序,生成相关的日志文件:
/var/log/syslog文件的部分日志
Jan 12 22:51:22 k8s-master kernel: [31693.170585] audit: type=1400 audit(1673535082.352:290): apparmor="STATUS" operation="profile_load" profile="unconfined" name="/sbin/pivot_root" pid=128518 comm="apparmor_parser"
Jan 12 22:51:22 k8s-master root: GenProf: 7da38636a415bfc154b9d7e3303b6226
Jan 12 22:51:29 k8s-master kernel: [31700.804407] audit: type=1400 audit(1673535089.988:291): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="/sbin/pivot_root" pid=128629 comm="apparmor_parser"
Jan 12 22:51:46 k8s-master kernel: [31717.547306] audit: type=1400 audit(1673535106.732:292): apparmor="STATUS" operation="profile_load" profile="unconfined" name="/usr/bin/passwd" pid=128922 comm="apparmor_parser"
Jan 12 22:51:46 k8s-master root: GenProf: 4f5800b7e323735332abb0252b0afcf3
Jan 12 22:51:53 k8s-master kernel: [31724.097027] audit: type=1400 audit(1673535113.280:293): apparmor="ALLOWED" operation="open" profile="/usr/bin/passwd" name="/proc/129027/loginuid" pid=129027 comm="passwd" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Jan 12 22:51:53 k8s-master kernel: [31724.097252] audit: type=1400 audit(1673535113.280:294): apparmor="ALLOWED" operation="open" profile="/usr/bin/passwd" name="/etc/nsswitch.conf" pid=129027 comm="passwd" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Jan 12 22:51:53 k8s-master kernel: [31724.097915] audit: type=1400 audit(1673535113.284:295): apparmor="ALLOWED" operation="open" profile="/usr/bin/passwd" name="/etc/passwd" pid=1290
九,
移除apparmor的配置文件:
apparmor_parser -R /etc/apparmor.d/k8s-apparmor-example-deny-write
再次查询,看不到k8s-apparmor-example-deny-write了:
root@k8s-master:/etc/apparmor# apparmor_status |grep k8s
十,
查询内核是否开启了apparmor:
root@k8s-master:/etc/apparmor# cat /sys/module/apparmor/parameters/enabled
Y
root@k8s-master:/etc/apparmor# cat /sys/module/apparmor/parameters/debug
N
查看加载了哪些profile:
root@k8s-master:/etc/apparmor# cat /sys/kernel/security/apparmor/profiles
/usr/bin/passwd (enforce)
/sbin/pivot_root (enforce)
kubernetes查询是否支持apparmor:
kubernetes的版本必须是大于1.14的哦
root@k8s-master:/etc/apparmor# kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}\n{end}'
k8s-master: kubelet is posting ready status. AppArmor enabled
k8s-node1: kubelet is posting ready status. AppArmor enabled
k8s-node2: kubelet is posting ready status. AppArmor enabled