Ubuntu 26.04 sudo-rs 변경 사항 및 sudoers 설정 가이드
Ubuntu 26.04부터 기본 적용된 sudo-rs는 기존 C 기반 sudo와 달리 인자 내 와일드카드 매칭을 지원하지 않습니다. 업그레이드 시 sudoers 파일에서 발생하는 규칙 오류를 해결하는 방법과 현재 시스템의 sudo 구현체를 확인하는 명령어를 상세히 안내합니다.
Ubuntu에서 sudo-rs가 변경하는 사항
Ubuntu 26.04 LTS는 sudo-rs를 기본 sudo로 제공하므로, 새로 설치된 서버에서 sudo 명령을 실행하면 기존 C 프로그램 대신 Rust로 재구현된 버전이 실행됩니다. 대부분의 sudoers 파일은 이전과 동일하게 작동합니다. 다만 명령 인자 내에 와일드카드가 포함된 규칙은 작동하지 않는데, 이는 sudo-rs가 인자 텍스트에 대해 glob 패턴 매칭을 수행하지 않기 때문입니다.
Ubuntu 25.10에서 처음 이 변경이 도입되었고 26.04 LTS에서도 유지되었습니다. Ubuntu 24.04 LTS는 sudo-rs를 수동으로 설치하지 않는 한 기존 sudo를 그대로 사용하므로 영향을 받지 않습니다. 이 문제가 중요해지는 시점은 Ubuntu 24.04에서 26.04로 업그레이드할 때, 또는 최신 릴리스로 새 서버를 구축할 때입니다. 중간 릴리스를 사용하는 경우, LTS와 중간 Ubuntu 릴리스가 서버에서 어떻게 다른지에 대한 설명을 통해 어떤 시스템에서 이러한 변경 사항이 먼저 적용되는지 확인할 수 있습니다.
서버에서 실제로 실행 중인 sudo 확인하기
릴리스 번호로 추측하지 마십시오. 시스템에 직접 질의해야 합니다.
sudo --version
update-alternatives --config sudo
dpkg -l 'sudo*'인터넷상의 어떤 버전 표보다 본인 서버의 sudo --version를 신뢰하십시오. update-alternatives --config sudo은 나머지 절반의 해답입니다. 이 명령은 /usr/bin/sudo의 모든 설치된 제공자를 나열하고 현재 선택된 항목을 표시합니다. 패키지가 설치되어 있다고 해서 반드시 선택된 것은 아니므로, 패키지 목록이 아닌 선택 상태를 확인해야 합니다.
전환기 동안에는 두 가지 구현체가 모두 패키징됩니다. Rust로 작성된 버전은 sudo-rs이며, 2026년 8월 기준 26.04 버전에서 0.2.13 버전을 사용합니다. Todd C. Miller가 유지 관리하는 원본 버전은 sudo.ws으로 패키징되며, 해당 프로그램들은 .ws 접미사가 붙어 sudo.ws 및 visudo.ws으로 실행됩니다.
Ubuntu가 sudo-rs로 전환한 이유
sudo는 setuid root 방식으로 동작합니다. 시스템의 모든 사용자가 sudo를 실행할 수 있으며, 실행 시 전체 권한을 획득하므로 내부의 메모리 버그는 곧 로컬 root 권한 탈취로 이어집니다. CVE-2021-3156이 바로 그런 사례였습니다. 이는 모든 로컬 사용자가 접근 가능한 힙 버퍼 오버플로우 취약점이었으며, 약 10년 동안 릴리스된 코드에 방치되어 있었습니다. Rust는 컴파일 시점에 이러한 유형의 버그를 잡아내며, 이것이 바로 재작성을 추진한 핵심 이유입니다.
두 번째 이유는 범위이며, 이는 사용자의 설정에도 영향을 미칩니다. 기존의 sudo는 30년 동안 방대한 기능 세트를 축적해 왔으며, 모든 기능은 root 권한으로 실행되는 코드입니다. sudo-rs는 의도적으로 기능의 일부만을 구현합니다. 작성자가 틈새 기능이거나 보안에 해롭다고 판단한 기능은 제외되었으므로, 수년간 잘 작동하던 sudoers 구문이 단순히 존재하지 않을 수 있습니다. 사용자의 와일드카드 규칙도 그중 하나입니다.
메모리 안전성은 한 가지 유형의 버그를 제거할 뿐입니다. 프로그램이 버그로부터 완전히 자유로워지는 것은 아니며, sudo-rs 역시 기본값으로 채택된 이후 자체적인 보안 패치를 릴리스해 왔습니다. 다른 소프트웨어와 마찬가지로 동일하게 패치하십시오.
어떤 sudoers 규칙이 여전히 작동하는가
파일은 동일합니다. sudo-rs는 /etc/sudoers과 /etc/sudoers.d/의 드롭인 파일을 읽으며, 서버 운영자가 작성하는 일반적인 설정은 모두 지원됩니다.
deploy ALL=(ALL:ALL) ALL및%sudo ALL=(ALL:ALL) ALL과 같은 그룹 형식NOPASSWD:및PASSWD:태그User_Alias,Runas_Alias,Host_Alias및Cmnd_Alias- 정확한 인수 목록을 포함한 명령어(예:
/usr/bin/systemctl restart app-api) - 명령어 뒤에
""을 붙여 인수가 전혀 없는 경우에만 명령어를 허용하는 방식 - 명령어의 마지막 인수로
*를 붙여 뒤에 오는 모든 인수를 허용하는 방식 /으로 끝나는 디렉터리 경로를 지정하여 해당 디렉터리 내의 모든 명령어를 허용하는 방식- 목록에서 명령어를 제외하기 위한
! secure_path,env_keep,env_check,timestamp_timeout,passwd_tries,editor,umask,targetpw,rootpw및use_pty를 포함한Defaults의 유용한 하위 집합
두 가지 기본 설정은 다르게 동작하여 사용자가 혼동을 겪을 수 있습니다. env_reset은 sudo-rs에서 끌 수 없으며 항상 켜져 있습니다. use_pty는 기본적으로 켜져 있으므로, 명령어는 별도의 의사 터미널(pseudo-terminal)에서 실행됩니다.
와일드카드 sudoers 규칙이 일치하지 않는 이유
와일드카드는 명령의 파일 이름 부분에서만 여전히 허용됩니다. %ops ALL = /sbin/fsck* 규칙은 sudo fsck 및 sudo fsck_exfat을 여전히 허용하는데, 이는 *이 파일 시스템과 대조되는 경로의 일부이기 때문입니다.
인수 목록 내부에서 sudo-rs는 두 가지 특수 형식만 허용하며, 둘 다 패턴은 아닙니다. ""는 인수가 없음을 의미합니다. 마지막의 *은 뒤에 오는 모든 인수를 의미합니다. 그 외의 모든 인수는 리터럴 텍스트로 비교됩니다. 따라서 %ops ALL = /sbin/service ntp *은 문제가 없는데, ntp는 리터럴이고 *이 마지막에 위치하기 때문입니다. 그러나 다음과 같은 규칙은 의도한 권한을 전혀 부여하지 않습니다.
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app-*app-*는 인수 중간에 있는 패턴입니다. sudo-rs는 이를 확장하지 않으므로 해당 규칙은 systemctl restart app-api를 포함하지 않으며, sudo는 해당 명령을 거부합니다. 서버의 규칙이 실제로 어떻게 작동하는지 확인하려면 다음 두 명령을 사용하십시오. root 권한으로 실행하는 sudo -l -U deploy은 해당 계정이 실제로 실행할 수 있는 명령을 출력하며, sudo visudo -c은 파일의 구문 분석 가능 여부를 알려줍니다. 무작위로 파일을 수정하기 전에 이 명령들을 먼저 실행하십시오.
와일드카드 규칙은 항상 보안 허점이었습니다
기존 sudo에서는 사용자가 입력한 인자들이 하나의 문자열로 결합된 뒤, 규칙의 인자 문자열과 glob 방식으로 대조됩니다. glob은 공백 문자도 일치시킵니다. 이것이 거의 모든 사람이 간과하는 부분입니다.
sudo-rs 문서가 가장 명확한 예시를 보여줍니다. /bin/rm *.txt 규칙은 sudo rm -rf /home .txt도 허용합니다. 왜냐하면 * 하나가 -rf /home 을 포함해 버리고, 결합된 문자열이 여전히 .txt로 끝나기 때문입니다. 이 규칙은 "텍스트 파일만" 허용하는 것처럼 보이지만, 실제로는 "줄 끝이 .txt로 끝나기만 한다면 어떤 인자든" 허용한다는 의미입니다.
이는 systemctl 예시에도 동일하게 적용됩니다. 인자들이 하나의 결합된 문자열로 비교되기 때문에, 뒤에 붙는 패턴은 그 뒤에 추가하는 모든 내용과도 일치하게 됩니다. 따라서 restart app-*은 restart app-api와 호출자가 추가하는 모든 인자를 포함하게 됩니다. 인자 내의 패턴은 그 주변의 인자들을 노출시키며, 명령어의 권한은 바로 그 인자들로부터 나옵니다. sudo-rs는 안전하게 만들 방법을 찾는 대신 해당 구문 자체를 거부합니다. 안전한 일반화 형태가 존재하지 않기 때문입니다.
와일드카드를 명시적인 명령어 목록으로 교체
대부분의 와일드카드 규칙은 누군가 네 줄을 입력하기 귀찮아해서 존재합니다. 그냥 네 줄을 입력하십시오.
Cmnd_Alias APP_RESTART = /usr/bin/systemctl restart app-api, /usr/bin/systemctl restart app-worker
Cmnd_Alias APP_STATUS = /usr/bin/systemctl status app-api, /usr/bin/systemctl status app-worker
deploy ALL=(root) NOPASSWD: APP_RESTART, APP_STATUS경로를 정확히 지정하십시오. 바이너리가 /usr/bin/systemctl인 시스템에서 /bin/systemctl를 지정하는 규칙은 절대 일치하지 않으며, 그 실패 양상은 권한 문제와 동일하게 보입니다. command -v systemctl 명령어로 확인하고 출력된 내용을 붙여넣으십시오.
규칙을 /etc/sudoers에 직접 넣지 말고 별도의 드롭인 파일로 만드십시오. 그래야 패키지 업그레이드 시 편집 내용과 충돌하지 않습니다.
sudo visudo -f /etc/sudoers.d/90-deploy
sudo visudo -c
sudo -l -U deploy파일 이름에 점(dot)이나 끝에 물결표(tilde)가 포함되지 않도록 하십시오. 원본 sudo는 sudoers.d 내의 파일 이름에 점이 포함되어 있으면 무시하므로, 90-deploy.conf은 아무런 동작도 하지 않는 전형적인 사례가 됩니다. 관례를 따르는 데는 비용이 들지 않습니다.
허용 목록이 길어지면 root 소유의 래퍼를 사용하십시오
허용할 항목이 너무 많아 sudoers 파일에 나열하기 어려울 경우, 결정 로직을 sudoers에서 분리하여 root가 소유한 작은 프로그램으로 옮기십시오.
sudo tee /usr/local/sbin/app-restart >/dev/null <<'EOF'
#!/bin/sh
set -eu
case "${1:-}" in
app-api|app-worker) ;;
*) echo "app-restart: not allowed: ${1:-}" >&2; exit 1 ;;
esac
exec /usr/bin/systemctl restart "$1"
EOF
sudo chown root:root /usr/local/sbin/app-restart
sudo chmod 0755 /usr/local/sbin/app-restart
ls -l /usr/local/sbin/app-restart이제 sudoers 설정에는 하나의 명령어만 명시합니다.
deploy ALL=(root) NOPASSWD: /usr/local/sbin/app-restart *여기서 뒤에 붙는 *은 허용 가능합니다. sudo가 아닌 스크립트가 무엇을 허용할지 결정하기 때문입니다. 단, 이 조건은 스크립트가 root 소유이고 다른 누구도 쓰기 권한을 가지지 않을 때만 유효합니다. 만약 deploy가 해당 파일에 쓰기 권한을 가진다면, deploy은 파일 내용을 교체하여 무엇이든 root 권한으로 실행할 수 있습니다. 이는 제거했던 와일드카드 규칙보다 더 위험합니다. ls -l로 권한 모드를 확인하십시오. 출력 결과가 이해되지 않는다면 drwxr-xr-x 권한 문자열 읽는 법을 학습하는 데 5분만 투자하십시오. 동일한 규칙이 디렉터리에도 적용됩니다. /usr/local/sbin 또한 해당 계정이 쓰기 권한을 가져서는 안 됩니다. 디렉터리에 쓰기 권한이 있으면 파일 전체를 교체할 수 있기 때문입니다.
sudo 규칙 대신 전용 계정 사용하기
더 중요한 질문은 해당 명령어가 왜 root 권한을 필요로 하는가입니다. 자체 계정으로 실행되는 서비스는 해당 사용자가 직접 관리할 수 있으며, sudoers 설정이 필요하지 않습니다. systemd 유닛의 경우, systemd는 이미 해당 권한 결정을 polkit에 위임하므로 규칙 하나로 특정 유닛과 특정 운영자를 지정할 수 있습니다.
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "app-api.service" &&
subject.user == "deploy") {
return polkit.Result.YES;
}
});이 내용을 /etc/polkit-1/rules.d/50-app-api.rules에 저장하면 deploy은 sudo 없이 systemctl restart app-api을 실행할 수 있습니다. 규칙을 적용하기 전에 실제로 사용할 환경에서 직접 테스트하십시오. SSH 세션에서 작동하는 규칙이라도 cron에서 실행할 계획이라면 cron 환경에서 확인하는 것이 안전합니다. 어떤 경우든 작업을 수행하는 계정은 해당 작업만을 위해 존재해야 하며, 이는 VPS에서의 최소 권한 사용자 계정을 사용하는 것과 같은 맥락입니다.
sudo-rs에서 제외된 기능
sudo -E는 구현되지 않았습니다. 필요한 변수는 Defaults env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY"을 사용하여 지정하십시오. 또한 env_reset은 항상 활성화되어 있으므로, 유지되지 않는 모든 데이터는 삭제된다는 점을 유의하십시오.
LDAP을 통한 중앙 집중식 sudoers 저장 기능은 제거되었습니다. sudoers.ldap 및 cvtsudoers은 구현되지 않았으며, sudo-ldap 패키지는 26.04 버전에서 삭제되었습니다. PAM이나 SSSD를 통한 LDAP 인증은 여전히 정상적으로 작동합니다. 정책을 디렉터리에 저장하는 기능만이 범위에서 제외되었습니다.
허용된 명령에서 셸 이스케이프를 방지하려던 INTERCEPT는 구현되지 않았습니다. 이는 애초에 의지가 있는 사용자를 막을 수 없었습니다. 규칙을 통해 누군가 root 권한으로 편집기나 인터프리터를 실행할 수 있다면, 그 사용자는 이미 root 권한을 가진 것이며 어떠한 sudo 옵션으로도 이를 변경할 수 없습니다.
세션 기록 기능은 구현되지 않았으므로 I/O 로그나 sudoreplay은 존재하지 않습니다. 로그는 syslog로만 전송되며, 이를 다른 곳으로 리다이렉트하는 logfile 옵션은 없습니다. 따라서 sudo 메시지는 시스템이 이미 syslog를 전송하도록 설정된 위치로 기록됩니다.
sudo.ws로 다시 전환해야 합니까?
전환할 수 있습니다. 26.04 주기 동안에는 바로 이러한 이유로 기존 패키지가 그대로 유지됩니다.
sudo apt install sudo.ws
update-alternatives --config sudo
sudo update-alternatives --set sudo /usr/bin/sudo.ws이 페이지에 있는 경로를 복사하지 말고 --config 출력에서 정확한 경로를 복사하십시오. 해당 목록이 귀하의 시스템이 수용하는 유일한 경로이기 때문입니다. 나중에 sudo-rs로 다시 돌아가려면 동일한 목록에 있는 sudo-rs 바이너리 경로로 대안(alternative)을 설정해야 합니다.
sudo에 영향을 주는 작업을 수행하기 전에 SSH 세션을 하나 더 열어 로그인 상태로 유지하십시오. sudoers 파일의 구문 분석이 실패하거나 설치되지 않은 바이너리를 가리키는 대안이 설정되면 원격 서버에서 root 권한을 획득할 방법이 사라질 수 있습니다. 이러한 습관은 새 VPS에서 처음 10분 동안 수행하는 모든 작업과 함께 반드시 지켜야 합니다.
다시 전환하는 것을 해결책이 아닌 기한으로 간주하십시오. 이는 규칙을 올바르게 다시 작성할 수 있는 일주일의 시간을 벌어줍니다. 삭제하는 모든 와일드카드 규칙은 작성자가 의도한 것보다 더 많은 권한을 부여하고 있었으므로, 다시 작성하는 작업 자체만으로도 충분한 가치가 있습니다.
FAQ
Ubuntu 26.04에서 sudoers 와일드카드 규칙이 작동하지 않는 이유는 무엇입니까?
Ubuntu 26.04 LTS는 기본 sudo로 sudo-rs를 채택했는데, sudo-rs는 명령 인자 내부의 와일드카드 패턴을 일치시키지 않기 때문입니다. 명령의 파일 이름에는 와일드카드를 허용하며, ""는 인자 없음을 의미하고, * 하나는 마지막 인자로 사용됩니다. /usr/bin/systemctl restart app-*과 같은 규칙은 인자 중간에 패턴을 넣으므로 아무런 권한도 부여하지 않으며 명령이 거부됩니다. root 권한으로 sudo -l -U deploy를 실행하여 해당 계정이 실제로 가진 권한을 확인한 뒤, 규칙을 정확한 명령어로 대체하거나 root 소유의 래퍼 스크립트를 사용하십시오.
Ubuntu 26.04에서 원래의 sudo로 되돌리려면 어떻게 해야 합니까?
원래의 sudo는 sudo.ws 패키지로 제공됩니다. sudo apt install sudo.ws로 설치한 다음, sudo update-alternatives --set sudo /usr/bin/sudo.ws를 사용하여 alternative를 해당 패키지로 지정하십시오. 먼저 update-alternatives --config sudo을 실행하여 시스템이 제공하는 정확한 경로를 확인하고, 변경하는 동안 두 번째 SSH 세션을 열어 두십시오. 이 작업으로 sudo-ldap이 복구되지는 않으며, 이는 어떤 구현을 선택하든 26.04에서 제거되었습니다.
sudo-rs도 동일한 /etc/sudoers 파일을 읽습니까?
예, 그렇습니다. sudo-rs는 /etc/sudoers과 /etc/sudoers.d/ 하위의 드롭인 파일을 읽으며, 사용자, 그룹, 별칭, 실행 권한 지정 및 NOPASSWD 태그에 대해 동일한 구문을 사용합니다. sudo-rs는 sudoers 언어의 하위 집합을 구현하므로, 차이점은 다르게 동작하는 구문이 아니라 지원되지 않는 구문으로 나타납니다. sudo visudo로 편집하고 세션을 닫기 전에 sudo visudo -c로 검증하십시오.
sudo-rs에서 sudo -E을 대체하는 것은 무엇입니까?
sudo -E는 구현되지 않았으며, 원래의 sudo에서도 권장되지 않던 기능입니다. 호출자가 제어하는 환경을 root 프로세스에 전달하는 것은 해당 프로세스의 동작을 변경할 수 있는 잘 알려진 방법이기 때문입니다. 대신 Defaults env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY"와 같은 줄을 사용하여 sudoers에 실제로 필요한 변수를 명시하십시오. env_reset은 sudo-rs에서 항상 활성화되어 있으며 비활성화할 수 없으므로, 유지하지 않는 모든 변수는 삭제됩니다.