Ubuntu AppArmor 커스텀 프로파일 작성하기 — merged /usr와 apparmor-utils에서 실제로 겪은 문제

프로세스 하나가 뚫리면 그 프로세스가 가진 권한 전체가 그대로 노출된다. 파일 소유권이나 setuid만으로는 “이 프로그램은 딱 이 파일 몇 개만 건드릴 수 있다”는 세밀한 통제가 안 된다. AppArmor는 경로 기반 MAC(Mandatory Access Control)으로 프로그램별 접근 가능한 파일과 기능을 화이트리스트로 제한한다. 우분투는 기본 탑재되어 있어 커널 옵션을 새로 켤 필요도 없다. 이 글에서는 스크립트 하나에 커스텀 프로파일을 만들어 적용하면서 실제로 두 번 삽질한 과정까지 그대로 정리한다.

테스트 스크립트와 기본 동작

파일 두 개를 읽는 단순한 스크립트다. 하나는 프로파일이 허용할 파일, 하나는 막을 파일이다.

#!/bin/bash
echo "== allowed.txt =="
cat /home/noble/work/aa_demo/allowed.txt
echo "== denied.txt =="
cat /home/noble/work/aa_demo/denied.txt

AppArmor 프로파일 없이 실행하면 당연히 둘 다 읽힌다.

$ ./myapp.sh
== allowed.txt ==
이 파일은 읽어도 됩니다
== denied.txt ==
이 파일은 프로파일이 막습니다

프로파일 작성과 로드 — 첫 시도는 실패했다

스크립트 자신과 bash, cat 실행, 그리고 allowed.txt 읽기만 허용하는 프로파일을 만들었다.

#include <tunables/global>

/home/noble/work/aa_demo/myapp.sh {
  #include <abstractions/base>

  /bin/bash rix,
  /bin/cat rix,
  /home/noble/work/aa_demo/myapp.sh r,
  /home/noble/work/aa_demo/allowed.txt r,
}
$ sudo apparmor_parser -r myapp.profile
$ ./myapp.sh
== allowed.txt ==
./myapp.sh: line 3: /usr/bin/cat: Permission denied
== denied.txt ==
./myapp.sh: line 5: /usr/bin/cat: Permission denied

로드는 성공했는데, 허용했어야 할 allowed.txt까지 막혔다. dmesg를 보니 원인이 바로 나온다.

$ sudo dmesg | grep DENIED
apparmor="DENIED" operation="exec" profile="/home/noble/work/aa_demo/myapp.sh" name="/usr/bin/cat" requested_mask="x" denied_mask="x"

프로파일에는 /bin/cat을 허용했는데, 커널이 실제로 검사한 경로는 /usr/bin/cat이었다. 이 우분투는 merged-/usr 레이아웃이라 /binusr/bin으로 가는 심볼릭 링크인데, AppArmor는 심볼릭 링크를 따라간 뒤의 실제 경로를 기준으로 매칭한다. /bin/cat이라는 규칙은 글자 그대로 그 경로만 매칭하지, 링크가 가리키는 /usr/bin/cat까지 자동으로 허용해주지 않는다. /usr/bin/bash, /usr/bin/cat으로 고치니 바로 해결됐다.

$ sudo apparmor_parser -r myapp.profile   # /usr/bin/* 로 수정 후
$ ./myapp.sh
== allowed.txt ==
이 파일은 읽어도 됩니다
== denied.txt ==
cat: /home/noble/work/aa_demo/denied.txt: Permission denied

의도한 그대로다. allowed.txt는 읽히고 denied.txt만 막힌다.

complain 모드로 프로파일 다듬기

새 프로파일을 처음부터 enforce로 밀어붙이면 필요한 권한을 빠뜨려 앱이 깨지기 쉽다. aa-complain으로 일단 “막지는 말고 로그만 남기는” 모드로 돌려보는 게 정석이다. 그런데 이것도 한 번 걸렸다.

$ sudo aa-complain /home/noble/work/aa_demo/myapp.sh
Profile for /home/noble/work/aa_demo/myapp.sh not found, skipping

apparmor_parser -r로 임의 경로의 파일을 직접 로드하면 커널에는 올라가서 aa-status에는 나타나지만, aa-complain/aa-enforce/aa-logprof 같은 헬퍼 스크립트는 /etc/apparmor.d/ 안에 있는 프로파일 파일만 대상으로 찾는다. 프로파일을 그 디렉터리로 옮기고 나서야 인식했다.

$ sudo cp myapp.profile /etc/apparmor.d/home.noble.work.aa_demo.myapp.sh
$ sudo apparmor_parser -r /etc/apparmor.d/home.noble.work.aa_demo.myapp.sh
$ sudo aa-complain /home/noble/work/aa_demo/myapp.sh
Setting /home/noble/work/aa_demo/myapp.sh to complain mode.

$ ./myapp.sh
== denied.txt ==
이 파일은 프로파일이 막습니다        # complain 모드라 실제로는 읽힘

$ sudo dmesg | grep denied.txt | tail -1
apparmor="ALLOWED" operation="file_perm" name=".../denied.txt" requested_mask="r" denied_mask="r"

여기서 흥미로운 부분은 로그의 apparmor="ALLOWED"인데 denied_mask="r"도 같이 찍힌다는 점이다 — “원래 규칙대로면 막혔어야 하는데 complain 모드라 통과시켰다”는 걸 그대로 기록해두는 것이다. aa-logprof는 이 로그를 읽어서 “이 접근을 허용 규칙에 추가할까요?”라고 물어보며 프로파일을 자동으로 다듬어주는 도구다. 확인이 끝나면 다시 enforce로 돌린다.

$ sudo aa-enforce /home/noble/work/aa_demo/myapp.sh
Setting /home/noble/work/aa_demo/myapp.sh to enforce mode.

$ ./myapp.sh
== denied.txt ==
cat: .../denied.txt: Permission denied

주의사항

  • merged-/usr 배포판(최근 우분투는 기본값)에서는 /bin·/sbin 대신 실제 물리 경로(/usr/bin, /usr/sbin) 기준으로 규칙을 써야 한다. ls -la /bin으로 심볼릭 링크 여부를 먼저 확인하는 습관을 들이는 게 좋다.
  • aa-complain/aa-enforce/aa-logprofapparmor-utils 패키지에 들어 있고, 최소 설치 이미지에는 기본으로 없다. 게다가 이 도구들은 /etc/apparmor.d/ 안의 프로파일 파일만 인식하므로, apparmor_parser로 임의 위치에서 바로 로드했다면 aa-status에는 보여도 이 헬퍼들 대상은 아니다.
  • complain 모드는 디버깅용이지 운영용이 아니다. 아무것도 막지 않고 로그만 남기므로, 프로파일을 다듬은 뒤에는 반드시 aa-enforce로 되돌려야 한다.
  • rix(하위 프로세스를 같은 프로파일로 실행), ix(부모 프로파일 상속), px(다른 프로파일로 전환) 같은 실행 권한 플래그는 옵션이 많아 처음부터 완벽하게 고르기 어렵다. 우선 ix/rix 정도로 시작해 complain 모드로 실제 필요한 권한을 관찰하며 점진적으로 좁혀가는 쪽이 현실적이다.

마무리

AppArmor 프로파일 자체의 문법은 단순하지만, 실제로 걸리는 문제는 문법이 아니라 “커널이 실제로 어떤 경로를 보는가”와 “관리 도구가 어디서 프로파일을 찾는가” 같은 운영상의 디테일이었다. 이 두 가지만 알고 나면 나머지는 complain 모드로 필요한 권한을 관찰하고 하나씩 추가해나가는 반복 작업에 가깝다. 컨테이너나 데몬처럼 외부 입력을 직접 처리하는 프로세스에는 이렇게 화이트리스트 방식으로 접근을 좁혀두는 것만으로도 침해 시 피해 범위를 크게 줄일 수 있다.

참고

답글 남기기