自動發出對完整性驗證失敗的回應

瞭解如何使用 Cloud Functions 觸發條件來自動因應受防護的 VM 完整性監控事件。

總覽

完整性監控功能會從受防護的 VM 執行個體收集測量資料,然後呈現在 Stackdriver Logging 中。各個受防護的 VM 執行個體啟動時,如果完整性測量結果有任何變更,完整性驗證就會失敗。系統會擷取這個失敗並將其視為記錄事件,而且也會在 Stackdriver Monitoring 中提出。

有時候,受防護的 VM 完整性測量結果會因為正當理由而變更。舉例來說,系統更新可能就會導致作業系統核心出現預期的變更。因此,完整性監控功能可以讓您在預期完整性驗證會失敗時,先向受防護的 VM 提示有新的完整性政策基準。

在這個教學課程中,您會先建立一個簡單的自動系統,讓受防護的 VM 執行個體在完整性驗證失敗時可以先關閉:

  1. 將所有完整性監控事件都匯出到 Cloud Pub/Sub 主題。
  2. 建立 Cloud Functions 觸發條件,透過觸發條件使用該主題中的事件來找出完整性驗證失敗的受防護 VM 執行個體,然後予以關閉。

接著,您可以選擇是否要擴充系統,提示完整性驗證失敗的受防護 VM 執行個體有新的基準;藉此基準來判斷其符合已知的理想測量結果,否則就予以關閉:

  1. 建立 Cloud Firestore 資料庫來維護一組已知的理想完整性基準測量結果。
  2. 更新 Cloud Functions 觸發條件,以便在受防護的 VM 執行個體未通過完整性驗證時收到提示,進一步瞭解新的基準並判斷是否符合資料庫內的測量結果,否則就予以關閉。

如果您選擇實作擴充版的解決方案,請按照以下方式使用:

  1. 只要預期更新會因為正當理由導致驗證失敗,就對執行個體群組中的單一受防護 VM 執行個體執行該項更新。
  2. 將已更新 VM 執行個體的最近啟動事件做為來源,在 known_good_measurements 集合中建立新文件,將新政策基準測量結果加入資料庫。詳情請參閱「建立資料庫存放已知的理想基準測量結果」。
  3. 更新剩餘的其他受防護 VM 執行個體。觸發條件會提示其餘執行個體使用新的基準,因為該基準已驗證為是已知的理想結果。詳情請參閱更新 Cloud Functions 觸發條件以使用已知的理想基準

必備條件

  • 使用選取做為資料庫服務的原生模式 Cloud Firestore;這個服務是您在建立專案時選取的,且無法變更。如果您的專案並非使用原生模式的 Cloud Firestore,當您開啟 Cloud Firestore 主控台時就會看見「這個專案使用另一個資料庫服務」訊息。
  • 將該專案中的一個 Compute Engine 受防護 VM 執行個體當做完整性基準測量的來源。受防護的 VM 執行個體必須重新啟動至少一次。
  • 已安裝 gcloud 指令列工具。
  • 按照下列步驟啟用 Stackdriver Logging 和 Cloud Functions API:

    1. 前往「API 和服務」
    2. 查看 Cloud Functions APIStackdriver Logging API 是否已顯示在「已啟用 API 和服務」清單上。
    3. 如果有任何一個 API 未顯示,請按一下 [新增 API 和 服務]
    4. 視需要搜尋並啟用 API。

將完整性監控記錄項目匯出到 Cloud Pub/Sub 主題

使用 Logging 將受防護的 VM 執行個體產生的所有完整性監控記錄項目都匯出到 Cloud Pub/Sub 主題。您可以將這個主題做為 Cloud Functions 觸發條件的資料來源,自動回覆完整性監控事件。

  1. 前往 Stackdriver Logging
  2. 按一下「按標籤或搜尋字詞篩選」右側的下拉式箭頭,然後按一下 [轉換為進階篩選器]
  3. 輸入下列進階篩選器:

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

    請以您的專案 ID 取代 YOUR_PROJECT_ID

  4. 按一下 [Submit Filter] (提交篩選器)

  5. 按一下 [建立匯出作業]

  6. 在「接收器名稱」欄位中輸入 integrity-monitoring

  7. 在「接收器服務」選單中選取 [Cloud Pub/Sub]

  8. 按一下「接收器目標位置」右側的下拉式箭頭,然後按一下 [新建 Cloud Pub/Sub 主題]

  9. 在「名稱」欄位中輸入 integrity-monitoring,然後按一下 [建立]

  10. 按一下 [建立接收器]

建立 Cloud Functions 觸發條件來回應完整性失敗

建立 Cloud Functions 觸發條件,讀取 Cloud Pub/Sub 主題中的資料,並停止所有未通過完整性驗證的受防護 VM 執行個體。

  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'))
    
        if report_event is None:
          # We received a different event type, ignore.
          return
    
        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_ID \
        --runtime python37 --trigger-resource integrity-monitoring \
        --trigger-event google.pubsub.topic.publish
    

    請以您的專案 ID 取代 YOUR_PROJECT_ID

建立資料庫來存放已知的理想基準測量結果

建立 Cloud Firestore 資料庫來提供一組已知的理想完整性基準測量結果。您必須手動新增基準測量結果,讓這個資料庫保持在最新狀態。

  1. 前往 VM 執行個體頁面
  2. 按一下受防護的 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. 在「欄位名稱」(Field name) 中,輸入從 lateBootReportEvent > policyMeasurements 的元素 0 取得的 pcrNum 欄位值。
  12. 在「欄位類型」選單中選取 [對應]
  13. 在名稱為「hashAlgo」、「pcrNum」和「value」的對應欄位中個新增三個字串欄位;填入從 lateBootReportEvent > policyMeasurements 元素 0 欄位中取得的值。
  14. 建立多個對應欄位,lateBootReportEvent > policyMeasurements 中每個額外元素各一個;並為這些元素提供和第一個對應欄位一樣的子欄位。這些子欄位的值應對應於每個額外元素的值。

    舉例來說,如果您使用 Linux VM,完成之後,集合看起來應該類似於以下架構:

    建立 Cloud Firestore 資料庫來顯示完整的 known_good_measurements 集合。

更新 Cloud Functions 觸發條件以使用已知的理想基準

  1. 下列程式碼用於建立 Cloud Functions 觸發條件,能讓完整性驗證失敗的受防護 VM 執行個體取得新的基準,並判斷是否符合資料庫內的理想測量結果,否則就予以關閉。複製這段程式碼,並用於覆寫 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', {})
        entry_type = payload.get('@type')
        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().setShieldedInstanceIntegrityPolicy(
            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 deploy relearn_if_known_good --project YOUR_PROJECT_ID \
        --runtime python37 --trigger-resource integrity-monitoring \
        --trigger-event google.pubsub.topic.publish
    

    請以您的專案 ID 取代 YOUR_PROJECT_ID

本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
說明文件