mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
Add disconnect expired cert and lock mode for scoped role (#66376)
* Add disconnect expired cert and lock mode for scoped role Update IAC docs * respond to code review Add a scoped authorizer in the forwarder_test fix up proto comments Update forwarder.go * fix forwarder setting nil disconnectExpiredCert * Update validate lock to accept empty string - will be treated as best_effort down the line add comment
This commit is contained in:
@@ -234,8 +234,14 @@ type ScopedRoleDefaults struct {
|
||||
// SessionRecording configures the session recording strategy for all protocols that don't
|
||||
// explicitly set their session recording mode.
|
||||
SessionRecording *SessionRecording `protobuf:"bytes,2,opt,name=session_recording,json=sessionRecording,proto3" json:"session_recording,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// DisconnectExpiredCert defines the default behavior of all protocols when certs expire for a session.
|
||||
// If unset, cluster wide defaults are used.
|
||||
DisconnectExpiredCert *bool `protobuf:"varint,3,opt,name=disconnect_expired_cert,json=disconnectExpiredCert,proto3,oneof" json:"disconnect_expired_cert,omitempty"`
|
||||
// Lock specifies the default locking mode for access sessions across all protocols that
|
||||
// do not specify their own value. If unset, cluster wide defaults are used.
|
||||
Lock *Lock `protobuf:"bytes,4,opt,name=lock,proto3" json:"lock,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ScopedRoleDefaults) Reset() {
|
||||
@@ -282,6 +288,20 @@ func (x *ScopedRoleDefaults) GetSessionRecording() *SessionRecording {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ScopedRoleDefaults) GetDisconnectExpiredCert() bool {
|
||||
if x != nil && x.DisconnectExpiredCert != nil {
|
||||
return *x.DisconnectExpiredCert
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ScopedRoleDefaults) GetLock() *Lock {
|
||||
if x != nil {
|
||||
return x.Lock
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScopedRoleSSH groups all scoped role fields relevant to SSH access. Fields within the SSH block
|
||||
// encompass selection criteria and preconditions for access, as well as the controls to be applied in
|
||||
// cases where access is permitted. An SSH block is the primary source of truth for controls to be applied
|
||||
@@ -318,8 +338,14 @@ type ScopedRoleSSH struct {
|
||||
EnhancedRecording *EnhancedRecording `protobuf:"bytes,12,opt,name=enhanced_recording,json=enhancedRecording,proto3" json:"enhanced_recording,omitempty"`
|
||||
// SessionRecording configures the session recording strategy for SSH sessions.
|
||||
SessionRecording *SessionRecording `protobuf:"bytes,13,opt,name=session_recording,json=sessionRecording,proto3" json:"session_recording,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// DisconnectExpiredCert controls whether SSH sessions are disconnected when the
|
||||
// user certificate expires.
|
||||
// Defaults to value cluster wide auth preference if not set.
|
||||
DisconnectExpiredCert *bool `protobuf:"varint,14,opt,name=disconnect_expired_cert,json=disconnectExpiredCert,proto3,oneof" json:"disconnect_expired_cert,omitempty"`
|
||||
// Lock configures the role's locking behavior for SSH sessions.
|
||||
Lock *Lock `protobuf:"bytes,15,opt,name=lock,proto3" json:"lock,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ScopedRoleSSH) Reset() {
|
||||
@@ -436,6 +462,20 @@ func (x *ScopedRoleSSH) GetSessionRecording() *SessionRecording {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ScopedRoleSSH) GetDisconnectExpiredCert() bool {
|
||||
if x != nil && x.DisconnectExpiredCert != nil {
|
||||
return *x.DisconnectExpiredCert
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ScopedRoleSSH) GetLock() *Lock {
|
||||
if x != nil {
|
||||
return x.Lock
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The group of all scoped role fields relevant to kube access. Fields within the kube block
|
||||
// encompass selection criteria and preconditions for access, as well as the controls to be applied in
|
||||
// cases where access is permitted. A kube block is the primary source of truth for controls to be applied
|
||||
@@ -453,8 +493,12 @@ type ScopedRoleKube struct {
|
||||
// Must be a valid Go duration string (e.g. "30m", "1h"). If empty, the defaults block value
|
||||
// (or global default) applies.
|
||||
ClientIdleTimeout string `protobuf:"bytes,5,opt,name=client_idle_timeout,json=clientIdleTimeout,proto3" json:"client_idle_timeout,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// DisconnectExpiredCert controls whether Kube sessions are disconnected when the user certificate expires.
|
||||
DisconnectExpiredCert *bool `protobuf:"varint,6,opt,name=disconnect_expired_cert,json=disconnectExpiredCert,proto3,oneof" json:"disconnect_expired_cert,omitempty"`
|
||||
// Lock configures the role's locking behavior for kubernetes sessions.
|
||||
Lock *Lock `protobuf:"bytes,7,opt,name=lock,proto3" json:"lock,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ScopedRoleKube) Reset() {
|
||||
@@ -515,6 +559,20 @@ func (x *ScopedRoleKube) GetClientIdleTimeout() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ScopedRoleKube) GetDisconnectExpiredCert() bool {
|
||||
if x != nil && x.DisconnectExpiredCert != nil {
|
||||
return *x.DisconnectExpiredCert
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ScopedRoleKube) GetLock() *Lock {
|
||||
if x != nil {
|
||||
return x.Lock
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScopedRule maps resources to verbs. This is the underlying type used to describe
|
||||
// permissions like 'scoped_role:read' or 'scoped_role_assignment:create'.
|
||||
type ScopedRule struct {
|
||||
@@ -847,7 +905,7 @@ func (x *EnhancedRecording) GetDisk() bool {
|
||||
// SessionRecording sets the session recording behavior.
|
||||
type SessionRecording struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Mode sets the session recording mode. Allowed values: strict | best_effort.
|
||||
// Mode sets the session recording mode. Allowed values: strict or best_effort.
|
||||
Mode string `protobuf:"bytes,1,opt,name=mode,proto3" json:"mode,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -890,6 +948,54 @@ func (x *SessionRecording) GetMode() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Lock controls locking mode for sessions authorized via this
|
||||
// scoped role.
|
||||
type Lock struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Allowed values: strict or best_effort.
|
||||
// Defaults to value cluster wide auth preference if not set.
|
||||
Mode string `protobuf:"bytes,1,opt,name=mode,proto3" json:"mode,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Lock) Reset() {
|
||||
*x = Lock{}
|
||||
mi := &file_teleport_scopes_access_v1_role_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Lock) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Lock) ProtoMessage() {}
|
||||
|
||||
func (x *Lock) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_teleport_scopes_access_v1_role_proto_msgTypes[12]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Lock.ProtoReflect.Descriptor instead.
|
||||
func (*Lock) Descriptor() ([]byte, []int) {
|
||||
return file_teleport_scopes_access_v1_role_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *Lock) GetMode() string {
|
||||
if x != nil {
|
||||
return x.Mode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_teleport_scopes_access_v1_role_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_teleport_scopes_access_v1_role_proto_rawDesc = "" +
|
||||
@@ -908,10 +1014,13 @@ const file_teleport_scopes_access_v1_role_proto_rawDesc = "" +
|
||||
"\bdefaults\x18\x05 \x01(\v2-.teleport.scopes.access.v1.ScopedRoleDefaultsR\bdefaults\x12;\n" +
|
||||
"\x05rules\x18\x06 \x03(\v2%.teleport.scopes.access.v1.ScopedRuleR\x05rules\x12:\n" +
|
||||
"\x03ssh\x18\a \x01(\v2(.teleport.scopes.access.v1.ScopedRoleSSHR\x03ssh\x12=\n" +
|
||||
"\x04kube\x18\b \x01(\v2).teleport.scopes.access.v1.ScopedRoleKubeR\x04kubeJ\x04\b\x02\x10\x03J\x04\b\x03\x10\x04R\x05allowR\aoptions\"\x9e\x01\n" +
|
||||
"\x04kube\x18\b \x01(\v2).teleport.scopes.access.v1.ScopedRoleKubeR\x04kubeJ\x04\b\x02\x10\x03J\x04\b\x03\x10\x04R\x05allowR\aoptions\"\xac\x02\n" +
|
||||
"\x12ScopedRoleDefaults\x12.\n" +
|
||||
"\x13client_idle_timeout\x18\x01 \x01(\tR\x11clientIdleTimeout\x12X\n" +
|
||||
"\x11session_recording\x18\x02 \x01(\v2+.teleport.scopes.access.v1.SessionRecordingR\x10sessionRecording\"\x8b\x06\n" +
|
||||
"\x11session_recording\x18\x02 \x01(\v2+.teleport.scopes.access.v1.SessionRecordingR\x10sessionRecording\x12;\n" +
|
||||
"\x17disconnect_expired_cert\x18\x03 \x01(\bH\x00R\x15disconnectExpiredCert\x88\x01\x01\x123\n" +
|
||||
"\x04lock\x18\x04 \x01(\v2\x1f.teleport.scopes.access.v1.LockR\x04lockB\x1a\n" +
|
||||
"\x18_disconnect_expired_cert\"\x99\a\n" +
|
||||
"\rScopedRoleSSH\x12\x16\n" +
|
||||
"\x06logins\x18\x01 \x03(\tR\x06logins\x120\n" +
|
||||
"\x06labels\x18\x02 \x03(\v2\x18.teleport.label.v1.LabelR\x06labels\x12.\n" +
|
||||
@@ -925,17 +1034,23 @@ const file_teleport_scopes_access_v1_role_proto_rawDesc = "" +
|
||||
" \x03(\tR\vhostSudoers\x12 \n" +
|
||||
"\tfile_copy\x18\v \x01(\bH\x03R\bfileCopy\x88\x01\x01\x12[\n" +
|
||||
"\x12enhanced_recording\x18\f \x01(\v2,.teleport.scopes.access.v1.EnhancedRecordingR\x11enhancedRecording\x12X\n" +
|
||||
"\x11session_recording\x18\r \x01(\v2+.teleport.scopes.access.v1.SessionRecordingR\x10sessionRecordingB\x18\n" +
|
||||
"\x11session_recording\x18\r \x01(\v2+.teleport.scopes.access.v1.SessionRecordingR\x10sessionRecording\x12;\n" +
|
||||
"\x17disconnect_expired_cert\x18\x0e \x01(\bH\x04R\x15disconnectExpiredCert\x88\x01\x01\x123\n" +
|
||||
"\x04lock\x18\x0f \x01(\v2\x1f.teleport.scopes.access.v1.LockR\x04lockB\x18\n" +
|
||||
"\x16_permit_x11_forwardingB\x10\n" +
|
||||
"\x0e_forward_agentB\x0f\n" +
|
||||
"\r_max_sessionsB\f\n" +
|
||||
"\n" +
|
||||
"_file_copy\"\xb1\x01\n" +
|
||||
"_file_copyB\x1a\n" +
|
||||
"\x18_disconnect_expired_cert\"\xbf\x02\n" +
|
||||
"\x0eScopedRoleKube\x120\n" +
|
||||
"\x06labels\x18\x01 \x03(\v2\x18.teleport.label.v1.LabelR\x06labels\x12\x16\n" +
|
||||
"\x06groups\x18\x02 \x03(\tR\x06groups\x12\x14\n" +
|
||||
"\x05users\x18\x03 \x03(\tR\x05users\x12.\n" +
|
||||
"\x13client_idle_timeout\x18\x05 \x01(\tR\x11clientIdleTimeoutJ\x04\b\x04\x10\x05R\tresources\"@\n" +
|
||||
"\x13client_idle_timeout\x18\x05 \x01(\tR\x11clientIdleTimeout\x12;\n" +
|
||||
"\x17disconnect_expired_cert\x18\x06 \x01(\bH\x00R\x15disconnectExpiredCert\x88\x01\x01\x123\n" +
|
||||
"\x04lock\x18\a \x01(\v2\x1f.teleport.scopes.access.v1.LockR\x04lockB\x1a\n" +
|
||||
"\x18_disconnect_expired_certJ\x04\b\x04\x10\x05R\tresources\"@\n" +
|
||||
"\n" +
|
||||
"ScopedRule\x12\x1c\n" +
|
||||
"\tresources\x18\x01 \x03(\tR\tresources\x12\x14\n" +
|
||||
@@ -965,6 +1080,8 @@ const file_teleport_scopes_access_v1_role_proto_rawDesc = "" +
|
||||
"\b_networkB\a\n" +
|
||||
"\x05_disk\"&\n" +
|
||||
"\x10SessionRecording\x12\x12\n" +
|
||||
"\x04mode\x18\x01 \x01(\tR\x04mode\"\x1a\n" +
|
||||
"\x04Lock\x12\x12\n" +
|
||||
"\x04mode\x18\x01 \x01(\tR\x04modeBWZUgithub.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1;accessv1b\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -979,7 +1096,7 @@ func file_teleport_scopes_access_v1_role_proto_rawDescGZIP() []byte {
|
||||
return file_teleport_scopes_access_v1_role_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_teleport_scopes_access_v1_role_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
|
||||
var file_teleport_scopes_access_v1_role_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
|
||||
var file_teleport_scopes_access_v1_role_proto_goTypes = []any{
|
||||
(*ScopedRole)(nil), // 0: teleport.scopes.access.v1.ScopedRole
|
||||
(*ScopedRoleSpec)(nil), // 1: teleport.scopes.access.v1.ScopedRoleSpec
|
||||
@@ -993,30 +1110,34 @@ var file_teleport_scopes_access_v1_role_proto_goTypes = []any{
|
||||
(*CreateHostUser)(nil), // 9: teleport.scopes.access.v1.CreateHostUser
|
||||
(*EnhancedRecording)(nil), // 10: teleport.scopes.access.v1.EnhancedRecording
|
||||
(*SessionRecording)(nil), // 11: teleport.scopes.access.v1.SessionRecording
|
||||
(*v1.Metadata)(nil), // 12: teleport.header.v1.Metadata
|
||||
(*v11.Label)(nil), // 13: teleport.label.v1.Label
|
||||
(*Lock)(nil), // 12: teleport.scopes.access.v1.Lock
|
||||
(*v1.Metadata)(nil), // 13: teleport.header.v1.Metadata
|
||||
(*v11.Label)(nil), // 14: teleport.label.v1.Label
|
||||
}
|
||||
var file_teleport_scopes_access_v1_role_proto_depIdxs = []int32{
|
||||
12, // 0: teleport.scopes.access.v1.ScopedRole.metadata:type_name -> teleport.header.v1.Metadata
|
||||
13, // 0: teleport.scopes.access.v1.ScopedRole.metadata:type_name -> teleport.header.v1.Metadata
|
||||
1, // 1: teleport.scopes.access.v1.ScopedRole.spec:type_name -> teleport.scopes.access.v1.ScopedRoleSpec
|
||||
2, // 2: teleport.scopes.access.v1.ScopedRoleSpec.defaults:type_name -> teleport.scopes.access.v1.ScopedRoleDefaults
|
||||
5, // 3: teleport.scopes.access.v1.ScopedRoleSpec.rules:type_name -> teleport.scopes.access.v1.ScopedRule
|
||||
3, // 4: teleport.scopes.access.v1.ScopedRoleSpec.ssh:type_name -> teleport.scopes.access.v1.ScopedRoleSSH
|
||||
4, // 5: teleport.scopes.access.v1.ScopedRoleSpec.kube:type_name -> teleport.scopes.access.v1.ScopedRoleKube
|
||||
11, // 6: teleport.scopes.access.v1.ScopedRoleDefaults.session_recording:type_name -> teleport.scopes.access.v1.SessionRecording
|
||||
13, // 7: teleport.scopes.access.v1.ScopedRoleSSH.labels:type_name -> teleport.label.v1.Label
|
||||
6, // 8: teleport.scopes.access.v1.ScopedRoleSSH.port_forwarding:type_name -> teleport.scopes.access.v1.SSHPortForwarding
|
||||
9, // 9: teleport.scopes.access.v1.ScopedRoleSSH.host_user_creation:type_name -> teleport.scopes.access.v1.CreateHostUser
|
||||
10, // 10: teleport.scopes.access.v1.ScopedRoleSSH.enhanced_recording:type_name -> teleport.scopes.access.v1.EnhancedRecording
|
||||
11, // 11: teleport.scopes.access.v1.ScopedRoleSSH.session_recording:type_name -> teleport.scopes.access.v1.SessionRecording
|
||||
13, // 12: teleport.scopes.access.v1.ScopedRoleKube.labels:type_name -> teleport.label.v1.Label
|
||||
7, // 13: teleport.scopes.access.v1.SSHPortForwarding.local:type_name -> teleport.scopes.access.v1.SSHLocalPortForwarding
|
||||
8, // 14: teleport.scopes.access.v1.SSHPortForwarding.remote:type_name -> teleport.scopes.access.v1.SSHRemotePortForwarding
|
||||
15, // [15:15] is the sub-list for method output_type
|
||||
15, // [15:15] is the sub-list for method input_type
|
||||
15, // [15:15] is the sub-list for extension type_name
|
||||
15, // [15:15] is the sub-list for extension extendee
|
||||
0, // [0:15] is the sub-list for field type_name
|
||||
12, // 7: teleport.scopes.access.v1.ScopedRoleDefaults.lock:type_name -> teleport.scopes.access.v1.Lock
|
||||
14, // 8: teleport.scopes.access.v1.ScopedRoleSSH.labels:type_name -> teleport.label.v1.Label
|
||||
6, // 9: teleport.scopes.access.v1.ScopedRoleSSH.port_forwarding:type_name -> teleport.scopes.access.v1.SSHPortForwarding
|
||||
9, // 10: teleport.scopes.access.v1.ScopedRoleSSH.host_user_creation:type_name -> teleport.scopes.access.v1.CreateHostUser
|
||||
10, // 11: teleport.scopes.access.v1.ScopedRoleSSH.enhanced_recording:type_name -> teleport.scopes.access.v1.EnhancedRecording
|
||||
11, // 12: teleport.scopes.access.v1.ScopedRoleSSH.session_recording:type_name -> teleport.scopes.access.v1.SessionRecording
|
||||
12, // 13: teleport.scopes.access.v1.ScopedRoleSSH.lock:type_name -> teleport.scopes.access.v1.Lock
|
||||
14, // 14: teleport.scopes.access.v1.ScopedRoleKube.labels:type_name -> teleport.label.v1.Label
|
||||
12, // 15: teleport.scopes.access.v1.ScopedRoleKube.lock:type_name -> teleport.scopes.access.v1.Lock
|
||||
7, // 16: teleport.scopes.access.v1.SSHPortForwarding.local:type_name -> teleport.scopes.access.v1.SSHLocalPortForwarding
|
||||
8, // 17: teleport.scopes.access.v1.SSHPortForwarding.remote:type_name -> teleport.scopes.access.v1.SSHRemotePortForwarding
|
||||
18, // [18:18] is the sub-list for method output_type
|
||||
18, // [18:18] is the sub-list for method input_type
|
||||
18, // [18:18] is the sub-list for extension type_name
|
||||
18, // [18:18] is the sub-list for extension extendee
|
||||
0, // [0:18] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_teleport_scopes_access_v1_role_proto_init() }
|
||||
@@ -1024,7 +1145,9 @@ func file_teleport_scopes_access_v1_role_proto_init() {
|
||||
if File_teleport_scopes_access_v1_role_proto != nil {
|
||||
return
|
||||
}
|
||||
file_teleport_scopes_access_v1_role_proto_msgTypes[2].OneofWrappers = []any{}
|
||||
file_teleport_scopes_access_v1_role_proto_msgTypes[3].OneofWrappers = []any{}
|
||||
file_teleport_scopes_access_v1_role_proto_msgTypes[4].OneofWrappers = []any{}
|
||||
file_teleport_scopes_access_v1_role_proto_msgTypes[7].OneofWrappers = []any{}
|
||||
file_teleport_scopes_access_v1_role_proto_msgTypes[8].OneofWrappers = []any{}
|
||||
file_teleport_scopes_access_v1_role_proto_msgTypes[10].OneofWrappers = []any{}
|
||||
@@ -1034,7 +1157,7 @@ func file_teleport_scopes_access_v1_role_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_scopes_access_v1_role_proto_rawDesc), len(file_teleport_scopes_access_v1_role_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 12,
|
||||
NumMessages: 13,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -89,6 +89,14 @@ message ScopedRoleDefaults {
|
||||
// SessionRecording configures the session recording strategy for all protocols that don't
|
||||
// explicitly set their session recording mode.
|
||||
SessionRecording session_recording = 2;
|
||||
|
||||
// DisconnectExpiredCert defines the default behavior of all protocols when certs expire for a session.
|
||||
// If unset, cluster wide defaults are used.
|
||||
optional bool disconnect_expired_cert = 3;
|
||||
|
||||
// Lock specifies the default locking mode for access sessions across all protocols that
|
||||
// do not specify their own value. If unset, cluster wide defaults are used.
|
||||
Lock lock = 4;
|
||||
}
|
||||
|
||||
// ScopedRoleSSH groups all scoped role fields relevant to SSH access. Fields within the SSH block
|
||||
@@ -136,6 +144,14 @@ message ScopedRoleSSH {
|
||||
|
||||
// SessionRecording configures the session recording strategy for SSH sessions.
|
||||
SessionRecording session_recording = 13;
|
||||
|
||||
// DisconnectExpiredCert controls whether SSH sessions are disconnected when the
|
||||
// user certificate expires.
|
||||
// Defaults to value cluster wide auth preference if not set.
|
||||
optional bool disconnect_expired_cert = 14;
|
||||
|
||||
// Lock configures the role's locking behavior for SSH sessions.
|
||||
Lock lock = 15;
|
||||
}
|
||||
|
||||
// The group of all scoped role fields relevant to kube access. Fields within the kube block
|
||||
@@ -160,6 +176,12 @@ message ScopedRoleKube {
|
||||
// Must be a valid Go duration string (e.g. "30m", "1h"). If empty, the defaults block value
|
||||
// (or global default) applies.
|
||||
string client_idle_timeout = 5;
|
||||
|
||||
// DisconnectExpiredCert controls whether Kube sessions are disconnected when the user certificate expires.
|
||||
optional bool disconnect_expired_cert = 6;
|
||||
|
||||
// Lock configures the role's locking behavior for kubernetes sessions.
|
||||
Lock lock = 7;
|
||||
}
|
||||
|
||||
// ScopedRule maps resources to verbs. This is the underlying type used to describe
|
||||
@@ -213,6 +235,14 @@ message EnhancedRecording {
|
||||
|
||||
// SessionRecording sets the session recording behavior.
|
||||
message SessionRecording {
|
||||
// Mode sets the session recording mode. Allowed values: strict | best_effort.
|
||||
// Mode sets the session recording mode. Allowed values: strict or best_effort.
|
||||
string mode = 1;
|
||||
}
|
||||
|
||||
// Lock controls locking mode for sessions authorized via this
|
||||
// scoped role.
|
||||
message Lock {
|
||||
// Allowed values: strict or best_effort.
|
||||
// Defaults to value cluster wide auth preference if not set.
|
||||
string mode = 1;
|
||||
}
|
||||
|
||||
+26
-2
@@ -41,21 +41,31 @@ resource, which you can apply after installing the Teleport Kubernetes operator.
|
||||
|Field|Type|Description|
|
||||
|---|---|---|
|
||||
|client_idle_timeout|string|ClientIdleTimeout sets the default idle timeout for access sessions across all protocols that do not specify their own value. Must be a valid Go duration string (e.g. "30m", "1h").|
|
||||
|disconnect_expired_cert|boolean|DisconnectExpiredCert defines the default behavior of all protocols when certs expire for a session. If unset, cluster wide defaults are used.|
|
||||
|lock|[object](#specdefaultslock)|Lock specifies the default locking mode for access sessions across all protocols that do not specify their own value. If unset, cluster wide defaults are used.|
|
||||
|session_recording|[object](#specdefaultssession_recording)|SessionRecording configures the session recording strategy for all protocols that don't explicitly set their session recording mode.|
|
||||
|
||||
### spec.defaults.lock
|
||||
|
||||
|Field|Type|Description|
|
||||
|---|---|---|
|
||||
|mode|string|Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.|
|
||||
|
||||
### spec.defaults.session_recording
|
||||
|
||||
|Field|Type|Description|
|
||||
|---|---|---|
|
||||
|mode|string|Mode sets the session recording mode. Allowed values: strict | best_effort.|
|
||||
|mode|string|Mode sets the session recording mode. Allowed values: strict or best_effort.|
|
||||
|
||||
### spec.kube
|
||||
|
||||
|Field|Type|Description|
|
||||
|---|---|---|
|
||||
|client_idle_timeout|string|Overrides the defaults block idle timeout specifically for kube sessions. Must be a valid Go duration string (e.g. "30m", "1h"). If empty, the defaults block value (or global default) applies.|
|
||||
|disconnect_expired_cert|boolean|DisconnectExpiredCert controls whether Kube sessions are disconnected when the user certificate expires.|
|
||||
|groups|[]string|The list of kubernetes groups this role allows.|
|
||||
|labels|[][object](#speckubelabels-items)|The map of kubernetes cluster labels used for RBAC.|
|
||||
|lock|[object](#speckubelock)|Lock configures the role's locking behavior for kubernetes sessions.|
|
||||
|users|[]string|An optional list of impersonatable kubernetes users this role allows.|
|
||||
|
||||
### spec.kube.labels items
|
||||
@@ -65,6 +75,12 @@ resource, which you can apply after installing the Teleport Kubernetes operator.
|
||||
|name|string|The name of the label.|
|
||||
|values|[]string|The values associated with the label.|
|
||||
|
||||
### spec.kube.lock
|
||||
|
||||
|Field|Type|Description|
|
||||
|---|---|---|
|
||||
|mode|string|Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.|
|
||||
|
||||
### spec.rules items
|
||||
|
||||
|Field|Type|Description|
|
||||
@@ -77,12 +93,14 @@ resource, which you can apply after installing the Teleport Kubernetes operator.
|
||||
|Field|Type|Description|
|
||||
|---|---|---|
|
||||
|client_idle_timeout|string|ClientIdleTimeout overrides the defaults block idle timeout specifically for SSH sessions. Must be a valid Go duration string (e.g. "30m", "1h"). If empty, the defaults block value (or global default) applies.|
|
||||
|disconnect_expired_cert|boolean|DisconnectExpiredCert controls whether SSH sessions are disconnected when the user certificate expires. Defaults to value cluster wide auth preference if not set.|
|
||||
|enhanced_recording|[object](#specsshenhanced_recording)|EnhancedRecording is the set of BPF events to record for enhanced session recording.|
|
||||
|file_copy|boolean|FileCopy indicates whether remote file operations via SCP or SFTP are allowed over an SSH session. It defaults to allowing the user to download and upload files by default.|
|
||||
|forward_agent|boolean|ForwardAgent enables SSH agent forwarding.|
|
||||
|host_sudoers|[]string|Sudoers is a list of entries to include in a users sudoer file|
|
||||
|host_user_creation|[object](#specsshhost_user_creation)|HostUserCreation configures the creation of host users.|
|
||||
|labels|[][object](#specsshlabels-items)|Labels is the set of node labels used to dynamically select which nodes this role applies to.|
|
||||
|lock|[object](#specsshlock)|Lock configures the role's locking behavior for SSH sessions.|
|
||||
|logins|[]string|Logins is the list of OS logins this role permits on matching nodes.|
|
||||
|max_sessions|integer|MaxSessions defines the maximum number of concurrent sessions per connection.|
|
||||
|permit_x11_forwarding|boolean|PermitX11Forwarding, when true, authorizes use of X11 forwarding over SSH sessions. If not set, X11 forwarding is not permitted.|
|
||||
@@ -112,6 +130,12 @@ resource, which you can apply after installing the Teleport Kubernetes operator.
|
||||
|name|string|The name of the label.|
|
||||
|values|[]string|The values associated with the label.|
|
||||
|
||||
### spec.ssh.lock
|
||||
|
||||
|Field|Type|Description|
|
||||
|---|---|---|
|
||||
|mode|string|Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.|
|
||||
|
||||
### spec.ssh.port_forwarding
|
||||
|
||||
|Field|Type|Description|
|
||||
@@ -135,5 +159,5 @@ resource, which you can apply after installing the Teleport Kubernetes operator.
|
||||
|
||||
|Field|Type|Description|
|
||||
|---|---|---|
|
||||
|mode|string|Mode sets the session recording mode. Allowed values: strict | best_effort.|
|
||||
|mode|string|Mode sets the session recording mode. Allowed values: strict or best_effort.|
|
||||
|
||||
|
||||
+29
-2
@@ -63,13 +63,22 @@ Optional:
|
||||
Optional:
|
||||
|
||||
- `client_idle_timeout` (String) ClientIdleTimeout sets the default idle timeout for access sessions across all protocols that do not specify their own value. Must be a valid Go duration string (e.g. "30m", "1h").
|
||||
- `disconnect_expired_cert` (Boolean) DisconnectExpiredCert defines the default behavior of all protocols when certs expire for a session. If unset, cluster wide defaults are used.
|
||||
- `lock` (Attributes) Lock specifies the default locking mode for access sessions across all protocols that do not specify their own value. If unset, cluster wide defaults are used. (see [below for nested schema](#nested-schema-for-specdefaultslock))
|
||||
- `session_recording` (Attributes) SessionRecording configures the session recording strategy for all protocols that don't explicitly set their session recording mode. (see [below for nested schema](#nested-schema-for-specdefaultssession_recording))
|
||||
|
||||
### Nested Schema for `spec.defaults.lock`
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.
|
||||
|
||||
|
||||
### Nested Schema for `spec.defaults.session_recording`
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Mode sets the session recording mode. Allowed values: strict | best_effort.
|
||||
- `mode` (String) Mode sets the session recording mode. Allowed values: strict or best_effort.
|
||||
|
||||
|
||||
|
||||
@@ -78,8 +87,10 @@ Optional:
|
||||
Optional:
|
||||
|
||||
- `client_idle_timeout` (String) Overrides the defaults block idle timeout specifically for kube sessions. Must be a valid Go duration string (e.g. "30m", "1h"). If empty, the defaults block value (or global default) applies.
|
||||
- `disconnect_expired_cert` (Boolean) DisconnectExpiredCert controls whether Kube sessions are disconnected when the user certificate expires.
|
||||
- `groups` (List of String) The list of kubernetes groups this role allows.
|
||||
- `labels` (Attributes List) The map of kubernetes cluster labels used for RBAC. (see [below for nested schema](#nested-schema-for-speckubelabels))
|
||||
- `lock` (Attributes) Lock configures the role's locking behavior for kubernetes sessions. (see [below for nested schema](#nested-schema-for-speckubelock))
|
||||
- `users` (List of String) An optional list of impersonatable kubernetes users this role allows.
|
||||
|
||||
### Nested Schema for `spec.kube.labels`
|
||||
@@ -90,6 +101,13 @@ Optional:
|
||||
- `values` (List of String) The values associated with the label.
|
||||
|
||||
|
||||
### Nested Schema for `spec.kube.lock`
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.
|
||||
|
||||
|
||||
|
||||
### Nested Schema for `spec.rules`
|
||||
|
||||
@@ -104,12 +122,14 @@ Optional:
|
||||
Optional:
|
||||
|
||||
- `client_idle_timeout` (String) ClientIdleTimeout overrides the defaults block idle timeout specifically for SSH sessions. Must be a valid Go duration string (e.g. "30m", "1h"). If empty, the defaults block value (or global default) applies.
|
||||
- `disconnect_expired_cert` (Boolean) DisconnectExpiredCert controls whether SSH sessions are disconnected when the user certificate expires. Defaults to value cluster wide auth preference if not set.
|
||||
- `enhanced_recording` (Attributes) EnhancedRecording is the set of BPF events to record for enhanced session recording. (see [below for nested schema](#nested-schema-for-specsshenhanced_recording))
|
||||
- `file_copy` (Boolean) FileCopy indicates whether remote file operations via SCP or SFTP are allowed over an SSH session. It defaults to allowing the user to download and upload files by default.
|
||||
- `forward_agent` (Boolean) ForwardAgent enables SSH agent forwarding.
|
||||
- `host_sudoers` (List of String) Sudoers is a list of entries to include in a users sudoer file
|
||||
- `host_user_creation` (Attributes) HostUserCreation configures the creation of host users. (see [below for nested schema](#nested-schema-for-specsshhost_user_creation))
|
||||
- `labels` (Attributes List) Labels is the set of node labels used to dynamically select which nodes this role applies to. (see [below for nested schema](#nested-schema-for-specsshlabels))
|
||||
- `lock` (Attributes) Lock configures the role's locking behavior for SSH sessions. (see [below for nested schema](#nested-schema-for-specsshlock))
|
||||
- `logins` (List of String) Logins is the list of OS logins this role permits on matching nodes.
|
||||
- `max_sessions` (Number) MaxSessions defines the maximum number of concurrent sessions per connection.
|
||||
- `permit_x11_forwarding` (Boolean) PermitX11Forwarding, when true, authorizes use of X11 forwarding over SSH sessions. If not set, X11 forwarding is not permitted.
|
||||
@@ -142,6 +162,13 @@ Optional:
|
||||
- `values` (List of String) The values associated with the label.
|
||||
|
||||
|
||||
### Nested Schema for `spec.ssh.lock`
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.
|
||||
|
||||
|
||||
### Nested Schema for `spec.ssh.port_forwarding`
|
||||
|
||||
Optional:
|
||||
@@ -168,5 +195,5 @@ Optional:
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Mode sets the session recording mode. Allowed values: strict | best_effort.
|
||||
- `mode` (String) Mode sets the session recording mode. Allowed values: strict or best_effort.
|
||||
|
||||
|
||||
+29
-2
@@ -92,13 +92,22 @@ Optional:
|
||||
Optional:
|
||||
|
||||
- `client_idle_timeout` (String) ClientIdleTimeout sets the default idle timeout for access sessions across all protocols that do not specify their own value. Must be a valid Go duration string (e.g. "30m", "1h").
|
||||
- `disconnect_expired_cert` (Boolean) DisconnectExpiredCert defines the default behavior of all protocols when certs expire for a session. If unset, cluster wide defaults are used.
|
||||
- `lock` (Attributes) Lock specifies the default locking mode for access sessions across all protocols that do not specify their own value. If unset, cluster wide defaults are used. (see [below for nested schema](#nested-schema-for-specdefaultslock))
|
||||
- `session_recording` (Attributes) SessionRecording configures the session recording strategy for all protocols that don't explicitly set their session recording mode. (see [below for nested schema](#nested-schema-for-specdefaultssession_recording))
|
||||
|
||||
### Nested Schema for `spec.defaults.lock`
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.
|
||||
|
||||
|
||||
### Nested Schema for `spec.defaults.session_recording`
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Mode sets the session recording mode. Allowed values: strict | best_effort.
|
||||
- `mode` (String) Mode sets the session recording mode. Allowed values: strict or best_effort.
|
||||
|
||||
|
||||
|
||||
@@ -107,8 +116,10 @@ Optional:
|
||||
Optional:
|
||||
|
||||
- `client_idle_timeout` (String) Overrides the defaults block idle timeout specifically for kube sessions. Must be a valid Go duration string (e.g. "30m", "1h"). If empty, the defaults block value (or global default) applies.
|
||||
- `disconnect_expired_cert` (Boolean) DisconnectExpiredCert controls whether Kube sessions are disconnected when the user certificate expires.
|
||||
- `groups` (List of String) The list of kubernetes groups this role allows.
|
||||
- `labels` (Attributes List) The map of kubernetes cluster labels used for RBAC. (see [below for nested schema](#nested-schema-for-speckubelabels))
|
||||
- `lock` (Attributes) Lock configures the role's locking behavior for kubernetes sessions. (see [below for nested schema](#nested-schema-for-speckubelock))
|
||||
- `users` (List of String) An optional list of impersonatable kubernetes users this role allows.
|
||||
|
||||
### Nested Schema for `spec.kube.labels`
|
||||
@@ -119,6 +130,13 @@ Optional:
|
||||
- `values` (List of String) The values associated with the label.
|
||||
|
||||
|
||||
### Nested Schema for `spec.kube.lock`
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.
|
||||
|
||||
|
||||
|
||||
### Nested Schema for `spec.rules`
|
||||
|
||||
@@ -133,12 +151,14 @@ Optional:
|
||||
Optional:
|
||||
|
||||
- `client_idle_timeout` (String) ClientIdleTimeout overrides the defaults block idle timeout specifically for SSH sessions. Must be a valid Go duration string (e.g. "30m", "1h"). If empty, the defaults block value (or global default) applies.
|
||||
- `disconnect_expired_cert` (Boolean) DisconnectExpiredCert controls whether SSH sessions are disconnected when the user certificate expires. Defaults to value cluster wide auth preference if not set.
|
||||
- `enhanced_recording` (Attributes) EnhancedRecording is the set of BPF events to record for enhanced session recording. (see [below for nested schema](#nested-schema-for-specsshenhanced_recording))
|
||||
- `file_copy` (Boolean) FileCopy indicates whether remote file operations via SCP or SFTP are allowed over an SSH session. It defaults to allowing the user to download and upload files by default.
|
||||
- `forward_agent` (Boolean) ForwardAgent enables SSH agent forwarding.
|
||||
- `host_sudoers` (List of String) Sudoers is a list of entries to include in a users sudoer file
|
||||
- `host_user_creation` (Attributes) HostUserCreation configures the creation of host users. (see [below for nested schema](#nested-schema-for-specsshhost_user_creation))
|
||||
- `labels` (Attributes List) Labels is the set of node labels used to dynamically select which nodes this role applies to. (see [below for nested schema](#nested-schema-for-specsshlabels))
|
||||
- `lock` (Attributes) Lock configures the role's locking behavior for SSH sessions. (see [below for nested schema](#nested-schema-for-specsshlock))
|
||||
- `logins` (List of String) Logins is the list of OS logins this role permits on matching nodes.
|
||||
- `max_sessions` (Number) MaxSessions defines the maximum number of concurrent sessions per connection.
|
||||
- `permit_x11_forwarding` (Boolean) PermitX11Forwarding, when true, authorizes use of X11 forwarding over SSH sessions. If not set, X11 forwarding is not permitted.
|
||||
@@ -171,6 +191,13 @@ Optional:
|
||||
- `values` (List of String) The values associated with the label.
|
||||
|
||||
|
||||
### Nested Schema for `spec.ssh.lock`
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.
|
||||
|
||||
|
||||
### Nested Schema for `spec.ssh.port_forwarding`
|
||||
|
||||
Optional:
|
||||
@@ -197,4 +224,4 @@ Optional:
|
||||
|
||||
Optional:
|
||||
|
||||
- `mode` (String) Mode sets the session recording mode. Allowed values: strict | best_effort.
|
||||
- `mode` (String) Mode sets the session recording mode. Allowed values: strict or best_effort.
|
||||
|
||||
+47
-2
@@ -56,6 +56,22 @@ spec:
|
||||
access sessions across all protocols that do not specify their
|
||||
own value. Must be a valid Go duration string (e.g. "30m", "1h").
|
||||
type: string
|
||||
disconnect_expired_cert:
|
||||
description: DisconnectExpiredCert defines the default behavior
|
||||
of all protocols when certs expire for a session. If unset,
|
||||
cluster wide defaults are used.
|
||||
type: boolean
|
||||
lock:
|
||||
description: Lock specifies the default locking mode for access
|
||||
sessions across all protocols that do not specify their own
|
||||
value. If unset, cluster wide defaults are used.
|
||||
nullable: true
|
||||
properties:
|
||||
mode:
|
||||
description: 'Allowed values: strict or best_effort. Defaults
|
||||
to value cluster wide auth preference if not set.'
|
||||
type: string
|
||||
type: object
|
||||
session_recording:
|
||||
description: SessionRecording configures the session recording
|
||||
strategy for all protocols that don't explicitly set their session
|
||||
@@ -64,7 +80,7 @@ spec:
|
||||
properties:
|
||||
mode:
|
||||
description: 'Mode sets the session recording mode. Allowed
|
||||
values: strict | best_effort.'
|
||||
values: strict or best_effort.'
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
@@ -78,6 +94,10 @@ spec:
|
||||
"30m", "1h"). If empty, the defaults block value (or global
|
||||
default) applies.
|
||||
type: string
|
||||
disconnect_expired_cert:
|
||||
description: DisconnectExpiredCert controls whether Kube sessions
|
||||
are disconnected when the user certificate expires.
|
||||
type: boolean
|
||||
groups:
|
||||
description: The list of kubernetes groups this role allows.
|
||||
items:
|
||||
@@ -100,6 +120,16 @@ spec:
|
||||
type: object
|
||||
nullable: true
|
||||
type: array
|
||||
lock:
|
||||
description: Lock configures the role's locking behavior for kubernetes
|
||||
sessions.
|
||||
nullable: true
|
||||
properties:
|
||||
mode:
|
||||
description: 'Allowed values: strict or best_effort. Defaults
|
||||
to value cluster wide auth preference if not set.'
|
||||
type: string
|
||||
type: object
|
||||
users:
|
||||
description: An optional list of impersonatable kubernetes users
|
||||
this role allows.
|
||||
@@ -140,6 +170,11 @@ spec:
|
||||
string (e.g. "30m", "1h"). If empty, the defaults block value
|
||||
(or global default) applies.
|
||||
type: string
|
||||
disconnect_expired_cert:
|
||||
description: DisconnectExpiredCert controls whether SSH sessions
|
||||
are disconnected when the user certificate expires. Defaults
|
||||
to value cluster wide auth preference if not set.
|
||||
type: boolean
|
||||
enhanced_recording:
|
||||
description: EnhancedRecording is the set of BPF events to record
|
||||
for enhanced session recording.
|
||||
@@ -206,6 +241,16 @@ spec:
|
||||
type: object
|
||||
nullable: true
|
||||
type: array
|
||||
lock:
|
||||
description: Lock configures the role's locking behavior for SSH
|
||||
sessions.
|
||||
nullable: true
|
||||
properties:
|
||||
mode:
|
||||
description: 'Allowed values: strict or best_effort. Defaults
|
||||
to value cluster wide auth preference if not set.'
|
||||
type: string
|
||||
type: object
|
||||
logins:
|
||||
description: Logins is the list of OS logins this role permits
|
||||
on matching nodes.
|
||||
@@ -250,7 +295,7 @@ spec:
|
||||
properties:
|
||||
mode:
|
||||
description: 'Mode sets the session recording mode. Allowed
|
||||
values: strict | best_effort.'
|
||||
values: strict or best_effort.'
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
|
||||
@@ -56,6 +56,22 @@ spec:
|
||||
access sessions across all protocols that do not specify their
|
||||
own value. Must be a valid Go duration string (e.g. "30m", "1h").
|
||||
type: string
|
||||
disconnect_expired_cert:
|
||||
description: DisconnectExpiredCert defines the default behavior
|
||||
of all protocols when certs expire for a session. If unset,
|
||||
cluster wide defaults are used.
|
||||
type: boolean
|
||||
lock:
|
||||
description: Lock specifies the default locking mode for access
|
||||
sessions across all protocols that do not specify their own
|
||||
value. If unset, cluster wide defaults are used.
|
||||
nullable: true
|
||||
properties:
|
||||
mode:
|
||||
description: 'Allowed values: strict or best_effort. Defaults
|
||||
to value cluster wide auth preference if not set.'
|
||||
type: string
|
||||
type: object
|
||||
session_recording:
|
||||
description: SessionRecording configures the session recording
|
||||
strategy for all protocols that don't explicitly set their session
|
||||
@@ -64,7 +80,7 @@ spec:
|
||||
properties:
|
||||
mode:
|
||||
description: 'Mode sets the session recording mode. Allowed
|
||||
values: strict | best_effort.'
|
||||
values: strict or best_effort.'
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
@@ -78,6 +94,10 @@ spec:
|
||||
"30m", "1h"). If empty, the defaults block value (or global
|
||||
default) applies.
|
||||
type: string
|
||||
disconnect_expired_cert:
|
||||
description: DisconnectExpiredCert controls whether Kube sessions
|
||||
are disconnected when the user certificate expires.
|
||||
type: boolean
|
||||
groups:
|
||||
description: The list of kubernetes groups this role allows.
|
||||
items:
|
||||
@@ -100,6 +120,16 @@ spec:
|
||||
type: object
|
||||
nullable: true
|
||||
type: array
|
||||
lock:
|
||||
description: Lock configures the role's locking behavior for kubernetes
|
||||
sessions.
|
||||
nullable: true
|
||||
properties:
|
||||
mode:
|
||||
description: 'Allowed values: strict or best_effort. Defaults
|
||||
to value cluster wide auth preference if not set.'
|
||||
type: string
|
||||
type: object
|
||||
users:
|
||||
description: An optional list of impersonatable kubernetes users
|
||||
this role allows.
|
||||
@@ -140,6 +170,11 @@ spec:
|
||||
string (e.g. "30m", "1h"). If empty, the defaults block value
|
||||
(or global default) applies.
|
||||
type: string
|
||||
disconnect_expired_cert:
|
||||
description: DisconnectExpiredCert controls whether SSH sessions
|
||||
are disconnected when the user certificate expires. Defaults
|
||||
to value cluster wide auth preference if not set.
|
||||
type: boolean
|
||||
enhanced_recording:
|
||||
description: EnhancedRecording is the set of BPF events to record
|
||||
for enhanced session recording.
|
||||
@@ -206,6 +241,16 @@ spec:
|
||||
type: object
|
||||
nullable: true
|
||||
type: array
|
||||
lock:
|
||||
description: Lock configures the role's locking behavior for SSH
|
||||
sessions.
|
||||
nullable: true
|
||||
properties:
|
||||
mode:
|
||||
description: 'Allowed values: strict or best_effort. Defaults
|
||||
to value cluster wide auth preference if not set.'
|
||||
type: string
|
||||
type: object
|
||||
logins:
|
||||
description: Logins is the list of OS logins this role permits
|
||||
on matching nodes.
|
||||
@@ -250,7 +295,7 @@ spec:
|
||||
properties:
|
||||
mode:
|
||||
description: 'Mode sets the session recording mode. Allowed
|
||||
values: strict | best_effort.'
|
||||
values: strict or best_effort.'
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
|
||||
@@ -121,9 +121,23 @@ func GenSchemaScopedRole(ctx context.Context) (github_com_hashicorp_terraform_pl
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
},
|
||||
"disconnect_expired_cert": {
|
||||
Description: "DisconnectExpiredCert defines the default behavior of all protocols when certs expire for a session. If unset, cluster wide defaults are used.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.BoolType,
|
||||
},
|
||||
"lock": {
|
||||
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"mode": {
|
||||
Description: "Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
}}),
|
||||
Description: "Lock specifies the default locking mode for access sessions across all protocols that do not specify their own value. If unset, cluster wide defaults are used.",
|
||||
Optional: true,
|
||||
},
|
||||
"session_recording": {
|
||||
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"mode": {
|
||||
Description: "Mode sets the session recording mode. Allowed values: strict | best_effort.",
|
||||
Description: "Mode sets the session recording mode. Allowed values: strict or best_effort.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
}}),
|
||||
@@ -141,6 +155,11 @@ func GenSchemaScopedRole(ctx context.Context) (github_com_hashicorp_terraform_pl
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
},
|
||||
"disconnect_expired_cert": {
|
||||
Description: "DisconnectExpiredCert controls whether Kube sessions are disconnected when the user certificate expires.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.BoolType,
|
||||
},
|
||||
"groups": {
|
||||
Description: "The list of kubernetes groups this role allows.",
|
||||
Optional: true,
|
||||
@@ -162,6 +181,15 @@ func GenSchemaScopedRole(ctx context.Context) (github_com_hashicorp_terraform_pl
|
||||
Description: "The map of kubernetes cluster labels used for RBAC.",
|
||||
Optional: true,
|
||||
},
|
||||
"lock": {
|
||||
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"mode": {
|
||||
Description: "Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
}}),
|
||||
Description: "Lock configures the role's locking behavior for kubernetes sessions.",
|
||||
Optional: true,
|
||||
},
|
||||
"users": {
|
||||
Description: "An optional list of impersonatable kubernetes users this role allows.",
|
||||
Optional: true,
|
||||
@@ -194,6 +222,11 @@ func GenSchemaScopedRole(ctx context.Context) (github_com_hashicorp_terraform_pl
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
},
|
||||
"disconnect_expired_cert": {
|
||||
Description: "DisconnectExpiredCert controls whether SSH sessions are disconnected when the user certificate expires. Defaults to value cluster wide auth preference if not set.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.BoolType,
|
||||
},
|
||||
"enhanced_recording": {
|
||||
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{
|
||||
"command": {
|
||||
@@ -267,6 +300,15 @@ func GenSchemaScopedRole(ctx context.Context) (github_com_hashicorp_terraform_pl
|
||||
Description: "Labels is the set of node labels used to dynamically select which nodes this role applies to.",
|
||||
Optional: true,
|
||||
},
|
||||
"lock": {
|
||||
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"mode": {
|
||||
Description: "Allowed values: strict or best_effort. Defaults to value cluster wide auth preference if not set.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
}}),
|
||||
Description: "Lock configures the role's locking behavior for SSH sessions.",
|
||||
Optional: true,
|
||||
},
|
||||
"logins": {
|
||||
Description: "Logins is the list of OS logins this role permits on matching nodes.",
|
||||
Optional: true,
|
||||
@@ -308,7 +350,7 @@ func GenSchemaScopedRole(ctx context.Context) (github_com_hashicorp_terraform_pl
|
||||
},
|
||||
"session_recording": {
|
||||
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"mode": {
|
||||
Description: "Mode sets the session recording mode. Allowed values: strict | best_effort.",
|
||||
Description: "Mode sets the session recording mode. Allowed values: strict or best_effort.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
}}),
|
||||
@@ -634,6 +676,59 @@ func CopyScopedRoleFromTerraform(_ context.Context, tf github_com_hashicorp_terr
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
a, ok := tf.Attrs["disconnect_expired_cert"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"ScopedRole.spec.defaults.disconnect_expired_cert"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Bool)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"ScopedRole.spec.defaults.disconnect_expired_cert", "github.com/hashicorp/terraform-plugin-framework/types.Bool"})
|
||||
} else {
|
||||
var t *bool
|
||||
if !v.Null && !v.Unknown {
|
||||
c := bool(v.Value)
|
||||
t = &c
|
||||
}
|
||||
obj.DisconnectExpiredCert = t
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
a, ok := tf.Attrs["lock"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"ScopedRole.spec.defaults.lock"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"ScopedRole.spec.defaults.lock", "github.com/hashicorp/terraform-plugin-framework/types.Object"})
|
||||
} else {
|
||||
obj.Lock = nil
|
||||
if !v.Null && !v.Unknown {
|
||||
tf := v
|
||||
obj.Lock = &github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1.Lock{}
|
||||
obj := obj.Lock
|
||||
{
|
||||
a, ok := tf.Attrs["mode"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"ScopedRole.spec.defaults.lock.mode"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"ScopedRole.spec.defaults.lock.mode", "github.com/hashicorp/terraform-plugin-framework/types.String"})
|
||||
} else {
|
||||
var t string
|
||||
if !v.Null && !v.Unknown {
|
||||
t = string(v.Value)
|
||||
}
|
||||
obj.Mode = t
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1227,6 +1322,59 @@ func CopyScopedRoleFromTerraform(_ context.Context, tf github_com_hashicorp_terr
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
a, ok := tf.Attrs["disconnect_expired_cert"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"ScopedRole.spec.ssh.disconnect_expired_cert"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Bool)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"ScopedRole.spec.ssh.disconnect_expired_cert", "github.com/hashicorp/terraform-plugin-framework/types.Bool"})
|
||||
} else {
|
||||
var t *bool
|
||||
if !v.Null && !v.Unknown {
|
||||
c := bool(v.Value)
|
||||
t = &c
|
||||
}
|
||||
obj.DisconnectExpiredCert = t
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
a, ok := tf.Attrs["lock"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"ScopedRole.spec.ssh.lock"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"ScopedRole.spec.ssh.lock", "github.com/hashicorp/terraform-plugin-framework/types.Object"})
|
||||
} else {
|
||||
obj.Lock = nil
|
||||
if !v.Null && !v.Unknown {
|
||||
tf := v
|
||||
obj.Lock = &github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1.Lock{}
|
||||
obj := obj.Lock
|
||||
{
|
||||
a, ok := tf.Attrs["mode"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"ScopedRole.spec.ssh.lock.mode"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"ScopedRole.spec.ssh.lock.mode", "github.com/hashicorp/terraform-plugin-framework/types.String"})
|
||||
} else {
|
||||
var t string
|
||||
if !v.Null && !v.Unknown {
|
||||
t = string(v.Value)
|
||||
}
|
||||
obj.Mode = t
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1389,6 +1537,59 @@ func CopyScopedRoleFromTerraform(_ context.Context, tf github_com_hashicorp_terr
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
a, ok := tf.Attrs["disconnect_expired_cert"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"ScopedRole.spec.kube.disconnect_expired_cert"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Bool)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"ScopedRole.spec.kube.disconnect_expired_cert", "github.com/hashicorp/terraform-plugin-framework/types.Bool"})
|
||||
} else {
|
||||
var t *bool
|
||||
if !v.Null && !v.Unknown {
|
||||
c := bool(v.Value)
|
||||
t = &c
|
||||
}
|
||||
obj.DisconnectExpiredCert = t
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
a, ok := tf.Attrs["lock"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"ScopedRole.spec.kube.lock"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"ScopedRole.spec.kube.lock", "github.com/hashicorp/terraform-plugin-framework/types.Object"})
|
||||
} else {
|
||||
obj.Lock = nil
|
||||
if !v.Null && !v.Unknown {
|
||||
tf := v
|
||||
obj.Lock = &github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1.Lock{}
|
||||
obj := obj.Lock
|
||||
{
|
||||
a, ok := tf.Attrs["mode"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"ScopedRole.spec.kube.lock.mode"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"ScopedRole.spec.kube.lock.mode", "github.com/hashicorp/terraform-plugin-framework/types.String"})
|
||||
} else {
|
||||
var t string
|
||||
if !v.Null && !v.Unknown {
|
||||
t = string(v.Value)
|
||||
}
|
||||
obj.Mode = t
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1856,6 +2057,86 @@ func CopyScopedRoleToTerraform(ctx context.Context, obj *github_com_gravitationa
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
t, ok := tf.AttrTypes["disconnect_expired_cert"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"ScopedRole.spec.defaults.disconnect_expired_cert"})
|
||||
} else {
|
||||
v, ok := tf.Attrs["disconnect_expired_cert"].(github_com_hashicorp_terraform_plugin_framework_types.Bool)
|
||||
if !ok {
|
||||
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
|
||||
if err != nil {
|
||||
diags.Append(attrWriteGeneralError{"ScopedRole.spec.defaults.disconnect_expired_cert", err})
|
||||
}
|
||||
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.Bool)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"ScopedRole.spec.defaults.disconnect_expired_cert", "github.com/hashicorp/terraform-plugin-framework/types.Bool"})
|
||||
}
|
||||
}
|
||||
if obj.DisconnectExpiredCert == nil {
|
||||
v.Null = true
|
||||
} else {
|
||||
v.Null = false
|
||||
v.Value = bool(*obj.DisconnectExpiredCert)
|
||||
}
|
||||
v.Unknown = false
|
||||
tf.Attrs["disconnect_expired_cert"] = v
|
||||
}
|
||||
}
|
||||
{
|
||||
a, ok := tf.AttrTypes["lock"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"ScopedRole.spec.defaults.lock"})
|
||||
} else {
|
||||
o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"ScopedRole.spec.defaults.lock", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"})
|
||||
} else {
|
||||
v, ok := tf.Attrs["lock"].(github_com_hashicorp_terraform_plugin_framework_types.Object)
|
||||
if !ok {
|
||||
v = github_com_hashicorp_terraform_plugin_framework_types.Object{
|
||||
|
||||
AttrTypes: o.AttrTypes,
|
||||
Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)),
|
||||
}
|
||||
} else {
|
||||
if v.Attrs == nil {
|
||||
v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes))
|
||||
}
|
||||
}
|
||||
if obj.Lock == nil {
|
||||
v.Null = true
|
||||
} else {
|
||||
obj := obj.Lock
|
||||
tf := &v
|
||||
{
|
||||
t, ok := tf.AttrTypes["mode"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"ScopedRole.spec.defaults.lock.mode"})
|
||||
} else {
|
||||
v, ok := tf.Attrs["mode"].(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
|
||||
if err != nil {
|
||||
diags.Append(attrWriteGeneralError{"ScopedRole.spec.defaults.lock.mode", err})
|
||||
}
|
||||
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"ScopedRole.spec.defaults.lock.mode", "github.com/hashicorp/terraform-plugin-framework/types.String"})
|
||||
}
|
||||
v.Null = string(obj.Mode) == ""
|
||||
}
|
||||
v.Value = string(obj.Mode)
|
||||
v.Unknown = false
|
||||
tf.Attrs["mode"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
v.Unknown = false
|
||||
tf.Attrs["lock"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
v.Unknown = false
|
||||
tf.Attrs["defaults"] = v
|
||||
@@ -2858,6 +3139,86 @@ func CopyScopedRoleToTerraform(ctx context.Context, obj *github_com_gravitationa
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
t, ok := tf.AttrTypes["disconnect_expired_cert"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"ScopedRole.spec.ssh.disconnect_expired_cert"})
|
||||
} else {
|
||||
v, ok := tf.Attrs["disconnect_expired_cert"].(github_com_hashicorp_terraform_plugin_framework_types.Bool)
|
||||
if !ok {
|
||||
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
|
||||
if err != nil {
|
||||
diags.Append(attrWriteGeneralError{"ScopedRole.spec.ssh.disconnect_expired_cert", err})
|
||||
}
|
||||
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.Bool)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"ScopedRole.spec.ssh.disconnect_expired_cert", "github.com/hashicorp/terraform-plugin-framework/types.Bool"})
|
||||
}
|
||||
}
|
||||
if obj.DisconnectExpiredCert == nil {
|
||||
v.Null = true
|
||||
} else {
|
||||
v.Null = false
|
||||
v.Value = bool(*obj.DisconnectExpiredCert)
|
||||
}
|
||||
v.Unknown = false
|
||||
tf.Attrs["disconnect_expired_cert"] = v
|
||||
}
|
||||
}
|
||||
{
|
||||
a, ok := tf.AttrTypes["lock"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"ScopedRole.spec.ssh.lock"})
|
||||
} else {
|
||||
o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"ScopedRole.spec.ssh.lock", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"})
|
||||
} else {
|
||||
v, ok := tf.Attrs["lock"].(github_com_hashicorp_terraform_plugin_framework_types.Object)
|
||||
if !ok {
|
||||
v = github_com_hashicorp_terraform_plugin_framework_types.Object{
|
||||
|
||||
AttrTypes: o.AttrTypes,
|
||||
Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)),
|
||||
}
|
||||
} else {
|
||||
if v.Attrs == nil {
|
||||
v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes))
|
||||
}
|
||||
}
|
||||
if obj.Lock == nil {
|
||||
v.Null = true
|
||||
} else {
|
||||
obj := obj.Lock
|
||||
tf := &v
|
||||
{
|
||||
t, ok := tf.AttrTypes["mode"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"ScopedRole.spec.ssh.lock.mode"})
|
||||
} else {
|
||||
v, ok := tf.Attrs["mode"].(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
|
||||
if err != nil {
|
||||
diags.Append(attrWriteGeneralError{"ScopedRole.spec.ssh.lock.mode", err})
|
||||
}
|
||||
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"ScopedRole.spec.ssh.lock.mode", "github.com/hashicorp/terraform-plugin-framework/types.String"})
|
||||
}
|
||||
v.Null = string(obj.Mode) == ""
|
||||
}
|
||||
v.Value = string(obj.Mode)
|
||||
v.Unknown = false
|
||||
tf.Attrs["mode"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
v.Unknown = false
|
||||
tf.Attrs["lock"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
v.Unknown = false
|
||||
tf.Attrs["ssh"] = v
|
||||
@@ -3151,6 +3512,86 @@ func CopyScopedRoleToTerraform(ctx context.Context, obj *github_com_gravitationa
|
||||
tf.Attrs["client_idle_timeout"] = v
|
||||
}
|
||||
}
|
||||
{
|
||||
t, ok := tf.AttrTypes["disconnect_expired_cert"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"ScopedRole.spec.kube.disconnect_expired_cert"})
|
||||
} else {
|
||||
v, ok := tf.Attrs["disconnect_expired_cert"].(github_com_hashicorp_terraform_plugin_framework_types.Bool)
|
||||
if !ok {
|
||||
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
|
||||
if err != nil {
|
||||
diags.Append(attrWriteGeneralError{"ScopedRole.spec.kube.disconnect_expired_cert", err})
|
||||
}
|
||||
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.Bool)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"ScopedRole.spec.kube.disconnect_expired_cert", "github.com/hashicorp/terraform-plugin-framework/types.Bool"})
|
||||
}
|
||||
}
|
||||
if obj.DisconnectExpiredCert == nil {
|
||||
v.Null = true
|
||||
} else {
|
||||
v.Null = false
|
||||
v.Value = bool(*obj.DisconnectExpiredCert)
|
||||
}
|
||||
v.Unknown = false
|
||||
tf.Attrs["disconnect_expired_cert"] = v
|
||||
}
|
||||
}
|
||||
{
|
||||
a, ok := tf.AttrTypes["lock"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"ScopedRole.spec.kube.lock"})
|
||||
} else {
|
||||
o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"ScopedRole.spec.kube.lock", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"})
|
||||
} else {
|
||||
v, ok := tf.Attrs["lock"].(github_com_hashicorp_terraform_plugin_framework_types.Object)
|
||||
if !ok {
|
||||
v = github_com_hashicorp_terraform_plugin_framework_types.Object{
|
||||
|
||||
AttrTypes: o.AttrTypes,
|
||||
Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)),
|
||||
}
|
||||
} else {
|
||||
if v.Attrs == nil {
|
||||
v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes))
|
||||
}
|
||||
}
|
||||
if obj.Lock == nil {
|
||||
v.Null = true
|
||||
} else {
|
||||
obj := obj.Lock
|
||||
tf := &v
|
||||
{
|
||||
t, ok := tf.AttrTypes["mode"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"ScopedRole.spec.kube.lock.mode"})
|
||||
} else {
|
||||
v, ok := tf.Attrs["mode"].(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
|
||||
if err != nil {
|
||||
diags.Append(attrWriteGeneralError{"ScopedRole.spec.kube.lock.mode", err})
|
||||
}
|
||||
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"ScopedRole.spec.kube.lock.mode", "github.com/hashicorp/terraform-plugin-framework/types.String"})
|
||||
}
|
||||
v.Null = string(obj.Mode) == ""
|
||||
}
|
||||
v.Value = string(obj.Mode)
|
||||
v.Unknown = false
|
||||
tf.Attrs["mode"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
v.Unknown = false
|
||||
tf.Attrs["lock"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
v.Unknown = false
|
||||
tf.Attrs["kube"] = v
|
||||
|
||||
+5
-3
@@ -184,19 +184,21 @@ func (s *ScopedContext) GetDisconnectCertExpiry(authPref readonly.AuthPreference
|
||||
if s.unscopedContext != nil {
|
||||
return s.unscopedContext.GetDisconnectCertExpiry(authPref)
|
||||
}
|
||||
|
||||
if !authPref.GetDisconnectExpiredCert() {
|
||||
return time.Time{}
|
||||
}
|
||||
return s.GetDisconnectCertExpiryTime()
|
||||
}
|
||||
|
||||
// GetDisconnectCertExpiryTime returns the timestamp for when the identity expires.
|
||||
// It takes in a bool parameter to specify whether to return the expiry time or not.
|
||||
func (s *ScopedContext) GetDisconnectCertExpiryTime() time.Time {
|
||||
identity := s.Identity.GetIdentity()
|
||||
if !identity.PreviousIdentityExpires.IsZero() {
|
||||
// If this is a short-lived mfa verified cert, return the certificate extension
|
||||
// that holds its issuing certificates expiry value.
|
||||
return identity.PreviousIdentityExpires
|
||||
}
|
||||
|
||||
// Otherwise, return the current certificates expiration
|
||||
return identity.Expires
|
||||
}
|
||||
|
||||
|
||||
@@ -211,6 +211,9 @@ func (f *ForwarderConfig) CheckAndSetDefaults() error {
|
||||
if f.ScopedAuthz == nil {
|
||||
return trace.BadParameter("missing parameter ScopedAuthz")
|
||||
}
|
||||
if f.LockWatcher == nil {
|
||||
return trace.BadParameter("missing parameter LockWatcher")
|
||||
}
|
||||
if f.Emitter == nil {
|
||||
return trace.BadParameter("missing parameter Emitter")
|
||||
}
|
||||
@@ -470,6 +473,9 @@ type authContext struct {
|
||||
// It is false if the target cluster is served by another teleport service or a different
|
||||
// Teleport cluster.
|
||||
isLocalKubernetesCluster bool
|
||||
|
||||
// LockingMode determines the kubernetes' behavior when locks are stale
|
||||
LockingMode constants.LockingMode
|
||||
}
|
||||
|
||||
func (c authContext) String() string {
|
||||
@@ -1274,6 +1280,27 @@ func (f *Forwarder) authorize(ctx context.Context, actx *authContext) error {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
authPref, err := f.cfg.CachingAuthClient.GetAuthPreference(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if actx.checker.Kube().AdjustDisconnectExpiredCert(authPref.GetDisconnectExpiredCert()) {
|
||||
actx.disconnectExpiredCert = actx.ScopedContext.GetDisconnectCertExpiryTime()
|
||||
} else {
|
||||
actx.disconnectExpiredCert = time.Time{}
|
||||
}
|
||||
|
||||
// For scoped roles, check for the locking mode here so that we can verify whether users can connect or not when not
|
||||
// when the lock is stale.
|
||||
if isScoped {
|
||||
actx.LockingMode = actx.checker.Kube().LockingMode(authPref.GetLockingMode())
|
||||
// TODO(williamo/scopes): Potentially delete this in favor of checking locks in lib/authz/scoped.go
|
||||
if err := f.cfg.LockWatcher.CheckLockInForce(actx.LockingMode, actx.LockTargets()...); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
// If the user has active Access requests we need to validate that they allow
|
||||
// the kubeResource.
|
||||
// This is required because CheckAccess does not validate the subresource type.
|
||||
@@ -2615,6 +2642,7 @@ func (s *clusterSession) monitorConn(conn net.Conn, err error, hostID string) (n
|
||||
Emitter: s.parent.cfg.AuthClient,
|
||||
EmitterContext: s.parent.ctx,
|
||||
MessageWriter: formatForwardResponseError(s.sendErrStatus),
|
||||
LockingMode: s.LockingMode,
|
||||
})
|
||||
if err != nil {
|
||||
tc.CloseWithCause(err)
|
||||
|
||||
@@ -57,6 +57,11 @@ import (
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/api/client/proto"
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
|
||||
labelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/label/v1"
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
apievents "github.com/gravitational/teleport/api/types/events"
|
||||
"github.com/gravitational/teleport/api/utils/tlsutils"
|
||||
@@ -69,6 +74,7 @@ import (
|
||||
testingkubemock "github.com/gravitational/teleport/lib/kube/proxy/testing/kube_server"
|
||||
"github.com/gravitational/teleport/lib/modules"
|
||||
"github.com/gravitational/teleport/lib/reversetunnelclient"
|
||||
"github.com/gravitational/teleport/lib/scopes/pinning"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/services/local"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
@@ -121,6 +127,27 @@ func TestAuthenticate(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
newTestLockWatcher := func(t *testing.T) *services.LockWatcher {
|
||||
lockWatcher, err := services.NewLockWatcher(t.Context(), services.LockWatcherConfig{
|
||||
ResourceWatcherConfig: services.ResourceWatcherConfig{
|
||||
Component: teleport.ComponentProxy,
|
||||
Client: &mockEventClient{},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(lockWatcher.Close)
|
||||
return lockWatcher
|
||||
}
|
||||
lockWatcher := newTestLockWatcher(t)
|
||||
|
||||
// marks the lock watcher as stale
|
||||
configureStaleLockWatcher := func(t *testing.T, f *Forwarder) {
|
||||
staleLockWatcher := newTestLockWatcher(t)
|
||||
close(staleLockWatcher.StaleC)
|
||||
require.Eventually(t, staleLockWatcher.IsStale, time.Second, 10*time.Millisecond)
|
||||
f.cfg.LockWatcher = staleLockWatcher
|
||||
}
|
||||
|
||||
ap := &mockAccessPoint{
|
||||
netConfig: nc,
|
||||
recordingConfig: types.DefaultSessionRecordingConfig(),
|
||||
@@ -141,49 +168,58 @@ func TestAuthenticate(t *testing.T) {
|
||||
"local": mockCluster{name: "local"},
|
||||
},
|
||||
}
|
||||
f := &Forwarder{
|
||||
log: logtest.NewLogger(),
|
||||
cfg: ForwarderConfig{
|
||||
ClusterName: "local",
|
||||
CachingAuthClient: ap,
|
||||
TracerProvider: otel.GetTracerProvider(),
|
||||
tracer: otel.Tracer(teleport.ComponentKube),
|
||||
ClusterFeatures: fakeClusterFeatures,
|
||||
KubeServiceType: ProxyService,
|
||||
},
|
||||
getKubernetesServersForKubeCluster: func(ctx context.Context, name string) ([]types.KubeServer, error) {
|
||||
servers, err := ap.GetKubernetesServers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var filtered []types.KubeServer
|
||||
for _, server := range servers {
|
||||
if server.GetCluster().GetName() == name {
|
||||
filtered = append(filtered, server)
|
||||
newForwarder := func() *Forwarder {
|
||||
return &Forwarder{
|
||||
log: logtest.NewLogger(),
|
||||
cfg: ForwarderConfig{
|
||||
ClusterName: "local",
|
||||
CachingAuthClient: ap,
|
||||
TracerProvider: otel.GetTracerProvider(),
|
||||
tracer: otel.Tracer(teleport.ComponentKube),
|
||||
ClusterFeatures: fakeClusterFeatures,
|
||||
KubeServiceType: ProxyService,
|
||||
LockWatcher: lockWatcher,
|
||||
},
|
||||
getKubernetesServersForKubeCluster: func(ctx context.Context, name string) ([]types.KubeServer, error) {
|
||||
servers, err := ap.GetKubernetesServers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
},
|
||||
var filtered []types.KubeServer
|
||||
for _, server := range servers {
|
||||
if server.GetCluster().GetName() == name {
|
||||
filtered = append(filtered, server)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const remoteAddr = "user.example.com"
|
||||
activeAccessRequests := []string{uuid.NewString(), uuid.NewString()}
|
||||
tests := []struct {
|
||||
desc string
|
||||
user authz.IdentityGetter
|
||||
authzErr bool
|
||||
roleKubeUsers []string
|
||||
roleKubeGroups []string
|
||||
routeToCluster string
|
||||
kubernetesCluster string
|
||||
haveKubeCreds bool
|
||||
tunnel reversetunnelclient.Server
|
||||
kubeServers []types.KubeServer
|
||||
activeRequests []string
|
||||
desc string
|
||||
user authz.IdentityGetter
|
||||
authzErr bool
|
||||
roleKubeUsers []string
|
||||
roleKubeGroups []string
|
||||
routeToCluster string
|
||||
kubernetesCluster string
|
||||
haveKubeCreds bool
|
||||
tunnel reversetunnelclient.Server
|
||||
kubeServers []types.KubeServer
|
||||
activeRequests []string
|
||||
configureForwarder func(t *testing.T, f *Forwarder)
|
||||
scoped bool
|
||||
scopedKubeLockMode constants.LockingMode
|
||||
scopedKubeDisconnectExpiredCert *bool
|
||||
|
||||
wantCtx *authContext
|
||||
wantErr bool
|
||||
wantAuthErr bool
|
||||
wantCtx *authContext
|
||||
wantErr bool
|
||||
wantAuthErr bool
|
||||
wantAuthorizeErrContains string
|
||||
wantDisconnectExpiredCert *time.Time
|
||||
}{
|
||||
{
|
||||
desc: "local user and cluster with active access request",
|
||||
@@ -316,6 +352,227 @@ func TestAuthenticate(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "scoped user with stale locks and best effort locking produces no error",
|
||||
user: authz.LocalUser{},
|
||||
scoped: true,
|
||||
roleKubeGroups: []string{"kube-group-a", "kube-group-b"},
|
||||
scopedKubeLockMode: constants.LockingModeBestEffort,
|
||||
routeToCluster: "local",
|
||||
kubernetesCluster: "local",
|
||||
haveKubeCreds: true,
|
||||
tunnel: tun,
|
||||
configureForwarder: configureStaleLockWatcher,
|
||||
kubeServers: newKubeServersFromKubeClusters(
|
||||
t,
|
||||
&types.KubernetesClusterV3{
|
||||
Metadata: types.Metadata{
|
||||
Name: "local",
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Scope: scopedTestScope,
|
||||
Spec: types.KubernetesClusterSpecV3{
|
||||
DynamicLabels: map[string]types.CommandLabelV2{},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantCtx: &authContext{
|
||||
kubeUsers: set.New("user-a"),
|
||||
kubeGroups: set.New("kube-group-a", "kube-group-b", teleport.KubeSystemAuthenticated),
|
||||
kubeClusterName: "local",
|
||||
kubeClusterLabels: make(map[string]string),
|
||||
certExpires: certExpiration,
|
||||
teleportCluster: teleportClusterClient{
|
||||
name: "local",
|
||||
remoteAddr: *utils.MustParseAddr(remoteAddr),
|
||||
},
|
||||
kubeServers: newKubeServersFromKubeClusters(
|
||||
t,
|
||||
&types.KubernetesClusterV3{
|
||||
Metadata: types.Metadata{
|
||||
Name: "local",
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Scope: scopedTestScope,
|
||||
Spec: types.KubernetesClusterSpecV3{
|
||||
DynamicLabels: map[string]types.CommandLabelV2{},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "scoped user with stale locks and strict locking produces error",
|
||||
user: authz.LocalUser{},
|
||||
scoped: true,
|
||||
roleKubeGroups: []string{"kube-group-a", "kube-group-b"},
|
||||
scopedKubeLockMode: constants.LockingModeStrict,
|
||||
routeToCluster: "local",
|
||||
kubernetesCluster: "local",
|
||||
haveKubeCreds: true,
|
||||
tunnel: tun,
|
||||
configureForwarder: configureStaleLockWatcher,
|
||||
wantAuthorizeErrContains: services.StrictLockingModeAccessDenied.Error(),
|
||||
kubeServers: newKubeServersFromKubeClusters(
|
||||
t,
|
||||
&types.KubernetesClusterV3{
|
||||
Metadata: types.Metadata{
|
||||
Name: "local",
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Scope: scopedTestScope,
|
||||
Spec: types.KubernetesClusterSpecV3{
|
||||
DynamicLabels: map[string]types.CommandLabelV2{},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
desc: "scoped user inherits disconnect on cert expiry from auth pref",
|
||||
user: authz.LocalUser{},
|
||||
scoped: true,
|
||||
roleKubeGroups: []string{"kube-group-a", "kube-group-b"},
|
||||
routeToCluster: "local",
|
||||
kubernetesCluster: "local",
|
||||
haveKubeCreds: true,
|
||||
tunnel: tun,
|
||||
wantDisconnectExpiredCert: &certExpiration,
|
||||
kubeServers: newKubeServersFromKubeClusters(
|
||||
t,
|
||||
&types.KubernetesClusterV3{
|
||||
Metadata: types.Metadata{
|
||||
Name: "local",
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Scope: scopedTestScope,
|
||||
Spec: types.KubernetesClusterSpecV3{
|
||||
DynamicLabels: map[string]types.CommandLabelV2{},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantCtx: &authContext{
|
||||
kubeUsers: set.New("user-a"),
|
||||
kubeGroups: set.New("kube-group-a", "kube-group-b", teleport.KubeSystemAuthenticated),
|
||||
kubeClusterName: "local",
|
||||
kubeClusterLabels: make(map[string]string),
|
||||
certExpires: certExpiration,
|
||||
teleportCluster: teleportClusterClient{
|
||||
name: "local",
|
||||
remoteAddr: *utils.MustParseAddr(remoteAddr),
|
||||
},
|
||||
kubeServers: newKubeServersFromKubeClusters(
|
||||
t,
|
||||
&types.KubernetesClusterV3{
|
||||
Metadata: types.Metadata{
|
||||
Name: "local",
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Scope: scopedTestScope,
|
||||
Spec: types.KubernetesClusterSpecV3{
|
||||
DynamicLabels: map[string]types.CommandLabelV2{},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "scoped user disables on cert expiry",
|
||||
user: authz.LocalUser{},
|
||||
scoped: true,
|
||||
roleKubeGroups: []string{"kube-group-a", "kube-group-b"},
|
||||
routeToCluster: "local",
|
||||
kubernetesCluster: "local",
|
||||
scopedKubeDisconnectExpiredCert: ptr(false),
|
||||
wantDisconnectExpiredCert: &time.Time{},
|
||||
haveKubeCreds: true,
|
||||
tunnel: tun,
|
||||
kubeServers: newKubeServersFromKubeClusters(
|
||||
t,
|
||||
&types.KubernetesClusterV3{
|
||||
Metadata: types.Metadata{
|
||||
Name: "local",
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Scope: scopedTestScope,
|
||||
Spec: types.KubernetesClusterSpecV3{
|
||||
DynamicLabels: map[string]types.CommandLabelV2{},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantCtx: &authContext{
|
||||
kubeUsers: set.New("user-a"),
|
||||
kubeGroups: set.New("kube-group-a", "kube-group-b", teleport.KubeSystemAuthenticated),
|
||||
kubeClusterName: "local",
|
||||
kubeClusterLabels: make(map[string]string),
|
||||
certExpires: certExpiration,
|
||||
teleportCluster: teleportClusterClient{
|
||||
name: "local",
|
||||
remoteAddr: *utils.MustParseAddr(remoteAddr),
|
||||
},
|
||||
kubeServers: newKubeServersFromKubeClusters(
|
||||
t,
|
||||
&types.KubernetesClusterV3{
|
||||
Metadata: types.Metadata{
|
||||
Name: "local",
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Scope: scopedTestScope,
|
||||
Spec: types.KubernetesClusterSpecV3{
|
||||
DynamicLabels: map[string]types.CommandLabelV2{},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "scoped user sets disconnectExpiredCert to true",
|
||||
user: authz.LocalUser{},
|
||||
scoped: true,
|
||||
roleKubeGroups: []string{"kube-group-a", "kube-group-b"},
|
||||
scopedKubeDisconnectExpiredCert: ptr(true),
|
||||
wantDisconnectExpiredCert: &certExpiration,
|
||||
routeToCluster: "local",
|
||||
kubernetesCluster: "local",
|
||||
haveKubeCreds: true,
|
||||
tunnel: tun,
|
||||
kubeServers: newKubeServersFromKubeClusters(
|
||||
t,
|
||||
&types.KubernetesClusterV3{
|
||||
Metadata: types.Metadata{
|
||||
Name: "local",
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Scope: scopedTestScope,
|
||||
Spec: types.KubernetesClusterSpecV3{
|
||||
DynamicLabels: map[string]types.CommandLabelV2{},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantCtx: &authContext{
|
||||
kubeUsers: set.New("user-a"),
|
||||
kubeGroups: set.New("kube-group-a", "kube-group-b", teleport.KubeSystemAuthenticated),
|
||||
kubeClusterName: "local",
|
||||
kubeClusterLabels: make(map[string]string),
|
||||
certExpires: certExpiration,
|
||||
teleportCluster: teleportClusterClient{
|
||||
name: "local",
|
||||
remoteAddr: *utils.MustParseAddr(remoteAddr),
|
||||
},
|
||||
kubeServers: newKubeServersFromKubeClusters(
|
||||
t,
|
||||
&types.KubernetesClusterV3{
|
||||
Metadata: types.Metadata{
|
||||
Name: "local",
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Scope: scopedTestScope,
|
||||
Spec: types.KubernetesClusterSpecV3{
|
||||
DynamicLabels: map[string]types.CommandLabelV2{},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "local user and cluster, no kubeconfig",
|
||||
user: authz.LocalUser{},
|
||||
@@ -707,6 +964,10 @@ func TestAuthenticate(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
f := newForwarder()
|
||||
if tt.configureForwarder != nil {
|
||||
tt.configureForwarder(t, f)
|
||||
}
|
||||
f.cfg.ReverseTunnelSrv = tt.tunnel
|
||||
ap.kubeServers = tt.kubeServers
|
||||
roles, err := services.RoleSetFromSpec("ops", types.RoleSpecV6{
|
||||
@@ -723,13 +984,36 @@ func TestAuthenticate(t *testing.T) {
|
||||
Roles: roles.RoleNames(),
|
||||
}, "local", roles),
|
||||
Identity: authz.WrapIdentity(tlsca.Identity{
|
||||
Username: username,
|
||||
Groups: roles.RoleNames(),
|
||||
RouteToCluster: tt.routeToCluster,
|
||||
KubernetesCluster: tt.kubernetesCluster,
|
||||
ActiveRequests: tt.activeRequests,
|
||||
Expires: certExpiration,
|
||||
}),
|
||||
UnmappedIdentity: authz.WrapIdentity(tlsca.Identity{
|
||||
Username: username,
|
||||
Groups: roles.RoleNames(),
|
||||
RouteToCluster: tt.routeToCluster,
|
||||
KubernetesCluster: tt.kubernetesCluster,
|
||||
ActiveRequests: tt.activeRequests,
|
||||
Expires: certExpiration,
|
||||
}),
|
||||
}
|
||||
authorizer := mockAuthorizer{scopedCtx: authz.ScopedContextFromUnscopedContext(&authCtx)}
|
||||
var authorizer mockAuthorizer
|
||||
if tt.scoped {
|
||||
authorizer = newScopedKubeAuthorizer(t, scopedKubeAuthorizerConfig{
|
||||
user: user,
|
||||
identity: authCtx.Identity,
|
||||
kubeUsers: tt.roleKubeUsers,
|
||||
kubeGroups: tt.roleKubeGroups,
|
||||
kubeLockMode: tt.scopedKubeLockMode,
|
||||
kubeDisconnectExpired: tt.scopedKubeDisconnectExpiredCert,
|
||||
clusterName: "local",
|
||||
})
|
||||
} else {
|
||||
authorizer = mockAuthorizer{scopedCtx: authz.ScopedContextFromUnscopedContext(&authCtx)}
|
||||
}
|
||||
if tt.authzErr {
|
||||
authorizer.err = trace.AccessDenied("denied!")
|
||||
}
|
||||
@@ -767,13 +1051,22 @@ func TestAuthenticate(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, err)
|
||||
err = f.authorize(context.Background(), gotCtx)
|
||||
if tt.wantAuthorizeErrContains != "" {
|
||||
require.ErrorContains(t, err, tt.wantAuthorizeErrContains)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Empty(t, cmp.Diff(gotCtx, tt.wantCtx,
|
||||
cmp.AllowUnexported(authContext{}, teleportClusterClient{}, metaResource{}, apiResource{}),
|
||||
cmpopts.IgnoreFields(authContext{}, "clientIdleTimeout", "sessionTTL", "ScopedContext", "recordingConfig", "disconnectExpiredCert", "kubeCluster", "checker", "accessState"),
|
||||
cmpopts.IgnoreFields(authContext{}, "clientIdleTimeout", "sessionTTL", "ScopedContext", "recordingConfig", "disconnectExpiredCert", "kubeCluster", "checker", "accessState", "LockingMode"),
|
||||
))
|
||||
|
||||
if tt.wantDisconnectExpiredCert != nil {
|
||||
require.Equal(t, *tt.wantDisconnectExpiredCert, gotCtx.disconnectExpiredCert)
|
||||
}
|
||||
|
||||
// validate authCtx.key() to make sure it includes certExpires timestamp.
|
||||
// this is important to make sure user's credentials are correctly cached
|
||||
// and once user's re-login, Teleport won't reuse their previous cache entry.
|
||||
@@ -1313,6 +1606,89 @@ func (a mockAuthorizer) AuthorizeScoped(context.Context) (*authz.ScopedContext,
|
||||
return a.scopedCtx, a.err
|
||||
}
|
||||
|
||||
const scopedTestRole = "scoped-kube-admin"
|
||||
const scopedTestScope = "/staging"
|
||||
|
||||
type scopedKubeAuthorizerConfig struct {
|
||||
user types.User
|
||||
identity authz.IdentityGetter
|
||||
kubeUsers []string
|
||||
kubeGroups []string
|
||||
kubeLockMode constants.LockingMode
|
||||
kubeDisconnectExpired *bool
|
||||
clusterName string
|
||||
}
|
||||
|
||||
func newScopedKubeAuthorizer(t *testing.T, cfg scopedKubeAuthorizerConfig) mockAuthorizer {
|
||||
t.Helper()
|
||||
|
||||
var lock *scopedaccessv1.Lock
|
||||
if cfg.kubeLockMode != "" {
|
||||
lock = &scopedaccessv1.Lock{Mode: string(cfg.kubeLockMode)}
|
||||
}
|
||||
|
||||
role := &scopedaccessv1.ScopedRole{
|
||||
Kind: "scoped_role",
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: scopedTestRole,
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{scopedTestScope},
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
Labels: []*labelv1.Label{
|
||||
{Name: types.Wildcard, Values: []string{types.Wildcard}},
|
||||
},
|
||||
Groups: cfg.kubeGroups,
|
||||
Users: cfg.kubeUsers,
|
||||
Lock: lock,
|
||||
DisconnectExpiredCert: cfg.kubeDisconnectExpired,
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
}
|
||||
|
||||
reader := &fakeScopedRoleReader{
|
||||
role: role,
|
||||
}
|
||||
|
||||
pin := &scopesv1.Pin{Scope: scopedTestScope}
|
||||
pin.AssignmentTree = pinning.AssignmentTreeFromMap(map[string]map[string][]string{
|
||||
"/": {
|
||||
scopedTestScope: {scopedTestRole},
|
||||
},
|
||||
})
|
||||
|
||||
checkerCtx, err := services.NewScopedAccessCheckerContext(t.Context(), &services.AccessInfo{
|
||||
Username: cfg.user.GetName(),
|
||||
ScopePin: pin,
|
||||
}, cfg.clusterName, reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
return mockAuthorizer{
|
||||
scopedCtx: &authz.ScopedContext{
|
||||
User: cfg.user,
|
||||
Identity: cfg.identity,
|
||||
CheckerContext: checkerCtx,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
type fakeScopedRoleReader struct {
|
||||
role *scopedaccessv1.ScopedRole
|
||||
}
|
||||
|
||||
func (r *fakeScopedRoleReader) GetScopedRole(_ context.Context, req *scopedaccessv1.GetScopedRoleRequest) (*scopedaccessv1.GetScopedRoleResponse, error) {
|
||||
|
||||
return &scopedaccessv1.GetScopedRoleResponse{Role: r.role}, nil
|
||||
}
|
||||
|
||||
func (r *fakeScopedRoleReader) ListScopedRoles(context.Context, *scopedaccessv1.ListScopedRolesRequest) (*scopedaccessv1.ListScopedRolesResponse, error) {
|
||||
return &scopedaccessv1.ListScopedRolesResponse{Roles: []*scopedaccessv1.ScopedRole{r.role}}, nil
|
||||
}
|
||||
|
||||
type mockEventClient struct {
|
||||
services.Presence
|
||||
types.Events
|
||||
|
||||
@@ -274,6 +274,27 @@ func StrongValidateRole(role *scopedaccessv1.ScopedRole) error {
|
||||
}
|
||||
}
|
||||
|
||||
// verify that the lock.Mode is a recognized value for Defaults
|
||||
if lock := role.GetSpec().GetDefaults().GetLock(); lock != nil {
|
||||
if err := validateLock(lock); err != nil {
|
||||
return trace.BadParameter("scoped role %q has invalid defaults.lock.mode %q", role.GetMetadata().GetName(), lock.GetMode())
|
||||
}
|
||||
}
|
||||
|
||||
// verify that lock.Mode is a recognized value for SSH
|
||||
if lock := role.GetSpec().GetSsh().GetLock(); lock != nil {
|
||||
if err := validateLock(lock); err != nil {
|
||||
return trace.BadParameter("scoped role %q has invalid ssh.lock.mode %q", role.GetMetadata().GetName(), lock.GetMode())
|
||||
}
|
||||
}
|
||||
|
||||
// verify that lock.Mode is a recognized value for Kube
|
||||
if lock := role.GetSpec().GetKube().GetLock(); lock != nil {
|
||||
if err := validateLock(lock); err != nil {
|
||||
return trace.BadParameter("scoped role %q has invalid kube.lock.mode %q", role.GetMetadata().GetName(), lock.GetMode())
|
||||
}
|
||||
}
|
||||
|
||||
// verify that kube labels are well-formed
|
||||
for _, label := range role.GetSpec().GetKube().GetLabels() {
|
||||
// we currently don't support any form of wildcard/regex/substitution in scoped role
|
||||
@@ -324,6 +345,19 @@ func validateDoesNotContain(values []string, invalidSet string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func validateLock(lock *scopedaccessv1.Lock) error {
|
||||
mode := lock.GetMode()
|
||||
switch constants.LockingMode(mode) {
|
||||
// Allow for empty string - we will fall back to the cluster defaults - or best_effort if not set.
|
||||
// This matches current behavior for unscoped lock mode checks.
|
||||
case "":
|
||||
case constants.LockingModeBestEffort, constants.LockingModeStrict:
|
||||
default:
|
||||
return trace.Errorf("invalid lock mode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRoleName(name string) error {
|
||||
// note: having the scope name be validated as a segment name is a bit of an arbitrary choice, but its basically
|
||||
// equivalent to what we would want from a standalone name requirement, and there may even be some future benefit
|
||||
|
||||
@@ -409,6 +409,24 @@ func TestValidateRole(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid defaults.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Defaults: &scopedaccessv1.ScopedRoleDefaults{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: false,
|
||||
@@ -447,6 +465,24 @@ func TestValidateRole(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid ssh.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: false,
|
||||
@@ -466,6 +502,172 @@ func TestValidateRole(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid ssh.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: string(constants.LockingModeStrict),
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: true,
|
||||
weakOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "empty ssh.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: true,
|
||||
weakOk: true,
|
||||
},
|
||||
{
|
||||
name: "invalid kube.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: false,
|
||||
weakOk: true,
|
||||
},
|
||||
{
|
||||
name: "valid Kube.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: string(constants.LockingModeStrict),
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: true,
|
||||
weakOk: true,
|
||||
},
|
||||
{
|
||||
name: "empty Kube.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: true,
|
||||
weakOk: true,
|
||||
},
|
||||
{
|
||||
name: "invalid defaults.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Defaults: &scopedaccessv1.ScopedRoleDefaults{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: false,
|
||||
weakOk: true,
|
||||
},
|
||||
{
|
||||
name: "valid defaults.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Defaults: &scopedaccessv1.ScopedRoleDefaults{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: string(constants.LockingModeStrict),
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: true,
|
||||
weakOk: true,
|
||||
},
|
||||
{
|
||||
name: "empty defaults.lock.mode",
|
||||
role: &scopedaccessv1.ScopedRole{
|
||||
Kind: KindScopedRole,
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
Scope: "/",
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo"},
|
||||
Defaults: &scopedaccessv1.ScopedRoleDefaults{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Version: types.V1,
|
||||
},
|
||||
strongOk: true,
|
||||
@@ -1296,6 +1498,10 @@ func TestStrongValidateRoleSpecAllFieldsValidated(t *testing.T) {
|
||||
SessionRecording: &scopedaccessv1.SessionRecording{
|
||||
Mode: string(constants.SessionRecordingModeStrict),
|
||||
},
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "strict",
|
||||
},
|
||||
DisconnectExpiredCert: ptr(true),
|
||||
},
|
||||
Rules: []*scopedaccessv1.ScopedRule{
|
||||
{
|
||||
@@ -1331,6 +1537,10 @@ func TestStrongValidateRoleSpecAllFieldsValidated(t *testing.T) {
|
||||
SessionRecording: &scopedaccessv1.SessionRecording{
|
||||
Mode: string(constants.SessionRecordingModeStrict),
|
||||
},
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: string(constants.LockingModeBestEffort),
|
||||
},
|
||||
DisconnectExpiredCert: proto.Bool(true),
|
||||
},
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
Groups: []string{"viewer"},
|
||||
@@ -1338,7 +1548,11 @@ func TestStrongValidateRoleSpecAllFieldsValidated(t *testing.T) {
|
||||
Labels: []*labelv1.Label{
|
||||
{Name: "env", Values: []string{"prod"}},
|
||||
},
|
||||
ClientIdleTimeout: "1h",
|
||||
ClientIdleTimeout: "1h",
|
||||
DisconnectExpiredCert: ptr(true),
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "strict",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -231,6 +231,7 @@ func baseScopedRole() *scopedaccessv1.ScopedRole {
|
||||
Spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
AssignableScopes: []string{"/foo/bar"},
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{},
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{},
|
||||
},
|
||||
Version: types.V1,
|
||||
}
|
||||
@@ -335,13 +336,36 @@ func TestEnhancedRecordingNotInClassicRole(t *testing.T) {
|
||||
Network: ptr(true),
|
||||
Disk: ptr(true),
|
||||
}
|
||||
|
||||
role, err := ScopedRoleToRole(sr, "/foo/bar")
|
||||
require.NoError(t, err)
|
||||
// BPF is the classic role equivalent of enhanced_recording.
|
||||
require.Equal(t, apidefaults.EnhancedEvents(), role.GetOptions().BPF)
|
||||
}
|
||||
|
||||
func TestDisconnectExpiredCertNotInClassicRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sr := baseScopedRole()
|
||||
sr.Spec.Ssh.DisconnectExpiredCert = ptr(true)
|
||||
|
||||
role, err := ScopedRoleToRole(sr, "/foo/bar")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, types.NewBool(false), role.GetOptions().DisconnectExpiredCert)
|
||||
}
|
||||
|
||||
func TestKubeDisconnectExpiredCertNotInClassicRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sr := baseScopedRole()
|
||||
sr.Spec.Kube = &scopedaccessv1.ScopedRoleKube{
|
||||
DisconnectExpiredCert: ptr(true),
|
||||
}
|
||||
|
||||
role, err := ScopedRoleToRole(sr, "/foo/bar")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, types.NewBool(false), role.GetOptions().DisconnectExpiredCert)
|
||||
}
|
||||
|
||||
func TestSessionRecordingNotInClassicRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -352,6 +376,7 @@ func TestSessionRecordingNotInClassicRole(t *testing.T) {
|
||||
|
||||
role, err := ScopedRoleToRole(sr, "/foo/bar")
|
||||
require.NoError(t, err)
|
||||
|
||||
// RecordSession is the classic role equivalent of session_recording_mode.
|
||||
require.Equal(t, &types.RecordSession{
|
||||
Desktop: types.NewBoolOption(true),
|
||||
@@ -359,6 +384,33 @@ func TestSessionRecordingNotInClassicRole(t *testing.T) {
|
||||
}, role.GetOptions().RecordSession)
|
||||
}
|
||||
|
||||
func TestSSHLockingModeNotInClassicRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sr := baseScopedRole()
|
||||
sr.Spec.Ssh.Lock = &scopedaccessv1.Lock{
|
||||
Mode: "strict",
|
||||
}
|
||||
|
||||
role, err := ScopedRoleToRole(sr, "/foo/bar")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Empty(t, string(role.GetOptions().Lock))
|
||||
}
|
||||
|
||||
func TestKubeLockingModeNotInClassicRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sr := baseScopedRole()
|
||||
sr.Spec.Kube.Lock = &scopedaccessv1.Lock{
|
||||
Mode: "strict",
|
||||
}
|
||||
|
||||
role, err := ScopedRoleToRole(sr, "/foo/bar")
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, string(role.GetOptions().Lock))
|
||||
}
|
||||
|
||||
// TestKubeConversion verifies the various kube-related scoped role conversion scenarios.
|
||||
func TestKubeConversion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2026 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
)
|
||||
|
||||
// adjust the client idle timeout value - preferring the default if not set
|
||||
func (c *ScopedAccessChecker) adjustScopedClientIdleTimeout(idleStr string, timeout time.Duration) (time.Duration, error) {
|
||||
if idleStr == "" {
|
||||
idleStr = c.role.GetSpec().GetDefaults().GetClientIdleTimeout()
|
||||
}
|
||||
if idleStr != "" {
|
||||
d, err := time.ParseDuration(idleStr)
|
||||
if err != nil {
|
||||
return 0, trace.Errorf("invalid client_idle_timeout %q in scoped role %q: %w", idleStr, c.role.GetMetadata().GetName(), err)
|
||||
}
|
||||
if d > 0 && (timeout == 0 || d < timeout) {
|
||||
return max(d, 0), nil
|
||||
}
|
||||
}
|
||||
return max(timeout, 0), nil
|
||||
}
|
||||
|
||||
// adjustScopedDisconnectExpiredCert returns the disconnect on Expired Cert condition - - applying the default if applicable.
|
||||
func (c *ScopedAccessChecker) adjustScopedDisconnectExpiredCert(roleSpecifiedDisconnect *bool, defaultdisconnect bool) bool {
|
||||
if roleSpecifiedDisconnect == nil && c.role.GetSpec().GetDefaults() != nil {
|
||||
roleSpecifiedDisconnect = c.role.GetSpec().GetDefaults().DisconnectExpiredCert
|
||||
}
|
||||
|
||||
if roleSpecifiedDisconnect != nil {
|
||||
return *roleSpecifiedDisconnect
|
||||
}
|
||||
return defaultdisconnect
|
||||
}
|
||||
|
||||
// LockingMode returns the lock enforcement mode to apply - applying the default if applicable.
|
||||
func (c *ScopedAccessChecker) scopedLockingMode(lock *accessv1.Lock, defaultMode constants.LockingMode) constants.LockingMode {
|
||||
if lock == nil {
|
||||
lock = c.role.GetSpec().GetDefaults().GetLock()
|
||||
}
|
||||
// both protocol specific lock and the default locks are nil, so return the defaultMode.
|
||||
if lock == nil {
|
||||
return defaultMode
|
||||
}
|
||||
|
||||
mode := constants.LockingMode(lock.GetMode())
|
||||
switch mode {
|
||||
case constants.LockingModeStrict, constants.LockingModeBestEffort:
|
||||
return mode
|
||||
default:
|
||||
return defaultMode
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,7 @@ package services
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
)
|
||||
|
||||
@@ -78,19 +77,27 @@ func (c *KubeAccessChecker) AdjustClientIdleTimeout(timeout time.Duration) (time
|
||||
if !c.checker.isScoped() {
|
||||
return c.checker.unscopedChecker.AdjustClientIdleTimeout(timeout), nil
|
||||
}
|
||||
// Kube block takes precedence over defaults block.
|
||||
idleStr := c.checker.role.GetSpec().GetKube().GetClientIdleTimeout()
|
||||
if idleStr == "" {
|
||||
idleStr = c.checker.role.GetSpec().GetDefaults().GetClientIdleTimeout()
|
||||
return c.checker.adjustScopedClientIdleTimeout(c.checker.role.GetSpec().GetKube().GetClientIdleTimeout(), timeout)
|
||||
}
|
||||
|
||||
// AdjustDisconnectExpiredCert adjusts whether to disconnect on certificate expiry.
|
||||
func (c *KubeAccessChecker) AdjustDisconnectExpiredCert(disconnect bool) bool {
|
||||
if !c.checker.isScoped() {
|
||||
return c.checker.unscopedChecker.AdjustDisconnectExpiredCert(disconnect)
|
||||
}
|
||||
if idleStr != "" {
|
||||
d, err := time.ParseDuration(idleStr)
|
||||
if err != nil {
|
||||
return 0, trace.Errorf("invalid client_idle_timeout %q in scoped role %q: %w", idleStr, c.checker.role.GetMetadata().GetName(), err)
|
||||
}
|
||||
if d > 0 && (timeout == 0 || d < timeout) {
|
||||
return max(d, 0), nil
|
||||
}
|
||||
kube := c.checker.role.GetSpec().GetKube()
|
||||
var disconnectExpiredCert *bool
|
||||
if kube != nil {
|
||||
disconnectExpiredCert = kube.DisconnectExpiredCert
|
||||
}
|
||||
return max(timeout, 0), nil
|
||||
return c.checker.adjustScopedDisconnectExpiredCert(disconnectExpiredCert, disconnect)
|
||||
}
|
||||
|
||||
// LockingMode returns the SSH lock enforcement mode to apply.
|
||||
func (c *KubeAccessChecker) LockingMode(defaultMode constants.LockingMode) constants.LockingMode {
|
||||
if !c.checker.isScoped() {
|
||||
return c.checker.unscopedChecker.LockingMode(defaultMode)
|
||||
}
|
||||
|
||||
return c.checker.scopedLockingMode(c.checker.role.GetSpec().GetKube().GetLock(), defaultMode)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2026 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
)
|
||||
|
||||
func TestKubeAccessCheckerAdjustDisconnectExpiredCert(t *testing.T) {
|
||||
|
||||
tts := []struct {
|
||||
name string
|
||||
spec *scopedaccessv1.ScopedRoleSpec
|
||||
defaultVal bool
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
name: "unset defers to default false",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{},
|
||||
},
|
||||
defaultVal: false,
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "unset kube and default defers to default true",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{},
|
||||
},
|
||||
defaultVal: true,
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "explicit true overrides default false",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
DisconnectExpiredCert: ptr(true),
|
||||
},
|
||||
},
|
||||
defaultVal: false,
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "explicit false overrides default true",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
DisconnectExpiredCert: ptr(false),
|
||||
},
|
||||
},
|
||||
defaultVal: true,
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "default block is applied when kube block is empty",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Defaults: &scopedaccessv1.ScopedRoleDefaults{
|
||||
DisconnectExpiredCert: ptr(false),
|
||||
},
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{},
|
||||
},
|
||||
defaultVal: true,
|
||||
expect: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tts {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
checker := newScopedCheckerWithRole(tt.spec).Kube()
|
||||
require.Equal(t, tt.expect, checker.AdjustDisconnectExpiredCert(tt.defaultVal))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubeAccessCheckerLockingMode(t *testing.T) {
|
||||
|
||||
tts := []struct {
|
||||
name string
|
||||
spec *scopedaccessv1.ScopedRoleSpec
|
||||
defaultMode constants.LockingMode
|
||||
expect constants.LockingMode
|
||||
}{
|
||||
{
|
||||
name: "unset kube and default blocks defers to default mode",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{},
|
||||
},
|
||||
defaultMode: constants.LockingModeBestEffort,
|
||||
expect: constants.LockingModeBestEffort,
|
||||
},
|
||||
{
|
||||
name: "strict from role",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: string(constants.LockingModeStrict),
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultMode: constants.LockingModeBestEffort,
|
||||
expect: constants.LockingModeStrict,
|
||||
},
|
||||
{
|
||||
name: "best effort from role",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: string(constants.LockingModeBestEffort),
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultMode: constants.LockingModeStrict,
|
||||
expect: constants.LockingModeBestEffort,
|
||||
},
|
||||
{
|
||||
name: "invalid value falls back to default",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultMode: constants.LockingModeStrict,
|
||||
expect: constants.LockingModeStrict,
|
||||
},
|
||||
{
|
||||
name: "empty mode falls back to default block",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Defaults: &scopedaccessv1.ScopedRoleDefaults{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: string(constants.LockingModeStrict),
|
||||
},
|
||||
},
|
||||
Kube: &scopedaccessv1.ScopedRoleKube{
|
||||
Lock: nil,
|
||||
},
|
||||
},
|
||||
defaultMode: constants.LockingModeBestEffort,
|
||||
expect: constants.LockingModeStrict,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tts {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
checker := newScopedCheckerWithRole(tt.spec).Kube()
|
||||
require.Equal(t, tt.expect, checker.LockingMode(tt.defaultMode))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -62,21 +62,8 @@ func (c *SSHAccessChecker) AdjustClientIdleTimeout(timeout time.Duration) (time.
|
||||
if !c.checker.isScoped() {
|
||||
return c.checker.unscopedChecker.AdjustClientIdleTimeout(timeout), nil
|
||||
}
|
||||
// SSH block takes precedence over defaults block.
|
||||
idleStr := c.checker.role.GetSpec().GetSsh().GetClientIdleTimeout()
|
||||
if idleStr == "" {
|
||||
idleStr = c.checker.role.GetSpec().GetDefaults().GetClientIdleTimeout()
|
||||
}
|
||||
if idleStr != "" {
|
||||
d, err := time.ParseDuration(idleStr)
|
||||
if err != nil {
|
||||
return 0, trace.Errorf("invalid client_idle_timeout %q in scoped role %q: %w", idleStr, c.checker.role.GetMetadata().GetName(), err)
|
||||
}
|
||||
if d > 0 && (timeout == 0 || d < timeout) {
|
||||
return max(d, 0), nil
|
||||
}
|
||||
}
|
||||
return max(timeout, 0), nil
|
||||
|
||||
return c.checker.adjustScopedClientIdleTimeout(c.checker.role.GetSpec().GetSsh().GetClientIdleTimeout(), timeout)
|
||||
}
|
||||
|
||||
// AdjustDisconnectExpiredCert adjusts whether to disconnect on certificate expiry.
|
||||
@@ -84,7 +71,21 @@ func (c *SSHAccessChecker) AdjustDisconnectExpiredCert(disconnect bool) bool {
|
||||
if !c.checker.isScoped() {
|
||||
return c.checker.unscopedChecker.AdjustDisconnectExpiredCert(disconnect)
|
||||
}
|
||||
return c.checker.scopedCompatChecker.AdjustDisconnectExpiredCert(disconnect)
|
||||
ssh := c.checker.role.GetSpec().GetSsh()
|
||||
var disconnectExpiredCert *bool
|
||||
if ssh != nil {
|
||||
disconnectExpiredCert = ssh.DisconnectExpiredCert
|
||||
}
|
||||
return c.checker.adjustScopedDisconnectExpiredCert(disconnectExpiredCert, disconnect)
|
||||
}
|
||||
|
||||
// LockingMode returns the SSH lock enforcement mode to apply.
|
||||
func (c *SSHAccessChecker) LockingMode(defaultMode constants.LockingMode) constants.LockingMode {
|
||||
if !c.checker.isScoped() {
|
||||
return c.checker.unscopedChecker.LockingMode(defaultMode)
|
||||
}
|
||||
|
||||
return c.checker.scopedLockingMode(c.checker.role.GetSpec().GetSsh().GetLock(), defaultMode)
|
||||
}
|
||||
|
||||
// SessionRecordingMode returns the session recording mode for SSH sessions.
|
||||
|
||||
@@ -194,6 +194,149 @@ func TestSSHAccessCheckerAdjustClientIdleTimeout(t *testing.T) {
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func TestSSHAccessCheckerAdjustDisconnectExpiredCert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tts := []struct {
|
||||
name string
|
||||
spec *scopedaccessv1.ScopedRoleSpec
|
||||
defaultVal bool
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
name: "unset defers to default false",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{},
|
||||
},
|
||||
defaultVal: false,
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "unset defers to default true",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{},
|
||||
},
|
||||
defaultVal: true,
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "explicit true overrides default false",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{
|
||||
DisconnectExpiredCert: ptr(true),
|
||||
},
|
||||
},
|
||||
defaultVal: false,
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "explicit false overrides default true",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{
|
||||
DisconnectExpiredCert: ptr(false),
|
||||
},
|
||||
},
|
||||
defaultVal: true,
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "unset ssh block defaults to default block",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Defaults: &scopedaccessv1.ScopedRoleDefaults{
|
||||
DisconnectExpiredCert: ptr(false),
|
||||
},
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{},
|
||||
},
|
||||
defaultVal: true,
|
||||
expect: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tts {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checker := newScopedCheckerWithRole(tt.spec).SSH()
|
||||
require.Equal(t, tt.expect, checker.AdjustDisconnectExpiredCert(tt.defaultVal))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHAccessCheckerLockingMode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tts := []struct {
|
||||
name string
|
||||
spec *scopedaccessv1.ScopedRoleSpec
|
||||
defaultMode constants.LockingMode
|
||||
expect constants.LockingMode
|
||||
}{
|
||||
{
|
||||
name: "unset defers to default",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{},
|
||||
},
|
||||
defaultMode: constants.LockingModeBestEffort,
|
||||
expect: constants.LockingModeBestEffort,
|
||||
},
|
||||
{
|
||||
name: "strict from role",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: string(constants.LockingModeStrict),
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultMode: constants.LockingModeBestEffort,
|
||||
expect: constants.LockingModeStrict,
|
||||
},
|
||||
{
|
||||
name: "best effort from role",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: string(constants.LockingModeBestEffort),
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultMode: constants.LockingModeStrict,
|
||||
expect: constants.LockingModeBestEffort,
|
||||
},
|
||||
{
|
||||
name: "invalid value falls back to default",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{
|
||||
|
||||
Lock: &scopedaccessv1.Lock{
|
||||
Mode: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultMode: constants.LockingModeStrict,
|
||||
expect: constants.LockingModeStrict,
|
||||
},
|
||||
{
|
||||
name: "empty mode falls back to default",
|
||||
spec: &scopedaccessv1.ScopedRoleSpec{
|
||||
Ssh: &scopedaccessv1.ScopedRoleSSH{
|
||||
|
||||
Lock: &scopedaccessv1.Lock{},
|
||||
},
|
||||
},
|
||||
defaultMode: constants.LockingModeStrict,
|
||||
expect: constants.LockingModeStrict,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tts {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checker := newScopedCheckerWithRole(tt.spec).SSH()
|
||||
require.Equal(t, tt.expect, checker.LockingMode(tt.defaultMode))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHAccessCheckerPermitX11Forwarding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1182,7 +1182,7 @@ func (a *ahLoginChecker) evaluateScopedSSHAccess(ident *sshca.Identity, ca types
|
||||
ClientIdleTimeout: durationpb.New(clientIdleTimeout),
|
||||
DisconnectExpiredCert: timestampFromGoTime(getDisconnectExpiredCertFromSSHIdentityScoped(checker.SSH(), authPref, ident)),
|
||||
SessionRecordingMode: string(checker.SSH().SessionRecordingMode()),
|
||||
LockingMode: string(checker.LockingMode(authPref.GetLockingMode())),
|
||||
LockingMode: string(checker.SSH().LockingMode(authPref.GetLockingMode())),
|
||||
PrivateKeyPolicy: string(privateKeyPolicy),
|
||||
LockTargets: decision.LockTargetsToProto(lockTargets),
|
||||
MappedRoles: accessInfo.Roles,
|
||||
|
||||
Reference in New Issue
Block a user