Jamf Concepts

ガイド

Terraform と jamformer を使用して Jamf を導入する

~5 min read

以前の記事では、Terraform と Jamf Pro を使用した Infrastructure as Code の基礎をカバーし、Jamf Protect と Jamf Platform 向けの専用 Terraform プロバイダーについて説明しました。まだお読みでない場合は、そちらから始めることをお勧めします。Terraform の基礎、プロジェクト構造、状態管理、およびスクラッチから Jamf リソースをコードで定義する方法がカバーされています。

今回は、よく聞かれる質問に取り組みます。「これはすべて素晴らしく聞こえますが、Jamf 環境にはすでに数百のポリシー、プロファイル、スクリプト、グループがあります。本当にすべてを手作業で書き直す必要がありますか?」

短い答えは「いいえ」です。正確にこれに役立つ、jamformer というツールを構築しました。ただし、詳しく説明する前に、それが何であり、何でないかを明確にすることが重要です。

jamformer とは何か?

jamformer は、Terraform を使用して Jamf を導入するチーム向けの支援・加速ツールです。既存の Jamf インスタンスに接続し、発見できるすべてをスキャンして、出発点として Terraform プロジェクトを生成します。実践的なスキャフォルドで、学習して改善できます。これは、現在の状態(コンソールで管理されている完全に設定された Jamf 環境)から目的の状態(コードとして管理されている同じ環境)への橋だと考えてください。ただし、その橋はコンベアベルトではありません。歩いて渡ります。

これは意図的にではありません:

  • 本番環境対応のコード ジェネレータ。生成される HCL は初版です。実際のインフラストラクチャを管理する前に、すべてのファイルを確認し、独自のモジュール構造にリファクタリングし、命名規則を適用し、シークレット処理を強化し、プロバイダーのドリフト特性に対処することを期待してください。
  • Terraform または Jamf プロバイダーの学習に代わるもの。学習曲線を平坦にします。削除しません。
  • ワンクリック移行ボタン。大規模または異常な Jamf 環境には、常に人間の判断が必要です。

役に立つもの:

  • Terraform プロバイダー独自のリソース モデル(属性、相互参照、ライフサイクルの特性など)で表現される、Jamf インスタンスに実際に含まれているものを確認します。
  • 概念実証、デモ、ワークショップ、内部トレーニング、および移行計画セッションのブートストラップ。
  • エンジニアが空白のエディタを見つめるのではなく、IaC にリファクタリングする現実的なスキャフォルドを提供します。

また、読み取り専用です。jamformer は、Jamf インスタンス内の何かを変更、作成、または削除しません。書き込むのは、ローカル マシン上の Terraform ファイルのセットだけです。

このフレーミングを念頭に置いて、このポストの残りを読んでください。ワークフローを実行し、出力例を示す場合、人間のエンジニアが改善する必要がある生の素材を見ています。完成した記事ではありません。

terraform import を使用しないのはなぜですか?

確実に利用できます。Protect 記事を読んだ場合、terraform importlist ブロック付きの terraform query を使用して既存のリソースを管理下に置く方法を既に見ています。

ただし、コンソールを通じて Jamf 環境を任意の期間管理している場合は、その中に多くのものがあることをご存じでしょう。すべてを手動でインポートするには、すべてのリソース タイプをインベントリ化し、正しい Jamf ID を持つ数百のインポート ブロックを記述し、どのポリシーがどのスクリプトを参照しているか、どのグループとどのカテゴリを参照しているかを把握し、埋め込みスクリプトと設定プロファイルのペイロードをスタンドアロン ファイルに抽出し、デフォルト値に関するプロバイダーの特性に対処します。

それは大量の作業です。これを簡単にしたいと思いました。

何をサポートしていますか?

jamformer は、Jamf 製品ファミリー全体の 4 つの Terraform プロバイダーと連携します:

プロバイダー 検出方法
Jamf Pro (デフォルト) Jamf Pro API
Jamf Protect terraform query
Jamf Platform terraform query
Jamf Security Cloud (JSC) Terraform データソース

Jamf Pro だけでも、ポリシー、スクリプト、設定プロファイル、Smart Groups、Static Groups、コンピュータ プレステージ、モバイル デバイス プロファイル、拡張属性、パッケージ、カテゴリ、建物、部門などをカバーします。さらに、Client Check-In、Cloud Distribution Point、Self Service 設定などの設定もカバーします。権威ある常に最新のリストは jamformer -list-resources です(オプションで -provider でフィルタリングできます)。

実行するとどうなりますか?

jamformer を Jamf インスタンスに指定すると、いくつかのステージを通過します。それぞれが何をするかについて説明するので、何を期待するかがわかります。

まず、リソースを検出します。Jamf Pro の場合、インスタンスに認証し(OAuth2 または基本認証)、サポートされているリソース タイプごとに API をクエリします。依存関係の順序で環境を歩み進みます。サイトとカテゴリ、その後スクリプトとパッケージ、その後ポリシーとプロファイルです。リソース間の関係をマップできます。Jamf Protect と Jamf Platform の場合、list ブロック付きの terraform query を使用します(Protect 記事でカバーされたアプローチと同じ)。プロバイダーを通じて直接リソースを検出します。

次に、インポート ブロックを生成します。検出された各リソースについて、jamformer はリソース名から派生したラベルを持つ Terraform import ブロックを書き込みます。スペースはアンダースコアになり、特殊文字は削除され、2 つのリソースが同じラベルになった場合、数値サフィックスが追加されて一意性が保たれます。また、正しいプロバイダー向けに設定された 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 は HCL を file() 参照で更新するため、スクリプトとプロファイルは独立して diff、確認、編集できる適切なファイルになります。デバイス登録と Volume Purchase Program トークンは file(var.xxx) パス変数を使用します。トークン ファイル(Apple Business Manager からダウンロード)を変数で指定したパスに配置します。
  • jamformer はアイコン リソースを CDN URL に解決し(アイコンはローカルでダウンロードされません)、lifecycle { ignore_changes } ブロックを追加して、最初の適用時の不要な destroy/create サイクルを防ぎます。
  • null として戻ってくるオプション属性は削除され、問題を引き起こすことが知られている値(カテゴリなしのリソースの場合の category_id = -1)は削除され、単一の大きなファイルはリソース タイプごとのファイルに分割されます。policies.tfscripts.tfcategories.tf など。
  • 検証パスは terraform validate を実行して、条件付きで無効な属性を検出します。例えば、特定の CDN タイプにのみ有効な属性。手作業でスキーマ エラーを追跡する必要がないように自動的に削除します。

後処理後、jamformer は出力でシークレットをスキャンします。Jamf 環境には、設定プロファイル、スクリプト、リソース属性に埋め込まれた認証情報が含まれることがよくあります。LDAP バインド パスワード、SMTP 認証情報、Wi-Fi 共有シークレット、API トークンなど。jamformer は gitleaks を Jamf 固有のルールで使用して、誤って Git にコミットする前に検出します。

jamformer がシークレットを検出すると、リソースと属性別にグループ化されたレポートが表示されます。対話型モードでは、すべての結果を自動的に修復するか、個別に確認するか、修復をスキップするかを選択できます。修復はシークレットを sensitive 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-tenantyour-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 フラグからは決して読み込みません。シェル履歴とプロセス リストでシークレットをリークするのを避けるためです。URL をフラグ(-url)または JAMF_URL 経由で渡すことができます。

jamformer は認証情報を terraform.tfvars に書き込まず、tfvars ファイル、状態ファイル、.terraform/ ディレクトリを除外する .gitignore を生成します。

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

依存タイプが含まれていない場合(ポリシーをインポートしますがカテゴリはない場合)、これらのタイプへの参照は Terraform 式ではなくリテラル ID のままです。快適になったら、いつでも後でより多くのタイプで再実行できます。

では、出力は実際にどのように見えますか?

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 トークン ファイルの推奨される場所です。API がトークン データを返さないため、file(var.xxx) パス変数を使用します。

Compact モード

デフォルトでは、jamformer は 1 つのリソース ブロック(フラットで明示的なレイアウト)を生成します。読みやすく確認しやすいです。ただし、環境に多くの類似リソース(カテゴリ、建物、部門)がある場合、出力は反復的に感じられます。

-compact フラグは、適格なリソース タイプを for_each + locals パターンに統合し、手作業で書くものに近い出力を生成します:

jamformer -compact

compact モードなしでは、個別のブロックが表示されます:

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

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

compact モードを有効にすると、これらは次のようになります:

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 を使用します。すべてが接続されたままです。インポート ブロックも同じ方法で更新されます。

jamformer は実行時に適格性を動的に決定します。リソース タイプは、ファイルが 2 つ以上のリソース ブロックを含み、すべてが同じ属性セットを共有し、ネストされたブロック(ライフサイクル以外)を持たない場合に適格になります。すべてのインスタンスで同じ属性は、リソース ブロック上のリテラル値になります。バリエーションのある値だけが locals マップに入ります。これは 4 つのプロバイダー全体で機能します。

より細かく制御したい場合は、特定のタイプをターゲットにできます:

# 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 は、各長寿命ブランチが環境を表す Git ブランチング ワークフロー向けに設計された、共有モジュールとこれぞれの環境ルート ディレクトリの周りに構造化されたTerraform プロジェクトを生成できます。

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 を適切な状態バックエンドで設定し、そのブランチに対応する environments/<env>/ ディレクトリ内で terraform apply を実行するように CI をセットアップします。変更は機能ブランチから最初の環境にフローし、プル リクエスト経由でブランチを通じて昇格します。

環境が乖離するほど、出力に含まれる変数と環境固有のファイルが増えます。インスタンスが大幅に異なる場合は、jamformer をそれぞれに対して個別に実行し、独立した Terraform プロジェクトを維持する方が良いかもしれません。

生成された出力を使用する

最初に言った。ここでも言い直す理由はそこです。最も重要な部分なので:生成設定は本番環境対応 Terraform ではありません。 jamformer は支援・加速ツールです。Jamf 環境をコードとして管理する過程での現実的な出発点を提供します。すぐにそこに到達するショートカットではなく。実際のインフラストラクチャ変更に何かが影響を与える前に、生成されたものを確認、改善、理解するのに実際の時間を計画してください。

出力は意図的にフラット。1 つのリソース ブロック/オブジェクト、タイプごと 1 つのファイル、モジュールなし。実際には、モジュール プロジェクト構造にリファクタリングし、for_eachlocals などの HCL 機能を利用して反復を減らし、インスタンス URL や一般的な設定などを環境向けの変数にし、チームにとって意味のある方法でリソースを整理したいと思うでしょう。jamformer はそれをするための生の素材を提供します。どのように形成するかはあなた次第です。その成形作業は、ほとんどの Terraform 学習が実際に行われるところです。これが jamformer が現在の形で存在する理由全体です。

これを念頭に置いて、推奨するワークフローはこち