整合性検証の失敗に対するレスポンスを自動化する

は、

Cloud Functions のトリガーを使用して Shielded VM 整合性モニタリング イベントに自動的に対処する方法について説明します。

概要

整合性モニタリングは Shielded VM インスタンスから測定値を収集し、それらを Stackdriver Logging で明らかにします。整合性測定値が Shielded VM インスタンスのブート内で変わると、整合性検証は失敗します。この失敗は記録されたイベントとして取り込まれます。これは Stackdriver Monitoring でも発生します。

正当な理由で Shielded VM の整合性の測定値が変わることがあります。たとえば、システムの更新によって、オペレーティング システムのカーネルに予想される変化が生じた場合などです。この変化のため、整合性検証が予想どおりに失敗した場合、整合性モニタリングによって Shielded VM インスタンスが新しい整合性ポリシーのベースラインを習得する必要があります。

このチュートリアルでは、まず、次の手順で、整合性検証に失敗した Shielded VM インスタンスをシャットダウンする単純な自動システムを作成します。

  1. すべての整合性モニタリング イベントを Cloud Pub/Sub トピックにエクスポートします。
  2. 整合性検証に失敗した Shielded VM インスタンスを識別してシャットダウンするため、そのトピックのイベントを使用する Cloud Functions のトリガーを作成します。

次に、必要に応じてシステムを拡張し、整合性検証に失敗した Shielded VM インスタンスが既知の正常な測定値と一致する場合は新しいベースラインを習得するか、そうでない場合はシャットダウンするように指示します。

  1. Cloud Firestore データベースを作成して、既存の正常な整合性ベースライン測定値を維持します。
  2. Cloud Functions のトリガーを更新して、整合性検証に失敗した Shielded VM インスタンスがデータベース内にある場合は、インスタンスに新しいベースラインを習得するように指示し、それ以外の場合はシャットダウンするように指示します。

拡張されたソリューションを実装する場合は、次の方法で行ってください。

  1. 正当な理由で検証が失敗すると予想される更新があるたびに、インスタンス グループ内の単一の Shielded VM インスタンスでその更新を実行します。
  2. 更新された VM インスタンスからの後期ブートイベントをソースとして使用し、known_good_measurements コレクションに新しいドキュメントを作成して、新しいポリシー ベースライン測定値をデータベースに追加します。詳しくは、既存の正常なベースライン測定値のデータベースの作成をご覧ください。
  3. 残りの Shielded VM インスタンスを更新します。トリガーによって、残りのインスタンスに新しいベースラインを習得することを求められます。既存の正常なベースラインとして検証される可能性があるためです。

事前準備

  • ネイティブ モードの Cloud Firestore をデータベース サービスとして選択しているプロジェクトを使用します。選択はプロジェクトを作成するときに実施します。これは変更できません。プロジェクトがネイティブ モードの Cloud Firestore を使用していない場合は、Cloud Firestore コンソールを開いたときに「このプロジェクトでは、別のデータベース サービスを使用しています」というメッセージが表示されます。
  • そのプロジェクトに Compute Engine Shielded VM インスタンスを配置して、整合性ベースライン測定値のソースとして機能させます。Shielded VM インスタンスは少なくとも 1 回は再起動される必要があります。
  • gcloud コマンドライン ツールをインストールします。
  • 次の手順に従って、Stackdriver Logging API と Cloud Functions API を有効にします。

    1. [API とサービス] に移動します。
    2. Cloud Functions APIStackdriver Logging API が [有効化された API とサービス] リストに表示されているか確認します。
    3. どちらの API も表示されていない場合は、[API とサービスを追加] をクリックします。
    4. 必要に応じて API を検索して有効にします。

Cloud Pub/Sub トピックに整合性モニタリング エントリをエクスポートする

ログを使用して、Shielded VM インスタンスによって生成されたすべての整合性モニタリング ログエントリを Cloud Pub/Sub トピックにエクスポートします。このトピックを Cloud Functions のトリガーのデータソースとして使用して、整合性モニタリング イベントへのレスポンスを自動化します。

  1. Stackdriver Logging に移動します。
  2. [ラベルまたはテキスト検索でフィルタ] の右側にあるプルダウン矢印をクリックし、続いて [高度なフィルタに変換] をクリックします。
  3. 次の高度なフィルタを入力します。

    resource.type="gce_instance" AND logName:"projects/YOUR_PROJECT_NAME/logs/compute.googleapis.com%2Fshielded_vm_integrity"
    

    YOUR_PROJECT_NAME を自分のプロジェクトの名前で置き換えます。

  4. [フィルタを送信] をクリックします。

  5. [エクスポートを作成] をクリックします。

  6. [シンク名] に「integrity-monitoring」と入力します。

  7. [シンクサービス] で [Cloud Pub/Sub] を選択します。

  8. [シンクのエクスポート先] の右側にあるプルダウン矢印をクリックし、続いて [新しいトピックを作成する(Cloud Pub/Sub)] をクリックします。

  9. [名前] に「integrity-monitoring」と入力し、続いて [作成] をクリックします。

  10. [シンクを作成] をクリックします。

整合性の失敗に対応するための Cloud Functions のトリガーを作成する

Cloud Pub/Sub トピックのデータを読み取り、整合性検証に失敗したすべての Shielded VM インスタンスを停止する Cloud Functions のトリガーを作成します。

  1. 次のコードは Cloud Functions のトリガーを定義します。このコードを main.py という名前のファイルにコピーします。

    import base64
    import json
    import googleapiclient.discovery
    
    def shutdown_vm(data, context):
        """A Cloud Function that shuts down a VM on failed integrity check."""
        log_entry = json.loads(base64.b64decode(data['data']).decode('utf-8'))
        payload = log_entry.get('jsonPayload', {})
        entry_type = payload.get('@type')
        if entry_type != 'type.googleapis.com/cloud_integrity.IntegrityEvent':
          raise TypeError("Unexpected log entry type: %s" % entry_type)
    
        report_event = (payload.get('earlyBootReportEvent')
            or payload.get('lateBootReportEvent'))
    
        policy_passed = report_event['policyEvaluationPassed']
        if not policy_passed:
          print('Integrity evaluation failed: %s' % report_event)
          print('Shutting down the VM')
    
          instance_id = log_entry['resource']['labels']['instance_id']
          project_id = log_entry['resource']['labels']['project_id']
          zone = log_entry['resource']['labels']['zone']
    
          # Shut down the instance.
          compute = googleapiclient.discovery.build(
              'compute', 'v1', cache_discovery=False)
    
          # Get the instance name from instance id.
          list_result = compute.instances().list(
              project=project_id,
              zone=zone,
                  filter='id eq %s' % instance_id).execute()
          if len(list_result['items']) != 1:
            raise KeyError('unexpected number of items: %d'
                % len(list_result['items']))
          instance_name = list_result['items'][0]['name']
    
          result = compute.instances().stop(project=project_id,
              zone=zone,
              instance=instance_name).execute()
          print('Instance %s in project %s has been scheduled for shut down.'
              % (instance_name, project_id))
    
  2. main.py と同じ場所に requirements.txt という名前のファイルを作成し、次の依存関係をコピーします。

    google-api-python-client==1.6.6
    google-auth==1.4.1
    google-auth-httplib2==0.0.3
    
  3. ターミナル ウィンドウを開き、main.pyrequirements.txt を含むディレクトリに移動します。

  4. gcloud beta functions deploy コマンドを実行してトリガーをデプロイします。

    gcloud beta functions deploy shutdown_vm --project YOUR_PROJECT_NAME \
        --runtime python37 --trigger-resource integrity-monitoring \
        --trigger-event google.pubsub.topic.publish
    

    YOUR_PROJECT_NAME を自分のプロジェクトの名前で置き換えます。

既存の正常なベースライン測定値のデータベースを作成する

Cloud Firestore データベースを作成して、既存の正常な整合性ポリシーのベースライン測定値のソースを提供します。このデータベースを最新の状態に保つには、ベースライン測定値を手動で追加する必要があります。

  1. [VM インスタンス] ページに移動します。
  2. Shielded VM インスタンス ID をクリックして、[VM インスタンスの詳細] ページを開きます。
  3. [ログ] の下の [Stackdriver Logging] をクリックします。
  4. 最新の lateBootReportEvent ログエントリを見つけます。
  5. ログエントリ > jsonPayload > lateBootReportEvent > policyMeasurements を展開します。
  6. lateBootReportEvent > policyMeasurements に含まれる要素の値に注目します。
  7. Cloud Firestore コンソールに移動します。
  8. [コレクションを開始] を選択します。
  9. [コレクション ID] に「known_good_measurements」と入力します。
  10. [ドキュメント ID] に「baseline1」と入力します。
  11. [フィールド名] に、lateBootReportEvent > policyMeasurements の要素 0 に含まれる [pcrNum] フィールドの値を入力します。
  12. [フィールド タイプ] で [マップ] を選択します。
  13. hashAlgopcrNumvalue という名前の 3 つの文字列フィールドを [マップ] フィールドに追加します。この値を lateBootReportEvent > policyMeasurements の要素 0 フィールドの値にします。
  14. さらにマップ フィールドを作成(lateBootReportEvent > policyMeasurements の要素ごとに 1 つ)します。作成したマップ フィールドに、最初のマップ フィールドと同じサブフィールドを指定します。このサブフィールドの値は、追加の要素ごとの値にマッピングされます。

    たとえば、Linux VM を使用している場合、作成が完了したコレクションは次のようになります。

    完成した既存の正常な測定値コレクションを示す Cloud Firestore データベース。

Cloud Functions のトリガーを更新する

  1. 次のコードにより、既存の正常な測定結果のデータベースにある場合は、整合性検証に失敗したすべての Shielded VM インスタンスに新しいベースラインを習得させ、それ以外の場合はシャットダウンする Cloud Functions のトリガーが作成されます。このコードをコピーし、main.py の既存のコードを上書きします。

    import base64
    import json
    import googleapiclient.discovery
    
    import firebase_admin
    from firebase_admin import credentials
    from firebase_admin import firestore
    
    PROJECT_ID = 'YOUR_PROJECT_ID'
    
    firebase_admin.initialize_app(credentials.ApplicationDefault(), {
        'projectId': PROJECT_ID,
    })
    
    def pcr_values_to_dict(pcr_values):
      """Converts a list of PCR values to a dict, keyed by PCR num"""
      result = {}
      for value in pcr_values:
        result[value['pcrNum']] = value
      return result
    
    def instance_id_to_instance_name(compute, zone, project_id, instance_id):
      list_result = compute.instances().list(
          project=project_id,
          zone=zone,
          filter='id eq %s' % instance_id).execute()
      if len(list_result['items']) != 1:
        raise KeyError('unexpected number of items: %d'
            % len(list_result['items']))
      return list_result['items'][0]['name']
    
    def relearn_if_known_good(data, context):
        """A Cloud Function that shuts down a VM on failed integrity check.
        """
        log_entry = json.loads(base64.b64decode(data['data']).decode('utf-8'))
        payload = log_entry.get('jsonPayload', {})
        if entry_type != 'type.googleapis.com/cloud_integrity.IntegrityEvent':
          raise TypeError("Unexpected log entry type: %s" % entry_type)
    
        # We only send relearn signal upon receiving late boot report event: if
        # early boot measurements are in a known good database, but late boot
        # measurements aren't, and we send relearn signal upon receiving early boot
        # report event, the VM will also relearn late boot policy baseline, which we
        # don't want, because they aren't known good.
        report_event = payload.get('lateBootReportEvent')
        if report_event is None:
          return
    
        evaluation_passed = report_event['policyEvaluationPassed']
        if evaluation_passed:
          # Policy evaluation passed, nothing to do.
          return
    
        # See if the new measurement is known good, and if it is, relearn.
        measurements = pcr_values_to_dict(report_event['policyMeasurements'])
    
        db = firestore.Client()
        kg_ref = db.collection('known_good_measurements')
    
        # Check current measurements against known good database.
        relearn = False
        for kg in kg_ref.get():
          if kg.to_dict() == measurements:
            relearn = True
    
        if not relearn:
          print('New measurement is not known good. Shutting down a VM.')
          instance_name = instance_id_to_instance_name(
            compute, zone, project_id, instance_id)
          result = compute.instances().stop(project=project_id,
              zone=zone,
              instance=instance_name).execute()
          print('Instance %s in project %s has been scheduled for shut down.'
                % (instance_name, project_id))
    
        print('New measurement is known good. Relearning...')
        instance_id = log_entry['resource']['labels']['instance_id']
        project_id = log_entry['resource']['labels']['project_id']
        zone = log_entry['resource']['labels']['zone']
    
        # Issue relearn API call.
        compute = googleapiclient.discovery.build('compute', 'beta',
            cache_discovery=False)
        instance_name = instance_id_to_instance_name(
            compute, zone, project_id, instance_id)
        result = compute.instances().setShieldedVmIntegrityPolicy(
            project=project_id,
            zone=zone,
            instance=instance_name,
            body={'updateAutoLearnPolicy':True}).execute()
        print('Instance %s in project %s has been scheduled for relearning.'
              % (instance_name, project_id))
    
  2. 次の依存関係をコピーし、requirements.txt の既存のコードを上書きします。

    google-api-python-client==1.6.6
    google-auth==1.4.1
    google-auth-httplib2==0.0.3
    google-cloud-firestore==0.29.0
    firebase-admin==2.13.0
    
  3. ターミナル ウィンドウを開き、main.pyrequirements.txt を含むディレクトリに移動します。

  4. gcloud beta functions deploy コマンドを実行してトリガーをデプロイします。

    gcloud beta functions relearn_if_known_good --project YOUR_PROJECT_NAME \
        --runtime python37 --trigger-resource integrity-monitoring \
        --trigger-event google.pubsub.topic.publish
    

    YOUR_PROJECT_NAME を自分のプロジェクトの名前で置き換えます。

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...