// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

syntax = "proto3";

package hashicorp.consul.internal.peering;

import "annotations/ratelimit/ratelimit.proto";
import "google/protobuf/timestamp.proto";
import "private/pbcommon/common.proto";

// PeeringService handles operations for establishing peering relationships
// between disparate Consul clusters.
service PeeringService {
  rpc GenerateToken(GenerateTokenRequest) returns (GenerateTokenResponse) {
    option (hashicorp.consul.internal.ratelimit.spec) = {
      operation_type: OPERATION_TYPE_WRITE,
      operation_category: OPERATION_CATEGORY_PEERING
    };
  }

  rpc Establish(EstablishRequest) returns (EstablishResponse) {
    option (hashicorp.consul.internal.ratelimit.spec) = {
      operation_type: OPERATION_TYPE_WRITE,
      operation_category: OPERATION_CATEGORY_PEERING
    };
  }

  rpc PeeringRead(PeeringReadRequest) returns (PeeringReadResponse) {
    option (hashicorp.consul.internal.ratelimit.spec) = {
      operation_type: OPERATION_TYPE_READ,
      operation_category: OPERATION_CATEGORY_PEERING
    };
  }

  rpc PeeringList(PeeringListRequest) returns (PeeringListResponse) {
    option (hashicorp.consul.internal.ratelimit.spec) = {
      operation_type: OPERATION_TYPE_READ,
      operation_category: OPERATION_CATEGORY_PEERING
    };
  }

  rpc PeeringDelete(PeeringDeleteRequest) returns (PeeringDeleteResponse) {
    option (hashicorp.consul.internal.ratelimit.spec) = {
      operation_type: OPERATION_TYPE_WRITE,
      operation_category: OPERATION_CATEGORY_PEERING
    };
  }

  // TODO(peering): As of writing, this method is only used in tests to set up Peerings in the state store.
  // Consider removing if we can find another way to populate state store in peering_endpoint_test.go
  rpc PeeringWrite(PeeringWriteRequest) returns (PeeringWriteResponse) {
    option (hashicorp.consul.internal.ratelimit.spec) = {
      operation_type: OPERATION_TYPE_WRITE,
      operation_category: OPERATION_CATEGORY_PEERING
    };
  }

  // TODO(peering): Rename this to PeeredServiceRoots? or something like that?
  rpc TrustBundleListByService(TrustBundleListByServiceRequest) returns (TrustBundleListByServiceResponse) {
    option (hashicorp.consul.internal.ratelimit.spec) = {
      operation_type: OPERATION_TYPE_READ,
      operation_category: OPERATION_CATEGORY_PEERING
    };
  }

  rpc TrustBundleRead(TrustBundleReadRequest) returns (TrustBundleReadResponse) {
    option (hashicorp.consul.internal.ratelimit.spec) = {
      operation_type: OPERATION_TYPE_READ,
      operation_category: OPERATION_CATEGORY_PEERING
    };
  }
}

// PeeringState enumerates all the states a peering can be in.
enum PeeringState {
  // Undefined represents an unset value for PeeringState during
  // writes.
  UNDEFINED = 0;

  // Pending means the peering was created by generating a peering token.
  // Peerings stay in a pending state until the peer uses the token to dial
  // the local cluster.
  PENDING = 1;

  // Establishing means the peering is being established from a peering token.
  // This is the initial state for dialing peers.
  ESTABLISHING = 2;

  // Active means that the peering connection is active and healthy.
  ACTIVE = 3;

  // Failing means the peering connection has been interrupted but has not yet
  // been terminated.
  FAILING = 4;

  // Deleting means a peering was marked for deletion and is in the process
  // of being deleted.
  DELETING = 5;

  // Terminated means the peering relationship has been removed.
  TERMINATED = 6;
}

// SecretsWriteRequest encodes a request to write a peering secret as the result
// of some operation. Different operations, such as generating a peering token,
// lead to modifying the known secrets associated with a peering.
message SecretsWriteRequest {
  // PeerID is the local UUID of the peering this request applies to.
  string PeerID = 1;

  oneof Request {
    GenerateTokenRequest generate_token = 2;
    ExchangeSecretRequest exchange_secret = 3;
    PromotePendingRequest promote_pending = 4;
    EstablishRequest establish = 5;
  }

  // GenerateTokenRequest encodes a request to persist a peering establishment
  // secret. It is triggered by generating a new peering token for a peer cluster.
  message GenerateTokenRequest {
    // establishment_secret is the proposed secret ID to store as the establishment
    // secret for this peering.
    string establishment_secret = 1;
  }

  // ExchangeSecretRequest encodes a request to persist a pending stream secret
  // secret. It is triggered by an acceptor peer generating a long-lived stream secret
  // in exchange for an establishment secret.
  message ExchangeSecretRequest {
    // establishment_secret is the secret to exchange for the given pending stream secret.
    string establishment_secret = 1;

    // pending_stream_secret is the proposed secret ID to store as the pending stream
    // secret for this peering.
    string pending_stream_secret = 2;
  }

  // PromotePendingRequest encodes a request to promote a pending stream secret
  // to be an active stream secret. It is triggered when the accepting stream handler
  // validates an Open request from a peer with a pending stream secret.
  message PromotePendingRequest {
    // active_stream_secret is the proposed secret ID to store as the active stream
    // secret for this peering.
    string active_stream_secret = 1;
  }

  // EstablishRequest encodes a request to persist an active stream secret.
  // It is triggered after a dialing peer exchanges their establishment secret
  // for a long-lived active stream secret.
  message EstablishRequest {
    // active_stream_secret is the proposed secret ID to store as the active stream
    // secret for this peering.
    string active_stream_secret = 1;
  }
}

// PeeringSecrets defines a secret used for authenticating/authorizing peer clusters.
message PeeringSecrets {
  // PeerID is the local UUID of the peering this secret was generated for.
  string PeerID = 1;

  message Establishment {
    // SecretID is the one-time-use peering establishment secret.
    string SecretID = 1;
  }

  message Stream {
    // ActiveSecretID is the active UUID-v4 secret being used for authorization at
    // the peering stream.
    string ActiveSecretID = 1;

    // PendingSecretID is a UUID-v4 secret introduced during secret rotation.
    // When a peering is established or re-established, both the active secret and
    // pending secret are considered valid. However, once the dialing peer uses
    // the pending secret then it transitions to active and the previously active
    // secret is discarded.
    //
    // Pending secret IDs are only valid for long-lived stream secrets.
    // Only one establishment secret can exist for a peer at a time since they
    // are designed for one-time use.
    string PendingSecretID = 2;
  }

  Establishment establishment = 2;

  Stream stream = 3;
}

// Peering defines a peering relationship between two disparate Consul clusters
//
// mog annotation:
//
// target=github.com/hashicorp/consul/api.Peering
// output=peering.gen.go
// name=API
message Peering {
  // ID is a datacenter-scoped UUID for the peering.
  // The ID is generated when a peering is first written to the state store.
  string ID = 1;

  // Name is the local alias for the peering relationship.
  string Name = 2;

  // Partition is the local partition connecting to the peer.
  string Partition = 3;

  // DeletedAt is the time when the Peering was marked for deletion
  // This is nullable so that we can omit if empty when encoding in JSON
  // mog: func-to=TimePtrFromProto func-from=TimePtrToProto
  google.protobuf.Timestamp DeletedAt = 4;

  // Meta is a mapping of some string value to any other string value
  map<string, string> Meta = 5;

  // State is one of the valid PeeringState values to represent the status of
  // peering relationship.
  //
  // mog: func-to=PeeringStateToAPI func-from=PeeringStateFromAPI
  PeeringState State = 6;

  // PeerID is the ID that our peer assigned to this peering.
  // This ID is to be used when dialing the peer, so that it can know who dialed it.
  string PeerID = 7;

  // PeerCAPems contains all the CA certificates for the remote peer.
  repeated string PeerCAPems = 8;

  // PeerServerName is the name of the remote server as it relates to TLS.
  string PeerServerName = 9;

  // PeerServerAddresses contains all the the connection addresses for the remote peer.
  repeated string PeerServerAddresses = 10;

  // StreamStatus contains information computed on read based on the state of the stream.
  //
  // mog: func-to=StreamStatusToAPI func-from=StreamStatusFromAPI
  StreamStatus StreamStatus = 13;

  // CreateIndex is the Raft index at which the Peering was created.
  // @gotags: bexpr:"-"
  uint64 CreateIndex = 11;

  // ModifyIndex is the latest Raft index at which the Peering. was modified.
  // @gotags: bexpr:"-"
  uint64 ModifyIndex = 12;

  // Remote contains metadata about the remote peer.
  RemoteInfo Remote = 17;

  // ManualServerAddresses provides a list of manually specified server addresses from the
  // user. If this is defined, then the automatic PeerServerAddresses are ignored.
  repeated string ManualServerAddresses = 18;
}

// RemoteInfo contains metadata about the remote peer.

// mog annotation:
//
// target=github.com/hashicorp/consul/api.PeeringRemoteInfo
// output=peering.gen.go
// name=API
message RemoteInfo {
  // Partition is the remote peer's partition.
  string Partition = 1;
  // Datacenter is the remote peer's datacenter.
  string Datacenter = 2;

  // Locality identifies where the peer is running.
  // mog: func-to=LocalityToAPI func-from=LocalityFromAPI
  common.Locality Locality = 3;
}

// StreamStatus represents information about an active peering stream.
message StreamStatus {
  // ImportedServices is the list of services imported from this peering.
  repeated string ImportedServices = 1;

  // ExportedServices is the list of services exported to this peering.
  repeated string ExportedServices = 2;

  // LastHeartbeat represents when the last heartbeat message was received.
  google.protobuf.Timestamp LastHeartbeat = 3;

  // LastReceive represents when any message was last received, regardless of success or error.
  google.protobuf.Timestamp LastReceive = 4;

  // LastSend represents when any message was last sent, regardless of success or error.
  google.protobuf.Timestamp LastSend = 5;
}

// PeeringTrustBundle holds the trust information for validating requests from a peer.
message PeeringTrustBundle {
  // TrustDomain is the domain for the bundle, example.com, foo.bar.gov for example. Note that this must not have a prefix such as "spiffe://".
  string TrustDomain = 1;

  // PeerName associates the trust bundle with a peer.
  string PeerName = 2;

  // Partition isolates the bundle from other trust bundles in separate local partitions.
  string Partition = 3;

  // RootPEMs holds ASN.1 DER encoded X.509 certificate data for the trust bundle.
  repeated string RootPEMs = 4;

  // ExportedPartition references the remote partition of the peer
  // which sent this trust bundle. Used for generating SpiffeIDs.
  string ExportedPartition = 5;

  // CreateIndex is the Raft index at which the trust domain was created.
  // @gotags: bexpr:"-"
  uint64 CreateIndex = 6;

  // ModifyIndex is the latest Raft index at which the trust bundle was modified.
  // @gotags: bexpr:"-"
  uint64 ModifyIndex = 7;
}

// PeeringServerAddresses contains the latest snapshot of all known
// server addresses for a peer.
message PeeringServerAddresses {
  repeated string Addresses = 1;
}

message PeeringReadRequest {
  string Name = 1;
  string Partition = 2;
}

message PeeringReadResponse {
  Peering Peering = 1;
}

message PeeringListRequest {
  string Partition = 1;
}

message PeeringListResponse {
  repeated Peering Peerings = 1;
  uint64 OBSOLETE_Index = 2; // Deprecated in favor of gRPC metadata
}

message PeeringWriteRequest {
  // Peering is the peering to write with the request.
  Peering Peering = 1;

  // SecretsWriteRequest contains the optional peering secrets to persist
  // with the peering. Peering secrets are not embedded in the peering
  // object to avoid leaking them.
  SecretsWriteRequest SecretsRequest = 2;

  map<string, string> Meta = 3;
}

// TODO(peering): Consider returning Peering if we keep this endpoint around
message PeeringWriteResponse {}

message PeeringDeleteRequest {
  string Name = 1;

  string Partition = 2;
}

message PeeringDeleteResponse {}

message TrustBundleListByServiceRequest {
  string ServiceName = 1;
  string Namespace = 2;
  string Partition = 3;
  string Kind = 4;
}

message TrustBundleListByServiceResponse {
  uint64 OBSOLETE_Index = 1; // Deprecated in favor of gRPC metadata
  repeated PeeringTrustBundle Bundles = 2;
}

message TrustBundleReadRequest {
  string Name = 1;
  string Partition = 2;
}

message TrustBundleReadResponse {
  uint64 OBSOLETE_Index = 1; // Deprecated in favor of gRPC metadata
  PeeringTrustBundle Bundle = 2;
}

// This is a purely internal type and does not require query metadata.
message PeeringTerminateByIDRequest {
  string ID = 1;
}

message PeeringTerminateByIDResponse {}

message PeeringTrustBundleWriteRequest {
  PeeringTrustBundle PeeringTrustBundle = 1;
}

message PeeringTrustBundleWriteResponse {}

message PeeringTrustBundleDeleteRequest {
  string Name = 1;

  string Partition = 2;
}

message PeeringTrustBundleDeleteResponse {}

// mog annotation:
//
// target=github.com/hashicorp/consul/api.PeeringGenerateTokenRequest
// output=peering.gen.go
// name=API
message GenerateTokenRequest {
  // Name of the remote peer.
  string PeerName = 1;

  // Partition is the local partition being peered.
  string Partition = 2;

  // Meta is a mapping of some string value to any other string value
  map<string, string> Meta = 5;

  // ServerExternalAddresses is a list of addresses to put into the generated token. This could be used to specify
  // load balancer(s) or external IPs to reach the servers from the dialing side, and will override any server
  // addresses obtained from the "consul" service.
  repeated string ServerExternalAddresses = 6;
}

// mog annotation:
//
// target=github.com/hashicorp/consul/api.PeeringGenerateTokenResponse
// output=peering.gen.go
// name=API
message GenerateTokenResponse {
  // PeeringToken is an opaque string provided to the remote peer for it to complete
  // the peering initialization handshake.
  string PeeringToken = 1;
}

// mog annotation:
//
// target=github.com/hashicorp/consul/api.PeeringEstablishRequest
// output=peering.gen.go
// name=API
message EstablishRequest {
  // Name of the remote peer.
  string PeerName = 1;

  // The peering token returned from the peer's GenerateToken endpoint.
  string PeeringToken = 2;

  // Partition is the local partition being peered.
  string Partition = 3;

  // Meta is a mapping of some string value to any other string value
  map<string, string> Meta = 4;
}

// mog annotation:
//
// target=github.com/hashicorp/consul/api.PeeringEstablishResponse
// output=peering.gen.go
// name=API
message EstablishResponse {}
