Automatizar respuestas a fallos de validación de integridad

Descubre cómo utilizar los activadores de Cloud Functions para responder automáticamente a eventos de supervisión de integridad de VM blindadas.

Descripción general

En el proceso de supervisión de integridad se recopilan mediciones de instancias de máquinas virtuales (VM) blindadas y se muestran en Stackdriver Logging. Si las mediciones de integridad cambian cada vez que se inicia una instancia de VM blindada, la validación de integridad falla. Este error se captura como un evento registrado y se muestra en Stackdriver Monitoring.

A veces, las mediciones de integridad de VM blindadas cambian por un motivo legítimo. Por ejemplo, en una actualización del sistema pueden esperarse algunos cambios en el kernel del sistema operativo. Por esta razón, la supervisión de integridad te permite solicitar a una instancia de VM blindada que obtenga un nuevo valor de referencia de la política de integridad en caso de que se prevea que no se superará la validación de integridad.

En este tutorial, primero crearás un sistema automatizado simple que apagará las instancias de VM blindadas que no superen la validación de integridad:

  1. Exporta todos los eventos de la supervisión de integridad a un tema de Cloud Pub/Sub.
  2. Crea un activador de Cloud Functions que use los eventos de ese tema para identificar y apagar las instancias de VM blindadas que no superen la validación de integridad.

A continuación, si lo deseas, puedes ampliar el sistema para solicitar a las instancias de VM blindadas que no superen la validación de integridad que obtengan el nuevo valor de referencia si este coincide con una medición correcta o que, en caso contrario, se apaguen:

  1. Crea una base de datos de Cloud Firestore para mantener un conjunto de mediciones de valores de referencia de integridad correctas.
  2. Actualiza el activador de Cloud Functions para solicitar a las instancias de VM blindadas que no superen la validación de integridad que obtengan el nuevo valor de referencia si este se encuentra en la base de datos o que, en caso contrario, se apaguen.

Si optas por implementar la solución ampliada, úsala de la siguiente manera:

  1. Cada vez se prevea que una actualización causará un error de validación por un motivo legítimo, ejecuta dicha actualización en una única instancia de VM blindada del grupo de instancias.
  2. Crea un documento en la colección known_good_measurements con el evento de arranque tardío de la instancia de máquina virtual actualizada como fuente para añadir las nuevas mediciones de valores de referencia de la política a la base de datos. Para obtener más información, consulta Crear una base de datos de mediciones de valores de referencia correctas.
  3. Actualiza el resto de las instancias de VM blindadas. El activador solicita al resto de las instancias que obtengan el nuevo valor de referencia, ya que puede verificarse que es correcto. Para obtener más información, consulta Actualizar el activador de Cloud Functions para obtener un valor de referencia correcto.

Requisitos

  • Utiliza un proyecto que tenga Cloud Firestore en modo nativo como servicio de base de datos. Esta selección se realiza al crear el proyecto y no se puede cambiar. Si tu proyecto no utiliza Cloud Firestore en modo nativo, aparecerá el mensaje "Este proyecto usa otro servicio de bases de datos" al abrir la consola de Cloud Firestore.
  • Ten una instancia de VM blindada de Compute Engine en ese proyecto para utilizarla como origen de las mediciones de valores de referencia de integridad. La instancia de VM blindada debe haberse reiniciado al menos una vez.
  • Instala la herramienta de línea de comandos gcloud.
  • Habilita las API Stackdriver Logging y Cloud Functions de la siguiente manera:

    1. Ve a APIs y servicios.
    2. Comprueba si la API Cloud Functions y la API Stackdriver Logging aparecen en la lista API y servicios habilitados.
    3. Si alguna de las API no aparece, haz clic en Add APIs and Services.
    4. Busca y habilita las API si es necesario.

Exportar entradas de registro de la supervisión de integridad a un tema de Cloud Pub/Sub

Usa Logging para exportar todas las entradas de registro de la supervisión de integridad que han generado las instancias de VM blindadas a un tema de Cloud Pub/Sub. Este tema se utiliza como fuente de datos de un activador de Cloud Functions para automatizar las respuestas a eventos de la supervisión de integridad.

  1. Ve a Stackdriver Logging.
  2. Haz clic en la flecha de menú desplegable situada a la derecha de Filtrar por etiqueta o buscar texto y, luego, en Convertir a filtro avanzado.
  3. Introduce el siguiente filtro avanzado:

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

    Sustituye YOUR_PROJECT_ID por el ID del proyecto.

  4. Haz clic en Enviar filtro.

  5. Haz clic en Crear exportación.

  6. En Nombre del sumidero, introduce integrity-monitoring.

  7. En Servicio del sumidero, selecciona Cloud Pub/Sub.

  8. Haz clic en la flecha de menú desplegable situada a la derecha de Destino del sumidero y, a continuación, en Create new Cloud Pub/Sub topic.

  9. En Nombre, introduce integrity-monitoring y haz clic en Crear.

  10. Haz clic en Crear sumidero.

Crear un activador de Cloud Functions para responder a errores de integridad

Crea un activador de Cloud Functions que lea los datos del tema de Cloud Pub/Sub y que detenga cualquier instancia de VM blindada que no supere la validación de integridad.

  1. El siguiente código define el activador de Cloud Functions. Cópialo en un archivo con el nombre 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. En la misma ubicación que main.py, crea un archivo con el nombre requirements.txt y cópialo en las siguientes dependencias:

    google-api-python-client==1.6.6
    google-auth==1.4.1
    google-auth-httplib2==0.0.3
    
  3. Abre una ventana de terminal y ve al directorio que contiene main.py y requirements.txt.

  4. Ejecuta el comando gcloud beta functions deploy para desplegar el activador:

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

    Sustituye YOUR_PROJECT_ID por el ID del proyecto.

Crear una base de datos de mediciones de valores de referencia correctas

Crea una base de datos de Cloud Firestore para proporcionar una fuente de mediciones de valores de referencia de integridad correctas. Para mantener actualizada esta base de datos, debes añadir las mediciones de valores de referencia de forma manual.

  1. Ve a la página Instancias de VM.
  2. Haz clic en el ID de la instancia de VM blindada para abrir la página Detalles de la instancia de VM.
  3. En Registros, haz clic en Stackdriver Logging.
  4. Busca la entrada de registro lateBootReportEvent más reciente.
  5. Despliega la entrada de registro > jsonPayload > lateBootReportEvent > policyMeasurements.
  6. Anota los valores de los elementos que se incluyen en lateBootReportEvent > policyMeasurements.
  7. Ve a la consola de Cloud Firestore.
  8. Selecciona Iniciar colección.
  9. En ID de colección, introduce known_good_measurements.
  10. En ID de documento, introduce baseline1.
  11. En Nombre del campo, introduce el valor del campo pcrNum del elemento 0 de lateBootReportEvent > policyMeasurements.
  12. En Tipo de campo, selecciona mapa.
  13. Añade tres campos de cadena al campo de mapa con los nombres hashAlgo, pcrNum y value, respectivamente. Asígnales los valores de los campos del elemento 0 en lateBootReportEvent > policyMeasurements.
  14. Crea más campos de mapas, uno para cada elemento adicional de lateBootReportEvent > policyMeasurements. Asígnales los mismos subcampos que al primer campo de mapa. Los valores de esos subcampos deben asignarse a los valores respectivos de cada uno de los elementos adicionales.

    Por ejemplo, si utilizas una máquina virtual de Linux, cuando termines, la colección debería verse como en la siguiente imagen:

    Una base de datos de Cloud Firestore que muestra una colección known_good_measurements completada

Actualizar el activador de Cloud Functions para obtener un valor de referencia correcto

  1. El siguiente código crea un activador de Cloud Functions que solicita a cualquier instancia de VM blindada que no supere la validación de integridad que obtenga el nuevo valor de referencia si este se encuentra en la base de datos de mediciones correctas o que, en caso contrario, se apague. Copia este código y utilízalo para sobrescribir el código actual en 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 las siguientes dependencias y utilízalas para sobrescribir el código actual en 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. Abre una ventana de terminal y ve al directorio que contiene main.py y requirements.txt.

  4. Ejecuta el comando gcloud beta functions deploy para desplegar el activador:

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

    Sustituye YOUR_PROJECT_ID por el ID del proyecto.

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...