mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
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>
156 lines
4.2 KiB
Go
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)
|
|
}
|