Skip to content

Commit

Permalink
Add option to allow for host users not to be deleted
Browse files Browse the repository at this point in the history
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
lxea committed Jun 28, 2023
1 parent 58df205 commit 43a607a
Show file tree
Hide file tree
Showing 15 changed files with 1,937 additions and 1,484 deletions.
19 changes: 18 additions & 1 deletion api/proto/teleport/legacy/types/types.proto
Expand Up @@ -2315,6 +2315,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 @@ -2427,7 +2440,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 @@ -2471,6 +2484,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
Expand Up @@ -890,9 +890,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 @@ -911,6 +908,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 @@ -1673,3 +1674,95 @@ func (a AccessReviewConditions) IsEmpty() bool {
len(a.Roles) == 0 &&
len(a.Where) == 0
}

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
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 @@ -143,3 +146,72 @@ func TestAccessReviewConditionsIsEmpty(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)
}
}

0 comments on commit 43a607a

Please sign in to comment.