From e57bb8f5f76bae75fc0c200c4025b96b77d72c85 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:45:26 +0000 Subject: [PATCH 1/2] chore(deps): update all non-major dependencies Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- build.gradle.kts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ca2b976..2a22b47 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ plugins { id("com.google.protobuf") version "0.9.5" id("com.palantir.git-version") version "4.2.0" id("org.jreleaser") version "1.21.0" - id("com.gradleup.shadow") version "9.2.2" + id("com.gradleup.shadow") version "9.3.0" } val gitVersion: groovy.lang.Closure by extra @@ -44,7 +44,7 @@ java { protobuf { protoc { - artifact = "com.google.protobuf:protoc:4.33.1" + artifact = "com.google.protobuf:protoc:4.33.2" } plugins { @@ -63,14 +63,14 @@ protobuf { } dependencies { - implementation("com.google.protobuf:protobuf-java:4.33.1") - implementation("com.google.protobuf:protobuf-java-util:4.33.1") + implementation("com.google.protobuf:protobuf-java:4.33.2") + implementation("com.google.protobuf:protobuf-java-util:4.33.2") implementation("io.grpc:grpc-protobuf:1.77.0") implementation("io.grpc:grpc-stub:1.77.0") implementation("io.grpc:grpc-netty-shaded:1.77.0") implementation("io.netty:netty-tcnative-boringssl-static:2.0.74.Final") implementation("org.testcontainers:testcontainers:2.0.2") - implementation("build.buf:protovalidate:1.0.1") + implementation("build.buf:protovalidate:1.1.0") implementation("commons-io:commons-io:2.21.0") implementation("com.google.code.gson:gson:2.13.2") implementation("io.github.resilience4j:resilience4j-circuitbreaker:2.3.0") @@ -79,8 +79,8 @@ dependencies { testImplementation("org.testcontainers:junit-jupiter:1.21.3") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.14.1") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - testImplementation("ch.qos.logback:logback-core:1.5.21") - testImplementation("ch.qos.logback:logback-classic:1.5.21") + testImplementation("ch.qos.logback:logback-core:1.5.22") + testImplementation("ch.qos.logback:logback-classic:1.5.22") testImplementation("com.fasterxml.jackson.core:jackson-core:2.20.1") testImplementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.1") } From 8ec77907c4c092479b599ee8d0807698741f0cf7 Mon Sep 17 00:00:00 2001 From: Charith Ellawala Date: Mon, 15 Dec 2025 15:23:13 +0000 Subject: [PATCH 2/2] Update protos Signed-off-by: Charith Ellawala --- .../authzen/authorization/v1/evaluation.proto | 283 ++++++++++++++++++ .../proto/authzen/authorization/v1/svc.proto | 65 ++++ src/main/proto/buf/validate/validate.proto | 189 +++++++++++- src/main/proto/cerbos/audit/v1/audit.proto | 6 + .../proto/cerbos/cloud/bundle/v2/bundle.proto | 30 ++ .../proto/cerbos/cloud/epdp/v2/epdp.proto | 105 +++++++ .../proto/cerbos/cloud/store/v1/store.proto | 12 + src/main/proto/cerbos/engine/v1/engine.proto | 12 +- src/main/proto/cerbos/policy/v1/policy.proto | 7 +- .../proto/cerbos/request/v1/request.proto | 4 +- .../proto/cerbos/response/v1/response.proto | 2 +- src/main/proto/google/rpc/code.proto | 186 ++++++++++++ 12 files changed, 884 insertions(+), 17 deletions(-) create mode 100644 src/main/proto/authzen/authorization/v1/evaluation.proto create mode 100644 src/main/proto/authzen/authorization/v1/svc.proto create mode 100644 src/main/proto/cerbos/cloud/epdp/v2/epdp.proto create mode 100644 src/main/proto/google/rpc/code.proto diff --git a/src/main/proto/authzen/authorization/v1/evaluation.proto b/src/main/proto/authzen/authorization/v1/evaluation.proto new file mode 100644 index 0000000..cd56cc0 --- /dev/null +++ b/src/main/proto/authzen/authorization/v1/evaluation.proto @@ -0,0 +1,283 @@ +// Copyright 2021-2025 Zenauth Ltd. +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package authzen.authorization.v1; + +import "buf/validate/validate.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option csharp_namespace = "Cerbos.AuthZen.Api.V1.Authorization"; +option go_package = "github.com/cerbos/cerbos/api/genpb/authzen/authorization/v1;authorizationv1"; +option java_package = "dev.cerbos.authzen.api.v1.authorization"; + +// Subject represents the principal requesting access +message Subject { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "Subject requesting access"} + }; + + string type = 1 [ + (buf.validate.field).string = {min_len: 1}, + (buf.validate.field).required = true, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Type of the subject." + example: "\"user\"" + } + ]; + + string id = 2 [ + (buf.validate.field).string = {min_len: 1}, + (buf.validate.field).required = true, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Unique identifier for the subject." + example: "\"alice@acmecorp.com\"" + } + ]; + + map properties = 3 [ + (buf.validate.field).map.keys = { + string: {min_len: 1} + }, + (buf.validate.field).map.values.required = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Key-value pairs of contextual data about this subject that should be used during policy evaluation." + example: "{\"department\": \"engineering\"}" + } + ]; +} + +// Resource represents the target of the access request +message Resource { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "Resource being accessed"} + }; + + string type = 1 [ + (buf.validate.field).string = {min_len: 1}, + (buf.validate.field).required = true, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Type of the resource." + example: "\"account\"" + } + ]; + + string id = 2 [ + (buf.validate.field).string = {min_len: 1}, + (buf.validate.field).required = true, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Unique identifier for the resource." + example: "\"123\"" + } + ]; + + map properties = 3 [ + (buf.validate.field).map.keys = { + string: {min_len: 1} + }, + (buf.validate.field).map.values.required = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Key-value pairs of contextual data about this resource that should be used during policy evaluation." + example: "{\"owner\": \"bugs_bunny\"}" + } + ]; +} + +// Action represents the operation being performed +message Action { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "Action being performed"} + }; + + string name = 1 [ + (buf.validate.field).string = {min_len: 1}, + (buf.validate.field).required = true, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Name of the action being performed." + example: "\"can_read\"" + } + ]; + + map properties = 2 [ + (buf.validate.field).map.keys = { + string: {min_len: 1} + }, + (buf.validate.field).map.values.required = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Key-value pairs of contextual data about this action that should be used during policy evaluation." + example: "{\"method\": \"GET\"}" + } + ]; +} + +// AuthZEN evaluation request message +message AccessEvaluationRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "AuthZEN access evaluation request"} + }; + + Subject subject = 1 [ + (buf.validate.field).required = true, + (google.api.field_behavior) = REQUIRED + ]; + + Resource resource = 2 [ + (buf.validate.field).required = true, + (google.api.field_behavior) = REQUIRED + ]; + + Action action = 3 [ + (buf.validate.field).required = true, + (google.api.field_behavior) = REQUIRED + ]; + + map context = 4 [ + (buf.validate.field).map.keys = { + string: {min_len: 1} + }, + (buf.validate.field).map.values.required = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Key-value pairs of environmental/contextual data that should be used during policy evaluation." + example: "{\"time\": \"2023-01-01T00:00:00Z\"}" + } + ]; +} + +// AuthZEN evaluation response message +message AccessEvaluationResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "AuthZEN access evaluation response"} + }; + + optional bool decision = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Whether to allow or deny the operation." + example: "true" + }]; + + map context = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Additional context about evaluation" + example: "{\"time\": \"2023-01-01T00:00:00Z\"}" + }]; +} + +// AuthZEN evaluations request message +message AccessEvaluationBatchRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "AuthZEN access evaluations request"} + }; + + Subject subject = 1; + Resource resource = 2; + Action action = 3; + + map context = 4 [ + (buf.validate.field).map.keys = { + string: {min_len: 1} + }, + (buf.validate.field).map.values.required = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Key-value pairs of environmental/contextual data that should be used during policy evaluation." + example: "{\"time\": \"2023-01-01T00:00:00Z\"}" + } + ]; + + message Evaluation { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "An evaluation"} + }; + + Subject subject = 1; + Resource resource = 2; + Action action = 3; + + map context = 4 [ + (buf.validate.field).map.keys = { + string: {min_len: 1} + }, + (buf.validate.field).map.values.required = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Key-value pairs of environmental/contextual data that should be used during policy evaluation." + example: "{\"time\": \"2023-01-01T00:00:00Z\"}" + } + ]; + } + + repeated Evaluation evaluations = 5 [(buf.validate.field).repeated.min_items = 1]; + + AccessEvaluationsOptions options = 6; + + option (buf.validate.message).cel = { + id: "actions" + message: "either the default action must be set or all evaluations must have an action" + expression: "has(this.action) || this.evaluations.all(x, has(x.action))" + }; + + option (buf.validate.message).cel = { + id: "subjects" + message: "either the default subject must be set or all evaluations must have a subject" + expression: "has(this.subject) || this.evaluations.all(x, has(x.subject))" + }; + option (buf.validate.message).cel = { + id: "resources" + message: "either the default resource must be set or all evaluations must have a resource" + expression: "has(this.resource) || this.evaluations.all(x, has(x.resource))" + }; + option (buf.validate.message).cel = { + id: "contexts" + message: "either the default context must be set or all evaluations must have a context" + expression: "has(this.context) || this.evaluations.all(x, has(x.context))" + }; +} + +// AuthZEN evaluations response message +message AccessEvaluationBatchResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "AuthZEN access evaluations response"} + }; + repeated AccessEvaluationResponse evaluations = 1; +} + +message MetadataRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "AuthZEN Policy Decision Point Metadata request"} + }; +} + +message MetadataResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "AuthZEN Policy Decision Point Metadata"} + }; + string policy_decision_point = 1 [json_name = "policy_decision_point"]; + string access_evaluation_endpoint = 2 [json_name = "access_evaluation_endpoint"]; + string access_evaluations_endpoint = 3 [json_name = "access_evaluations_endpoint"]; +} + +// AccessEvaluationsOptions captures optional execution controls for batch requests. +message AccessEvaluationsOptions { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: {description: "Optional execution controls for AuthZEN batch requests"} + }; + // Controls how the PDP should execute the evaluations array. + string evaluations_semantic = 1 [ + (buf.validate.field).string = { + in: [ + "", + "execute_all", + "deny_on_first_deny", + "permit_on_first_permit" + ] + }, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Evaluation semantic to apply. Defaults to \"execute_all\" when unset." + example: "\"deny_on_first_deny\"" + } + ]; +} diff --git a/src/main/proto/authzen/authorization/v1/svc.proto b/src/main/proto/authzen/authorization/v1/svc.proto new file mode 100644 index 0000000..25c30a2 --- /dev/null +++ b/src/main/proto/authzen/authorization/v1/svc.proto @@ -0,0 +1,65 @@ +// Copyright 2021-2025 Zenauth Ltd. +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package authzen.authorization.v1; + +import "authzen/authorization/v1/evaluation.proto"; +import "google/api/annotations.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option csharp_namespace = "Cerbos.AuthZen.Api.V1.Authorization"; +option go_package = "github.com/cerbos/cerbos/api/genpb/authzen/authorization/v1;authorizationv1"; +option java_package = "dev.cerbos.authzen.api.v1.authorization"; +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: { + title: "AuthZEN Authorization API" + version: "v1" + description: "OpenID AuthZEN Authorization API for access evaluations" + contact: { + name: "Cerbos" + url: "https://cerbos.dev" + } + } + schemes: HTTPS + consumes: "application/json" + produces: "application/json" +}; + +// AuthZEN Authorization Service +service AuthorizationService { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_tag) = {description: "AuthZEN Authorization Service for access evaluations"}; + + // Metadata returns the Policy Decision Point metadata + rpc Metadata(MetadataRequest) returns (MetadataResponse) { + option (google.api.http) = {get: "/.well-known/authzen-configuration"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Get Policy Decision Point metadata" + description: "Returns the AuthZEN Policy Decision Point metadata including endpoint URLs" + }; + } + + // Evaluate performs an access evaluation + rpc AccessEvaluation(AccessEvaluationRequest) returns (AccessEvaluationResponse) { + option (google.api.http) = { + post: "/access/v1/evaluation" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Evaluate access request" + description: "Evaluate whether a subject can perform an action on a resource according to AuthZEN specification" + }; + } + // Evaluate performs an access evaluation + rpc AccessEvaluationBatch(AccessEvaluationBatchRequest) returns (AccessEvaluationBatchResponse) { + option (google.api.http) = { + post: "/access/v1/evaluations" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Batch evaluate access request" + description: "Batch evaluate whether a subject can perform an action on a resource according to AuthZEN specification" + }; + } +} diff --git a/src/main/proto/buf/validate/validate.proto b/src/main/proto/buf/validate/validate.proto index f7453d3..eefab5f 100644 --- a/src/main/proto/buf/validate/validate.proto +++ b/src/main/proto/buf/validate/validate.proto @@ -18,6 +18,7 @@ package buf.validate; import "google/protobuf/descriptor.proto"; import "google/protobuf/duration.proto"; +import "google/protobuf/field_mask.proto"; import "google/protobuf/timestamp.proto"; option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"; @@ -109,6 +110,25 @@ message Rule { // MessageRules represents validation rules that are applied to the entire message. // It includes disabling options and a list of Rule messages representing Common Expression Language (CEL) validation rules. message MessageRules { + // `cel_expression` is a repeated field CEL expressions. Each expression specifies a validation + // rule to be applied to this message. These rules are written in Common Expression Language (CEL) syntax. + // + // This is a simplified form of the `cel` Rule field, where only `expression` is set. This allows for + // simpler syntax when defining CEL Rules where `id` and `message` derived from the `expression`. `id` will + // be same as the `expression`. + // + // For more information, [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/). + // + // ```proto + // message MyMessage { + // // The field `foo` must be greater than 42. + // option (buf.validate.message).cel_expression = "this.foo > 42"; + // // The field `foo` must be less than 84. + // option (buf.validate.message).cel_expression = "this.foo < 84"; + // optional int32 foo = 1; + // } + // ``` + repeated string cel_expression = 5; // `cel` is a repeated field of type Rule. Each Rule specifies a validation rule to be applied to this message. // These rules are written in Common Expression Language (CEL) syntax. For more information, // [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/). @@ -201,6 +221,22 @@ message OneofRules { // FieldRules encapsulates the rules for each type of field. Depending on // the field, the correct set should be used to ensure proper validations. message FieldRules { + // `cel_expression` is a repeated field CEL expressions. Each expression specifies a validation + // rule to be applied to this message. These rules are written in Common Expression Language (CEL) syntax. + // + // This is a simplified form of the `cel` Rule field, where only `expression` is set. This allows for + // simpler syntax when defining CEL Rules where `id` and `message` derived from the `expression`. `id` will + // be same as the `expression`. + // + // For more information, [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/). + // + // ```proto + // message MyMessage { + // // The field `value` must be greater than 42. + // optional int32 value = 1 [(buf.validate.field).cel_expression = "this > 42"]; + // } + // ``` + repeated string cel_expression = 29; // `cel` is a repeated field used to represent a textual expression // in the Common Expression Language (CEL) syntax. For more information, // [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/). @@ -243,7 +279,8 @@ message FieldRules { // the fields are set and valid. // // Fields that don't track presence are always validated by Protovalidate, - // whether they are set or not. It is not necessary to add `required`: + // whether they are set or not. It is not necessary to add `required`. It + // can be added to indicate that the field cannot be the zero value. // // ```proto // syntax="proto3"; @@ -254,9 +291,14 @@ message FieldRules { // (buf.validate.field).string.email = true // ]; // // `repeated.min_items` always applies, even to an empty list. - // repeated string labels = 4 [ + // repeated string labels = 2 [ // (buf.validate.field).repeated.min_items = 1 // ]; + // // `required`, for fields that don't track presence, indicates + // // the value of the field can't be the zero value. + // int32 zero_value_not_allowed = 3 [ + // (buf.validate.field).required = true + // ]; // } // ``` // @@ -307,6 +349,7 @@ message FieldRules { // Well-Known Field Types AnyRules any = 20; DurationRules duration = 21; + FieldMaskRules field_mask = 28; TimestampRules timestamp = 22; } @@ -419,7 +462,8 @@ enum Ignore { // // The field's rules will always be ignored, including any validations // // on value's fields. // MyOtherMessage value = 1 [ - // (buf.validate.field).ignore = IGNORE_ALWAYS]; + // (buf.validate.field).ignore = IGNORE_ALWAYS + // ]; // } // ``` IGNORE_ALWAYS = 3; @@ -3724,6 +3768,29 @@ message StringRules { } ]; + // `ulid` specifies that the field value must be a valid ULID (Universally Unique + // Lexicographically Sortable Identifier) as defined by the [ULID specification](https://github.com/ulid/spec). + // If the field value isn't a valid ULID, an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid ULID + // string value = 1 [(buf.validate.field).string.ulid = true]; + // } + // ``` + bool ulid = 35 [ + (predefined).cel = { + id: "string.ulid" + message: "value must be a valid ULID" + expression: "!rules.ulid || this == '' || this.matches('^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$')" + }, + (predefined).cel = { + id: "string.ulid_empty" + message: "value is empty, which is not a valid ULID" + expression: "!rules.ulid || this != ''" + } + ]; + // `well_known_regex` specifies a common well-known pattern // defined as a regex. If the field value doesn't match the well-known // regex, an error message will be generated. @@ -3936,7 +4003,7 @@ message BytesRules { // the string. // If the field value doesn't meet the requirement, an error message is generated. // - // ```protobuf + // ```proto // message MyBytes { // // value does not contain \x02\x03 // optional bytes value = 1 [(buf.validate.field).bytes.contains = "\x02\x03"]; @@ -3951,7 +4018,7 @@ message BytesRules { // values. If the field value doesn't match any of the specified values, an // error message is generated. // - // ```protobuf + // ```proto // message MyBytes { // // value must in ["\x01\x02", "\x02\x03", "\x03\x04"] // optional bytes value = 1 [(buf.validate.field).bytes.in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; @@ -4045,6 +4112,31 @@ message BytesRules { expression: "!rules.ipv6 || this.size() != 0" } ]; + + // `uuid` ensures that the field `value` encodes the 128-bit UUID data as + // defined by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.2). + // The field must contain exactly 16 bytes + // representing the UUID. If the field value isn't a valid UUID, an error + // message will be generated. + // + // ```proto + // message MyBytes { + // // value must be a valid UUID + // optional bytes value = 1 [(buf.validate.field).bytes.uuid = true]; + // } + // ``` + bool uuid = 15 [ + (predefined).cel = { + id: "bytes.uuid" + message: "value must be a valid UUID" + expression: "!rules.uuid || this.size() == 0 || this.size() == 16" + }, + (predefined).cel = { + id: "bytes.uuid_empty" + message: "value is empty, which is not a valid UUID" + expression: "!rules.uuid || this.size() != 0" + } + ]; } // `example` specifies values that the field may have. These values SHOULD @@ -4598,6 +4690,93 @@ message DurationRules { extensions 1000 to max; } +// FieldMaskRules describe rules applied exclusively to the `google.protobuf.FieldMask` well-known type. +message FieldMaskRules { + // `const` dictates that the field must match the specified value of the `google.protobuf.FieldMask` type exactly. + // If the field's value deviates from the specified value, an error message + // will be generated. + // + // ```proto + // message MyFieldMask { + // // value must equal ["a"] + // google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask.const = { + // paths: ["a"] + // }]; + // } + // ``` + optional google.protobuf.FieldMask const = 1 [(predefined).cel = { + id: "field_mask.const" + expression: "this.paths != getField(rules, 'const').paths ? 'value must equal paths %s'.format([getField(rules, 'const').paths]) : ''" + }]; + + // `in` requires the field value to only contain paths matching specified + // values or their subpaths. + // If any of the field value's paths doesn't match the rule, + // an error message is generated. + // See: https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask + // + // ```proto + // message MyFieldMask { + // // The `value` FieldMask must only contain paths listed in `in`. + // google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = { + // in: ["a", "b", "c.a"] + // }]; + // } + // ``` + repeated string in = 2 [(predefined).cel = { + id: "field_mask.in" + expression: "!this.paths.all(p, p in getField(rules, 'in') || getField(rules, 'in').exists(f, p.startsWith(f+'.'))) ? 'value must only contain paths in %s'.format([getField(rules, 'in')]) : ''" + }]; + + // `not_in` requires the field value to not contain paths matching specified + // values or their subpaths. + // If any of the field value's paths matches the rule, + // an error message is generated. + // See: https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask + // + // ```proto + // message MyFieldMask { + // // The `value` FieldMask shall not contain paths listed in `not_in`. + // google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = { + // not_in: ["forbidden", "immutable", "c.a"] + // }]; + // } + // ``` + repeated string not_in = 3 [(predefined).cel = { + id: "field_mask.not_in" + expression: "!this.paths.all(p, !(p in getField(rules, 'not_in') || getField(rules, 'not_in').exists(f, p.startsWith(f+'.')))) ? 'value must not contain any paths in %s'.format([getField(rules, 'not_in')]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other rules. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFieldMask { + // google.protobuf.FieldMask value = 1 [ + // (buf.validate.field).field_mask.example = { paths: ["a", "b"] }, + // (buf.validate.field).field_mask.example = { paths: ["c.a", "d"] }, + // ]; + // } + // ``` + repeated google.protobuf.FieldMask example = 4 [(predefined).cel = { + id: "field_mask.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field rules that can then be + // set on the field options of other fields to apply field rules. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + // TimestampRules describe the rules applied exclusively to the `google.protobuf.Timestamp` well-known type. message TimestampRules { // `const` dictates that this field, of the `google.protobuf.Timestamp` type, must exactly match the specified value. If the field value doesn't correspond to the specified timestamp, an error message will be generated. diff --git a/src/main/proto/cerbos/audit/v1/audit.proto b/src/main/proto/cerbos/audit/v1/audit.proto index 5853520..994e9f2 100644 --- a/src/main/proto/cerbos/audit/v1/audit.proto +++ b/src/main/proto/cerbos/audit/v1/audit.proto @@ -105,6 +105,11 @@ message PolicySource { } message Hub { + message EmbeddedBundle { + string rule_id = 1; + repeated string scopes = 2; + } + message LocalBundle { string path = 1; } @@ -114,6 +119,7 @@ message PolicySource { string deployment_id = 2; string playground_id = 3; LocalBundle local_bundle = 4; + EmbeddedBundle embedded_bundle = 5; } } diff --git a/src/main/proto/cerbos/cloud/bundle/v2/bundle.proto b/src/main/proto/cerbos/cloud/bundle/v2/bundle.proto index 2c12504..e35043a 100644 --- a/src/main/proto/cerbos/cloud/bundle/v2/bundle.proto +++ b/src/main/proto/cerbos/cloud/bundle/v2/bundle.proto @@ -15,6 +15,12 @@ option csharp_namespace = "Cerbos.Api.Cloud.V2.Bundle"; option go_package = "github.com/cerbos/cloud-api/genpb/cerbos/cloud/bundle/v2;bundlev2"; option java_package = "dev.cerbos.api.cloud.v2.bundle"; +enum BundleType { + BUNDLE_TYPE_UNSPECIFIED = 0; + BUNDLE_TYPE_LEGACY = 1; + BUNDLE_TYPE_RULE_TABLE = 2; +} + message Source { oneof source { option (buf.validate.oneof).required = true; @@ -42,6 +48,12 @@ message BundleInfo { bytes output_hash = 3 [(buf.validate.field).bytes.len = 32]; bytes encryption_key = 4; repeated Segment segments = 5 [(buf.validate.field).repeated.min_items = 1]; + optional BundleType bundle_type = 6 [(buf.validate.field).enum = { + in: [ + 1, + 2 + ] + }]; } message Meta { @@ -59,6 +71,12 @@ message Manifest { message GetBundleRequest { cerbos.cloud.pdp.v1.Identifier pdp_id = 1 [(buf.validate.field).required = true]; Source source = 2 [(buf.validate.field).required = true]; + optional BundleType bundle_type = 3 [(buf.validate.field).enum = { + in: [ + 1, + 2 + ] + }]; } message GetBundleResponse { @@ -68,6 +86,12 @@ message GetBundleResponse { message WatchBundleRequest { message Start { Source source = 1 [(buf.validate.field).required = true]; + optional BundleType bundle_type = 2 [(buf.validate.field).enum = { + in: [ + 1, + 2 + ] + }]; } message Heartbeat { @@ -76,6 +100,12 @@ message WatchBundleRequest { (buf.validate.field).timestamp.within = {seconds: 60} ]; string active_bundle_id = 2 [(buf.validate.field).string.len = 16]; + optional BundleType bundle_type = 3 [(buf.validate.field).enum = { + in: [ + 1, + 2 + ] + }]; } cerbos.cloud.pdp.v1.Identifier pdp_id = 1 [(buf.validate.field).required = true]; diff --git a/src/main/proto/cerbos/cloud/epdp/v2/epdp.proto b/src/main/proto/cerbos/cloud/epdp/v2/epdp.proto new file mode 100644 index 0000000..a769897 --- /dev/null +++ b/src/main/proto/cerbos/cloud/epdp/v2/epdp.proto @@ -0,0 +1,105 @@ +// Copyright 2021-2025 Zenauth Ltd. +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package cerbos.cloud.epdp.v2; + +import "buf/validate/validate.proto"; +import "cerbos/audit/v1/audit.proto"; +import "cerbos/response/v1/response.proto"; +import "google/api/visibility.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "google/rpc/code.proto"; + +option csharp_namespace = "Cerbos.Api.Cloud.V2.Epdp"; +option go_package = "github.com/cerbos/cloud-api/genpb/cerbos/cloud/epdp/v2;epdpv2"; +option java_package = "dev.cerbos.api.cloud.v2.epdp"; + +message Config { + message Evaluator { + map globals = 1; + string default_policy_version = 2; + bool lenient_scope_search = 3; + string default_scope = 4; + } + + message Schema { + enum Enforcement { + ENFORCEMENT_UNSPECIFIED = 0; + ENFORCEMENT_NONE = 1; + ENFORCEMENT_WARN = 2; + ENFORCEMENT_REJECT = 3; + } + + Enforcement enforcement = 1; + } + + Evaluator evaluator = 1; + Schema schema = 2; +} + +message Error { + google.rpc.Code code = 1 [(buf.validate.field).enum = { + defined_only: true + not_in: [0] + }]; + string message = 2; +} + +message Metadata { + string cerbos_version = 1 [(buf.validate.field).string.min_len = 1]; + string cerbos_commit_hash = 2 [(buf.validate.field).string.len = 40]; + string wasm_checksum = 3 [(buf.validate.field).string.len = 64]; + google.protobuf.Timestamp built_at = 4 [(buf.validate.field).required = true]; +} + +message CheckResourcesResponse { + cerbos.response.v1.CheckResourcesResponse response = 1 [(buf.validate.field).required = true]; + cerbos.audit.v1.AuditTrail audit_trail = 2 [(buf.validate.field).required = true]; +} + +message PlanResourcesResponse { + cerbos.response.v1.PlanResourcesResponse response = 1 [(buf.validate.field).required = true]; + cerbos.audit.v1.AuditTrail audit_trail = 2 [(buf.validate.field).required = true]; +} + +message Bundle { + message Metadata { + string bundle_id = 1 [(buf.validate.field).string.len = 16]; + int64 rule_revision = 2 [(buf.validate.field).int64.gt = 0]; + } + + Metadata metadata = 1 [(buf.validate.field).required = true]; + bytes contents = 2; +} + +message GetBundleRequest { + string rule_id = 1 [(buf.validate.field).string.len = 12]; + repeated string scopes = 2 [(buf.validate.field).repeated = { + max_items: 128 + unique: true + items: { + string: {min_len: 1} + } + }]; + optional Bundle.Metadata if_modified_since = 3; +} + +message GetBundleResponse { + oneof result { + option (buf.validate.oneof).required = true; + Bundle bundle = 1; + google.protobuf.Empty not_modified = 2; + } +} + +service BundleService { + option (google.api.api_visibility).restriction = "EXPERIMENTAL"; + + rpc GetBundle(GetBundleRequest) returns (GetBundleResponse) { + option idempotency_level = NO_SIDE_EFFECTS; + } +} diff --git a/src/main/proto/cerbos/cloud/store/v1/store.proto b/src/main/proto/cerbos/cloud/store/v1/store.proto index 07b4ebb..17e3698 100644 --- a/src/main/proto/cerbos/cloud/store/v1/store.proto +++ b/src/main/proto/cerbos/cloud/store/v1/store.proto @@ -74,6 +74,15 @@ message GetFilesResponse { repeated File files = 2; } +message GetCurrentVersionRequest { + string store_id = 1 [(buf.validate.field).string.len = 12]; +} + +message GetCurrentVersionResponse { + int64 store_version = 1; + optional ChangeDetails change_details = 2; +} + message ChangeDetails { message Git { string repo = 1; @@ -204,6 +213,9 @@ service CerbosStoreService { rpc ListFiles(ListFilesRequest) returns (ListFilesResponse) { option idempotency_level = NO_SIDE_EFFECTS; } + rpc GetCurrentVersion(GetCurrentVersionRequest) returns (GetCurrentVersionResponse) { + option idempotency_level = NO_SIDE_EFFECTS; + } rpc GetFiles(GetFilesRequest) returns (GetFilesResponse) { option idempotency_level = NO_SIDE_EFFECTS; } diff --git a/src/main/proto/cerbos/engine/v1/engine.proto b/src/main/proto/cerbos/engine/v1/engine.proto index 0d34532..cd03145 100644 --- a/src/main/proto/cerbos/engine/v1/engine.proto +++ b/src/main/proto/cerbos/engine/v1/engine.proto @@ -42,11 +42,11 @@ message PlanResourcesInput { ]; string scope = 4 [ - (buf.validate.field).string = {pattern: "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$"}, + (buf.validate.field).string = {pattern: "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$"}, (google.api.field_behavior) = OPTIONAL, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { description: "A dot-separated scope that describes the hierarchy this resource belongs to. This is used for determining policy inheritance." - pattern: "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$" + pattern: "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$" } ]; } @@ -213,11 +213,11 @@ message Resource { } ]; string scope = 5 [ - (buf.validate.field).string = {pattern: "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$"}, + (buf.validate.field).string = {pattern: "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$"}, (google.api.field_behavior) = OPTIONAL, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { description: "A dot-separated scope that describes the hierarchy this resource belongs to. This is used for determining policy inheritance." - pattern: "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$" + pattern: "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$" example: "\"acme.corp\"" } ]; @@ -273,11 +273,11 @@ message Principal { } ]; string scope = 5 [ - (buf.validate.field).string = {pattern: "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$"}, + (buf.validate.field).string = {pattern: "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$"}, (google.api.field_behavior) = OPTIONAL, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { description: "A dot-separated scope that describes the hierarchy this principal belongs to. This is used for determining policy inheritance." - pattern: "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$" + pattern: "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$" example: "\"acme.corp\"" } ]; diff --git a/src/main/proto/cerbos/policy/v1/policy.proto b/src/main/proto/cerbos/policy/v1/policy.proto index 79ad746..684d3aa 100644 --- a/src/main/proto/cerbos/policy/v1/policy.proto +++ b/src/main/proto/cerbos/policy/v1/policy.proto @@ -82,7 +82,7 @@ message ResourcePolicy { } }]; repeated ResourceRule rules = 4; - string scope = 5 [(buf.validate.field).string.pattern = "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$"]; + string scope = 5 [(buf.validate.field).string.pattern = "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$"]; Schemas schemas = 6; Variables variables = 7; ScopePermissions scope_permissions = 8; @@ -137,7 +137,7 @@ message RolePolicy { string: {min_len: 1} } }]; - string scope = 2 [(buf.validate.field).string.pattern = "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$"]; + string scope = 2 [(buf.validate.field).string.pattern = "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$"]; repeated RoleRule rules = 3; // Deprecated: no-op. ScopePermissions scope_permissions = 4 [ @@ -179,7 +179,7 @@ message PrincipalPolicy { (buf.validate.field).string.pattern = "^[\\w]+$" ]; repeated PrincipalRule rules = 3; - string scope = 4 [(buf.validate.field).string.pattern = "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$"]; + string scope = 4 [(buf.validate.field).string.pattern = "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$"]; Variables variables = 5; ScopePermissions scope_permissions = 6; Constants constants = 7; @@ -404,6 +404,7 @@ message TestOptions { bool lenient_scope_search = 2; map globals = 3; string default_policy_version = 4; + string default_scope = 5; } message TestSuite { diff --git a/src/main/proto/cerbos/request/v1/request.proto b/src/main/proto/cerbos/request/v1/request.proto index bcb9bbd..d28d2a0 100644 --- a/src/main/proto/cerbos/request/v1/request.proto +++ b/src/main/proto/cerbos/request/v1/request.proto @@ -147,11 +147,11 @@ message ResourceSet { } ]; string scope = 4 [ - (buf.validate.field).string.pattern = "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$", + (buf.validate.field).string.pattern = "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$", (google.api.field_behavior) = OPTIONAL, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { description: "A dot-separated scope that describes the hierarchy these resources belong to. This is used for determining policy inheritance." - pattern: "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$" + pattern: "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$" } ]; } diff --git a/src/main/proto/cerbos/response/v1/response.proto b/src/main/proto/cerbos/response/v1/response.proto index 5bbcea4..101e295 100644 --- a/src/main/proto/cerbos/response/v1/response.proto +++ b/src/main/proto/cerbos/response/v1/response.proto @@ -210,7 +210,7 @@ message CheckResourcesResponse { "A dot-separated scope that describes the hierarchy " "this resource belongs to. This is used for " "determining policy inheritance." - pattern: "^([0-9a-zA-Z][\\w\\-]*(\\.[\\w\\-]*)*)*$" + pattern: "^(^$|\\.|[0-9a-zA-Z][\\w\\-]*(\\.\\w[\\w\\-]*)*)$" example: "\"acme.corp\"" }]; } diff --git a/src/main/proto/google/rpc/code.proto b/src/main/proto/google/rpc/code.proto new file mode 100644 index 0000000..aa6ce15 --- /dev/null +++ b/src/main/proto/google/rpc/code.proto @@ -0,0 +1,186 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.rpc; + +option go_package = "google.golang.org/genproto/googleapis/rpc/code;code"; +option java_multiple_files = true; +option java_outer_classname = "CodeProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// The canonical error codes for gRPC APIs. +// +// +// Sometimes multiple error codes may apply. Services should return +// the most specific error code that applies. For example, prefer +// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. +// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. +enum Code { + // Not an error; returned on success. + // + // HTTP Mapping: 200 OK + OK = 0; + + // The operation was cancelled, typically by the caller. + // + // HTTP Mapping: 499 Client Closed Request + CANCELLED = 1; + + // Unknown error. For example, this error may be returned when + // a `Status` value received from another address space belongs to + // an error space that is not known in this address space. Also + // errors raised by APIs that do not return enough error information + // may be converted to this error. + // + // HTTP Mapping: 500 Internal Server Error + UNKNOWN = 2; + + // The client specified an invalid argument. Note that this differs + // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments + // that are problematic regardless of the state of the system + // (e.g., a malformed file name). + // + // HTTP Mapping: 400 Bad Request + INVALID_ARGUMENT = 3; + + // The deadline expired before the operation could complete. For operations + // that change the state of the system, this error may be returned + // even if the operation has completed successfully. For example, a + // successful response from a server could have been delayed long + // enough for the deadline to expire. + // + // HTTP Mapping: 504 Gateway Timeout + DEADLINE_EXCEEDED = 4; + + // Some requested entity (e.g., file or directory) was not found. + // + // Note to server developers: if a request is denied for an entire class + // of users, such as gradual feature rollout or undocumented allowlist, + // `NOT_FOUND` may be used. If a request is denied for some users within + // a class of users, such as user-based access control, `PERMISSION_DENIED` + // must be used. + // + // HTTP Mapping: 404 Not Found + NOT_FOUND = 5; + + // The entity that a client attempted to create (e.g., file or directory) + // already exists. + // + // HTTP Mapping: 409 Conflict + ALREADY_EXISTS = 6; + + // The caller does not have permission to execute the specified + // operation. `PERMISSION_DENIED` must not be used for rejections + // caused by exhausting some resource (use `RESOURCE_EXHAUSTED` + // instead for those errors). `PERMISSION_DENIED` must not be + // used if the caller can not be identified (use `UNAUTHENTICATED` + // instead for those errors). This error code does not imply the + // request is valid or the requested entity exists or satisfies + // other pre-conditions. + // + // HTTP Mapping: 403 Forbidden + PERMISSION_DENIED = 7; + + // The request does not have valid authentication credentials for the + // operation. + // + // HTTP Mapping: 401 Unauthorized + UNAUTHENTICATED = 16; + + // Some resource has been exhausted, perhaps a per-user quota, or + // perhaps the entire file system is out of space. + // + // HTTP Mapping: 429 Too Many Requests + RESOURCE_EXHAUSTED = 8; + + // The operation was rejected because the system is not in a state + // required for the operation's execution. For example, the directory + // to be deleted is non-empty, an rmdir operation is applied to + // a non-directory, etc. + // + // Service implementors can use the following guidelines to decide + // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: + // (a) Use `UNAVAILABLE` if the client can retry just the failing call. + // (b) Use `ABORTED` if the client should retry at a higher level. For + // example, when a client-specified test-and-set fails, indicating the + // client should restart a read-modify-write sequence. + // (c) Use `FAILED_PRECONDITION` if the client should not retry until + // the system state has been explicitly fixed. For example, if an "rmdir" + // fails because the directory is non-empty, `FAILED_PRECONDITION` + // should be returned since the client should not retry unless + // the files are deleted from the directory. + // + // HTTP Mapping: 400 Bad Request + FAILED_PRECONDITION = 9; + + // The operation was aborted, typically due to a concurrency issue such as + // a sequencer check failure or transaction abort. + // + // See the guidelines above for deciding between `FAILED_PRECONDITION`, + // `ABORTED`, and `UNAVAILABLE`. + // + // HTTP Mapping: 409 Conflict + ABORTED = 10; + + // The operation was attempted past the valid range. E.g., seeking or + // reading past end-of-file. + // + // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may + // be fixed if the system state changes. For example, a 32-bit file + // system will generate `INVALID_ARGUMENT` if asked to read at an + // offset that is not in the range [0,2^32-1], but it will generate + // `OUT_OF_RANGE` if asked to read from an offset past the current + // file size. + // + // There is a fair bit of overlap between `FAILED_PRECONDITION` and + // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific + // error) when it applies so that callers who are iterating through + // a space can easily look for an `OUT_OF_RANGE` error to detect when + // they are done. + // + // HTTP Mapping: 400 Bad Request + OUT_OF_RANGE = 11; + + // The operation is not implemented or is not supported/enabled in this + // service. + // + // HTTP Mapping: 501 Not Implemented + UNIMPLEMENTED = 12; + + // Internal errors. This means that some invariants expected by the + // underlying system have been broken. This error code is reserved + // for serious errors. + // + // HTTP Mapping: 500 Internal Server Error + INTERNAL = 13; + + // The service is currently unavailable. This is most likely a + // transient condition, which can be corrected by retrying with + // a backoff. Note that it is not always safe to retry + // non-idempotent operations. + // + // See the guidelines above for deciding between `FAILED_PRECONDITION`, + // `ABORTED`, and `UNAVAILABLE`. + // + // HTTP Mapping: 503 Service Unavailable + UNAVAILABLE = 14; + + // Unrecoverable data loss or corruption. + // + // HTTP Mapping: 500 Internal Server Error + DATA_LOSS = 15; +}