Jamf Concepts

指南

使用 jamformer 採納 Terraform for Jamf

~5 min read

在我們的之前的文章中,我們介紹了 Terraform 和 Jamf Pro 的基礎設施即程式碼基礎知識,然後逐步講解了 Jamf Protect 和 Jamf Platform 的專用 Terraform 提供者。如果您還沒有閱讀過,我們建議從那裡開始 — 它們涵蓋了 Terraform 基礎、專案結構、狀態管理,以及如何從零開始在程式碼中定義 Jamf 資源。

這一次,我們要解決一個我們經常聽到的問題:「這一切聽起來不錯,但我的 Jamf 環境中已經有數百個原則、設定檔、指令碼和群組。我真的必須手動重寫所有這些嗎?」

簡短的答案是否定的。我們建立了一個稱為 jamformer 的工具來幫助解決這個問題 — 但在深入探討之前,重要的是要坦誠說明它是什麼,以及不是什麼。

什麼是 jamformer?

jamformer 是一個支援和加速工具,協助團隊採納 Jamf with Terraform。它連接到您現有的 Jamf 執行個體、探索能找到的所有內容,並生成 Terraform 專案作為起點 — 一個真實的基礎架構供您學習和改進。可以將其視為連接您今天所在位置(透過主控台管理的完全配置 Jamf 環境)和您想要達到的位置(作為程式碼管理的同一環境)的橋樑 — 但橋樑不是傳送帶。您仍然需要走過去。

它刻意不是

  • 生產程式碼產生器。它產生的 HCL 是初稿。在您使用它管理真實基礎設施之前,預期需要審查每個檔案、重構為您自己的模組結構、應用命名慣例、加強祕密處理,以及處理提供者漂移特性。
  • Terraform 或 Jamf 提供者學習的替代品。它可以簡化學習曲線;它不會消除學習曲線。
  • 一鍵移轉按鈕。大型或不尋常的 Jamf 環境將始終需要人工判斷。

適合做的是:

  • 查看您的 Jamf 執行個體中實際存在的內容,以 Terraform 提供者自己的資源模型表示 — 屬性、交叉參照、生命週期特性等。
  • 啟動概念驗證、示範、工作坊、內部培訓和移轉規劃會議。
  • 為工程師提供真實的基礎架構來重構為 IaC,而不是盯著空白編輯器。

它也是唯讀的。jamformer 不會修改、建立或刪除 Jamf 執行個體中的任何內容。它唯一寫入的是本地機器上的一組 Terraform 檔案。

在閱讀本文的其餘部分時,請記住這個框架。當我們逐步講解工作流程並顯示範例輸出時,您看到的是供人類工程師改進的原始材料 — 而不是成品。

為什麼不直接使用 terraform import?

您當然可以,如果您已閱讀過 Protect 文章,您已經看到 terraform import 和帶 list 區塊的 terraform query 如何將現有資源納入管理。

但是如果您已經透過主控台管理 Jamf 環境一段時間,您會知道裡面有很多東西。手動匯入所有內容意味著要編制每個資源類型的清單、編寫數百個帶有正確 Jamf ID 的匯入區塊、找出哪個原則參照哪個指令碼、群組和類別、將嵌入的指令碼和設定檔有效負荷提取到獨立檔案中,以及處理預設值周圍的提供者特性。

這是大量的工作。我們想讓這個過程變得更容易。

它支援什麼?

jamformer 適用於 Jamf 產品系列中的四個 Terraform 提供者:

提供者 探索方式
Jamf Pro(預設) Jamf Pro API
Jamf Protect terraform query
Jamf Platform terraform query
Jamf Security Cloud (JSC) Terraform 資料來源

僅針對 Jamf Pro,這涵蓋了原則、指令碼、設定檔、智慧群組、靜態群組、電腦預置、行動裝置設定檔、延伸屬性、套件、類別、建築物、部門等等 — 加上 Client Check-In、Cloud Distribution Point 和 Self Service 設定等設定。權威的、始終最新的列表是 jamformer -list-resources(可選擇使用 -provider 進行篩選)。

執行時會發生什麼?

當您將 jamformer 指向 Jamf 執行個體時,它會經過幾個階段。我們將逐步講解每個階段的作用,以便您知道會發生什麼。

首先,它探索您的資源。針對 Jamf Pro,它驗證您的執行個體(OAuth2 或基本驗證)並查詢 API 以獲取每個支援的資源類型。它按依賴項順序遍歷您的環境 — 首先是站點和類別,然後是指令碼和套件,然後是原則和設定檔 — 因此它可以對應資源之間的關係。對於 Jamf Protect 和 Jamf Platform,它使用 terraform query 配合 list 區塊(這是我們在 Protect 文章中涵蓋的相同方法),直接透過提供者探索資源。

接下來,它生成匯入區塊。對於每個探索到的資源,jamformer 編寫一個 Terraform import 區塊,其標籤從資源名稱衍生而來。空格變成底線,特殊字元被移除,如果兩個資源最終得到相同的標籤,則新增數字尾碼以保持唯一性。它還生成針對正確提供者配置的 provider.tfvariables.tfterraform.tfvars

這些匯入區塊可能如下所示:

import {
  to = jamfpro_policy.software_update_enforce
  id = "142"
}

import {
  to = jamfpro_script.install_rosetta
  id = "87"
}

import {
  to = jamfpro_category.productivity
  id = "5"
}

然後它要求 Terraform 生成 HCL。jamformer 執行 terraform plan -generate-config-out(或針對 Protect 和 Platform 的 terraform query -generate-config-out),提供者會為每個匯入的資源產生 HCL。由於提供者本身在這裡承擔大部分工作,生成的設定始終符合提供者的期望。

如果特定資源無法匯入 — 這有時會發生,有時提供者無法乾淨地讀回特定資源 — jamformer 會自動移除失敗的匯入區塊並重試。您會看到一個訊息,指出哪些資源被略過,以便您稍後可以處理它們。

最後,它清理輸出。來自 Terraform 的原始輸出是一個大型檔案,到處都是文字值。jamformer 將其轉換為您實際想要使用的內容:

  • jamformer 將文字 Jamf ID 重寫為 Terraform 參照。參照類別 ID 5 的原則變成 jamfpro_category.productivity.id,這樣 Terraform 就能理解您資源之間的關係 — 就像您手動編寫一樣。
  • 嵌入的指令碼進入 support_files/scripts/ 下的個別檔案,設定檔有效負荷進入 support_files/macos_configuration_profiles/,延伸屬性指令碼進入 support_files/extension_attributes/,以及行動裝置應用設定進入 support_files/app_configurations/。jamformer 使用 file() 參照更新 HCL,因此您的指令碼和設定檔是您可以比較、審查和單獨編輯的適當檔案。裝置註冊和大量購買令牌使用 file(var.xxx) 路徑變數 — 您將從 Apple Business Manager 下載的令牌檔案放在您在變數中指定的路徑處。
  • jamformer 將圖示資源解析為 CDN URL(圖示不是在本地下載的),並新增 lifecycle { ignore_changes } 區塊以防止首次應用時不必要的銷毀/建立循環。
  • 傳回為 null 的選用屬性會被剝除,已知會造成問題的值(例如未分類資源的 category_id = -1)會被移除,單一大型檔案會分割成每個資源類型的檔案 — policies.tfscripts.tfcategories.tf 等。
  • 驗證程序執行 terraform validate 以偵測條件無效的屬性 — 例如,僅對特定 CDN 類型有效的屬性 — 並自動移除它們,這樣您就不必手動追蹤架構錯誤。

後處理後,jamformer 掃描輸出中的祕密。Jamf 環境通常包含嵌入在設定檔、指令碼和資源屬性中的認證 — 例如 LDAP 繫結密碼、SMTP 認證、Wi-Fi 共用祕密和 API 令牌。jamformer 使用gitleaks配合 Jamf 特定規則來在您意外提交之前尋找這些。

當 jamformer 發現祕密時,它會顯示按資源和屬性分組的報告。在互動模式下,您可以選擇自動修復所有發現、逐個檢查或完全跳過修復。修復會將祕密移到敏感 Terraform 變數中 — 對於 .tf 檔案,祕密值被替換為 var. 參照;對於指令碼和設定檔等支援檔案,檔案被轉換為使用 templatefile().tpl 範本。如果您想自己處理祕密,可以使用 -skip-secret-scan 跳過此步驟。

快速入門

如果您已按照簡介文章進行操作,您應該已經設定好 Terraform 和文字編輯器。這裡的步驟很簡單。

1. 安裝 jamformer

Homebrew:

brew install Jamf-Concepts/tap/jamformer

預先建置的二進位檔和 .pkg 安裝程式可從發布頁面取得。

您無需單獨安裝 Terraform — jamformer 會自動將其下載到臨時目錄,因此不會與您機器上的任何現有 Terraform 安裝衝突。

2. 針對您的執行個體執行它

開始最簡單的方法就是無參數執行 jamformer:

jamformer

它將以互動方式引導您完成所有操作 — 您想使用的提供者、您的執行個體 URL 和您的認證。您輸入密碼和用戶端祕密作為隱藏輸入,以免它們顯示在終端機歷史中。

簡寫 URL 也可以在此工作 — 您只需輸入 yourinstance,jamformer 會將其展開為 yourinstance.jamfcloud.com。對於 Protect,your-tenant 展開為 your-tenant.protect.jamfcloud.com

一旦您對工具感到舒適或想要編寫腳本,您可以透過環境變數設定認證:

# Jamf Pro with OAuth2
export JAMF_CLIENT_ID='your-client-id'
export JAMF_CLIENT_SECRET='your-client-secret'
jamformer -url https://yourinstance.jamfcloud.com

# Jamf Protect
export JAMF_CLIENT_ID='your-client-id'
export JAMF_CLIENT_SECRET='your-client-secret'
jamformer -provider jamfprotect -url https://your-tenant.protect.jamfcloud.com

jamformer 只從環境變數或互動提示讀取認證 — 永遠不會使用 CLI 標誌 — 以避免在 shell 歷史和程序列表中洩露祕密。您可以將 URL 作為標誌傳遞(-url)或透過 JAMF_URL

jamformer 不會將認證寫入 terraform.tfvars,並生成一個 .gitignore,排除 tfvars 檔案、狀態檔案和 .terraform/ 目錄。

3. 一次匯入所有內容是選項

如果您的環境很大,您想要小規模開始,可以指定特定資源類型:

# Start with just policies, scripts, and categories
./jamformer -include-resources "policies scripts categories"

# Everything except packages and icons
./jamformer -exclude-resources "packages icons"

# See all available filter names
./jamformer -list-resources

如果未包括依賴項類型(比如您匯入原則但不匯入類別),這些類型的參照將保持為文字 ID 而非 Terraform 表達式。當您變得更舒適時,您可以隨時使用更多類型重新執行。

那麼,輸出實際上看起來像什麼?

jamformer 完成後,您將擁有一個看起來像這樣的目錄:

generated/
  .gitignore
  provider.tf
  variables.tf
  terraform.tfvars
  policies.tf
  policies_import.tf
  scripts.tf
  scripts_import.tf
  categories.tf
  categories_import.tf
  ...
  support_files/
    scripts/
      install_rosetta.sh
      set_wallpaper.sh
    extension_attributes/
      battery_health.sh
    macos_configuration_profiles/
      wifi_corporate.mobileconfig
      filevault_enforce.mobileconfig
    device_enrollment_tokens/     (empty - place your .p7m files here)
    volume_purchasing_tokens/     (empty - place your .vpptoken files here)
    app_configurations/
      managed_browser.xml

每個資源類型都有自己的 .tf 檔案和相應的 _import.tf 檔案。support_files/ 目錄包含透過 file() 表達式參照的已提取指令碼、設定檔和應用設定。jamformer 建立令牌目錄(device_enrollment_tokens/volume_purchasing_tokens/)作為 Apple Business Manager 令牌檔案的建議位置 — 這些使用 file(var.xxx) 路徑變數,因為 API 不傳回令牌資料。

精簡模式

預設情況下,jamformer 為每個物件生成一個資源區塊 — 一個平面、明確的配置,易於閱讀和審查。但是如果您的環境有很多相似的資源(類別、建築物、部門),輸出可能會感到重複。

-compact 標誌將符合條件的資源類型合併到 for_each + locals 模式中,產生更接近您手動編寫的內容的輸出:

jamformer -compact

沒有精簡模式,您會得到個別區塊:

resource "jamfpro_category" "productivity" {
  name = "Productivity"
}

resource "jamfpro_category" "security" {
  name = "Security"
}

啟用精簡模式後,這些變成:

locals {
  categories = {
    productivity = { name = "Productivity" }
    security     = { name = "Security" }
  }
}

resource "jamfpro_category" "all" {
  for_each = local.categories
  name     = each.value.name
}

jamformer 會自動重寫交叉資源參照以使用新的定址 — jamfpro_category.all["productivity"].id 而非 jamfpro_category.productivity.id — 所以一切保持連接。它以相同的方式更新匯入區塊。

jamformer 在執行時動態決定符合條件。當檔案包含兩個或更多資源區塊,全部共享相同的屬性集合,且沒有嵌套區塊(除 lifecycle 外)時,資源類型符合條件。在所有執行個體中相同的屬性變成資源區塊上的文字值;只有在變數間不同的值進入 locals 對應。這在所有四個提供者中都有效。

如果您想要更細級的控制,可以指定特定類型:

# Only compact categories and buildings
jamformer -compact -compact-include "categories buildings"

# Compact everything eligible except policies
jamformer -compact -compact-exclude "policies"

多環境支援

⚠️ 實驗性和高度進階。 此功能針對已經熟悉 Terraform 模組、長期分支工作流程和 Jamf 提供者資源模型的人員。輸出是更多基礎架構等級而不是單環境模式 — 在任何內容可用之前,預期需要編輯生成的模組、每個環境根和變數提取。將其視為研究/支援功能,而不是受支援的生產工作流程。如果您是 Terraform 新手或仍在完成第一個單一執行個體專案,請從那裡開始,稍後再回到這裡。

如果您管理多個 Jamf Pro 環境 — 預置和生產,或開發和生產 — 您可能已經考慮過如何保持同步。jamformer 可以生成圍繞共用模組的 Terraform 專案,具有每個環境根目錄,專為 Git 分支工作流程設計,其中每個長期分支代表一個環境。

export JAMF_URL_STAGING=https://staging.jamfcloud.com
export JAMF_CLIENT_ID_STAGING=xxx
export JAMF_CLIENT_SECRET_STAGING=xxx
export JAMF_URL_PROD=https://prod.jamfcloud.com
export JAMF_CLIENT_ID_PROD=xxx
export JAMF_CLIENT_SECRET_PROD=xxx

jamformer -multi-env "staging prod"

jamformer 針對每個環境單獨執行探索、按名稱符合資源,並產生看起來像這樣的輸出:

generated/
  modules/jamf/                # shared resource definitions
    policies.tf                # resources present in all environments
    scripts.tf
    categories.tf
    policies_staging_only.tf   # resources only in staging
    variables.tf               # module input variables
    providers.tf
    support_files/             # files identical across environments
      scripts/
      macos_configuration_profiles/
  environments/
    staging/
      main.tf                  # provider config + module call
      backend.tf               # placeholder for your state backend
      variables.tf
      terraform.tfvars         # environment-specific values
      imports.tf               # import blocks with module.jamf. prefix
      support_files/           # files that differ from the other env
    prod/
      main.tf
      backend.tf
      variables.tf
      terraform.tfvars
      imports.tf
      support_files/

modules/jamf/ 中的共用模組包含所有環境共有的資源定義。當屬性值跨環境有所不同時 — 具有不同 category_id 的原則或具有不同 description 的設定檔 — jamformer 會將這些值提取到模組變數,並在 terraform.tfvars 中按環境設定。交叉資源參照保持為適當的 Terraform 表達式(例如 jamfpro_category.productivity.id),所以資源之間的關係保持完整。

當您有意保持您的環境同步時,這效果最好 — 在執行個體中部署相同的原則、指令碼、設定檔和群組。它們匹配得越多,輸出就越清晰。也就是說,jamformer 處理常見的真實世界差異:

  • 相同資源、不同設定(例如,在預置和生產中具有不同類別或頻率的原則)— 不同的屬性變成模組變數,每個環境的值在其自己的 terraform.tfvars 中。jamformer 按字母順序排序變數,並按資源類型分組以提高可讀性。
  • 在某些環境中存在但不在其他環境中存在的資源(例如,僅在預置中的測試原則)— jamformer 將這些分隔成清楚標記的檔案,例如模組中的 policies_staging_only.tf。在另一個環境的分支上,您只需刪除這些檔案。
  • 不同的支援檔案(例如,在執行個體間內容略有不同的指令碼)— 在所有環境中相同的檔案位於共用模組的 support_files/ 目錄中。當檔案不同時,jamformer 將它們放在每個環境自己的 support_files/ 目錄中,並將它們作為變數傳遞到模組。

每個環境目錄都有自己的 backend.tf 佔位符、自己的狀態和自己的匯入區塊,前綴為 module.jamf.。這意味著您可以獨立執行每個環境的 terraform planterraform apply,如果需要也可以並行執行,不存在一個環境的狀態干擾另一個環境的風險。

jamformer 使用列出的第一個環境作為共用資源定義的真實來源;使用 -source-env 覆蓋此項。它按名稱符合資源,因此預置中稱為「Software Update - Enforce」的原則將符合生產中具有相同名稱的原則,無論其 Jamf ID 如何。

預期的工作流程是將輸出提交到存放庫、為每個環境建立長期分支、使用適當的狀態後端設定每個分支的 backend.tf,以及設定 CI 在程式碼進入該分支時在對應的 environments/<env>/ 目錄中執行 terraform apply。變更從功能分支流入第一個環境,然後透過拉取請求升級分支。

您的環境差異越大,輸出包含的變數和環境特定檔案就越多。如果您的執行個體差異很大,您可能最好針對每個執行個體單獨執行 jamformer,並維護獨立的 Terraform 專案。

使用生成的輸出

我們在最上面說過,我們也會在這裡再說一遍,因為這是最重要的部分:生成的設定不是生產就緒的 Terraform。 jamformer 是一個支援和加速工具。它為您在管理 Jamf 環境作為程式碼的旅程中提供了真實的起點 — 不是立即到達那裡的捷徑。在任何內容驅動真實基礎設施變更之前,計劃花費真實的時間審查、改進和理解它生成的內容。

輸出刻意是平面的 — 每個物件一個資源區塊、每個類型一個檔案、沒有模組。實際上,您可能想要將其重構為模組化專案結構、利用 HCL 功能(如 for_eachlocals)來減少重複、將實例 URL 和通用設定等內容轉換為您環境的變數,以及以對您的團隊有意義的方式組織資源。jamformer 為您提供原始材料來進行此操作;您如何塑形它是由您決定的。該塑形工作是大多數 Terraform 學習實際發生的地方 — 這是 jamformer 存在於其當前形式的全部原因。

考慮到這一點,以下是我們建議的工作流程。

您在簡介中學到的相同命令在此適用:

cd generated
terraform plan       # Review the import plan, check for provider errors

預期首次計劃會出現差異。 看到 Terraform 生成的內容和 Jamf 執行個體中實際存在的內容之間出現差異是正常的。某些屬性可能顯示與您在主控台中看到的不符的值,某些選用欄位可能具有提供者填寫的預設值,這些預設值與實時設定不同,且某些資源可能由於提供者無法讀回的唯寫欄位而出現驗證錯誤。這些是提供者級別的特性,不是 jamformer 中的錯誤,它們需要手動注意。

逐步完成計劃輸出,修復任何錯誤並調整屬性,直到您對 Terraform 報告的內容感到滿意。此審查過程是您學習最多關於提供者如何表示您資源的地方 — 這是在將控制權交給 Terraform 之前的重要步驟。

一旦計劃看起來乾淨:

terraform apply      # Import resources into state (you'll be asked to confirm)

在此執行 terraform apply 會將您現有的資源匯入 Terraform 狀態。它不會改變 Jamf 中的任何內容 — 它只是告訴 Terraform「這些資源已存在,以下是它們的外觀。」

成功應用後,匯入區塊已完成其任務。您可以移除它們:

rm *_import.tf

然後再次執行 terraform plan。理想情況下,您會看到沒有變更。如果出現差異,這些是提供者的預設值與 Jamf 中實際存在的內容不完全符合的情況 — 調整生成的 HCL 直到獲得乾淨的計劃。

此時,您有一個代表 Jamf