Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to allow for host users not to be deleted #26892

Merged
merged 12 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion api/proto/teleport/legacy/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2325,6 +2325,19 @@ message RoleSpecV6 {
];
}

// CreateHostUserMode determines whether host user creation should be
// disabled or if host users should be cleaned up or kept after
// sessions end.
enum CreateHostUserMode {
HOST_USER_MODE_UNSPECIFIED = 0;
// HOST_USER_MODE_OFF disables host user creation.
HOST_USER_MODE_OFF = 1;
// HOST_USER_MODE_DROP enables host user creation and deletes users at session end.
HOST_USER_MODE_DROP = 2;
// HOST_USER_MODE_KEEP enables host user creation and leaves users behind at session end.
HOST_USER_MODE_KEEP = 3;
}

// RoleOptions is a set of role options
message RoleOptions {
// ForwardAgent is SSH agent forwarding.
Expand Down Expand Up @@ -2438,7 +2451,7 @@ message RoleOptions {
// CreateHostUser allows users to be automatically created on a host
BoolValue CreateHostUser = 20 [
(gogoproto.nullable) = true,
(gogoproto.jsontag) = "create_host_user",
(gogoproto.jsontag) = "create_host_user,omitempty",
(gogoproto.customtype) = "BoolOption"
];

Expand Down Expand Up @@ -2482,6 +2495,10 @@ message RoleOptions {
(gogoproto.jsontag) = "create_db_user",
(gogoproto.customtype) = "BoolOption"
];

// CreateHostUserMode allows users to be automatically created on a
// host when not set to off
CreateHostUserMode CreateHostUserMode = 28 [(gogoproto.jsontag) = "create_host_user_mode,omitempty"];
}

message RecordSession {
Expand Down
99 changes: 96 additions & 3 deletions api/types/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -938,9 +938,6 @@ func (r *RoleV6) CheckAndSetDefaults() error {
if r.Spec.Options.DesktopDirectorySharing == nil {
r.Spec.Options.DesktopDirectorySharing = NewBoolOption(true)
}
if r.Spec.Options.CreateHostUser == nil {
r.Spec.Options.CreateHostUser = NewBoolOption(false)
}
if r.Spec.Options.CreateDesktopUser == nil {
r.Spec.Options.CreateDesktopUser = NewBoolOption(false)
}
Expand All @@ -959,6 +956,10 @@ func (r *RoleV6) CheckAndSetDefaults() error {
}
}

if _, ok := CreateHostUserMode_name[int32(r.Spec.Options.CreateHostUserMode)]; !ok {
return trace.BadParameter("invalid host user mode %q, expected one of off, drop or keep", r.Spec.Options.CreateHostUserMode)
}

switch r.Version {
case V3:
if r.Spec.Allow.NodeLabels == nil {
Expand Down Expand Up @@ -1755,3 +1756,95 @@ var LabelMatcherKinds = []string{
KindWindowsDesktopService,
KindUserGroup,
}

const (
createHostUserModeOffString = "off"
createHostUserModeDropString = "drop"
createHostUserModeKeepString = "keep"
)

func (h CreateHostUserMode) encode() (string, error) {
switch h {
case CreateHostUserMode_HOST_USER_MODE_UNSPECIFIED:
return "", nil
case CreateHostUserMode_HOST_USER_MODE_OFF:
return createHostUserModeOffString, nil
case CreateHostUserMode_HOST_USER_MODE_DROP:
return createHostUserModeDropString, nil
case CreateHostUserMode_HOST_USER_MODE_KEEP:
return createHostUserModeKeepString, nil
}
return "", trace.BadParameter("invalid host user mode %v", h)
}

func (h *CreateHostUserMode) decode(val any) error {
var valS string
switch val := val.(type) {
case string:
valS = val
case bool:
if val {
return trace.BadParameter("create_host_user_mode cannot be true, got %v", val)
}
valS = createHostUserModeOffString
default:
return trace.BadParameter("bad value type %T, expected string", val)
}

switch valS {
case "":
*h = CreateHostUserMode_HOST_USER_MODE_UNSPECIFIED
case createHostUserModeOffString:
*h = CreateHostUserMode_HOST_USER_MODE_OFF
case createHostUserModeDropString:
*h = CreateHostUserMode_HOST_USER_MODE_DROP
case createHostUserModeKeepString:
*h = CreateHostUserMode_HOST_USER_MODE_KEEP
default:
return trace.BadParameter("invalid host user mode %v", val)
}
return nil
}

// UnmarshalYAML supports parsing CreateHostUserMode from string.
func (h *CreateHostUserMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
var val interface{}
err := unmarshal(&val)
if err != nil {
return trace.Wrap(err)
}

err = h.decode(val)
return trace.Wrap(err)
}

// MarshalYAML marshals CreateHostUserMode to yaml.
func (h *CreateHostUserMode) MarshalYAML() (interface{}, error) {
val, err := h.encode()
if err != nil {
return nil, trace.Wrap(err)
}
return val, nil
}

// MarshalJSON marshals CreateHostUserMode to json bytes.
func (h *CreateHostUserMode) MarshalJSON() ([]byte, error) {
val, err := h.encode()
if err != nil {
return nil, trace.Wrap(err)
}
out, err := json.Marshal(val)
return out, trace.Wrap(err)
}

// UnmarshalJSON supports parsing CreateHostUserMode from string.
func (h *CreateHostUserMode) UnmarshalJSON(data []byte) error {
var val interface{}
err := json.Unmarshal(data, &val)
if err != nil {
return trace.Wrap(err)
}

err = h.decode(val)
return trace.Wrap(err)
}
72 changes: 72 additions & 0 deletions api/types/role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ limitations under the License.
package types

import (
"encoding/json"
"fmt"
"testing"

"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"

"github.com/gravitational/teleport/api/types/wrappers"
)
Expand Down Expand Up @@ -338,3 +341,72 @@ func TestRole_GetKubeResources(t *testing.T) {
})
}
}

func TestMarshallCreateHostUserModeJSON(t *testing.T) {
for _, tc := range []struct {
input CreateHostUserMode
expected string
}{
{input: CreateHostUserMode_HOST_USER_MODE_OFF, expected: "off"},
{input: CreateHostUserMode_HOST_USER_MODE_UNSPECIFIED, expected: ""},
{input: CreateHostUserMode_HOST_USER_MODE_DROP, expected: "drop"},
{input: CreateHostUserMode_HOST_USER_MODE_KEEP, expected: "keep"},
} {
got, err := json.Marshal(&tc.input)
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("%q", tc.expected), string(got))
}

}

func TestMarshallCreateHostUserModeYAML(t *testing.T) {
for _, tc := range []struct {
input CreateHostUserMode
expected string
}{
{input: CreateHostUserMode_HOST_USER_MODE_OFF, expected: "\"off\""},
{input: CreateHostUserMode_HOST_USER_MODE_UNSPECIFIED, expected: "\"\""},
{input: CreateHostUserMode_HOST_USER_MODE_DROP, expected: "drop"},
{input: CreateHostUserMode_HOST_USER_MODE_KEEP, expected: "keep"},
} {
got, err := yaml.Marshal(&tc.input)
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("%s\n", tc.expected), string(got))
}

}

func TestUnmarshallCreateHostUserModeJSON(t *testing.T) {
for _, tc := range []struct {
expected CreateHostUserMode
input string
}{
{expected: CreateHostUserMode_HOST_USER_MODE_OFF, input: "off"},
{expected: CreateHostUserMode_HOST_USER_MODE_UNSPECIFIED, input: ""},
{expected: CreateHostUserMode_HOST_USER_MODE_DROP, input: "drop"},
{expected: CreateHostUserMode_HOST_USER_MODE_KEEP, input: "keep"},
} {
var got CreateHostUserMode
err := json.Unmarshal([]byte(fmt.Sprintf("%q", tc.input)), &got)
require.NoError(t, err)
require.Equal(t, tc.expected, got)
}
}

func TestUnmarshallCreateHostUserModeYAML(t *testing.T) {
for _, tc := range []struct {
expected CreateHostUserMode
input string
}{
{expected: CreateHostUserMode_HOST_USER_MODE_OFF, input: "\"off\""},
{expected: CreateHostUserMode_HOST_USER_MODE_OFF, input: "off"},
{expected: CreateHostUserMode_HOST_USER_MODE_UNSPECIFIED, input: "\"\""},
{expected: CreateHostUserMode_HOST_USER_MODE_DROP, input: "drop"},
{expected: CreateHostUserMode_HOST_USER_MODE_KEEP, input: "keep"},
} {
var got CreateHostUserMode
err := yaml.Unmarshal([]byte(tc.input), &got)
require.NoError(t, err)
require.Equal(t, tc.expected, got)
}
}
Loading