Automazione delle risposte a errori di convalida dell'integrità

Scopri come utilizzare un trigger di Cloud Functions per agire automaticamente sugli eventi di monitoraggio dell'integrità di VM schermate.

Panoramica

Il monitoraggio dell'integrità raccoglie le misurazioni dalle istanze VM schermate e le mostra in Stackdriver Logging. Se le misurazioni di integrità cambiano attraverso gli avvii di un'istanza VM schermata, la convalida dell'integrità fallisce. Questo errore viene acquisito come evento registrato e viene anche generato in Stackdriver 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à:

  1. Esporta tutti gli eventi di monitoraggio dell'integrità in un argomento Cloud Pub/Sub.
  2. Crea un trigger di Cloud Functions che utilizza gli eventi in quell'argomento per identificare e arrestare le istanze VM schermate che non rientrano nella 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:

  1. Crea un database Cloud Firestore per mantenere un insieme di misurazioni di baseline di integrità note.
  2. Aggiorna il trigger di Cloud Functions 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:

  1. 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.
  2. 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. Consulta Creazione di un database con misurazioni di baseline note per ulteriori informazioni.
  3. 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 di Cloud Functions per apprendere la baseline nota per ulteriori informazioni.

Prerequisiti

  • Utilizza un progetto che ha Cloud 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 progetto non utilizza Cloud Firestore in modalità nativa, visualizzerai il messaggio "Questo progetto utilizza un altro servizio di database" quando apri la console di Cloud 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 di Stackdriver Logging e Cloud Functions seguendo questi passaggi:

    1. Consulta API e servizi
    2. Verifica se l'API Cloud Functions e l'API Stackdriver Logging sono visualizzate nell'elenco API e servizi abilitati.
    3. Se nessuna delle due API viene visualizzata, fai clic su Aggiungi API e servizi.
    4. Cerca e abilita le API, se necessario.

Esportazione delle voci di log del monitoraggio di integrità in un argomento Cloud Pub/Sub

Utilizza Logging per esportare tutte le voci di log del monitoraggio di integrità generate dalle istanze VM schermate in un argomento Cloud Pub/Sub. Utilizza questo argomento come origine dati per un trigger di Cloud Functions al fine di automatizzare le risposte agli eventi di monitoraggio dell'integrità.

  1. Vai a Stackdriver Logging
  2. Fai clic sulla freccia menu a discesa sul lato destro di Filtra per etichetta o testo, quindi fai clic su Converti in filtro avanzato.
  3. Digita il seguente filtro avanzato:

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

    sostituendo YOUR_PROJECT_ID con l'ID del tuo progetto.

  4. Fai clic su Invia filtro.

  5. Fai clic su Crea esportazione.

  6. Per Nome sink, digita integrity-monitoring.

  7. Per il Servizio sink, seleziona Cloud Pub/Sub.

  8. Fai clic sulla freccia menu a discesa sul lato destro di Destinazione sink, quindi fai clic su Crea nuovo argomento Cloud Pub/Sub.

  9. Per Nome, digita integrity-monitoring e quindi fai clic su Crea.

  10. Fai clic su Crea sink.

Creazione di un trigger di Cloud Functions per rispondere agli errori di integrità

Crea un trigger di Cloud Functions che legge i dati nell'argomento Cloud Pub/Sub e interrompe qualsiasi istanza VM schermata che non rientra nella convalida di integrità.

  1. Il codice seguente definisce il trigger di Cloud Functions. Copialo 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))
    
  2. Nella stessa posizione di main.py, crea un file denominato requirements.txt e copia nelle seguenti dipendenze:

    google-api-python-client==1.6.6
    google-auth==1.4.1
    google-auth-httplib2==0.0.3
    
  3. Apri una finestra del terminale e vai alla directory contenente main.py e requirements.txt.

  4. Esegui il comando gcloud beta functions deploy per eseguire il deployment del trigger:

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

    sostituendo YOUR_PROJECT_ID con l'ID del tuo progetto.

Creazione di un database con misurazioni di baseline note

Crea un database Cloud Firestore per fornire una fonte di misurazioni di baseline dei criteri di integrità note. Devi aggiungere manualmente le misurazioni di baseline per mantenere aggiornato questo database.

  1. Vai alla pagina Istanze VM
  2. Fai clic sull'ID dell'istanza VM schermata per aprire la pagina dei dettagli dell'istanza VM.
  3. Sotto Log, fai clic su Stackdriver Logging.
  4. Individua la voce di log lateBootReportEvent più recente.
  5. Espandi la voce di log > jsonPayload > lateBootReportEvent > policyMeasurements.
  6. Prendi nota dei valori per gli elementi contenuti in lateBootReportEvent > policyMeasurements.
  7. Vai alla console di Cloud Firestore
  8. Scegli Avvia raccolta.
  9. Per ID raccolta, digita known_good_measurements.
  10. Per ID documento, digita baseline1.
  11. Per Nome campo, digita il valore del campo pcrNum dall'elemento 0 in lateBootReportEvent > policyMeasurements.
  12. Per Tipo di campo, seleziona mappa.
  13. Aggiungi tre campi stringa al campo mappa, rispettivamente denominati hashAlgo, pcrNum e value. Rendi i loro valori i valori dei campi elemento 0 in lateBootReportEvent > policyMeasurements.
  14. 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:

    Un database Cloud Firestore che mostra una raccolta known_good_measurements completata.

Aggiornamento del trigger di Cloud Functions per apprendere la baseline nota

  1. Il codice seguente crea un trigger di Cloud Functions che consente a un'eventuale istanza VM schermata che non rientra nella convalida dell'integrità di apprendere la nuova baseline se questa si trova nel database delle misurazioni note oppure di arrestarsi. 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 = '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. Copia le seguenti dipendenze e usale per sovrascrivere il codice esistente 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. Apri una finestra del terminale e vai alla directory contenente main.py e requirements.txt.

  4. Esegui il comando gcloud beta functions deploy per eseguire il deployment del trigger:

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

    sostituendo YOUR_PROJECT_ID con l'ID del tuo progetto.