리눅스 파일 권한 시스템의 이해와 응용 [with Proxmox, Docker]

서론

현재 Proxmox OS 기반의 홈서버를 구축하여 아래처럼 서비스들을 운용하고 있다.

Proxmox GUI | 출처 : 내 Proxmox

기본적으로 헤놀로지(XPEnology) NAS를 중심으로 하여 각 서비스들을 사용하는 구조이다.

현재 서비스들은 헤놀로지의 공유 폴더를 Proxmox OS로 마운트 한 뒤,
해당 마운트 디렉토리를 다시 각 VM, LXC 컨테이너 내부로 연결하여 사용하는 구조이다.

일례로, Dockge LXC 컨테이너 내부에 Immich(사진 관리 서비스)는 다음과 같은 흐름으로 구성된다.

XPEnology Volume → Proxmox Mount → Dockge Mount → Immich Container Volume

대충 아래같은 그림이라고 생각하면 되겠다.

디렉토리 마운트 구조 | 출처 : 나

위와 같은 구조를 설계하는 와중 지속적으로 겪은 문제가 존재하는데,
마운트 된 디렉토리에 접근할 때마다 “Permission Denied” 오류가 반복적으로 발생했다.

이에 이번 글을 통해 리눅스 파일 권한 구조에 대해 보다 알아보고자 한다.


본론

리눅스 파일 권한 구조

리눅스에서 모든 파일은 소유자(User)그룹(Group)기타 사용자(Others) 세 가지 범주로 접근 권한을 제어한다.

당연한 얘기겠지만, 디렉토리도 파일이다. 디렉토리 실행 권한이 있어야 하위 디렉토리로 진입할 수 있다.
리눅스 파일 권한 구조 | 출처 : 리눅스 파일 & 디렉토리 권한 (소유권 / 허가권 / 특수권한), Inpa Dev

우리가 흔히 말하는 755 권한을 해석해보면 아래와 같다.

  • 소유자(User) : 읽기, 쓰기, 실행 가능 (rwx) = 7
  • 그룹(Group) : 읽기, 실행 가능 (r-x) = 5
  • 기타(Others) : 읽기, 실행 가능 (r-x) = 5

이러한 권한 구조는 모두 익숙할 것이다.

아쉽게도 이번 문제는 단순한 chmod 문제가 아니라, UID와 GID 매핑 불일치에서 비롯된 것이었다.

UID(User ID)와 GID(Group ID)

리눅스의 파일 시스템의 권한은 이름이 아니라 숫자값, 즉 UID(User ID)와 GID(Group ID)로 구분된다.

'ls -al'을 통해 확인할 수 있는 소유자 이름은 내부적으로 /etc/passwd 에 등록된 사용자 UID를 이름으로 매핑해 보여주는 것뿐이다. 따라서, 두 시스템 간에 UID가 다르다면 동일한 이름의 사용자라도 전혀 다른 사용자로 인식된다.

이 UID, GID의 불일치가 이번 문제의 원인이었다.

통일되지 않은 UID, GID | 출처 : 내 Proxmox

NFS(Network File System)

일반적으로 우리가 다른 시스템의 파일을 사용하기 위해 NFS(Network File System)를 사용한다. 디렉토리 마운트(Mount)는 일전에 다뤄보았으니, 이번엔 NFS에 대해서만 이야기해보고자 한다.

전지전능한 주에서 이제는 범부로 영락해 버리신, 그러나 언젠간 복귀하실 ChatGPT님께 자문을 구해보았다.

참고로 필자는 GPT 결제 끊었다. 언젠가 왕좌에 복귀하신다면 그간의 무례를 사죄드리며 다시 결제할 예정이다.
NFS(Network File System)는 네트워크를 통해 다른 컴퓨터의 파일 시스템을 로컬 디렉터리처럼 마운트 해서 사용하는 프로토콜이다. 즉, 리눅스·유닉스 환경에서 네트워크 기반 공유 폴더 시스템을 구현할 때 사용하는 기술이다.

가장 간단히 말하면,
“서버의 특정 디렉터리를 네트워크를 통해 다른 클라이언트가 로컬 폴더처럼 접근하도록 하는 시스템”이다.


출처 | OpenAI. (2025). ChatGPT (GPT-5 Version) [Large language model]. https://chatgpt.com/
NFS 구조 도식 ❘ 출처 : [Linux] NFS(Network File System), 뭉게뭉게 클라우드

그냥 쉽게 네트워크 기반의 파일 마운트 방식이라고 이해하면 될 것 같다.

이러한 NFS의 가장 큰 특징으로는, NFS 서버에서는 '/etc/exports'를 통해 디렉토리의 마운트를 허용하는 데, 해당 설정 파일에 명시된 UID, GID를 숫자 형태 그대로 전달한다는 점에 있다.

이 말이 무슨 뜻이냐면, NFS 서버에 root(UID 1000)라는 사용자가 존재하고, 클라이언트에 guest(UID 1000) 사용자가 존재할 경우, guest(UID 1000) 사용자 권한으로 해당 디렉토리의 원본 소유자(root)처럼 동작할 수 있다는 것이다.

이러한 특징으로 인해 NFS를 사용할 땐 굉장한 주의가 필요하다.

자칫 잘못하면 엄한 사람에게 권한을 허용해 줄 수 있게 된다.

그렇다면 원인은?

여기까지 미루어 보면 문제점이 어느 정도 예상이 된다.

  • NFS 서버(XPEnology)의 UID/GID
  • Proxmox OS의 UID/GID
  • Dockge LXC의 UID/GID
  • Immich 컨테이너 내부의 UID/GID

위 4가지 단계 중 일부, 혹은 전체에서 UID/GID가 불일치하여 발생한 문제일 것이란 가설을 세워볼 수 있다.

그렇다면 가설을 검증하기 위해 NFS 서버에 생성된 볼륨의 권한부터 살펴보자.
NFS를 통해 외부로 공유되는 모든 볼륨 정보는 '/etc/exports'를 통해 확인할 수 있다.

/etc/exports | 출처 : 내 Xpenology

상기 설정 중 유심히 살펴봐야 할 부분은 'no_root_squash'와 'anonuid=1025' 부분이다.

Root Squash와 UID

앞서 언급하기로, NFS는 디렉토리의 UID, GID를 클라이언트에 그대로 전달한다고 하였다. 이로 인해 마운트 시스템에 존재하는 UID가 우연히 일치한다면, 아찔한 보안사고가 발생할 수 있게 된다.

최근 이슈가 된 삼* 바이오로직스의 공유폴더가 공개된 것도 이와 관련이 있지 않을까 하는 생각을 잠깐 해봤다.

'root_squash'는 이를 방지하기 위한 NFS의 보안체계 중 하나로, 원격 클라이언트의 root(UID 0) 사용자의 접근을 익명 사용자(anouid)로 강제 변환시키는 보안 체계이다. 이를 통해 NFS 클라이언트의 root를 익명 사용자로 매핑시켜 NFS 서버의 root 계정과 분리하는 것이다.

현재 생성된 볼륨들의 경우 'no_root_squash' 옵션을 사용하여 Proxmox의 root가 NFS의 root와 동일시되게 된다.

그렇다면 또 왜?

그렇다면 이 시점에서 또 의문이 생겨야 한다.

Proxmox와 NFS 서버의 root가 동일하다면, 왜 권한 문제가 발생한 것일까?
여기서부터는 Proxmox LXC의 Privileged에 대해 알아야 한다.

앞서 언급한 NFS의 root squash 옵션과 동일하게, Proxmox LXC에는 Privileged 옵션이 존재한다.

Proxmox의 모든 LXC들은 기본적으로 unprivileged(특권이 없는) 상태로 실행하는데, 이 경우 컨테이너 내부의 사용자들은 호스트(Proxmox) 입장에서는 UID 100000 처럼 매핑되어 버린다.

Proxmox LXC Unprivileged Option | 출처 : 내 Proxmox

이 설정으로 인해 Dockge LXC의 사용자가 익명화(일종의 squash)되게 되고, 다시 NFS 서버의 'anonuid=1025' 설정에 의해 익명 사용자의 모든 접근이 guest(1025)로 처리된다.

당연하게도 공유폴더를 생성할 때 guest 사용자는 접근이 불가하도록 생성하였으니, LXC 내부에서 마운트 된 디렉토리에 접근할 때 Permission Denied가 발생하게 된 것이다.

필자는 혼자 쓰는 서버이기에 문제가 된 LXC를 privileged로 변경하여 해결했지만 근본적인 해결책은 아니다.

결국 root 사용자가 익명화되는걸 방지한 미봉책으로,
Dockge 내부에 있는 컨테이너들 중 일부가 root가 아닌 다른 사용자로 띄워지면 동일 현상이 발생할 것이다.

제일 확실한 수단은 그냥 공유폴더의 접근 권한을 guest에 대해서도 허용해 주면 해결되지 않을까 싶다.

이를 요약해 보면 아래와 같이 거슬러 올라가는 구조이다.

XPEnology Volume
Proxmox NFS Mount
LXC Container (unprivileged root → uid=100000 → guest 매핑)
Docker Container (Immich, 컨테이너 내부 사용자 UID)

번외. 그렇다면 도커(Docker)는?

앞서 다룬 내용은 UID/GID 불일치로 인해 발생하는 문제였다. 그렇다면 여기서 또 하나의 의문이 떠오른다.

우리는 호스트 디렉토리를 도커 컨테이너 내부로 마운트 하여 자유롭게 사용하곤 한다. 근데 어떻게 도커가 마운트 된 호스트 디렉토리 내부에 자유롭게 읽기, 쓰기, 실행이 가능한 걸까? 단순히 '마운트 하였으니까 가능하지'라고 넘어가기엔 앞서 살펴본 내용을 보니 그럴 리가 없다는 사실을 알 수 있다.

그래서 컨테이너 내부에서 ID를 직접 확인해 보았더니 놀라운 사실을 알 수 있었다.

root@aafc1c025e8c:/app# id
uid=0(root) gid=0(root) groups=0(root)

그렇다. Docker 컨테이너는 root 권한(UID 0)으로 실행되는 것이다. 그렇다면 간혹 호스트에서 Docker Volume을 열어보려 할 때 Permission Denied가 발생하는 것도 납득이 된다.

  • 컨테이너 내부 프로세스 = root (UID 0)
  • 호스트에서 로그인한 일반 사용자 = root 아님
  • 따라서 호스트 사용자는 root가 생성한 파일에 접근할 권한이 없다

즉, 호스트에 로그인한 사용자 계정이 root가 아니므로, root가 생성한 Docker Volume을 제어할 수 없다는 것이 자연스레 납득된다.

이쯤 되니 흥미로운 가설 하나가 또 떠오른다.

도커 컨테이너는 자체적으로 root 권한으로 동작한다.

그렇다면 root 권한은 없지만 Docker 그룹에 속해있다면, 
호스트의 '/' 디렉토리를 컨테이너로 마운트 하면 사실상의 root처럼 동작할 수 있는 것 아닌가?

이게 가능하다면 사실상 Docker 그룹 = root 그룹이 되는 셈이다.

실험 결과

일전에 pfSense 내부망 설정을 위해 만들어두었던 Ubuntu VM에서 실험을 진행해 보았다.

실험을 위해 root 계정으로 Docker를 설치 및 실행한 뒤, test(UID 1001)을 만든 다음 Docker 그룹에 추가하였다.

root@siroh-Standard-PC-i440FX-PIIX-1996:~# sudo usermod -aG docker tester
root@siroh-Standard-PC-i440FX-PIIX-1996:~# su - tester

tester@siroh-Standard-PC-i440FX-PIIX-1996:~$ id
uid=1001(tester) gid=1001(tester) groups=1001(tester),999(docker)

tester@siroh-Standard-PC-i440FX-PIIX-1996:~$ docker --version
Docker version 29.0.2, build 8108357

tester@siroh-Standard-PC-i440FX-PIIX-1996:~$ ls -ld /root
drwx------ 4 root root 4096 11월 19 14:08 /root

tester@siroh-Standard-PC-i440FX-PIIX-1996:~$ ls -al /root/
ls: cannot open directory '/root/': Permission denied

위처럼 tester 사용자로는 '/root' 디렉토리로 접근이 불가능하다는 사실을 알 수 있다.

그렇다면 '/' 디렉토리를 도커 컨테이너 내부로 마운트 한 다음 접근을 시도하면 어떨까? 이를 위해 간단히 Ubuntu 컨테이너를 만든 다음 컨테이너에 '/' 디렉토리 전체를 마운트 하여 시도해 보았다.

tester@siroh-Standard-PC-i440FX-PIIX-1996:~$ docker run -it --rm \
    -v /:/host \
    ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
20043066d3d5: Pull complete 
Digest: sha256:c35e29c9450151419d9448b0fd75374fec4fff364a27f176fb458d472dfc9e54
Status: Downloaded newer image for ubuntu:latest

root@9433c46a3e3e:/# id
uid=0(root) gid=0(root) groups=0(root)

root@9433c46a3e3e:~# cd /root    
root@9433c46a3e3e:~# ls -al
total 16
drwx------ 2 root root 4096 Oct 13 14:09 .
drwxr-xr-x 1 root root 4096 Nov 19 05:21 ..
-rw-r--r-- 1 root root 3106 Apr 22  2024 .bashrc
-rw-r--r-- 1 root root  161 Apr 22  2024 .profile

root@9433c46a3e3e:~# echo test > test.txt
root@9433c46a3e3e:~# ls -aln
total 20
drwx------ 1 0 0 4096 Nov 19 05:31 .
drwxr-xr-x 1 0 0 4096 Nov 19 05:21 ..
-rw-r--r-- 1 0 0 3106 Apr 22  2024 .bashrc
-rw-r--r-- 1 0 0  161 Apr 22  2024 .profile
-rw-r--r-- 1 0 0    5 Nov 19 05:31 test.txt

root@9433c46a3e3e:~# cat /etc/shadow
root:!:20358:0:99999:7:::
daemon:*:19977:0:99999:7:::
bin:*:19977:0:99999:7:::

놀랍게도 컨테이너 내부에서 호스트, 즉 Ubuntu VM의 root 권한을 탈취하는데 성공하였다. 즉, 도커 그룹(docker group)에 사용자를 추가하는 것은 사실상 root 권한을 주는 것과 같다는 사실을 알 수 있다.

심지어 /etc/shadow, /etc/passwd 도 접근이 가능하였다.

한계점

같은 실험을 기존 작업환경인 macOS + OrbStack에서 수행하니 결과가 상이하였다.

마찬가지로 '/' 디렉토리를 마운트 하여 도커 내부에서 접근은 가능하였으나, 아래와 같은 현상이 발생하였다.

  • 컨테이너 내부 ID는 root(UID 0)으로 보인다.
  • 하지만 실제 macOS에서의 root가 아닌, 컨테이너 내부에서만 root로써 동작한다.

이유는 간단하다. 기본적으로 Linux Docker는 Kernel이 제공하는 namespaces, cgroups, chroot를 통해 커널의 일부를 격리한다. 즉, 완전히 별개의 커널이 아닌 일부만 격리된, 같은 커널을 공유하는 셈이므로 root 권한 탈취가 가능하였다.

반면 macOS는 Linux Kernel이 제공하는 namespaces, cgroups, chroot와 같은 기능을 사용하지 못한다. 때문에 OrbStack(=Docker Desktop)은 자체적으로 Linux VM을 만든 다음 도커 컨테이너를 생성한다. 즉, Linux Docker처럼 호스트 커널을 공유하는 것이 아닌 별개의 커널(Linux VM의 Kernel)을 사용하는 셈이다.

이로 인해 Linux와는 달리 호스트(macOS)의 root 권한을 탈취당하는 것을 방지할 수 있는 것이다.

Linux VS macOS Kernel | 출처 : Generated by Google Gemini


결론

처음 문제의 발단은 Immich LXC에서 Permission Denied가 발생하여 찾아보기 시작한 것이었다. 이러한 공부를 통해 얻는 것도 홈서버를 구매함으로써 생긴 이점 중 하나가 아닌가 싶다.

시행착오를 통해 얻는 무형자산들로 홈서버 구매비용의 어느 정도는 멘징 했다고 합리화하였다.

특히 놀랐던 사실은 그동안 무분별히 사용해 왔던 Docker 권한이 이런 식으로까지 확장될 수 있었단 점이다.

물론 macOS 같은 환경에서는 이를 방지할 수 있다곤 하지만, 어느 서버가 macOS에서 돌겠나. Linux에서 돌지.

찾아보니 Rootless Docker를 사용하면 이러한 것을 방지할 수 있다고 한다. 관심 있다면 아래 글을 보면 좋을 듯하다

Rootless Docker: sudo 없이 Docker 실행하기
Docker는 보통 루트(root) 권한이 필요한 프로그램입니다. 하지만 저는 보안담당자로서 루트 권한을 사용하지 않고 Docker를 실행하는 것이 더 안전하게 사용할 수 있다고 생각합니다. 그럴 때 유용한 방법이 바로 Rootless Docker라는 방법으로 공식홈페이지에서는 Docker 데몬과 컨테이너를 비루트 사용자로 실행할 수 있는 모드로, 시스템의 보안을 강화하고 루트 권한 없이도 Docker를 사용할 수 있도록 해줍니다. 이 글에서는 Rootless Docker를 설치하고 설정하는 방법을 쉽고 간단하게 알려드릴게요. 1. Rootless Docker란?Docker는 기본적으로 루트 권한을 사용해 데몬을 실행하고 컨테이너를 관리합니다. 하지만 Rootless Docker는 이름 그대로 루트 권한…


참고자료

Unprivileged LXC containers - Proxmox VE
Proxmox LXC 권한이있는 컨테이너(Privileged)와 권한이없는 컨테이너(Unprivileged) 차이점.
LXC를 생성하실때 참고하시면 좋습니다. 기본적으로 LXC의 경우 host의 커널을 공유하기때문에 권한을 지정해서 생성할 수 있습니다. NFS를 사용하시려면 반드시 권한이 있는 컨테이너 옵션으로 생성하셔야합니다. Pr…
🐧 리눅스 파일 & 디렉토리 권한 (소유권 / 허가권 / 특수권한)
파일 및 디렉터리 권한 리눅스는 여러사용자가 들어와 사용하는 멀티유저 시스템이다. 따라서 여러 사용자가 들어와 사용하다보면, 서버에 올려진 비밀자료 열람과 그것을 변조,수정,삭제를 할 우려가 높아지게 된다. 이 문제를 극복하기위해 리눅스 OS에는 특별한 기능이 있는데 바로 퍼미션(권한)이라는 기능이다. 윈도우 OS에도 퍼미션기능이 있긴하지만, 여러사용자들이 들어오는 서버용이 아니기에 리눅스처럼 활성화 되어 있지않는다. 이 기능으로 사용자들은 자신한테 읽기 퍼미션이 부여된 파일만 읽을 수 있고, 쓰기 퍼미션이 부여된 파일만 쓰기와 수정이 가능하고, 실행 퍼미션이 부여된 파일만 실행이 가능하게 된다. 그리고 열람 권한 뿐만 아니라 파일 소유자 개념도 지원해준다. 소유권 & 허가권 확인 방법 파일 권한 정보…
[Linux] NFS(Network File System)
✔️ NFS(Network File System)란 ? NFS란 스토리지 서버와 일반(Web, DB) 서버들이 네트워크를 통해 저장 공간을 공유하는 프로세스이다. 네트워크에 연결된 다른 컴퓨터의 하드 디스크를 내 컴퓨터의 하드처럼 사용한다. 공통으로 사용되는 파일이 있으면 각 컴퓨터에 파일을 저장하지 않고 서버 1개에 저장해 효율적으로 사용한다. 즉, 컴퓨터 사용자가 원격 컴퓨터에 있는 파일을 마치 자신의 컴퓨터에 있는 것처럼 검색, 저장, 수정하도록 해주는 클라이언트/서버형 응용 프로그램이다. ✔️ NFS 특징 다른 서버에 있는 디렉토리를 나의 것처럼 사용하는 것이기 때문에 보안에 취약하다. (회사에서는 잘 사용하지 않음) 네트워크를 이용하므로 내장 하드보다 속도가 느리다. NFS를 사용하려면 서버나…
리눅스 NFS
NFS란 네트워크를 통해 파티션을 공유하도록 제공하는 서비스입니다. 유닉스 계열의 거의 모든 시스템에서 공유 가능합니다. (윈도우와 리눅스 간은 불가능) 설정 파일은 /etc/exports 하나밖에 없습니다. NFS는 사용하기가 굉장히 까다롭습니다. 서버-클라이언트간 계정 매핑은 UID를 기준으로 하기 때문에 만약 UID만 같은 다른 계정이 접근한다면 권한을 획득하게 됩니다. (예를 들면 여러 유닉스 시스템이 있을 때) 그렇기 때문에 유저를 생성할 때 반드시 계획을 세우고 만들어야 합니다. 명령어는 클라이언트 측에서 입력하면 됩니다. 형식은 다음과 같습니다. mount –t nfs NFS서버IP:/공유디렉토리 /마운트할디렉토리 mount –t nfs 192.168.10.102:/home/a1 /home/…
Rootless Docker: sudo 없이 Docker 실행하기
Docker는 보통 루트(root) 권한이 필요한 프로그램입니다. 하지만 저는 보안담당자로서 루트 권한을 사용하지 않고 Docker를 실행하는 것이 더 안전하게 사용할 수 있다고 생각합니다. 그럴 때 유용한 방법이 바로 Rootless Docker라는 방법으로 공식홈페이지에서는 Docker 데몬과 컨테이너를 비루트 사용자로 실행할 수 있는 모드로, 시스템의 보안을 강화하고 루트 권한 없이도 Docker를 사용할 수 있도록 해줍니다. 이 글에서는 Rootless Docker를 설치하고 설정하는 방법을 쉽고 간단하게 알려드릴게요. 1. Rootless Docker란?Docker는 기본적으로 루트 권한을 사용해 데몬을 실행하고 컨테이너를 관리합니다. 하지만 Rootless Docker는 이름 그대로 루트 권한…