Mengotomatiskan respons terhadap kegagalan validasi integritas

Pelajari cara menggunakan pemicu Cloud Functions untuk menindaklanjuti peristiwa Shielded VM integrity monitoring.

Ringkasan

Pemantauan integritas mengumpulkan pengukuran dari instance Shielded VM dan menampilkannya di Cloud Logging. Jika pengukuran integritas berubah di seluruh booting instance Shielded VM, validasi integritas akan gagal. Kegagalan ini dicatat sebagai peristiwa yang dicatat ke dalam log, dan juga dilaporkan di Cloud Monitoring.

Terkadang, pengukuran integritas Shielded VM berubah karena alasan yang sah. Misalnya, update sistem dapat menyebabkan perubahan yang diharapkan pada kernel sistem operasi. Karena itu, pemantauan integritas memungkinkan Anda meminta instance Shielded VM untuk mempelajari dasar pengukuran kebijakan integritas baru jika terjadi kegagalan validasi integritas yang diharapkan.

Dalam tutorial ini, pertama-tama Anda akan membuat sistem otomatis sederhana yang menonaktifkan instance VM yang Terlindungi yang gagal dalam validasi integritas:

  1. Ekspor semua peristiwa pemantauan integritas ke topik Pub/Sub.
  2. Buat pemicu Cloud Functions yang menggunakan peristiwa dalam topik tersebut untuk mengidentifikasi dan menonaktifkan instance Shielded VM yang gagal dalam validasi integritas.

Selanjutnya, Anda dapat memperluas sistem secara opsional agar meminta instance Shielded VM yang gagal dalam validasi integritas untuk mempelajari dasar pengukuran baru jika cocok dengan pengukuran bagus yang diketahui, atau untuk dimatikan jika tidak:

  1. Buat database Firestore untuk mempertahankan sekumpulan pengukuran dasar integritas yang baik dan diketahui.
  2. Update pemicu Cloud Functions sehingga memicu instance Shielded VM yang gagal dalam validasi integritas untuk mempelajari baseline baru jika ada di dalam database, atau jika ingin dimatikan.

Jika Anda memilih untuk menerapkan solusi yang diperluas, gunakan dengan cara berikut:

  1. Setiap kali ada update yang diperkirakan akan menyebabkan kegagalan validasi karena alasan yang sah, jalankan update tersebut pada satu instance VM yang Disembunyikan dalam grup instance.
  2. Dengan menggunakan peristiwa booting terlambat dari instance VM yang diupdate sebagai sumber, tambahkan pengukuran dasar pengukuran kebijakan baru ke database dengan membuat dokumen baru di koleksi known_good_measurements. Lihat Membuat database pengukuran dasar pengukuran yang diketahui berkualitas untuk informasi selengkapnya.
  3. Update instance Shielded VM yang tersisa. Pemicu meminta instance yang tersisa untuk mempelajari baseline baru karena dapat diverifikasi sebagai hasilnya yang diketahui. Lihat Memperbarui pemicu Cloud Functions untuk mempelajari dasar pengukuran bagus yang diketahui untuk mengetahui informasi selengkapnya.

Prasyarat

  • Gunakan project yang memiliki Firestore dalam mode Native yang dipilih sebagai layanan database. Anda membuat pilihan ini saat membuat project, dan tidak dapat diubah. Jika project Anda tidak menggunakan Firestore dalam mode Native, Anda akan melihat pesan "Project ini menggunakan layanan database lain" saat membuka konsol Firestore.
  • Memiliki instance VM Compute Engine Shielded dalam project tersebut untuk berfungsi sebagai sumber pengukuran dasar pengukuran integritas. Instance Shielded VM harus sudah dimulai ulang setidaknya sekali.
  • Sudah menginstal alat command line gcloud.
  • Aktifkan Cloud Logging dan Cloud Functions API dengan mengikuti langkah-langkah berikut:

    1. Di konsol Google Cloud, buka halaman APIs & Services.

      Buka API & Layanan

    2. Lihat apakah Cloud Functions API dan Stackdriver Logging API muncul di daftar Enabled APIs and services.

    3. Jika salah satu API tidak muncul, klik Add APIs and Services.

    4. Telusuri dan aktifkan API, sesuai kebutuhan.

Mengekspor entri log pemantauan integritas ke topik Pub/Sub

Gunakan Logging untuk mengekspor semua entri log pemantauan integritas yang dihasilkan oleh instance Shielded VM ke topik Pub/Sub. Anda menggunakan topik ini sebagai sumber data untuk pemicu Cloud Functions guna mengotomatiskan respons terhadap peristiwa pemantauan integritas.

Logs Explorer

  1. Di Konsol Google Cloud, buka halaman Logs Explorer.

    Buka Cloud Logging

  2. Di Query Builder, masukkan nilai berikut.

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

  3. Klik Run Filter.

  4. Klik More actions, kemudian pilih Create sink.

  5. Di halaman Create logs routing sink:

    1. Di Sink details, untuk Sink Name, masukkan integrity-monitoring, lalu klik Next.
    2. Di Sink destination, luaskan Sink Service, lalu pilih Cloud Pub/Sub.
    3. Luaskan Select a Cloud Pub/Sub topic, lalu pilih Create a topic.
    4. Dalam dialog Create a topic, untuk Topic ID, masukkan integrity-monitoring, lalu klik Create topic.
    5. Klik Next, lalu klik Create sink.

Logs Explorer

  1. Di Konsol Google Cloud, buka halaman Logs Explorer.

    Buka Cloud Logging

  2. Klik Options, lalu pilih Go back to Legacy Logs Explorer.

  3. Luaskan Filter menurut label atau penelusuran teks, lalu klik Convert to advanced filter.

  4. Masukkan filter lanjutan berikut:

    resource.type="gce_instance"
    AND logName:  "projects/YOUR_PROJECT_ID/logs/compute.googleapis.com%2Fshielded_vm_integrity"
    
    Perhatikan bahwa ada dua spasi setelah logName:.

  5. Klik Kirim Filter.

  6. Klik Buat Ekspor.

  7. Untuk Sink Name, masukkan integrity-monitoring.

  8. Untuk Sink Service, pilih Cloud Pub/Sub.

  9. Luaskan Sink Destination, lalu klik Create new Cloud Pub/Sub topic.

  10. Untuk Name, masukkan integrity-monitoring lalu klik Create.

  11. Klik Create Sink.

Membuat pemicu Cloud Functions untuk merespons kegagalan integritas

Buat pemicu Cloud Functions yang membaca data dalam topik Pub/Sub dan yang menghentikan instance Shielded VM yang gagal dalam validasi integritas.

  1. Kode berikut menentukan pemicu Cloud Functions. Salin ke dalam file bernama 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. Di lokasi yang sama dengan main.py, buat file bernama requirements.txt lalu salin dalam dependensi berikut:

    google-api-python-client==1.6.6
    google-auth==1.4.1
    google-auth-httplib2==0.0.3
    
  3. Buka jendela terminal dan buka direktori yang berisi main.py dan requirements.txt.

  4. Jalankan perintah gcloud beta functions deploy untuk men-deploy pemicu:

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

Membuat database untuk pengukuran dasar pengukuran yang diketahui

Buat database Firestore untuk menyediakan sumber pengukuran dasar pengukuran kebijakan integritas yang baik dan diketahui. Anda harus menambahkan pengukuran dasar pengukuran secara manual agar database ini selalu terbaru.

  1. Di konsol Google Cloud, buka halaman VM instances.

    Buka halaman VM instances

  2. Klik ID instance Shielded VM untuk membuka halaman VM instance details.

  3. Di bagian Logs, klik Stackdriver Logging.

  4. Cari entri log lateBootReportEvent terbaru.

  5. Luaskan entri log > jsonPayload > lateBootReportEvent > policyMeasurements.

  6. Perhatikan nilai untuk elemen yang terdapat dalam lateBootReportEvent > policyMeasurements.

  7. Di Konsol Google Cloud, buka halaman Firestore.

    Buka konsol Firestore

  8. Pilih Start collection.

  9. Untuk Collection ID, ketikkan known_good_measurements.

  10. Untuk Document ID, ketik baseline1.

  11. Untuk Field name, ketik nilai kolom pcrNum dari elemen 0 di lateBootReportEvent > policyMeasurements.

  12. Untuk Field type, pilih map.

  13. Tambahkan tiga kolom string ke kolom peta, yang bernama hashAlgo, pcrNum, dan value. Jadikan nilainya sebagai nilai kolom elemen 0 di lateBootReportEvent > policyMeasurements.

  14. Buat lebih banyak kolom peta, satu untuk setiap elemen tambahan di lateBootReportEvent > policyMeasurements. Beri mereka subkolom yang sama seperti kolom peta pertama. Nilai untuk subkolom tersebut harus dipetakan ke nilai di setiap elemen tambahan.

    Misalnya, jika Anda menggunakan VM Linux, koleksinya akan terlihat mirip seperti berikut ini setelah Anda selesai:

    Database Firestore yang menampilkan koleksi known_good_measurements yang telah selesai untuk Linux.

    Jika menggunakan VM Windows, Anda akan melihat lebih banyak pengukuran sehingga koleksi akan terlihat seperti berikut:

    Database Firestore yang menampilkan koleksi known_good_measurements yang telah selesai untuk Windows.

Memperbarui pemicu Cloud Functions untuk mempelajari baseline yang sudah dikenali dengan baik

  1. Kode berikut membuat pemicu Cloud Functions yang menyebabkan instance Shielded VM yang gagal melakukan validasi integritas dapat mempelajari baseline baru jika berada dalam database pengukuran baik yang dikenali, jika tidak, instance akan dinonaktifkan. Salin kode ini dan gunakan untuk menimpa kode yang sudah ada di 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. Salin dependensi berikut, lalu gunakan untuk menimpa kode yang sudah ada di 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. Buka jendela terminal dan buka direktori yang berisi main.py dan requirements.txt.

  4. Jalankan perintah gcloud beta functions deploy untuk men-deploy pemicu:

    gcloud beta functions deploy relearn_if_known_good --project PROJECT_ID \
        --runtime python37 --trigger-resource integrity-monitoring \
        --trigger-event google.pubsub.topic.publish
    
  5. Hapus fungsi shutdown_vm sebelumnya secara manual di cloud function console.

  6. Di konsol Google Cloud, buka halaman Cloud Functions.

    Buka Cloud Functions

  7. Pilih fungsi shutdown_vm, lalu klik hapus.

Memverifikasi respons otomatis terhadap kegagalan validasi integritas

  1. Pertama, periksa apakah Anda memiliki instance yang berjalan dengan mengaktifkan Booting Aman sebagai opsi Shielded VM. Jika tidak, Anda dapat membuat instance baru dengan Shielded VM image (Ubuntu 18.04LTS) dan mengaktifkan opsi Secure Boot. Anda mungkin akan dikenai biaya beberapa sen untuk instance ini (langkah ini dapat selesai dalam waktu satu jam).
  2. Sekarang, asumsikan untuk beberapa alasan, Anda ingin mengupgrade kernel secara manual.
  3. Terapkan SSH ke instance, dan gunakan perintah berikut untuk memeriksa kernel saat ini.

    uname -sr
    

    Anda akan melihat yang seperti Linux 4.15.0-1028-gcp.

  4. Download kernel generik dari https://kernel.ubuntu.com/~kernel-ppa/mainline/

  5. Gunakan perintah untuk menginstal.

    sudo dpkg -i *.deb
    
  6. Mulai ulang VM.

  7. Anda akan melihat VM tidak booting (tidak dapat menjalankan SSH ke dalam mesin). Inilah yang kami harapkan, karena tanda tangan kernel baru tidak ada dalam daftar yang diizinkan Booting Aman. Contoh ini juga menunjukkan cara Booting Aman dapat mencegah modifikasi kernel yang tidak sah/berbahaya.

  8. Namun, karena saat ini upgrade kernel tidak berbahaya dan memang dilakukan sendiri, kita dapat menonaktifkan Secure Boot untuk mem-booting kernel baru.

  9. Matikan VM dan hapus centang pada opsi Booting Aman, lalu mulai ulang VM.

  10. Booting mesin semestinya akan gagal lagi. Namun, kali ini fungsi tersebut dimatikan secara otomatis oleh cloud function yang kita buat sebagai opsi Secure Boot telah diubah (juga karena image kernel yang baru), dan keduanya menyebabkan pengukuran berbeda dari garis dasar. (Kita dapat memeriksa hal tersebut di log Stackdriver pada fungsi cloud.)

  11. Karena kita tahu ini bukan modifikasi berbahaya dan akar masalahnya, kita dapat menambahkan pengukuran saat ini di lateBootReportEvent ke tabel Firebase pengukuran baik yang diketahui. (Ingat ada dua hal yang diubah: 1. Opsi Booting Aman 2. Image Kernel.)

    Ikuti langkah sebelumnya Membuat database pengukuran dasar pengukuran yang diketahui bernilai baik untuk menambahkan dasar pengukuran baru ke database Firestore menggunakan pengukuran sebenarnya di lateBootReportEvent terbaru.

    Database Firestore yang menampilkan koleksi known_good_measurements yang baru dan telah selesai.

  12. Sekarang reboot komputernya. Saat memeriksa log Stackdriver, Anda akan melihat lateBootReportEvent masih menampilkan nilai salah, tetapi sekarang mesin akan berhasil di-booting, karena cloud function mempercayai dan mempelajari ulang ukuran yang baru. Kita dapat memverifikasinya dengan memeriksa Stackdriver dari fungsi cloud.

  13. Dengan menonaktifkan Secure Boot, sekarang kita dapat melakukan booting ke kernel. Terapkan SSH ke komputer dan periksa kernel lagi, Anda akan melihat versi kernel baru.

    uname -sr
    
  14. Terakhir, mari bersihkan sumber daya dan data yang digunakan pada langkah ini.

  15. Matikan VM jika Anda membuat VM untuk langkah ini guna menghindari biaya tambahan.

  16. Di konsol Google Cloud, buka halaman VM instances.

    Buka halaman VM instances

  17. Hapus pengukuran baik yang diketahui yang Anda tambahkan pada langkah ini.

  18. Di Konsol Google Cloud, buka halaman Firestore.

    Buka halaman Firestore