Jamf Pro でのスクリプトの管理(コード管理)のお悩みツイートを目にしました。
自社ではまだ使い込めていないので想像ですが、運用規模が大きくなると以下が悩みどころになるのかなと思います。
- コード管理
- バージョン管理
- 変更管理
- 構成管理
- Jamf Pro 内でのスクリプトとポリシーの紐付け可視化
解決につながりそうな事例を探したところ、 JNUC 2018 の発表スライドで「 GitHub でコードを管理し Jenkins で自動デプロイ」する仕組みを見つけました。
Jenkins を構築するのは避けたかったので、 GitHub Actions を利用して GitHub → Jamf Pro へのスクリプトの自動デプロイ環境を作成・検証してみました。(今回は自動デプロイにスコープを絞って確認しています。 ポリシーとの紐付けの可視化は別途やってみたいと思います。)
なお、検証にあたって Sandbox を払い出していただけました。 Jamf Pro を契約している場合は Jamf の担当営業の方に相談すると対応していただけるようです。担当営業が誰だかわからない場合は @yoshifin さんにコンタクトをとると何とかしてくれます!感謝です!!
検証の概要
全体の構成です。
- GitHub Actions - https://github.co.jp/features/actions
- Jamf Pro API - https://www.jamf.com/developers/apis/jamf-pro/
GitHub 上での main ブランチへの push をトリガに GitHub Actions が自動実行され、 API 経由で Jamf Pro にスクリプトがデプロイされます。スクリプト本体だけでなく [ 情報 ] や [ 優先度 ] 等の各属性もあわせてデプロイできます。
その他に確認できたことです。
- スクリプトの更新前に(なんちゃって)バックアップを取得
- Jamf Pro API へのアクセスに利用するユーザの credential の隠蔽
- Jamf Pro API に対するアクセス権のコントロール(操作可能なリソースや権限を最小限に制限)
- Jamf Pro 上のスクリプトと GitHub リポジトリの紐付けを自動記録
設定作業
環境構築のための作業メモです。
Jamf Pro
Jamf Pro API へのアクセス(認証)に利用するユーザを作成します。
ユーザ script-manager を作成して、[ スクリプト ] の作成・読み取り・更新の権限セットを付与しました。対象のオブジェクトが細かく指定できるので、必要最小限の権限に留められて良いですね。
GitHub
GitHub でリポジトリを作成し、以下のディレクトリ構成でファイルを作成しました。
sample_deploy_jamf-pro_scripts/ ├.github/ │ └ workflows/ │ └ deploy.yml # GitHub Actions の設定ファイル ├ deploy.sh # デプロイ用スクリプト └ script/ ├echo-hello.sh # デプロイ対象のスクリプト └config.json # デプロイモードの制御情報(新規作成・更新)、各種属性情報
.github/workflow/deploy.yml
YAML ファイルで GitHub Actions の設定を定義します。
name: Deploy script to Jamf pro on: push: branches: - main jobs: deploy: runs-on: macos-10.15 steps: - uses: actions/checkout@v2 - name: install GNU sed run: brew install gnu-sed - name: Grant execute permission run: chmod +x deploy.sh - name: Deploy env: JAMF_URL: "https://xxxxxxsandbox.jamfcloud.com" JAMF_USER: "script-manager" JAMF_PASSWORD: ${{ secrets.JAMF_PASSWORD }} run: sh deploy.sh
on:
で GitHub Actions の実行トリガを main ブランチへの push に限定しています。他のブランチへの push では実行されません。
runs-on:
で CI/CD の実行環境(コンテナ)を指定します。手元の環境にあわせて macOS にしてみました。
デプロイ用スクリプト( deploy.sh )のなかで GNU sed を使いたかったので、実行環境にインストールしています( brew install gnu-sed
)。
Jamf Pro の URL やユーザの credential は環境変数として扱っています( env:
)。 パスワードに関しては GitHub の Secrets で管理しています。
script/config.json
スクリプトの属性情報や、 デプロイの動作(新規作成・更新)を定義しています。デプロイ用スクリプト( deploy.sh )に読み込ませます。
{ "mode": "NEW", <- "NEW" or "UPDATE" "id": "", "name": "< Generate dynamically >", "info": "say Hello.", "notes": "< Generate dynamically >", "priority": "AFTER", "categoryId": "0", "categoryName": "", "parameter4": "arg-1", "parameter5": "arg-2", "parameter6": "arg-3", "parameter7": "arg-4", "parameter8": "arg-5", "parameter9": "arg-6", "parameter10": "arg-7", "parameter11": "arg-8", "osRequirements": "10.10.x", "scriptContents": "< Generate dynamically >" }
mode
の値によってデプロイの挙動を切り替えています。
スクリプトの ID は 新規作成時の HTTP Response を確認するか、設定画面の URL で確認できます
その他の key とダッシュボード上での表記との紐付けは以下の通りです
name
... [ 一般 ] - [ 表示名 ]info
... [ 一般 ] - [ 情報 ]notes
... [ 一般 ] - [ 注記 ]priority
... [ オプション ] - [ 優先度 ]categoryId
... 表示なし ※[ カテゴリ] の番号と対応categoryName
... [ 一般 ] - [ カテゴリ ]parameter4~11
... [ オプション ] - [ Parameter Labels ] - [ パラメータ 4 ~ 11 ]osRequirements
... [ 制限 ] - [ オペレーティングシステム要件 ]scriptContents
... [ スクリプト ]
今回の検証では name
, scriptContens
, notes
の値が以下となるようデプロイ時に自動生成しています。
name
: script/ に配置されているシェルスクリプトのファイル名scriptContens
: name に該当するファイル内に記述したコードnotes
: GitHub のリポジトリやコミットのハッシュ値に対応する URL, GitHub Actions の Run Number
script/echo-hello.sh
デプロイするスクリプトです。
#!/bin/bash echo "Hello, Jamf!! from GitHub."
deploy.sh
デプロイ処理のためのスクリプトです。 GitHub Actions の CI/CD 環境( macOS コンテナ)で実行されます。
script-manager の credential (ユーザ名、パスワード)を Base64 形式でエンコードした文字列を使って API へのアクセスに必要なトークンを取得し( /uapi/auth/tokens
)、 config.json, echo-hello.sh からデータを読み込みこんで API に送信しています( /uapi/v1/scripts/
, /uapi/v1/scripts/{id}
)。
冗長な部分はいつか直します...
#!/bin/bash # server connection information URL=$JAMF_URL username=$JAMF_USER password=$JAMF_PASSWORD # created base64-encoded credentials encodedCredentials=$( printf "${username}:${password}" | /usr/bin/iconv -t ISO-8859-1 | /usr/bin/base64 -i - ) # generate an auth token authToken=$( /usr/bin/curl "$URL/uapi/auth/tokens" \ --silent \ --request POST \ --header "Authorization: Basic $encodedCredentials" ) # parse authToken for token, omit expiration token=$( awk -F \" '{ print $4 }' <<< "$authToken" | xargs ) # set config FILENAME=$(basename ./script/*.sh) CONTENTS=$(cat ./script/$FILENAME | sed -e 's/"/\\"/g' | gsed -z 's/\n/\\n/g' ) conf_info=$(cat ./script/config.json | jq .info | sed -e 's/^"//' -e 's/"$//' ) #conf_notes=$(cat ./script/config.json | jq .notes | sed -e 's/^"//' -e 's/"$//' ) conf_notes="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA} \n- GitHub Actions Run Number: #${GITHUB_RUN_NUMBER}" conf_priority=$(cat ./script/config.json | jq .priority | sed -e 's/^"//' -e 's/"$//' ) conf_categoryId=$(cat ./script/config.json | jq .categoryId | sed -e 's/^"//' -e 's/"$//' ) conf_categoryName=$(cat ./script/config.json | jq .categoryName | sed -e 's/^"//' -e 's/"$//' ) conf_param4=$(cat ./script/config.json | jq .parameter4 | sed -e 's/^"//' -e 's/"$//' ) conf_param5=$(cat ./script/config.json | jq .parameter5 | sed -e 's/^"//' -e 's/"$//' ) conf_param6=$(cat ./script/config.json | jq .parameter6 | sed -e 's/^"//' -e 's/"$//' ) conf_param7=$(cat ./script/config.json | jq .parameter7 | sed -e 's/^"//' -e 's/"$//' ) conf_param8=$(cat ./script/config.json | jq .parameter8 | sed -e 's/^"//' -e 's/"$//' ) conf_param9=$(cat ./script/config.json | jq .parameter9 | sed -e 's/^"//' -e 's/"$//' ) conf_param10=$(cat ./script/config.json | jq .parameter10 | sed -e 's/^"//' -e 's/"$//' ) conf_param11=$(cat ./script/config.json | jq .parameter11 | sed -e 's/^"//' -e 's/"$//' ) conf_osRequirements=$(cat ./script/config.json | jq .osRequirements | sed -e 's/^"//' -e 's/"$//' ) # create json data jsonData=$(cat <<EOS { "name": "${FILENAME}", "info": "${conf_info}", "notes": "${conf_notes}", "priority": "${conf_priority}", "categoryId": "${conf_categoryId}", "categoryName": "${conf_categoryName}", "parameter4": "${conf_param4}", "parameter5": "${conf_param5}", "parameter6": "${conf_param6}", "parameter7": "${conf_param7}", "parameter8": "${conf_param8}", "parameter9": "${conf_param9}", "parameter10": "${conf_param10}", "parameter11": "${conf_param11}", "osRequirements": "${conf_osRequirements}", "scriptContents": "${CONTENTS}" } EOS ) echo "=== jsonData ===" echo $jsonData # deploy MODE=$( cat ./script/config.json | jq .mode | sed -e 's/^"//' -e 's/"$//' ) case "$MODE" in "NEW") httpResponse=$( curl "$URL/uapi/v1/scripts" \ --silent \ --request POST \ --header "Authorization: Bearer $token" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ --data "$jsonData" ) echo "=== POST /uapi/v1/scripts ===" echo $httpResponse ;; "UPDATE") scriptId=$( cat ./script/config.json | jq .id | sed -e 's/^"//' -e 's/"$//' ) # back up httpResponse=$( curl "$URL/uapi/v1/scripts/$scriptId" \ --silent \ --request GET \ --header "Authorization: Bearer $token" ) echo "=== GET /uapi/v1/scripts/$scriptId ===" echo $httpResponse httpResponse=$( curl "$URL/uapi/v1/scripts/$scriptId" \ --silent \ --request PUT \ --header "Authorization: Bearer $token" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ --data "$jsonData" ) echo "=== PUT /uapi/v1/scripts/$scriptId ===" echo $httpResponse ;; *) echo "\$MODE is invalid value." exit 1 ;; esac
動作確認
上記ファイルを main ブランチに push すると GitHub Actions が自動実行されました。 Jamf Pro にデプロイされたスクリプトの ID は 18 のようです。
ダッシュボードを確認すると、 echo-hello.sh が登録されていました。
[ 注記 ]( note )には、GitHub のリポジトリやコミットのハッシュ値に対応した URL と、デプロイした GitHub Actions の Run Number が記録されています。
その他項目の反映も問題ないようです。
次はスクリプトを更新してみます。
dev ブランチを作成してファイルを更新し、 push します。
% git checkout -b dev Switched to a new branch 'dev' xxxxx@xxxxx sample_deploy_japf-pro_scripts % git branch * dev main % git diff diff --git a/script/config.json b/script/config.json ~ - "mode": "NEW", - "id": "", + "mode": "UPDATE", <- mode を "UPDATE" に変更 + "id": "18", <- 更新対象のスクリプトの ID を設定 ~ - "osRequirements": "10.10.x", + "osRequirements": "10.15.x", <- 実行制限の OS バージョンを変更 ~ diff --git a/script/echo-hello.sh b/script/echo-hello.sh ~ -echo "Hello, Jamf!! from GitHub." \ No newline at end of file +echo "Hello, Jamf!! from GitHub. (update)" <- 末尾に "(update)" を追記 \ No newline at end of file % git add script/* % git commit -m "[update] update script." [dev a679517] [update] update script. 2 files changed, 4 insertions(+), 4 deletions(-) % git push --set-upstream origin dev
main ブランチ以外への push では GitHub Actions は実行されないため、 main ブランチへ Pull Request -> merge して発火させます。(実運用ではここでコードレビューが入る形になると思います。)
更新前の状態と更新後の設定を出力しています。なんちゃってバックアップです。
ダッシュボード上でも実際に値が更新されていることを確認できました。
ポリシーに紐付けて Mac に配信したログです。ちゃんと動いたようです。
まとめ
GitHub から Jamf Pro へスクリプトの自動デプロイができました。これでコード管理を GitHub に集約することができそうです。 GitHub Actions を採用することで構成コンポーネントを少なくできるのもメリットだと感じます。
Clasic API ではポリシーの操作が可能なようなので、スクリプトの配信対象も含めてコードで構成管理ができるかもしれません。
今回は勉強がてらシェルスクリプトでデプロイしてみましたが、エラー処理等も考えると別の言語で書くほうが楽だと思いました。。
参考情報
Jamf Nation - Creating an Authorization Token With JAMF Pro API - Help would be greatly appreciated