自动响应完整性验证失败

了解如何使用 Cloud Functions 触发器自动响应安全强化型虚拟机的完整性监控事件。

概览

完整性监控可从受防护虚拟机的实例收集测量结果,并在 Stackdriver Logging 中显示这些结果。如果完整性测量结果在受防护虚拟机的实例的启动之间发生更改,则完整性验证将失败。此故障将作为记录事件被捕获,并将在 Stackdriver Monitoring 中引发。

有时,受防护虚拟机的完整性测量结果会因合理原因而发生更改。例如,系统更新可能会导致操作系统内核发生预期更改。因此,完整性监控允许您在预期的完整性验证失败的情况下,提示受防护虚拟机的实例学习新的完整性政策基准。

在本教程中,您将首先创建一个简单的自动化系统,以关闭未通过完整性验证的受防护虚拟机的实例:

  1. 将所有完整性监控事件导出到 Cloud Pub/Sub 主题。
  2. 创建 Cloud Functions 触发器,以使用上述主题中的事件来识别并关闭未通过完整性验证的受防护虚拟机的实例。

接下来,您可以选择扩展该系统,以便它提示未通过完整性验证的受防护虚拟机的实例学习新基准(在实例与已知良好测量结果匹配时)或关闭(其他情况下):

  1. 创建 Cloud Firestore 数据库,以维护一组已知的良好完整性基准测量结果。
  2. 更新 Cloud Functions 触发器,以便它提示未通过完整性验证的受防护虚拟机的实例,学习新基准(如果实例位于相关数据库中)或关闭(其他情况下)。

如果您选择实施扩展后的解决方案,请按以下方式使用相应解决方案:

  1. 每次出现预计会因合理原因而导致验证失败的更新时,请在实例组中的单个受防护虚拟机的实例上运行该更新。
  2. 使用来自更新后的虚拟机实例的后期启动事件作为源,通过在 known_good_measurements 集合中创建新文档,将新的政策基准测量结果添加到数据库。有关更多信息,请参阅创建已知良好基准测量结果的数据库
  3. 更新安全强化型虚拟机的剩余实例。触发器会提示剩余实例学习新基准,因为这些实例可以被验证为属于已知良好的类型。如需了解详情,请参阅更新 Cloud Functions 触发器以了解已知良好的基准

前提条件

  • 使用原生模式下的 Cloud Firestore 被选作数据库服务的项目。您可以在创建项目时进行这一选择,并且选择后无法更改。如果您的项目不使用原生模式下的 Cloud Firestore,则在打开 Cloud Firestore 控制台时,您将看到消息“This project uses another database service”。
  • 在项目中使用 Compute Engine 受防护虚拟机的实例作为完整性基准测量结果的源。受防护虚拟机的实例必须已被重启至少一次。
  • 已安装 gcloud 命令行工具。
  • 按照以下步骤启用 Stackdriver Logging 和 Cloud Functions API:

    1. 转到 API 和服务
    2. 查看已启用的 API 和服务列表中是否包含 Cloud Functions APIStackdriver Logging API
    3. 如果未显示任一 API,请点击添加 API 和服务
    4. 根据需要搜索并启用相应 API。

将完整性监控日志条目导出到 Cloud Pub/Sub 主题

使用 Logging 将受防护虚拟机的实例生成的所有完整性监控日志条目导出到 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"
    

    YOUR_PROJECT_ID 替换为您的项目 ID。

  4. 点击提交过滤器

  5. 点击创建导出

  6. 对于接收器名称,键入 integrity-monitoring

  7. 对于接收器服务,选择 Cloud Pub/Sub

  8. 点击接收器目标位置右侧的下拉箭头,然后点击创建新的 Cloud Pub/Sub 主题

  9. 对于名称,键入 integrity-monitoring,然后点击创建

  10. 点击创建接收器

创建 Cloud Functions 触发器以响应完整性验证失败

创建 Cloud Functions 触发器,以读取 Cloud Pub/Sub 主题中的数据,并停止未通过完整性验证的任何受防护虚拟机的实例。

  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
    

    YOUR_PROJECT_ID 替换为您的项目 ID。

创建已知良好基准测量结果的数据库

创建 Cloud Firestore 数据库,以提供已知良好完整性政策基准测量结果的源。您必须手动添加基线测量结果以使此数据库保持最新。

  1. 转到“虚拟机实例”页面
  2. 点击受防护虚拟机的实例 ID 以打开虚拟机实例详情页面。
  3. 日志下,点击 Stackdriver Logging
  4. 找到最新的 lateBootReportEvent 日志条目。
  5. 展开日志条目 > jsonPayload > lateBootReportEvent > policyMeasurements
  6. 注意 lateBootReportEvent > policyMeasurements 中包含的元素的值。
  7. 转到 Cloud Storage 控制台
  8. 选择开始使用集合
  9. 对于集合 ID,键入 known_good_measurements
  10. 对于文档 ID,键入 baseline1
  11. 对于字段名称,在 lateBootReportEvent > policyMeasurements 中从元素 0 开始输入 pcrNum 字段值。
  12. 对于字段类型,选择映射
  13. 向映射字段添加三个字符串字段,分别命名为 hashAlgopcrNumvalue。使用 lateBootReportEvent > policyMeasurements 中元素 0 字段的值为上述三个字段赋值。
  14. 创建更多映射字段,一个字段对应 lateBootReportEvent > policyMeasurements 中的一个附加元素。为它们提供与第一个映射字段相同的子字段。这些子字段的值应映射到各个附加元素中的对应的值。

    例如,如果您使用的是 Linux 虚拟机,操作完成后该集合应类似于以下内容:

    显示已完成 known_good_measurements 集合的 Cloud Firestore 数据库。

更新 Cloud Functions 触发器以了解已知良好的基准

  1. 以下代码将创建一个 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', {})
        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
    

    YOUR_PROJECT_ID 替换为您的项目 ID。

此页内容是否有用?请给出您的反馈和评价:

发送以下问题的反馈:

此网页