Files
teleport/lib/utils/fields.go
T
Alan ParraandAndrej Tokarčík 64679d2db8 Implement where conditions for active sessions (#9040)
Implements RFD 45 / "where" conditions for active sessions[1].

In few words, the purpose of the RFD is to allow the creation of roles that
permits users to only join a subset of active sessions (for example, only their
own sessions).

Implementation goes a bit further than the RFD, allowing the conditions to be
applied to  `update` and `delete` verbs as well.

Originally implemented by @andrejtokarcik (#8568), tweaks by @codingllama.

[1] https://github.com/gravitational/teleport/blob/master/rfd/0045-ssh_session-where-condition.md


* Implement where conditions for active sessions list/read
* actionWithConditionForList => actionForListWithCondition
* Make Context-exposed sessions follow the RFD API
* Add tests for "where" conditions on active sessions
* Fix typos
* Fix typos and spacing
* Rename "parties" to "participants" in the context session
* Update RFD to reflect PR changes

Update RFD to reflect PR changes

Specifically, mark as implemented and rename `parties` to `participants`.

* Push list authz logic to ServerWithRoles, obsolete cond
* Remove cond from GetSessions signature
* Simplify cast in lib.utils.Fields.GetString
* Add TODO to refactor SearchSessionEvents / stored sessions

Co-authored-by: Andrej Tokarčík <andrej@goteleport.com>
2021-11-18 15:05:13 -08:00

156 lines
4.2 KiB
Go

/*
Copyright 2021 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 utils
import (
"time"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils"
)
// Fields represents a generic string-keyed map.
type Fields map[string]interface{}
// GetString returns a string representation of a field.
func (f Fields) GetString(key string) string {
val, found := f[key]
if !found {
return ""
}
return val.(string)
}
// GetStrings returns a slice-of-strings representation of a field.
func (f Fields) GetStrings(key string) []string {
val, found := f[key]
if !found {
return nil
}
strings, ok := val.([]string)
if ok {
return strings
}
slice, _ := val.([]interface{})
res := make([]string, 0, len(slice))
for _, v := range slice {
s, ok := v.(string)
if ok {
res = append(res, s)
}
}
return res
}
// GetInt returns an int representation of a field.
func (f Fields) GetInt(key string) int {
val, found := f[key]
if !found {
return 0
}
v, ok := val.(int)
if !ok {
f, ok := val.(float64)
if ok {
v = int(f)
}
}
return v
}
// GetTime returns a time.Time representation of a field.
func (f Fields) GetTime(key string) time.Time {
val, found := f[key]
if !found {
return time.Time{}
}
v, ok := val.(time.Time)
if !ok {
s := f.GetString(key)
v, _ = time.Parse(time.RFC3339, s)
}
return v
}
// HasField returns true if the field exists.
func (f Fields) HasField(key string) bool {
_, ok := f[key]
return ok
}
// FieldsCondition is a boolean function on Fields.
type FieldsCondition func(Fields) bool
// ToFieldsCondition converts a WhereExpr into a FieldsCondition.
func ToFieldsCondition(expr *types.WhereExpr) (FieldsCondition, error) {
if expr == nil {
return nil, trace.BadParameter("expr is nil")
}
binOp := func(e types.WhereExpr2, op func(a, b bool) bool) (FieldsCondition, error) {
left, err := ToFieldsCondition(e.L)
if err != nil {
return nil, trace.Wrap(err)
}
right, err := ToFieldsCondition(e.R)
if err != nil {
return nil, trace.Wrap(err)
}
return func(f Fields) bool { return op(left(f), right(f)) }, nil
}
if expr, err := binOp(expr.And, func(a, b bool) bool { return a && b }); err == nil {
return expr, nil
}
if expr, err := binOp(expr.Or, func(a, b bool) bool { return a || b }); err == nil {
return expr, nil
}
if inner, err := ToFieldsCondition(expr.Not); err == nil {
return func(f Fields) bool { return !inner(f) }, nil
}
if expr.Equals.L != nil && expr.Equals.R != nil {
left, right := expr.Equals.L, expr.Equals.R
switch {
case left.Field != "" && right.Field != "":
return func(f Fields) bool { return f[left.Field] == f[right.Field] }, nil
case left.Literal != nil && right.Field != "":
return func(f Fields) bool { return left.Literal == f[right.Field] }, nil
case left.Field != "" && right.Literal != nil:
return func(f Fields) bool { return f[left.Field] == right.Literal }, nil
}
}
if expr.Contains.L != nil && expr.Contains.R != nil {
left, right := expr.Contains.L, expr.Contains.R
switch {
case left.Field != "" && right.Field != "":
return func(f Fields) bool { return utils.SliceContainsStr(f.GetStrings(left.Field), f.GetString(right.Field)) }, nil
case left.Literal != nil && right.Field != "":
if ss, ok := left.Literal.([]string); ok {
return func(f Fields) bool { return utils.SliceContainsStr(ss, f.GetString(right.Field)) }, nil
}
case left.Field != "" && right.Literal != nil:
if s, ok := right.Literal.(string); ok {
return func(f Fields) bool { return utils.SliceContainsStr(f.GetStrings(left.Field), s) }, nil
}
}
}
return nil, trace.BadParameter("failed to convert expression %q to FieldsCondition", expr)
}