Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add negative RBAC label matches #4253

Merged
merged 4 commits into from
Sep 29, 2020
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
27 changes: 16 additions & 11 deletions lib/services/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,12 +385,9 @@ func ApplyTraits(r Role, traits map[string][]string) Role {
// at least one value in case if return value is nil
func applyValueTraits(val string, traits map[string][]string) ([]string, error) {
// Extract the variable from the role variable.
variable, err := parse.RoleVariable(val)
variable, err := parse.NewExpression(val)
if err != nil {
if !trace.IsNotFound(err) {
return nil, trace.Wrap(err)
}
return []string{val}, nil
return nil, trace.Wrap(err)
}

// For internal traits, only internal.logins, internal.kubernetes_users and
Expand Down Expand Up @@ -690,7 +687,7 @@ func (r *RoleV3) CheckAndSetDefaults() error {
for _, condition := range []RoleConditionType{Allow, Deny} {
for _, login := range r.GetLogins(condition) {
if strings.Contains(login, "{{") || strings.Contains(login, "}}") {
_, err := parse.RoleVariable(login)
_, err := parse.NewExpression(login)
if err != nil {
return trace.BadParameter("invalid login found: %v", login)
}
Expand Down Expand Up @@ -1588,11 +1585,19 @@ func MatchLabels(selector Labels, target map[string]string) (bool, string, error
}

if !utils.SliceContainsStr(selectorValues, Wildcard) {
result, err := utils.SliceMatchesRegex(targetVal, selectorValues)
if err != nil {
return false, "", trace.Wrap(err)
} else if !result {
return false, fmt.Sprintf("no value match: got '%v' want: '%v'", targetVal, selectorValues), nil
matched := false
for _, expr := range selectorValues {
m, err := parse.NewMatcher(expr)
if err != nil {
return false, "", trace.Wrap(err)
}
if m.Match(targetVal) {
matched = true
break
}
}
if !matched {
return false, fmt.Sprintf("no value match: got %q want: %q", targetVal, selectorValues), nil
}
}
}
Expand Down
58 changes: 58 additions & 0 deletions lib/services/role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,64 @@ func (s *RoleSuite) TestCheckAccess(c *C) {
{server: serverC2, login: "admin", hasAccess: true},
},
},
{
name: "role matches a regexp label",
roles: []RoleV3{
RoleV3{
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV3{
Options: RoleOptions{
MaxSessionTTL: Duration(20 * time.Hour),
},
Allow: RoleConditions{
Logins: []string{"admin"},
NodeLabels: Labels{"role": []string{`{{regexp.match("worker.*")}}`}},
Namespaces: []string{defaults.Namespace, namespaceC},
},
},
},
},
checks: []check{
{server: serverA, login: "root", hasAccess: false},
{server: serverA, login: "admin", hasAccess: false},
{server: serverB, login: "root", hasAccess: false},
{server: serverB, login: "admin", hasAccess: true},
{server: serverC, login: "root", hasAccess: false},
{server: serverC, login: "admin", hasAccess: false},
},
},
{
name: "role matches a negative regexp label",
roles: []RoleV3{
RoleV3{
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV3{
Options: RoleOptions{
MaxSessionTTL: Duration(20 * time.Hour),
},
Allow: RoleConditions{
Logins: []string{"admin"},
NodeLabels: Labels{"role": []string{`{{regexp.not_match("db.*")}}`}},
Namespaces: []string{defaults.Namespace, namespaceC},
},
},
},
},
checks: []check{
{server: serverA, login: "root", hasAccess: false},
{server: serverA, login: "admin", hasAccess: false},
{server: serverB, login: "root", hasAccess: false},
{server: serverB, login: "admin", hasAccess: true},
{server: serverC, login: "root", hasAccess: false},
{server: serverC, login: "admin", hasAccess: false},
},
},
}
for i, tc := range testCases {

Expand Down
4 changes: 2 additions & 2 deletions lib/services/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,8 @@ func (u *UserV1) Check() error {
return trace.BadParameter("user name cannot be empty")
}
for _, login := range u.AllowedLogins {
_, err := parse.RoleVariable(login)
if err == nil {
e, err := parse.NewExpression(login)
Copy link
Contributor

Choose a reason for hiding this comment

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

New logic only returns error in case of grammatic errors and accepts literal values. Doesn't it mean that now err should return error here if it's not nil? Otherwise the following will be accepted: {{regexp_not_match(...) which probably should not be ignored as it's a typo and we should let users know

if err == nil && e.Namespace() != parse.LiteralNamespace {
return trace.BadParameter("role variables not allowed in allowed logins")
}
}
Expand Down
Loading