Skip to content

Commit

Permalink
Merge branch 'branch/v15' into bot/backport-35799-branch/v15
Browse files Browse the repository at this point in the history
  • Loading branch information
kimlisa authored Mar 28, 2024
2 parents c70079b + cc0c6ed commit 6a591b6
Show file tree
Hide file tree
Showing 35 changed files with 813 additions and 559 deletions.
4 changes: 3 additions & 1 deletion api/types/appserver_or_saml_idp_sp.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ func (a *AppServerOrSAMLIdPServiceProviderV1) GetLabel(key string) (value string
v, ok := appServer.Spec.App.Metadata.Labels[key]
return v, ok
} else {
return "", true
sp := a.GetSAMLIdPServiceProvider()
v, ok := sp.Metadata.Labels[key]
return v, ok
}
}

Expand Down
36 changes: 22 additions & 14 deletions api/types/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,13 +504,8 @@ func (m *Metadata) CheckAndSetDefaults() error {
// MatchLabels takes a map of labels and returns `true` if the resource has ALL
// of them.
func MatchLabels(resource ResourceWithLabels, labels map[string]string) bool {
if len(labels) == 0 {
return true
}

resourceLabels := resource.GetAllLabels()
for name, value := range labels {
if resourceLabels[name] != value {
for key, value := range labels {
if v, ok := resource.GetLabel(key); !ok || v != value {
return false
}
}
Expand Down Expand Up @@ -544,15 +539,11 @@ func IsValidLabelKey(s string) bool {
// Returns true if all search vals were matched (or if nil search vals).
// Returns false if no or partial match (or nil field values).
func MatchSearch(fieldVals []string, searchVals []string, customMatch func(val string) bool) bool {
// Case fold all values to avoid repeated case folding while matching.
caseFoldedSearchVals := utils.ToLowerStrings(searchVals)
caseFoldedFieldVals := utils.ToLowerStrings(fieldVals)

Outer:
for _, searchV := range caseFoldedSearchVals {
for _, searchV := range searchVals {
// Iterate through field values to look for a match.
for _, fieldV := range caseFoldedFieldVals {
if strings.Contains(fieldV, searchV) {
for _, fieldV := range fieldVals {
if containsFold(fieldV, searchV) {
continue Outer
}
}
Expand All @@ -568,6 +559,23 @@ Outer:
return true
}

// containsFold is a case-insensitive alternative to strings.Contains, used to help avoid excess allocations during searches.
func containsFold(s, substr string) bool {
if len(s) < len(substr) {
return false
}

n := len(s) - len(substr)

for i := 0; i <= n; i++ {
if strings.EqualFold(s[i:i+len(substr)], substr) {
return true
}
}

return false
}

func stringCompare(a string, b string, isDesc bool) bool {
if isDesc {
return a > b
Expand Down
31 changes: 21 additions & 10 deletions api/types/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,11 @@ func (s *ServerV2) GetAllLabels() map[string]string {

// CombineLabels combines the passed in static and dynamic labels.
func CombineLabels(static map[string]string, dynamic map[string]CommandLabelV2) map[string]string {
lmap := make(map[string]string)
if len(dynamic) == 0 {
return static
}

lmap := make(map[string]string, len(static)+len(dynamic))
for key, value := range static {
lmap[key] = value
}
Expand Down Expand Up @@ -533,20 +537,27 @@ func (s *ServerV2) CheckAndSetDefaults() error {
// MatchSearch goes through select field values and tries to
// match against the list of search values.
func (s *ServerV2) MatchSearch(values []string) bool {
var fieldVals []string
if s.GetKind() != KindNode {
return false
}

var custom func(val string) bool
if s.GetUseTunnel() {
custom = func(val string) bool {
return strings.EqualFold(val, "tunnel")
}
}

if s.GetKind() == KindNode {
fieldVals = append(utils.MapToStrings(s.GetAllLabels()), s.GetName(), s.GetHostname(), s.GetAddr())
fieldVals = append(fieldVals, s.GetPublicAddrs()...)
fieldVals := make([]string, 0, (len(s.Metadata.Labels)*2)+(len(s.Spec.CmdLabels)*2)+len(s.Spec.PublicAddrs)+3)

if s.GetUseTunnel() {
custom = func(val string) bool {
return strings.EqualFold(val, "tunnel")
}
}
labels := CombineLabels(s.Metadata.Labels, s.Spec.CmdLabels)
for key, value := range labels {
fieldVals = append(fieldVals, key, value)
}

fieldVals = append(fieldVals, s.Metadata.Name, s.Spec.Hostname, s.Spec.Addr)
fieldVals = append(fieldVals, s.Spec.PublicAddrs...)

return MatchSearch(fieldVals, values, custom)
}

Expand Down
2 changes: 1 addition & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2125,7 +2125,7 @@
"aws_secret_access_key": "zyxw9876-this-is-an-example"
},
"cloud": {
"version": "15.1.1",
"version": "15.1.9",
"major_version": "15",
"sla": {
"monthly_percentage": "99.9%",
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/access-controls/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ user:
| `desktop_clipboard` | Allow clipboard sharing for desktop sessions | Logical "AND" i.e. evaluates to "yes" if all roles enable clipboard sharing |
| `pin_source_ip` | Enable source IP pinning for SSH certificates. | Logical "OR" i.e. evaluates to "yes" if at least one role requires session termination |
| `cert_extensions` | Specifies extensions to be included in SSH certificates | |
| `create_host_user_mode` | Allow users to be automatically created on a host | Logical "AND" i.e. if all roles matching a server specify host user creation (`off`, `drop`, `keep`), it will evaluate to the option specified by all of the roles. If some roles specify both `drop` or `keep` it will evaluate to `keep`|
| `create_host_user_mode` | Allow users to be automatically created on a host | Logical "AND" i.e. if all roles matching a server specify host user creation (`off`, `keep`, `insecure-drop`), it will evaluate to the option specified by all of the roles. If some roles specify both `insecure-drop` or `keep` it will evaluate to `keep`|
| `create_db_user_mode` | Allow [database user auto provisioning](../database-access/auto-user-provisioning.mdx). Options: `off` (disable database user auto-provisioning), `keep` (disables the user at session end, removing the roles and locking it), and `best_effort_drop` (try to drop the user at session end, if it doesn't succeed, fallback to disabling it). | Logical "OR" i.e. if any role allows database user auto-provisioning, it's allowed |

## Preset roles
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/includes/role-spec.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ spec:
name: [email protected]
value: "{{ external.github_login }}"
# Controls whether this role supports auto provisioning of SSH users.
# Options: drop (remove user on session end), keep (keep users at session end)
# Options: keep (keep users at session end), insecure-drop (remove user on session end),
# and off (disable host user creation)
create_host_user_mode: drop
create_host_user_mode: keep
# Controls whether this role requires automatic database user provisioning.
# Options: off (disable database user auto-provisioning), keep (disables the
# user at session end, removing the roles and locking it), and
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/server-access/guides/host-user-creation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ ssh_service:
```

In low-security environments, you can also set `create_host_user_mode` to
`insecure_drop`, which deletes users once the session ends. However, in this
`insecure-drop`, which deletes users once the session ends. However, in this
mode it is possible for a created user to get the same UID as a previously
deleted user, which would give the new user access to all of the old user's
files if they are not deleted. Use `keep` mode unless you really need users to
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/server-access/rbac.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ spec:
#....
options:
# Controls whether this role supports auto provisioning of users.
# Options: drop (remove user on session end), keep (keep users at session end)
# Options: keep (keep users at session end), insecure-drop (remove user on session end),
# and off (disable host user creation)
create_host_user_mode: drop
create_host_user_mode: keep
# forward_agent controls whether SSH agent forwarding is allowed
forward_agent: true
# port_forwarding controls whether TCP port forwarding is allowed for SSH
Expand Down
2 changes: 1 addition & 1 deletion e
Submodule e updated from d3bb6f to d68d3c
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func main() {
)

opts := zap.Options{
Development: true,
Development: false,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
Expand Down
2 changes: 1 addition & 1 deletion integrations/lib/testing/integration/authservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (
"github.com/gravitational/teleport/integrations/lib/logger"
)

var regexpAuthStarting = regexp.MustCompile(`Auth service [^ ]+ is starting on [^ ]+:(\d+)`)
var regexpAuthStarting = regexp.MustCompile(`Auth service.*listen_address:[^ ]+:(\d+)`)

type AuthService struct {
mu sync.Mutex
Expand Down
6 changes: 3 additions & 3 deletions integrations/lib/testing/integration/proxyservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ import (
"github.com/gravitational/teleport/integrations/lib/logger"
)

var regexpWebProxyStarting = regexp.MustCompile(`Web proxy service [^ ]+ is starting on [^ ]+:(\d+)`)
var regexpSSHProxyStarting = regexp.MustCompile(`SSH proxy service [^ ]+ is starting on [^ ]+:(\d+)`)
var regexpReverseTunnelStarting = regexp.MustCompile(`Reverse tunnel service [^ ]+ is starting on [^ ]+:(\d+)`)
var regexpWebProxyStarting = regexp.MustCompile(`Starting web proxy service.*listen_address:[^ ]+:(\d+)`)
var regexpSSHProxyStarting = regexp.MustCompile(`Starting SSH proxy service.*listen_address:[^ ]+:(\d+)`)
var regexpReverseTunnelStarting = regexp.MustCompile(`Starting reverse tunnel server.*listen_address:[^ ]+:(\d+)`)

type ProxyService struct {
mu sync.Mutex
Expand Down
2 changes: 1 addition & 1 deletion integrations/lib/testing/integration/sshservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (
"github.com/gravitational/teleport/integrations/lib/logger"
)

var regexpSSHStarting = regexp.MustCompile(`Service [^ ]+ is starting on [^ ]+:(\d+)`)
var regexpSSHStarting = regexp.MustCompile(`SSH Service is starting.*listen_address:[^ ]+:(\d+)`)

type SSHService struct {
mu sync.Mutex
Expand Down
46 changes: 38 additions & 8 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -1329,18 +1329,34 @@ func (a *ServerWithRoles) checkUnifiedAccess(resource types.ResourceWithLabels,
return false, trace.Wrap(canAccessErr)
}

if resourceKind != types.KindSAMLIdPServiceProvider {
if err := checker.CanAccess(resource); err != nil {
// Filter first and only check RBAC if there is a match to improve perf.
match, err := services.MatchResourceByFilters(resource, filter, nil)
if err != nil {
log.WithFields(logrus.Fields{
"resource_name": resource.GetName(),
"resource_kind": resourceKind,
"error": err,
}).
Warn("Unable to determine access to resource, matching with filter failed")
return false, nil
}

if trace.IsAccessDenied(err) {
return false, nil
}
return false, trace.Wrap(err)
if !match {
return false, nil
}

if resourceKind == types.KindSAMLIdPServiceProvider {
return true, nil
}

if err := checker.CanAccess(resource); err != nil {
if trace.IsAccessDenied(err) {
return false, nil
}
return false, trace.Wrap(err)
}

match, err := services.MatchResourceByFilters(resource, filter, nil)
return match, trace.Wrap(err)
return true, nil
}

// ListUnifiedResources returns a paginated list of unified resources filtered by user access.
Expand All @@ -1358,6 +1374,20 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
Kinds: req.Kinds,
}

// If a predicate expression was provided, evaluate it with an empty
// server to determine if the expression is valid before attempting
// to do any listing.
if filter.PredicateExpression != "" {
parser, err := services.NewResourceParser(&types.ServerV2{})
if err != nil {
return nil, trace.Wrap(err)
}

if _, err := parser.EvalBoolPredicate(filter.PredicateExpression); err != nil {
return nil, trace.BadParameter("failed to parse predicate expression: %s", err.Error())
}
}

// Populate resourceAccessMap with any access errors the user has for each possible
// resource kind. This allows the access check to occur a single time per resource
// kind instead of once per matching resource.
Expand Down
Loading

0 comments on commit 6a591b6

Please sign in to comment.