mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
This commit fixes #3369, refs #3374 It adds support for kuberenetes_users section in roles, allowing Teleport proxy to impersonate user identities. It also extends variable interpolation syntax by adding suffix and prefix to variables and function `email.local`: Example: ```yaml kind: role version: v3 metadata: name: admin spec: allow: # extract email local part from the email claim logins: ['{{email.local(external.email)}}'] # impersonate a kubernetes user with IAM prefix kubernetes_users: ['IAM#{{external.email}}'] # the deny section uses the identical format as the 'allow' section. # the deny rules always override allow rules. deny: {} ``` Some notes on email.local behavior: * This is the only function supported in the template variables for now * In case if the email.local will encounter invalid email address, it will interpolate to empty value, will be removed from resulting output. Changes in impersonation behavior: * By default, if no kubernetes_users is set, which is a majority of cases, user will impersonate themselves, which is the backwards-compatible behavior. * As long as at least one `kubernetes_users` is set, the forwarder will start limiting the list of users allowed by the client to impersonate. * If the users' role set does not include actual user name, it will be rejected, otherwise there will be no way to exclude the user from the list). * If the `kuberentes_users` role set includes only one user (quite frequently that's the real intent), teleport will default to it, otherwise it will refuse to select. This will enable the use case when `kubernetes_users` has just one field to link the user identity with the IAM role, for example `IAM#{{external.email}}` * Previous versions of the forwarding proxy were denying all external impersonation headers, this commit allows 'Impesrsonate-User' and 'Impersonate-Group' header values that are allowed by role set. * Previous versions of the forwarding proxy ignored 'Deny' section of the roles when applied to impersonation, this commit fixes that - roles with deny kubernetes_users and kubernetes_groups section will not allow impersonation of those users and groups.
198 lines
5.5 KiB
Go
198 lines
5.5 KiB
Go
/*
|
|
Copyright 2017-2020 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package parse
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
|
|
"github.com/gravitational/trace"
|
|
"gopkg.in/check.v1"
|
|
)
|
|
|
|
func TestParse(t *testing.T) { check.TestingT(t) }
|
|
|
|
type ParseSuite struct{}
|
|
|
|
var _ = check.Suite(&ParseSuite{})
|
|
var _ = fmt.Printf
|
|
|
|
func (s *ParseSuite) SetUpSuite(c *check.C) {
|
|
utils.InitLoggerForTests()
|
|
}
|
|
func (s *ParseSuite) TearDownSuite(c *check.C) {}
|
|
func (s *ParseSuite) SetUpTest(c *check.C) {}
|
|
func (s *ParseSuite) TearDownTest(c *check.C) {}
|
|
|
|
// TestRoleVariable tests variable parsing
|
|
func (s *ParseSuite) TestRoleVariable(c *check.C) {
|
|
var tests = []struct {
|
|
title string
|
|
in string
|
|
err error
|
|
out Expression
|
|
}{
|
|
{
|
|
title: "no curly bracket prefix",
|
|
in: "external.foo}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "invalid syntax",
|
|
in: `{{external.foo("bar")`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "invalid variable syntax",
|
|
in: "{{internal.}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "invalid dot syntax",
|
|
in: "{{external..foo}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "empty variable",
|
|
in: "{{}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "no curly bracket suffix",
|
|
in: "{{internal.foo",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "too many levels of nesting in the variable",
|
|
in: "{{internal.foo.bar}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "valid with brackets",
|
|
in: `{{internal["foo"]}}`,
|
|
out: Expression{namespace: "internal", variable: "foo"},
|
|
},
|
|
{
|
|
title: "external with no brackets",
|
|
in: "{{external.foo}}",
|
|
out: Expression{namespace: "external", variable: "foo"},
|
|
},
|
|
{
|
|
title: "internal with no brackets",
|
|
in: "{{internal.bar}}",
|
|
out: Expression{namespace: "internal", variable: "bar"},
|
|
},
|
|
{
|
|
title: "internal with spaces removed",
|
|
in: " {{ internal.bar }} ",
|
|
out: Expression{namespace: "internal", variable: "bar"},
|
|
},
|
|
{
|
|
title: "variable with prefix and suffix",
|
|
in: " hello, {{ internal.bar }} there! ",
|
|
out: Expression{prefix: "hello, ", namespace: "internal", variable: "bar", suffix: " there!"},
|
|
},
|
|
{
|
|
title: "variable with local function",
|
|
in: "{{email.local(internal.bar)}}",
|
|
out: Expression{namespace: "internal", variable: "bar", transform: EmailLocal},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
comment := check.Commentf("Test(%v) %q", i, tt.title)
|
|
|
|
variable, err := RoleVariable(tt.in)
|
|
if tt.err != nil {
|
|
c.Assert(err, check.FitsTypeOf, tt.err, comment)
|
|
continue
|
|
}
|
|
c.Assert(err, check.IsNil, comment)
|
|
// functionns are not directly comparable, compare fields
|
|
// directly, except functions as a workaround
|
|
c.Assert(variable.prefix, check.Equals, tt.out.prefix, comment)
|
|
c.Assert(variable.variable, check.Equals, tt.out.variable, comment)
|
|
c.Assert(variable.suffix, check.Equals, tt.out.suffix, comment)
|
|
// functions are not comparable
|
|
if tt.out.transform == nil {
|
|
c.Assert(variable.transform, check.IsNil, comment)
|
|
} else {
|
|
c.Assert(variable.transform, check.NotNil, comment)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestInterpolate tests variable interpolation
|
|
func (s *ParseSuite) TestInterpolate(c *check.C) {
|
|
type result struct {
|
|
values []string
|
|
err error
|
|
}
|
|
var tests = []struct {
|
|
title string
|
|
in Expression
|
|
traits map[string][]string
|
|
res result
|
|
}{
|
|
{
|
|
title: "mapped traits",
|
|
in: Expression{variable: "foo"},
|
|
traits: map[string][]string{"foo": []string{"a", "b"}, "bar": []string{"c"}},
|
|
res: result{values: []string{"a", "b"}},
|
|
},
|
|
{
|
|
title: "mapped traits with email.local",
|
|
in: Expression{variable: "foo", transform: EmailLocal},
|
|
traits: map[string][]string{"foo": []string{"Alice <alice@example.com>", "bob@example.com"}, "bar": []string{"c"}},
|
|
res: result{values: []string{"alice", "bob"}},
|
|
},
|
|
{
|
|
title: "missed traits",
|
|
in: Expression{variable: "baz"},
|
|
traits: map[string][]string{"foo": []string{"a", "b"}, "bar": []string{"c"}},
|
|
res: result{err: trace.NotFound("not found"), values: []string{}},
|
|
},
|
|
{
|
|
title: "traits with prefix and suffix",
|
|
in: Expression{prefix: "IAM#", variable: "foo", suffix: ";"},
|
|
traits: map[string][]string{"foo": []string{"a", "b"}, "bar": []string{"c"}},
|
|
res: result{values: []string{"IAM#a;", "IAM#b;"}},
|
|
},
|
|
{
|
|
title: "error in mapping traits",
|
|
in: Expression{variable: "foo", transform: EmailLocal},
|
|
traits: map[string][]string{"foo": []string{"Alice <alice"}},
|
|
res: result{err: trace.BadParameter("")},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
comment := check.Commentf("Test(%v) %q", i, tt.title)
|
|
|
|
values, err := tt.in.Interpolate(tt.traits)
|
|
if tt.res.err != nil {
|
|
c.Assert(err, check.FitsTypeOf, tt.res.err, comment)
|
|
c.Assert(values, check.HasLen, 0)
|
|
continue
|
|
}
|
|
c.Assert(err, check.IsNil, comment)
|
|
c.Assert(values, check.DeepEquals, tt.res.values, comment)
|
|
}
|
|
}
|