Scopri come utilizzare un trigger di funzioni Cloud Run per agire automaticamente sugli eventi di monitoraggio dell'integrità di VM schermate.
Panoramica
Il monitoraggio dell'integrità raccoglie le misurazioni dalle istanze Shielded VM e le visualizza in Cloud Logging. Se le misurazioni di integrità cambiano attraverso gli avvii di un'istanza VM schermata, la convalida dell'integrità fallisce. Questo l'errore viene acquisito come evento registrato e viene generato anche in Cloud Monitoring.
A volte, le misurazioni di integrità della VM schermata vengono modificate per un motivo legittimo. Ad esempio, un aggiornamento di sistema potrebbe causare modifiche previste al kernel del sistema operativo. Per questo motivo, il monitoraggio dell'integrità consente di richiedere all'istanza VM schermata di apprendere una nuova baseline dei criteri di integrità nel caso di un errore di convalida dell'integrità previsto.
In questo tutorial, creerai innanzitutto un semplice sistema automatizzato che arresta le istanze VM schermate che non rientrano nella convalida dell'integrità:
- Esporta tutto il monitoraggio dell'integrità in un argomento Pub/Sub.
- Crea un trigger di funzioni Cloud Run che utilizza gli eventi in quell'argomento per identificare e arrestare Istanze Shielded VM che non superano la convalida dell'integrità.
Successivamente, puoi espandere facoltativamente il sistema in modo che richieda alle istanze VM schermate che non rientrano nella convalida dell'integrità di apprendere la nuova baseline se questa corrisponde a una misurazione nota, o altrimenti di arrestarla:
- Crea un database Firestore per mantenere un insieme di beni noti misurazioni di base dell'integrità.
- Aggiorna l'trigger delle funzioni Cloud Run in modo che richieda alle istanze VM schermate che non rientrano nella convalida dell'integrità di apprendere la nuova baseline se questa si trova nel database, oppure di arrestarsi.
Se scegli di implementare la soluzione espansa, utilizzala nel modo seguente:
- Ogni volta che è previsto un aggiornamento che causa un errore di convalida per un motivo legittimo, esegui tale aggiornamento su una singola istanza VM schermata nel gruppo di istanze.
- Utilizzando l'evento della fase finale di avvio dall'istanza VM aggiornata come origine, aggiungi le nuove misurazioni di baseline dei criteri nel database creando un nuovo documento nella raccolta known_good_measurements. Per ulteriori informazioni, consulta Creazione di un database con misurazioni di baseline note.
- Aggiorna le rimanenti istanze VM schermate. Il trigger richiede alle istanze rimanenti di apprendere la nuova baseline, perché può essere verificata come nota. Consulta: Aggiornamento del trigger delle funzioni di Cloud Run per apprendere la baseline nota per ulteriori informazioni.
Prerequisiti
- Utilizza un progetto che ha Firestore in modalità nativa selezionata come servizio di database. Puoi effettuare questa selezione al momento della creazione del progetto e non può essere modificata. Se il tuo progetto non utilizza Firestore in modalità Native viene visualizzato il messaggio "Questo progetto utilizza un altro servizio di database" quando apri la console Firestore.
- Disponi di un'istanza VM schermata di Compute Engine in quel progetto che funga da fonte delle misurazioni di baseline dell'integrità. L'istanza VM schermata deve essere stata riavviata almeno una volta.
- Installa lo strumento a riga di comando
gcloud
. Abilita le API Cloud Logging e Cloud Run seguendo questi passaggi:
Nella console Google Cloud, vai al menu API e Servizi.
Verifica se l'API Cloud Functions e l'API Stackdriver Logging sono visualizzate nell'elenco API e servizi abilitati.
Se nessuna delle due API viene visualizzata, fai clic su Aggiungi API e servizi.
Cerca e abilita le API, se necessario.
Esportazione delle voci di log del monitoraggio di integrità in un argomento Pub/Sub
Utilizza Logging per esportare tutte le voci di log del monitoraggio di integrità generate dalle istanze VM schermate in un argomento Pub/Sub. Questo argomento viene utilizzato come origine dati per un trigger di funzioni Cloud Run per Automatizzare le risposte agli eventi di monitoraggio dell'integrità.
Esplora log
Nella console Google Cloud, vai alla pagina Esplora log.
In Query Builder, inserisci i valori riportati di seguito.
resource.type="gce_instance" AND logName: "projects/YOUR_PROJECT_ID/logs/compute.googleapis.com%2Fshielded_vm_integrity"
Fai clic su Esegui filtro.
Fai clic su
Altre azioni e quindi seleziona Crea sink.Nella pagina Crea sink di routing dei log:
- In Dettagli sink, in Nome sink, inserisci
integrity-monitoring
e poi fai clic su Avanti. - In Destinazione sink, espandi Servizio sink, quindi seleziona Cloud Pub/Sub.
- Espandi Seleziona un argomento Cloud Pub/Sub e poi seleziona Crea un argomento.
- Nella finestra di dialogo Crea un argomento, per ID argomento, inserisci
integrity-monitoring
, quindi fai clic su Crea argomento. - Fai clic su Avanti, quindi su Crea sink.
- In Dettagli sink, in Nome sink, inserisci
Esplora log
Nella console Google Cloud, vai alla pagina Esplora log.
Fai clic su Opzioni e seleziona Torna a Esplora log legacy.
Espandi Filtra per etichetta o testo, quindi fai clic su Converti in filtro avanzato.
Inserisci il seguente filtro avanzato:
Tieni presente che ci sono due spazi doporesource.type="gce_instance" AND logName: "projects/YOUR_PROJECT_ID/logs/compute.googleapis.com%2Fshielded_vm_integrity"
logName:
.Fai clic su Invia filtro.
Fai clic su Crea esportazione.
In Nome sink, inserisci
integrity-monitoring
.Per il Servizio sink, seleziona Cloud Pub/Sub.
Espandi Destinazione sink e poi fai clic su Crea nuovo argomento Cloud Pub/Sub.
In Nome, inserisci
integrity-monitoring
e fai clic su Crea.Fai clic su Crea sink.
Creazione di un trigger di funzioni Cloud Run per rispondere agli errori di integrità
Crea un trigger di funzioni Cloud Run che legga i dati in Pub/Sub e che arresta qualsiasi istanza Shielded VM che non supera la convalida dell'integrità.
Il codice seguente definisce il trigger delle funzioni Cloud Run. Copia in un file denominato
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))
Nella stessa posizione di
main.py
, crea un file denominatorequirements.txt
e copia le seguenti dipendenze:google-api-python-client==1.6.6 google-auth==1.4.1 google-auth-httplib2==0.0.3
Apri una finestra del terminale e vai alla directory contenente
main.py
erequirements.txt
.Esegui l' Comando
gcloud beta functions deploy
per eseguire il deployment del trigger:gcloud beta functions deploy shutdown_vm --project PROJECT_ID \ --runtime python37 --trigger-resource integrity-monitoring \ --trigger-event google.pubsub.topic.publish
Creazione di un database con misurazioni di baseline note
Crea un database Firestore per fornire un'origine di dati buone misurazioni di base dei criteri di integrità. Devi aggiungere manualmente le misurazioni di baseline per mantenere aggiornato questo database.
Nella console Google Cloud, vai alla pagina Istanze VM.
Fai clic sull'ID dell'istanza VM schermata per aprire la pagina dei dettagli dell'istanza VM.
Sotto Log, fai clic su Stackdriver Logging.
Individua la voce di log
lateBootReportEvent
più recente.Espandi la voce di log >
jsonPayload
>lateBootReportEvent
>policyMeasurements
.Prendi nota dei valori per gli elementi contenuti in
lateBootReportEvent
>policyMeasurements
.Nella console Google Cloud, vai alla pagina Firestore.
Scegli Avvia raccolta.
Per ID raccolta, digita known_good_measurements.
Per ID documento, digita baseline1.
Per Nome campo, digita il valore del campo pcrNum dall'elemento
0
inlateBootReportEvent
>policyMeasurements
.Per Tipo di campo, seleziona mappa.
Aggiungi tre campi stringa al campo mappa, rispettivamente denominati hashAlgo, pcrNum e value. Rendi i loro valori i valori dell'elemento
0
campi inlateBootReportEvent
>policyMeasurements
.Crea più campi mappa, uno per ogni elemento aggiuntivo in
lateBootReportEvent
>policyMeasurements
. Fornisci loro gli stessi sottocampi del primo campo mappa. I valori per tali sottocampi dovrebbero essere mappati in quelli di ciascuno degli elementi aggiuntivi.Ad esempio, se stai utilizzando una VM Linux, la raccolta dovrebbe apparire simile a quanto segue quando hai terminato:
Se utilizzi una VM Windows, vedrai più misurazioni, quindi la raccolta dovrebbe avere il seguente aspetto:
Aggiornamento del trigger delle funzioni di Cloud Run per apprendere la baseline nota
Il codice seguente crea un trigger di funzioni Cloud Run che causa qualsiasi Istanza Shielded VM che non supera la convalida dell'integrità per apprendere il una nuova base di riferimento se è presente nel database delle misurazioni note oppure è stato arrestato. Copia questo codice e utilizzalo per sovrascrivere il codice esistente 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))
Copia le seguenti dipendenze e usale per sovrascrivere il codice esistente tra
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
Apri una finestra del terminale e vai alla directory contenente
main.py
erequirements.txt
.Esegui l' Comando
gcloud beta functions deploy
per eseguire il deployment del trigger:gcloud beta functions deploy relearn_if_known_good --project PROJECT_ID \ --runtime python37 --trigger-resource integrity-monitoring \ --trigger-event google.pubsub.topic.publish
Elimina manualmente la funzione
shutdown_vm
precedente nella console Cloud Functions.Nella console Google Cloud, vai alla pagina Cloud Functions.
Seleziona la funzione shutdown_vm e fai clic su Elimina.
Verificare le risposte automatiche agli errori di convalida dell'integrità
- Innanzitutto, controlla se hai un'istanza in esecuzione con l'Avvio protetto attivato come un'opzione Shielded VM. In caso contrario, puoi creare una nuova istanza con l'immagine VM protetta (Ubuntu 18.04 LTS) e attivare l'opzione Avvio protetto. È possibile che ti vengano addebitati alcuni centesimi per l'istanza (questo passaggio può completato entro un'ora).
- Supponiamo che, per qualche motivo, tu voglia eseguire l'upgrade manuale del kernel.
Connettiti all'istanza tramite SSH e utilizza il seguente comando per controllare il kernel corrente.
uname -sr
Il risultato dovrebbe essere simile a
Linux 4.15.0-1028-gcp
.Scarica un kernel generico da https://kernel.ubuntu.com/~kernel-ppa/mainline/
Utilizza il comando per l'installazione.
sudo dpkg -i *.deb
Riavvia la VM.
Dovresti notare che la VM non si avvia (non riesci ad accedere alla macchina tramite SSH). Questo è ciò che ci aspettiamo, perché la firma del nuovo kernel non è la nostra lista consentita di Avvio protetto. Questo dimostra anche come l'Avvio protetto può impedire la modifica non autorizzata/dannosa del kernel.
Ma poiché sappiamo che questa volta l'upgrade del kernel non è dannoso ed da parte nostra, possiamo disattivare l'Avvio protetto per avviare un nuovo kernel.
Arresta la VM e deseleziona l'opzione Avvio protetto, quindi riavvia la VM.
L'avvio della macchina dovrebbe non riuscire di nuovo. Tuttavia, questa volta viene chiuso automaticamente dalla funzione cloud che abbiamo creato perché l'opzione Secure Boot è stata modificata (anche a causa della nuova immagine del kernel) e questo ha causato una misurazione diversa rispetto al valore di riferimento. Possiamo verificarlo nel log Stackdriver della funzione cloud.
Sappiamo che si tratta di una modifica dannosa e che alla radice perché possiamo aggiungere la misura attuale in
lateBootReportEvent
alla di misurazione di Firebase di buona qualità. Ricorda che vengono modificate due cose: 1. Opzione 2 di Avvio protetto. Immagine del kernel.)Segui il passaggio precedente Creazione di un database di misurazioni di base note per aggiungere una nuova di base al database Firestore utilizzando la misurazione effettiva in ultimi
lateBootReportEvent
.Ora riavvia il computer. Quando controlli il log di Stackdriver, vedrai
lateBootReportEvent
continua a essere false, ma la macchina ora si avvia correttamente perché la funzione Cloud Functions considerava attendibile misurazione. Possiamo verificarlo controllando Stackdriver della funzione cloud.Ora che l'Avvio protetto è disabilitato, possiamo avviare il kernel. Accedi tramite SSH alla macchina e controlla di nuovo il kernel, vedrai la nuova versione del kernel.
uname -sr
Infine, puliamo le risorse e i dati utilizzati in questo passaggio.
Arresta la VM se ne hai creata una per questo passaggio per evitare un addebito aggiuntivo.
Nella console Google Cloud, vai alla pagina Istanze VM.
Rimuovi le misurazioni di cui è nota la validità aggiunte in questo passaggio.
Nella console Google Cloud, vai alla pagina Firestore.