
前言:當最安全的 Base Image 遇上最古老的傳輸協定
在企業級環境中,Red Hat Universal Base Image (UBI) 因其安全性與長期支援,成為容器化的首選基底。然而,當我們嘗試將傳統的 SFTP (SSH File Transfer Protocol) 搬進 UBI 容器時,往往會發現:「明明在 VM 上能跑的設定,進了容器就全掛了。」
這是因為 OpenSSH 的嚴格權限檢查與容器的輕量化特性產生了衝突。本文將拆解在 UBI 環境實作 SFTP 帳號密碼驗證時,最常遇到的五大技術挑戰與解決方案。
挑戰一:OpenSSH Chroot 的權限死結
這是 90% 工程師都會踩到的坑。為了將使用者限制在特定目錄(Chroot),我們通常會在 sshd_config 設定 ChrootDirectory。
-
錯誤觀念: 建立使用者後,直接將其家目錄設為 Chroot 目標。
-
技術限制: OpenSSH 強制規定,Chroot 目標目錄必須屬於 root,且權限不能超過 755(不可被群組或其他人寫入)。
-
解決方案: 採用「雙層目錄」結構。根目錄由 root 擁有,子目錄才給使用者寫入。
# 正確的權限結構示意
/home/sftpuser (Owner: root, Perm: 755) <-- Chroot 鎖定於此
└── /upload (Owner: sftpuser, Perm: 700) <-- 使用者實際上傳區挑戰二:消失的 Host Keys 與各種「無法啟動」
UBI Minimal 為了輕量化,安裝 openssh-server 後並不會自動產生伺服器金鑰(Host Keys)。若直接在 Dockerfile 中執行 CMD ["/usr/sbin/sshd", "-D"],容器會瞬間退出。
- 對策: 必須在容器啟動階段(Entrypoint)執行
ssh-keygen -A,或者在 Dockerfile 建置階段預先生成(較不推薦,因金鑰會固定在鏡像中)。
挑戰三:SELinux 與容器檔案系統的衝突
雖然容器內部看起來是獨立的檔案系統,但若掛載了 Host Volume,主機上的 SELinux 標籤就會介入。
- 症狀: 使用者登入成功,但無法列出目錄或上傳檔案,日誌顯示
Permission denied。 - 對策: 使用
:Z選項掛載 Volume,讓 Docker/Podman 自動處理 SELinux 上下文重標(Relabeling)。- 範例:
docker run -v /host/data:/home/sftpuser/upload:Z ...
- 範例:
挑戰四:密碼管理的資安兩難
在 Dockerfile 中使用 RUN echo 'user:pass' | chpasswd 是極度危險的行為,因為密碼會以明文殘留在鏡像層(Image Layers)中。
- 最佳實踐:
- 建置時: 僅建立使用者,不設定密碼。
- 執行時: 透過環境變數(如
SFTP_PASSWORD)傳入密碼,並由 Entrypoint 腳本動態設定。
實戰解法:修正版 Dockerfile 範本
以下是修正了 Chroot 權限與金鑰生成問題的可用版本。此範本使用 ubi8/ubi-minimal 以確保輕量化。
FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
# 1. 安裝必要套件 (使用 microdnf)
RUN microdnf install -y openssh-server shadow-utils \
&& microdnf clean all
# 2. 建立使用者與目錄結構 (關鍵修正)
# 建立使用者,但不建立預設家目錄 (由後續手動建立以控制權限)
RUN useradd -M -d /home/sftpuser -s /sbin/nologin sftpuser && \
mkdir -p /home/sftpuser/upload && \
# Chroot 目錄必須由 root 擁有
chown root:root /home/sftpuser && \
chmod 755 /home/sftpuser && \
# 上傳目錄由使用者擁有
chown sftpuser:sftpuser /home/sftpuser/upload
# 3. 配置 sshd_config
RUN echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config && \
echo 'PermitRootLogin no' >> /etc/ssh/sshd_config && \
echo 'Subsystem sftp internal-sftp' >> /etc/ssh/sshd_config && \
echo 'Match User sftpuser' >> /etc/ssh/sshd_config && \
# 鎖定在 root 擁有的目錄
echo ' ChrootDirectory /home/sftpuser' >> /etc/ssh/sshd_config && \
echo ' ForceCommand internal-sftp' >> /etc/ssh/sshd_config && \
echo ' AllowTcpForwarding no' >> /etc/ssh/sshd_config && \
echo ' X11Forwarding no' >> /etc/ssh/sshd_config
# 4. 設定 Entrypoint 腳本 (處理密碼與 Key)
# 警告:生產環境請勿寫死密碼,應透過 ENV 傳入
RUN echo '#!/bin/bash' > /entrypoint.sh && \
echo 'if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then' >> /entrypoint.sh && \
echo ' ssh-keygen -A' >> /entrypoint.sh && \
echo 'fi' >> /entrypoint.sh && \
# 若有環境變數傳入密碼則設定,否則設為預設值 (僅供測試)
echo 'PASS=${SFTP_PASSWORD:-DefaultPass123!}' >> /entrypoint.sh && \
echo 'echo "sftpuser:$PASS" | chpasswd' >> /entrypoint.sh && \
echo 'exec /usr/sbin/sshd -D -e' >> /entrypoint.sh && \
chmod +x /entrypoint.sh
EXPOSE 22
ENTRYPOINT ["/entrypoint.sh"]ITRE ATT&CK 對應戰術
在部署此服務時,需監控以下潛在攻擊面:
- T1078 – Valid Accounts (攻擊者嘗試暴力破解 SFTP 密碼)
- T1021.004 – Remote Services: SSH (利用 SSH 協定進行橫向移動)
- T1046 – Network Service Scanning (掃描暴露的 22 Port)
結語
在 UBI 容器中執行 SFTP,重點在於**「權限分離」**。透過正確的 Chroot 目錄權限設定與動態 Entrypoint 腳本,我們既能享受 UBI 的安全性,也能避開 OpenSSH 的嚴格檢查機制,建立穩定的檔案傳輸通道。
參考資料
🧠本文由 DreamJ AI 技術新聞生成系統 自動撰寫並進行語意優化,僅供技術研究與教學使用。
請以原廠公告、CVE 官方資料與安全建議為最終依據。











發佈留言