OpenSSH 는 별도 데몬 없이 SFTP 를 제공한다. sshd_config 의 Subsystem sftp 줄이 그 진입점이고, 기본값이 이미 켜져 있으므로 "SFTP 를 설치한다" 는 작업은 없다. 실제로 필요한 작업은 특정 계정에게 셸 로그인은 막고 파일 전송만 허용하며, 그 계정을 자기 디렉터리 밖으로 나가지 못하게 가두는 것이다.
배포판 기본값은 외부 실행 파일을 쓰는 형태다.
# RHEL 계열
Subsystem sftp /usr/libexec/openssh/sftp-server
# Debian · Ubuntu 계열
Subsystem sftp /usr/lib/openssh/sftp-server
chroot 를 걸면 가둔 디렉터리 안에 그 실행 파일과 의존 라이브러리가 없어서 전송이 곧바로 끊긴다. 그래서 chroot 를 쓸 때는 sshd 프로세스 안에서 도는 내장 구현으로 바꾼다.
Subsystem sftp internal-sftp
ChrootDirectory 로 지정한 경로는 root 소유이고 그룹·기타 쓰기 권한이 없어야 한다. 이 조건을 어기면 sshd 가 접속을 거부하며 /var/log/secure 에 bad ownership or modes for chroot directory 를 남긴다. 사용자가 실제로 쓸 공간은 그 아래에 따로 만들어 소유자를 준다.
sudo groupadd sftpusers
sudo useradd -g sftpusers -s /sbin/nologin sftpuser
sudo mkdir -p /srv/sftp/sftpuser/upload
sudo chown root:root /srv/sftp/sftpuser
sudo chmod 755 /srv/sftp/sftpuser
sudo chown sftpuser:sftpusers /srv/sftp/sftpuser/upload
파일 끝에 Match 블록을 둔다. Match 이후의 지시어는 블록이 끝날 때까지 이어지므로 반드시 파일 맨 끝에 놓는다.
Subsystem sftp internal-sftp
Match Group sftpusers
ChrootDirectory /srv/sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
%u 는 접속한 계정 이름으로 치환된다. 적용한다.
sudo sshd -t
sudo systemctl restart sshd
sshd -t 는 설정 문법만 검사한다. 원격으로 작업 중이라면 재시작 전에 이 검사를 반드시 통과시킨다. 문법 오류가 있으면 sshd 가 올라오지 않아 접속 경로를 잃는다.
RHEL 계열에서 홈 디렉터리 밖(/srv 등)을 SFTP 공간으로 쓰면 SELinux 가 막는다. 컨텍스트를 붙여 준다.
sudo semanage fcontext -a -t ssh_home_t "/srv/sftp(/.*)?"
sudo restorecon -Rv /srv/sftp
sudo setsebool -P ssh_chroot_rw_homedirs on
sftp sftpuser@192.168.0.10
sftp> cd upload
sftp> put local.txt
sftp> mput *.csv
sftp> get remote.txt
sftp> bye
스크립트에서 쓸 때는 대화형 세션 대신 -b 로 배치 파일을 넘기거나 scp 를 쓴다.
sftp -b /tmp/commands.txt sftpuser@192.168.0.10
ForceCommand internal-sftp 를 걸면 그 계정으로는 셸 명령을 전혀 실행할 수 없다. 배포 스크립트가 전송 후 원격에서 압축을 풀도록 되어 있다면 그 동작이 같이 죽는다. 셸이 필요한 계정과 전송만 하는 계정을 나눈다.
ChrootDirectory 안에서는 /etc/passwd 도 보이지 않으므로 ls -l 의 소유자 표시가 숫자 UID 로 나온다. 정상 동작이다.