Create GCP Chronicle Detection Rules

The gcp:chronicle/rule:Rule resource, part of the Pulumi GCP provider, defines a Chronicle detection rule: its YARA-L content, compilation behavior, and data access constraints. This guide focuses on three capabilities: YARA-L rule syntax, deletion policies for rules with detections, and data access scopes for log filtering.

Rules require a Chronicle instance and may reference data access scopes that control which logs the rule can evaluate. The examples are intentionally small. Combine them with your own Chronicle instance configuration and data access policies.

Create a detection rule with YARA-L syntax

Security teams write detection rules in YARA-L to identify suspicious patterns across log data. Chronicle compiles these rules and evaluates them against incoming events.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const example = new gcp.chronicle.Rule("example", {
    location: "us",
    instance: "00000000-0000-0000-0000-000000000000",
    deletionPolicy: "DEFAULT",
    text: "rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }\n",
});
import pulumi
import pulumi_gcp as gcp

example = gcp.chronicle.Rule("example",
    location="us",
    instance="00000000-0000-0000-0000-000000000000",
    deletion_policy="DEFAULT",
    text="rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }\n")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/chronicle"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := chronicle.NewRule(ctx, "example", &chronicle.RuleArgs{
			Location:       pulumi.String("us"),
			Instance:       pulumi.String("00000000-0000-0000-0000-000000000000"),
			DeletionPolicy: pulumi.String("DEFAULT"),
			Text:           pulumi.String("rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }\n"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var example = new Gcp.Chronicle.Rule("example", new()
    {
        Location = "us",
        Instance = "00000000-0000-0000-0000-000000000000",
        DeletionPolicy = "DEFAULT",
        Text = @"rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }
",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.chronicle.Rule;
import com.pulumi.gcp.chronicle.RuleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Rule("example", RuleArgs.builder()
            .location("us")
            .instance("00000000-0000-0000-0000-000000000000")
            .deletionPolicy("DEFAULT")
            .text("""
rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }
            """)
            .build());

    }
}
resources:
  example:
    type: gcp:chronicle:Rule
    properties:
      location: us
      instance: 00000000-0000-0000-0000-000000000000
      deletionPolicy: DEFAULT
      text: |
        rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }        

The text property contains the YARA-L rule definition, which specifies the detection logic. Chronicle parses this syntax to extract metadata like rule name and author, then compiles it into an executable form. The location and instance properties identify where the rule runs. The deletionPolicy defaults to “DEFAULT”, which prevents deletion if the rule has associated detections or retrohunts.

Control deletion behavior for rules with detections

Rules accumulate detections and retrohunt results over time. When removing a rule, teams must decide whether to preserve or delete this historical data.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const example = new gcp.chronicle.Rule("example", {
    location: "us",
    instance: "00000000-0000-0000-0000-000000000000",
    deletionPolicy: "FORCE",
    text: "rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }\n",
});
import pulumi
import pulumi_gcp as gcp

example = gcp.chronicle.Rule("example",
    location="us",
    instance="00000000-0000-0000-0000-000000000000",
    deletion_policy="FORCE",
    text="rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }\n")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/chronicle"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := chronicle.NewRule(ctx, "example", &chronicle.RuleArgs{
			Location:       pulumi.String("us"),
			Instance:       pulumi.String("00000000-0000-0000-0000-000000000000"),
			DeletionPolicy: pulumi.String("FORCE"),
			Text:           pulumi.String("rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }\n"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var example = new Gcp.Chronicle.Rule("example", new()
    {
        Location = "us",
        Instance = "00000000-0000-0000-0000-000000000000",
        DeletionPolicy = "FORCE",
        Text = @"rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }
",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.chronicle.Rule;
import com.pulumi.gcp.chronicle.RuleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Rule("example", RuleArgs.builder()
            .location("us")
            .instance("00000000-0000-0000-0000-000000000000")
            .deletionPolicy("FORCE")
            .text("""
rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }
            """)
            .build());

    }
}
resources:
  example:
    type: gcp:chronicle:Rule
    properties:
      location: us
      instance: 00000000-0000-0000-0000-000000000000
      deletionPolicy: FORCE
      text: |
        rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }        

Setting deletionPolicy to “FORCE” allows you to delete the rule even when detections or retrohunts exist. Chronicle removes the rule, its deployment, and all associated data. The “DEFAULT” policy requires you to clean up detections and retrohunts manually before deletion succeeds.

Restrict rule evaluation to specific log types

Organizations with multiple data sources often need to limit which logs a rule can access, either for compliance or to optimize performance.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const dataAccessScopeTest = new gcp.chronicle.DataAccessScope("data_access_scope_test", {
    location: "us",
    instance: "00000000-0000-0000-0000-000000000000",
    dataAccessScopeId: "scope-name",
    description: "scope-description",
    allowedDataAccessLabels: [{
        logType: "GCP_CLOUDAUDIT",
    }],
});
const example = new gcp.chronicle.Rule("example", {
    location: "us",
    instance: "00000000-0000-0000-0000-000000000000",
    scope: googleChronicleDataAccessScope.dataAccessScopeTest.name,
    text: "rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }\n",
});
import pulumi
import pulumi_gcp as gcp

data_access_scope_test = gcp.chronicle.DataAccessScope("data_access_scope_test",
    location="us",
    instance="00000000-0000-0000-0000-000000000000",
    data_access_scope_id="scope-name",
    description="scope-description",
    allowed_data_access_labels=[{
        "log_type": "GCP_CLOUDAUDIT",
    }])
example = gcp.chronicle.Rule("example",
    location="us",
    instance="00000000-0000-0000-0000-000000000000",
    scope=google_chronicle_data_access_scope["dataAccessScopeTest"]["name"],
    text="rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }\n")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/chronicle"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := chronicle.NewDataAccessScope(ctx, "data_access_scope_test", &chronicle.DataAccessScopeArgs{
			Location:          pulumi.String("us"),
			Instance:          pulumi.String("00000000-0000-0000-0000-000000000000"),
			DataAccessScopeId: pulumi.String("scope-name"),
			Description:       pulumi.String("scope-description"),
			AllowedDataAccessLabels: chronicle.DataAccessScopeAllowedDataAccessLabelArray{
				&chronicle.DataAccessScopeAllowedDataAccessLabelArgs{
					LogType: pulumi.String("GCP_CLOUDAUDIT"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = chronicle.NewRule(ctx, "example", &chronicle.RuleArgs{
			Location: pulumi.String("us"),
			Instance: pulumi.String("00000000-0000-0000-0000-000000000000"),
			Scope:    pulumi.Any(googleChronicleDataAccessScope.DataAccessScopeTest.Name),
			Text:     pulumi.String("rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }\n"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var dataAccessScopeTest = new Gcp.Chronicle.DataAccessScope("data_access_scope_test", new()
    {
        Location = "us",
        Instance = "00000000-0000-0000-0000-000000000000",
        DataAccessScopeId = "scope-name",
        Description = "scope-description",
        AllowedDataAccessLabels = new[]
        {
            new Gcp.Chronicle.Inputs.DataAccessScopeAllowedDataAccessLabelArgs
            {
                LogType = "GCP_CLOUDAUDIT",
            },
        },
    });

    var example = new Gcp.Chronicle.Rule("example", new()
    {
        Location = "us",
        Instance = "00000000-0000-0000-0000-000000000000",
        Scope = googleChronicleDataAccessScope.DataAccessScopeTest.Name,
        Text = @"rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }
",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.chronicle.DataAccessScope;
import com.pulumi.gcp.chronicle.DataAccessScopeArgs;
import com.pulumi.gcp.chronicle.inputs.DataAccessScopeAllowedDataAccessLabelArgs;
import com.pulumi.gcp.chronicle.Rule;
import com.pulumi.gcp.chronicle.RuleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var dataAccessScopeTest = new DataAccessScope("dataAccessScopeTest", DataAccessScopeArgs.builder()
            .location("us")
            .instance("00000000-0000-0000-0000-000000000000")
            .dataAccessScopeId("scope-name")
            .description("scope-description")
            .allowedDataAccessLabels(DataAccessScopeAllowedDataAccessLabelArgs.builder()
                .logType("GCP_CLOUDAUDIT")
                .build())
            .build());

        var example = new Rule("example", RuleArgs.builder()
            .location("us")
            .instance("00000000-0000-0000-0000-000000000000")
            .scope(googleChronicleDataAccessScope.dataAccessScopeTest().name())
            .text("""
rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }
            """)
            .build());

    }
}
resources:
  dataAccessScopeTest:
    type: gcp:chronicle:DataAccessScope
    name: data_access_scope_test
    properties:
      location: us
      instance: 00000000-0000-0000-0000-000000000000
      dataAccessScopeId: scope-name
      description: scope-description
      allowedDataAccessLabels:
        - logType: GCP_CLOUDAUDIT
  example:
    type: gcp:chronicle:Rule
    properties:
      location: us
      instance: 00000000-0000-0000-0000-000000000000
      scope: ${googleChronicleDataAccessScope.dataAccessScopeTest.name}
      text: |
        rule test_rule { meta: events:  $userid = $e.principal.user.userid  match: $userid over 10m condition: $e }        

The scope property binds the rule to a DataAccessScope resource, which defines allowedDataAccessLabels that filter log types. In this configuration, the rule only evaluates GCP_CLOUDAUDIT logs. Chronicle validates that any reference lists used in the rule are compatible with both the user’s scope and the rule’s scope.

Beyond these examples

These snippets focus on specific rule-level features: YARA-L rule definition and compilation, deletion policies for rules with detections, and data access scopes for log filtering. They’re intentionally minimal rather than full detection workflows.

The examples require pre-existing infrastructure such as a Chronicle instance (customer ID) and a GCP project with Chronicle API enabled. They focus on rule configuration rather than provisioning the Chronicle environment.

To keep things focused, common rule patterns are omitted, including:

  • Rule versioning and revision management (revisionId, revisionCreateTime)
  • Compilation diagnostics and error handling (compilationState, compilationDiagnostics)
  • Run frequency configuration (allowedRunFrequencies)
  • Reference lists and data tables (referenceLists, dataTables)

These omissions are intentional: the goal is to illustrate how each rule feature is wired, not provide drop-in detection modules. See the Chronicle Rule resource reference for all available configuration options.

Let's create GCP Chronicle Detection Rules

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Rule Lifecycle & Deletion
What's the difference between DEFAULT and FORCE deletion policies?
With deletionPolicy set to DEFAULT, deletion only succeeds if the rule has no associated retrohunts (including completed ones) or detections. Setting it to FORCE deletes the rule along with all associated retrohunts and detections.
Why is my rule deletion failing?
If using deletionPolicy DEFAULT, the rule cannot have any associated retrohunts or detections. Switch to FORCE to delete the rule with its dependencies.
Updates & Versioning
Why am I getting an ABORTED error when updating my rule?
Updates require the etag to match the server-computed value. If the etag doesn’t match, the update fails with an ABORTED error.
When does Chronicle create a new rule revision?
A new revision is automatically created whenever you change the rule’s text content in any way.
What properties can't I change after creating a rule?
The instance, location, and project properties are immutable and cannot be changed after creation.
Data Access & Scoping
How do I bind a data access scope to my rule?
Set the scope parameter to the DataAccessScope resource name using the format: projects/{project}/locations/{location}/instances/{instance}/dataAccessScopes/{scope}.
What happens if my reference lists aren't compatible with the rule's scope?
Chronicle validates that reference lists used in the rule are compatible with both the user’s scope and the rule’s bound DataAccessScope. Incompatible reference lists will cause validation failures.
Rule Configuration
What format should I use for the rule content?
Use YARA-L format for the text property. The rule should include meta, events, match, and condition sections as shown in the examples.

Using a different cloud?

Explore security guides for other cloud providers: