Open9

Apple 宣言型デバイス管理 めも

Yusuke IwakiYusuke Iwaki

https://developer.apple.com/documentation/devicemanagement/enable_declarative_managment

DeclarativeManagementというMDMコマンドを打つところから始まるらしい。

Dataってなんだ?

https://developer.apple.com/documentation/devicemanagement/declarativemanagementcommand/command

The base64-encoded Declarative Management JSON request using a TokensResponse.

https://developer.apple.com/documentation/devicemanagement/tokensresponse

DeclarationsTokenというのは、iOS側のクライアントが、宣言に変更があるかどうかを識別する用のランダム文字列らしい。

https://developer.apple.com/documentation/devicemanagement/integrating_declarative_management#4084043

The server maintains a token that represents the current state of the declarations to synchronize with a particular device. The server must persist this token on a per-device and per-user basis because declarations may be different for some devices and some users. Whenever the set of declarations changes (new ones added or existing ones changed or removed), the server must change the value of the token. The token value is an opaque string that the client uses for comparisons, so the server is free to use whatever format it prefers, but needs to limit the size to no more that 256 octets.

$ echo 'eyJTeW5jVG9rZW5zIjogeyJUaW1lc3RhbXAiOiAiMjAyMS0wNi0wMlQwMToy
            ODowMFoiLCAiRGVjbGFyYXRpb25zVG9rZW4iOiAiYjY1NDQwMjdhMzE1Y2Qw
            MDg1ZDRjZjA4MTc0NjI0YzJkMTQyNDQ0ODA0MzBhODdiMTc2YTI3MjdlNzM2
            NjEzOCJ9fQ==' | base64 -d
{"SyncTokens": {"Timestamp": "2021-06-02T01:28:00Z", "DeclarationsToken": "b6544027a315cd0085d4cf08174624c2d14244480430a87b176a2727e7366138"}}
Yusuke IwakiYusuke Iwaki

実際に適当にDeclarativeManagementコマンドを投げてみると、2発 /checkinエンドポイントに叩きにやってきた。

1発目: endpoint=status

dataに以下のようなデータ(文字列)が詰まっている。

{
  "StatusItems" : {
    "management" : {
      "declarations" : {
        "activations" : [

        ],
        "configurations" : [

        ],
        "assets" : [

        ],
        "management" : [

        ]
      },
      "client-capabilities" : {
        "supported-versions" : [
          "1.0.0"
        ],
        "supported-payloads" : {
          "declarations" : {
            "activations" : [
              "com.apple.activation.simple"
            ],
            "assets" : [
              "com.apple.asset.credential.userpassword",
              "com.apple.asset.useridentity"
            ],
            "configurations" : [
              "com.apple.configuration.account.caldav",
              "com.apple.configuration.account.carddav",
              "com.apple.configuration.account.exchange",
              "com.apple.configuration.account.google",
              "com.apple.configuration.account.ldap",
              "com.apple.configuration.account.mail",
              "com.apple.configuration.account.subscribed-calendar",
              "com.apple.configuration.legacy",
              "com.apple.configuration.legacy.interactive",
              "com.apple.configuration.management.status-subscriptions",
              "com.apple.configuration.management.test",
              "com.apple.configuration.passcode.settings"
            ],
            "management" : [
              "com.apple.management.organization-info",
              "com.apple.management.properties",
              "com.apple.management.server-capabilities"
            ]
          },
          "status-items" : [
            "account.list.caldav",
            "account.list.carddav",
            "account.list.exchange",
            "account.list.google",
            "account.list.ldap",
            "account.list.mail.incoming",
            "account.list.mail.outgoing",
            "account.list.subscribed-calendar",
            "device.identifier.serial-number",
            "device.identifier.udid",
            "device.model.family",
            "device.model.identifier",
            "device.model.marketing-name",
            "device.operating-system.build-version",
            "device.operating-system.family",
            "device.operating-system.marketing-name",
            "device.operating-system.supplemental.build-version",
            "device.operating-system.supplemental.extra-version",
            "device.operating-system.version",
            "management.client-capabilities",
            "management.declarations",
            "mdm.app",
            "passcode.is-compliant",
            "passcode.is-present",
            "test.array-value",
            "test.boolean-value",
            "test.dictionary-value",
            "test.error-value",
            "test.integer-value",
            "test.real-value",
            "test.string-value"
          ]
        },
        "supported-features" : {

        }
      }
    },
    "device" : {
      "identifier" : {
        "serial-number" : "ひみつ",
        "udid" : "00008030-ひみつ"
      },
      "model" : {
        "family" : "iPhone"
      },
      "operating-system" : {
        "family" : "iOS",
        "build-version" : "20E252",
        "supplemental" : {
          "extra-version" : ""
        },
        "version" : "16.4.1"
      }
    }
  },
  "Errors" : [

  ]
}

iOS 17 betaだとちょっとケイパビリティが増えてるっぽい

      "client-capabilities": {
        "supported-versions": [
          "1.0.0"
        ],
        "supported-payloads": {
          "declarations": {
            "activations": [
              "com.apple.activation.simple"
            ],
            "assets": [
              "com.apple.asset.credential.acme",
              "com.apple.asset.credential.certificate",
              "com.apple.asset.credential.identity",
              "com.apple.asset.credential.scep",
              "com.apple.asset.credential.userpassword",
              "com.apple.asset.data",
              "com.apple.asset.useridentity"
            ],
            "configurations": [
              "com.apple.configuration.account.caldav",
              "com.apple.configuration.account.carddav",
              "com.apple.configuration.account.exchange",
              "com.apple.configuration.account.google",
              "com.apple.configuration.account.ldap",
              "com.apple.configuration.account.mail",
              "com.apple.configuration.account.subscribed-calendar",
              "com.apple.configuration.legacy",
              "com.apple.configuration.legacy.interactive",
              "com.apple.configuration.management.status-subscriptions",
              "com.apple.configuration.management.test",
              "com.apple.configuration.passcode.settings",
              "com.apple.configuration.security.certificate",
              "com.apple.configuration.security.identity",
              "com.apple.configuration.security.passkey.attestation",
              "com.apple.configuration.softwareupdate.enforcement.specific",
              "com.apple.configuration.watch.enrollment"
            ],
            "management": [
              "com.apple.management.organization-info",
              "com.apple.management.properties",
              "com.apple.management.server-capabilities"
            ]
          },
          "status-items": [
            "account.list.caldav",
            "account.list.carddav",
            "account.list.exchange",
            "account.list.google",
            "account.list.ldap",
            "account.list.mail.incoming",
            "account.list.mail.outgoing",
            "account.list.subscribed-calendar",
            "device.identifier.serial-number",
            "device.identifier.udid",
            "device.model.family",
            "device.model.identifier",
            "device.model.marketing-name",
            "device.model.number",
            "device.operating-system.build-version",
            "device.operating-system.family",
            "device.operating-system.marketing-name",
            "device.operating-system.supplemental.build-version",
            "device.operating-system.supplemental.extra-version",
            "device.operating-system.version",
            "device.power.battery-health",
            "management.client-capabilities",
            "management.declarations",
            "mdm.app",
            "passcode.is-compliant",
            "passcode.is-present",
            "security.certificate.list",
            "softwareupdate.failure-reason",
            "softwareupdate.install-reason",
            "softwareupdate.install-state",
            "softwareupdate.pending-version",
            "test.array-value",
            "test.boolean-value",
            "test.dictionary-value",
            "test.error-value",
            "test.integer-value",
            "test.real-value",
            "test.string-value"
          ]
        },
        "supported-features": {
        }
      }

iOS 17.1 betaだとこんなかんじ。

      "client-capabilities": {
        "supported-versions": [
          "1.0.0"
        ],
        "supported-payloads": {
          "declarations": {
            "activations": [
              "com.apple.activation.simple"
            ],
            "assets": [
              "com.apple.asset.credential.acme",
              "com.apple.asset.credential.certificate",
              "com.apple.asset.credential.identity",
              "com.apple.asset.credential.scep",
              "com.apple.asset.credential.userpassword",
              "com.apple.asset.data",
              "com.apple.asset.useridentity"
            ],
            "configurations": [
              "com.apple.configuration.account.caldav",
              "com.apple.configuration.account.carddav",
              "com.apple.configuration.account.exchange",
              "com.apple.configuration.account.google",
              "com.apple.configuration.account.ldap",
              "com.apple.configuration.account.mail",
              "com.apple.configuration.account.subscribed-calendar",
              "com.apple.configuration.legacy",
              "com.apple.configuration.legacy.interactive",
              "com.apple.configuration.management.status-subscriptions",
              "com.apple.configuration.management.test",
              "com.apple.configuration.passcode.settings",
              "com.apple.configuration.security.certificate",
              "com.apple.configuration.security.identity",
              "com.apple.configuration.security.passkey.attestation",
              "com.apple.configuration.softwareupdate.enforcement.specific",
              "com.apple.configuration.watch.enrollment"
            ],
            "management": [
              "com.apple.management.organization-info",
              "com.apple.management.properties",
              "com.apple.management.server-capabilities"
            ]
          },
          "status-items": [
            "account.list.caldav",
            "account.list.carddav",
            "account.list.exchange",
            "account.list.google",
            "account.list.ldap",
            "account.list.mail.incoming",
            "account.list.mail.outgoing",
            "account.list.subscribed-calendar",
            "device.identifier.serial-number",
            "device.identifier.udid",
            "device.model.family",
            "device.model.identifier",
            "device.model.marketing-name",
            "device.model.number",
            "device.operating-system.build-version",
            "device.operating-system.family",
            "device.operating-system.marketing-name",
            "device.operating-system.supplemental.build-version",
            "device.operating-system.supplemental.extra-version",
            "device.operating-system.version",
            "device.power.battery-health",
            "management.client-capabilities",
            "management.declarations",
            "mdm.app",
            "passcode.is-compliant",
            "passcode.is-present",
            "security.certificate.list",
            "softwareupdate.failure-reason",
            "softwareupdate.install-reason",
            "softwareupdate.install-state",
            "softwareupdate.pending-version",
            "test.array-value",
            "test.boolean-value",
            "test.dictionary-value",
            "test.error-value",
            "test.integer-value",
            "test.real-value",
            "test.string-value"
          ]
        },
        "supported-features": {
        }
      }

iOS 17.2 betaだとさらに少し増えたようだ

{
  "StatusItems": {
    "management": {
      "declarations": {
        "activations": [

        ],
        "configurations": [

        ],
        "assets": [

        ],
        "management": [

        ]
      },
      "client-capabilities": {
        "supported-versions": [
          "1.0.0"
        ],
        "supported-payloads": {
          "declarations": {
            "activations": [
              "com.apple.activation.simple"
            ],
            "assets": [
              "com.apple.asset.credential.acme",
              "com.apple.asset.credential.certificate",
              "com.apple.asset.credential.identity",
              "com.apple.asset.credential.scep",
              "com.apple.asset.credential.userpassword",
              "com.apple.asset.data",
              "com.apple.asset.useridentity"
            ],
            "configurations": [
              "com.apple.configuration.account.caldav",
              "com.apple.configuration.account.carddav",
              "com.apple.configuration.account.exchange",
              "com.apple.configuration.account.google",
              "com.apple.configuration.account.ldap",
              "com.apple.configuration.account.mail",
              "com.apple.configuration.account.subscribed-calendar",
              "com.apple.configuration.app.managed",
              "com.apple.configuration.legacy",
              "com.apple.configuration.legacy.interactive",
              "com.apple.configuration.management.status-subscriptions",
              "com.apple.configuration.management.test",
              "com.apple.configuration.passcode.settings",
              "com.apple.configuration.security.certificate",
              "com.apple.configuration.security.identity",
              "com.apple.configuration.security.passkey.attestation",
              "com.apple.configuration.softwareupdate.enforcement.specific",
              "com.apple.configuration.watch.enrollment"
            ],
            "management": [
              "com.apple.management.organization-info",
              "com.apple.management.properties",
              "com.apple.management.server-capabilities"
            ]
          },
          "status-items": [
            "account.list.caldav",
            "account.list.carddav",
            "account.list.exchange",
            "account.list.google",
            "account.list.ldap",
            "account.list.mail.incoming",
            "account.list.mail.outgoing",
            "account.list.subscribed-calendar",
            "app.managed.list",
            "device.identifier.serial-number",
            "device.identifier.udid",
            "device.model.family",
            "device.model.identifier",
            "device.model.marketing-name",
            "device.model.number",
            "device.operating-system.build-version",
            "device.operating-system.family",
            "device.operating-system.marketing-name",
            "device.operating-system.supplemental.build-version",
            "device.operating-system.supplemental.extra-version",
            "device.operating-system.version",
            "device.power.battery-health",
            "management.client-capabilities",
            "management.declarations",
            "mdm.app",
            "passcode.is-compliant",
            "passcode.is-present",
            "security.certificate.list",
            "softwareupdate.failure-reason",
            "softwareupdate.install-reason",
            "softwareupdate.install-state",
            "softwareupdate.pending-version",
            "test.array-value",
            "test.boolean-value",
            "test.dictionary-value",
            "test.error-value",
            "test.integer-value",
            "test.real-value",
            "test.string-value"
          ]
        },
        "supported-features": {
        }
      }
    },
    "device": {
      "identifier": {
        "serial-number": "ひみつ",
        "udid": "00008030-ひみつ"
      },
      "model": {
        "family": "iPhone"
      },
      "operating-system": {
        "family": "iOS",
        "build-version": "21C5054b",
        "supplemental": {
          "extra-version": ""
        },
        "version": "17.2"
      }
    }
  },
  "Errors": [

  ]
}

2発目: endpoint=declaration-items

dataは空。

Yusuke IwakiYusuke Iwaki

「宣言」をどういうデータモデルにすべきか

従来の構成プロファイルに近い位置づけになりそう。こんな例もある。

https://github.com/jessepeterson/kmfddm#compositing

WWDC2021, 2022の動画である程度のヒントがある。

基本的なデータモデルはこのあたりにまとまってるのが読みやすい。

https://mobile-jon.com/2021/06/08/meet-my-new-friend-declarative-device-management/

ざっくりかくとこんなかんじか。

Declaration
  |-[N:N] Activation

Activation
  |-[1:1] Predicate (NSPredicate)
  |-[N:N] Configuration

Configuration
  |-[N:0-1] Asset

Asset

Management Property (好きなJSON。Predicateで @propertyで参照できるもの)
Yusuke IwakiYusuke Iwaki

宣言がデバイスに適用されると、ステータス通知がポンポン上がってくる

Yusuke IwakiYusuke Iwaki

iOSのバージョンが上がって、ケイパビリティが増えた場合にどういうステータス通知がくるか観測してみる。

iOS 17.2 beta2 -> iOS 17.2 正式版。

このバージョンでは、アプリの宣言的配布(configuration type= com.apple.configuration.app.managed と、status channe type= app.managed.list ) に対応している。

まず、iOS 17.2 beta2時点で受けていたステータス通知はこんなの↓

{
  "StatusItems": {
    "management": {
      "declarations": {
        "activations": [

        ],
        "configurations": [

        ],
        "assets": [

        ],
        "management": [

        ]
      },
      "client-capabilities": {
        "supported-versions": [
          "1.0.0"
        ],
        "supported-payloads": {
          "declarations": {
            "activations": [
              "com.apple.activation.simple"
            ],
            "assets": [
              "com.apple.asset.credential.acme",
              "com.apple.asset.credential.certificate",
              "com.apple.asset.credential.identity",
              "com.apple.asset.credential.scep",
              "com.apple.asset.credential.userpassword",
              "com.apple.asset.data",
              "com.apple.asset.useridentity"
            ],
            "configurations": [
              "com.apple.configuration.account.caldav",
              "com.apple.configuration.account.carddav",
              "com.apple.configuration.account.exchange",
              "com.apple.configuration.account.google",
              "com.apple.configuration.account.ldap",
              "com.apple.configuration.account.mail",
              "com.apple.configuration.account.subscribed-calendar",
              "com.apple.configuration.legacy",
              "com.apple.configuration.legacy.interactive",
              "com.apple.configuration.management.status-subscriptions",
              "com.apple.configuration.management.test",
              "com.apple.configuration.passcode.settings",
              "com.apple.configuration.security.certificate",
              "com.apple.configuration.security.identity",
              "com.apple.configuration.security.passkey.attestation",
              "com.apple.configuration.softwareupdate.enforcement.specific",
              "com.apple.configuration.watch.enrollment"
            ],
            "management": [
              "com.apple.management.organization-info",
              "com.apple.management.properties",
              "com.apple.management.server-capabilities"
            ]
          },
          "status-items": [
            "account.list.caldav",
            "account.list.carddav",
            "account.list.exchange",
            "account.list.google",
            "account.list.ldap",
            "account.list.mail.incoming",
            "account.list.mail.outgoing",
            "account.list.subscribed-calendar",
            "device.identifier.serial-number",
            "device.identifier.udid",
            "device.model.family",
            "device.model.identifier",
            "device.model.marketing-name",
            "device.model.number",
            "device.operating-system.build-version",
            "device.operating-system.family",
            "device.operating-system.marketing-name",
            "device.operating-system.supplemental.build-version",
            "device.operating-system.supplemental.extra-version",
            "device.operating-system.version",
            "device.power.battery-health",
            "management.client-capabilities",
            "management.declarations",
            "mdm.app",
            "passcode.is-compliant",
            "passcode.is-present",
            "security.certificate.list",
            "softwareupdate.failure-reason",
            "softwareupdate.install-reason",
            "softwareupdate.install-state",
            "softwareupdate.pending-version",
            "test.array-value",
            "test.boolean-value",
            "test.dictionary-value",
            "test.error-value",
            "test.integer-value",
            "test.real-value",
            "test.string-value"
          ]
        },
        "supported-features": {
        }
      }
    },
    "device": {
      "identifier": {
        "udid": "00008027-ひみつ",
        "serial-number": "ひみつ"
      },
      "model": {
        "family": "iPad"
      },
      "operating-system": {
        "family": "iPadOS",
        "build-version": "21C5046c",
        "supplemental": {
          "extra-version": ""
        },
        "version": "17.2"
      }
    }
  },
  "Errors": [

  ]
}

iOS 17.2にアップデート後、ロックを解除してWiFiに接続されてしばらくすると、以下のようなステータス通知がやってきた。
シリアルナンバーなどすでに送っている情報は載っていないが、ケイパビリティに関する情報はフルフルで乗っているようにみえる。

上の方のスクラップで書いた「iOS 17.2 betaだとさらに少し増えたようだ」のところの情報が、最初からiOS 17.2 beta4で試したときのステータスチャネルなので、まっさらなときとアップデートのときで少し情報に差がある(デバイス情報などすでに送ったものは送らない)ことは分かる。

{
  "StatusItems": {
    "management": {
      "client-capabilities": {
        "supported-versions": [
          "1.0.0"
        ],
        "supported-payloads": {
          "declarations": {
            "activations": [
              "com.apple.activation.simple"
            ],
            "assets": [
              "com.apple.asset.credential.acme",
              "com.apple.asset.credential.certificate",
              "com.apple.asset.credential.identity",
              "com.apple.asset.credential.scep",
              "com.apple.asset.credential.userpassword",
              "com.apple.asset.data",
              "com.apple.asset.useridentity"
            ],
            "configurations": [
              "com.apple.configuration.account.caldav",
              "com.apple.configuration.account.carddav",
              "com.apple.configuration.account.exchange",
              "com.apple.configuration.account.google",
              "com.apple.configuration.account.ldap",
              "com.apple.configuration.account.mail",
              "com.apple.configuration.account.subscribed-calendar",
              "com.apple.configuration.app.managed",
              "com.apple.configuration.legacy",
              "com.apple.configuration.legacy.interactive",
              "com.apple.configuration.management.status-subscriptions",
              "com.apple.configuration.management.test",
              "com.apple.configuration.passcode.settings",
              "com.apple.configuration.security.certificate",
              "com.apple.configuration.security.identity",
              "com.apple.configuration.security.passkey.attestation",
              "com.apple.configuration.softwareupdate.enforcement.specific",
              "com.apple.configuration.watch.enrollment"
            ],
            "management": [
              "com.apple.management.organization-info",
              "com.apple.management.properties",
              "com.apple.management.server-capabilities"
            ]
          },
          "status-items": [
            "account.list.caldav",
            "account.list.carddav",
            "account.list.exchange",
            "account.list.google",
            "account.list.ldap",
            "account.list.mail.incoming",
            "account.list.mail.outgoing",
            "account.list.subscribed-calendar",
            "app.managed.list",
            "device.identifier.serial-number",
            "device.identifier.udid",
            "device.model.family",
            "device.model.identifier",
            "device.model.marketing-name",
            "device.model.number",
            "device.operating-system.build-version",
            "device.operating-system.family",
            "device.operating-system.marketing-name",
            "device.operating-system.supplemental.build-version",
            "device.operating-system.supplemental.extra-version",
            "device.operating-system.version",
            "device.power.battery-health",
            "management.client-capabilities",
            "management.declarations",
            "mdm.app",
            "passcode.is-compliant",
            "passcode.is-present",
            "security.certificate.list",
            "softwareupdate.failure-reason",
            "softwareupdate.install-reason",
            "softwareupdate.install-state",
            "softwareupdate.pending-version",
            "test.array-value",
            "test.boolean-value",
            "test.dictionary-value",
            "test.error-value",
            "test.integer-value",
            "test.real-value",
            "test.string-value"
          ]
        },
        "supported-features": {
        }
      }
    },
    "device": {
      "operating-system": {
        "build-version": "21C62",
        "supplemental": {
          "build-version": "21C62"
        }
      }
    }
  },
  "Errors": [

  ]
}

ちなみに、iOS 17.2 beta4 -> iOS 17.2 正式版のようにケイパビリティに変更がない場合には、OSアップデート後のステータス通知はこんなかんじ。

{
  "StatusItems": {
    "device": {
      "operating-system": {
        "supplemental": {
          "build-version": "21C62"
        },
        "build-version": "21C62"
      }
    }
  },
  "Errors": [

  ]
}
Yusuke IwakiYusuke Iwaki

ProfileURLなどは、どういうリクエストが来るのか観察してみる。

Configurationなどでプロファイルはここだよ!ってダウンロード先を指定するものがいくつかある。

従来のMDMであれば、TLSクライアント認証に加えて、コマンドエンドポイントのペイロードにUDIDがあったので、誰からのリクエストなのかを簡単に判別することができた。

一方でProfileURLだと、取りに来た人が誰かによって振り分けるような処理はできるのだろうか?部外者のアクセスを遮断できるのだろうか?

The com.apple.configuration.legacy and com.apple.configuration.legacy.interactive configurations both contain a ProfileURL key that specifies the URL where the corresponding profile data resides. The device considers this URL hosted by the MDM server and applies normal MDM protocol rules to the request. In particular:

The device uses a TLS connection with a client certificate set to the MDM device identity certificate.

The device verifies the TLS connection server cert by evaluating trust with any CheckInURLPinningCertificateUUIDs specified in the MDM enrollment profile payload. In this case, the device honors the PinningRevocationCheckRequired specified in the MDM enrollment profile payload.

いちおうリファレンスによると証明書を使ったTLSクライアント認証はしてるっぽいけど、このあたりはアプリケーション層で実装されることは少ないので、

Host: iwaki-mdm-dev.ngrok.dev
User-Agent: MDM/1.0
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: ja
Cache-Control: no-cache
X-Forwarded-For: 133.218.32.140
X-Forwarded-Host: iwaki-mdm-dev.ngrok.dev
X-Forwarded-Proto: https

アプリケーション層で見える情報としては↑のようなリクエストヘッダーだけで、まぁ誰からのアクセスかを見分けることは不可能だ。