Add support for Protobuf Enums into Operator CRDs (#32469)

* Add support for Protobuf Enums into Operator CRDs

This PR marks the Teleport enum fields as integer or string values. The
integer option is to ensure we are backwards compatibile with
previously installed CRDs.

Users can now represent their roles in Kubernetes custom resources and
refer enum fields as strings while their protobuf wire type is int32.

Fixes #29686

* add tests

* fix unit test
This commit is contained in:
Tiago Silva 2023-09-26 14:43:50 +01:00 committed by GitHub
parent 13c283036b
commit 9a556d8ab1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 159 additions and 68 deletions

View file

@ -953,8 +953,27 @@ func (r *RequireMFAType) decode(val interface{}) error {
} else {
*r = RequireMFAType_OFF
}
case int32:
return trace.Wrap(r.setFromEnum(v))
case int64:
return trace.Wrap(r.setFromEnum(int32(v)))
case int:
return trace.Wrap(r.setFromEnum(int32(v)))
case float64:
return trace.Wrap(r.setFromEnum(int32(v)))
case float32:
return trace.Wrap(r.setFromEnum(int32(v)))
default:
return trace.BadParameter("RequireMFAType invalid type %T", val)
}
return nil
}
// setFromEnum sets the value from enum value as int32.
func (r *RequireMFAType) setFromEnum(val int32) error {
if _, ok := RequireMFAType_name[val]; !ok {
return trace.BadParameter("invalid required mfa mode %v", val)
}
*r = RequireMFAType(val)
return nil
}

View file

@ -39,16 +39,40 @@ func (t CertExtensionType) MarshalJSON() ([]byte, error) {
}
func (t *CertExtensionType) UnmarshalJSON(b []byte) error {
var stringVal string
if err := json.Unmarshal(b, &stringVal); err != nil {
var anyVal any
if err := json.Unmarshal(b, &anyVal); err != nil {
return err
}
val, ok := certExtensionTypeValue[stringVal]
if !ok {
return trace.Errorf("invalid certificate extension type: %q", string(b))
switch val := anyVal.(type) {
case string:
enumVal, ok := certExtensionTypeValue[val]
if !ok {
return trace.Errorf("invalid certificate extension type: %q", string(b))
}
*t = enumVal
return nil
case int32:
return t.setFromEnum(val)
case int:
return t.setFromEnum(int32(val))
case int64:
return t.setFromEnum(int32(val))
case float64:
return trace.Wrap(t.setFromEnum(int32(val)))
case float32:
return trace.Wrap(t.setFromEnum(int32(val)))
default:
return trace.BadParameter("unexpected type %T", val)
}
*t = val
}
// setFromEnum sets the value from enum value as int32.
func (t *CertExtensionType) setFromEnum(val int32) error {
if _, ok := CertExtensionType_name[val]; !ok {
return trace.BadParameter("invalid cert extension mode %v", val)
}
*t = CertExtensionType(val)
return nil
}
@ -69,14 +93,38 @@ func (t CertExtensionMode) MarshalJSON() ([]byte, error) {
}
func (t *CertExtensionMode) UnmarshalJSON(b []byte) error {
var stringVal string
if err := json.Unmarshal(b, &stringVal); err != nil {
var anyVal any
if err := json.Unmarshal(b, &anyVal); err != nil {
return err
}
val, ok := certExtensionModeValue[stringVal]
if !ok {
return trace.Errorf("invalid certificate extension mode: %q", string(b))
switch val := anyVal.(type) {
case string:
enumVal, ok := certExtensionModeValue[val]
if !ok {
return trace.Errorf("invalid certificate extension mode: %q", string(b))
}
*t = enumVal
return nil
case int32:
return t.setFromEnum(val)
case int:
return t.setFromEnum(int32(val))
case int64:
return t.setFromEnum(int32(val))
case float64:
return trace.Wrap(t.setFromEnum(int32(val)))
case float32:
return trace.Wrap(t.setFromEnum(int32(val)))
default:
return trace.BadParameter("unexpected type %T", val)
}
*t = val
}
// setFromEnum sets the value from enum value as int32.
func (t *CertExtensionMode) setFromEnum(val int32) error {
if _, ok := CertExtensionMode_name[val]; !ok {
return trace.BadParameter("invalid cert extension mode %v", val)
}
*t = CertExtensionMode(val)
return nil
}

View file

@ -1828,6 +1828,16 @@ func (h CreateHostUserMode) encode() (string, error) {
func (h *CreateHostUserMode) decode(val any) error {
var valS string
switch val := val.(type) {
case int32:
return trace.Wrap(h.setFromEnum(val))
case int64:
return trace.Wrap(h.setFromEnum(int32(val)))
case int:
return trace.Wrap(h.setFromEnum(int32(val)))
case float64:
return trace.Wrap(h.setFromEnum(int32(val)))
case float32:
return trace.Wrap(h.setFromEnum(int32(val)))
case string:
valS = val
case bool:
@ -1836,7 +1846,7 @@ func (h *CreateHostUserMode) decode(val any) error {
}
valS = createHostUserModeOffString
default:
return trace.BadParameter("bad value type %T, expected string", val)
return trace.BadParameter("bad value type %T, expected string or int", val)
}
switch valS {
@ -1854,6 +1864,15 @@ func (h *CreateHostUserMode) decode(val any) error {
return nil
}
// setFromEnum sets the value from enum value as int32.
func (h *CreateHostUserMode) setFromEnum(val int32) error {
if _, ok := CreateHostUserMode_name[val]; !ok {
return trace.BadParameter("invalid host user mode %v", val)
}
*h = CreateHostUserMode(val)
return nil
}
// UnmarshalYAML supports parsing CreateHostUserMode from string.
func (h *CreateHostUserMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
var val interface{}

View file

@ -400,15 +400,17 @@ func TestMarshallCreateHostUserModeYAML(t *testing.T) {
func TestUnmarshallCreateHostUserModeJSON(t *testing.T) {
for _, tc := range []struct {
expected CreateHostUserMode
input string
input any
}{
{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"},
{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\""},
{expected: CreateHostUserMode_HOST_USER_MODE_KEEP, input: 3},
{expected: CreateHostUserMode_HOST_USER_MODE_OFF, input: 1},
} {
var got CreateHostUserMode
err := json.Unmarshal([]byte(fmt.Sprintf("%q", tc.input)), &got)
err := json.Unmarshal([]byte(fmt.Sprintf("%v", tc.input)), &got)
require.NoError(t, err)
require.Equal(t, tc.expected, got)
}

View file

@ -963,8 +963,7 @@ spec:
mode:
description: Mode is the type of extension to be used --
currently critical-option is not supported
format: int32
type: integer
x-kubernetes-int-or-string: true
name:
description: Name specifies the key to be used in the cert
extension.
@ -972,8 +971,7 @@ spec:
type:
description: Type represents the certificate type being
extended, only ssh is supported at this time.
format: int32
type: integer
x-kubernetes-int-or-string: true
value:
description: Value specifies the value to be used in the
cert extension.
@ -1006,8 +1004,7 @@ spec:
create_host_user_mode:
description: CreateHostUserMode allows users to be automatically
created on a host when not set to off
format: int32
type: integer
x-kubernetes-int-or-string: true
desktop_clipboard:
description: DesktopClipboard indicates whether clipboard sharing
is allowed between the user's workstation and the remote desktop.
@ -1118,8 +1115,7 @@ spec:
require_session_mfa:
description: RequireMFAType is the type of MFA requirement enforced
for this user.
format: int32
type: integer
x-kubernetes-int-or-string: true
ssh_file_copy:
description: SSHFileCopy indicates whether remote file operations
via SCP or SFTP are allowed over an SSH session. It defaults
@ -2160,8 +2156,7 @@ spec:
mode:
description: Mode is the type of extension to be used --
currently critical-option is not supported
format: int32
type: integer
x-kubernetes-int-or-string: true
name:
description: Name specifies the key to be used in the cert
extension.
@ -2169,8 +2164,7 @@ spec:
type:
description: Type represents the certificate type being
extended, only ssh is supported at this time.
format: int32
type: integer
x-kubernetes-int-or-string: true
value:
description: Value specifies the value to be used in the
cert extension.
@ -2203,8 +2197,7 @@ spec:
create_host_user_mode:
description: CreateHostUserMode allows users to be automatically
created on a host when not set to off
format: int32
type: integer
x-kubernetes-int-or-string: true
desktop_clipboard:
description: DesktopClipboard indicates whether clipboard sharing
is allowed between the user's workstation and the remote desktop.
@ -2315,8 +2308,7 @@ spec:
require_session_mfa:
description: RequireMFAType is the type of MFA requirement enforced
for this user.
format: int32
type: integer
x-kubernetes-int-or-string: true
ssh_file_copy:
description: SSHFileCopy indicates whether remote file operations
via SCP or SFTP are allowed over an SSH session. It defaults

View file

@ -963,8 +963,7 @@ spec:
mode:
description: Mode is the type of extension to be used --
currently critical-option is not supported
format: int32
type: integer
x-kubernetes-int-or-string: true
name:
description: Name specifies the key to be used in the cert
extension.
@ -972,8 +971,7 @@ spec:
type:
description: Type represents the certificate type being
extended, only ssh is supported at this time.
format: int32
type: integer
x-kubernetes-int-or-string: true
value:
description: Value specifies the value to be used in the
cert extension.
@ -1006,8 +1004,7 @@ spec:
create_host_user_mode:
description: CreateHostUserMode allows users to be automatically
created on a host when not set to off
format: int32
type: integer
x-kubernetes-int-or-string: true
desktop_clipboard:
description: DesktopClipboard indicates whether clipboard sharing
is allowed between the user's workstation and the remote desktop.
@ -1118,8 +1115,7 @@ spec:
require_session_mfa:
description: RequireMFAType is the type of MFA requirement enforced
for this user.
format: int32
type: integer
x-kubernetes-int-or-string: true
ssh_file_copy:
description: SSHFileCopy indicates whether remote file operations
via SCP or SFTP are allowed over an SSH session. It defaults
@ -2160,8 +2156,7 @@ spec:
mode:
description: Mode is the type of extension to be used --
currently critical-option is not supported
format: int32
type: integer
x-kubernetes-int-or-string: true
name:
description: Name specifies the key to be used in the cert
extension.
@ -2169,8 +2164,7 @@ spec:
type:
description: Type represents the certificate type being
extended, only ssh is supported at this time.
format: int32
type: integer
x-kubernetes-int-or-string: true
value:
description: Value specifies the value to be used in the
cert extension.
@ -2203,8 +2197,7 @@ spec:
create_host_user_mode:
description: CreateHostUserMode allows users to be automatically
created on a host when not set to off
format: int32
type: integer
x-kubernetes-int-or-string: true
desktop_clipboard:
description: DesktopClipboard indicates whether clipboard sharing
is allowed between the user's workstation and the remote desktop.
@ -2315,8 +2308,7 @@ spec:
require_session_mfa:
description: RequireMFAType is the type of MFA requirement enforced
for this user.
format: int32
type: integer
x-kubernetes-int-or-string: true
ssh_file_copy:
description: SSHFileCopy indicates whether remote file operations
via SCP or SFTP are allowed over an SSH session. It defaults

View file

@ -82,6 +82,26 @@ func TestRoleCreationFromYAML(t *testing.T) {
shouldFail bool
expectedSpec *types.RoleSpecV6
}{
{
name: "Valid login list with integer create_host_user_mode",
roleSpecYAML: `
allow:
logins:
- ubuntu
- root
options:
create_host_user_mode: 2
`,
shouldFail: false,
expectedSpec: &types.RoleSpecV6{
Allow: types.RoleConditions{
Logins: []string{"ubuntu", "root"},
},
Options: types.RoleOptions{
CreateHostUserMode: types.CreateHostUserMode_HOST_USER_MODE_DROP,
},
},
},
{
name: "Valid login list",
roleSpecYAML: `
@ -89,12 +109,17 @@ allow:
logins:
- ubuntu
- root
options:
create_host_user_mode: keep
`,
shouldFail: false,
expectedSpec: &types.RoleSpecV6{
Allow: types.RoleConditions{
Logins: []string{"ubuntu", "root"},
},
Options: types.RoleOptions{
CreateHostUserMode: types.CreateHostUserMode_HOST_USER_MODE_KEEP,
},
},
},
{

View file

@ -297,9 +297,11 @@ func (generator *SchemaGenerator) singularProp(field *Field, prop *apiextv1.JSON
case field.IsTime():
prop.Type = "string"
prop.Format = "date-time"
case field.IsInt32() || field.IsUint32() || field.desc.IsEnum():
case field.IsInt32() || field.IsUint32():
prop.Type = "integer"
prop.Format = "int32"
case field.desc.IsEnum():
prop.XIntOrString = true
case field.IsInt64() || field.IsUint64():
prop.Type = "integer"
prop.Format = "int64"

View file

@ -963,8 +963,7 @@ spec:
mode:
description: Mode is the type of extension to be used --
currently critical-option is not supported
format: int32
type: integer
x-kubernetes-int-or-string: true
name:
description: Name specifies the key to be used in the cert
extension.
@ -972,8 +971,7 @@ spec:
type:
description: Type represents the certificate type being
extended, only ssh is supported at this time.
format: int32
type: integer
x-kubernetes-int-or-string: true
value:
description: Value specifies the value to be used in the
cert extension.
@ -1006,8 +1004,7 @@ spec:
create_host_user_mode:
description: CreateHostUserMode allows users to be automatically
created on a host when not set to off
format: int32
type: integer
x-kubernetes-int-or-string: true
desktop_clipboard:
description: DesktopClipboard indicates whether clipboard sharing
is allowed between the user's workstation and the remote desktop.
@ -1118,8 +1115,7 @@ spec:
require_session_mfa:
description: RequireMFAType is the type of MFA requirement enforced
for this user.
format: int32
type: integer
x-kubernetes-int-or-string: true
ssh_file_copy:
description: SSHFileCopy indicates whether remote file operations
via SCP or SFTP are allowed over an SSH session. It defaults
@ -2160,8 +2156,7 @@ spec:
mode:
description: Mode is the type of extension to be used --
currently critical-option is not supported
format: int32
type: integer
x-kubernetes-int-or-string: true
name:
description: Name specifies the key to be used in the cert
extension.
@ -2169,8 +2164,7 @@ spec:
type:
description: Type represents the certificate type being
extended, only ssh is supported at this time.
format: int32
type: integer
x-kubernetes-int-or-string: true
value:
description: Value specifies the value to be used in the
cert extension.
@ -2203,8 +2197,7 @@ spec:
create_host_user_mode:
description: CreateHostUserMode allows users to be automatically
created on a host when not set to off
format: int32
type: integer
x-kubernetes-int-or-string: true
desktop_clipboard:
description: DesktopClipboard indicates whether clipboard sharing
is allowed between the user's workstation and the remote desktop.
@ -2315,8 +2308,7 @@ spec:
require_session_mfa:
description: RequireMFAType is the type of MFA requirement enforced
for this user.
format: int32
type: integer
x-kubernetes-int-or-string: true
ssh_file_copy:
description: SSHFileCopy indicates whether remote file operations
via SCP or SFTP are allowed over an SSH session. It defaults