diff --git a/api/gen/proto/go/teleport/userprovisioning/v2/statichostuser.pb.go b/api/gen/proto/go/teleport/userprovisioning/v2/statichostuser.pb.go index 3399141691519..7b236aefda1fa 100644 --- a/api/gen/proto/go/teleport/userprovisioning/v2/statichostuser.pb.go +++ b/api/gen/proto/go/teleport/userprovisioning/v2/statichostuser.pb.go @@ -143,6 +143,8 @@ type Matcher struct { Uid int64 `protobuf:"varint,5,opt,name=uid,proto3" json:"uid,omitempty"` // gid is the new user's gid. Gid int64 `protobuf:"varint,6,opt,name=gid,proto3" json:"gid,omitempty"` + // default_shell is the new user's default shell + DefaultShell string `protobuf:"bytes,7,opt,name=default_shell,json=defaultShell,proto3" json:"default_shell,omitempty"` } func (x *Matcher) Reset() { @@ -219,6 +221,13 @@ func (x *Matcher) GetGid() int64 { return 0 } +func (x *Matcher) GetDefaultShell() string { + if x != nil { + return x.DefaultShell + } + return "" +} + // StaticHostUserSpec is the static host user spec. type StaticHostUserSpec struct { state protoimpl.MessageState @@ -293,7 +302,7 @@ var file_teleport_userprovisioning_v2_statichostuser_proto_rawDesc = []byte{ 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, - 0xd0, 0x01, 0x0a, 0x07, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x0b, 0x6e, + 0xf5, 0x01, 0x0a, 0x07, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, @@ -306,19 +315,21 @@ var file_teleport_userprovisioning_v2_statichostuser_proto_rawDesc = []byte{ 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x64, 0x6f, 0x65, 0x72, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x67, - 0x69, 0x64, 0x22, 0x57, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, 0x12, 0x41, 0x0a, 0x08, 0x6d, 0x61, 0x74, 0x63, - 0x68, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, - 0x72, 0x52, 0x08, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x73, 0x42, 0x64, 0x5a, 0x62, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, - 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x2f, 0x76, 0x32, 0x3b, 0x75, - 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x76, - 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x73, 0x68, + 0x65, 0x6c, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x22, 0x57, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x74, 0x69, + 0x63, 0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, 0x12, 0x41, 0x0a, + 0x08, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x32, 0x2e, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x73, + 0x42, 0x64, 0x5a, 0x62, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, + 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, + 0x2f, 0x76, 0x32, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x69, 0x6e, 0x67, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/teleport/userprovisioning/v2/statichostuser.proto b/api/proto/teleport/userprovisioning/v2/statichostuser.proto index 61875fe007890..ef60d3eb7080b 100644 --- a/api/proto/teleport/userprovisioning/v2/statichostuser.proto +++ b/api/proto/teleport/userprovisioning/v2/statichostuser.proto @@ -53,6 +53,8 @@ message Matcher { int64 uid = 5; // gid is the new user's gid. int64 gid = 6; + // default_shell is the new user's default shell + string default_shell = 7; } // StaticHostUserSpec is the static host user spec. diff --git a/integration/hostuser_test.go b/integration/hostuser_test.go index 982368f9e5b2b..25da77b73ab96 100644 --- a/integration/hostuser_test.go +++ b/integration/hostuser_test.go @@ -657,6 +657,21 @@ func TestRootStaticHostUsers(t *testing.T) { }, }, }) + goodLoginWithShell := utils.GenerateLocalUsername(t) + goodUserWithShell := userprovisioning.NewStaticHostUser(goodLoginWithShell, &userprovisioningpb.StaticHostUserSpec{ + Matchers: []*userprovisioningpb.Matcher{ + { + NodeLabels: []*labelv1.Label{ + { + Name: "foo", + Values: []string{"bar"}, + }, + }, + Groups: groups, + DefaultShell: "bash", + }, + }, + }) nonMatchingLogin := utils.GenerateLocalUsername(t) nonMatchingUser := userprovisioning.NewStaticHostUser(nonMatchingLogin, &userprovisioningpb.StaticHostUserSpec{ Matchers: []*userprovisioningpb.Matcher{ @@ -691,24 +706,25 @@ func TestRootStaticHostUsers(t *testing.T) { }) clt := instance.Process.GetAuthServer() - for _, hostUser := range []*userprovisioningpb.StaticHostUser{goodUser, nonMatchingUser, conflictingUser} { + for _, hostUser := range []*userprovisioningpb.StaticHostUser{goodUser, goodUserWithShell, nonMatchingUser, conflictingUser} { _, err := clt.UpsertStaticHostUser(context.Background(), hostUser) require.NoError(t, err) } t.Cleanup(func() { cleanupUsersAndGroups([]string{goodLogin, nonMatchingLogin, conflictingLogin}, groups) }) // Test that a node picks up new host users from the cache. - testStaticHostUsers(t, nodeCfg.HostUUID, goodLogin, nonMatchingLogin, conflictingLogin, groups) + testStaticHostUsers(t, nodeCfg.HostUUID, goodLogin, goodLoginWithShell, nonMatchingLogin, conflictingLogin, groups) cleanupUsersAndGroups([]string{goodLogin, nonMatchingLogin, conflictingLogin}, groups) require.NoError(t, instance.StopNodes()) _, err = instance.StartNode(nodeCfg) require.NoError(t, err) // Test that a new node picks up existing host users on startup. - testStaticHostUsers(t, nodeCfg.HostUUID, goodLogin, nonMatchingLogin, conflictingLogin, groups) + testStaticHostUsers(t, nodeCfg.HostUUID, goodLogin, goodLoginWithShell, nonMatchingLogin, conflictingLogin, groups) // Check that a deleted resource doesn't affect the host user. require.NoError(t, clt.DeleteStaticHostUser(context.Background(), goodLogin)) + require.NoError(t, clt.DeleteStaticHostUser(context.Background(), goodLoginWithShell)) var lookupErr error var homeDirErr error var sudoerErr error @@ -722,7 +738,7 @@ func TestRootStaticHostUsers(t *testing.T) { lookupErr, homeDirErr, sudoerErr) } -func testStaticHostUsers(t *testing.T, nodeUUID, goodLogin, nonMatchingLogin, conflictingLogin string, groups []string) { +func testStaticHostUsers(t *testing.T, nodeUUID, goodLogin, goodLoginWithShell, nonMatchingLogin, conflictingLogin string, groups []string) { t.Cleanup(func() { os.Remove(sudoersPath(goodLogin, nodeUUID)) }) @@ -746,6 +762,9 @@ func testStaticHostUsers(t *testing.T, nodeUUID, goodLogin, nonMatchingLogin, co assert.Contains(collect, userGroups, types.TeleportStaticGroup) // Check that the sudoers file was created. assert.FileExists(collect, sudoersPath(goodLogin, nodeUUID)) + userShells, err := getUserShells("/etc/passwd") + assert.NoError(collect, err) + assert.Equal(collect, "/usr/bin/bash", userShells[goodLoginWithShell]) }, 10*time.Second, time.Second) // Check that the nonmatching and conflicting users were not created. diff --git a/lib/srv/statichostusers.go b/lib/srv/statichostusers.go index a5d2897e8fc3a..9dcbe0662772c 100644 --- a/lib/srv/statichostusers.go +++ b/lib/srv/statichostusers.go @@ -247,6 +247,7 @@ func (s *StaticHostUserHandler) handleNewHostUser(ctx context.Context, hostUser ui := services.HostUsersInfo{ Groups: createUser.Groups, Mode: services.HostUserModeStatic, + Shell: createUser.DefaultShell, } if createUser.Uid != 0 { ui.UID = strconv.Itoa(int(createUser.Uid))