安全設置運行Java服務的Linux--為Tomcat構建一個安全的籠子
發表于:2007-07-04來源:作者:點擊數:
標簽:
Linux 平臺和 Java 平臺有著久遠的但有經常經歷曲折的關系。構建高 性能 虛擬機的同時又要跟上日益增長的核心 Java API 集合,這樣做所帶來的復雜性在很大程度上使開發 Java 平臺的開放源碼“凈室”實現的早期行動困難重重。Java 技術的特許實現最終可用于 L
Linux 平臺和 Java 平臺有著久遠的但有經常經歷曲折的關系。構建高
性能虛擬機的同時又要跟上日益增長的核心 Java API 集合,這樣做所帶來的復雜性在很大程度上使開發 Java 平臺的開放源碼“凈室”實現的早期行動困難重重。Java 技術的特許實現最終可用于 Linux,但這些實現并不是開放源碼。因此,大多數 Linux 分發版沒有包含該特許實現。
盡管有這些困難,Java 平臺還是提供了許多好處,從而導致在 Linux 上越來越多地使用該特許實現,尤其是對于
服務器應用程序。在本文中,我回顧了 Java 平臺給服務器應用程序帶來的優點,然后研究了在 Linux 上簡單且
安全地部署 Java 服務所涉及的問題。作為一個實際示例,我將討論設置 Apache Software Foundation 的廣泛使用的 Tomcat Java servlet 引擎的詳細信息以用于獨立操作。
為什么使用 Java 平臺?
有許多原因可以解釋為什么 Java 平臺成為基于服務器的商業應用程序的廣為接受的選擇。我將主要討論我認為對于該環境至關重要的三個原因:跨平臺
兼容性、受管運行時環境和易于開發。
Java 應用程序提供了跨多種操作系統和硬件平臺的二進制兼容性。對于非 GUI 服務器應用程序尤其是這樣,在此類應用程序中,通常在實際目標系統中需要執行非常少的
測試。開發人員可以在任何他們喜歡的平臺上進行編碼和調試,同時仍可以將這些應用程序部署到他們也許不能直接控制的環境中。
Java 虛擬機(Java Virtual Machine,JVM)環境的運行時特性以幾種方式來加強程序安全性。最顯著的方面之一是嚴格的類型檢查、數組邊界檢查和自動垃圾收集的組合徹底防止了最具破壞性形式的服務器代碼攻擊:緩沖區溢出、重復釋放的錯誤和游離的指針。Java 語言早期用于 applet,經過不斷發展,該語言還有一個完善的系統,用于對那些已確信存在安全性風險的設施進行細顆粒度的訪問控制。這些方法可供獨立應用程序選擇使用,但它們已構建在許多 Java 服務的框架中。
這些運行時程序安全特性還提供了用 Java 語言開發的便利性。要對便利性這類問題作任何精確測量是困難的,但大多數具有諸如 C 和 C++ 之類語言背景而轉向 Java 編程的開發人員都承認在轉變之后他們的生產力提高了。其中部分是因為在編譯時和運行時嚴格執行類型確定,以及自動內存管理的簡單性。另一個因素是為 Java 平臺開發的標準 API 擴展的集合。這些 API 對于新的開發人員可能是一個重大挑戰,但是一旦學會了,API 會為各種企業
需求提供優秀的跨平臺支持。
當然,對于某些應用程序而言,Java 平臺可能是一個糟糕的選擇。盡管 JVM 體系結構在持續改進,但 Java 應用程序通常會比使用相同算法的 C 或 C++ 應用程序運行得稍微慢一點。根據我的經驗和測試,我估計這個速度差異對于在特許 JVM 上運行的大多數服務器應用程序來說大約是在 20% 到 50% 的范圍內,然而這很大程度上取決于代碼的
質量。與獨立程序相比,在這些 JVM 上運行的 Java 應用程序還忍受著比較慢的啟動,但是這對于長時間運行的服務器應用程序通常并不是一個重大問題。在大多數情況中,降低的性能和較慢的啟動只是為獲得 Java 平臺的增強的安全性和更快速的開發優點所付出的微小代價。
開放源碼替代選擇
除了標準特許 JVM(免費使用,但是源碼受到限制;可用于 Sun、IBM、BEA 和 Blackdown 組織的 Linux)之外,對于 Linux 還有其它幾個替代選擇。這些選擇包括“凈室”開放源碼 JVM 實現,其中使用最廣泛的可能是 Kaffe(在許多 Linux 分發版中都包含它)。Kaffe 是一個非常有意義的項目,它已經完成了一些令人驚訝的工作,但它只能提供與當前特許 JVM 有限的兼容性。因此,它通常不可用于本文所關注的企業類型的服務器應用程序。
用于 Java 程序的本機代碼編譯器的開放源碼工作也有幾個替代選擇。這里最重要的項目是 GNU 編譯器集(GNU Compiler Collection)的 GCJ。使用諸如 CGJ 之類的本機代碼編譯器會將獨立于平臺的 Java 字節碼在其執行之前轉換成特定于平臺的代碼(這與在 JVM 中執行成對比,在 JVM 中執行通常在運行時將字節碼轉換成特定于平臺的代碼)。
本機代碼編譯顯示出它極有可能成為一種避免在 JVM 中運行的 Java 應用程序啟動較慢的方法。但是,使用這種方法的編譯器通常都不能與當代特許 JVM 的穩定狀態性能相匹配。如果 Java 應用程序使用 Java 平臺的動態特性(如使用反射來訪問字段或裝入在運行時選擇的類),這種情況尤其突出。根據所使用的實現和編譯選項,本機代碼編譯也許還會削弱 Java 平臺的許多運行時安全特性。最后,由于許可證問題,許多 Java API 不能與已編譯的本機代碼一起使用。由于這些限制,本機代碼編譯目前還不是 Java 平臺服務器應用程序的一個好選擇。
C# 怎么樣?
與 Java 運行時環境有許多共同點的一個替代方法是 Microsoft 的 C# 語言和相關的公共語言運行時(Common Language Runtime,CLR)。C# 是 Java 語言的關系緊密的衍生物,CLR 可能允許 C# 在許多平臺上使用。CLR 還提供了 JVM 的許多運行時安全特性(盡管有嚴重削弱安全保證的逃離出口)。Microsoft .Net 實現還支持預編譯成本機代碼的選項以獲得更快速啟動,這與 GCJ 對 Java 字節碼所做的工作相同。當然,Linux 用戶并不能直接使用這項功能,因為 .Net 只適用于
Windows 系統。
Mono Project 正致力于為多種 Linux 產品構建“凈室”開放源碼 C# 和等價于 CLR 的產品?,F在,該項目中的 C# 編譯器已開發完成,而且還完成了大部分的 CLR,Microsoft 已發布將它用于標準化。但是,無論從性能還是功能角度來看,在它成為合理的 Java 平臺替代選擇之前還有許多工作要做。CLR 只包含了與 Java 核心類庫等價的基本內容。在可以將它看作是企業軟件開發的合理選項之前,還需要用許多附加的 API 來補充它。
Mono Project 正在致力于開發 CLR 以外的 .Net 其它部分的移植,如果這些移植成功了 — 并且如果 Microsoft 不對 .Net 的這些部分強加它的專利權 — 那么它們會有助于滿足 C# 成為 Linux 上服務器軟件開發的可靠平臺的需要。但要使那些假設成為現實,還需要做很多工作,同時,Java 程序的本機代碼編譯器和開放源碼 JVM 向那些確實想要避免使用特許 JVM 并可以忍受有限功能性的用戶提供了比較穩定的替代選擇。
Apache Tomcat
最普遍存在的 Java 平臺服務器應用程序之一是 Apache Tomcat。Tomcat 是基于最初由 Sun 捐贈的源代碼的開放源碼項目。它是一個 HTTP 服務器,是 Sun 通過 Java Community Process 開發的、對廣泛使用的 servlet 和 JavaServer Page(JSP)技術的正式參考實現。我將在本文中使用 Tomcat 作為樣本 Java 應用程序,將其部署成 Linux 上的一個服務。如果您想要嘗試自己運行 Tomcat,那么您將需要在系統上安裝 Java 開發工具箱(Java Development Kit,JDK),而不是安裝更小的 Java 運行時環境(Java Runtime Environment,JRE)。
servlet 和 JSP 技術用于構造 HTTP 服務器應用程序。雖然 servlet 技術中添加了許多特性(包括訪問安全性、會話管理和線程控制),但它本身只是粗略地等價于為快速直接的 Java 語言調用而定制的 CGI 接口。JSP 技術提供了一種處理動態生成的 HTML 頁面的簡便方法,這些 HTML 頁面被直接編譯成 servlet 以用于快速運行時操作。
在這兩種技術之外,Tomcat 還提供了其它許多特性。憑它本身的性能,它實際上是全功能 Web 服務器,但它通常在 Linux 系統上與 Apache Web 服務器前端共同使用。Apache 向 Tomcat 提供了許多高級性能以適合靜態內容。對于靜態內容所占比例比較高且使用率很高的 Web 應用程序,Apache 前端非常有用。但對于許多簡單的 Web 應用程序,就沒必要使用它了,當更易于配置和管理時,單獨運行 Tomcat 就可提供足夠的性能(至少對于以前沒有使用過 Apache 的開發人員來說是這樣)。
端口難題
單獨運行 Tomcat 的一個大問題是它不能訪問標準 HTTP 端口 80,除非是作為 root 用戶運行。作為 root 用戶運行服務器應用程序的想法通常并不是上流公司所討論的問題,因此我將完全放棄這個想法!使用除 80 以外的端口是一個更好的選擇(例如,Tomcat 缺省端口 8080)。這通常適用于測試,但當用戶正在訪問服務時,它會導致雜亂的 URL,因為需要在請求中清楚地說明端口號。使用非標準端口還意味著如果需要外部訪問,就需要重新配置所有的防火墻。
x
.netd
解決方案 幸好,Linux 支持一些利用 Tomcat(或任何其它用戶方式應用程序)處理端口 80 請求的簡便方式。一種常用方式是通過 xinetd。xinetd 是帶有廣泛訪問控制和日志記錄支持的因特網服務守護程序,它還擁有方便的重定向特性。重定向讓您將系統配置成接受一個端口上的進入請求,然后將請求傳遞到另一個端口或者甚至另一個 IP 地址進行處理。
如果您想要在系統上設置 Tomcat 以處理端口 80 請求,就需要添加 xinetd 配置文件來實現這一目的。假設按常規在缺省路徑上安裝了 xinetd,那么您可以通過對 /etc/xinetd.d 目錄添加一個文件(以 root 用戶身份)來執行這一操作。清單 1 提供了用于 Tomcat 的一個樣本配置文件。
清單 1. xinetd 重定向配置
# Redirects any requests on port 80
# to port 8080 (where Tomcat is listening)
service tomcat
{
socket_type = stream
protocol = tcp
user = root
wait = no
port = 80
redirect = localhost 8080
disable = no
}
在添加了配置文件之后,需要重新啟動 xinetd 來真正激活重定向。在大多數 Linux 安裝上,通過以 root 用戶身份執行以下命令來重新啟動 xinetd:
/sbin/service xinetd restart
只要將配置文件放在 /etc/xinetd.d 目錄中,當重新啟動系統時,重定向就會自動啟動。如果沒有將 Tomcat 設置成自動啟動,那么在啟動 Tomcat 之前,會拒絕進入請求。
iptables 解決方案
xinetd 是處理請求重定向的一種好方法,但它運行了一個進程以在端口之間實際轉發數據,這確實增加了一些開銷。最新的 Linux 內核版本通過使用 iptables 來支持一種更好的設置重定向的方法。iptables 與 xinetd 的區別之處在于它是一個真正的內核組件。因此,它可以避免 xinetd 方法增加的開銷。使用 iptables 的唯一缺點是它可能比 xinetd 更難以配置,而且它只可用于相當新的內核版本。
您需要運行支持 iptables 的 2.4.x 或更新的內核,以便使用我在這里描述的技術。配置和設置 iptables 是一個確信由幾篇文章來單獨描述的主題,所以我不打算在這里嘗試討論該主題。如果對 iptables 的入門需要幫助,請閱讀 Linux 分發版的手冊。要快速檢查 iptables 是否在您的系統上運行,嘗試以 root 用戶身份執行:
/sbin/service iptables status
如果它正在運行,您將會在控制臺上看到表和鏈的清單。
iptables 使用幾個遵循包處理規則的不同的表和鏈。為了將進入 HTTP 請求從端口 80 重定向到系統中的另一個端口,您將要使用 nat 表(表示
網絡地址轉換,Network Address Translation)和 PREROUTING 鏈。清單 2 提供了要執行的實際命令(以 root 用戶身份),以便于添加一條處理這一請求的規則。這條規則的作用是將進入包的目標端口 80 修改成目標端口 8080,因此只有在您沒有阻止從外部使用端口 8080 時,這條規則才會正確工作。一旦執行了該命令,您就應該能夠立即處理進入請求。
清單 2. iptables 重定向規則
/sbin/iptables -t nat -A PREROUTING -j REDIRECT -p tcp --destination-port 80:80 --to-ports 8080
在您重新啟動系統之前,它將一直有效。如果您想使該重定向永久有效,需要執行以下命令:
/sbin/service iptables save
以便于保存當前 iptables 配置。
自動啟動 Tomcat
當運行諸如 Tomcat 之類的 Java 服務時的另一個問題是當系統啟動時,如何自動啟動該應用程序,以及當系統關機時如何自動停止它(換句話說,將它當作守護程序運行)。經驗豐富的 Linux 用戶已經知道怎樣做,但如果您還是個 Linux 新手,以下就是一些基礎
知識。
如果您就在您的個人系統上運行它并且想要使用與直接運行 Tomcat 一樣的對 Tomcat 的文件和目錄的訪問權,那么您可以用您自己的用戶名來設置它。但是,通常,一個比較好的想法是:為將要作為守護程序運行的任何程序設置一個單獨的用戶。要針對 Tomcat 執行這一操作,以 root 用戶身份執行:
/usr/sbin/useradd tomcat
這將創建一個名為 tomcat 的用戶帳戶并創建一個用于 Tomcat 安裝的主目錄 /home/tomcat。所創建的主目錄的所有者是 tomcat 用戶,而且通常只允許該用戶訪問(當然還有 root 用戶)。如果想要從其它帳戶訪問 Tomcat 安裝,可以將許可權更改成包括組訪問權,并將 tomcat 組添加到這些其它帳戶。
總之,要將 Tomcat 當作守護程序運行,需要將服務配置文件添加到 /etc/init.d 目錄中,而您可能要將該文件命名為“tomcat”。清單 3 提供了該文件的樣本。這假設了 Tomcat 安裝在 /home/tomcat 下,并且該位置中有兩個 shell 腳本文件,用于處理啟動和停止服務器(tcstart.sh 和 tcstop.sh)。在執行實際的 Tomcat 啟動或停止腳本之前,需要使用這些文件來設置 Tomcat 所需的環境變量(包括 JAVA_HOME 和 JDK_HOME)。
清單 3. Tomcat 服務定義
#!/bin/bash
#
# tomcat Starts Tomcat Java server.
#
#
# chkconfig: 345 88 12
# description: Tomcat is the server for Java servlet applications.
### BEGIN INIT INFO
# Provides: $tomcat
### END INIT INFO
# Source function library.
. /etc/init.d/functions
[ -f /home/tomcat/tcstart.sh ] || exit 0
[ -f /home/tomcat/tcstop.sh ] || exit 0
RETVAL=0
umask 077
start() {
echo -n $"Starting Tomcat Java server: "
daemon su -c /home/tomcat/tcstart.sh tomcat
echo
return $RETVAL
}
stop() {
echo -n $"Shutting down Tomcat Java server: "
daemon su -c /home/tomcat/tcstop.sh tomcat
echo
return $RETVAL
}
restart() {
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
*)
echo $"Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
以下是一個樣本 tcstart.sh,可以修改它來適合您的安裝。
清單 4. 樣本 tcstart.sh
#!/bin/bash
export JDK_HOME=/usr/
java/jdk
export JAVA_HOME=/usr/java/jdk
#run the startup script from Tomcat installation
/home/tomcat/server/bin/startup.sh
chroot:最大的安全性監獄
對于真正的偏執狂,還可能進一步討論保護 Java 語言服務。當該服務提供對本地文件系統的某種形式的訪問權時,這尤其有用。JVM 運行時安全性特性并不能阻止已經有權訪問文件系統的應用程序訪問除了用戶專用的文件之外的文件。在 Tomcat 的情況中,文件訪問是它作為 HTTP 服務器使用時所固有的。它通常將為每個 Web 應用程序提供服務的文件限制為該應用程序目錄中的那些文件,但 servlet 應用程序可以避開這些限制。當 Tomcat 與諸如 Apache 之類的前端 Web 服務器共同運行時,也會發生這種情況。
通過使用 chroot,您可以阻止 Tomcat(和所有在 Tomcat 下運行的 Web 應用程序)訪問為該服務器留出的空間以外的任何東西。chroot 并不以任何方式特定于 Java 應用程序,但它是為 JVM 提供的安全性添加最終包裝器的簡便方法。我將在這里為那些不熟悉 chroot 概念的人介紹設置它的要點。
chroot 所做的類似于用于執行 Java 代碼的 JVM 沙箱,但它適用于文件系統本身。chroot 執行一條命令并將您指定的位置設置成有效根目錄。所執行的命令(可以是執行其它命令(包括應用程序)的 shell 腳本)只能訪問所指定的有效根目錄下的文件系統的一部分。文件系統的其余部分對于該命令來說完全不存在。
要對諸如 Tomcat 之類的應用程序使用 chroot,需要將一些基本系統應用程序和庫(包括實際的 Java JDK 安裝)復制到新的虛擬根目錄下。這可能會占用許多空間 — 也許從一百兆字節到一千兆字節或更多 — 這取決于您想盡多少努力來將這些應用程序和庫減少到最小。設置它的最簡單方法也最浪費空間:只要將整個 /bin、/lib、/usr/bin 和 /usr/lib 目錄樹以及 Java 安裝復制到新的根目錄下,使 root 用戶成為對所有文件的擁有者和唯一授權的寫入者。如果想要讓磁盤使用率保持到最小,可以有選擇地只復制 chroot 中需要的命令(包括基本命令,如 ls、rm、echo 和 cat 等,以及實際的 Java 安裝)以及那些命令使用的庫(通過使用 ldd 可以找到那些庫)。
接下來,您將需要創建一些附加的目錄作為常規系統的縮減版本。這包括 /dev,以及設備 /dev/null 和 /dev/zero;/etc 以及 /etc/passwd 和 /etc/group 文件的已編輯版本(只保留 root 和 tomcat 項),也許還有主機。如果正在多處理器系統上運行,您還需要在新的根目錄下掛裝 /proc 系統,因為 JVM 使用該系統來協調各個處理器。
最后,為了在設置了 chroot 后仍作為用戶 tomcat 運行,也許需要構建一個可以在虛擬根目錄下運行的 su 命令版本(許多分發版的一般版本不允許這樣做)。要這樣做,可以從 GNU 項目獲取 sh-utils 源碼并根據該源碼直接構建 su,然后將它復制到新的根目錄的 /bin 目錄中。
完成了所有這些設置后,可以嘗試以 root 用戶身份執行(假設新的根目錄位于 /home/tomcat):
/usr/sbin/chroot /home/tomcat /bin/su tomcat
如果設置正確,這應該讓您在新的根目錄下作為 tomcat 運行,而且您可以嘗試使用您的腳本來啟動和停止 Tomcat。在證實了所有東西都工作之后,最后一步就是將清單 3 中的 Tomcat 服務定義更改成使用 chroot 來代替 su,將 su 命令移到 tcstart.sh 腳本和 tcstop.sh 腳本。還需要確保只能由 root 用戶修改這些腳本。
希望這篇概述的解釋很清楚,這個過程并不適合膽小的人!如果您選擇走使用 chroot 這條路線,但以前沒有使用過 chroot,那么您一定要參考網上的 chroot 參考資料之一,以獲取詳細信息。但是這確實會給您的 Java 服務器代碼可能最好的隔離,而且在某些情況下,帶來的內心的寧靜值得這樣做。
結束語
Linux 和 Java 技術都正在贏得商業系統的市場份額。盡管開放源碼 Linux 和特許 Java 技術之間存在著原理上的差異,但這兩者在一起確實配合得很好。Linux 對于 Java 應用程序,尤其是對于服務器類型的應用程序是一個極好的部署環境,而 Java 技術是作為企業軟件開發的先進方法而建立且得到了認可。
通過正確的預防措施,在 Linux 上運行的 Java 服務器應用程序可以提供非常高的安全性程度 — 甚至高于本機應用程序 — 因為 Java 技術消除了服務器應用程序中許多弱點的常見來源。Java 技術的跨平臺性質所帶來的是包括企業開發人員和 Linux 立即就緒的應用程序的巨大資源集合。Java 服務器應用程序開始在日益增長的 Linux 的服務器市場份額中扮演重要角色,而且這種趨勢在將來只會幫助這兩種技術。
原文轉自:http://www.anti-gravitydesign.com