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:
- Exportieren Sie alle Ereignisse einer Integritätsüberwachung in ein Pub/Sub-Thema.
- 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:
- Erstellen Sie eine Firestore-Datenbank zur Verwaltung einer Reihe von als funktionierend bekannten Messwerten als Integritätsreferenz.
- 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:
- 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.
- 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.
- 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:
Rufen Sie in der Google Cloud Console die Seite APIs & Dienste auf.
Prüfen Sie, ob die Cloud Functions API und die Stackdriver Logging API in der Liste Aktivierte APIs und Dienste angezeigt werden.
Wenn eine der APIs nicht angezeigt wird, klicken Sie auf APIs und Services hinzufügen.
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
Rufen Sie in der Google Cloud Console die Seite Logs-Explorer auf.
Geben Sie im Query Builder Folgendes ein:
resource.type="gce_instance" AND logName: "projects/YOUR_PROJECT_ID/logs/compute.googleapis.com/shielded_vm_integrity"
Klicken Sie auf Filter ausführen.
Klicken Sie auf
Weitere Aktionen und wählen Sie Senke erstellen aus.Auf der Seite Logs-Routing-Senke erstellen:
- Geben Sie unter Senkendetails unter Senkenname
integrity-monitoring
ein und klicken Sie dann auf Weiter. - Erweitern Sie unter Senkenziel den Bereich Senkendienst und wählen Sie dann Cloud Pub/Sub aus.
- Maximieren Sie Cloud Pub/Sub-Thema auswählen und wählen Sie dann Thema erstellen aus.
- Geben Sie im Dialogfeld Thema erstellen unter Themen-ID
integrity-monitoring
ein und klicken Sie dann auf Thema erstellen. - Klicken Sie auf Weiter und dann auf Senke erstellen.
- Geben Sie unter Senkendetails unter Senkenname
Log-Explorer
Rufen Sie in der Google Cloud Console die Seite Logs-Explorer auf.
Klicken Sie auf Optionen und wählen Sie Zurück zum Legacy-Log-Explorer aus.
Maximieren Sie Nach Label oder Textsuche filtern und klicken Sie dann auf In erweiterten Filter umwandeln.
Geben Sie den folgenden erweiterten Filter ein:
Beachten Sie, dass nachresource.type="gce_instance" AND logName: "projects/YOUR_PROJECT_ID/logs/compute.googleapis.com/shielded_vm_integrity"
logName:
zwei Leerzeichen stehen.Klicken Sie auf Filter senden.
Klicken Sie auf Export erstellen.
Geben Sie als Namen der Senke
integrity-monitoring
ein.Wählen Sie als Senkendienst "Cloud Pub/Sub" aus.
Maximieren Sie Senkenziel und klicken Sie dann auf Neues Cloud Pub/Sub-Thema erstellen.
Geben Sie für Name
integrity-monitoring
ein und klicken Sie dann auf Erstellen.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.
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))
Erstellen Sie am gleichen Speicherort wie für
main.py
die Dateirequirements.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
Öffnen Sie ein Terminalfenster und gehen Sie zum Verzeichnis mit
main.py
undrequirements.txt
.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.
Rufen Sie in der Google Cloud Console die Seite VM-Instanzen auf.
Klicken Sie auf die Shielded VM-Instanz-ID, um die Seite VM-Instanzdetails zu öffnen.
Klicken Sie unter Logs auf Stackdriver Logging.
Suchen Sie den neuesten Logeintrag
lateBootReportEvent
.Maximieren Sie den Logeintrag >
jsonPayload
>lateBootReportEvent
>policyMeasurements
.Notieren Sie sich die Werte für die Elemente in
lateBootReportEvent
>policyMeasurements
.Öffnen Sie in der Google Cloud Console die Seite Firestore.
Wählen Sie Sammlung starten aus.
Geben Sie bei Sammlungs-ID die Bezeichnung known_good_measurements ein.
Geben Sie bei Dokument-ID die Bezeichnung baseline1 ein.
Geben Sie für Feldname den Wert des Feldes pcrNum aus dem Element
0
inlateBootReportEvent
>policyMeasurements
ein.Wählen Sie als Feldtyp "zuordnen" aus.
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
inlateBootReportEvent
>policyMeasurements
.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:
Wenn Sie eine Windows-VM verwenden, sehen Sie mehr Messungen. Daher sollte die Sammlung in etwa so aussehen:
Cloud Run-Funktions-Trigger aktualisieren, um die als funktionierend bekannte Referenz zu lernen
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))
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
Öffnen Sie ein Terminalfenster und gehen Sie zum Verzeichnis mit
main.py
undrequirements.txt
.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
Löschen Sie die vorherige Funktion
shutdown_vm
in der Cloud Functions-Konsole manuell.Wechseln Sie in der Google Cloud Console zur Seite Cloud Functions.
Wählen Sie die Funktion shutdown_vm aus und klicken Sie auf Löschen.
Automatisierte Antworten auf Fehler der Integritätsprüfung prüfen
- 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.
- Angenommen, Sie möchten den Kernel manuell aktualisieren.
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.Laden Sie einen generischen Kernel von https://kernel.ubuntu.com/~kernel-ppa/mainline/ herunter.
Verwenden Sie den Befehl zur Installation.
sudo dpkg -i *.deb
Starten Sie die VM neu.
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.
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.
Fahren Sie die VM herunter und deaktivieren Sie die Option Secure Boot. Starten Sie anschließend die VM neu.
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.)
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.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.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
Bereinigen Sie anschließend die Ressourcen und Daten, die in diesem Schritt verwendet werden.
Wenn Sie eine VM für diesen Schritt erstellt haben, fahren Sie sie herunter, um zusätzliche Kosten zu vermeiden.
Rufen Sie in der Google Cloud Console die Seite VM-Instanzen auf.
Entfernen Sie die als funktionierend bekannten Messwerte, die Sie in diesem Schritt hinzugefügt haben.
Öffnen Sie in der Google Cloud Console die Seite Firestore.