Skip to content

Commit

Permalink
Add option to allow for host users not to be deleted (gravitational#2…
Browse files Browse the repository at this point in the history
…6892)

* Add option to allow for host users not to be deleted

This adds a new role option called create_host_users_mode which allows
for it to be configured to not delete users when a session ends. The
old `create_host_user` option will be deprecated with this.

If the deprecated option is set, the new mode option will default to
dropping users as is the current behavior.

* add generated protos

* use combined output for commands, dont set CreateHostUser

* fix tests

* ci fixes

* add godocs to HostUserMode constants, fix tests

* remain -> keep

* Use an enum instead of a string

* fix merge errors

* Add tests & resolve comments

* Resolve issues

* fmt
  • Loading branch information
Alex McGrath authored Jun 23, 2023
1 parent b37a8b5 commit e2b76bb
Show file tree
Hide file tree
Showing 15 changed files with 1,939 additions and 1,486 deletions.
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

0 comments on commit e2b76bb

Please sign in to comment.