Skip to content
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
49 changes: 34 additions & 15 deletions api/types/system_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package types

import (
"fmt"
"strings"

"github.com/gravitational/trace"
Expand Down Expand Up @@ -61,8 +60,26 @@ const (
RoleWindowsDesktop SystemRole = "WindowsDesktop"
)

// LegacyClusterTokenType exists for backwards compatibility reasons, needed to upgrade to 2.3
const LegacyClusterTokenType SystemRole = "Trustedcluster"
// roleMappings maps a set of allowed lowercase system role names
// to the proper system role
var roleMappings = map[string]SystemRole{
"auth": RoleAuth,
"node": RoleNode,
"proxy": RoleProxy,
"admin": RoleAdmin,
"provisiontoken": RoleProvisionToken,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is provision_token a valid input?

Copy link
Copy Markdown
Collaborator Author

@zmb3 zmb3 Jan 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it never has been.

We're a little more tolerant and allow _ separators for things that are commonly typed on the command line, like when generating tokens to allow a kube service or windows desktop service to join the cluster.

Since no one ever needs to generate provision tokens via the CLI, I didn't see a need to make provision_token valid.

In fact, after a quick search, it seems RoleProvisionToken is never used. Maybe we should delete it.

"trusted_cluster": RoleTrustedCluster,
"trustedcluster": RoleTrustedCluster,
"signup": RoleSignup,
"nop": RoleNop,
"remoteproxy": RoleRemoteProxy,
"remote_proxy": RoleRemoteProxy,
"kube": RoleKube,
"app": RoleApp,
"db": RoleDatabase,
"windowsdesktop": RoleWindowsDesktop,
"windows_desktop": RoleWindowsDesktop,
}

// NewTeleportRoles return a list of teleport roles from slice of strings
func NewTeleportRoles(in []string) (SystemRoles, error) {
Expand All @@ -78,15 +95,17 @@ func NewTeleportRoles(in []string) (SystemRoles, error) {
func ParseTeleportRoles(str string) (SystemRoles, error) {
var roles SystemRoles
for _, s := range strings.Split(str, ",") {
s = strings.TrimSpace(s)
if r := SystemRole(s); r.Check() == nil {
cleaned := strings.ToLower(strings.TrimSpace(s))
if r, ok := roleMappings[cleaned]; ok && r.Check() == nil {
roles = append(roles, r)
continue
}
// If role is not valid as-is, attempt to canonicalize the format.
r := SystemRole(strings.Title(strings.ToLower(s)))
roles = append(roles, r)
return nil, trace.BadParameter("invalid role %q", s)
}
if len(roles) == 0 {
return nil, trace.BadParameter("no valid roles in $%q", str)
}

return roles, roles.Check()
}

Expand Down Expand Up @@ -178,22 +197,22 @@ func (r *SystemRole) String() string {
switch *r {
case RoleSignup:
return "Password"
case RoleTrustedCluster, LegacyClusterTokenType:
case RoleTrustedCluster:
return "trusted_cluster"
default:
return fmt.Sprintf("%v", string(*r))
return string(*r)
}
}

// Check checks if this a a valid teleport role value, returns nil
// if it's ok, false otherwise
// Check checks if this a a valid teleport role value, returns nil
// if it's ok, false otherwise
func (r *SystemRole) Check() error {
switch *r {
case RoleAuth, RoleNode, RoleApp, RoleDatabase,
RoleAdmin, RoleProvisionToken,
RoleTrustedCluster, LegacyClusterTokenType,
RoleSignup, RoleProxy, RoleNop, RoleKube, RoleWindowsDesktop:
sr, ok := roleMappings[strings.ToLower(string(*r))]
if ok && string(*r) == string(sr) {
return nil
}

return trace.BadParameter("role %v is not registered", *r)
}
78 changes: 77 additions & 1 deletion api/types/system_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package types

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestRolesCheck(t *testing.T) {
Expand All @@ -26,7 +28,7 @@ func TestRolesCheck(t *testing.T) {
}{
{roles: []SystemRole{}, wantErr: false},
{roles: []SystemRole{RoleAuth}, wantErr: false},
{roles: []SystemRole{RoleAuth, RoleNode, RoleProxy, RoleAdmin, RoleProvisionToken, RoleTrustedCluster, LegacyClusterTokenType, RoleSignup, RoleNop}, wantErr: false},
{roles: []SystemRole{RoleAuth, RoleNode, RoleProxy, RoleAdmin, RoleProvisionToken, RoleTrustedCluster, RoleSignup, RoleNop}, wantErr: false},
{roles: []SystemRole{RoleAuth, RoleNode, RoleAuth}, wantErr: true},
{roles: []SystemRole{SystemRole("unknown")}, wantErr: true},
}
Expand Down Expand Up @@ -62,3 +64,77 @@ func TestRolesEqual(t *testing.T) {
}
}
}

func TestParseTeleportRoles(t *testing.T) {
t.Parallel()
for _, test := range []struct {
in string
out SystemRoles
wantErr bool
}{
{
// system role constant
in: string(RoleNode),
out: SystemRoles{RoleNode},
wantErr: false,
},
{
// alternate capitalization
in: "node",
out: SystemRoles{RoleNode},
wantErr: false,
},
{
in: "windowsdesktop",
out: SystemRoles{RoleWindowsDesktop},
wantErr: false,
},
{
// allow underscores for camelcase roles
in: "windows_desktop,trusted_cluster,remote_proxy",
out: SystemRoles{RoleWindowsDesktop, RoleTrustedCluster, RoleRemoteProxy},
wantErr: false,
},
{
// multiple comma-separated roles
in: "Auth,Proxy",
out: SystemRoles{RoleAuth, RoleProxy},
wantErr: false,
},
{
// extra whitespace handled properly
in: "Auth , Proxy,WindowsDesktop",
out: SystemRoles{RoleAuth, RoleProxy, RoleWindowsDesktop},
wantErr: false,
},
{
// duplicate roles is an error
in: "Auth,Auth",
wantErr: true,
},
{
in: "Auth, auth",
wantErr: true,
},
{
// invalid role errors
in: "invalidrole",
wantErr: true,
},
{
// valid + invalid role errors
in: "Admin,invalidrole",
wantErr: true,
},
} {
t.Run(test.in, func(t *testing.T) {
roles, err := ParseTeleportRoles(test.in)
if test.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, test.out, roles)
}
})
}
}
2 changes: 1 addition & 1 deletion lib/auth/trustedcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ func (a *Server) validateTrustedClusterToken(token string) (map[string]string, e
return nil, trace.AccessDenied("the remote server denied access: invalid cluster token")
}

if !roles.Include(types.RoleTrustedCluster) && !roles.Include(types.LegacyClusterTokenType) {
if !roles.Include(types.RoleTrustedCluster) {
return nil, trace.AccessDenied("role does not match")
}

Expand Down
4 changes: 2 additions & 2 deletions tool/tctl/common/node_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ func (c *NodeCommand) Invite(client auth.ClientI) error {
return trace.Errorf("This cluster does not have any auth servers running.")
}

// output format swtich:
// output format switch:
if c.format == "text" {
if roles.Include(types.RoleTrustedCluster) || roles.Include(types.LegacyClusterTokenType) {
if roles.Include(types.RoleTrustedCluster) {
fmt.Printf(trustedClusterMessage, token, int(c.ttl.Minutes()))
} else {
return nodeMessageTemplate.Execute(os.Stdout, map[string]interface{}{
Expand Down
2 changes: 1 addition & 1 deletion tool/tctl/common/token_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func (c *TokenCommand) Add(client auth.ClientI) error {
"db_protocol": c.dbProtocol,
"db_uri": c.dbURI,
})
case roles.Include(types.RoleTrustedCluster), roles.Include(types.LegacyClusterTokenType):
case roles.Include(types.RoleTrustedCluster):
fmt.Printf(trustedClusterMessage,
token,
int(c.ttl.Minutes()))
Expand Down