Security Command Center API を使用して知見を管理する

このガイドでは、Security Command Center API を使用した検出結果の作成、更新について説明します。

始める前に

知見を作成および更新する前に、次の手順を行う必要があります。

このガイドを完了するには、組織レベルで Identity and Access Management(IAM)のSecurity Center 検出結果編集者securitycenter.findingsEditor)ロールが必要です。Security Command Center のロールの詳細については、アクセス制御をご覧ください。

セキュリティ マークが付いた知見を作成するには、使用するマークの種類に対する権限を含む次の IAM ロールも必要です。

  • Asset セキュリティ マーク ライターsecuritycenter.assetSecurityMarksWriter
  • セキュリティ マーク ライターの検出securitycenter.findingSecurityMarksWriter

マークの詳細については、Security Command Center のセキュリティ マークの使用をご覧ください。

知見の作成

ソースに対してアクティブな検出結果を作成します。

gcloud

gcloud scc findings create FINDING_NAME \
    --organization=PARENT_ID \
    --location=LOCATION \
    --source=SOURCE_ID \
    --state=STATE \
    --category=CATEGORY \
    --event-time=EVENT_TIME \
    --resource-name=RESOURCE_NAME

次のように置き換えます。

  • FINDING_NAME: 検出結果の名前。
  • PARENT_ID: 親組織の数値 ID。
  • LOCATION: データ所在地が有効になっている場合は、検出結果を作成する Security Command Center のロケーション。データ所在地が有効になっていない場合は、値 global を使用します。
  • SOURCE_ID: 検出結果のソースの数値 ID。
  • STATE: 検出結果の状態。検出結果に注意を払う必要がある場合は ACTIVE、検出結果に対処済みの場合は INACTIVE を使用します。
  • CATEGORY: 検出結果が属する分類グループ(例: AUDIT_LOGGING_DISABLED)。
  • EVENT_TIME: イベントが発生した時刻。RFC 822 タイムスタンプ形式または gcloud CLI がサポートするその他のタイムスタンプ形式で指定します。
  • RESOURCE_NAME: 検出結果が適用されるリソースの完全なリソース名。

Go

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

	securitycenter "cloud.google.com/go/securitycenter/apiv2"
	"cloud.google.com/go/securitycenter/apiv2/securitycenterpb"
	"github.com/golang/protobuf/ptypes"
)

// createFinding demonstrates how to create a new security finding in CSCC.
// sourceName is the full resource name of the source the finding should
// be associated with.
func createFinding(w io.Writer, sourceName string) error {
	// sourceName := "organizations/111122222444/sources/1234/locations/global"
	// 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: %w", err)
	}
	defer client.Close() // Closing the client safely cleans up background resources.
	// Use now as the eventTime for the security finding.
	eventTime, err := ptypes.TimestampProto(time.Now())
	if err != nil {
		return fmt.Errorf("TimestampProto: %w", err)
	}

	req := &securitycenterpb.CreateFindingRequest{
		Parent:    sourceName,
		FindingId: "samplefindingid",
		Finding: &securitycenterpb.Finding{
			State: securitycenterpb.Finding_ACTIVE,
			// Resource the finding is associated with. This is an
			// example any resource identifier can be used.
			ResourceName: "//cloudresourcemanager.googleapis.com/organizations/11232/sources/-/locations/global",
			// A free-form category.
			Category: "MEDIUM_RISK_ONE",
			// The time associated with discovering the issue.
			EventTime: eventTime,
		},
	}
	finding, err := client.CreateFinding(ctx, req)
	if err != nil {
		return fmt.Errorf("CreateFinding: %w", err)
	}
	fmt.Fprintf(w, "New finding created: %s\n", finding.Name)
	fmt.Fprintf(w, "Event time (Epoch Seconds): %d\n", eventTime.Seconds)
	return nil
}

Java


import com.google.cloud.securitycenter.v2.CreateFindingRequest;
import com.google.cloud.securitycenter.v2.Finding;
import com.google.cloud.securitycenter.v2.Finding.FindingClass;
import com.google.cloud.securitycenter.v2.Finding.Mute;
import com.google.cloud.securitycenter.v2.Finding.Severity;
import com.google.cloud.securitycenter.v2.Finding.State;
import com.google.cloud.securitycenter.v2.SecurityCenterClient;
import com.google.cloud.securitycenter.v2.SourceName;
import com.google.protobuf.Timestamp;
import java.io.IOException;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;

public class CreateFindings {

  public static void main(String[] args) throws IOException {
    // TODO: Replace the sample resource name
    // organizationId: Google Cloud Organization id.
    String organizationId = "{google-cloud-organization-id}";

    // Specify the location to list the findings.
    String location = "global";

    // The source id corresponding to the finding.
    String sourceId = "{source-id}";

    // The finding id.
    String findingId = "testfindingv2" + UUID.randomUUID().toString().split("-")[0];

    // Specify the category.
    Optional<String> category = Optional.of("MEDIUM_RISK_ONE");

    createFinding(organizationId, location, findingId, sourceId, category);
  }

  /**
   * Creates a security finding within a specific source in the Security Command Center.
   */
  public static Finding createFinding(String organizationId, String location, String findingId,
      String sourceId, Optional<String> category) throws IOException {
    try (SecurityCenterClient client = SecurityCenterClient.create()) {
      // Optionally SourceName or String can be used.
      // String sourceName = String.format("organizations/%s/sources/%s", organizationId, sourceId);
      SourceName sourceName = SourceName.of(organizationId, sourceId);

      Instant eventTime = Instant.now();
      // The resource this finding applies to. The Cloud Security Command Center UI can link the
      // findings for a resource to the corresponding asset of a resource if there are matches.
      String resourceName = String.format("//cloudresourcemanager.googleapis.com/organizations/%s",
          organizationId);

      // Set up a request to create a finding in a source.
      String parent = String.format("%s/locations/%s", sourceName.toString(), location);
      Finding finding = Finding.newBuilder()
          .setParent(parent)
          .setState(State.ACTIVE)
          .setSeverity(Severity.LOW)
          .setMute(Mute.UNMUTED)
          .setFindingClass(FindingClass.OBSERVATION)
          .setResourceName(resourceName)
          .setEventTime(Timestamp.newBuilder()
              .setSeconds(eventTime.getEpochSecond())
              .setNanos(eventTime.getNano()))
          .setCategory(category.orElse("LOW_RISK_ONE"))
          .build();

      CreateFindingRequest createFindingRequest = CreateFindingRequest.newBuilder()
          .setParent(parent)
          .setFindingId(findingId)
          .setFinding(finding).build();

      // Call the API.
      Finding response = client.createFinding(createFindingRequest);
      return response;
    }
  }
}

Node.js

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

// Create a Security Center client
const client = new SecurityCenterClient();

// TODO(developer): Update the following for your own environment.
const organizationId = '1081635000895';
const location = 'global';

const [source] = await client.createSource({
  source: {
    displayName: 'Customized Display Name V2',
    description: 'A new custom source that does X',
  },
  parent: client.organizationPath(organizationId),
});

const sourceId = source.name.split('/')[3];

// Resource name of the new finding's parent. Examples:
//  - `organizations/[organization_id]/sources/[source_id]`
//  - `organizations/[organization_id]/sources/[source_id]/locations/[location_id]`
const parent = `organizations/${organizationId}/sources/${sourceId}/locations/${location}`;

// The resource this finding applies to. The Cloud Security Command Center UI can link the
// findings for a resource to the corresponding asset of a resource if there are matches.
const resourceName = `//cloudresourcemanager.googleapis.com/organizations/${organizationId}`;

// Unique identifier provided by the client within the parent scope.
// It must be alphanumeric and less than or equal to 32 characters and
// greater than 0 characters in length.
const findingId = uuid.v4().replace(/-/g, '');

// Get the current timestamp.
const eventDate = new Date();

// Finding category.
const category = 'MEDIUM_RISK_ONE';

// Build the finding request object.
const createFindingRequest = {
  parent: parent,
  findingId: findingId,
  finding: {
    resourceName,
    category,
    state: 'ACTIVE',
    // The time associated with discovering the issue.
    eventTime: {
      seconds: Math.floor(eventDate.getTime() / 1000),
      nanos: (eventDate.getTime() % 1000) * 1e6,
    },
  },
};

// Call the API.
const [finding] = await client.createFinding(createFindingRequest);
console.log('New finding created: %j', finding);

Python

def create_finding(
    organization_id, location_id, finding_id, source_name, category
) -> Dict:
    """
    cretaes a new finding
    Args:
        organization_id: organization_id is the numeric ID of the organization. e.g.:organization_id = "111122222444"
        source_name: is the resource path for a source that has been created
        finding_id: unique identifier provided by the client.
        location_id: GCP location id; example: 'global'
        category: the additional category group with in findings.
    Returns:
         Dict: returns the created findings details.
    """
    import datetime

    from google.cloud import securitycenter_v2
    from google.cloud.securitycenter_v2 import Finding

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

    # Use the current time as the finding "event time".
    event_time = datetime.datetime.now(tz=datetime.timezone.utc)

    # '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"
    # source_name = f"organizations/{organization_id}/sources/{source_name}"
    # category= "MEDIUM_RISK_ONE"
    # The resource this finding applies to.  The CSCC UI can link
    # the findings for a resource to the corresponding Asset of a resource
    # if there are matches.
    resource_name = (
        f"//cloudresourcemanager.googleapis.com/organizations/{organization_id}"
    )

    finding = Finding(
        state=Finding.State.ACTIVE,
        resource_name=resource_name,
        category=category,
        event_time=event_time,
    )
    parent = source_name + "/locations/" + location_id
    # Call The API.
    created_finding = client.create_finding(
        request={"parent": parent, "finding_id": finding_id, "finding": finding}
    )
    print(created_finding)
    return created_finding

検出結果のデータが Security Command Center に保存される期間については、検出結果の保持をご覧ください。

検出結果の状態の更新

Security Command Center には、知見の状態だけを更新する API も用意されています。この API は、知見の状態だけを更新する方法を提供するために存在します。これは、プリンシパルに状態の変更のみを許可し、知見の他の部分を変更できないようにするシンプルな API です。下記の例では、知見の状態を非アクティブに変更する方法を示します。

gcloud

gcloud scc findings update \
    PARENT/PARENT_ID/sources/SOURCE_ID/locations/LOCATION/findings/FINDING_NAME \
    --state=STATE

次のように置き換えます。

  • PARENT: 検出結果が存在するリソース階層のレベル。organizationsfolders、または projects を使用します。
  • PARENT_ID: 親組織、フォルダ、プロジェクトの数値 ID、または親プロジェクトの英数字 ID。
  • SOURCE_ID: 検出結果のソースの数値 ID。
  • LOCATION: データ所在地が有効になっている場合は、検出結果を更新する Security Command Center のロケーション。データ所在地が有効になっていない場合は、値 global を使用します。
  • FINDING_NAME: 更新する検出結果。
  • STATE: 検出結果の状態。検出結果に注意を払う必要がある場合は ACTIVE、検出結果に対処済みの場合は INACTIVE を使用します。

Go

import (
	"context"
	"fmt"
	"io"

	securitycenter "cloud.google.com/go/securitycenter/apiv2"
	"cloud.google.com/go/securitycenter/apiv2/securitycenterpb"
)

// updateFindingState demonstrates how to update a security finding's state
// in CSCC.  findingName is the full resource name of the finding to update.
func setFindingState(w io.Writer, findingName string) error {
	// findingName := "organizations/111122222444/sources/1234/locations/global"
	// 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: %w", err)
	}
	defer client.Close() // Closing the client safely cleans up background resources.

	req := &securitycenterpb.SetFindingStateRequest{
		Name:  findingName,
		State: securitycenterpb.Finding_INACTIVE,
		// New state is effective immediately.
	}

	finding, err := client.SetFindingState(ctx, req)
	if err != nil {
		return fmt.Errorf("SetFindingState: %w", err)
	}

	fmt.Fprintf(w, "Finding updated: %s\n", finding.Name)
	fmt.Fprintf(w, "Finding state: %v\n", finding.State)
	fmt.Fprintf(w, "Event time (Epoch Seconds): %d\n", finding.EventTime.Seconds)

	return nil
}

Java


import com.google.cloud.securitycenter.v2.Finding;
import com.google.cloud.securitycenter.v2.Finding.State;
import com.google.cloud.securitycenter.v2.FindingName;
import com.google.cloud.securitycenter.v2.SecurityCenterClient;
import com.google.cloud.securitycenter.v2.SetFindingStateRequest;
import java.io.IOException;

public class SetFindingsByState {

  public static void main(String[] args) throws IOException {
    // organizationId: Google Cloud Organization id.
    String organizationId = "{google-cloud-organization-id}";

    // Specify the location to list the findings.
    String location = "global";

    // The source id corresponding to the finding.
    String sourceId = "{source-id}";

    // The finding id.
    String findingId = "{finding-id}";

    setFindingState(organizationId, location, sourceId, findingId);
  }

  // Demonstrates how to update a finding's state
  public static Finding setFindingState(String organizationId, String location, String sourceId,
      String findingId) throws IOException {
    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests.
    try (SecurityCenterClient client = SecurityCenterClient.create()) {
      // Optionally FindingName or String can be used.
      // String findingName = String.format("organizations/%s/sources/%s/locations/%s/findings/%s",
      // organizationId,sourceId,location,findingId);
      FindingName findingName = FindingName
          .ofOrganizationSourceLocationFindingName(organizationId, sourceId, location, findingId);

      SetFindingStateRequest request = SetFindingStateRequest.newBuilder()
          .setName(findingName.toString())
          .setState(State.INACTIVE)
          .build();

      // Call the API.
      Finding finding = client.setFindingState(request);

      System.out.println("Updated Finding: " + finding);
      return finding;
    }
  }
}

Node.js

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

// Creates a new client.
const client = new SecurityCenterClient();

// TODO(developer): Update the following for your own environment.
const organizationId = '1081635000895';
const location = 'global';

async function createSampleFinding() {
  const uuid = require('uuid');

  const [source] = await client.createSource({
    source: {
      displayName: 'Customized Display Name V2',
      description: 'A new custom source that does X',
    },
    parent: client.organizationPath(organizationId),
  });

  const sourceId = source.name.split('/')[3];

  // Resource name of the new finding's parent. Examples:
  //  - `organizations/[organization_id]/sources/[source_id]`
  //  - `organizations/[organization_id]/sources/[source_id]/locations/[location_id]`
  const parent = `organizations/${organizationId}/sources/${sourceId}/locations/${location}`;

  // The resource this finding applied to. The Cloud Security Command Center UI can link the
  // findings for a resource to the corresponding asset of a resource if there are matches.
  const resourceName = `//cloudresourcemanager.googleapis.com/organizations/${organizationId}`;

  // Unique identifier provided by the client within the parent scope.
  // It must be alphanumeric and less than or equal to 32 characters and
  // greater than 0 characters in length.
  const findingId = uuid.v4().replace(/-/g, '');

  // Get the current timestamp.
  const eventDate = new Date();

  // Finding category.
  const category = 'MEDIUM_RISK_ONE';

  // Build the finding request object.
  const createFindingRequest = {
    parent: parent,
    findingId: findingId,
    finding: {
      resourceName,
      category,
      state: 'ACTIVE',
      // The time associated with discovering the issue.
      eventTime: {
        seconds: Math.floor(eventDate.getTime() / 1000),
        nanos: (eventDate.getTime() % 1000) * 1e6,
      },
    },
  };

  await client.createFinding(createFindingRequest);
  return {sourceId, findingId};
}

const {sourceId, findingId} = await createSampleFinding();

// The name is the full resource name of the source the finding should be associated with.
const name = `organizations/${organizationId}/sources/${sourceId}/locations/${location}/findings/${findingId}`;

// Build the request.
const eventTime = new Date();

const findingRequest = {
  name,
  state: 'INACTIVE',
  // use now as the time when the new state takes effect.
  startTime: {
    seconds: Math.floor(eventTime.getTime() / 1000),
    nanos: (eventTime.getTime() % 1000) * 1e6,
  },
};

async function setFindingState() {
  // Call the API.
  const [response] = await client.setFindingState(findingRequest);
  console.log('Set finding state: %j', response);
}

await setFindingState();

次のステップ

クライアント ライブラリを使用した Security Command Center へのアクセスの詳細を確認する。