來自MicroBadger的Ross Fairbanks從兩個關于容器安全的視頻里總結了幾個針對dockerfile的安全調整,有助于構建更加安全的容器鏡像。以下內容翻譯自Ross的文章。
最近我觀看了兩個有關容器安全的演講視頻,其中一個視頻的演講者是來自Docker的Justin Cormack,另一個是來自Container Solutions的Adrian Mouat。我們已經遵循了很多安全方面的建議,不過仍然有改進的空間。所以,我們覺得是時候對我們的dockerfile來一次安全大調整。
官方鏡像
我們是Alpine Linux鏡像的忠實用戶,因為相比于Debian或Ubuntu,它的體積更小,并且具有更小的可攻擊表面。所以,我們使用官方的Alpine鏡像作為其它鏡像的基礎。除此以外,官方鏡像還有另一個好處——Docker有一個專門的團隊在維護它,并一直遵循著最佳實踐。
我們的主要開發語言是Go,不過有時候也會用Ruby來處理一些基于Web和腳本的任務。我們使用ruby:2.3-alpine作為這些鏡像的基礎。ruby:2.3-alpine的Ruby是從源代碼安裝的,并非來自Alpine包。通過語義化版本控制(Semantic Versioning),Ruby 2.3能夠接收到來自Ruby核心團隊的安全更新。同時,Docker團隊會更新標簽2.3-alpine。
如果不使用ruby:2.3-alpine,我們就要自己從源代碼安裝Ruby,并持續跟進Ruby新版本的發布情況,或者使用Alpine包,并持續檢查Alpine包內是否有新的Ruby版本。
通知和Web鉤子(Webhook)
從Ruby的例子可以看到,當底層的基礎鏡像接收到安全更新時,你的整個鏡像也要跟著重新構建。這個時候,我們的MicroBadger通知系統可以幫我們完成一些事情。我們在Alpine和Ruby的官方鏡像上使用了通知。我們會從Slack接收到我們所關心的公共鏡像的變更通知。
當基礎鏡像發生變更時,我們還會利用通知來自動觸發鏡像的構建。Docker Hub也有這個功能,不過我們的通知可以用于任何支持Web鉤子的系統,比如CI系統或安全掃描器。
非授權用戶
容器和虛擬機之間的一個關鍵區別是容器與主機共享內核。在默認情況下,Docker容器運行在root用戶下,這會導致泄露風險。因為如果容器遭到破壞,那么主機的root訪問權限也會暴露。
使用非授權用戶來運行容器可以降低這種風險。下面是一個有關Rails應用的例子:
# Create working directory.WORKDIR /app# Copy Rails app code into the imageCOPY . ./# Create non privileged user, set ownership and change userRUN addgroup rails && adduser -D -G rails rails && chown -R rails:rails /appUSER rails安全掃描
在容器注冊中心運行安全掃描可以為我們帶來額外的價值。除了存放鏡像,鏡像注冊中心定期運行安全掃描可以幫助我們找出薄弱點。Docker為官方鏡像和托管在Docker Cloud的私有鏡像提供了安全掃描。
來自CoreOS的Clair也很不錯,它是開源的,并被用于Quay.io的鏡像注冊中心安全掃描。Clair將支持Alpine,這是一個好消息,希望可以盡快在Quay上面看到它。還有其它一些掃描器,比如TwistLock和Aqua,它們都是付費產品。
經過Docker的安全掃描,我們可以得到一個干凈的Go鏡像。我們把一個二進制版本拷貝到鏡像里,然后加入一個CA認證依賴,這樣我們就可以建立HTTPS連接。我們的Rails應用會有更多的依賴,因為Ruby是解釋型語言。我們需要為我們的應用安裝所有的Ruby gem,同時需要為這些gem安裝所有的操作系統依賴。
安全掃描會把掃描結果保存到libxml2和libxslt文件里。Nokogiri是XML和JSON解析器,在構建鏡像時,它會依賴前面提到的兩種文件。為了得到更好的性能,Nokogiri使用了編譯過的C語言擴展,不過一旦安裝完畢,就不再需要libxml2和libxslt文件了。
下面我們來移除構建依賴,這些命令有點復雜:
# Cache installing gemsWORKDIR /tmpADD Gemfile* /tmp/# Update and install all of the required packages.# At the end, remove build packages and apk cacheRUN apk update && apk upgrade && apk add --no-cache $RUBY_PACKAGES && apk add --no-cache --virtual build-deps $BUILD_PACKAGES && bundle install --jobs 20 --retry 5 && apk del build-depsGemfile包含了需要安裝的gem,同時Gemfile.lock用來管理依賴關系鏈。通過把這些文件緩存在/tmp目錄,只有當Gemfile發生變更時,bundle install命令才會被執行。如果Gemfile沒有發生變更,它會使用緩存里的文件。這個緩存很有用,因為安裝gem是一個很耗時的任務,還會占用很多帶寬。
run命令跨了很多行,只有一個層會被添加到鏡像里,這個層包含了apk和gem包。構建包是作為虛擬包被添加進去的,在安裝完畢之后它們可以很容易被移除。
更新:編譯過的二進制仍然可能是不安全的
移除構建依賴會為我們帶來一些好處,不過為了gem的擴展,鏡像里仍然會存留一個二進制包。這個二進制包很難被掃描器發現,所以它或許仍然是一個會暴露薄弱點的地方。
所以,對這個二進制包進行安全檢查是必要的。對于Nokogiri來說,我們使用了1.6.8版本,這個版本包含了最新的libxml和libxslt安全補丁。不過這些包的CVE元數據可能存在一個問題,我已經向Docker安全掃描團隊反饋過這個問題。
自動構建
關于容器安全很關鍵的一點是,當鏡像或基礎鏡像有安全方面的更新時,需要對鏡像進行重新構建。鏡像被關聯到Git倉庫,所以自動構建會讓整個過程變得很容易。當倉庫分支上有新的提交時,如果我們對其進行了跟蹤,那么一個構建就會被觸發。在之前的例子里我們已經看到,當基礎鏡像發生變更時也會自動觸發構建。
我們的Ruby鏡像自動構建會簡單一些,因為自動構建可以直接使用本地的Dockerfile。不過我們的Go鏡像就麻煩一些,因為在把二進制包加入鏡像之前需要先通過編譯,我們通過使用本地的makefile來完成編譯。
我們可以使用鉤子來進行自動構建,并使用Docker容器來編譯二進制包。來自CenturyLinkLabs和Prometheus的Go語言構建器鏡像都是很不錯的選擇。
可以通過鉤子來調用這些構建器鏡像。我們也可以使用鉤子往鏡像里添加動態的元數據,就像我最近在博客里所說的那樣。
我無法覆蓋視頻里所提到的所有主題,所以如果有可能,大家可以自己去看視頻。最后我想說的是,我們在MicroBadger里添加了對私有倉庫的支持,所以你們可以為Docker Hub上的私有倉庫使用通知。
本文已獲得原作者翻譯授權,查看英文原文:Dockerfile security tuneup
感謝木環對本文的審校。