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 FREEFrequently Asked Questions
Rule Lifecycle & Deletion
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.deletionPolicy DEFAULT, the rule cannot have any associated retrohunts or detections. Switch to FORCE to delete the rule with its dependencies.Updates & Versioning
etag to match the server-computed value. If the etag doesn’t match, the update fails with an ABORTED error.text content in any way.instance, location, and project properties are immutable and cannot be changed after creation.Data Access & Scoping
scope parameter to the DataAccessScope resource name using the format: projects/{project}/locations/{location}/instances/{instance}/dataAccessScopes/{scope}.Rule Configuration
text property. The rule should include meta, events, match, and condition sections as shown in the examples.