Security Command Center API를 사용하여 보안 발견 항목 나열

>

Security Command Center 발견 항목은 조직 애셋의 잠재적 보안 위험을 모델링합니다. 발견 항목은 항상 Security Command Center의 특정 애셋과 관련이 있습니다.

이 가이드에서는 Security Command Center 클라이언트 라이브러리를 사용하여 조직의 발견 항목에 액세스하는 방법을 설명합니다. 각 발견 항목은 소스에 속합니다. 대부분의 감지기나 발견 항목 제공업체는 동일한 소스 내에서 발견 항목을 생성합니다.

시작하기 전에

소스를 설정하기 전에 다음을 완료해야 합니다.

페이지 크기

모든 Security Command Center 목록 API는 페이지로 나뉩니다. 각 응답은 결과 페이지와 다음 페이지를 반환하는 토큰을 반환합니다. 페이지 크기는 구성 가능합니다. 기본 pageSize는 10, 최소 1, 최대 1000으로 설정할 수 있습니다.

발견 항목 보관

발견에는 스캔 중에 관련 취약점이나 위협이 발생할 때마다 발견 항목의 상태와 속성을 캡처하는 일련의 event_time 스냅샷이 포함됩니다.

Security Command Center는 event_time에서 13개월 동안 또는 이벤트가 발생한 시간 동안 스냅샷 찾기를 저장합니다. 13개월 후에는 Security Command Center 데이터베이스에서 스냅샷과 해당 데이터가 삭제되며 복구할 수 없습니다. 이렇게 하면 발견 항목의 스냅샷 수가 줄어들어 발견 내역의 기록 및 시간이 지남에 따라 바뀌는 방식을 볼 수 있는 기능이 제한됩니다.

13개월 보다 더 최근의 event_time가 포함된 스냅샷이 하나 이상 포함된다면 발견 항목은 Security Command Center에서 유지됩니다. 발견 항목 및 모든 데이터를 더 오랜 기간 동안 보관하려면 다른 스토리지 위치로 내보냅니다. 자세한 안내는 Security Command Center 데이터 내보내기를 참조하세요.

조직의 모든 발견 항목 나열

gcloud

  # ORGANIZATION_ID=organization-id

  gcloud scc findings list $ORGANIZATION_ID

더 많은 예시를 보려면 다음을 실행하세요.

  gcloud scc findings list --help

Python

from google.cloud import securitycenter

# Create a client.
client = securitycenter.SecurityCenterClient()

# organization_id is the numeric ID of the organization. e.g.:
# organization_id = "111122222444"
org_name = "organizations/{org_id}".format(org_id=organization_id)
# The "sources/-" suffix lists findings across all sources.  You
# also use a specific source_name instead.
all_sources = "{org_name}/sources/-".format(org_name=org_name)
finding_result_iterator = client.list_findings(request={"parent": all_sources})
for i, finding_result in enumerate(finding_result_iterator):
    print(
        "{}: name: {} resource: {}".format(
            i, finding_result.finding.name, finding_result.finding.resource_name
        )
    )

자바

static ImmutableList<ListFindingsResult> listAllFindings(OrganizationName organizationName) {
  try (SecurityCenterClient client = SecurityCenterClient.create()) {
    // OrganizationName organizationName = OrganizationName.of(/*organizationId=*/"123234324");
    // "-" Indicates listing across all sources.
    SourceName sourceName = SourceName.of(organizationName.getOrganization(), "-");

    ListFindingsRequest.Builder request =
        ListFindingsRequest.newBuilder().setParent(sourceName.toString());

    // Call the API.
    ListFindingsPagedResponse response = client.listFindings(request.build());

    // This creates one list for all findings.  If your organization has a large number of
    // findings this can cause out of memory issues.  You can process them in incrementally
    // by returning the Iterable returned response.iterateAll() directly.
    ImmutableList<ListFindingsResult> results = ImmutableList.copyOf(response.iterateAll());
    System.out.println("Findings:");
    System.out.println(results);
    return results;
  } catch (IOException e) {
    throw new RuntimeException("Couldn't create client.", e);
  }
}

Go

import (
	"context"
	"fmt"
	"io"

	securitycenter "cloud.google.com/go/securitycenter/apiv1"
	"google.golang.org/api/iterator"
	securitycenterpb "google.golang.org/genproto/googleapis/cloud/securitycenter/v1"
)

// listFindings prints all findings in orgID to w.  orgID is the numeric
// identifier of the organization.
func listFindings(w io.Writer, orgID string) error {
	// orgID := "12321311"
	// Instantiate a context and a security service client to make API calls.
	ctx := context.Background()
	client, err := securitycenter.NewClient(ctx)
	if err != nil {
		return fmt.Errorf("securitycenter.NewClient: %v", err)
	}
	defer client.Close() // Closing the client safely cleans up background resources.

	req := &securitycenterpb.ListFindingsRequest{
		// List findings across all sources.
		Parent: fmt.Sprintf("organizations/%s/sources/-", orgID),
	}
	it := client.ListFindings(ctx, req)
	for {
		result, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return fmt.Errorf("it.Next: %v", err)
		}
		finding := result.Finding
		fmt.Fprintf(w, "Finding Name: %s, ", finding.Name)
		fmt.Fprintf(w, "Resource Name %s, ", finding.ResourceName)
		fmt.Fprintf(w, "Category: %s\n", finding.Category)
	}
	return nil
}

Node.js

// Imports the Google Cloud client library.
const {SecurityCenterClient} = require('@google-cloud/security-center');

// Creates a new client.
const client = new SecurityCenterClient();
//  organizationId is the numeric ID of the organization.
/*
 * TODO(developer): Uncomment the following lines
 */
// const organizationId = "1234567777";

async function listAllFindings() {
  const [response] = await client.listFindings({
    // List findings across all sources.
    parent: `organizations/${organizationId}/sources/-`,
  });
  let count = 0;
  Array.from(response).forEach(result =>
    console.log(
      `${++count} ${result.finding.name} ${result.finding.resourceName}`
    )
  );
}
listAllFindings();

각 발견 항목의 출력은 다음과 비슷합니다.

finding:
  category: 'Persistence: IAM Anomalous Grant'
  createTime: '2020-09-13T12:30:10.248Z'
  eventTime: '2020-09-13T12:30:09.528Z'
  name: organizations/organization-id/sources/source-id/findings/finding-id
  parent: organizations/organization-id/sources/source-id
  resourceName: //cloudresourcemanager.googleapis.com/projects/project-number
  securityMarks:
    name: organizations/organization-id/sources/source-id/findings/finding-id/securityMarks
  sourceProperties:
    affectedResources:
    - gcpResourceName: //cloudresourcemanager.googleapis.com/projects/project-number
    detectionCategory:
      indicator: audit_log
      ruleName: iam_anomalous_grant
      subRuleName: service_account_granted_sensitive_role_to_member
      technique: persistence
    detectionPriority: HIGH
    evidence:
    - sourceLogId:
        insertId: insert-id
        projectId: project-id
        timestamp:
          nanos: 183000000
          seconds: '1600000206'
    findingId: finding-id
    properties:
      sensitiveRoleGrant:
        bindingDeltas:
        - action: ADD
          member: user:user-email@gmail.com
          role: roles/editor
        members:
        - user:user-email@gmail.com
        principalEmail: principal-email.iam.gserviceaccount.com
    sourceId:
      customerOrganizationNumber: 'organization-id'
      projectNumber: 'project-number'
  state: ACTIVE
resource:
  name: //cloudresourcemanager.googleapis.com/projects/project-number
  parentDisplayName: organization-name
  parentName: //cloudresourcemanager.googleapis.com/folders/folder-id
  projectDisplayName: project-id
  projectName: //cloudresourcemanager.googleapis.com/projects/project-number

발견 항목 필터링

조직에는 여러 발견 항목이 있을 수 있습니다. 앞의 예시에서는 필터를 사용하지 않으므로 모든 발견 항목 레코드가 반환됩니다. Security Command Center를 사용하면 발견 항목 필터를 사용하여 원하는 발견 항목에 대한 정보를 가져오고 상위 항목을 특정 소스로 제한할 수 있습니다.

발견 항목 필터는 열 대신 API에서 반환한 객체를 적용하는 것을 제외하고는 SQL 문의 'where' 절과 같습니다.

다음은 'MEDIUM_RISK_ONE' 카테고리가 있는 발견 항목만 나열하는 예시입니다. 특정 카테고리는 변경될 수 있으며 발견 항목 제공자의 문서를 참조하여 사용하는 카테고리를 확인해야 합니다.

gcloud

  # ORGANIZATION_ID=organization-id
  # SOURCE_ID="source-id"
  FILTER="category=\"MEDIUM_RISK_ONE\""

  gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID --filter="$FILTER"

더 많은 예시를 보려면 다음을 실행하세요.

  gcloud scc findings list --help

Python

from google.cloud import securitycenter

# Create a new client.
client = securitycenter.SecurityCenterClient()

# source_name is the resource path for a source that has been
# created previously (you can use list_sources to find a specific one).
# Its format is:
# source_name = "organizations/{organization_id}/sources/{source_id}"
# e.g.:
# source_name = "organizations/111122222444/sources/1234"
# You an also use a wild-card "-" for all sources:
#   source_name = "organizations/111122222444/sources/-"
finding_result_iterator = client.list_findings(
    request={"parent": source_name, "filter": 'category="MEDIUM_RISK_ONE"'}
)
# Iterate an print all finding names and the resource they are
# in reference to.
for i, finding_result in enumerate(finding_result_iterator):
    print(
        "{}: name: {} resource: {}".format(
            i, finding_result.finding.name, finding_result.finding.resource_name
        )
    )

자바

static ImmutableList<ListFindingsResult> listFilteredFindings(SourceName sourceName) {
  try (SecurityCenterClient client = SecurityCenterClient.create()) {
    // SourceName sourceName = SourceName.of(/*organizationId=*/"123234324",
    // /*sourceId=*/"423432321");

    // Create filter to category of MEDIUM_RISK_ONE
    String filter = "category=\"MEDIUM_RISK_ONE\"";

    ListFindingsRequest.Builder request =
        ListFindingsRequest.newBuilder().setParent(sourceName.toString()).setFilter(filter);

    // Call the API.
    ListFindingsPagedResponse response = client.listFindings(request.build());

    // This creates one list for all findings.  If your organization has a large number of
    // findings this can cause out of memory issues.  You can process them in incrementally
    // by returning the Iterable returned response.iterateAll() directly.
    ImmutableList<ListFindingsResult> results = ImmutableList.copyOf(response.iterateAll());
    System.out.println("Findings:");
    System.out.println(results);
    return results;
  } catch (IOException e) {
    throw new RuntimeException("Couldn't create client.", e);
  }
}

Go

import (
	"context"
	"fmt"
	"io"

	securitycenter "cloud.google.com/go/securitycenter/apiv1"
	"google.golang.org/api/iterator"
	securitycenterpb "google.golang.org/genproto/googleapis/cloud/securitycenter/v1"
)

// listFilteredFindings prints findings with category 'MEDIUM_RISK_ONE' for a
// specific source to w. sourceName is the full resource name of the source
// to search for findings under.
func listFilteredFindings(w io.Writer, sourceName string) error {
	// Specific source.
	// sourceName := "organizations/111122222444/sources/1234"
	// All sources.
	// sourceName := "organizations/111122222444/sources/-"
	// Instantiate a context and a security service client to make API calls.
	ctx := context.Background()
	client, err := securitycenter.NewClient(ctx)
	if err != nil {
		return fmt.Errorf("securitycenter.NewClient: %v", err)
	}
	defer client.Close() // Closing the client safely cleans up background resources.

	req := &securitycenterpb.ListFindingsRequest{
		Parent: sourceName,
		Filter: `category="MEDIUM_RISK_ONE"`,
	}
	it := client.ListFindings(ctx, req)
	for {
		result, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return fmt.Errorf("it.Next: %v", err)
		}
		finding := result.Finding
		fmt.Fprintf(w, "Finding Name: %s, ", finding.Name)
		fmt.Fprintf(w, "Resource Name %s, ", finding.ResourceName)
		fmt.Fprintf(w, "Category: %s\n", finding.Category)
	}
	return nil
}

Node.js

// Imports the Google Cloud client library.
const {SecurityCenterClient} = require('@google-cloud/security-center');

// Creates a new client.
const client = new SecurityCenterClient();
//  sourceName is the full resource path of the source to search for
//  findings.
/*
 * TODO(developer): Uncomment the following lines
 */
// const sourceName = "organizations/111122222444/sources/1234";

async function listFilteredFindings() {
  const [response] = await client.listFindings({
    // List findings across all sources.
    parent: sourceName,
    filter: 'category="MEDIUM_RISK_ONE"',
  });
  let count = 0;
  Array.from(response).forEach(result =>
    console.log(
      `${++count} ${result.finding.name} ${result.finding.resourceName}`
    )
  );
}
listFilteredFindings();

또한 Security Command Center는 전체 JSON 배열 및 객체를 잠재적인 속성 유형으로 지원합니다. 다음을 기준으로 필터링할 수 있습니다.

  • 배열 요소
  • 객체 내에서 부분 문자열이 일치하는 전체 JSON 객체
  • JSON 객체 하위 필드

하위 필드는 숫자, 문자열, 부울이어야 하며 다음 연산자를 사용해야 합니다.

  • 문자열:
    • 전체 같음 =
    • 부분 문자열 일치 :
  • 숫자:
    • 같지 않음 <, >, <=, >=
    • 같음 =
  • 부울:
    • 같음 =

이 페이지의 뒷부분에서는 다음 JSON 객체가 발견 항목의 소스 속성이라고 가정합니다.

{
    "outer_object": {
        "middle_object": {
            "deeply_nested_object": {
                "x": 123,
            },
            "y": "some-string-value",
        },
        "z": "some-other-string-value",
        "u": ["list-element-1", "list-element-2", "list-element-3", ],
    },
}

발견 항목 필터링 예시

이 예시에서 이전 JSON 객체는 발견 항목에서 my_property라는 소스 속성입니다. 다음 예시에는 객체를 속성으로 가진 발견 항목에 대한 쿼리가 포함되어 있습니다. 쿼리에서 ANDOR을 사용하여 이러한 필터를 다른 필터와 함께 사용할 수도 있습니다.

# ORGANIZATION_ID=organization-id
# SOURCE_ID="source-id"

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --filter="source_properties.my_property.outer_object.middle_object.deeply_nested_object.x = 123"

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --filter="source_properties.my_property.outer_object.middle_object.y = \"some-string-value\""

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --filter="source_properties.my_property.outer_object.middle_object.y : \"string-value\""

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --filter="source_properties.my_property.outer_object.z = \"some-other-string-value\""

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --filter="source_properties.my_property.outer_object.z : \"other-string-value\""

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --filter="source_properties.my_property.outer_object.u : \"list-element-1\""

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --filter="source_properties.my_property.outer_object.u : \"list-element-2\""

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --filter="source_properties.my_property.outer_object.u : \"list-element-3\""

발견 항목 정렬 예시

기본 유형인 엄격한 하위 필드(문자열, 숫자, 부울)로 발견 항목을 정렬할 수 있습니다. 이 예시에서 이전 JSON 객체는 발견 항목의 my_property라는 소스 속성입니다. 다음 예시에는 발견 항목 필드를 정렬하는 쿼리가 포함되어 있습니다.

# ORGANIZATION_ID=organization-id
# SOURCE_ID="source-id"

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --order-by="source_properties.my_property.outer_object.middle_object.deeply_nested_object.x DESC"

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --order-by="source_properties.my_property.outer_object.middle_object.deeply_nested_object.x"

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --order-by="source_properties.my_property.outer_object.middle_object.y DESC"

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --order-by="source_properties.my_property.outer_object.middle_object.y"

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --order-by="source_properties.my_property.outer_object.z DESC"

gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID \
  --order-by="source_properties.my_property.outer_object.z"

특정 시점 쿼리

Security Command Center를 사용하면 최대 13개월까지의 특정 스냅샷 시간의 기준으로 결과를 나열할 수 있습니다. 자세한 내용은 발겨 항목 보관을 참조하세요.

gcloud

  # ORGANIZATION_ID=organization-id
  # SOURCE_ID="source-id"
  # READ_TIME follows the format YYYY-MM-DDThh:mm:ss.ffffffZ
  READ_TIME=2019-02-28T07:00:06.861Z

  gcloud scc findings list $ORGANIZATION_ID --source=$SOURCE_ID --read-time=$READ_TIME

더 많은 예시를 보려면 다음을 실행하세요.

  gcloud scc findings list --help

Python

from google.cloud import securitycenter
from datetime import timedelta, datetime

# Create a new client.
client = securitycenter.SecurityCenterClient()

# source_name is the resource path for a source that has been
# created previously (you can use list_sources to find a specific one).
# Its format is:
# source_name = "organizations/{organization_id}/sources/{source_id}"
# e.g.:
# source_name = "organizations/111122222444/sources/1234"
# You an also use a wild-card "-" for all sources:
#   source_name = "organizations/111122222444/sources/-"
five_days_ago = str(datetime.now() - timedelta(days=5))

finding_result_iterator = client.list_findings(
    request={"parent": source_name, "filter": five_days_ago}
)
for i, finding_result in enumerate(finding_result_iterator):
    print(
        "{}: name: {} resource: {}".format(
            i, finding_result.finding.name, finding_result.finding.resource_name
        )
    )

자바

static ImmutableList<ListFindingsResult> listFindingsAtTime(SourceName sourceName) {
  try (SecurityCenterClient client = SecurityCenterClient.create()) {
    // SourceName sourceName = SourceName.of(/*organizationId=*/"123234324",
    // /*sourceId=*/"423432321");

    // 5 days ago
    Instant fiveDaysAgo = Instant.now().minus(Duration.ofDays(5));

    ListFindingsRequest.Builder request =
        ListFindingsRequest.newBuilder()
            .setParent(sourceName.toString())
            .setReadTime(
                Timestamp.newBuilder()
                    .setSeconds(fiveDaysAgo.getEpochSecond())
                    .setNanos(fiveDaysAgo.getNano()));

    // Call the API.
    ListFindingsPagedResponse response = client.listFindings(request.build());

    // This creates one list for all findings.  If your organization has a large number of
    // findings this can cause out of memory issues.  You can process them in incrementally
    // by returning the Iterable returned response.iterateAll() directly.
    ImmutableList<ListFindingsResult> results = ImmutableList.copyOf(response.iterateAll());
    System.out.println("Findings:");
    System.out.println(results);
    return results;
  } catch (IOException e) {
    throw new RuntimeException("Couldn't create client.", e);
  }
}

Go

import (
	"context"
	"fmt"
	"io"
	"time"

	securitycenter "cloud.google.com/go/securitycenter/apiv1"
	"github.com/golang/protobuf/ptypes"
	"google.golang.org/api/iterator"
	securitycenterpb "google.golang.org/genproto/googleapis/cloud/securitycenter/v1"
)

// listFindingsAtTime prints findings that where present for a specific source
// as of five days ago to w. sourceName is the full resource name of the
// source to search for findings under.
func listFindingsAtTime(w io.Writer, sourceName string) error {
	// Specific source.
	// sourceName := "organizations/111122222444/sources/1234"
	// All sources.
	// sourceName := "organizations/111122222444/sources/-"
	ctx := context.Background()
	client, err := securitycenter.NewClient(ctx)
	if err != nil {
		return fmt.Errorf("securitycenter.NewClient: %v", err)
	}
	defer client.Close() // Closing the client safely cleans up background resources.
	fiveDaysAgo, err := ptypes.TimestampProto(time.Now().AddDate(0, 0, -5))
	if err != nil {
		return fmt.Errorf("Error converting five days ago: %v", err)
	}

	req := &securitycenterpb.ListFindingsRequest{
		Parent:   sourceName,
		ReadTime: fiveDaysAgo,
	}
	it := client.ListFindings(ctx, req)
	for {
		result, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return fmt.Errorf("it.Next: %v", err)
		}
		finding := result.Finding
		fmt.Fprintf(w, "Finding Name: %s, ", finding.Name)
		fmt.Fprintf(w, "Resource Name %s, ", finding.ResourceName)
		fmt.Fprintf(w, "Category: %s\n", finding.Category)
	}
	return nil
}

Node.js

// Imports the Google Cloud client library.
const {SecurityCenterClient} = require('@google-cloud/security-center');

// Creates a new client.
const client = new SecurityCenterClient();
// sourceName is the fully qualified source name to search for findings
// under.
/*
 * TODO(developer): Uncomment the following lines
 */
// const sourceName = "organizations/111122222444/sources/1234";

const fiveDaysAgo = new Date();
fiveDaysAgo.setDate(fiveDaysAgo.getDate() - 5);

async function listFindingsAtTime() {
  const [response] = await client.listFindings({
    // List findings across all sources.
    parent: sourceName,
    readTime: {
      seconds: Math.floor(fiveDaysAgo.getTime() / 1000),
      nanos: (fiveDaysAgo.getTime() % 1000) * 1e6,
    },
  });
  let count = 0;
  Array.from(response).forEach(result =>
    console.log(
      `${++count} ${result.finding.name} ${result.finding.resourceName}`
    )
  );
}
listFindingsAtTime();

필터 예시

다음은 몇 가지 유용한 발견 항목 필터입니다.

특정 시점 이후에 발생한 발견 항목

이러한 필터 예시는 2019년 6월 5일 수요일 오후 10:12:05 GMT 이후 가장 최근에 발생한 결과와 일치합니다. event_time 필터를 사용하면 다음 형식과 유형을 사용하여 시간을 표현할 수 있습니다.

  • 정수 리터럴로서의 Unix 시간(밀리초)

    "event_time > 1559772725000"
    
  • 문자열 리터럴로서의 RFC 3339

    "event_time > \"2019-06-05T22:34:40+00:00\""