Cloud 애셋 인벤토리로 보안 비밀 분석

이 주제에서는 Cloud 애셋 인벤토리를 활용하여 Secret Manager 리소스를 분석하는 방법을 알아봅니다.

이 내용은 Secret Manager에 대한 고급 주제입니다. 이 가이드를 읽기 전에 다음 사항을 검토하는 것이 좋습니다.

개요

Secret Manager는 Google Cloud의 관리형 메타데이터 인벤토리 시스템인 Cloud 애셋 인벤토리와 통합됩니다. 이 통합을 통해 조직, 폴더 또는 프로젝트에서 보안 비밀을 식별하고 감사할 수 있으며 조직의 요구사항을 준수하지 않는 구성을 검색할 수 있습니다. 이 가이드에서는 애셋 모니터링, BigQuery로 애셋 내보내기, Secret Manager 리소스에 대한 Cloud 애셋 인벤토리 쿼리 샘플을 다룹니다.

참고

  • 모든 쿼리에 Google Cloud CLI 및 BigQuery로 작성된 샘플이 있지만 보안 비밀 및 보안 비밀 버전을 BigQuery로 내보내는 것이 좋습니다. 애셋을 BigQuery로 내보내면 SQL과 유사한 쿼리를 작성하여 의미 있는 분석을 생성하고 저장할 수 있습니다.
  • Secret Manager는 애셋 검색 또는 정책 분석자와 통합되지 않습니다. 아래 쿼리는 Google Cloud CLI 명령줄 도구와 BigQuery의 기본 속성을 활용하여 애셋을 검색합니다.
  • Cloud 애셋 인벤토리는 지난 5주 동안만의 스냅샷 내보내기 및 나열을 지원합니다.

애셋 변경사항 모니터링

Cloud 애셋 인벤토리는 실시간 업데이트를 추적하고 이러한 변경사항 모니터링을 지원합니다. 피드를 구성하여 리소스가 수정될 때마다 구성된 Pub/Sub 주제 모음에 알림을 보낼 수 있습니다. 또한 Cloud 애셋 인벤토리는 피드의 조건 구성을 지원하므로 특정 애셋 유형의 특정 변경사항을 모니터링할 수 있습니다. 애셋 변경에 대한 워크플로를 트리거하는 방법은 Pub/Sub 문서를 참조하세요.

BigQuery로 애셋 내보내기

보안 비밀과 보안 비밀 버전을 BigQuery로 내보내면 대규모 데이터에 대해 SQL과 유사한 쿼리를 실행하고 애셋에 대한 의미 있는 정보를 생성할 수 있습니다. 애셋을 내보내기 전에 데이터 세트 및 서비스 계정이 올바르게 구성되었는지 확인하세요. 애셋을 내보내려면 다음 명령어를 실행합니다.

gcloud

$ gcloud asset export \
   --content-type CONTENT_TYPE \
   --project PROJECT_ID \
   --snapshot-time SNAPSHOT_TIME \
   --bigquery-table BIGQUERY_TABLE \
   --output-bigquery-force

각 항목의 의미는 다음과 같습니다.

  • CONTENT_TYPE: 애셋 콘텐츠 유형(RESOURCE)입니다.
  • PROJECT_ID: 모니터링할 애셋이 포함된 프로젝트 ID입니다.
  • SNAPSHOT_TIME: 리소스의 스냅샷을 만들 시간이며, 현재와 지난 5주 이내입니다.
  • BIGQUERY_TABLE: projects/PROJECT_ID/datasets/DATASET_ID/tables/TABLE_NAME 형식으로 데이터를 내보낼 테이블입니다.

자세한 내용은 BigQuery로 내보내기를 참조하세요.

샘플 쿼리

지난 2주 동안 생성된 보안 비밀

지난 2주 동안 조직에 추가된 보안 비밀과 그 속성을 검색합니다.

BigQuery

SELECT name, FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/Secret' AND
DATE(JSON_VALUE(resource.data, '$.createTime')) > DATE_SUB(CURRENT_DATE(), INTERVAL 2 WEEK);

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.createTime>-P2W"

자동으로 복제된 보안 비밀

자동으로 복제된 모든 보안 비밀을 찾습니다.

BigQuery

SELECT * FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/Secret' AND
JSON_EXTRACT(resource.data, '$.replication.automatic') IS NOT NULL;

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.replication.automatic != NULL"

지정된 리전에 복제된 보안 비밀

us-central1에 복제된 모든 보안 비밀을 찾습니다.

BigQuery

SELECT * FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE
(
  SELECT * FROM
  UNNEST(JSON_EXTRACT_ARRAY(resource.data, '$.replication.userManaged.replicas')) AS location
  WHERE JSON_VALUE(JSON_EXTRACT(location, '$.location')) = "us-central1"
)
IS NOT NULL;

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.replication.userManaged.replicas.location=us-central1"

180일 이상 사용 설정된 보안 비밀 버전

180일 이전에 생성된 모든 보안 비밀 버전을 나열합니다.

BigQuery

SELECT * FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/SecretVersion' AND
DATE(JSON_VALUE(resource.data, '$.createTime')) < DATE_SUB(CURRENT_DATE(), INTERVAL 180 DAY) AND
JSON_VALUE(resource.data, '$.state') = "ENABLED";

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/SecretVersion' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.createTime < P6M AND resource.data.state=ENABLED"

CMEK가 구성되지 않은 보안 비밀

고객 관리자 암호화 키(CMEK)로 암호화되지 않은 모든 보안 비밀(자동 및 사용자 관리)을 나열합니다.

BigQuery

SELECT * FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/Secret'
  AND (
    JSON_VALUE(resource.data, "$.replication.automatic.customerManagedEncryption.kmsKeyName") IS NULL
    AND JSON_VALUE(resource.data, "$.replication.userManaged.replicas[0].customerManagedEncryption.kmsKeyName") IS NULL
  );

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.replication.userManaged.replicas.customerManagedEncryption = NULL OR resource.data.replication.automatic.customerManagedEncryption=NULL"

CMEK가 구성된 보안 비밀

CMEK로 암호화된 모든 보안 비밀(자동 및 사용자 관리)을 나열합니다.

BigQuery

SELECT * FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/Secret'
AND (
  JSON_VALUE(resource.data, "$.replication.automatic.customerManagedEncryption.kmsKeyName") IS NOT NULL
  OR JSON_VALUE(resource.data, "$.replication.userManaged.replicas[0].customerManagedEncryption.kmsKeyName") IS NOT NULL
);

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.replication.userManaged.replicas.customerManagedEncryption != NULL OR resource.data.replication.automatic.customerManagedEncryption!=NULL"

특정 CMEK로 암호화된 보안 비밀

특정 CMEK로 암호화된 보안 비밀 버전인 보안 비밀을 찾습니다.

BigQuery

SELECT * FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/Secret'
  AND (
    JSON_VALUE(resource.data, "$.replication.automatic.customerManagedEncryption.kmsKeyName") = KMS_KEY_NAME
    OR JSON_VALUE(resource.data, "$.replication.userManaged.replicas[0].customerManagedEncryption.kmsKeyName") = KMS_KEY_NAME
  );

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.replication.userManaged.replicas.customerManagedEncryption.kmsKeyName=KMS_KEY_NAME"

CMEK가 구성되지 않은 보안 비밀 버전

CMEK로 암호화되지 않은 사용 설정된 모든 보안 비밀 버전을 찾습니다.

BigQuery

SELECT * FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/SecretVersion'
AND (
  JSON_VALUE(resource.data, "$.replicationStatus.automatic.customerManagedEncryption.kmsKeyVersionName") IS NULL
  AND JSON_VALUE(resource.data, "$.replicationStatus.userManaged.replicas[0].customerManagedEncryption.kmsKeyVersionName") IS NULL
)
AND JSON_VALUE(resource.data, "$.state") = "ENABLED";

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/SecretVersion' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="(resource.data.replicationStatus.userManaged.replicas.customerManagedEncryption = NULL OR resource.data.replicationStatus.automatic.customerManagedEncryption=NULL) AND resource.data.state=ENABLED"

특정 CMEK로 암호화된 보안 비밀 버전

특정 CMEK 버전으로 암호화된 모든 사용 설정된 보안 비밀 버전을 나열합니다.

BigQuery

SELECT * FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/SecretVersion'
AND (
  JSON_VALUE(resource.data, "$.replicationStatus.automatic.customerManagedEncryption.kmsKeyVersionName") = KMS_KEY_VERSION_NAME
  OR JSON_VALUE(resource.data, "$.replicationStatus.userManaged.replicas[0].customerManagedEncryption.kmsKeyVersionName") = KMS_KEY_VERSION_NAME
)
AND JSON_VALUE(resource.data,"$.state")="ENABLED";

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/SecretVersion' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.replicationStatus.userManaged.replicas.customerManagedEncryption.kmsKeyVersionName=$FULL_KMS_KEY_VERSION_RESOURCE_NAME AND resource.data.status=ENABLED"

순환이 구성되지 않은 보안 비밀

순환 일정이 없는 모든 보안 비밀을 찾습니다.

BigQuery

SELECT name FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/Secret' AND
JSON_EXTRACT(resource.data, '$.rotation') IS NULL;

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.rotation=NULL"

특정 순환 기간이 있는 보안 비밀

90일 미만마다 한 번 순환하도록 예약된 모든 보안 비밀을 찾습니다.

BigQuery

SELECT *
FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE
  CAST(
    TRIM(
      JSON_VALUE(JSON_EXTRACT(resource.data, "$.rotation.rotationPeriod")),"s")
    AS INT64)
< 86400 * 90 #Rotation period in seconds (86400s in 1 day * 90 days)

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ ROTATION_PERIOD_SECONDS=$((90 * 24 * 60 * 60))
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.rotation != null AND resource.data.rotation.rotationPeriod < ${ROTATION_PERIOD_SECONDS}s"

향후 30일 이내에 만료되는 보안 비밀

향후 30일 이내에 만료되는 보안 비밀을 나열합니다.

BigQuery

SELECT * FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/Secret' AND
DATE(JSON_VALUE(resource.data, '$.expireTime')) < DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY);

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.expireTime < PD30"

Pub/Sub 주제가 구성된 보안 비밀

Pub/Sub 주제가 하나 이상 구성된 모든 보안 비밀을 나열합니다.

BigQuery

SELECT name, ARRAY_LENGTH(JSON_EXTRACT_ARRAY(resource.data, '$.topics')) AS topics_count,
FROM PROJECT_ID.DATASET_ID.TABLE_NAME
WHERE asset_type='secretmanager.googleapis.com/Secret' AND
ARRAY_LENGTH(JSON_EXTRACT_ARRAY(resource.data, '$.topics')) > 0

gcloud

$ NOW=$(TZ=GMT date +"%Y-%m-%dT%H:%M:%SZ")
$ gcloud asset list --project=PROJECT_ID \
    --asset-types='secretmanager.googleapis.com/Secret' \
    --snapshot-time=$NOW \
    --content-type='resource' \
    --filter="resource.data.topics !=NULL"

다음 단계