Antworten auf Fehler der Integritätsprüfung automatisieren

Hier erfahren Sie, wie Sie einen Cloud Run-Funktions-Trigger verwenden, um automatisch auf Ereignisse der Integritätsüberwachung von Shielded VM-Instanzen zu reagieren.

Übersicht

Bei dem Integritätsmonitoring werden Messwerte von Shielded VM-Instanzen erfasst und in Cloud Logging angezeigt. Wenn sich die Integritätsmesswerte während des Startvorgangs einer Shielded VM-Instanz ändern, schlägt die Integritätsprüfung fehl. Dieser Fehler wird als protokolliertes Ereignis erfasst und auch in Cloud Monitoring angezeigt.

Manchmal sind Änderungen bei Integritätsmesswerten von Shielded VMs berechtigt. So kann zum Beispiel ein Systemupdate zu erwarteten Änderungen am Betriebssystem-Kernel führen. Daher können Sie bei einer Integritätsüberwachung eine Shielded VM-Instanz auffordern, für den Fall eines erwarteten Fehlers bei der Integritätsprüfung eine neue Referenz der Integritätsrichtlinie zu lernen.

In dieser Anleitung erstellen Sie zuerst ein einfaches automatisiertes System, das Shielded VM-Instanzen herunterfährt, die die Integritätsprüfung nicht bestehen:

  1. Exportieren Sie alle Ereignisse einer Integritätsüberwachung in ein Pub/Sub-Thema.
  2. Erstellen Sie dann einen Cloud Run-Funktions-Trigger, der die Ereignisse in diesem Thema dazu nutzt, um Shielded VM-Instanzen mit fehlschlagender Identitätsprüfung zu ermitteln und herunterzufahren.

Als Nächstes können Sie das System optional so erweitern, dass Shielded VM-Instanzen mit fehlschlagender Identitätsprüfung dazu aufgefordert werden, die neue Referenz zu lernen, wenn sie einen als funktionierend bekannten Messwert aufweisen, oder sonst herunterzufahren:

  1. Erstellen Sie eine Firestore-Datenbank zur Verwaltung einer Reihe von als funktionierend bekannten Messwerten als Integritätsreferenz.
  2. Aktualisieren Sie den Cloud Run-Funktions-Trigger so, dass Shielded VM-Instanzen mit fehlschlagender Integritätsprüfung aufgefordert werden, die neue Referenz zu lernen, wenn sich diese in der Datenbank befindet, oder andernfalls herunterzufahren.

Wenn Sie die erweiterte Lösung implementieren möchten, sollte die Verwendung so erfolgen:

  1. Jedes Mal, wenn Sie erwarten, dass ein Update berechtigterweise einen Fehler bei der Prüfung verursacht, führen Sie dieses Update auf einer einzelnen Shielded VM-Instanz in der Instanzgruppe aus.
  2. Fügen Sie mithilfe des Ereignisses zum letzten Startvorgang der aktualisierten VM-Instanz als Quelle der Datenbank die neuen Referenzmesswerte der Richtlinie hinzu. Erstellen Sie dafür ein neues Dokument in der Sammlung known_good_measurements. Weitere Informationen finden Sie unter Datenbank mit als funktionierend bekannten Referenzmesswerten erstellen.
  3. Aktualisieren Sie die übrigen Shielded VM-Instanzen. Der Trigger fordert diese Instanzen auf, die neue Referenz zu lernen, da diese als funktionierend bekannt bestätigt werden kann. Weitere Informationen finden Sie unter Cloud Run-Funktions-Trigger aktualisieren, um die als funktionierend bekannte Referenz zu lernen.

Vorbereitung

  • Verwenden Sie ein Projekt, bei dem Firestore im nativen Modus als Datenbankdienst ausgewählt ist. Diese Auswahl treffen Sie beim Erstellen des Projekts und sie kann nicht geändert werden. Wenn das Projekt Firestore nicht im nativen Modus verwendet, wird beim Öffnen der Firestore-Konsole die Meldung "Für dieses Projekt wird ein anderer Datenbankdienst eingesetzt" angezeigt.
  • In dem Projekt muss sich eine Shielded VM-Instanz mit Compute Engine befinden, die als Quelle der Referenzmesswerte für die Integritätsprüfung dient. Die Shielded VM-Instanz muss mindestens einmal neu gestartet worden sein.
  • Installieren Sie das gcloud-Befehlszeilentool.
  • Aktivieren Sie mithilfe der folgenden Schritte die Cloud Logging und Cloud Run Functions APIs:

    1. Rufen Sie in der Google Cloud Console die Seite APIs & Dienste auf.

      Rufen Sie "APIs & Dienste" auf.

    2. Prüfen Sie, ob die Cloud Functions API und die Stackdriver Logging API in der Liste Aktivierte APIs und Dienste angezeigt werden.

    3. Wenn eine der APIs nicht angezeigt wird, klicken Sie auf APIs und Services hinzufügen.

    4. Suchen Sie nach den APIs und aktivieren Sie sie nach Bedarf.

Logeinträge einer Integritätsüberwachung in ein Pub/Sub-Thema exportieren

Mit Logging können Sie alle Logeinträge einer Integritätsüberwachung, die von Shielded VM-Instanzen erzeugt werden, in ein Pub/Sub-Thema exportieren. Das Thema verwenden Sie dabei als Datenquelle für einen Cloud Run-Funktions-Trigger, um die Reaktionen auf Ereignisse der Integritätsüberwachung zu automatisieren.

Log-Explorer

  1. Rufen Sie in der Google Cloud Console die Seite Logs-Explorer auf.

    Zu Cloud Logging

  2. Geben Sie im Query Builder Folgendes ein:

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

  3. Klicken Sie auf Filter ausführen.

  4. Klicken Sie auf Weitere Aktionen und wählen Sie Senke erstellen aus.

  5. Auf der Seite Logs-Routing-Senke erstellen:

    1. Geben Sie unter Senkendetails unter Senkenname integrity-monitoring ein und klicken Sie dann auf Weiter.
    2. Erweitern Sie unter Senkenziel den Bereich Senkendienst und wählen Sie dann Cloud Pub/Sub aus.
    3. Maximieren Sie Cloud Pub/Sub-Thema auswählen und wählen Sie dann Thema erstellen aus.
    4. Geben Sie im Dialogfeld Thema erstellen unter Themen-ID integrity-monitoring ein und klicken Sie dann auf Thema erstellen.
    5. Klicken Sie auf Weiter und dann auf Senke erstellen.

Log-Explorer

  1. Rufen Sie in der Google Cloud Console die Seite Logs-Explorer auf.

    Zu Cloud Logging

  2. Klicken Sie auf Optionen und wählen Sie Zurück zum Legacy-Log-Explorer aus.

  3. Maximieren Sie Nach Label oder Textsuche filtern und klicken Sie dann auf In erweiterten Filter umwandeln.

  4. Geben Sie den folgenden erweiterten Filter ein:

    resource.type="gce_instance"
    AND logName:  "projects/YOUR_PROJECT_ID/logs/compute.googleapis.com%2Fshielded_vm_integrity"
    
    Beachten Sie, dass nach logName: zwei Leerzeichen stehen.

  5. Klicken Sie auf Filter senden.

  6. Klicken Sie auf Export erstellen.

  7. Geben Sie als Namen der Senke integrity-monitoring ein.

  8. Wählen Sie als Senkendienst "Cloud Pub/Sub" aus.

  9. Maximieren Sie Senkenziel und klicken Sie dann auf Neues Cloud Pub/Sub-Thema erstellen.

  10. Geben Sie für Name integrity-monitoring ein und klicken Sie dann auf Erstellen.

  11. Klicken Sie auf Senke erstellen.

Cloud Run-Funktions-Trigger erstellen, um auf Integritätsfehler zu reagieren

Erstellen Sie einen Cloud Run-Funktions-Trigger, der die Daten im Pub/Sub-Thema liest und jede Shielded VM-Instanz stoppt, die die Integritätsprüfung nicht besteht.

  1. Der folgende Code definiert den Cloud Run-Funktions-Trigger. Kopieren Sie ihn in eine Datei mit dem Namen 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. Erstellen Sie am gleichen Speicherort wie für main.py die Datei requirements.txt und kopieren Sie die folgenden Abhängigkeiten hinein:

    google-api-python-client==1.6.6
    google-auth==1.4.1
    google-auth-httplib2==0.0.3
    
  3. Öffnen Sie ein Terminalfenster und gehen Sie zum Verzeichnis mit main.py und requirements.txt.

  4. Führen Sie den Befehl gcloud beta functions deploy aus, um den Trigger bereitzustellen:

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

Datenbank mit als funktionierend bekannten Referenzmesswerten erstellen

Erstellen Sie eine Firestore-Datenbank, um eine Quelle als funktionierend bekannter Referenzmesswerte der Integritätsrichtlinie bereitzustellen. Die Referenzwerte müssen dabei manuell hinzugefügt werden, um die Datenbank auf dem neuesten Stand zu halten.

  1. Rufen Sie in der Google Cloud Console die Seite VM-Instanzen auf.

    Zur Seite "VM-Instanzen"

  2. Klicken Sie auf die Shielded VM-Instanz-ID, um die Seite VM-Instanzdetails zu öffnen.

  3. Klicken Sie unter Logs auf Stackdriver Logging.

  4. Suchen Sie den neuesten Logeintrag lateBootReportEvent.

  5. Maximieren Sie den Logeintrag > jsonPayload > lateBootReportEvent > policyMeasurements.

  6. Notieren Sie sich die Werte für die Elemente in lateBootReportEvent > policyMeasurements.

  7. Öffnen Sie in der Google Cloud Console die Seite Firestore.

    Zur Firestore-Konsole

  8. Wählen Sie Sammlung starten aus.

  9. Geben Sie bei Sammlungs-ID die Bezeichnung known_good_measurements ein.

  10. Geben Sie bei Dokument-ID die Bezeichnung baseline1 ein.

  11. Geben Sie für Feldname den Wert des Feldes pcrNum aus dem Element 0 in lateBootReportEvent > policyMeasurements ein.

  12. Wählen Sie als Feldtyp "zuordnen" aus.

  13. Fügen Sie dem Zuordnungsfeld drei String-Felder mit den Bezeichnungen hashAlgo, pcrNum und value hinzu. Übernehmen Sie hier die Werte der Felder von Element 0 in lateBootReportEvent > policyMeasurements.

  14. Erstellen Sie weitere Zuordnungsfelder, eines für jedes zusätzliche Element in lateBootReportEvent > policyMeasurements. Legen Sie die gleichen untergeordneten Felder an wie beim ersten Zuordnungsfeld. Die Werte dieser untergeordneten Felder müssen denen in den einzelnen zusätzlichen Elementen entsprechen.

    Wenn Sie beispielsweise eine Linux-VM verwenden, sollte die Sammlung am Ende ungefähr so aussehen:

    Firestore-Datenbank mit abgeschlossener Sammlung "known_good_measurements" für Linux

    Wenn Sie eine Windows-VM verwenden, sehen Sie mehr Messungen. Daher sollte die Sammlung in etwa so aussehen:

    Firestore-Datenbank mit abgeschlossener Sammlung "known_good_measurements" für Windows

Cloud Run-Funktions-Trigger aktualisieren, um die als funktionierend bekannte Referenz zu lernen

  1. Mit dem folgenden Code wird ein Cloud Run-Funktions-Trigger erstellt, durch den Shielded VM-Instanzen mit fehlschlagender Integritätsprüfung aufgefordert werden, die neue Referenz zu lernen, wenn sich diese in der Datenbank der als funktionierend bekannten Messwerte befindet, oder andernfalls herunterzufahren. Kopieren Sie diesen Code und überschreiben Sie damit den vorhandenen Code in main.py.

    import base64
    import json
    import googleapiclient.discovery
    
    import firebase_admin
    from firebase_admin import credentials
    from firebase_admin import firestore
    
    PROJECT_ID = '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['actualMeasurements'])
    
        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():
    
          kg_map = kg.to_dict()
    
          # Check PCR values for lateBootReportEvent measurements against the known good
          # measurements stored in the Firestore table
    
          if ('PCR_0' in kg_map and kg_map['PCR_0'] == measurements['PCR_0'] and
              'PCR_4' in kg_map and kg_map['PCR_4'] == measurements['PCR_4'] and
              'PCR_7' in kg_map and kg_map['PCR_7'] == measurements['PCR_7']):
    
            # Linux VM (3 measurements), only need to check above 3 measurements
            if len(kg_map) == 3:
              relearn = True
    
            # Windows VM (6 measurements), need to check 3 additional measurements
            elif len(kg_map) == 6:
              if ('PCR_11' in kg_map and kg_map['PCR_11'] == measurements['PCR_11'] and
                  'PCR_13' in kg_map and kg_map['PCR_13'] == measurements['PCR_13'] and
                  'PCR_14' in kg_map and kg_map['PCR_14'] == measurements['PCR_14']):
                relearn = True
    
        compute = googleapiclient.discovery.build('compute', 'beta',
            cache_discovery=False)
    
        instance_id = log_entry['resource']['labels']['instance_id']
        project_id = log_entry['resource']['labels']['project_id']
        zone = log_entry['resource']['labels']['zone']
    
        instance_name = instance_id_to_instance_name(compute, zone, project_id, instance_id)
    
        if not relearn:
          # Issue shutdown API call.
          print('New measurement is not known good. Shutting down a VM.')
    
          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))
    
        else:
          # Issue relearn API call.
          print('New measurement is known good. Relearning...')
    
          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. Kopieren Sie die folgenden Abhängigkeiten und überschreiben Sie damit den vorhandenen Code in 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. Öffnen Sie ein Terminalfenster und gehen Sie zum Verzeichnis mit main.py und requirements.txt.

  4. Führen Sie den Befehl gcloud beta functions deploy aus, um den Trigger bereitzustellen:

    gcloud beta functions deploy relearn_if_known_good --project PROJECT_ID \
        --runtime python37 --trigger-resource integrity-monitoring \
        --trigger-event google.pubsub.topic.publish
    
  5. Löschen Sie die vorherige Funktion shutdown_vm in der Cloud Functions-Konsole manuell.

  6. Wechseln Sie in der Google Cloud Console zur Seite Cloud Functions.

    Zu Cloud Functions

  7. Wählen Sie die Funktion shutdown_vm aus und klicken Sie auf Löschen.

Automatisierte Antworten auf Fehler der Integritätsprüfung prüfen

  1. Prüfen Sie zunächst, ob eine ausgeführte Instanz mit Secure Boot als eine Shielded VM-Option aktiviert ist. Falls nicht, können Sie eine neue Instanz mit Shielded VM-Image erstellen (Ubuntu 18.04LTS) und die Option Secure Boot aktivieren. Ihnen werden möglicherweise einige Cent für die Instanz in Rechnung gestellt. Dieser Schritt kann innerhalb einer Stunde abgeschlossen werden.
  2. Angenommen, Sie möchten den Kernel manuell aktualisieren.
  3. Stellen Sie eine SSH-Verbindung zur Instanz her und prüfen Sie mit dem folgenden Befehl den aktuellen Kernel.

    uname -sr
    

    Sie sollten etwa Linux 4.15.0-1028-gcp sehen.

  4. Laden Sie einen generischen Kernel von https://kernel.ubuntu.com/~kernel-ppa/mainline/ herunter.

  5. Verwenden Sie den Befehl zur Installation.

    sudo dpkg -i *.deb
    
  6. Starten Sie die VM neu.

  7. Sie sollten jetzt erkennen können, dass die VM nicht gebootet wird und keine SSH-Verbindung zur Maschine hergestellt werden kann. Das liegt erwartungsgemäß daran, dass die Signatur des neuen Kernels nicht auf der weißen Liste Secure Boot steht. Dies zeigt auch, wie Secure Boot eine nicht autorisierte/schädliche Änderung des Kernels verhindern kann.

  8. Da wir jedoch wissen, dass das Upgrade des Kernels nicht schädlich ist und von uns selbst durchgeführt wird, können wir Secure Boot deaktivieren, um den neuen Kernel zu booten.

  9. Fahren Sie die VM herunter und deaktivieren Sie die Option Secure Boot. Starten Sie anschließend die VM neu.

  10. Der Bootvorgang des Computers sollte erneut fehlschlagen. Dieses Mal wird er jedoch automatisch durch die von uns erstellte Cloud Functions-Funktion heruntergefahren. Dies liegt an der Änderung der Option Secure Boot und an dem neuen Kernel-Image. Dadurch weicht der Messwert von der Referenzversion ab. (Wir können dies im Stackdriver-Log der Cloud Function-Funktion prüfen.)

  11. Da wir wissen, dass dies keine schädliche Änderung ist und wir die Ursache kennen, können wir den aktuellen Messwert in lateBootReportEvent zur Firebase-Tabelle mit als funktionierend bekannten Messwerten hinzufügen. (Beachten Sie, dass zwei Dinge geändert werden: 1. Option Secure Boot 2. Kernel-Image.)

    Führen Sie dann den vorherigen Schritt Datenbank mit als funktionierend bekannten Referenzmesswerten erstellen aus, um eine neue Referenz mit den aktuellen Messwerten in lateBootReportEvent an die Firestore-Datenbank anzuhängen.

    Firestore-Datenbank mit neuer abgeschlossener Sammlung "known_good_measurements"

  12. Starten Sie nun den Computer neu. Wenn Sie Stackdriver-Log aktivieren, werden Sie feststellen, dass das lateBootReportEvent immer noch "false" anzeigt. Der Computer sollte nun jedoch erfolgreich gebootet werden, da die Cloud Function-Funktion den neuen Messwert als vertrauenswürdig eingestuft und ihn neu erkannt hat. Dies können wir anhand von Stackdriver der Cloud Function prüfen.

  13. Secure Boot ist jetzt deaktiviert und wir können den Kernel booten. Stellen Sie eine SSH-Verbindung zur Maschine her und prüfen Sie den Kernel erneut. Sie sehen dann die neue Kernel-Version.

    uname -sr
    
  14. Bereinigen Sie anschließend die Ressourcen und Daten, die in diesem Schritt verwendet werden.

  15. Wenn Sie eine VM für diesen Schritt erstellt haben, fahren Sie sie herunter, um zusätzliche Kosten zu vermeiden.

  16. Rufen Sie in der Google Cloud Console die Seite VM-Instanzen auf.

    Zur Seite "VM-Instanzen"

  17. Entfernen Sie die als funktionierend bekannten Messwerte, die Sie in diesem Schritt hinzugefügt haben.

  18. Öffnen Sie in der Google Cloud Console die Seite Firestore.

    Zur Seite "Firestore"