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 372f4fa797e90..4272ef0dc3b9b 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" ) @@ -34,11 +36,200 @@ 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 sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + // 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"` } func (x *SSHIdentity) Reset() { @@ -73,6 +264,318 @@ 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 + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 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"` +} + +func (x *CertExtension) Reset() { + *x = CertExtension{} + if protoimpl.UnsafeEnabled { + 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 protoimpl.UnsafeEnabled && 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 = []byte{ @@ -80,14 +583,134 @@ var file_teleport_decision_v1alpha1_ssh_identity_proto_rawDesc = []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 ( @@ -102,16 +725,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() } @@ -119,6 +755,7 @@ 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() if !protoimpl.UnsafeEnabled { file_teleport_decision_v1alpha1_ssh_identity_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*SSHIdentity); i { @@ -132,19 +769,32 @@ func file_teleport_decision_v1alpha1_ssh_identity_proto_init() { return nil } } + file_teleport_decision_v1alpha1_ssh_identity_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*CertExtension); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: 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/integration/helpers/instance.go b/integration/helpers/instance.go index a552193cad8e4..e60e0fba941a0 100644 --- a/integration/helpers/instance.go +++ b/integration/helpers/instance.go @@ -66,6 +66,7 @@ import ( "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" @@ -361,14 +362,16 @@ func NewInstance(t *testing.T, cfg InstanceConfig) *TeleInstance { signer, err := ssh.ParsePrivateKey(cfg.Priv) fatalIf(err) - cert, err := keygen.GenerateHostCert(services.HostCertParams{ + cert, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: signer, PublicHostKey: cfg.Pub, HostID: cfg.HostID, NodeName: cfg.NodeName, - ClusterName: cfg.ClusterName, - Role: types.RoleAdmin, TTL: 24 * time.Hour, + Identity: sshca.Identity{ + ClusterName: cfg.ClusterName, + SystemRole: types.RoleAdmin, + }, }) fatalIf(err) tlsCA, err := tlsca.FromKeys(tlsCACert, cfg.Priv) diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 90130005bbcfd..932e35c230f2b 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -1948,20 +1948,22 @@ func (a *Server) GenerateHostCert(ctx context.Context, hostPublicKey []byte, hos } // create and sign! - return a.generateHostCert(ctx, services.HostCertParams{ + return a.generateHostCert(ctx, sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: hostPublicKey, HostID: hostID, NodeName: nodeName, - Principals: principals, - ClusterName: clusterName, - Role: role, TTL: ttl, + Identity: sshca.Identity{ + Principals: principals, + ClusterName: clusterName, + SystemRole: role, + }, }) } func (a *Server) generateHostCert( - ctx context.Context, p services.HostCertParams, + ctx context.Context, req sshca.HostCertificateRequest, ) ([]byte, error) { readOnlyAuthPref, err := a.GetReadOnlyAuthPreference(ctx) @@ -1970,7 +1972,7 @@ func (a *Server) generateHostCert( } var locks []types.LockTarget - switch p.Role { + switch req.Identity.SystemRole { case types.RoleNode: // Node role is a special case because it was previously suported as a // lock target that only locked the `ssh_service`. If the same Teleport server @@ -1983,9 +1985,9 @@ func (a *Server) generateHostCert( // and `Node` fields if the role is `Node` so that the previous behavior // is preserved. // This is a legacy behavior that we need to support for backwards compatibility. - locks = []types.LockTarget{{ServerID: p.HostID, Node: p.HostID}, {ServerID: HostFQDN(p.HostID, p.ClusterName), Node: HostFQDN(p.HostID, p.ClusterName)}} + locks = []types.LockTarget{{ServerID: req.HostID, Node: req.HostID}, {ServerID: HostFQDN(req.HostID, req.Identity.ClusterName), Node: HostFQDN(req.HostID, req.Identity.ClusterName)}} default: - locks = []types.LockTarget{{ServerID: p.HostID}, {ServerID: HostFQDN(p.HostID, p.ClusterName)}} + locks = []types.LockTarget{{ServerID: req.HostID}, {ServerID: HostFQDN(req.HostID, req.Identity.ClusterName)}} } if lockErr := a.checkLockInForce(readOnlyAuthPref.GetLockingMode(), locks, @@ -1993,7 +1995,7 @@ func (a *Server) generateHostCert( return nil, trace.Wrap(lockErr) } - return a.Authority.GenerateHostCert(p) + return a.Authority.GenerateHostCert(req) } // GetKeyStore returns the KeyStore used by the auth server @@ -2035,7 +2037,7 @@ type certRequest struct { traits wrappers.Traits // activeRequests tracks privilege escalation requests applied // during the construction of the certificate. - activeRequests services.RequestIDs + activeRequests []string // appSessionID is the session ID of the application session. appSessionID string // appPublicAddr is the public address of the application. @@ -2733,7 +2735,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. defaultMode: readOnlyAuthPref.GetLockingMode(), username: req.user.GetName(), mfaVerified: req.mfaVerified, - activeAccessRequests: req.activeRequests.AccessRequests, + activeAccessRequests: req.activeRequests, deviceID: req.deviceExtensions.DeviceID, }); err != nil { return nil, trace.Wrap(err) @@ -2876,11 +2878,6 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. // All users have access to this and join RBAC rules are checked after the connection is established. allowedLogins = append(allowedLogins, teleport.SSHSessionJoinPrincipal) - requestedResourcesStr, err := types.ResourceIDsToString(req.checker.GetAllowedResourceIDs()) - if err != nil { - return nil, trace.Wrap(err) - } - pinnedIP := "" if caType == types.UserCA && (req.checker.PinSourceIP() || req.pinIP) { if req.loginIP == "" { @@ -2910,7 +2907,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. Identity: sshca.Identity{ Username: req.user.GetName(), Impersonator: req.impersonator, - AllowedLogins: allowedLogins, + Principals: allowedLogins, Roles: req.checker.RoleNames(), PermitPortForwarding: req.checker.CanPortForward(), PermitAgentForwarding: req.checker.CanForwardAgents(), @@ -2927,7 +2924,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. Generation: req.generation, BotName: req.botName, CertificateExtensions: req.checker.CertificateExtensions(), - AllowedResourceIDs: requestedResourcesStr, + AllowedResourceIDs: req.checker.GetAllowedResourceIDs(), ConnectionDiagnosticID: req.connectionDiagnosticID, PrivateKeyPolicy: attestedKeyPolicy, DeviceID: req.deviceExtensions.DeviceID, @@ -3017,7 +3014,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. AWSRoleARNs: roleARNs, AzureIdentities: azureIdentities, GCPServiceAccounts: gcpAccounts, - ActiveRequests: req.activeRequests.AccessRequests, + ActiveRequests: req.activeRequests, DisallowReissue: req.disallowReissue, Renewable: req.renewable, Generation: req.generation, @@ -4320,14 +4317,16 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ return nil, trace.Wrap(err) } // generate host SSH certificate - hostSSHCert, err := a.generateHostCert(ctx, services.HostCertParams{ + hostSSHCert, err := a.generateHostCert(ctx, sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: req.PublicSSHKey, HostID: req.HostID, NodeName: req.NodeName, - ClusterName: clusterName.GetClusterName(), - Role: req.Role, - Principals: req.AdditionalPrincipals, + Identity: sshca.Identity{ + ClusterName: clusterName.GetClusterName(), + SystemRole: req.Role, + Principals: req.AdditionalPrincipals, + }, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/auth_test.go b/lib/auth/auth_test.go index 95f9470cb0618..2c0edfc9b040b 100644 --- a/lib/auth/auth_test.go +++ b/lib/auth/auth_test.go @@ -2123,7 +2123,7 @@ func TestGenerateUserCertWithLocks(t *testing.T) { checker: accessChecker, mfaVerified: mfaID, publicKey: pub, - activeRequests: services.RequestIDs{AccessRequests: []string{requestID}}, + activeRequests: []string{requestID}, deviceExtensions: DeviceExtensions{ DeviceID: deviceID, AssetTag: "assettag1", diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 0ebb1bdd74fba..c28a2f2a56471 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -3146,11 +3146,9 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC checker: checker, // Copy IP from current identity to the generated certificate, if present, // to avoid generateUserCerts() being used to drop IP pinning in the new certificates. - loginIP: a.context.Identity.GetIdentity().LoginIP, - traits: accessInfo.Traits, - activeRequests: services.RequestIDs{ - AccessRequests: req.AccessRequests, - }, + loginIP: a.context.Identity.GetIdentity().LoginIP, + traits: accessInfo.Traits, + activeRequests: req.AccessRequests, connectionDiagnosticID: req.ConnectionDiagnosticID, attestationStatement: keys.AttestationStatementFromProto(req.AttestationStatement), botName: getBotName(user), diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index f2847633427f2..106c3662ad379 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -55,6 +55,7 @@ import ( "github.com/gravitational/teleport/lib/observability/tracing" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/suite" + "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/proxy" @@ -70,14 +71,16 @@ func TestReadIdentity(t *testing.T) { caSigner, err := ssh.ParsePrivateKey(priv) require.NoError(t, err) - cert, err := a.GenerateHostCert(services.HostCertParams{ + cert, err := a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "id1", NodeName: "node-name", - ClusterName: "example.com", - Role: types.RoleNode, TTL: 0, + Identity: sshca.Identity{ + ClusterName: "example.com", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) @@ -91,14 +94,16 @@ func TestReadIdentity(t *testing.T) { // test TTL by converting the generated cert to text -> back and making sure ExpireAfter is valid ttl := 10 * time.Second expiryDate := clock.Now().Add(ttl) - bytes, err := a.GenerateHostCert(services.HostCertParams{ + bytes, err := a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "id1", NodeName: "node-name", - ClusterName: "example.com", - Role: types.RoleNode, TTL: ttl, + Identity: sshca.Identity{ + ClusterName: "example.com", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) copy, err := apisshutils.ParseCertificate(bytes) @@ -118,14 +123,16 @@ func TestBadIdentity(t *testing.T) { require.IsType(t, trace.BadParameter(""), err) // missing authority domain - cert, err := a.GenerateHostCert(services.HostCertParams{ + cert, err := a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "id2", NodeName: "", - ClusterName: "", - Role: types.RoleNode, TTL: 0, + Identity: sshca.Identity{ + ClusterName: "", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) @@ -133,14 +140,16 @@ func TestBadIdentity(t *testing.T) { require.IsType(t, trace.BadParameter(""), err) // missing host uuid - cert, err = a.GenerateHostCert(services.HostCertParams{ + cert, err = a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "example.com", NodeName: "", - ClusterName: "", - Role: types.RoleNode, TTL: 0, + Identity: sshca.Identity{ + ClusterName: "", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) @@ -148,14 +157,16 @@ func TestBadIdentity(t *testing.T) { require.IsType(t, trace.BadParameter(""), err) // unrecognized role - cert, err = a.GenerateHostCert(services.HostCertParams{ + cert, err = a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "example.com", NodeName: "", - ClusterName: "id1", - Role: "bad role", TTL: 0, + Identity: sshca.Identity{ + ClusterName: "id1", + SystemRole: "bad role", + }, }) require.NoError(t, err) diff --git a/lib/auth/keygen/keygen.go b/lib/auth/keygen/keygen.go index b95004ee4fe7b..29258e95a0c01 100644 --- a/lib/auth/keygen/keygen.go +++ b/lib/auth/keygen/keygen.go @@ -34,9 +34,7 @@ import ( apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/modules" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" - "github.com/gravitational/teleport/lib/utils" ) // Keygen is a key generator that precomputes keys to provide quick access to @@ -87,56 +85,63 @@ func (k *Keygen) GenerateKeyPair() ([]byte, []byte, error) { // GenerateHostCert generates a host certificate with the passed in parameters. // The private key of the CA to sign the certificate must be provided. -func (k *Keygen) GenerateHostCert(c services.HostCertParams) ([]byte, error) { - if err := c.Check(); err != nil { +func (k *Keygen) GenerateHostCert(req sshca.HostCertificateRequest) ([]byte, error) { + if err := req.Check(); err != nil { return nil, trace.Wrap(err) } - return k.GenerateHostCertWithoutValidation(c) + return k.GenerateHostCertWithoutValidation(req) } // GenerateHostCertWithoutValidation generates a host certificate with the // passed in parameters without validating them. For use in tests only. -func (k *Keygen) GenerateHostCertWithoutValidation(c services.HostCertParams) ([]byte, error) { - pubKey, _, _, _, err := ssh.ParseAuthorizedKey(c.PublicHostKey) +func (k *Keygen) GenerateHostCertWithoutValidation(req sshca.HostCertificateRequest) ([]byte, error) { + pubKey, _, _, _, err := ssh.ParseAuthorizedKey(req.PublicHostKey) if err != nil { return nil, trace.Wrap(err) } + // create shallow copy of identity since we want to make some local changes + ident := req.Identity + + ident.CertType = ssh.HostCert + // Build a valid list of principals from the HostID and NodeName and then // add in any additional principals passed in. - principals := BuildPrincipals(c.HostID, c.NodeName, c.ClusterName, types.SystemRoles{c.Role}) - principals = append(principals, c.Principals...) + principals := BuildPrincipals(req.HostID, req.NodeName, ident.ClusterName, types.SystemRoles{ident.SystemRole}) + principals = append(principals, ident.Principals...) if len(principals) == 0 { - return nil, trace.BadParameter("no principals provided: %v, %v, %v", - c.HostID, c.NodeName, c.Principals) + return nil, trace.BadParameter("cannot generate host certificate without principals") } principals = apiutils.Deduplicate(principals) + ident.Principals = principals - // create certificate - validBefore := uint64(ssh.CertTimeInfinity) - if c.TTL != 0 { - b := k.clock.Now().UTC().Add(c.TTL) - validBefore = uint64(b.Unix()) + // calculate ValidBefore based on the outer request TTL + ident.ValidBefore = uint64(ssh.CertTimeInfinity) + if req.TTL != 0 { + b := k.clock.Now().UTC().Add(req.TTL) + ident.ValidBefore = uint64(b.Unix()) } - cert := &ssh.Certificate{ - ValidPrincipals: principals, - Key: pubKey, - ValidAfter: uint64(k.clock.Now().UTC().Add(-1 * time.Minute).Unix()), - ValidBefore: validBefore, - CertType: ssh.HostCert, + + ident.ValidAfter = uint64(k.clock.Now().UTC().Add(-1 * time.Minute).Unix()) + + // encode the identity into a certificate + cert, err := ident.Encode("") + if err != nil { + return nil, trace.Wrap(err) } - cert.Permissions.Extensions = make(map[string]string) - cert.Permissions.Extensions[utils.CertExtensionRole] = c.Role.String() - cert.Permissions.Extensions[utils.CertExtensionAuthority] = c.ClusterName + + // set the public key of the certificate + cert.Key = pubKey // sign host certificate with private signing key of certificate authority - if err := cert.SignCert(rand.Reader, c.CASigner); err != nil { + if err := cert.SignCert(rand.Reader, req.CASigner); err != nil { return nil, trace.Wrap(err) } log.Debugf("Generated SSH host certificate for role %v with principals: %v.", - c.Role, principals) + ident.SystemRole, ident.Principals) + return ssh.MarshalAuthorizedKey(cert), nil } @@ -160,22 +165,14 @@ func (k *Keygen) GenerateUserCertWithoutValidation(req sshca.UserCertificateRequ // create shallow copy of identity since we want to make some local changes ident := req.Identity - // since this method ignores the supplied values for ValidBefore/ValidAfter, avoid confusing by - // rejecting identities where they are set. - if ident.ValidBefore != 0 { - return nil, trace.BadParameter("ValidBefore should not be set in calls to GenerateUserCert") - } - - if ident.ValidAfter != 0 { - return nil, trace.BadParameter("ValidAfter should not be set in calls to GenerateUserCert") - } + ident.CertType = ssh.UserCert // calculate ValidBefore based on the outer request TTL ident.ValidBefore = uint64(ssh.CertTimeInfinity) if req.TTL != 0 { b := k.clock.Now().UTC().Add(req.TTL) ident.ValidBefore = uint64(b.Unix()) - log.Debugf("generated user key for %v with expiry on (%v) %v", ident.AllowedLogins, ident.ValidBefore, b) + log.Debugf("generated user key for %v with expiry on (%v) %v", ident.Principals, ident.ValidBefore, b) } // set ValidAfter to be 1 minute in the past diff --git a/lib/auth/keygen/keygen_test.go b/lib/auth/keygen/keygen_test.go index 5a7abeee64b5c..a02e131612446 100644 --- a/lib/auth/keygen/keygen_test.go +++ b/lib/auth/keygen/keygen_test.go @@ -35,7 +35,6 @@ import ( "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/auth/test" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" ) @@ -166,16 +165,17 @@ func TestBuildPrincipals(t *testing.T) { // run tests for _, tc := range tests { t.Logf("Running test case: %q", tc.desc) - hostCertificateBytes, err := tt.suite.A.GenerateHostCert( - services.HostCertParams{ - CASigner: caSigner, - PublicHostKey: hostPublicKey, - HostID: tc.inHostID, - NodeName: tc.inNodeName, - ClusterName: tc.inClusterName, - Role: tc.inRole, - TTL: time.Hour, - }) + hostCertificateBytes, err := tt.suite.A.GenerateHostCert(sshca.HostCertificateRequest{ + CASigner: caSigner, + PublicHostKey: hostPublicKey, + HostID: tc.inHostID, + NodeName: tc.inNodeName, + TTL: time.Hour, + Identity: sshca.Identity{ + ClusterName: tc.inClusterName, + SystemRole: tc.inRole, + }, + }) require.NoError(t, err) hostCertificate, err := sshutils.ParseCertificate(hostCertificateBytes) @@ -224,9 +224,9 @@ func TestUserCertCompatibility(t *testing.T) { TTL: time.Hour, CertificateFormat: tc.inCompatibility, Identity: sshca.Identity{ - Username: "user", - AllowedLogins: []string{"centos", "root"}, - Roles: []string{"foo"}, + Username: "user", + Principals: []string{"centos", "root"}, + Roles: []string{"foo"}, CertificateExtensions: []*types.CertExtension{{ Type: types.CertExtensionType_SSH, Mode: types.CertExtensionMode_EXTENSION, diff --git a/lib/auth/sessions.go b/lib/auth/sessions.go index ebcbdb38fd663..5c18acc472b34 100644 --- a/lib/auth/sessions.go +++ b/lib/auth/sessions.go @@ -158,7 +158,7 @@ func (a *Server) NewWebSession(ctx context.Context, req NewWebSessionRequest) (t publicKey: req.PrivateKey.MarshalSSHPublicKey(), checker: checker, traits: req.Traits, - activeRequests: services.RequestIDs{AccessRequests: req.AccessRequests}, + activeRequests: req.AccessRequests, }) if err != nil { return nil, trace.Wrap(err) @@ -261,7 +261,7 @@ func (a *Server) CreateAppSession(ctx context.Context, req types.CreateAppSessio checker: checker, ttl: ttl, traits: traits, - activeRequests: services.RequestIDs{AccessRequests: identity.ActiveRequests}, + activeRequests: identity.ActiveRequests, // Only allow this certificate to be used for applications. usage: []string{teleport.UsageAppsOnly}, // Add in the application routing information. @@ -286,6 +286,7 @@ func (a *Server) CreateAppSession(ctx context.Context, req types.CreateAppSessio if err != nil { return nil, trace.Wrap(err) } + bearer, err := utils.CryptoRandomHex(defaults.SessionTokenBytes) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/test/suite.go b/lib/auth/test/suite.go index 082768d8ca281..ce9c534a13616 100644 --- a/lib/auth/test/suite.go +++ b/lib/auth/test/suite.go @@ -64,16 +64,17 @@ func (s *AuthSuite) GenerateHostCert(t *testing.T) { caSigner, err := ssh.ParsePrivateKey(priv) require.NoError(t, err) - cert, err := s.A.GenerateHostCert( - services.HostCertParams{ - CASigner: caSigner, - PublicHostKey: pub, - HostID: "00000000-0000-0000-0000-000000000000", - NodeName: "auth.example.com", - ClusterName: "example.com", - Role: types.RoleAdmin, - TTL: time.Hour, - }) + cert, err := s.A.GenerateHostCert(sshca.HostCertificateRequest{ + CASigner: caSigner, + PublicHostKey: pub, + HostID: "00000000-0000-0000-0000-000000000000", + NodeName: "auth.example.com", + TTL: time.Hour, + Identity: sshca.Identity{ + ClusterName: "example.com", + SystemRole: types.RoleAdmin, + }, + }) require.NoError(t, err) certificate, err := sshutils.ParseCertificate(cert) @@ -102,7 +103,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"centos", "root"}, + Principals: []string{"centos", "root"}, PermitAgentForwarding: true, PermitPortForwarding: true, }, @@ -121,7 +122,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, PermitAgentForwarding: true, PermitPortForwarding: true, }, @@ -137,7 +138,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, PermitAgentForwarding: true, PermitPortForwarding: true, }, @@ -153,7 +154,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, PermitAgentForwarding: true, PermitPortForwarding: true, }, @@ -170,7 +171,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { Identity: sshca.Identity{ Username: "user", Impersonator: impersonator, - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, PermitAgentForwarding: true, PermitPortForwarding: true, Roles: inRoles, @@ -195,7 +196,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, MFAVerified: "mfa-device-id", PreviousIdentityExpires: clock.Now().Add(time.Hour), }, @@ -219,7 +220,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { PublicUserKey: pub, // Required. Identity: sshca.Identity{ Username: "llama", // Required. - AllowedLogins: []string{"llama"}, // Required. + Principals: []string{"llama"}, // Required. DeviceID: devID, DeviceAssetTag: devTag, DeviceCredentialID: devCred, diff --git a/lib/auth/testauthority/testauthority.go b/lib/auth/testauthority/testauthority.go index 7a94d3bb0daf2..724c8d5732571 100644 --- a/lib/auth/testauthority/testauthority.go +++ b/lib/auth/testauthority/testauthority.go @@ -29,7 +29,6 @@ import ( "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/lib/auth/keygen" "github.com/gravitational/teleport/lib/auth/native" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" ) @@ -68,8 +67,8 @@ func (n *Keygen) GenerateKeyPair() (priv []byte, pub []byte, err error) { return native.GenerateKeyPair() } -func (n *Keygen) GenerateHostCert(c services.HostCertParams) ([]byte, error) { - return n.GenerateHostCertWithoutValidation(c) +func (n *Keygen) GenerateHostCert(req sshca.HostCertificateRequest) ([]byte, error) { + return n.GenerateHostCertWithoutValidation(req) } func (n *Keygen) GenerateUserCert(c sshca.UserCertificateRequest) ([]byte, error) { diff --git a/lib/client/client_store_test.go b/lib/client/client_store_test.go index b2ac021056d4f..f0874fb499231 100644 --- a/lib/client/client_store_test.go +++ b/lib/client/client_store_test.go @@ -37,7 +37,6 @@ import ( "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/tlsca" @@ -97,7 +96,7 @@ func (s *testAuthority) makeSignedKey(t *testing.T, idx KeyIndex, makeExpired bo TTL: ttl, Identity: sshca.Identity{ Username: idx.Username, - AllowedLogins: allowedLogins, + Principals: allowedLogins, PermitAgentForwarding: false, PermitPortForwarding: true, }, @@ -293,13 +292,15 @@ func TestProxySSHConfig(t *testing.T) { caSigner, err := ssh.ParsePrivateKey(CAPriv) require.NoError(t, err) - hostCert, err := auth.keygen.GenerateHostCert(services.HostCertParams{ + hostCert, err := auth.keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: hostPub, HostID: "127.0.0.1", NodeName: "127.0.0.1", - ClusterName: "host-cluster-name", - Role: types.RoleNode, + Identity: sshca.Identity{ + ClusterName: "host-cluster-name", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) diff --git a/lib/client/identityfile/identity_test.go b/lib/client/identityfile/identity_test.go index 2b535846abeb0..25a363eaa3372 100644 --- a/lib/client/identityfile/identity_test.go +++ b/lib/client/identityfile/identity_test.go @@ -111,8 +111,8 @@ func newClientKey(t *testing.T, modifiers ...func(*tlsca.Identity)) *client.Key CASigner: caSigner, PublicUserKey: ssh.MarshalAuthorizedKey(privateKey.SSHPublicKey()), Identity: sshca.Identity{ - Username: "testuser", - AllowedLogins: []string{"testuser"}, + Username: "testuser", + Principals: []string{"testuser"}, }, }) require.NoError(t, err) diff --git a/lib/client/keyagent_test.go b/lib/client/keyagent_test.go index ced2e0a765165..cf50d90e67c91 100644 --- a/lib/client/keyagent_test.go +++ b/lib/client/keyagent_test.go @@ -47,7 +47,6 @@ import ( "github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/fixtures" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" @@ -367,17 +366,19 @@ func TestHostCertVerification(t *testing.T) { // Generate a host certificate for node with role "node". _, rootHostPub, err := keygen.GenerateKeyPair() require.NoError(t, err) - rootHostCertBytes, err := keygen.GenerateHostCert(services.HostCertParams{ + rootHostCertBytes, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: root.signer, PublicHostKey: rootHostPub, HostID: "5ff40d80-9007-4f28-8f49-7d4fda2f574d", NodeName: "server01", - Principals: []string{ - "127.0.0.1", + TTL: 1 * time.Hour, + Identity: sshca.Identity{ + Principals: []string{ + "127.0.0.1", + }, + ClusterName: "example.com", + SystemRole: types.RoleNode, }, - ClusterName: "example.com", - Role: types.RoleNode, - TTL: 1 * time.Hour, }) require.NoError(t, err) rootHostPublicKey, _, _, _, err := ssh.ParseAuthorizedKey(rootHostCertBytes) @@ -385,14 +386,16 @@ func TestHostCertVerification(t *testing.T) { _, leafHostPub, err := keygen.GenerateKeyPair() require.NoError(t, err) - leafHostCertBytes, err := keygen.GenerateHostCert(services.HostCertParams{ + leafHostCertBytes, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: leaf.signer, PublicHostKey: leafHostPub, HostID: "620bb71c-c9eb-4f6d-9823-f7d9125ebb1d", NodeName: "server02", - ClusterName: "leaf.example.com", - Role: types.RoleNode, TTL: 1 * time.Hour, + Identity: sshca.Identity{ + ClusterName: "leaf.example.com", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) leafHostPublicKey, _, _, _, err := ssh.ParseAuthorizedKey(leafHostCertBytes) @@ -621,14 +624,16 @@ func TestHostCertVerificationLoadAllCasProxyAddrEqClusterName(t *testing.T) { func mustGenerateHostPublicCert(t *testing.T, keygen *testauthority.Keygen, signer ssh.Signer, nodeName, clusterName string) ssh.PublicKey { _, leafHostPub, err := keygen.GenerateKeyPair() require.NoError(t, err) - leafHostCertBytes, err := keygen.GenerateHostCert(services.HostCertParams{ + leafHostCertBytes, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: signer, PublicHostKey: leafHostPub, HostID: uuid.NewString(), NodeName: nodeName, - ClusterName: clusterName, - Role: types.RoleNode, TTL: 1 * time.Hour, + Identity: sshca.Identity{ + ClusterName: clusterName, + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) leafCerts, err := sshutils.ParseAuthorizedKeys([][]byte{leafHostCertBytes}) @@ -749,7 +754,7 @@ func (s *KeyAgentTestSuite) makeKey(t *testing.T, username, proxyHost string, pr TTL: ttl, Identity: sshca.Identity{ Username: username, - AllowedLogins: []string{username}, + Principals: []string{username}, PermitAgentForwarding: true, PermitPortForwarding: true, RouteToCluster: s.clusterName, diff --git a/lib/client/known_hosts_migrate_test.go b/lib/client/known_hosts_migrate_test.go index 612e7d3082f06..cba71bda212d6 100644 --- a/lib/client/known_hosts_migrate_test.go +++ b/lib/client/known_hosts_migrate_test.go @@ -28,7 +28,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/gravitational/teleport/lib/auth/testauthority" - "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/sshca" ) type knownHostsMigrateTest struct { @@ -48,12 +48,14 @@ func generateHostCert(t *testing.T, s *knownHostsMigrateTest, clusterName string caSigner, err := ssh.ParsePrivateKey(CAPriv) require.NoError(t, err) - cert, err := s.keygen.GenerateHostCert(services.HostCertParams{ + cert, err := s.keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, HostID: "127.0.0.1", NodeName: "127.0.0.1", - ClusterName: clusterName, PublicHostKey: hostPub, + Identity: sshca.Identity{ + ClusterName: clusterName, + }, }) require.NoError(t, err) 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)) +} diff --git a/lib/reversetunnel/srv_test.go b/lib/reversetunnel/srv_test.go index b635c349644de..f78907cca40ab 100644 --- a/lib/reversetunnel/srv_test.go +++ b/lib/reversetunnel/srv_test.go @@ -40,7 +40,6 @@ import ( "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/testauthority" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/utils" ) @@ -82,13 +81,15 @@ func TestServerKeyAuth(t *testing.T) { { desc: "host cert", key: func() ssh.PublicKey { - rawCert, err := ta.GenerateHostCert(services.HostCertParams{ + rawCert, err := ta.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "host-id", NodeName: con.User(), - ClusterName: "host-cluster-name", - Role: types.RoleNode, + Identity: sshca.Identity{ + ClusterName: "host-cluster-name", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) key, _, _, _, err := ssh.ParseAuthorizedKey(rawCert) @@ -113,7 +114,7 @@ func TestServerKeyAuth(t *testing.T) { TTL: time.Minute, Identity: sshca.Identity{ Username: con.User(), - AllowedLogins: []string{con.User()}, + Principals: []string{con.User()}, Roles: []string{"dev", "admin"}, RouteToCluster: "user-cluster-name", }, diff --git a/lib/services/authority.go b/lib/services/authority.go index dc5c4b612b787..01ae9eda63ed1 100644 --- a/lib/services/authority.go +++ b/lib/services/authority.go @@ -23,14 +23,12 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" - "time" "github.com/gogo/protobuf/proto" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "golang.org/x/crypto/ssh" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" @@ -281,46 +279,6 @@ func GetSSHCheckingKeys(ca types.CertAuthority) [][]byte { return out } -// HostCertParams defines all parameters needed to generate a host certificate -type HostCertParams struct { - // CASigner is the signer that will sign the public key of the host with the CA private key. - CASigner ssh.Signer - // PublicHostKey is the public key of the host - PublicHostKey []byte - // HostID is used by Teleport to uniquely identify a node within a cluster - HostID string - // Principals is a list of additional principals to add to the certificate. - Principals []string - // NodeName is the DNS name of the node - NodeName string - // ClusterName is the name of the cluster within which a node lives - ClusterName string - // Role identifies the role of a Teleport instance - Role types.SystemRole - // TTL defines how long a certificate is valid for - TTL time.Duration -} - -// Check checks parameters for errors -func (c HostCertParams) Check() error { - if c.CASigner == nil { - return trace.BadParameter("CASigner is required") - } - if c.HostID == "" && len(c.Principals) == 0 { - return trace.BadParameter("HostID [%q] or Principals [%q] are required", - c.HostID, c.Principals) - } - if c.ClusterName == "" { - return trace.BadParameter("ClusterName [%q] is required", c.ClusterName) - } - - if err := c.Role.Check(); err != nil { - return trace.Wrap(err) - } - - return nil -} - // CertPoolFromCertAuthorities returns a certificate pool from the TLS certificates // set up in the certificate authorities list, as well as the number of certificates // that were added to the pool. diff --git a/lib/srv/authhandlers_test.go b/lib/srv/authhandlers_test.go index ee4efd0f288fe..d4f286348497b 100644 --- a/lib/srv/authhandlers_test.go +++ b/lib/srv/authhandlers_test.go @@ -218,8 +218,8 @@ func TestRBAC(t *testing.T) { CASigner: caSigner, PublicUserKey: ssh.MarshalAuthorizedKey(privateKey.SSHPublicKey()), Identity: sshca.Identity{ - Username: "testuser", - AllowedLogins: []string{"testuser"}, + Username: "testuser", + Principals: []string{"testuser"}, }, }) require.NoError(t, err) @@ -394,8 +394,8 @@ func TestRBACJoinMFA(t *testing.T) { PublicUserKey: ssh.MarshalAuthorizedKey(privateKey.SSHPublicKey()), CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ - Username: username, - AllowedLogins: []string{username}, + Username: username, + Principals: []string{username}, Traits: wrappers.Traits{ teleport.TraitInternalPrefix: []string{""}, }, diff --git a/lib/sshca/identity.go b/lib/sshca/identity.go index 5da6c7ac16bbe..7785a5a251bf8 100644 --- a/lib/sshca/identity.go +++ b/lib/sshca/identity.go @@ -35,22 +35,39 @@ import ( "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/utils" ) // Identity is a user identity. All identity fields map directly to an ssh certificate field. type Identity struct { + + // --- common identity fields --- + // ValidAfter is the unix timestamp that marks the start time for when the certificate should // be considered valid. ValidAfter uint64 // ValidBefore is the unix timestamp that marks the end time for when the certificate should // be considered valid. ValidBefore uint64 + // CertType indicates what type of cert this is (user or host). + CertType uint32 + // 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 + + // --- host identity fields --- + + // ClusterName is the name of the cluster within which a node lives + ClusterName string + // SystemRole identifies the system role of a Teleport instance + SystemRole types.SystemRole + + // -- user identity fields --- + // Username is teleport username Username string // Impersonator is set when a user requests certificate for another user Impersonator string - // AllowedLogins is a list of SSH principals - AllowedLogins []string // PermitX11Forwarding permits X11 forwarding for this cert PermitX11Forwarding bool // PermitAgentForwarding permits agent forwarding for this cert @@ -67,7 +84,7 @@ type Identity struct { Traits wrappers.Traits // ActiveRequests tracks privilege escalation requests applied during // certificate construction. - ActiveRequests services.RequestIDs + ActiveRequests []string // MFAVerified is the UUID of an MFA device when this Identity was // confirmed immediately after an MFA check. MFAVerified string @@ -97,7 +114,7 @@ type Identity struct { // Empty for human users. BotName string // AllowedResourceIDs lists the resources the user should be able to access. - AllowedResourceIDs string + AllowedResourceIDs []types.ResourceID // ConnectionDiagnosticID references the ConnectionDiagnostic that we should use to append traces when testing a Connection. ConnectionDiagnosticID string // PrivateKeyPolicy is the private key policy supported by this certificate. @@ -111,15 +128,6 @@ type Identity struct { DeviceCredentialID string } -// Check performs validation of certain fields in the identity. -func (i *Identity) Check() error { - if len(i.AllowedLogins) == 0 { - return trace.BadParameter("ssh user identity missing allowed logins") - } - - return nil -} - // Encode encodes the identity into an ssh certificate. Note that the returned certificate is incomplete // and must be have its public key set before signing. func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { @@ -131,18 +139,38 @@ func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { if validAfter == 0 { validAfter = uint64(time.Now().UTC().Add(-1 * time.Minute).Unix()) } + + if i.CertType == 0 { + return nil, trace.BadParameter("cannot encode ssh identity missing required field CertType") + } + cert := &ssh.Certificate{ // we have to use key id to identify teleport user KeyId: i.Username, - ValidPrincipals: i.AllowedLogins, + ValidPrincipals: i.Principals, ValidAfter: validAfter, ValidBefore: validBefore, - CertType: ssh.UserCert, + CertType: i.CertType, + } + + cert.Permissions.Extensions = make(map[string]string) + + if i.CertType == ssh.UserCert { + cert.Permissions.Extensions[teleport.CertExtensionPermitPTY] = "" + } + + // --- host extensions --- + + if sr := i.SystemRole.String(); sr != "" { + cert.Permissions.Extensions[utils.CertExtensionRole] = sr } - cert.Permissions.Extensions = map[string]string{ - teleport.CertExtensionPermitPTY: "", + + if i.ClusterName != "" { + cert.Permissions.Extensions[utils.CertExtensionAuthority] = i.ClusterName } + // --- user extensions --- + if i.PermitX11Forwarding { cert.Permissions.Extensions[teleport.CertExtensionPermitX11Forwarding] = "" } @@ -176,8 +204,12 @@ func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { if i.BotName != "" { cert.Permissions.Extensions[teleport.CertExtensionBotName] = i.BotName } - if i.AllowedResourceIDs != "" { - cert.Permissions.Extensions[teleport.CertExtensionAllowedResources] = i.AllowedResourceIDs + if len(i.AllowedResourceIDs) != 0 { + requestedResourcesStr, err := types.ResourceIDsToString(i.AllowedResourceIDs) + if err != nil { + return nil, trace.Wrap(err) + } + cert.Permissions.Extensions[teleport.CertExtensionAllowedResources] = requestedResourcesStr } if i.ConnectionDiagnosticID != "" { cert.Permissions.Extensions[teleport.CertExtensionConnectionDiagnosticID] = i.ConnectionDiagnosticID @@ -239,8 +271,11 @@ func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { if i.RouteToCluster != "" { cert.Permissions.Extensions[teleport.CertExtensionTeleportRouteToCluster] = i.RouteToCluster } - if !i.ActiveRequests.IsEmpty() { - requests, err := i.ActiveRequests.Marshal() + if len(i.ActiveRequests) != 0 { + reqs := services.RequestIDs{ + AccessRequests: i.ActiveRequests, + } + requests, err := reqs.Marshal() if err != nil { return nil, trace.Wrap(err) } @@ -253,14 +288,12 @@ func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { // DecodeIdentity decodes an ssh certificate into an identity. func DecodeIdentity(cert *ssh.Certificate) (*Identity, error) { - if cert.CertType != ssh.UserCert { - return nil, trace.BadParameter("DecodeIdentity intended for use with user certs, got %v", cert.CertType) - } ident := &Identity{ - Username: cert.KeyId, - AllowedLogins: cert.ValidPrincipals, - ValidAfter: cert.ValidAfter, - ValidBefore: cert.ValidBefore, + Username: cert.KeyId, + Principals: cert.ValidPrincipals, + ValidAfter: cert.ValidAfter, + ValidBefore: cert.ValidBefore, + CertType: cert.CertType, } // clone the extension map and remove entries from the clone as they are processed so @@ -286,9 +319,19 @@ func DecodeIdentity(cert *ssh.Certificate) (*Identity, error) { return ok } - // ignore the permit pty extension, it's always set + // ignore the permit pty extension, teleport considers this permission implied for all users _, _ = takeExtension(teleport.CertExtensionPermitPTY) + // --- host extensions --- + + if v, ok := takeExtension(utils.CertExtensionRole); ok { + ident.SystemRole = types.SystemRole(v) + } + + ident.ClusterName = takeValue(utils.CertExtensionAuthority) + + // --- user extensions --- + ident.PermitX11Forwarding = takeBool(teleport.CertExtensionPermitX11Forwarding) ident.PermitAgentForwarding = takeBool(teleport.CertExtensionPermitAgentForwarding) ident.PermitPortForwarding = takeBool(teleport.CertExtensionPermitPortForwarding) @@ -316,7 +359,15 @@ func DecodeIdentity(cert *ssh.Certificate) (*Identity, error) { } ident.BotName = takeValue(teleport.CertExtensionBotName) - ident.AllowedResourceIDs = takeValue(teleport.CertExtensionAllowedResources) + + if v, ok := takeExtension(teleport.CertExtensionAllowedResources); ok { + resourceIDs, err := types.ResourceIDsFromString(v) + if err != nil { + return nil, trace.BadParameter("failed to parse value %q for extension %q as resource IDs: %v", v, teleport.CertExtensionAllowedResources, err) + } + ident.AllowedResourceIDs = resourceIDs + } + ident.ConnectionDiagnosticID = takeValue(teleport.CertExtensionConnectionDiagnosticID) ident.PrivateKeyPolicy = keys.PrivateKeyPolicy(takeValue(teleport.CertExtensionPrivateKeyPolicy)) ident.DeviceID = takeValue(teleport.CertExtensionDeviceID) @@ -350,11 +401,11 @@ func DecodeIdentity(cert *ssh.Certificate) (*Identity, error) { ident.RouteToCluster = takeValue(teleport.CertExtensionTeleportRouteToCluster) if v, ok := takeExtension(teleport.CertExtensionTeleportActiveRequests); ok { - var requests services.RequestIDs - if err := requests.Unmarshal([]byte(v)); err != nil { + var reqs services.RequestIDs + if err := reqs.Unmarshal([]byte(v)); err != nil { return nil, trace.BadParameter("failed to unmarshal value %q for extension %q as active requests: %v", v, teleport.CertExtensionTeleportActiveRequests, err) } - ident.ActiveRequests = requests + ident.ActiveRequests = reqs.AccessRequests } // aggregate all remaining extensions into the CertificateExtensions field diff --git a/lib/sshca/identity_test.go b/lib/sshca/identity_test.go index d188db90bbdcc..673ac17dbbba7 100644 --- a/lib/sshca/identity_test.go +++ b/lib/sshca/identity_test.go @@ -26,31 +26,32 @@ import ( "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/constants" "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/services" "github.com/gravitational/teleport/lib/utils/testutils" ) func TestIdentityConversion(t *testing.T) { ident := &Identity{ - ValidAfter: 1, - ValidBefore: 2, - Username: "user", - Impersonator: "impersonator", - AllowedLogins: []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: services.RequestIDs{ - AccessRequests: []string{uuid.NewString()}, - }, + 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", @@ -62,10 +63,15 @@ func TestIdentityConversion(t *testing.T) { Type: types.CertExtensionType_SSH, Mode: types.CertExtensionMode_EXTENSION, }}, - Renewable: true, - Generation: 3, - BotName: "bot", - AllowedResourceIDs: "resource", + Renewable: true, + Generation: 3, + BotName: "bot", + 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", @@ -80,6 +86,9 @@ func TestIdentityConversion(t *testing.T) { "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...)) diff --git a/lib/sshca/sshca.go b/lib/sshca/sshca.go index 280d3ab9c7a10..4ec2ef7c050ab 100644 --- a/lib/sshca/sshca.go +++ b/lib/sshca/sshca.go @@ -26,7 +26,6 @@ import ( "golang.org/x/crypto/ssh" apidefaults "github.com/gravitational/teleport/api/defaults" - "github.com/gravitational/teleport/lib/services" ) // Authority implements minimal key-management facility for generating OpenSSH @@ -35,7 +34,7 @@ type Authority interface { // GenerateHostCert takes the private key of the CA, public key of the new host, // along with metadata (host ID, node name, cluster name, roles, and ttl) and generates // a host certificate. - GenerateHostCert(certParams services.HostCertParams) ([]byte, error) + GenerateHostCert(HostCertificateRequest) ([]byte, error) // GenerateUserCert generates user ssh certificate, it takes pkey as a signing // private key (user certificate authority) @@ -45,6 +44,47 @@ type Authority interface { Close() } +// HostCertificateRequest is a request to generate a new ssh host certificate. +type HostCertificateRequest struct { + // CASigner is the signer that will sign the public key of the host with the CA private key + CASigner ssh.Signer + // PublicHostKey is the public key of the host + PublicHostKey []byte + // HostID is used by Teleport to uniquely identify a node within a cluster (this is used to help infill + // Identity.Princiapals and is not a standalone cert field). + HostID string + // NodeName is the DNS name of the node (this is used to help infill Identity.Princiapals and is not a + // standalone cert field). + NodeName string + // TTL defines how long a certificate is valid for + TTL time.Duration + // Identity is the host identity to be encoded in the certificate. + Identity Identity +} + +func (r *HostCertificateRequest) Check() error { + if r.CASigner == nil { + return trace.BadParameter("ssh host certificate request missing ca signer") + } + if r.HostID == "" && len(r.Identity.Principals) == 0 { + return trace.BadParameter("ssh host certificate request missing host ID and principals") + } + if r.Identity.ClusterName == "" { + return trace.BadParameter("ssh host certificate request missing cluster name") + } + if r.Identity.ValidBefore != 0 { + return trace.BadParameter("ValidBefore should not be set in host cert requests (derived from TTL)") + } + if r.Identity.ValidAfter != 0 { + return trace.BadParameter("ValidAfter should not be set in host cert requests (derived from TTL)") + } + if err := r.Identity.SystemRole.Check(); err != nil { + return trace.Wrap(err) + } + + return nil +} + // UserCertificateRequest is a request to generate a new ssh user certificate. type UserCertificateRequest struct { // CASigner is the signer that will sign the public key of the user with the CA private key @@ -67,8 +107,14 @@ func (r *UserCertificateRequest) CheckAndSetDefaults() error { if r.TTL < apidefaults.MinCertDuration { r.TTL = apidefaults.MinCertDuration } - if err := r.Identity.Check(); err != nil { - return trace.Wrap(err) + if len(r.Identity.Principals) == 0 { + return trace.BadParameter("ssh user identity missing allowed logins") + } + if r.Identity.ValidBefore != 0 { + return trace.BadParameter("ValidBefore should not be set in user cert requests (derived from TTL)") + } + if r.Identity.ValidAfter != 0 { + return trace.BadParameter("ValidAfter should not be set in user cert requests (derived from TTL)") } return nil