AI 프로젝트 글/CS 공부

Docker와 Synology 권한을 이해하는 UID, GID, ACL

바로퇴장 2026. 8. 18. 00:40

한 문장 정의

UID와 GID는 Linux가 사용자와 그룹을 구분하는 숫자 신분증이고, ACL은 특정 사용자와 그룹이 파일이나 디렉터리에 어떤 작업을 할 수 있는지 표현하는 권한 목록이다.

이 개념이 중요한 이유

Docker 컨테이너가 정상적으로 실행되고 NAS 폴더가 컨테이너에 마운트됐는데도 Permission denied가 발생할 수 있다.

이때 흔히 다음과 같이 생각하기 쉽다.

“컨테이너 안에 mcp라는 사용자가 있고 비루트로 실행했으니 권한 설정이 끝난 것 아닌가?”

하지만 Linux 파일시스템은 대부분 사용자 이름보다 숫자 UID와 GID를 기준으로 접근 권한을 판단한다. 컨테이너 안의 사용자 이름과 Synology DSM의 사용자 이름이 같아 보여도 숫자 ID가 다르면 같은 신분으로 취급되지 않는다.

이 개념은 NAS뿐 아니라 Kubernetes의 securityContext, NFS, CI 실행 계정, 공유 개발 볼륨과 운영 서버 권한 문제에서도 반복해서 등장한다.

동작 원리

1. 사용자 이름은 사람이 보기 위한 이름이다

Linux에서 다음 명령을 실행하면 현재 프로세스의 숫자 신분을 볼 수 있다.

id

예상 출력의 형태는 다음과 같다.

uid=<UID>(service-user) gid=<GID>(service-group) groups=...

프로그램이 파일을 열려고 하면 커널은 이 숫자 신분과 파일의 소유권·권한·ACL을 비교한다.

2. Docker의 user 설정은 숫자 신분을 지정한다

Compose에서 다음과 같이 설정할 수 있다.

services:
  app:
    user: "${RUN_UID}:${RUN_GID}"

이 설정은 컨테이너 프로세스가 지정한 UID와 GID로 실행되게 한다. 하지만 이 숫자와 일치하는 DSM 계정을 자동으로 생성하거나 공유 폴더 권한을 자동으로 부여하지는 않는다.

3. bind mount는 호스트 권한을 없애지 않는다

volumes:
  - "${HOST_PATH}:/data/photos:ro"

호스트의 폴더를 컨테이너에 연결해도 호스트 파일시스템의 소유권과 접근 제어는 그대로 적용된다. 컨테이너 안에서 /data/photos로 보이더라도 실제 접근 허용 여부는 Synology의 파일 권한과 ACL이 결정한다.

4. :ro와 읽기 권한은 다른 문제다

ro는 마운트된 데이터를 수정하지 못하게 하는 옵션이다. 읽기 권한을 새로 부여하는 옵션이 아니다.

따라서 다음 두 조건이 모두 필요하다.

  • DSM 권한: 서비스 계정이 폴더를 읽을 수 있어야 한다.
  • Docker 마운트: 읽을 수는 있지만 쓸 수 없도록 :ro로 연결한다.

이를 표로 구분하면 다음과 같다.

DSM 읽기 권한Docker :ro결과

없음 있음 읽기 실패, 쓰기도 실패
있음 있음 읽기 성공, 쓰기 실패 — 이번 프로젝트의 목표
있음 없음 읽기와 쓰기가 가능할 수 있어 위험 증가

5. POSIX 권한과 ACL

기본 Linux 권한은 소유자·그룹·기타 사용자에 대해 rwx를 표현한다.

  • r: 파일 내용 또는 디렉터리 목록 읽기
  • w: 파일 수정, 생성 또는 삭제
  • x: 실행 또는 디렉터리 진입

ACL은 이 기본 구조보다 세밀하게 특정 사용자나 그룹별 권한을 추가할 수 있다. Synology DSM의 공유 폴더 권한은 이런 접근 제어와 연결되므로, 단순히 컨테이너 안의 ls -l 결과만 보고 모든 권한을 판단하면 놓치는 부분이 생길 수 있다.

프로젝트에서 만난 사례

Synology 사진 MCP 컨테이너는 처음에 이미지에 정의된 임의의 비루트 UID로 실행됐다. 프로세스와 HTTP 상태 확인은 정상이었지만, 사진 공유 폴더의 목록 읽기는 거부됐다.

원인은 다음과 같았다.

  1. 컨테이너는 임의 UID로 실행됐다.
  2. DSM 공유 폴더는 별도의 전용 계정에 읽기 권한을 부여했다.
  3. 두 숫자 신분이 일치하지 않았다.
  4. 마운트는 존재했지만 DSM ACL 관점에서 읽을 수 없는 사용자가 접근했다.

해결 방법은 컨테이너를 root로 실행하는 것이 아니라, DSM 전용 계정의 실제 UID/GID를 확인해 Compose의 실행 사용자에 매핑하는 것이었다.

관련 실습: Synology NAS 사진을 안전하게 AI에 연결하기

인증, 인가, 격리를 구분하기

이번 문제를 이해하려면 세 개념을 분리해야 한다.

구분질문프로젝트 예시

인증 누구인가? MCP Bearer 토큰이 올바른가?
인가 무엇을 할 수 있는가? DSM 서비스 계정이 사진 폴더를 읽을 수 있는가?
격리 침해돼도 어디까지 영향을 주는가? 비루트, :ro, capability 제거

올바른 토큰으로 MCP 인증을 통과해도 컨테이너 프로세스의 파일 인가가 실패할 수 있다. 반대로 파일을 읽을 수 있는 프로세스라도 MCP 인증이 없으면 네트워크 요청은 거부해야 한다.

자주 혼동하는 개념

“비루트면 자동으로 안전하다”

비루트 실행은 중요하지만 그것만으로 충분하지 않다. 비루트 계정에 지나치게 넓은 NAS 권한을 주면 최소 권한이 아니다. 반대로 권한을 전혀 주지 않으면 서비스가 동작하지 않는다.

“볼륨이 보이면 읽을 수 있다”

마운트 지점이 존재하는 것과 내부 파일을 읽을 권한이 있는 것은 다르다. mount 또는 컨테이너 설정 확인과 실제 ls·파일 열기 테스트를 분리해야 한다.

“chmod 777이면 해결이다”

권한 오류를 빠르게 없앨 수는 있지만 모든 사용자에게 넓은 권한을 주어 문제의 원인을 숨기고 공격 범위를 키울 수 있다. 운영 환경에서는 어떤 신분에 어떤 권한이 필요한지 확인해야 한다.

“root로 실행하면 해결이다”

동작 여부를 잠깐 진단하는 데 사용할 수는 있어도 최종 해결책으로 삼으면 컨테이너 침해 시 피해 범위가 커진다. 이번 프로젝트는 전용 계정의 숫자 ID를 매핑하는 방식으로 해결했다.

간단한 실습

아래 명령은 컨테이너 안에서 실행한다. 실제 서비스 이름과 경로는 자신의 환경에 맞게 바꾼다.

1. 실행 신분 확인

id

목적: 컨테이너 프로세스가 예상한 UID/GID로 실행되는지 확인한다.

2. 숫자 소유권과 권한 확인

ls -ldn /data/photos

목적: 사용자 이름 변환 없이 숫자 UID/GID와 기본 권한을 확인한다.

3. 읽기 확인

find /data/photos -maxdepth 1 -type f -print -quit

예상 결과: 읽기 권한이 있으면 첫 번째 파일 경로가 출력된다. 아무 파일도 없을 수 있으므로, 테스트 파일이 존재하는 환경에서 실행해야 한다.

4. 쓰기 차단 확인

touch /data/photos/write-test.txt

예상 결과: 읽기 전용 마운트라면 Read-only file system 또는 권한 거부로 실패해야 한다.

주의: 쓰기 가능한 운영 폴더에서 실행하면 실제 파일이 생성될 수 있다. 반드시 별도 테스트 폴더와 읽기 전용 마운트에서만 수행한다. 실수로 생성됐다면 대상 경로를 확인한 뒤 테스트 파일만 삭제한다.

운영과 보안 관점

권한 설계는 다음 순서로 점검하면 이해하기 쉽다.

  1. 자산: 어떤 데이터를 보호하는가?
  2. 신분: 어떤 프로세스가 어떤 UID/GID로 접근하는가?
  3. 인가: DSM에서 어느 폴더에 어떤 권한을 부여했는가?
  4. 격리: 컨테이너가 침해되면 다른 폴더나 호스트 기능에 접근할 수 있는가?
  5. 검증: 읽기는 성공하고 쓰기는 실제로 실패하는가?
  6. 회수: 계정이나 토큰이 유출되면 어떻게 비활성화하고 교체하는가?

권장 패턴은 다음과 같다.

  • 서비스마다 별도 계정 사용
  • 관리자 그룹 제외
  • 필요한 공유 폴더만 허용
  • 컨테이너 user에 실제 서비스 계정의 UID/GID 매핑
  • 데이터는 가능한 한 :ro
  • root와 chmod 777을 최종 해결책으로 사용하지 않음
  • 설정 후 성공 경로와 실패 경로를 모두 테스트

참고 자료