Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.swp
*.vscode
bazel-*
bazel-*
rulescli
15 changes: 15 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ bazel_dep(
version = "0.39.1",
repo_name = "bazel_gazelle",
)
bazel_dep(
name = "rules_proto",
version = "7.0.2",
)

bazel_dep(
name = "rules_proto_grpc",
version = "5.0.1",
)

bazel_dep(
name = "protobuf",
version = "29.0",
repo_name = "com_google_protobuf",
)

go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
go_deps.gazelle_override(
Expand Down
193 changes: 143 additions & 50 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ executed using `./rulescli` to provide a basic usage message.

## Usage

The CLI provides three modes `-expr`, `-file` and `-test`.
The CLI provides four modes `-expr`, `-file`, `-test` and `-textproto`.



### expr

Expand Down Expand Up @@ -328,4 +330,35 @@ referring to the
./rulescli -test $(pwd)'test/http-tests.yaml'
```

### Textproto

The `-textproto=<filename>` flag is used to validate a file containing a `VendorRulesetCollection` in the text protobuf format. The tool attempts to parse the file and will report any syntactical errors it finds. This is useful for checking the validity of a ruleset collection before it is used.

**Example Usage:**

Assuming you have a file named `my_ruleset.textproto` with content in the `VendorRulesetCollection` format:

```textproto
# A sample VendorRulesetCollection
uuid: "123e4567-e89b-12d3-a456-426614174000"
ruleset_metadata: {
owner: "Imperva"
description: "Initial set of rules."
}
rule_sets: {
name: "sqli-rules"
category: "sqli"
rules: {
id: "191190"
cel_expression: "request.headers['user-agent'].contains('sqlmap')"
}
}
```

You can validate this file by running the following command. If the file is valid, the command will exit successfully. If there are syntactical errors, it will print them to the console.

```sh
./rulescli -textproto="my_ruleset.textproto"
```

Disclaimer: This is not an official Google project
36 changes: 34 additions & 2 deletions cmd/rulescli.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const textFmtHeader = `# proto-file: github.com/google/cel-spec/proto/checked.pr
type options struct {
expr, file, test string
outputFormat, version string
textproto string
verbose bool
}

Expand All @@ -46,12 +47,13 @@ func (o *options) registerFlags(fs *flag.FlagSet) {
fs.StringVar(&o.file, "file", "", "File containing CEL expressions representing the Cloud Armor rule")
fs.StringVar(&o.outputFormat, "output_format", "", "output format (textproto, binarypb)")
fs.StringVar(&o.version, "version", "VCurrent", "valid versions (VCurrent, VNext)")
fs.StringVar(&o.textproto, "textproto", "", "File containing the rulesets as proto defined in VendorRulesetCollection")
fs.BoolVar(&o.verbose, "verbose", false, "Enable verbose logging")
}

func (o *options) validate() error {
if o.expr == "" && o.file == "" && o.test == "" {
return fmt.Errorf("either -expr=<expression> or -file=<file> or -test=<test_suite_file> is required")
if o.expr == "" && o.file == "" && o.test == "" && o.textproto == "" {
return fmt.Errorf("either -expr=<expression> or -file=<file> or -test=<test_suite_file> or -textproto=<textproto_file> is required")
}
if o.expr != "" && o.outputFormat != "" &&
o.outputFormat != "textproto" && o.outputFormat != "binarypb" {
Expand Down Expand Up @@ -160,6 +162,28 @@ func (r *rules) newProgram(ast *cel.Ast) cel.Program {
return prg
}

func processVendorRuleset(filename string, verbose bool) error {
verboseLog(verbose, "Reading vendor ruleset file: %s", filename)
content, err := os.ReadFile(filename)

if err != nil {
fmt.Fprintf(os.Stderr, "failed to read vendor ruleset file: %v\n", err)
}

rulesetCollection := cloudarmor.VendorRulesetCollection{}

// Unmarshal the text-formatted content into the struct.
parseErr := prototext.Unmarshal(content, &rulesetCollection)

if parseErr != nil {
fmt.Fprintf(os.Stderr, "failed to parse vendor ruleset file as VendorRulesetCollection: %v\n", parseErr)
return parseErr
}

fmt.Printf("Successfully validated vendor ruleset. \n")
return nil
}

func main() {
var opts options
opts.registerFlags(flag.CommandLine)
Expand All @@ -178,6 +202,14 @@ func main() {

r := newRules(opts.version)

if opts.textproto != "" {
if err := processVendorRuleset(opts.textproto, opts.verbose); err != nil {
fmt.Fprintf(os.Stderr, "failed to process vendor ruleset: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}

if opts.expr != "" {
ast, ok := r.newAST(opts.expr)
if ok {
Expand Down
1 change: 1 addition & 0 deletions pkg/cloudarmor/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ go_library(
"cloudarmor.go",
"testsuite.go",
"variables.go",
"vendor_ruleset_collection.pb.go",
],
embedsrcs = ["//pkg/cloudarmor/config"],
importpath = "github.com/cel-expr/cloud-armor-rules/pkg/cloudarmor",
Expand Down
26 changes: 26 additions & 0 deletions pkg/cloudarmor/proto/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library")

licenses(["notice"]) # Apache 2.0

proto_library(
name = "vendor_ruleset_collection_proto",
srcs = ["vendor_ruleset_collection.proto"],
visibility = ["//visibility:public"],
deps = ["@com_google_protobuf//:timestamp_proto"],
)

go_proto_library(
name = "vendor_ruleset_collection_go_proto",
importpath = "github.com/cel-expr/cloud-armor-rules/pkg/cloudarmor/proto",
proto = ":vendor_ruleset_collection_proto",
visibility = ["//visibility:public"],
)

go_library(
name = "proto",
embed = [":vendor_ruleset_collection_go_proto"],
importpath = "github.com/cel-expr/cloud-armor-rules/pkg/cloudarmor/proto",
visibility = ["//visibility:public"],
)
109 changes: 109 additions & 0 deletions pkg/cloudarmor/proto/vendor_ruleset_collection.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
syntax = "proto3";

package proto;

option go_package = "github.com/cel-expr/cloud-armor-rules/pkg/cloudarmor/proto";

import "google/protobuf/timestamp.proto";

// Vendor ruleset collection initially provided by Vendor
// This represents the initial set of data provided by the Vendor.
// For further details, please refer to shared(Google and Imperva) design doc:
// go/imperva-google-mgdrules-integration
message VendorRulesetCollection {
// Next ID: 4
// Unique ID associated with the ruleset collection
string uuid = 1;

// Metadata shared across Rulesets
message RulesetMetadata {
// Next ID: 6
// ruleset creation date
google.protobuf.Timestamp creation_date = 1;

// ruleset modification date
google.protobuf.Timestamp updation_date = 2;

// Name of the owner of the rulesets, e.g. Imperva
string owner = 3;

// A description of the ruleset's purpose
string description = 4;

// Encryption key identifier
string encryptionkey_id = 5;
}

// Metadata for the ruleset
RulesetMetadata ruleset_metadata = 2;

// Collection of vendor rulesets
repeated VendorRuleSet rule_sets = 3;
}

// Vendor rulesets containing rules for specific category, e.g. sqli, xss
message VendorRuleSet {
// Next ID: 7
// Name of the ruleset provided by Vendor
string name = 1;

// Version of the ruleset provided by Vendor, if any.
string version = 2;

// Category of the ruleset, e.g. SQLi, XSS
string category = 3;

// ChangeLog for the ruleset
message ChangeLog {
// This will be used to surface changelog information through
// documentation
string description = 1;

// this determines whether the ruleset description should be public
// e.g. Included in the release notes
bool always_public = 2;

// this is set if it should be included in a CVE release
bool use_for_cve = 3;
}

// Release notes corresponding to the rulesets
ChangeLog change_log = 4;

// Transformations should be applied sequentially to the input request
// before evaluating CEL Expression
// Rule shall be evaluated only once all the transformations were applied.
repeated string transformations = 5;

// Rule from the Vendor ruleset.
// It would only exist in the initial set of rules provided by the Vendor.
message VendorRule {
// Next ID: 7
// Unique ID associated with each rule
// For incremental updates, the rule ID remains unchanged but follows a
// versioning format. Example: id191190.0 to id191190.1
string id = 1;

// CEL expression
// Defines the rule logic in CEL string format using Cloud Armor (CA)
// attributes.
string cel_expression = 2;

// Array of Key value pairs of tags and additional info associated
// with the tag separated by ':'
repeated string tags = 3;

// Curl commands that would generate the matching expression for rule
repeated string e2e_test_command = 4;

// This field is reserved for the initial version of the ruleset
// defined by the Vendor.
reserved 5;

// By default, it would be false.
// If true, the rule shall only be evaluated if enabled by customers.
bool opt_in = 6;
}
// collection of rules under the ruleset
repeated VendorRule rules = 6;
}
Loading