diff --git a/api/gen/proto/go/teleport/decision/v1alpha1/ssh_identity.pb.go b/api/gen/proto/go/teleport/decision/v1alpha1/ssh_identity.pb.go
index afd8582b4e39d..8e47b7ca4109f 100644
--- a/api/gen/proto/go/teleport/decision/v1alpha1/ssh_identity.pb.go
+++ b/api/gen/proto/go/teleport/decision/v1alpha1/ssh_identity.pb.go
@@ -21,8 +21,10 @@
package decisionpb
import (
+ v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/trait/v1"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
@@ -35,11 +37,199 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
+// CertExtensionMode specifies the type of extension to use in the cert. This type
+// must be kept up to date with types.CertExtensionMode.
+type CertExtensionMode int32
+
+const (
+ // CERT_EXTENSION_MODE_UNSPECIFIED is the default value and should not be used.
+ CertExtensionMode_CERT_EXTENSION_MODE_UNSPECIFIED CertExtensionMode = 0
+ // EXTENSION represents a cert extension that may or may not be
+ // honored by the server.
+ CertExtensionMode_CERT_EXTENSION_MODE_EXTENSION CertExtensionMode = 1
+)
+
+// Enum value maps for CertExtensionMode.
+var (
+ CertExtensionMode_name = map[int32]string{
+ 0: "CERT_EXTENSION_MODE_UNSPECIFIED",
+ 1: "CERT_EXTENSION_MODE_EXTENSION",
+ }
+ CertExtensionMode_value = map[string]int32{
+ "CERT_EXTENSION_MODE_UNSPECIFIED": 0,
+ "CERT_EXTENSION_MODE_EXTENSION": 1,
+ }
+)
+
+func (x CertExtensionMode) Enum() *CertExtensionMode {
+ p := new(CertExtensionMode)
+ *p = x
+ return p
+}
+
+func (x CertExtensionMode) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (CertExtensionMode) Descriptor() protoreflect.EnumDescriptor {
+ return file_teleport_decision_v1alpha1_ssh_identity_proto_enumTypes[0].Descriptor()
+}
+
+func (CertExtensionMode) Type() protoreflect.EnumType {
+ return &file_teleport_decision_v1alpha1_ssh_identity_proto_enumTypes[0]
+}
+
+func (x CertExtensionMode) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use CertExtensionMode.Descriptor instead.
+func (CertExtensionMode) EnumDescriptor() ([]byte, []int) {
+ return file_teleport_decision_v1alpha1_ssh_identity_proto_rawDescGZIP(), []int{0}
+}
+
+// CertExtensionType represents the certificate type the extension is for.
+// Currently only ssh is supported. This type must be kept up to date with
+// types.CertExtensionType.
+type CertExtensionType int32
+
+const (
+ // CERT_EXTENSION_TYPE_UNSPECIFIED is the default value and should not be used.
+ CertExtensionType_CERT_EXTENSION_TYPE_UNSPECIFIED CertExtensionType = 0
+ // SSH is used when extending an ssh certificate
+ CertExtensionType_CERT_EXTENSION_TYPE_SSH CertExtensionType = 1
+)
+
+// Enum value maps for CertExtensionType.
+var (
+ CertExtensionType_name = map[int32]string{
+ 0: "CERT_EXTENSION_TYPE_UNSPECIFIED",
+ 1: "CERT_EXTENSION_TYPE_SSH",
+ }
+ CertExtensionType_value = map[string]int32{
+ "CERT_EXTENSION_TYPE_UNSPECIFIED": 0,
+ "CERT_EXTENSION_TYPE_SSH": 1,
+ }
+)
+
+func (x CertExtensionType) Enum() *CertExtensionType {
+ p := new(CertExtensionType)
+ *p = x
+ return p
+}
+
+func (x CertExtensionType) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (CertExtensionType) Descriptor() protoreflect.EnumDescriptor {
+ return file_teleport_decision_v1alpha1_ssh_identity_proto_enumTypes[1].Descriptor()
+}
+
+func (CertExtensionType) Type() protoreflect.EnumType {
+ return &file_teleport_decision_v1alpha1_ssh_identity_proto_enumTypes[1]
+}
+
+func (x CertExtensionType) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use CertExtensionType.Descriptor instead.
+func (CertExtensionType) EnumDescriptor() ([]byte, []int) {
+ return file_teleport_decision_v1alpha1_ssh_identity_proto_rawDescGZIP(), []int{1}
+}
+
// SSHIdentity is the identity used for SSH connections.
type SSHIdentity struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ state protoimpl.MessageState `protogen:"open.v1"`
+ // ValidAfter is the unix timestamp that marks the start time for when the certificate should
+ // be considered valid.
+ ValidAfter uint64 `protobuf:"varint,1,opt,name=valid_after,json=validAfter,proto3" json:"valid_after,omitempty"`
+ // ValidBefore is the unix timestamp that marks the end time for when the certificate should
+ // be considered valid.
+ ValidBefore uint64 `protobuf:"varint,2,opt,name=valid_before,json=validBefore,proto3" json:"valid_before,omitempty"`
+ // CertType indicates what type of cert this is (user or host).
+ CertType uint32 `protobuf:"varint,3,opt,name=cert_type,json=certType,proto3" json:"cert_type,omitempty"`
+ // Principals is the list of SSH principals associated with the certificate (this means the
+ // list of allowed unix logins in the case of user certs).
+ Principals []string `protobuf:"bytes,4,rep,name=principals,proto3" json:"principals,omitempty"`
+ // ClusterName is the name of the cluster within which a node lives
+ ClusterName string `protobuf:"bytes,5,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"`
+ // SystemRole identifies the system role of a Teleport instance
+ SystemRole string `protobuf:"bytes,6,opt,name=system_role,json=systemRole,proto3" json:"system_role,omitempty"`
+ // Username is teleport username
+ Username string `protobuf:"bytes,7,opt,name=username,proto3" json:"username,omitempty"`
+ // Impersonator is set when a user requests certificate for another user
+ Impersonator string `protobuf:"bytes,8,opt,name=impersonator,proto3" json:"impersonator,omitempty"`
+ // PermitX11Forwarding permits X11 forwarding for this cert
+ PermitX11Forwarding bool `protobuf:"varint,9,opt,name=permit_x11_forwarding,json=permitX11Forwarding,proto3" json:"permit_x11_forwarding,omitempty"`
+ // PermitAgentForwarding permits agent forwarding for this cert
+ PermitAgentForwarding bool `protobuf:"varint,10,opt,name=permit_agent_forwarding,json=permitAgentForwarding,proto3" json:"permit_agent_forwarding,omitempty"`
+ // PermitPortForwarding permits port forwarding.
+ PermitPortForwarding bool `protobuf:"varint,11,opt,name=permit_port_forwarding,json=permitPortForwarding,proto3" json:"permit_port_forwarding,omitempty"`
+ // Roles is a list of roles assigned to this user
+ Roles []string `protobuf:"bytes,12,rep,name=roles,proto3" json:"roles,omitempty"`
+ // RouteToCluster specifies the target cluster
+ // if present in the certificate, will be used
+ // to route the requests to
+ RouteToCluster string `protobuf:"bytes,13,opt,name=route_to_cluster,json=routeToCluster,proto3" json:"route_to_cluster,omitempty"`
+ // Traits hold claim data used to populate a role at runtime.
+ Traits []*v1.Trait `protobuf:"bytes,14,rep,name=traits,proto3" json:"traits,omitempty"`
+ // ActiveRequests tracks privilege escalation requests applied during
+ // certificate construction.
+ ActiveRequests []string `protobuf:"bytes,15,rep,name=active_requests,json=activeRequests,proto3" json:"active_requests,omitempty"`
+ // MFAVerified is the UUID of an MFA device when this Identity was
+ // confirmed immediately after an MFA check.
+ MfaVerified string `protobuf:"bytes,16,opt,name=mfa_verified,json=mfaVerified,proto3" json:"mfa_verified,omitempty"`
+ // PreviousIdentityExpires is the expiry time of the identity/cert that this
+ // identity/cert was derived from. It is used to determine a session's hard
+ // deadline in cases where both require_session_mfa and disconnect_expired_cert
+ // are enabled. See https://github.com/gravitational/teleport/issues/18544.
+ PreviousIdentityExpires *timestamppb.Timestamp `protobuf:"bytes,17,opt,name=previous_identity_expires,json=previousIdentityExpires,proto3" json:"previous_identity_expires,omitempty"`
+ // LoginIP is an observed IP of the client on the moment of certificate creation.
+ LoginIp string `protobuf:"bytes,18,opt,name=login_ip,json=loginIp,proto3" json:"login_ip,omitempty"`
+ // PinnedIP is an IP from which client must communicate with Teleport.
+ PinnedIp string `protobuf:"bytes,19,opt,name=pinned_ip,json=pinnedIp,proto3" json:"pinned_ip,omitempty"`
+ // DisallowReissue flags that any attempt to request new certificates while
+ // authenticated with this cert should be denied.
+ DisallowReissue bool `protobuf:"varint,20,opt,name=disallow_reissue,json=disallowReissue,proto3" json:"disallow_reissue,omitempty"`
+ // CertificateExtensions are user configured ssh key extensions (note: this field also
+ // ends up aggregating all *unknown* extensions during cert parsing, meaning that this
+ // can sometimes contain fields that were inserted by a newer version of teleport).
+ CertificateExtensions []*CertExtension `protobuf:"bytes,21,rep,name=certificate_extensions,json=certificateExtensions,proto3" json:"certificate_extensions,omitempty"`
+ // Renewable indicates this certificate is renewable.
+ Renewable bool `protobuf:"varint,22,opt,name=renewable,proto3" json:"renewable,omitempty"`
+ // Generation counts the number of times a certificate has been renewed, with a generation of 1
+ // meaning the cert has never been renewed. A generation of zero means the cert's generation is
+ // not being tracked.
+ Generation uint64 `protobuf:"varint,23,opt,name=generation,proto3" json:"generation,omitempty"`
+ // BotName is set to the name of the bot, if the user is a Machine ID bot user.
+ // Empty for human users.
+ BotName string `protobuf:"bytes,24,opt,name=bot_name,json=botName,proto3" json:"bot_name,omitempty"`
+ // BotInstanceID is the unique identifier for the bot instance, if this is a
+ // Machine ID bot. It is empty for human users.
+ BotInstanceId string `protobuf:"bytes,25,opt,name=bot_instance_id,json=botInstanceId,proto3" json:"bot_instance_id,omitempty"`
+ // AllowedResourceIDs lists the resources the user should be able to access.
+ AllowedResourceIds []*ResourceId `protobuf:"bytes,26,rep,name=allowed_resource_ids,json=allowedResourceIds,proto3" json:"allowed_resource_ids,omitempty"`
+ // ConnectionDiagnosticID references the ConnectionDiagnostic that we should use to append traces when testing a Connection.
+ ConnectionDiagnosticId string `protobuf:"bytes,27,opt,name=connection_diagnostic_id,json=connectionDiagnosticId,proto3" json:"connection_diagnostic_id,omitempty"`
+ // PrivateKeyPolicy is the private key policy supported by this certificate.
+ PrivateKeyPolicy string `protobuf:"bytes,28,opt,name=private_key_policy,json=privateKeyPolicy,proto3" json:"private_key_policy,omitempty"`
+ // DeviceID is the trusted device identifier.
+ DeviceId string `protobuf:"bytes,29,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
+ // DeviceAssetTag is the device inventory identifier.
+ DeviceAssetTag string `protobuf:"bytes,30,opt,name=device_asset_tag,json=deviceAssetTag,proto3" json:"device_asset_tag,omitempty"`
+ // DeviceCredentialID is the identifier for the credential used by the device
+ // to authenticate itself.
+ DeviceCredentialId string `protobuf:"bytes,31,opt,name=device_credential_id,json=deviceCredentialId,proto3" json:"device_credential_id,omitempty"`
+ // GitHubUserID indicates the GitHub user ID identified by the GitHub
+ // connector.
+ GithubUserId string `protobuf:"bytes,32,opt,name=github_user_id,json=githubUserId,proto3" json:"github_user_id,omitempty"`
+ // GitHubUsername indicates the GitHub username identified by the GitHub
+ // connector.
+ GithubUsername string `protobuf:"bytes,33,opt,name=github_username,json=githubUsername,proto3" json:"github_username,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *SSHIdentity) Reset() {
@@ -72,6 +262,315 @@ func (*SSHIdentity) Descriptor() ([]byte, []int) {
return file_teleport_decision_v1alpha1_ssh_identity_proto_rawDescGZIP(), []int{0}
}
+func (x *SSHIdentity) GetValidAfter() uint64 {
+ if x != nil {
+ return x.ValidAfter
+ }
+ return 0
+}
+
+func (x *SSHIdentity) GetValidBefore() uint64 {
+ if x != nil {
+ return x.ValidBefore
+ }
+ return 0
+}
+
+func (x *SSHIdentity) GetCertType() uint32 {
+ if x != nil {
+ return x.CertType
+ }
+ return 0
+}
+
+func (x *SSHIdentity) GetPrincipals() []string {
+ if x != nil {
+ return x.Principals
+ }
+ return nil
+}
+
+func (x *SSHIdentity) GetClusterName() string {
+ if x != nil {
+ return x.ClusterName
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetSystemRole() string {
+ if x != nil {
+ return x.SystemRole
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetUsername() string {
+ if x != nil {
+ return x.Username
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetImpersonator() string {
+ if x != nil {
+ return x.Impersonator
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetPermitX11Forwarding() bool {
+ if x != nil {
+ return x.PermitX11Forwarding
+ }
+ return false
+}
+
+func (x *SSHIdentity) GetPermitAgentForwarding() bool {
+ if x != nil {
+ return x.PermitAgentForwarding
+ }
+ return false
+}
+
+func (x *SSHIdentity) GetPermitPortForwarding() bool {
+ if x != nil {
+ return x.PermitPortForwarding
+ }
+ return false
+}
+
+func (x *SSHIdentity) GetRoles() []string {
+ if x != nil {
+ return x.Roles
+ }
+ return nil
+}
+
+func (x *SSHIdentity) GetRouteToCluster() string {
+ if x != nil {
+ return x.RouteToCluster
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetTraits() []*v1.Trait {
+ if x != nil {
+ return x.Traits
+ }
+ return nil
+}
+
+func (x *SSHIdentity) GetActiveRequests() []string {
+ if x != nil {
+ return x.ActiveRequests
+ }
+ return nil
+}
+
+func (x *SSHIdentity) GetMfaVerified() string {
+ if x != nil {
+ return x.MfaVerified
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetPreviousIdentityExpires() *timestamppb.Timestamp {
+ if x != nil {
+ return x.PreviousIdentityExpires
+ }
+ return nil
+}
+
+func (x *SSHIdentity) GetLoginIp() string {
+ if x != nil {
+ return x.LoginIp
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetPinnedIp() string {
+ if x != nil {
+ return x.PinnedIp
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetDisallowReissue() bool {
+ if x != nil {
+ return x.DisallowReissue
+ }
+ return false
+}
+
+func (x *SSHIdentity) GetCertificateExtensions() []*CertExtension {
+ if x != nil {
+ return x.CertificateExtensions
+ }
+ return nil
+}
+
+func (x *SSHIdentity) GetRenewable() bool {
+ if x != nil {
+ return x.Renewable
+ }
+ return false
+}
+
+func (x *SSHIdentity) GetGeneration() uint64 {
+ if x != nil {
+ return x.Generation
+ }
+ return 0
+}
+
+func (x *SSHIdentity) GetBotName() string {
+ if x != nil {
+ return x.BotName
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetBotInstanceId() string {
+ if x != nil {
+ return x.BotInstanceId
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetAllowedResourceIds() []*ResourceId {
+ if x != nil {
+ return x.AllowedResourceIds
+ }
+ return nil
+}
+
+func (x *SSHIdentity) GetConnectionDiagnosticId() string {
+ if x != nil {
+ return x.ConnectionDiagnosticId
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetPrivateKeyPolicy() string {
+ if x != nil {
+ return x.PrivateKeyPolicy
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetDeviceId() string {
+ if x != nil {
+ return x.DeviceId
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetDeviceAssetTag() string {
+ if x != nil {
+ return x.DeviceAssetTag
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetDeviceCredentialId() string {
+ if x != nil {
+ return x.DeviceCredentialId
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetGithubUserId() string {
+ if x != nil {
+ return x.GithubUserId
+ }
+ return ""
+}
+
+func (x *SSHIdentity) GetGithubUsername() string {
+ if x != nil {
+ return x.GithubUsername
+ }
+ return ""
+}
+
+// CertExtension represents a key/value for a certificate extension. This type must
+// be kept up to date with types.CertExtension.
+type CertExtension struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ // Type represents the certificate type being extended, only ssh
+ // is supported at this time.
+ // 0 is "ssh".
+ Type CertExtensionType `protobuf:"varint,1,opt,name=type,proto3,enum=teleport.decision.v1alpha1.CertExtensionType" json:"type,omitempty"`
+ // Mode is the type of extension to be used -- currently
+ // critical-option is not supported.
+ // 0 is "extension".
+ Mode CertExtensionMode `protobuf:"varint,2,opt,name=mode,proto3,enum=teleport.decision.v1alpha1.CertExtensionMode" json:"mode,omitempty"`
+ // Name specifies the key to be used in the cert extension.
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+ // Value specifies the value to be used in the cert extension.
+ Value string `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CertExtension) Reset() {
+ *x = CertExtension{}
+ mi := &file_teleport_decision_v1alpha1_ssh_identity_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CertExtension) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CertExtension) ProtoMessage() {}
+
+func (x *CertExtension) ProtoReflect() protoreflect.Message {
+ mi := &file_teleport_decision_v1alpha1_ssh_identity_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CertExtension.ProtoReflect.Descriptor instead.
+func (*CertExtension) Descriptor() ([]byte, []int) {
+ return file_teleport_decision_v1alpha1_ssh_identity_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *CertExtension) GetType() CertExtensionType {
+ if x != nil {
+ return x.Type
+ }
+ return CertExtensionType_CERT_EXTENSION_TYPE_UNSPECIFIED
+}
+
+func (x *CertExtension) GetMode() CertExtensionMode {
+ if x != nil {
+ return x.Mode
+ }
+ return CertExtensionMode_CERT_EXTENSION_MODE_UNSPECIFIED
+}
+
+func (x *CertExtension) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *CertExtension) GetValue() string {
+ if x != nil {
+ return x.Value
+ }
+ return ""
+}
+
var File_teleport_decision_v1alpha1_ssh_identity_proto protoreflect.FileDescriptor
var file_teleport_decision_v1alpha1_ssh_identity_proto_rawDesc = string([]byte{
@@ -79,14 +578,134 @@ var file_teleport_decision_v1alpha1_ssh_identity_proto_rawDesc = string([]byte{
0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x73, 0x73, 0x68,
0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x1a, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69,
- 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x22, 0x0d, 0x0a, 0x0b, 0x53,
- 0x53, 0x48, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x5a, 0x5a, 0x58, 0x67, 0x69,
- 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f,
- 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f,
- 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69,
- 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x65, 0x63, 0x69,
- 0x73, 0x69, 0x6f, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f,
+ 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d,
+ 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 0x74, 0x65,
+ 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2f,
+ 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x6c, 0x73, 0x5f, 0x69, 0x64, 0x65,
+ 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x74, 0x65, 0x6c,
+ 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x74, 0x72, 0x61, 0x69, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x74,
+ 0x72, 0x61, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9a, 0x0b, 0x0a, 0x0b, 0x53,
+ 0x53, 0x48, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x61,
+ 0x6c, 0x69, 0x64, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
+ 0x0a, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x76,
+ 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+ 0x04, 0x52, 0x0b, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x1b,
+ 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
+ 0x0d, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70,
+ 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52,
+ 0x0a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f,
+ 0x0a, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x06, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x6f, 0x6c, 0x65, 0x12,
+ 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x69,
+ 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x0c, 0x69, 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x12,
+ 0x32, 0x0a, 0x15, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x5f, 0x78, 0x31, 0x31, 0x5f, 0x66, 0x6f,
+ 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13,
+ 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x58, 0x31, 0x31, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
+ 0x69, 0x6e, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x5f, 0x61, 0x67,
+ 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x0a,
+ 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x41, 0x67, 0x65, 0x6e,
+ 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x16, 0x70,
+ 0x65, 0x72, 0x6d, 0x69, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61,
+ 0x72, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x70, 0x65, 0x72,
+ 0x6d, 0x69, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e,
+ 0x67, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09,
+ 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, 0x75, 0x74, 0x65,
+ 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x54, 0x6f, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65,
+ 0x72, 0x12, 0x30, 0x0a, 0x06, 0x74, 0x72, 0x61, 0x69, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28,
+ 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x61,
+ 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x69, 0x74, 0x52, 0x06, 0x74, 0x72, 0x61,
+ 0x69, 0x74, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x63,
+ 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c,
+ 0x6d, 0x66, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x10, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x66, 0x61, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12,
+ 0x56, 0x0a, 0x19, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x69, 0x64, 0x65, 0x6e,
+ 0x74, 0x69, 0x74, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x11, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x17,
+ 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
+ 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x69, 0x6e,
+ 0x5f, 0x69, 0x70, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x69, 0x6e,
+ 0x49, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x18,
+ 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x49, 0x70, 0x12,
+ 0x29, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x65, 0x69, 0x73,
+ 0x73, 0x75, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x6c,
+ 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x69, 0x73, 0x73, 0x75, 0x65, 0x12, 0x60, 0x0a, 0x16, 0x63, 0x65,
+ 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73,
+ 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c,
+ 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x76,
+ 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x45, 0x78, 0x74, 0x65,
+ 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x15, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x65, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09,
+ 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x08, 0x52,
+ 0x09, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x65,
+ 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x17, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a,
+ 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6f,
+ 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6f,
+ 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x73,
+ 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
+ 0x62, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x58, 0x0a,
+ 0x14, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
+ 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x1a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x65,
+ 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e,
+ 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
+ 0x65, 0x49, 0x64, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f,
+ 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
+ 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63,
+ 0x5f, 0x69, 0x64, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
+ 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x49,
+ 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
+ 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70,
+ 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12,
+ 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x1d, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10,
+ 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67,
+ 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x73,
+ 0x73, 0x65, 0x74, 0x54, 0x61, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
+ 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x1f,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, 0x64,
+ 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x74, 0x68,
+ 0x75, 0x62, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x20, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x0c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27,
+ 0x0a, 0x0f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
+ 0x65, 0x18, 0x21, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x55,
+ 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xbf, 0x01, 0x0a, 0x0d, 0x43, 0x65, 0x72, 0x74,
+ 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x04, 0x74, 0x79, 0x70,
+ 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f,
+ 0x72, 0x74, 0x2e, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x61, 0x6c,
+ 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
+ 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x04,
+ 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x74, 0x65, 0x6c,
+ 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x76,
+ 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x45, 0x78, 0x74, 0x65,
+ 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12,
+ 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+ 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x5b, 0x0a, 0x11, 0x43, 0x65, 0x72,
+ 0x74, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x23,
+ 0x0a, 0x1f, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x53, 0x49, 0x4f, 0x4e,
+ 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
+ 0x44, 0x10, 0x00, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x54, 0x45,
+ 0x4e, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e,
+ 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x2a, 0x55, 0x0a, 0x11, 0x43, 0x65, 0x72, 0x74, 0x45, 0x78,
+ 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x1f, 0x43,
+ 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59,
+ 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
+ 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x53, 0x49,
+ 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x53, 0x48, 0x10, 0x01, 0x42, 0x5a, 0x5a,
+ 0x58, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76,
+ 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f,
+ 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x63,
+ 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64,
+ 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x33,
})
var (
@@ -101,16 +720,29 @@ func file_teleport_decision_v1alpha1_ssh_identity_proto_rawDescGZIP() []byte {
return file_teleport_decision_v1alpha1_ssh_identity_proto_rawDescData
}
-var file_teleport_decision_v1alpha1_ssh_identity_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_teleport_decision_v1alpha1_ssh_identity_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
+var file_teleport_decision_v1alpha1_ssh_identity_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_teleport_decision_v1alpha1_ssh_identity_proto_goTypes = []any{
- (*SSHIdentity)(nil), // 0: teleport.decision.v1alpha1.SSHIdentity
+ (CertExtensionMode)(0), // 0: teleport.decision.v1alpha1.CertExtensionMode
+ (CertExtensionType)(0), // 1: teleport.decision.v1alpha1.CertExtensionType
+ (*SSHIdentity)(nil), // 2: teleport.decision.v1alpha1.SSHIdentity
+ (*CertExtension)(nil), // 3: teleport.decision.v1alpha1.CertExtension
+ (*v1.Trait)(nil), // 4: teleport.trait.v1.Trait
+ (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
+ (*ResourceId)(nil), // 6: teleport.decision.v1alpha1.ResourceId
}
var file_teleport_decision_v1alpha1_ssh_identity_proto_depIdxs = []int32{
- 0, // [0:0] is the sub-list for method output_type
- 0, // [0:0] is the sub-list for method input_type
- 0, // [0:0] is the sub-list for extension type_name
- 0, // [0:0] is the sub-list for extension extendee
- 0, // [0:0] is the sub-list for field type_name
+ 4, // 0: teleport.decision.v1alpha1.SSHIdentity.traits:type_name -> teleport.trait.v1.Trait
+ 5, // 1: teleport.decision.v1alpha1.SSHIdentity.previous_identity_expires:type_name -> google.protobuf.Timestamp
+ 3, // 2: teleport.decision.v1alpha1.SSHIdentity.certificate_extensions:type_name -> teleport.decision.v1alpha1.CertExtension
+ 6, // 3: teleport.decision.v1alpha1.SSHIdentity.allowed_resource_ids:type_name -> teleport.decision.v1alpha1.ResourceId
+ 1, // 4: teleport.decision.v1alpha1.CertExtension.type:type_name -> teleport.decision.v1alpha1.CertExtensionType
+ 0, // 5: teleport.decision.v1alpha1.CertExtension.mode:type_name -> teleport.decision.v1alpha1.CertExtensionMode
+ 6, // [6:6] is the sub-list for method output_type
+ 6, // [6:6] is the sub-list for method input_type
+ 6, // [6:6] is the sub-list for extension type_name
+ 6, // [6:6] is the sub-list for extension extendee
+ 0, // [0:6] is the sub-list for field type_name
}
func init() { file_teleport_decision_v1alpha1_ssh_identity_proto_init() }
@@ -118,18 +750,20 @@ func file_teleport_decision_v1alpha1_ssh_identity_proto_init() {
if File_teleport_decision_v1alpha1_ssh_identity_proto != nil {
return
}
+ file_teleport_decision_v1alpha1_tls_identity_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_decision_v1alpha1_ssh_identity_proto_rawDesc), len(file_teleport_decision_v1alpha1_ssh_identity_proto_rawDesc)),
- NumEnums: 0,
- NumMessages: 1,
+ NumEnums: 2,
+ NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_teleport_decision_v1alpha1_ssh_identity_proto_goTypes,
DependencyIndexes: file_teleport_decision_v1alpha1_ssh_identity_proto_depIdxs,
+ EnumInfos: file_teleport_decision_v1alpha1_ssh_identity_proto_enumTypes,
MessageInfos: file_teleport_decision_v1alpha1_ssh_identity_proto_msgTypes,
}.Build()
File_teleport_decision_v1alpha1_ssh_identity_proto = out.File
diff --git a/api/proto/teleport/decision/v1alpha1/ssh_identity.proto b/api/proto/teleport/decision/v1alpha1/ssh_identity.proto
index 01f4ea2af2d58..c63fa2f73850c 100644
--- a/api/proto/teleport/decision/v1alpha1/ssh_identity.proto
+++ b/api/proto/teleport/decision/v1alpha1/ssh_identity.proto
@@ -16,9 +16,174 @@ syntax = "proto3";
package teleport.decision.v1alpha1;
+import "google/protobuf/timestamp.proto";
+import "teleport/decision/v1alpha1/tls_identity.proto";
+import "teleport/trait/v1/trait.proto";
+
option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/decision/v1alpha1;decisionpb";
// SSHIdentity is the identity used for SSH connections.
message SSHIdentity {
- // TBD
+ // --- common identity fields ---
+
+ // ValidAfter is the unix timestamp that marks the start time for when the certificate should
+ // be considered valid.
+ uint64 valid_after = 1;
+
+ // ValidBefore is the unix timestamp that marks the end time for when the certificate should
+ // be considered valid.
+ uint64 valid_before = 2;
+
+ // CertType indicates what type of cert this is (user or host).
+ uint32 cert_type = 3;
+
+ // Principals is the list of SSH principals associated with the certificate (this means the
+ // list of allowed unix logins in the case of user certs).
+ repeated string principals = 4;
+
+ // --- host identity fields ---
+
+ // ClusterName is the name of the cluster within which a node lives
+ string cluster_name = 5;
+ // SystemRole identifies the system role of a Teleport instance
+ string system_role = 6;
+
+ // -- user identity fields ---
+
+ // Username is teleport username
+ string username = 7;
+
+ // Impersonator is set when a user requests certificate for another user
+ string impersonator = 8;
+
+ // PermitX11Forwarding permits X11 forwarding for this cert
+ bool permit_x11_forwarding = 9;
+
+ // PermitAgentForwarding permits agent forwarding for this cert
+ bool permit_agent_forwarding = 10;
+
+ // PermitPortForwarding permits port forwarding.
+ bool permit_port_forwarding = 11;
+
+ // Roles is a list of roles assigned to this user
+ repeated string roles = 12;
+
+ // RouteToCluster specifies the target cluster
+ // if present in the certificate, will be used
+ // to route the requests to
+ string route_to_cluster = 13;
+
+ // Traits hold claim data used to populate a role at runtime.
+ repeated teleport.trait.v1.Trait traits = 14;
+
+ // ActiveRequests tracks privilege escalation requests applied during
+ // certificate construction.
+ repeated string active_requests = 15;
+
+ // MFAVerified is the UUID of an MFA device when this Identity was
+ // confirmed immediately after an MFA check.
+ string mfa_verified = 16;
+
+ // PreviousIdentityExpires is the expiry time of the identity/cert that this
+ // identity/cert was derived from. It is used to determine a session's hard
+ // deadline in cases where both require_session_mfa and disconnect_expired_cert
+ // are enabled. See https://github.com/gravitational/teleport/issues/18544.
+ google.protobuf.Timestamp previous_identity_expires = 17;
+
+ // LoginIP is an observed IP of the client on the moment of certificate creation.
+ string login_ip = 18;
+
+ // PinnedIP is an IP from which client must communicate with Teleport.
+ string pinned_ip = 19;
+
+ // DisallowReissue flags that any attempt to request new certificates while
+ // authenticated with this cert should be denied.
+ bool disallow_reissue = 20;
+
+ // CertificateExtensions are user configured ssh key extensions (note: this field also
+ // ends up aggregating all *unknown* extensions during cert parsing, meaning that this
+ // can sometimes contain fields that were inserted by a newer version of teleport).
+ repeated CertExtension certificate_extensions = 21;
+
+ // Renewable indicates this certificate is renewable.
+ bool renewable = 22;
+
+ // Generation counts the number of times a certificate has been renewed, with a generation of 1
+ // meaning the cert has never been renewed. A generation of zero means the cert's generation is
+ // not being tracked.
+ uint64 generation = 23;
+
+ // BotName is set to the name of the bot, if the user is a Machine ID bot user.
+ // Empty for human users.
+ string bot_name = 24;
+
+ // BotInstanceID is the unique identifier for the bot instance, if this is a
+ // Machine ID bot. It is empty for human users.
+ string bot_instance_id = 25;
+
+ // AllowedResourceIDs lists the resources the user should be able to access.
+ repeated ResourceId allowed_resource_ids = 26;
+
+ // ConnectionDiagnosticID references the ConnectionDiagnostic that we should use to append traces when testing a Connection.
+ string connection_diagnostic_id = 27;
+
+ // PrivateKeyPolicy is the private key policy supported by this certificate.
+ string private_key_policy = 28;
+
+ // DeviceID is the trusted device identifier.
+ string device_id = 29;
+
+ // DeviceAssetTag is the device inventory identifier.
+ string device_asset_tag = 30;
+
+ // DeviceCredentialID is the identifier for the credential used by the device
+ // to authenticate itself.
+ string device_credential_id = 31;
+
+ // GitHubUserID indicates the GitHub user ID identified by the GitHub
+ // connector.
+ string github_user_id = 32;
+
+ // GitHubUsername indicates the GitHub username identified by the GitHub
+ // connector.
+ string github_username = 33;
+}
+
+// CertExtensionMode specifies the type of extension to use in the cert. This type
+// must be kept up to date with types.CertExtensionMode.
+enum CertExtensionMode {
+ // CERT_EXTENSION_MODE_UNSPECIFIED is the default value and should not be used.
+ CERT_EXTENSION_MODE_UNSPECIFIED = 0;
+
+ // EXTENSION represents a cert extension that may or may not be
+ // honored by the server.
+ CERT_EXTENSION_MODE_EXTENSION = 1;
+}
+
+// CertExtensionType represents the certificate type the extension is for.
+// Currently only ssh is supported. This type must be kept up to date with
+// types.CertExtensionType.
+enum CertExtensionType {
+ // CERT_EXTENSION_TYPE_UNSPECIFIED is the default value and should not be used.
+ CERT_EXTENSION_TYPE_UNSPECIFIED = 0;
+
+ // SSH is used when extending an ssh certificate
+ CERT_EXTENSION_TYPE_SSH = 1;
+}
+
+// CertExtension represents a key/value for a certificate extension. This type must
+// be kept up to date with types.CertExtension.
+message CertExtension {
+ // Type represents the certificate type being extended, only ssh
+ // is supported at this time.
+ // 0 is "ssh".
+ CertExtensionType type = 1;
+ // Mode is the type of extension to be used -- currently
+ // critical-option is not supported.
+ // 0 is "extension".
+ CertExtensionMode mode = 2;
+ // Name specifies the key to be used in the cert extension.
+ string name = 3;
+ // Value specifies the value to be used in the cert extension.
+ string value = 4;
}
diff --git a/lib/decision/ssh_identity.go b/lib/decision/ssh_identity.go
new file mode 100644
index 0000000000000..0bf120d5307a2
--- /dev/null
+++ b/lib/decision/ssh_identity.go
@@ -0,0 +1,143 @@
+// Teleport
+// Copyright (C) 2025 Gravitational, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package decision
+
+import (
+ decisionpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/decision/v1alpha1"
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/api/utils/keys"
+ "github.com/gravitational/teleport/lib/sshca"
+)
+
+// SSHIdentityToSSHCA transforms a [decisionpb.SSHIdentity] into its
+// equivalent [sshca.Identity].
+// Note that certain types, like slices, are not deep-copied.
+func SSHIdentityToSSHCA(id *decisionpb.SSHIdentity) *sshca.Identity {
+ if id == nil {
+ return nil
+ }
+
+ return &sshca.Identity{
+ ValidAfter: id.ValidAfter,
+ ValidBefore: id.ValidBefore,
+ CertType: id.CertType,
+ ClusterName: id.ClusterName,
+ SystemRole: types.SystemRole(id.SystemRole),
+ Username: id.Username,
+ Impersonator: id.Impersonator,
+ Principals: id.Principals,
+ PermitX11Forwarding: id.PermitX11Forwarding,
+ PermitAgentForwarding: id.PermitAgentForwarding,
+ PermitPortForwarding: id.PermitPortForwarding,
+ Roles: id.Roles,
+ RouteToCluster: id.RouteToCluster,
+ Traits: traitToWrappers(id.Traits),
+ ActiveRequests: id.ActiveRequests,
+ MFAVerified: id.MfaVerified,
+ PreviousIdentityExpires: timestampToGoTime(id.PreviousIdentityExpires),
+ LoginIP: id.LoginIp,
+ PinnedIP: id.PinnedIp,
+ DisallowReissue: id.DisallowReissue,
+ CertificateExtensions: certExtensionsFromProto(id.CertificateExtensions),
+ Renewable: id.Renewable,
+ Generation: id.Generation,
+ BotName: id.BotName,
+ BotInstanceID: id.BotInstanceId,
+ AllowedResourceIDs: resourceIDsToTypes(id.AllowedResourceIds),
+ ConnectionDiagnosticID: id.ConnectionDiagnosticId,
+ PrivateKeyPolicy: keys.PrivateKeyPolicy(id.PrivateKeyPolicy),
+ DeviceID: id.DeviceId,
+ DeviceAssetTag: id.DeviceAssetTag,
+ DeviceCredentialID: id.DeviceCredentialId,
+ GitHubUserID: id.GithubUserId,
+ GitHubUsername: id.GithubUsername,
+ }
+}
+
+func SSHIdentityFromSSHCA(id *sshca.Identity) *decisionpb.SSHIdentity {
+ if id == nil {
+ return nil
+ }
+
+ return &decisionpb.SSHIdentity{
+ ValidAfter: id.ValidAfter,
+ ValidBefore: id.ValidBefore,
+ CertType: id.CertType,
+ ClusterName: id.ClusterName,
+ SystemRole: string(id.SystemRole),
+ Username: id.Username,
+ Impersonator: id.Impersonator,
+ Principals: id.Principals,
+ PermitX11Forwarding: id.PermitX11Forwarding,
+ PermitAgentForwarding: id.PermitAgentForwarding,
+ PermitPortForwarding: id.PermitPortForwarding,
+ Roles: id.Roles,
+ RouteToCluster: id.RouteToCluster,
+ Traits: traitFromWrappers(id.Traits),
+ ActiveRequests: id.ActiveRequests,
+ MfaVerified: id.MFAVerified,
+ PreviousIdentityExpires: timestampFromGoTime(id.PreviousIdentityExpires),
+ LoginIp: id.LoginIP,
+ PinnedIp: id.PinnedIP,
+ DisallowReissue: id.DisallowReissue,
+ CertificateExtensions: certExtensionsToProto(id.CertificateExtensions),
+ Renewable: id.Renewable,
+ Generation: id.Generation,
+ BotName: id.BotName,
+ BotInstanceId: id.BotInstanceID,
+ AllowedResourceIds: resourceIDsFromTypes(id.AllowedResourceIDs),
+ ConnectionDiagnosticId: id.ConnectionDiagnosticID,
+ PrivateKeyPolicy: string(id.PrivateKeyPolicy),
+ DeviceId: id.DeviceID,
+ DeviceAssetTag: id.DeviceAssetTag,
+ DeviceCredentialId: id.DeviceCredentialID,
+ GithubUserId: id.GitHubUserID,
+ GithubUsername: id.GitHubUsername,
+ }
+}
+
+func certExtensionsFromProto(extensions []*decisionpb.CertExtension) []*types.CertExtension {
+ if len(extensions) == 0 {
+ return nil
+ }
+ out := make([]*types.CertExtension, 0, len(extensions))
+ for _, extension := range extensions {
+ out = append(out, &types.CertExtension{
+ Mode: types.CertExtensionMode(int32(extension.Mode) - 1), // enum is equivalent but off by 1
+ Type: types.CertExtensionType(int32(extension.Type) - 1), // enum is equivalent but off by 1
+ Name: extension.Name,
+ Value: extension.Value,
+ })
+ }
+ return out
+}
+
+func certExtensionsToProto(extensions []*types.CertExtension) []*decisionpb.CertExtension {
+ if len(extensions) == 0 {
+ return nil
+ }
+ out := make([]*decisionpb.CertExtension, 0, len(extensions))
+ for _, extension := range extensions {
+ out = append(out, &decisionpb.CertExtension{
+ Mode: decisionpb.CertExtensionMode(int32(extension.Mode) + 1), // enum is equivalent but off by 1
+ Type: decisionpb.CertExtensionType(int32(extension.Type) + 1), // enum is equivalent but off by 1
+ Name: extension.Name,
+ Value: extension.Value,
+ })
+ }
+ return out
+}
diff --git a/lib/decision/ssh_identity_test.go b/lib/decision/ssh_identity_test.go
new file mode 100644
index 0000000000000..9bd1412143010
--- /dev/null
+++ b/lib/decision/ssh_identity_test.go
@@ -0,0 +1,101 @@
+// Teleport
+// Copyright (C) 2025 Gravitational, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package decision
+
+import (
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/uuid"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/crypto/ssh"
+
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/api/types/wrappers"
+ "github.com/gravitational/teleport/api/utils/keys"
+ "github.com/gravitational/teleport/lib/sshca"
+ "github.com/gravitational/teleport/lib/utils/testutils"
+)
+
+func TestSSHIdentityConversion(t *testing.T) {
+ ident := &sshca.Identity{
+ ValidAfter: 1,
+ ValidBefore: 2,
+ CertType: ssh.UserCert,
+ ClusterName: "some-cluster",
+ SystemRole: types.RoleNode,
+ Username: "user",
+ Impersonator: "impersonator",
+ Principals: []string{"login1", "login2"},
+ PermitX11Forwarding: true,
+ PermitAgentForwarding: true,
+ PermitPortForwarding: true,
+ Roles: []string{"role1", "role2"},
+ RouteToCluster: "cluster",
+ Traits: wrappers.Traits{"trait1": []string{"value1"}, "trait2": []string{"value2"}},
+ ActiveRequests: []string{uuid.NewString()},
+ MFAVerified: "mfa",
+ PreviousIdentityExpires: time.Unix(12345, 0),
+ LoginIP: "127.0.0.1",
+ PinnedIP: "127.0.0.1",
+ DisallowReissue: true,
+ CertificateExtensions: []*types.CertExtension{&types.CertExtension{
+ Name: "extname",
+ Value: "extvalue",
+ Type: types.CertExtensionType_SSH,
+ Mode: types.CertExtensionMode_EXTENSION,
+ }},
+ Renewable: true,
+ Generation: 3,
+ BotName: "bot",
+ BotInstanceID: "instance",
+ AllowedResourceIDs: []types.ResourceID{{
+ ClusterName: "cluster",
+ Kind: types.KindKubePod, // must use a kube resource kind for parsing of sub-resource to work correctly
+ Name: "name",
+ SubResourceName: "sub/sub",
+ }},
+ ConnectionDiagnosticID: "diag",
+ PrivateKeyPolicy: keys.PrivateKeyPolicy("policy"),
+ DeviceID: "device",
+ DeviceAssetTag: "asset",
+ DeviceCredentialID: "cred",
+ GitHubUserID: "github",
+ GitHubUsername: "ghuser",
+ }
+
+ ignores := []string{
+ "CertExtension.Type", // only currently defined enum variant is a zero value
+ "CertExtension.Mode", // only currently defined enum variant is a zero value
+ // TODO(fspmarshall): figure out a mechanism for making ignore of grpc fields more convenient
+ "CertExtension.XXX_NoUnkeyedLiteral",
+ "CertExtension.XXX_unrecognized",
+ "CertExtension.XXX_sizecache",
+ "ResourceID.XXX_NoUnkeyedLiteral",
+ "ResourceID.XXX_unrecognized",
+ "ResourceID.XXX_sizecache",
+ }
+
+ require.True(t, testutils.ExhaustiveNonEmpty(ident, ignores...), "empty=%+v", testutils.FindAllEmpty(ident, ignores...))
+
+ proto := SSHIdentityFromSSHCA(ident)
+
+ ident2 := SSHIdentityToSSHCA(proto)
+
+ require.Empty(t, cmp.Diff(ident, ident2))
+}