무결성 확인 실패에 대한 응답 자동화

Cloud Functions 트리거를 사용하여 보안 설정된 VM 무결성 모니터링 이벤트에 대해 자동으로 조치를 취하는 방법을 알아봅니다.

개요

무결성 모니터링 기능은 보안 설정된 VM 인스턴스에서 측정 데이터를 수집하여 Stackdriver Logging에 보여줍니다. 보안 설정된 VM 인스턴스가 여러 차례 부팅되는 과정에서 무결성 측정 데이터가 변경되는 경우, 무결성 확인이 실패합니다. 이러한 실패는 로깅되는 이벤트로 캡처되고 Stackdriver Monitoring에서도 제기됩니다.

때로는 보안 설정된 VM 무결성 측정 데이터가 타당한 이유로 변경되기도 합니다. 예를 들어, 시스템을 업데이트하면 운영체제 커널에 예상되는 변화가 발생할 수 있습니다. 이러한 이유로 인해 무결성 모니터링을 통해 보안 설정된 VM 인스턴스에 예상한 무결성 확인이 실패할 경우, 새로운 무결성 정책 기준을 학습하도록 지시할 수 있습니다.

이 가이드에서는 먼저 무결성 확인에 실패하는 보안 설정된 VM 인스턴스를 종료하는 간단한 자동 시스템을 만듭니다.

  1. 모든 무결성 모니터링 이벤트를 Cloud Pub/Sub 주제로 내보냅니다.
  2. 해당 항목의 이벤트를 사용하여 무결성 확인에 실패한 보안 설정된 VM 인스턴스를 식별하고 종료하는 Cloud Functions 트리거를 만듭니다.

그런 다음, 무결성 확인에 실패한 보안 설정된 VM 인스턴스에 새 기준이 유효한 것으로 알려진 측정과 일치하는 경우에는 해당 기준을 학습하고 그렇지 않으면 종료하라는 지시를 하도록 시스템을 선택적으로 확장할 수 있습니다.

  1. 유효한 것으로 알려진 무결성 기준 측정 집합을 유지하기 위한 Cloud Firestore 데이터베이스를 만듭니다.
  2. 무결성 확인에 실패한 보안 설정된 VM 인스턴스에 새 기준이 데이터베이스에 있으면 해당 기준을 학습하고 그렇지 않으면 종료하라는 지시를 하도록 Cloud Functions 트리거를 업데이트합니다.

확장된 솔루션을 구현할 경우에는 다음 방법으로 사용하세요.

  1. 타당한 이유로 확인이 실패할 것으로 예상되는 업데이트가 있을 때마다 인스턴스 그룹에 있는 보안 설정된 단일 VM 인스턴스에서 해당 업데이트를 실행합니다.
  2. 업데이트된 VM 인스턴스에서 후기 부팅 이벤트를 소스로 사용하여 known_good_measurements 모음에 새 문서를 만들어 새 정책 기준 측정을 데이터베이스에 추가합니다. 자세한 내용은 유효한 것으로 알려진 기준 측정 데이터베이스 만들기를 참조하세요.
  3. 나머지 보안 설정된 VM 인스턴스를 업데이트합니다. 새 기준이 유효한 것으로 알려져 있다고 확인할 수 있으므로, 이 트리거는 나머지 인스턴스에 새 기준을 학습하도록 지시합니다.

기본 요건

  • 네이티브 모드의 Cloud Firestore가 데이터베이스 서비스로 선택되어 있는 프로젝트를 사용합니다. 프로젝트 생성 시 이를 선택하면 변경할 수 없습니다. 프로젝트에서 네이티브 모드의 Cloud Firestore를 사용하지 않을 경우, Cloud Firestore 콘솔을 열 때 '이 프로젝트에서는 다른 데이터베이스 서비스를 사용합니다.'라는 메시지가 나타납니다.
  • 해당 프로젝트에서 Compute Engine 보안 설정된 VM 인스턴스가 무결성 기준 측정의 소스 역할을 하도록 합니다. 보안 설정된 VM 인스턴스가 최소 한 번 이상 다시 시작되었음이 틀림없습니다.
  • gcloud 명령줄 도구를 설치했습니다.
  • 다음 단계를 따라 Stackdriver Logging과 Cloud Functions API를 사용 설정합니다.

    1. API 및 서비스로 이동합니다.
    2. Cloud Functions APIStackdriver Logging API사용 설정된 API 및 서비스 목록에 나타나는지 확인합니다.
    3. API 중 어느 하나가 나타나지 않으면 API 및 서비스 추가를 클릭합니다.
    4. 필요에 따라 API를 검색하여 사용 설정합니다.

무결성 모니터링 로그 항목을 Cloud Pub/Sub 항목으로 내보내기

Logging을 사용하여 보안 설정된 VM 인스턴스에서 생성된 모든 무결성 모니터링 로그 항목을 Cloud Pub/Sub 주제로 내보냅니다. 이 주제를 Cloud Functions 트리거에 대한 데이터 소스로 사용하여 무결성 모니터링 이벤트에 대한 응답을 자동화합니다.

  1. Stackdriver Logging으로 이동합니다.
  2. 라벨 또는 텍스트 검색 기준 필터링의 오른쪽에 있는 드롭다운 화살표를 클릭한 후 고급 필터로 전환을 클릭합니다.
  3. 다음 고급 필터를 입력합니다.

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

    YOUR_PROJECT_NAME을 프로젝트 이름으로 바꿉니다.

  4. 필터 제출을 클릭합니다.

  5. 내보내기 만들기를 클릭합니다.

  6. 싱크 이름integrity-monitoring을 입력합니다.

  7. 싱크 서비스의 경우 Cloud Pub/Sub를 선택합니다.

  8. 싱크 대상 위치의 오른쪽에 있는 드롭다운 화살표를 클릭한 다음, 새 Cloud Pub/Sub 항목 만들기를 클릭합니다.

  9. 이름integrity-monitoring을 입력한 다음 만들기를 클릭합니다.

  10. 싱크 만들기를 클릭합니다.

무결성 실패에 응답하는 Cloud Functions 트리거 만들기

Cloud Pub/Sub 항목에 있는 데이터를 읽고 무결성 확인에 실패한 보안 설정된 VM 인스턴스를 모두 중지하는 Cloud Functions 트리거를 만듭니다.

  1. 다음 코드는 Cloud Functions 트리거를 정의합니다. 이 트리거를 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'))
    
        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. main.py와 같은 위치에 requirements.txt로 명명된 파일을 만들어 다음 종속 항목에 복사합니다.

    google-api-python-client==1.6.6
    google-auth==1.4.1
    google-auth-httplib2==0.0.3
    
  3. 터미널 창을 열고 main.pyrequirements.txt를 포함한 디렉토리로 이동합니다.

  4. gcloud beta functions deploy 명령어를 실행하여 트리거를 배포합니다.

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

    YOUR_PROJECT_NAME을 프로젝트 이름으로 바꿉니다.

유효한 것으로 알려진 기준 측정의 데이터베이스 만들기

유효한 것으로 알려진 무결성 정책 기준 측정의 소스를 제공하기 위한 Cloud Firestore 데이터베이스를 만듭니다. 이 데이터베이스를 최신 상태로 유지하기 위해 기준 측정 데이터를 수동으로 추가해야 합니다.

  1. VM 인스턴스 페이지로 이동합니다.
  2. 보안 설정된 VM 인스턴스 ID를 클릭하여 VM 인스턴스 세부정보 페이지를 엽니다.
  3. 로그에서 Stackdriver Logging을 클릭합니다.
  4. 가장 최근의 lateBootReportEvent 로그 항목을 찾습니다.
  5. 로그 항목 > jsonPayload > lateBootReportEvent > policyMeasurements를 펼칩니다.
  6. lateBootReportEvent > policyMeasurements에 들어 있는 요소의 값을 기록합니다.
  7. Cloud Firestore 콘솔로 이동합니다.
  8. 컬렉션 시작을 선택합니다.
  9. 컬렉션 IDknown_good_measurements를 입력합니다.
  10. 문서 IDbaseline1을 입력합니다.
  11. 필드 이름lateBootReportEvent > policyMeasurements에 있는 요소 0pcrNum 필드 값을 입력합니다.
  12. 필드 유형에서 을 선택합니다.
  13. 맵 필드에 각각 hashAlgo, pcrNum, value로 명명된 3개의 문자열 필드를 추가합니다. 이들의 값을 lateBootReportEvent > policyMeasurements에 있는 요소 0 필드의 값으로 만듭니다.
  14. lateBootReportEvent > policyMeasurements에서 각각의 추가 요소에 대해 하나씩, 더 많은 맵 필드를 만듭니다. 이들 필드에 첫 번째 맵 필드와 똑같은 하위 필드를 제공합니다. 이러한 하위 필드의 값을 각각의 추가 요소에 있는 값과 매핑해야 합니다.

    예를 들어 Linux VM을 사용 중인 경우 이 절차를 마치면 컬렉션이 다음과 유사하게 보여야 합니다.

    완성된 known_good_measurements 컬렉션을 보여주는 Cloud Firestore 데이터베이스

Cloud Functions 트리거 업데이트

  1. 다음 코드는 무결성 확인에 실패한 보안 설정된 VM 인스턴스가 새 기준이 유효한 것으로 알려진 측정 데이터의 데이터베이스에 있으면 그 기준을 학습하고 그렇지 않으면 종료하도록 하는 Cloud Functions 트리거를 만듭니다. 이 코드를 복사하여 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', {})
        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().setShieldedVmIntegrityPolicy(
            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. 다음 종속 항목을 복사하고 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. 터미널 창을 열고 main.pyrequirements.txt를 포함한 디렉토리로 이동합니다.

  4. gcloud beta functions deploy 명령어를 실행하여 트리거를 배포합니다.

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

    YOUR_PROJECT_NAME을 프로젝트 이름으로 바꿉니다.

이 페이지가 도움이 되었나요? 평가를 부탁드립니다.

다음에 대한 의견 보내기...