如何在Docker Multi-Stage Builds使用Private Nuget Service還原套件
在使用 multi-stage builds 進行建置 .NET / .NET Core 應用程式時發現一個有趣的事,在 docker build
內進行 nuget.org 還原時可正常還原套件。但如果是使用到 Private Nuget Service 時,例如,我們採用的是 Azure Artifacts - NuGet 服務,就會取得一堆紅色的錯誤訊息:
Errors in packages.config projects
Unable to find version '2018.8.2.3' of package 'KingXXXX.YYYYYYYYYYor'.
https://api.nuget.org/v3/index.json: Package 'KingXXXX.YYYYYYYYYYor.2018.8.2.3' is not found on source 'https://api.nuget.org/v3/index.json'.
Unable to find version '2017.10.17.1' of package 'KingXXXX.YYYYYYYYYYYYYYFramework'.
https://api.nuget.org/v3/index.json: Package 'KingXXXX.YYYYYYYYYYYYYYFramework.2017.10.17.1' is not found on source 'https://api.nuget.org/v3/index.json'.
Unable to find version '2018.7.24.2' of package 'KingXXXX.PPPPService'.
https://api.nuget.org/v3/index.json: Package 'KingXXXX.PPPPService.2018.7.24.2' is not found on source 'https://api.nuget.org/v3/index.json'.
NuGet Config files used:
C:\Users\ContainerAdministrator\AppData\Roaming\NuGet\NuGet.Config
Feeds used:
https://api.nuget.org/v3/index.json
方案一:加入 nuget.config
在一個 GitHub 討論串裡,看到第一種解決,在原專案中加去 nuget.config 組態,並指定好 Private Nuget Service 位置,修改 Dockerfile 一併複製進去 Build Context,當 nuget restore
執行時發現 nuget.config 存在時,就會讀取設定好的 nuget.config 來進行還原。
我們以範例來說明:
Dockerfile
FROM microsoft/aspnetcore-build:latest AS build
WORKDIR /src
COPY . .
COPY NuGet.Config ./
RUN dir
RUN dotnet restore --configfile NuGet.Config -nowarn:msb3202,nu1503 --verbosity diag
RUN dotnet publish --output /output --configuration Release
FROM microsoft/aspnetcore:latest
WORKDIR /app
COPY --from=build /output /app
ENTRYPOINT ["dotnet", "DockerProject.dll"]
COPY NuGet.Config ./
把組態好的 nuget.config 複製進去 build context 裡。
nuget.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="AzurDevops" value="https://pkgs.dev.azure.com/{YourName}/_packaging/{YourFeed}/nuget/v3/index.json" />
</packageSources>
<packageSourceCredentials>
<AzurDevops>
<add key="Username" value="Email here" />
<add key="ClearTextPassword" value="PAT here" />
</AzurDevops>
</packageSourceCredentials>
</configuration>
預設只有 nuget.org 那一條組態,我們把 Private Nuget Service 加入組態,組態內容可以 Azure DevOps - NuGet 取得。此方法可行,但有個可能資安問題,在 nuget.config 組態裡,我們必須包含 PAT 憑證。不過,這是不是一個資安問題,我個人是保留態度,原因很簡單:
- web.config 裡一樣包含
ConnectionString
,ConnectionString
裡面如果沒有認真處理過,那麼資料庫的帳號外流,我覺得比 nuget.config 更慘。 - 由於我們是採用 multi-stage builds,nuget.config 只存在
AS build
這一暫存層,而非最終產出的 runtime 映像檔之內,我能想到的點只有COPY --from=build
沒處理好,把未編輯的專案給複製進最終 runtime 映像檔。 - 我比較偏向它是一個缺點,因為我們必須含 PAT 憑證的 nuget.config 給簽入版控:
當我們把 nuget.config 複製進去 build context 之後,我們的 .NET 採用 nuget.exe 來還原,nuget.exe 會發現存在其他設定,nuget.exe 會一併讀取 nuget.config 與使用。當然,如果你真的非常要求 PAT 憑證問題,那也能再多加一行 RUN del nuget.config
來處理 PAT 憑證問題。也就是說,記錄上會看到 nuget.config,但就算 build 暫存映像檔被人取走,裡面也不會有 nuget.config 的存在。
方案二:NuGet CLI
我們知道問題點在 nuget.exe 在 Windows 基礎映像檔(Windows Base Image)建置時沒有 Private Nuget Service 與 PAT 憑證相關設置,換個角度,那麼我們是否可以使用 nuget.exe 本身提供的 CLI 來處理。
- 參數說明:NuGet CLI
RUN nuget sources Add `
-Name "AzureDevOpsNuGet" `
-Source "https://pkgs.dev.azure.com/{YourName}/_packaging/{YourFeed}/nuget/v3/index.json" `
-username {Email} `
-password {PAT} `
-StorePasswordInClearText
透過文件,只要在 RUN nuget restore
之前把 Private NuGet Service 的相關組態寫好,也能解決。缺點一樣,只是從 nuget.config 轉移至 Dockerfile 之內,但轉移至 Dockerfile 之後有了另一好處!
方案三:docker build 與 Dockerfile
當我們把 nuget 的組態從 nuget.config 拉回 Dockerfile 之後,我們就能利用 Dockerfile 本身的 ARG 變數功能,把機敏資訊可以從 Dockerfile 裡抽離,最後我們只需要在 docker build --build-arg {Key}={Value} .
在呼叫建置指時才由外部指定 ARG
。
docker build -t {yourTag} `
--build-arg SOURCE='https://pkgs.dev.azure.com/{YourName}/_packaging/{YourFeed}/nuget/v3/index.json' `
--build-arg USERNAME='{Email}' `
--build-arg PAT='{PAT}' `
方案四:Azure Artifacts Credential Provider
Azure Artifacts Credential Provider 主要整合 dotnet.exe、NuGet.exe、MSBuild 多個指令,提供互動式取得憑證來存取 Azure Artifacts feeds。Azure Artifacts Credential Provider 文件寫的算清楚,請自行參考。但在測試過程碰到一個問題,VSS_NUGET_EXTERNAL_FEED_ENDPOINTS 同時間只能有一組設定值,剛好我們需要二組 FEED,我實在不想再 Dockerfile 內容把問題複雜化了。
ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS {"endpointCredentials": [{"endpoint":"http://example.index.json", "username":"optional", "password":"accesstoken"}]}
不過核心問題還是我們免不了將 PAT 傳入 Dockerfile 之內。
docker history
注意:所有在 Dockerfile 跑過的指令都能完整使用 docker history --no-trunc
來完整查詢。
包含使用 ARG
也是一樣會完整呈現在 docker history
。
解決四:docker build --secret
docker build --secret 透過 Builtkit 專案提供的功能,讓 Dockerfile 擁有類似 docker secret CLI 的能力,能從安全的地方來讀取機敏資料。但可惜,撰文當下,還不支援 Windows Container 的使用。
小結
在使用 Docker Multi-Stage Builds 過程,通常 PAT 只會存在於第一個 tag :sdk 映像檔 裡,當建置與發行完後從,我們會把可執行檔由 sdk 複製至 runtime 的映像檔中,建置出最終所需含執行檔的映像檔。這個 runtime 映像檔應該只包含發行後的 *.dll 或 *.exe,docker history
也不會含有相關執行 PAT 記錄。
如果確認 Build Agent 在 Build 過程暫存的 SDK 映像檔是不會被外人碰到的,那麼除了方案四無法在 Windows Container 使用之外,使用方案一、二、三都是可行的方式。在保護好 Build Agent (SDK 映像檔)的前提下,這個 PAT 是不是資安問題,我認為是有討論的空間。
沒有留言:
張貼留言
感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。