AppArmor: Controle de Acesso Baseado em Política
É um módulo de segurança para o kernel Linux que fornece um controle de acesso baseado em políticas de segurança. Incluído no kernel de de a versão 2.6.36, vem sendo mantido pela Canonical.
Protege proativamente o sistema operacional e apps contra ameaças externas ou internas impedindo que falhas sejam exploradas.
Ele permite que o administrador do sistema defina um conjunto de permissões para aplicativos específicos, limitando o que esses aplicativos podem fazer, como acessar arquivos, usar recursos de rede ou interagir com outros processos.
Esse módulo vem pre carregado junto com a distribuição escolhida (principalmente as baseada em Debian e Ubuntu) evitando que usuário precise instalá-lo. Ele fica habilitado o tempo todo apenas aguardando ser configurado. Outras distribuições podem optar por outras alternativas como o SELinux.
Para ver se sua distribuição possui o AppArmor disponível apenas execute.
root@cks-master:~# systemctl status apparmor.service
● apparmor.service - Load AppArmor profiles
Loaded: loaded (/lib/systemd/system/apparmor.service; enabled; vendor preset: enabled)
Active: active (exited) since Tue 2024-09-10 06:45:55 UTC; 1 day 13h ago
Docs: man:apparmor(7)
https://gitlab.com/apparmor/apparmor/wikis/home/
Main PID: 627499 (code=exited, status=0/SUCCESS)
Tasks: 0 (limit: 4680)
Memory: 0B
CGroup: /system.slice/apparmor.service
...
Rodando o comando abaixo podemos ver se esta carregado e os profiles existentes.
root@cks-worker:~# aa-status
apparmor module is loaded.
##### BLOCO SOBRE PROFILE ####
48 profiles are loaded. # TOTAL
42 profiles are in enforce mode. # A lista dos que estão em modo enforce
/snap/snapd/21759/usr/lib/snapd/snap-confine
/snap/snapd/21759/usr/lib/snapd/snap-confine//mount-namespace-capture-helper
/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/sbin/chronyd
/usr/sbin/tcpdump
/{,usr/}sbin/dhclient
cri-containerd.apparmor.d
docker-default
lsb_release
man_filter
man_groff
nvidia_modprobe
nvidia_modprobe//kmod
snap-update-ns.google-cloud-cli
snap-update-ns.lxd
snap.lxd.activate
snap.lxd.benchmark
snap.lxd.buginfo
snap.lxd.check-kernel
snap.lxd.daemon
snap.lxd.hook.configure
snap.lxd.hook.install
snap.lxd.hook.remove
snap.lxd.lxc
snap.lxd.lxc-to-lxd
snap.lxd.lxd
snap.lxd.migrate
ubuntu_pro_apt_news
ubuntu_pro_esm_cache
ubuntu_pro_esm_cache//apt_methods
ubuntu_pro_esm_cache//apt_methods_gpgv
ubuntu_pro_esm_cache//cloud_id
ubuntu_pro_esm_cache//dpkg
ubuntu_pro_esm_cache//ps
ubuntu_pro_esm_cache//ubuntu_distro_info
ubuntu_pro_esm_cache_systemctl
ubuntu_pro_esm_cache_systemd_detect_virt
6 profiles are in complain mode. # A lista do que estão em modo complain
snap.google-cloud-cli.anthoscli
snap.google-cloud-cli.bq
snap.google-cloud-cli.docker-credential-gcloud
snap.google-cloud-cli.gcloud
snap.google-cloud-cli.gsutil
snap.google-cloud-cli.kubectl
###############################
##### BLOCO SOBRE PROCESSOS ####
6 processes have profiles defined. # Processos nos sistema que possuem profiles
6 processes are in enforce mode. # Desses 6 processos temos os 6 em modo enforce
/usr/sbin/chronyd (1371)
/usr/sbin/chronyd (1372)
/usr/local/apache2/bin/httpd (194962) cri-containerd.apparmor.d
/usr/local/apache2/bin/httpd (194975) cri-containerd.apparmor.d
/usr/local/apache2/bin/httpd (194976) cri-containerd.apparmor.d
/usr/local/apache2/bin/httpd (194977) cri-containerd.apparmor.d
0 processes are in complain mode. # Nada em complain
0 processes are unconfined but have a profile defined. # Nada em unconfined
Cada app tem seu profile, na verdade um profile pode ser vinculado a um binário executável.
Cada profile, se carregado, pode estar em um dos 3 modos:
Unconfined
: permite que o processo saia da política associada a ele.Complain
: permite que o processo saia, mas será criado um log.Enforce
: O processo somente fará o que ele pode fazer.
Para mais comandos com o AppArmor podemos instalar o apparmor-utils
.
root@cks-worker:~# apt-get install apparmor-utils -y
## Possíveis commandos
root@cks-worker:~# aa-
aa-audit aa-cleanprof aa-decode aa-enabled aa-exec aa-logprof aa-remove-unknown aa-teardown aa-update-browser
aa-autodep aa-complain aa-disable aa-enforce aa-genprof aa-mergeprof aa-status aa-unconfined
- aa-complain: para colocar um profile no modo complain.
- aa-enforce: para colocar um profile no modo enforce.
- aa-unconfined: para colocar um profile em modo unconfined.
- aa-genprof e aa-genprof: para criar e deletar um profile para um executável.
- aa-logprof: Processa as entradas de logs para incluir regras no profile.
Um pouco mais sobre AppArmor pode ser encontrado em https://debian-handbook.info/browse/stable/sect.apparmor.html.
Os profiles são armazenados em /etc/apparmor.d/.
root@cks-worker:~# ls -l /etc/apparmor.d/
total 104
drwxr-xr-x 2 root root 4096 Sep 10 06:46 abi
drwxr-xr-x 4 root root 12288 Sep 10 06:46 abstractions
drwxr-xr-x 2 root root 4096 Aug 30 21:40 disable
drwxr-xr-x 2 root root 4096 Feb 11 2020 force-complain
drwxr-xr-x 2 root root 4096 Sep 10 06:46 local
-rw-r--r-- 1 root root 1313 Oct 10 2023 lsb_release
-rw-r--r-- 1 root root 1108 Oct 10 2023 nvidia_modprobe
-rw-r--r-- 1 root root 3500 Jan 31 2023 sbin.dhclient # <<<<
drwxr-xr-x 5 root root 4096 Sep 10 06:46 tunables
-rw-r--r-- 1 root root 1724 Jul 18 15:20 ubuntu_pro_apt_news
-rw-r--r-- 1 root root 6853 Jul 18 15:20 ubuntu_pro_esm_cache
-rw-r--r-- 1 root root 3202 Feb 25 2020 usr.bin.man # <<<<
-rw-r--r-- 1 root root 29450 Jul 26 02:58 usr.lib.snapd.snap-confine.real # <<<<
-rw-r--r-- 1 root root 1881 Apr 20 2020 usr.sbin.chronyd # <<<<
-rw-r--r-- 1 root root 1575 Feb 11 2020 usr.sbin.rsyslogd # <<<<
-rw-r--r-- 1 root root 1674 Feb 8 2024 usr.sbin.tcpdump # <<<<
Observe que os nomes dos profiles, quando não definidos, são os caminhos dos binários substituindo /
por .
.
Dentro do diretório /etc/apparmor.d/
, também podemos encontrar subdiretórios.
-
abstractions/
: Contém regras comuns, como acesso básico a arquivos ou permissões de rede, que podem ser reutilizadas em vários profiles. -
tunables/
: Define variáveis configuráveis, como diretórios padrão que podem variar de sistema para sistema (por exemplo, o diretório do home). -
disable/
: Este diretório armazena links simbólicos para profiles que foram explicitamente desativados. Quando um link para um profile existe neste diretório diretório, o AppArmor o ignora-o durante o carregamento.root@cks-worker:/etc/apparmor.d/disable# ls -lha
total 8.0K
drwxr-xr-x 2 root root 4.0K Aug 30 21:40 .
drwxr-xr-x 8 root root 4.0K Sep 10 06:46 ..
lrwxrwxrwx 1 root root 33 Aug 30 21:40 usr.sbin.rsyslogd -> /etc/apparmor.d/usr.sbin.rsyslogd -
force-complain/
: Contém links simbólicos para profiles que estão no modo "complain". -
local/
: Armazena configurações e ajustes locais para profiles. Este diretório é usado para sobrescrever ou adicionar regras específicas a profiles padrões sem modificar o próprio arquivo do profile. Se observarmos esse diretório temos praticamente os arquivos com os mesmo nomes dos profiles.root@cks-worker:/etc/apparmor.d/local# ls -l
total 4
-rw-r--r-- 1 root root 1111 Oct 10 2023 README
-rw-r--r-- 1 root root 0 Aug 30 21:41 lsb_release
-rw-r--r-- 1 root root 0 Aug 30 21:41 nvidia_modprobe
-rw-r--r-- 1 root root 0 Aug 30 21:40 sbin.dhclient
-rw-r--r-- 1 root root 0 Aug 30 21:40 ubuntu_pro_apt_news
-rw-r--r-- 1 root root 0 Aug 30 21:40 ubuntu_pro_esm_cache
-rw-r--r-- 1 root root 0 Aug 30 21:41 usr.bin.man
-rw-r--r-- 1 root root 0 Aug 30 21:41 usr.lib.snapd.snap-confine.real
-rw-r--r-- 1 root root 0 Aug 30 21:44 usr.sbin.chronyd
-rw-r--r-- 1 root root 0 Aug 30 21:40 usr.sbin.rsyslogd
-rw-r--r-- 1 root root 0 Aug 30 21:41 usr.sbin.tcpdump
Criando um Profile para um Binário
Vamos criar um profile para o curl e testar.
# Testando inicialmente
david@cks-worker:~$ curl devsecops.puziol.com.br
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>cloudflare</center>
</body>
</html>
* Connection #0 to host devsecops.puziol.com.br left intact
# Criando o profile
# Ao final será perguntado se devemos scanear os logs para aplicar possíveis permissões.
# Vou finalizar sem aplicar nada.
root@cks-worker:~# aa-genprof curl
Writing updated profile for /usr/bin/curl.
Setting /usr/bin/curl 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:
https://gitlab.com/apparmor/apparmor/wikis/Profiles
Profiling: /usr/bin/curl
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
Setting /usr/bin/curl to enforce mode.
Reloaded AppArmor profiles in enforce mode.
Please consider contributing your new profile!
See the following wiki page for more information:
https://gitlab.com/apparmor/apparmor/wikis/Profiles
Finished generating profile for /usr/bin/curl.
# Testando
root@cks-worker:~# curl devsecops.puziol.com.br -v
* Could not resolve host: devsecops.puziol.com.br
* Closing connection 0
curl: (6) Could not resolve host: devsecops.puziol.com.br
O curl não possui nenhuma permissão e a regra padrão é negar. Uma vez criado o profile ele é automaticamente colocado em modo enforce.
Vamos fazer algumas analises.
root@cks-worker:~# aa-status
apparmor module is loaded.
49 profiles are loaded. # Temos um a mais
43 profiles are in enforce mode.
/snap/snapd/21759/usr/lib/snapd/snap-confine
/snap/snapd/21759/usr/lib/snapd/snap-confine//mount-namespace-capture-helper
/usr/bin/curl #<<<<
/usr/bin/man
...
# Agora vamos conferir os arquivos criados.
# Na raíz foi criado
root@cks-worker:~# ls -l /etc/apparmor.d/ | grep curl
-rw------- 1 root root 141 Sep 12 13:15 usr.bin.curl
# Mas no local/ não, ou seja, não é criado automaticamente neste diretório.
root@cks-worker:~# ls -l /etc/apparmor.d/local/ | grep curl
# E temos nosso profile inicial bem limitado.
root@cks-worker:~# cat /etc/apparmor.d/usr.bin.curl
# Last Modified: Thu Sep 12 13:14:48 2024
#include <tunables/global>
/usr/bin/curl {
#include <abstractions/base>
/usr/bin/curl mr,
}
As negações do AppArmor são registradas em /var/log/syslog
(ou /var/log/audit/audit.log para violações de política não-DBus se auditd estiver instalado).
O kernel limitará a taxa de negações do AppArmor, o que pode causar problemas durante a criação do profile. Você pode evitar isso instalando auditd ou ajustando a limitação de taxa no kernel:
sysctl -w kernel.printk_ratelimit=0
Vamos utilizar o próprio log do AppArmor para incluir regras no curl. Mais pra frente vamos conferir esses logs para criar um profile de forma manual.
# O AppArmor irá conferir entradas do syslog e oferecer para adicionar permissões
root@cks-worker:/etc/apparmor.d# aa-logprof
Reading log entries from /var/log/syslog.
Updating AppArmor profiles in /etc/apparmor.d.
Enforce-mode changes:
Profile: /usr/bin/curl
Path: /etc/ssl/openssl.cnf
New Mode: owner r
Severity: 2
# ESTA OFERECENDO PARA COLOCAR A ENTRADA 1 OPENSSL NO PROFILE E EU VOU ACEITAR POIS PARECE FAZER SENTIDO.
[1 - #include <abstractions/openssl>]
2 - #include <abstractions/ssl_keys>
3 - owner /etc/ssl/openssl.cnf r,
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / (O)wner permissions off / Abo(r)t / (F)inish
Adding #include <abstractions/openssl> to profile.
Profile: /usr/bin/curl
Path: /etc/host.conf
New Mode: owner r
Severity: unknown
# IRA CONTINUAR PERGUNTANDO SE DEVO COLOCAR NAMESERVICE TAMBÉM, DESSA VEZ EU NÃO IREI ACEITAR PARA TESTAR.
# DAQUI PRA FRENTE VOU NEGAR TUDO
[1 - #include <abstractions/nameservice>]
2 - owner /etc/host.conf r,
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / (O)wner permissions off / Abo(r)t / (F)inish
Denying via an include file isnt supported by the AppArmor tools
Profile: /usr/bin/curl
Path: /etc/host.conf
New Mode: owner r
Severity: unknown
[1 - #include <abstractions/nameservice>]
2 - owner /etc/host.conf r,
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / (O)wner permissions off / Abo(r)t / (F)inish
Denying via an include file isnt supported by the AppArmor tools
Profile: /usr/bin/curl
Path: /etc/host.conf
New Mode: owner r
Severity: unknown
[1 - #include <abstractions/nameservice>]
2 - owner /etc/host.conf r,
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / (O)wner permissions off / Abo(r)t / (F)inish
= Changed Local Profiles =
The following local profiles were changed. Would you like to save them?
[1 - /usr/bin/curl]
(S)ave Changes / Save Selec(t)ed Profile / [(V)iew Changes] / View Changes b/w (C)lean profiles / Abo(r)t
Writing updated profile for /usr/bin/curl.
# Vamos conferir o que ele colocou que foi o openssl
root@cks-worker:/etc/apparmor.d# cat usr.bin.curl
# Last Modified: Thu Sep 12 13:46:48 2024
#include <tunables/global>
/usr/bin/curl {
#include <abstractions/base>
#include <abstractions/openssl>
/usr/bin/curl mr,
}
# Ainda não foi suficiente, pois sem resolver os nomes não vai chegar lá,
root@cks-worker:/etc/apparmor.d# curl devsecops.puziol.com.br
curl: (6) Could not resolve host: devsecops.puziol.com.br
# Vamos permitir o restante
root@cks-worker:/etc/apparmor.d# aa-logprof
root@cks-worker:/etc/apparmor.d# cat usr.bin.curl
# Last Modified: Thu Sep 12 13:52:25 2024
#include <tunables/global>
/usr/bin/curl {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/openssl>
/usr/bin/curl mr,
}
# Conseguimos
root@cks-worker:/etc/apparmor.d# curl devsecops.puziol.com.br
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>cloudflare</center>
</body>
</html>
Ativando e Desativando um Profile
root@cks-worker:# aa-disable curl
Disabling /usr/bin/curl.
# Um link simbólico foi criado
root@cks-worker:/etc/apparmor.d# ls -l /etc/apparmor.d/disable/
total 0
lrwxrwxrwx 1 root root 28 Sep 12 13:53 usr.bin.curl -> /etc/apparmor.d/usr.bin.curl
lrwxrwxrwx 1 root root 33 Aug 30 21:40 usr.sbin.rsyslogd -> /etc/apparmor.d/usr.sbin.rsyslogd
Para ativar é necessário colocar o profile em algum dos modos.
root@cks-worker:/etc/apparmor.d# aa-enforce curl
Setting /usr/bin/curl to enforce mode.
# E o link não existirá mais do disable
root@cks-worker:/etc/apparmor.d# ls -l /etc/apparmor.d/disable/
total 0
lrwxrwxrwx 1 root root 33 Aug 30 21:40 usr.sbin.rsyslogd -> /etc/apparmor.d/usr.sbin.rsyslogd
Remover um profile completamente implica em deletar o arquivo.
root@cks-worker:/etc/apparmor.d# rm -rf usr.bin.curl
## o comando aa-remove-unknow remove todos os profiles que não estão em uso.
Extra
Os profiles do AppArmor são arquivos de texto simples. Caminhos absolutos, bem como globbing de arquivo, podem ser usados ao especificar o acesso ao arquivo.
A maioria das regras de acesso a arquivo especifica o tipo de acesso que é permitido:
'r'
(leitura)'w'
(gravação)'x'
(execução)'m'
(mapa de memória como executável)'k'
(bloqueio de arquivo)'l'
(criação de hard links)'ix'
para executar outro programa com a nova política de herança de programa.'Px'
(executar em outro profile, após limpar o ambiente)'Cx'
(executar em um perfil filho, após limpar o ambiente)'Ux'
(executar sem confinamento, após limpar o ambiente).
O AppArmor oferece suporte a controles de acesso para:
- files
- Linux capabilities
- network
- mount, remount and umount
- root_pivot
- ptrace
- signal
- DBus
- unix domain sockets
Variáveis (por exemplo, @{HOME}
) podem ser definidas e manipuladas fora do profile ( #include <tunables/global>
para @{PROC}
e @{HOME}
)
Regras de negação explícitas são suportadas para substituir regras de permissão, por exemplo, o acesso a @{HOME}/bin/bad.sh
é negado com auditoria devido a, audit deny @{HOME}/bin/** mrwkl
, embora o acesso geral a @{HOME}
seja permitido com @{HOME}/** rw
.
Aproveitando esse gancho vamos explicar essa /usr/bin/curl mr
.
usr.bin/curl
/usr/bin/curl {
#include <abstractions/base>
/usr/bin/curl mr,
}
Isso significa que o arquivo pode ser mapeado em memória (m) e em modo leitura (r).
Ainda temos mais utilitários de comand line para nos ajudar na criação de profiles.
root@cks-worker:/etc/apparmor.d# apt install apparmor-easyprof apparmor-notify apparmor-utils -y
Usando o easyprof podemos criar um template inicial, nada muito fora do escopo do genprof mas pode ser usado caso necessário para manter um padrão.
# Esse comando somente gera a saída sendo necessário criar o arquivo.
root@cks-worker:/etc/apparmor.d# aa-easyprof /usr/bin/curl
# vim:syntax=apparmor
# AppArmor policy for curl
# ###AUTHOR###
# ###COPYRIGHT###
# ###COMMENT###
#include <tunables/global>
# No template variables specified
"/usr/bin/curl" {
#include <abstractions/base>
# No abstractions specified
# No policy groups specified
# No read paths specified
# No write paths specified
}
root@cks-worker:/etc/apparmor.d# aa-easyprof /usr/bin/curl > /etc/apparmor.d/usr.bin.curl
Diferentemente do aa-genprof
, o aa-easyprof
não carrega o profile no AppArmor, sendo necessário carregar manualmente. Confira com o aa-status
antes de aplicar o comando abaixo e verá que o profile não estará disponível.
root@cks-worker:/etc/apparmor.d# apparmor_parser /etc/apparmor.d/usr.bin.curl
Outra maneira de criar um profile é usando o comando aa-autodep
. Se fosse para o curl teríamos aa-autodep curl
.
Uma ferramenta que nos ajudará a ver os logs produzidos pelo AppArmor é o aa-notify
.
# -s numero de dias
root@cks-worker:/etc/apparmor.d# aa-notify -s 1
AppArmor denials: 33 (since Wed Sep 11 15:27:25 2024)
# Deixando mais verboso temos uma amostra de todas as recusas que já tivemos pelo profile do curl anteriomente.
root@cks-worker:/etc/apparmor.d#
root@cks-worker:/etc/apparmor.d# aa-notify -s 1 -v
Profile: /usr/bin/curl
Operation: open
Name: /etc/ssl/openssl.cnf
Denied: r
Logfile: /var/log/kern.log
(3 found, most recent from 'Thu Sep 12 13:43:28 2024')
Profile: /usr/bin/curl
Operation: create
Denied: create
Family: inet6
Socket type: dgram
Logfile: /var/log/kern.log
(6 found, most recent from 'Thu Sep 12 13:51:13 2024')
Profile: /usr/bin/curl
Operation: open
Name: /etc/host.conf
Denied: r
Logfile: /var/log/kern.log
(6 found, most recent from 'Thu Sep 12 13:51:13 2024')
Profile: /usr/bin/curl
Operation: open
Name: /run/systemd/resolve/stub-resolv.conf
Denied: r
Logfile: /var/log/kern.log
(6 found, most recent from 'Thu Sep 12 13:51:13 2024')
Profile: /usr/bin/curl
Operation: open
Name: /etc/nsswitch.conf
Denied: r
Logfile: /var/log/kern.log
(6 found, most recent from 'Thu Sep 12 13:51:13 2024')
Profile: /usr/bin/curl
Operation: create
Denied: create
Family: inet
Socket type: dgram
Logfile: /var/log/kern.log
(6 found, most recent from 'Thu Sep 12 13:51:13 2024')
AppArmor denials: 33 (since Wed Sep 11 15:27:36 2024)
For more information, please see: https://wiki.ubuntu.com/DebuggingApparmor
A maneira mais fácil de começar a criar um profile colocar ele no modo complain. Permitir que faça tudo, mas gerar logs para que possamos ir permitindo ao longo do caminho. Também podemos sempre usar o aa-logprof
para isso como mensionado anteriormente.
Embora os profiles do AppArmor sejam default-deny por padrão, adicionar regras de negação explícitas pode proteger contra erros.
Appamor com Docker e Kubernetes
Como hoje a conteinerização está em alta acho que vale a pena aprofundar mais nesse assunto.
Vamos criar um profile sem usar o aa-genprof
. Não estamos colocando nenhum binário específico para esse profile, apenas permissões.
Vamos fazer alguns comentários em caixa alta para diferenciar dos #includes.
#include <tunables/global>
# ESSE NOME SERÁ USADO PARA REFERENCIAR O PROFILE QUANDO QUISERMOS USAR
profile docker-nginx flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network inet tcp,
network inet udp,
network inet icmp,
deny network raw,
deny network packet,
file,
umount,
# REMOVENDO ACESSOS AOS DIRETÓRIOS ABAIXO PARA ESCRITA E CRIAÇÃO DE LINKS
deny /bin/** wl,
deny /boot/** wl,
deny /dev/** wl,
deny /etc/** wl,
deny /home/** wl,
deny /lib/** wl,
deny /lib64/** wl,
deny /media/** wl,
deny /mnt/** wl,
deny /opt/** wl,
deny /proc/** wl,
deny /root/** wl,
deny /sbin/** wl,
deny /srv/** wl,
deny /tmp/** wl,
deny /sys/** wl,
deny /usr/** wl,
audit /** w,
/var/run/nginx.pid w,
/usr/sbin/nginx ix,
# REMOVENDO ACESSO AOS SHELLS
deny /bin/dash mrwklx,
deny /bin/sh mrwklx,
deny /usr/bin/top mrwklx,
# ADICIONANDO ALGUMAS CAPABILITIES
capability chown,
capability dac_override,
capability setuid,
capability setgid,
capability net_bind_service,
# REMOVENDO PERMISSÃO DE ESCRITA EM TODOS OS ARQUIVOS EM /proc, MAS NÃO NOS SUBDIRETÓRIOS
deny @{PROC}/* w,
# REMOVENDO PERMISSÃO DE ESCRITA EM TODOS OS DIRETORIOS /proc/<number>/
deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
# REMOVENDO PERMISSÃO DE ESCRITA EM TODOS OS DIRETORIOS /proc/sys/** MENOS O /proc/sys/kernel
deny @{PROC}/sys/[^k]** w,
# REMOVENDO PERMISSÃO DE ESCRITA EXCETO EM shm** E /proc/sys/kernel
deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w,
# REMOVENDO PERMISSÕES DE ESCRITA, BLOQUEIO, CRIAÇAO DE LINKS E EXECUÇÃO EM /proc/mem,kmem,kcore,sysrg-trigger
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/mem rwklx,
deny @{PROC}/kmem rwklx,
deny @{PROC}/kcore rwklx,
# NÃO PERMITIR O COMANDO MOUNT
deny mount,
# REMOVENDO PERMISSÕES DE ESCRITA, BLOQUEIO, CRIAÇAO DE LINKS E EXECUÇÃO EM /sys/
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
# SOMENTE DENTRO DE /sys/fs
deny /sys/fs/[^c]*/** wklx,
deny /sys/fs/c[^g]*/** wklx,
deny /sys/fs/cg[^r]*/** wklx,
# REMOVENDO PERMISSÕES DE LEITURA, ESCRITA, BLOQUEIO, CRIAÇAO DE LINKS, E EXECUÇÃO
deny /sys/firmware/** rwklx,
deny /sys/kernel/security/** rwklx,
}
Vamos criar esse profile e torná-lo disponível.
root@cks-worker:~# vim /etc/apparmor.d/docker-nginx
# Carregar o profile
root@cks-worker:~# apparmor_parser /etc/apparmor.d/docker-nginx
Vamos rodar o nginx de diferentes maneiras e observar.
# Aqui não estamos passando nenhum profile
root@cks-worker:~# docker run nginx
# Aqui vamos usar a opção de segurança para passar um profile que nem existe.
# Veja que não é possível iniciar passando um profile inexistente.
root@cks-worker:~# docker run --security-opt apparmor=teste nginx
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: unable to apply apparmor profile: apparmor failed to apply profile: write /proc/self/attr/apparmor/exec: no such file or directory: unknown.
ERRO[0000] error waiting for container:
# Existe o profile do docker que é o docker-default além daquele que carregamos docker-nginx.
root@cks-worker:~# aa-status | grep docker
docker-default
docker-nginx
# Executando com o docker-default em modo detached para liberar o container e fazer análise.
root@cks-worker:~# docker run --security-opt apparmor=docker-default -d nginx
cf92415f4a8f7f1a233a3c80f49efb4e90d863f898149cf5ede7cefb76bd0127
root@cks-worker:~# docker exec -it cf92415f4a8f7f1a233a3c80f49efb4e90d863f898149cf5ede7cefb76bd0127 bash
root@cf92415f4a8f:/# touch teste # Foi possível criar um arquivo no /root
root@cf92415f4a8f:/# cat /proc/kcore
cat: /proc/kcore: Permission denied
root@cf92415f4a8f:/#
# Testando com o docker-nginx. Vamos primeiro sem o detached para ver a saída.
# Termos um permission denied, mas o container ainda continua vivo.
root@cks-worker:~# docker run --security-opt apparmor=docker-nginx nginx
/docker-entrypoint.sh: No files found in /docker-entrypoint.d/, skipping configuration
/docker-entrypoint.sh: 13: cannot create /dev/null: Permission denied
2024/09/12 18:49:21 [notice] 1#1: using the "epoll" event method
2024/09/12 18:49:21 [notice] 1#1: nginx/1.27.1
2024/09/12 18:49:21 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14)
2024/09/12 18:49:21 [notice] 1#1: OS: Linux 5.15.0-1067-gcp
2024/09/12 18:49:21 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2024/09/12 18:49:21 [notice] 1#1: start worker processes
2024/09/12 18:49:21 [notice] 1#1: start worker process 9
2024/09/12 18:49:21 [notice] 1#1: start worker process 10
# Vamos executar novamente, mas no modo detached e entrar no container para testar.
root@cks-worker:~# docker run --security-opt apparmor=docker-nginx -d nginx
c8d38a06828b6cfef268e044489f11fe528f7b85a820a3efc8a6915eff8d4b53
root@cks-worker:~# docker exec -it c8d38a06828b6cfef268e044489f11fe528f7b85a820a3efc8a6915eff8d4b53 bash
root@c8d38a06828b:~# touch teste
touch: cannot touch 'teste': Permission denied # Não foi possível criar no /root
root@c8d38a06828b:~# touch /tmp/test # nem no tmp
touch: cannot touch '/tmp/test': Permission denied
root@c8d38a06828b:~# mkdir /teste
root@c8d38a06828b:~# cd /teste/
root@c8d38a06828b:/teste# touch teste # Mas em uma pasta que não foi bloqueada sim.
root@c8d38a06828b:/teste# cat /proc/kcore
cat: /proc/kcore: Permission denied
Entendendo como funciona no Docker podemos passar par ao Kubernetes. Se quisermos utilizar isso no kubernetes, é necessário que:
- O Container runtime suporte o AppArmor.
- Apparmor esteja instalado em cada um dos nodes.
- O profile que for utilizar esteja disponível em cada um dos nodes.
- AppArmor profile seja especificado em nível de container ou em nível de pod.
Vamos testar.
root@cks-master:~# cat nginx.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx
name: nginx
spec:
# Nível de pod.
# securityContext:
# appArmorProfile:
# type: Localhost
# localhostProfile: docker-nginx
containers:
- image: nginx
name: nginx
resources: {}
# Nível de container
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: docker-nginx
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@cks-master:~# k apply -f nginx.yaml
pod/nginx created
root@cks-master:~# k exec -it pods/nginx -- bash
# Dentro do container do pod
root@nginx:/# touch /root/teste
touch: cannot touch '/root/teste': Permission denied
root@nginx:/# touch /tmp/teste
touch: cannot touch '/tmp/teste': Permission denied
root@nginx:/#
Se for especificado um profile que não existe o pod ficará com o status de Blocked esperando o profile ficar disponível.