// Copyright 2025 Envoy Project Authors
//
// 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 envoy.config.rbac.v3;

import "envoy/config/core/v3/address.proto";
import "envoy/config/core/v3/extension.proto";
import "envoy/config/route/v3/route_components.proto";
import "envoy/type/matcher/v3/filter_state.proto";
import "envoy/type/matcher/v3/metadata.proto";
import "envoy/type/matcher/v3/path.proto";
import "envoy/type/matcher/v3/string.proto";
import "envoy/type/v3/range.proto";

import "google/api/expr/v1alpha1/checked.proto";
import "google/api/expr/v1alpha1/syntax.proto";

import "envoy/annotations/deprecation.proto";
import "udpa/annotations/migrate.proto";
import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.config.rbac.v3";
option java_outer_classname = "RbacProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3;rbacv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Role Based Access Control (RBAC)]

enum MetadataSource {
  // Query :ref:`dynamic metadata <well_known_dynamic_metadata>`
  DYNAMIC = 0;

  // Query :ref:`route metadata <envoy_v3_api_field_config.route.v3.Route.metadata>`
  ROUTE = 1;
}

// Role Based Access Control (RBAC) provides service-level and method-level access control for a
// service. Requests are allowed or denied based on the ``action`` and whether a matching policy is
// found. For instance, if the action is ALLOW and a matching policy is found the request should be
// allowed.
//
// RBAC can also be used to make access logging decisions by communicating with access loggers
// through dynamic metadata. When the action is LOG and at least one policy matches, the
// ``access_log_hint`` value in the shared key namespace 'envoy.common' is set to ``true`` indicating
// the request should be logged.
//
// Here is an example of RBAC configuration. It has two policies:
//
// * Service account ``cluster.local/ns/default/sa/admin`` has full access to the service, and so
//   does "cluster.local/ns/default/sa/superuser".
//
// * Any user can read (``GET``) the service at paths with prefix ``/products``, so long as the
//   destination port is either 80 or 443.
//
//  .. code-block:: yaml
//
//   action: ALLOW
//   policies:
//     "service-admin":
//       permissions:
//         - any: true
//       principals:
//         - authenticated:
//             principal_name:
//               exact: "cluster.local/ns/default/sa/admin"
//         - authenticated:
//             principal_name:
//               exact: "cluster.local/ns/default/sa/superuser"
//     "product-viewer":
//       permissions:
//           - and_rules:
//               rules:
//                 - header:
//                     name: ":method"
//                     string_match:
//                       exact: "GET"
//                 - url_path:
//                     path: { prefix: "/products" }
//                 - or_rules:
//                     rules:
//                       - destination_port: 80
//                       - destination_port: 443
//       principals:
//         - any: true
//
message RBAC {
  option (udpa.annotations.versioning).previous_message_type = "envoy.config.rbac.v2.RBAC";

  // Should we do safe-list or block-list style access control?
  enum Action {
    // The policies grant access to principals. The rest are denied. This is safe-list style
    // access control. This is the default type.
    ALLOW = 0;

    // The policies deny access to principals. The rest are allowed. This is block-list style
    // access control.
    DENY = 1;

    // The policies set the ``access_log_hint`` dynamic metadata key based on if requests match.
    // All requests are allowed.
    LOG = 2;
  }

  message AuditLoggingOptions {
    // Deny and allow here refer to RBAC decisions, not actions.
    enum AuditCondition {
      // Never audit.
      NONE = 0;

      // Audit when RBAC denies the request.
      ON_DENY = 1;

      // Audit when RBAC allows the request.
      ON_ALLOW = 2;

      // Audit whether RBAC allows or denies the request.
      ON_DENY_AND_ALLOW = 3;
    }

    // [#not-implemented-hide:]
    message AuditLoggerConfig {
      // Typed logger configuration.
      //
      // [#extension-category: envoy.rbac.audit_loggers]
      core.v3.TypedExtensionConfig audit_logger = 1;

      // If true, when the logger is not supported, the data plane will not NACK but simply ignore it.
      bool is_optional = 2;
    }

    // Condition for the audit logging to happen.
    // If this condition is met, all the audit loggers configured here will be invoked.
    //
    // [#not-implemented-hide:]
    AuditCondition audit_condition = 1 [(validate.rules).enum = {defined_only: true}];

    // Configurations for RBAC-based authorization audit loggers.
    //
    // [#not-implemented-hide:]
    repeated AuditLoggerConfig logger_configs = 2;
  }

  // The action to take if a policy matches. Every action either allows or denies a request,
  // and can also carry out action-specific operations.
  //
  // Actions:
  //
  //  * ``ALLOW``: Allows the request if and only if there is a policy that matches
  //    the request.
  //  * ``DENY``: Allows the request if and only if there are no policies that
  //    match the request.
  //  * ``LOG``: Allows all requests. If at least one policy matches, the dynamic
  //    metadata key ``access_log_hint`` is set to the value ``true`` under the shared
  //    key namespace ``envoy.common``. If no policies match, it is set to ``false``.
  //    Other actions do not modify this key.
  //
  Action action = 1 [(validate.rules).enum = {defined_only: true}];

  // Maps from policy name to policy. A match occurs when at least one policy matches the request.
  // The policies are evaluated in lexicographic order of the policy name.
  map<string, Policy> policies = 2;

  // Audit logging options that include the condition for audit logging to happen
  // and audit logger configurations.
  //
  // [#not-implemented-hide:]
  AuditLoggingOptions audit_logging_options = 3;
}

// Policy specifies a role and the principals that are assigned/denied the role.
// A policy matches if and only if at least one of its permissions match the
// action taking place AND at least one of its principals match the downstream
// AND the condition is true if specified.
message Policy {
  option (udpa.annotations.versioning).previous_message_type = "envoy.config.rbac.v2.Policy";

  // Required. The set of permissions that define a role. Each permission is
  // matched with OR semantics. To match all actions for this policy, a single
  // Permission with the ``any`` field set to true should be used.
  repeated Permission permissions = 1 [(validate.rules).repeated = {min_items: 1}];

  // Required. The set of principals that are assigned/denied the role based on
  // “action”. Each principal is matched with OR semantics. To match all
  // downstreams for this policy, a single Principal with the ``any`` field set to
  // true should be used.
  repeated Principal principals = 2 [(validate.rules).repeated = {min_items: 1}];

  // An optional symbolic expression specifying an access control
  // :ref:`condition <arch_overview_condition>`. The condition is combined
  // with the permissions and the principals as a clause with AND semantics.
  // Only be used when checked_condition is not used.
  google.api.expr.v1alpha1.Expr condition = 3
      [(udpa.annotations.field_migrate).oneof_promotion = "expression_specifier"];

  // [#not-implemented-hide:]
  // An optional symbolic expression that has been successfully type checked.
  // Only be used when condition is not used.
  google.api.expr.v1alpha1.CheckedExpr checked_condition = 4
      [(udpa.annotations.field_migrate).oneof_promotion = "expression_specifier"];
}

// SourcedMetadata enables matching against metadata from different sources in the request processing
// pipeline. It extends the base MetadataMatcher functionality by allowing specification of where the
// metadata should be sourced from, rather than only matching against dynamic metadata.
//
// The matcher can be configured to look up metadata from:
//
// * Dynamic metadata: Runtime metadata added by filters during request processing
// * Route metadata: Static metadata configured on the route entry
//
message SourcedMetadata {
  // Metadata matcher configuration that defines what metadata to match against. This includes the filter name,
  // metadata key path, and expected value.
  type.matcher.v3.MetadataMatcher metadata_matcher = 1
      [(validate.rules).message = {required: true}];

  // Specifies which metadata source should be used for matching. If not set,
  // defaults to DYNAMIC (dynamic metadata). Set to ROUTE to match against
  // static metadata configured on the route entry.
  MetadataSource metadata_source = 2 [(validate.rules).enum = {defined_only: true}];
}

// Permission defines an action (or actions) that a principal can take.
// [#next-free-field: 15]
message Permission {
  option (udpa.annotations.versioning).previous_message_type = "envoy.config.rbac.v2.Permission";

  // Used in the ``and_rules`` and ``or_rules`` fields in the ``rule`` oneof. Depending on the context,
  // each are applied with the associated behavior.
  message Set {
    option (udpa.annotations.versioning).previous_message_type =
        "envoy.config.rbac.v2.Permission.Set";

    repeated Permission rules = 1 [(validate.rules).repeated = {min_items: 1}];
  }

  oneof rule {
    option (validate.required) = true;

    // A set of rules that all must match in order to define the action.
    Set and_rules = 1;

    // A set of rules where at least one must match in order to define the action.
    Set or_rules = 2;

    // When any is set, it matches any action.
    bool any = 3 [(validate.rules).bool = {const: true}];

    // A header (or pseudo-header such as ``:path`` or ``:method``) on the incoming HTTP request. Only available
    // for HTTP request.
    //
    // .. note::
    //
    //   The pseudo-header ``:path`` includes the query and fragment string. Use the ``url_path`` field if you
    //   want to match the URL path without the query and fragment string.
    //
    route.v3.HeaderMatcher header = 4;

    // A URL path on the incoming HTTP request. Only available for HTTP.
    type.matcher.v3.PathMatcher url_path = 10;

    // A CIDR block that describes the destination IP.
    core.v3.CidrRange destination_ip = 5;

    // A port number that describes the destination port connecting to.
    uint32 destination_port = 6 [(validate.rules).uint32 = {lte: 65535}];

    // A port number range that describes a range of destination ports connecting to.
    type.v3.Int32Range destination_port_range = 11;

    // Metadata that describes additional information about the action. This field is deprecated; please use
    // :ref:`sourced_metadata<envoy_v3_api_field_config.rbac.v3.Permission.sourced_metadata>` instead.
    type.matcher.v3.MetadataMatcher metadata = 7
        [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];

    // Negates matching the provided permission. For instance, if the value of
    // ``not_rule`` would match, this permission would not match. Conversely, if
    // the value of ``not_rule`` would not match, this permission would match.
    Permission not_rule = 8;

    // The request server from the client's connection request. This is typically TLS SNI.
    //
    // .. attention::
    //
    //   The behavior of this field may be affected by how Envoy is configured
    //   as explained below.
    //
    //   * If the :ref:`TLS Inspector <config_listener_filters_tls_inspector>`
    //     filter is not added, and if a ``FilterChainMatch`` is not defined for
    //     the :ref:`server name
    //     <envoy_v3_api_field_config.listener.v3.FilterChainMatch.server_names>`,
    //     a TLS connection's requested SNI server name will be treated as if it
    //     wasn't present.
    //
    //   * A :ref:`listener filter <arch_overview_listener_filters>` may
    //     overwrite a connection's requested server name within Envoy.
    //
    // Please refer to :ref:`this FAQ entry <faq_how_to_setup_sni>` to learn how to setup SNI.
    type.matcher.v3.StringMatcher requested_server_name = 9;

    // Extension for configuring custom matchers for RBAC.
    // [#extension-category: envoy.rbac.matchers]
    core.v3.TypedExtensionConfig matcher = 12;

    // URI template path matching.
    // [#extension-category: envoy.path.match]
    core.v3.TypedExtensionConfig uri_template = 13;

    // Matches against metadata from either dynamic state or route configuration. Preferred over the
    // ``metadata`` field as it provides more flexibility in metadata source selection.
    SourcedMetadata sourced_metadata = 14;
  }
}

// Principal defines an identity or a group of identities for a downstream
// subject.
// [#next-free-field: 15]
message Principal {
  option (udpa.annotations.versioning).previous_message_type = "envoy.config.rbac.v2.Principal";

  // Used in the ``and_ids`` and ``or_ids`` fields in the ``identifier`` oneof.
  // Depending on the context, each are applied with the associated behavior.
  message Set {
    option (udpa.annotations.versioning).previous_message_type =
        "envoy.config.rbac.v2.Principal.Set";

    repeated Principal ids = 1 [(validate.rules).repeated = {min_items: 1}];
  }

  // Authentication attributes for a downstream.
  // It is recommended to NOT use this type, but instead use
  // :ref:`MTlsAuthenticated <envoy_v3_api_msg_extensions.rbac.principals.mtls_authenticated.v3.Config>`,
  // configured via :ref:`custom <envoy_v3_api_field_config.rbac.v3.Principal.custom>`,
  // which should be used for most use cases due to its improved security.
  message Authenticated {
    option (udpa.annotations.versioning).previous_message_type =
        "envoy.config.rbac.v2.Principal.Authenticated";

    reserved 1;

    // The name of the principal. If set, The URI SAN or DNS SAN in that order
    // is used from the certificate, otherwise the subject field is used. If
    // unset, it applies to any user that is allowed by the downstream TLS configuration.
    // If :ref:`require_client_certificate <envoy_v3_api_field_extensions.transport_sockets.tls.v3.DownstreamTlsContext.require_client_certificate>`
    // is false or :ref:`trust_chain_verification <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.trust_chain_verification>`
    // is set to :ref:`ACCEPT_UNTRUSTED <envoy_v3_api_enum_value_extensions.transport_sockets.tls.v3.CertificateValidationContext.TrustChainVerification.ACCEPT_UNTRUSTED>`,
    // then no authentication is required.
    type.matcher.v3.StringMatcher principal_name = 2;
  }

  oneof identifier {
    option (validate.required) = true;

    // A set of identifiers that all must match in order to define the downstream.
    Set and_ids = 1;

    // A set of identifiers at least one must match in order to define the downstream.
    Set or_ids = 2;

    // When any is set, it matches any downstream.
    bool any = 3 [(validate.rules).bool = {const: true}];

    // Authenticated attributes that identify the downstream.
    // It is recommended to NOT use this field, but instead use
    // :ref:`MTlsAuthenticated <envoy_v3_api_msg_extensions.rbac.principals.mtls_authenticated.v3.Config>`,
    // configured via :ref:`custom <envoy_v3_api_field_config.rbac.v3.Principal.custom>`,
    // which should be used for most use cases due to its improved security.
    Authenticated authenticated = 4;

    // A CIDR block that describes the downstream IP.
    // This address will honor proxy protocol, but will not honor XFF.
    //
    // This field is deprecated; either use :ref:`remote_ip
    // <envoy_v3_api_field_config.rbac.v3.Principal.remote_ip>` for the same
    // behavior, or use
    // :ref:`direct_remote_ip <envoy_v3_api_field_config.rbac.v3.Principal.direct_remote_ip>`.
    core.v3.CidrRange source_ip = 5
        [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];

    // A CIDR block that describes the downstream remote/origin address.
    //
    // .. note::
    //
    //   This is always the physical peer even if the
    //   :ref:`remote_ip <envoy_v3_api_field_config.rbac.v3.Principal.remote_ip>` is inferred from the
    //   x-forwarder-for header, the proxy protocol, etc.
    //
    core.v3.CidrRange direct_remote_ip = 10;

    // A CIDR block that describes the downstream remote/origin address.
    //
    // .. note::
    //
    //   This may not be the physical peer and could be different from the :ref:`direct_remote_ip
    //   <envoy_v3_api_field_config.rbac.v3.Principal.direct_remote_ip>`. E.g, if the remote ip is inferred from
    //   the x-forwarder-for header, the proxy protocol, etc.
    //
    core.v3.CidrRange remote_ip = 11;

    // A header (or pseudo-header such as ``:path`` or ``:method``) on the incoming HTTP request. Only available
    // for HTTP request.
    //
    // .. note::
    //
    //   The pseudo-header ``:path`` includes the query and fragment string. Use the ``url_path`` field if you
    //   want to match the URL path without the query and fragment string.
    //
    route.v3.HeaderMatcher header = 6;

    // A URL path on the incoming HTTP request. Only available for HTTP.
    type.matcher.v3.PathMatcher url_path = 9;

    // Metadata that describes additional information about the principal. This field is deprecated; please use
    // :ref:`sourced_metadata<envoy_v3_api_field_config.rbac.v3.Principal.sourced_metadata>` instead.
    type.matcher.v3.MetadataMatcher metadata = 7
        [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];

    // Identifies the principal using a filter state object.
    type.matcher.v3.FilterStateMatcher filter_state = 12;

    // Negates matching the provided principal. For instance, if the value of
    // ``not_id`` would match, this principal would not match. Conversely, if the
    // value of ``not_id`` would not match, this principal would match.
    Principal not_id = 8;

    // Matches against metadata from either dynamic state or route configuration. Preferred over the
    // ``metadata`` field as it provides more flexibility in metadata source selection.
    SourcedMetadata sourced_metadata = 13;

    // Extension for configuring custom principals for RBAC.
    // [#extension-category: envoy.rbac.principals]
    core.v3.TypedExtensionConfig custom = 14;
  }
}

// Action defines the result of allowance or denial when a request matches the matcher.
message Action {
  // The name indicates the policy name.
  string name = 1 [(validate.rules).string = {min_len: 1}];

  // The action to take if the matcher matches. Every action either allows or denies a request,
  // and can also carry out action-specific operations.
  //
  // **Actions:**
  //
  //  * ``ALLOW``: If the request gets matched on ALLOW, it is permitted.
  //  * ``DENY``: If the request gets matched on DENY, it is not permitted.
  //  * ``LOG``: If the request gets matched on LOG, it is permitted. Besides, the
  //    dynamic metadata key ``access_log_hint`` under the shared key namespace
  //    ``envoy.common`` will be set to the value ``true``.
  //  * If the request cannot get matched, it will fallback to ``DENY``.
  //
  // **Log behavior:**
  //
  //  If the RBAC matcher contains at least one LOG action, the dynamic
  //  metadata key ``access_log_hint`` will be set based on if the request
  //  get matched on the LOG action.
  //
  RBAC.Action action = 2;
}
