From 8677277bb7dff73822dd049dcb6977309f7c9b98 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Mon, 29 Jul 2024 16:40:17 -0700 Subject: [PATCH] Import rate_limit_quota.proto (Filter) and http_inputs.proto (Matcher) http_inputs.proto provides HttpRequestHeaderMatchInput, which can be provided as a typed config in `bucket_matchers` predicate input. --- xds/third_party/envoy/import.sh | 3 + .../v3/rate_limit_quota.proto | 423 ++++++++++++++++++ .../envoy/type/matcher/v3/http_inputs.proto | 71 +++ .../proto/envoy/type/v3/http_status.proto | 143 ++++++ 4 files changed, 640 insertions(+) create mode 100644 xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/http_inputs.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/type/v3/http_status.proto diff --git a/xds/third_party/envoy/import.sh b/xds/third_party/envoy/import.sh index b5b59b5fb4c1..41506c2ed32c 100755 --- a/xds/third_party/envoy/import.sh +++ b/xds/third_party/envoy/import.sh @@ -74,6 +74,7 @@ envoy/data/accesslog/v3/accesslog.proto envoy/extensions/clusters/aggregate/v3/cluster.proto envoy/extensions/filters/common/fault/v3/fault.proto envoy/extensions/filters/http/fault/v3/fault.proto +envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto envoy/extensions/filters/http/rbac/v3/rbac.proto envoy/extensions/filters/http/router/v3/router.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -95,6 +96,7 @@ envoy/service/rate_limit_quota/v3/rlqs.proto envoy/service/status/v3/csds.proto envoy/type/http/v3/path_transformation.proto envoy/type/matcher/v3/filter_state.proto +envoy/type/matcher/v3/http_inputs.proto envoy/type/matcher/v3/metadata.proto envoy/type/matcher/v3/node.proto envoy/type/matcher/v3/number.proto @@ -106,6 +108,7 @@ envoy/type/matcher/v3/value.proto envoy/type/metadata/v3/metadata.proto envoy/type/tracing/v3/custom_tag.proto envoy/type/v3/http.proto +envoy/type/v3/http_status.proto envoy/type/v3/percent.proto envoy/type/v3/range.proto envoy/type/v3/ratelimit_strategy.proto diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto new file mode 100644 index 000000000000..57b8bdecd782 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto @@ -0,0 +1,423 @@ +syntax = "proto3"; + +package envoy.extensions.filters.http.rate_limit_quota.v3; + +import "envoy/config/core/v3/base.proto"; +import "envoy/config/core/v3/extension.proto"; +import "envoy/config/core/v3/grpc_service.proto"; +import "envoy/type/v3/http_status.proto"; +import "envoy/type/v3/ratelimit_strategy.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; +import "google/rpc/status.proto"; + +import "xds/annotations/v3/status.proto"; +import "xds/type/matcher/v3/matcher.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.http.rate_limit_quota.v3"; +option java_outer_classname = "RateLimitQuotaProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rate_limit_quota/v3;rate_limit_quotav3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: Rate Limit Quota] +// Rate Limit Quota :ref:`configuration overview `. +// [#extension: envoy.filters.http.rate_limit_quota] + +// Configures the Rate Limit Quota filter. +// +// Can be overridden in the per-route and per-host configurations. +// The more specific definition completely overrides the less specific definition. +// [#next-free-field: 7] +message RateLimitQuotaFilterConfig { + // Configures the gRPC Rate Limit Quota Service (RLQS) RateLimitQuotaService. + config.core.v3.GrpcService rlqs_server = 1 [(validate.rules).message = {required: true}]; + + // The application domain to use when calling the service. This enables sharing the quota + // server between different applications without fear of overlap. + // E.g., "envoy". + string domain = 2 [(validate.rules).string = {min_len: 1}]; + + // The match tree to use for grouping incoming requests into buckets. + // + // Example: + // + // .. validated-code-block:: yaml + // :type-name: xds.type.matcher.v3.Matcher + // + // matcher_list: + // matchers: + // # Assign requests with header['env'] set to 'staging' to the bucket { name: 'staging' } + // - predicate: + // single_predicate: + // input: + // typed_config: + // '@type': type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput + // header_name: env + // value_match: + // exact: staging + // on_match: + // action: + // typed_config: + // '@type': type.googleapis.com/envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings + // bucket_id_builder: + // bucket_id_builder: + // name: + // string_value: staging + // + // # Assign requests with header['user_group'] set to 'admin' to the bucket { acl: 'admin_users' } + // - predicate: + // single_predicate: + // input: + // typed_config: + // '@type': type.googleapis.com/xds.type.matcher.v3.HttpAttributesCelMatchInput + // custom_match: + // typed_config: + // '@type': type.googleapis.com/xds.type.matcher.v3.CelMatcher + // expr_match: + // # Shortened for illustration purposes. Here should be parsed CEL expression: + // # request.headers['user_group'] == 'admin' + // parsed_expr: {} + // on_match: + // action: + // typed_config: + // '@type': type.googleapis.com/envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings + // bucket_id_builder: + // bucket_id_builder: + // acl: + // string_value: admin_users + // + // # Catch-all clause for the requests not matched by any of the matchers. + // # In this example, deny all requests. + // on_no_match: + // action: + // typed_config: + // '@type': type.googleapis.com/envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings + // no_assignment_behavior: + // fallback_rate_limit: + // blanket_rule: DENY_ALL + // + // .. attention:: + // The first matched group wins. Once the request is matched into a bucket, matcher + // evaluation ends. + // + // Use ``on_no_match`` field to assign the catch-all bucket. If a request is not matched + // into any bucket, and there's no ``on_no_match`` field configured, the request will be + // ALLOWED by default. It will NOT be reported to the RLQS server. + // + // Refer to :ref:`Unified Matcher API ` + // documentation for more information on the matcher trees. + xds.type.matcher.v3.Matcher bucket_matchers = 3 [(validate.rules).message = {required: true}]; + + // If set, this will enable -- but not necessarily enforce -- the rate limit for the given + // fraction of requests. + // + // Defaults to 100% of requests. + config.core.v3.RuntimeFractionalPercent filter_enabled = 4; + + // If set, this will enforce the rate limit decisions for the given fraction of requests. + // For requests that are not enforced the filter will still obtain the quota and include it + // in the load computation, however the request will always be allowed regardless of the outcome + // of quota application. This allows validation or testing of the rate limiting service + // infrastructure without disrupting existing traffic. + // + // Note: this only applies to the fraction of enabled requests. + // + // Defaults to 100% of requests. + config.core.v3.RuntimeFractionalPercent filter_enforced = 5; + + // Specifies a list of HTTP headers that should be added to each request that + // has been rate limited and is also forwarded upstream. This can only occur when the + // filter is enabled but not enforced. + repeated config.core.v3.HeaderValueOption request_headers_to_add_when_not_enforced = 6 + [(validate.rules).repeated = {max_items: 10}]; +} + +// Per-route and per-host configuration overrides. The more specific definition completely +// overrides the less specific definition. +message RateLimitQuotaOverride { + // The application domain to use when calling the service. This enables sharing the quota + // server between different applications without fear of overlap. + // E.g., "envoy". + // + // If empty, inherits the value from the less specific definition. + string domain = 1; + + // The match tree to use for grouping incoming requests into buckets. + // + // If set, fully overrides the bucket matchers provided on the less specific definition. + // If not set, inherits the value from the less specific definition. + // + // See usage example: :ref:`RateLimitQuotaFilterConfig.bucket_matchers + // `. + xds.type.matcher.v3.Matcher bucket_matchers = 2; +} + +// Rate Limit Quota Bucket Settings to apply on the successful ``bucket_matchers`` match. +// +// Specify this message in the :ref:`Matcher.OnMatch.action +// ` field of the +// ``bucket_matchers`` matcher tree to assign the matched requests to the Quota Bucket. +// Usage example: :ref:`RateLimitQuotaFilterConfig.bucket_matchers +// `. +// [#next-free-field: 6] +message RateLimitQuotaBucketSettings { + // Configures the behavior after the first request has been matched to the bucket, and before the + // the RLQS server returns the first quota assignment. + message NoAssignmentBehavior { + oneof no_assignment_behavior { + option (validate.required) = true; + + // Apply pre-configured rate limiting strategy until the server sends the first assignment. + type.v3.RateLimitStrategy fallback_rate_limit = 1; + } + } + + // Specifies the behavior when the bucket's assignment has expired, and cannot be refreshed for + // any reason. + message ExpiredAssignmentBehavior { + // Reuse the last known quota assignment, effectively extending it for the duration + // specified in the :ref:`expired_assignment_behavior_timeout + // ` + // field. + message ReuseLastAssignment { + } + + // Limit the time :ref:`ExpiredAssignmentBehavior + // ` + // is applied. If the server doesn't respond within this duration: + // + // 1. Selected ``ExpiredAssignmentBehavior`` is no longer applied. + // 2. The bucket is abandoned. The process of abandoning the bucket is described in the + // :ref:`AbandonAction ` + // message. + // 3. If a new request is matched into the bucket that has become abandoned, + // the data plane restarts the subscription to the bucket. The process of restarting the + // subscription is described in the :ref:`AbandonAction + // ` + // message. + // + // If not set, defaults to zero, and the bucket is abandoned immediately. + google.protobuf.Duration expired_assignment_behavior_timeout = 1 + [(validate.rules).duration = {gt {}}]; + + oneof expired_assignment_behavior { + option (validate.required) = true; + + // Apply the rate limiting strategy to all requests matched into the bucket until the RLQS + // server sends a new assignment, or the :ref:`expired_assignment_behavior_timeout + // ` + // runs out. + type.v3.RateLimitStrategy fallback_rate_limit = 2; + + // Reuse the last ``active`` assignment until the RLQS server sends a new assignment, or the + // :ref:`expired_assignment_behavior_timeout + // ` + // runs out. + ReuseLastAssignment reuse_last_assignment = 3; + } + } + + // Customize the deny response to the requests over the rate limit. + message DenyResponseSettings { + // HTTP response code to deny for HTTP requests (gRPC excluded). + // Defaults to 429 (:ref:`StatusCode.TooManyRequests`). + type.v3.HttpStatus http_status = 1; + + // HTTP response body used to deny for HTTP requests (gRPC excluded). + // If not set, an empty body is returned. + google.protobuf.BytesValue http_body = 2; + + // Configure the deny response for gRPC requests over the rate limit. + // Allows to specify the `RPC status code + // `_, + // and the error message. + // Defaults to the Status with the RPC Code ``UNAVAILABLE`` and empty message. + // + // To identify gRPC requests, Envoy checks that the ``Content-Type`` header is + // ``application/grpc``, or one of the various ``application/grpc+`` values. + // + // .. note:: + // The HTTP code for a gRPC response is always 200. + google.rpc.Status grpc_status = 3; + + // Specifies a list of HTTP headers that should be added to each response for requests that + // have been rate limited. Applies both to plain HTTP, and gRPC requests. + // The headers are added even when the rate limit quota was not enforced. + repeated config.core.v3.HeaderValueOption response_headers_to_add = 4 + [(validate.rules).repeated = {max_items: 10}]; + } + + // ``BucketIdBuilder`` makes it possible to build :ref:`BucketId + // ` with values substituted + // from the dynamic properties associated with each individual request. See usage examples in + // the docs to :ref:`bucket_id_builder + // ` + // field. + message BucketIdBuilder { + // Produces the value of the :ref:`BucketId + // ` map. + message ValueBuilder { + oneof value_specifier { + option (validate.required) = true; + + // Static string value — becomes the value in the :ref:`BucketId + // ` map as is. + string string_value = 1; + + // Dynamic value — evaluated for each request. Must produce a string output, which becomes + // the value in the :ref:`BucketId ` + // map. For example, extensions with the ``envoy.matching.http.input`` category can be used. + config.core.v3.TypedExtensionConfig custom_value = 2; + } + } + + // The map translated into the ``BucketId`` map. + // + // The ``string key`` of this map and becomes the key of ``BucketId`` map as is. + // + // The ``ValueBuilder value`` for the key can be: + // + // * static ``StringValue string_value`` — becomes the value in the ``BucketId`` map as is. + // * dynamic ``TypedExtensionConfig custom_value`` — evaluated for each request. Must produce + // a string output, which becomes the value in the the ``BucketId`` map. + // + // See usage examples in the docs to :ref:`bucket_id_builder + // ` + // field. + map bucket_id_builder = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // ``BucketId`` builder. + // + // :ref:`BucketId ` is a map from + // the string key to the string value which serves as bucket identifier common for on + // the control plane and the data plane. + // + // While ``BucketId`` is always static, ``BucketIdBuilder`` allows to populate map values + // with the dynamic properties associated with the each individual request. + // + // Example 1: static fields only + // + // ``BucketIdBuilder``: + // + // .. validated-code-block:: yaml + // :type-name: envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings.BucketIdBuilder + // + // bucket_id_builder: + // name: + // string_value: my_bucket + // hello: + // string_value: world + // + // Produces the following ``BucketId`` for all requests: + // + // .. validated-code-block:: yaml + // :type-name: envoy.service.rate_limit_quota.v3.BucketId + // + // bucket: + // name: my_bucket + // hello: world + // + // Example 2: static and dynamic fields + // + // .. validated-code-block:: yaml + // :type-name: envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings.BucketIdBuilder + // + // bucket_id_builder: + // name: + // string_value: my_bucket + // env: + // custom_value: + // typed_config: + // '@type': type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput + // header_name: environment + // + // In this example, the value of ``BucketId`` key ``env`` is substituted from the ``environment`` + // request header. + // + // This is equivalent to the following ``pseudo-code``: + // + // .. code-block:: yaml + // + // name: 'my_bucket' + // env: $header['environment'] + // + // For example, the request with the HTTP header ``env`` set to ``staging`` will produce + // the following ``BucketId``: + // + // .. validated-code-block:: yaml + // :type-name: envoy.service.rate_limit_quota.v3.BucketId + // + // bucket: + // name: my_bucket + // env: staging + // + // For the request with the HTTP header ``environment`` set to ``prod``, will produce: + // + // .. validated-code-block:: yaml + // :type-name: envoy.service.rate_limit_quota.v3.BucketId + // + // bucket: + // name: my_bucket + // env: prod + // + // .. note:: + // The order of ``BucketId`` keys do not matter. Buckets ``{ a: 'A', b: 'B' }`` and + // ``{ b: 'B', a: 'A' }`` are identical. + // + // If not set, requests will NOT be reported to the server, and will always limited + // according to :ref:`no_assignment_behavior + // ` + // configuration. + BucketIdBuilder bucket_id_builder = 1; + + // The interval at which the data plane (RLQS client) is to report quota usage for this bucket. + // + // When the first request is matched to a bucket with no assignment, the data plane is to report + // the request immediately in the :ref:`RateLimitQuotaUsageReports + // ` message. + // For the RLQS server, this signals that the data plane is now subscribed to + // the quota assignments in this bucket, and will start sending the assignment as described in + // the :ref:`RLQS documentation `. + // + // After sending the initial report, the data plane is to continue reporting the bucket usage with + // the internal specified in this field. + // + // If for any reason RLQS client doesn't receive the initial assignment for the reported bucket, + // the data plane will eventually consider the bucket abandoned and stop sending the usage + // reports. This is explained in more details at :ref:`Rate Limit Quota Service (RLQS) + // `. + // + // [#comment: 100000000 nanoseconds = 0.1 seconds] + google.protobuf.Duration reporting_interval = 2 [(validate.rules).duration = { + required: true + gt {nanos: 100000000} + }]; + + // Customize the deny response to the requests over the rate limit. + // If not set, the filter will be configured as if an empty message is set, + // and will behave according to the defaults specified in :ref:`DenyResponseSettings + // `. + DenyResponseSettings deny_response_settings = 3; + + // Configures the behavior in the "no assignment" state: after the first request has been + // matched to the bucket, and before the the RLQS server returns the first quota assignment. + // + // If not set, the default behavior is to allow all requests. + NoAssignmentBehavior no_assignment_behavior = 4; + + // Configures the behavior in the "expired assignment" state: the bucket's assignment has expired, + // and cannot be refreshed. + // + // If not set, the bucket is abandoned when its ``active`` assignment expires. + // The process of abandoning the bucket, and restarting the subscription is described in the + // :ref:`AbandonAction ` + // message. + ExpiredAssignmentBehavior expired_assignment_behavior = 5; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/http_inputs.proto b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/http_inputs.proto new file mode 100644 index 000000000000..c90199eb6189 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/http_inputs.proto @@ -0,0 +1,71 @@ +syntax = "proto3"; + +package envoy.type.matcher.v3; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.type.matcher.v3"; +option java_outer_classname = "HttpInputsProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3;matcherv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Common HTTP inputs] + +// Match input indicates that matching should be done on a specific request header. +// The resulting input string will be all headers for the given key joined by a comma, +// e.g. if the request contains two 'foo' headers with value 'bar' and 'baz', the input +// string will be 'bar,baz'. +// [#comment:TODO(snowp): Link to unified matching docs.] +// [#extension: envoy.matching.inputs.request_headers] +message HttpRequestHeaderMatchInput { + // The request header to match on. + string header_name = 1 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}]; +} + +// Match input indicates that matching should be done on a specific request trailer. +// The resulting input string will be all headers for the given key joined by a comma, +// e.g. if the request contains two 'foo' headers with value 'bar' and 'baz', the input +// string will be 'bar,baz'. +// [#comment:TODO(snowp): Link to unified matching docs.] +// [#extension: envoy.matching.inputs.request_trailers] +message HttpRequestTrailerMatchInput { + // The request trailer to match on. + string header_name = 1 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}]; +} + +// Match input indicating that matching should be done on a specific response header. +// The resulting input string will be all headers for the given key joined by a comma, +// e.g. if the response contains two 'foo' headers with value 'bar' and 'baz', the input +// string will be 'bar,baz'. +// [#comment:TODO(snowp): Link to unified matching docs.] +// [#extension: envoy.matching.inputs.response_headers] +message HttpResponseHeaderMatchInput { + // The response header to match on. + string header_name = 1 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}]; +} + +// Match input indicates that matching should be done on a specific response trailer. +// The resulting input string will be all headers for the given key joined by a comma, +// e.g. if the request contains two 'foo' headers with value 'bar' and 'baz', the input +// string will be 'bar,baz'. +// [#comment:TODO(snowp): Link to unified matching docs.] +// [#extension: envoy.matching.inputs.response_trailers] +message HttpResponseTrailerMatchInput { + // The response trailer to match on. + string header_name = 1 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}]; +} + +// Match input indicates that matching should be done on a specific query parameter. +// The resulting input string will be the first query parameter for the value +// 'query_param'. +// [#extension: envoy.matching.inputs.query_params] +message HttpRequestQueryParamMatchInput { + // The query parameter to match on. + string query_param = 1 [(validate.rules).string = {min_len: 1}]; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/v3/http_status.proto b/xds/third_party/envoy/src/main/proto/envoy/type/v3/http_status.proto new file mode 100644 index 000000000000..ab03e1b2b727 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/type/v3/http_status.proto @@ -0,0 +1,143 @@ +syntax = "proto3"; + +package envoy.type.v3; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.type.v3"; +option java_outer_classname = "HttpStatusProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/v3;typev3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: HTTP status codes] + +// HTTP response codes supported in Envoy. +// For more details: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml +enum StatusCode { + // Empty - This code not part of the HTTP status code specification, but it is needed for proto + // `enum` type. + Empty = 0; + + Continue = 100; + + OK = 200; + + Created = 201; + + Accepted = 202; + + NonAuthoritativeInformation = 203; + + NoContent = 204; + + ResetContent = 205; + + PartialContent = 206; + + MultiStatus = 207; + + AlreadyReported = 208; + + IMUsed = 226; + + MultipleChoices = 300; + + MovedPermanently = 301; + + Found = 302; + + SeeOther = 303; + + NotModified = 304; + + UseProxy = 305; + + TemporaryRedirect = 307; + + PermanentRedirect = 308; + + BadRequest = 400; + + Unauthorized = 401; + + PaymentRequired = 402; + + Forbidden = 403; + + NotFound = 404; + + MethodNotAllowed = 405; + + NotAcceptable = 406; + + ProxyAuthenticationRequired = 407; + + RequestTimeout = 408; + + Conflict = 409; + + Gone = 410; + + LengthRequired = 411; + + PreconditionFailed = 412; + + PayloadTooLarge = 413; + + URITooLong = 414; + + UnsupportedMediaType = 415; + + RangeNotSatisfiable = 416; + + ExpectationFailed = 417; + + MisdirectedRequest = 421; + + UnprocessableEntity = 422; + + Locked = 423; + + FailedDependency = 424; + + UpgradeRequired = 426; + + PreconditionRequired = 428; + + TooManyRequests = 429; + + RequestHeaderFieldsTooLarge = 431; + + InternalServerError = 500; + + NotImplemented = 501; + + BadGateway = 502; + + ServiceUnavailable = 503; + + GatewayTimeout = 504; + + HTTPVersionNotSupported = 505; + + VariantAlsoNegotiates = 506; + + InsufficientStorage = 507; + + LoopDetected = 508; + + NotExtended = 510; + + NetworkAuthenticationRequired = 511; +} + +// HTTP status. +message HttpStatus { + option (udpa.annotations.versioning).previous_message_type = "envoy.type.HttpStatus"; + + // Supplies HTTP response code. + StatusCode code = 1 [(validate.rules).enum = {defined_only: true not_in: 0}]; +}