mirror of
https://github.com/gravitational/teleport
synced 2024-10-22 02:03:24 +00:00
304 lines
8.2 KiB
Go
304 lines
8.2 KiB
Go
/*
|
|
Copyright 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 services
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/api/types/wrappers"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/vulcand/predicate"
|
|
"github.com/vulcand/predicate/builder"
|
|
)
|
|
|
|
// RuleContext specifies context passed to the
|
|
// rule processing matcher, and contains information
|
|
// about current session, e.g. current user
|
|
type RuleContext interface {
|
|
// GetIdentifier returns identifier defined in a context
|
|
GetIdentifier(fields []string) (interface{}, error)
|
|
// String returns human friendly representation of a context
|
|
String() string
|
|
// GetResource returns resource if specified in the context,
|
|
// if unspecified, returns error.
|
|
GetResource() (Resource, error)
|
|
}
|
|
|
|
var (
|
|
// ResourceNameExpr is the identifier that specifies resource name.
|
|
ResourceNameExpr = builder.Identifier("resource.metadata.name")
|
|
// CertAuthorityTypeExpr is a function call that returns
|
|
// cert authority type.
|
|
CertAuthorityTypeExpr = builder.Identifier(`system.catype()`)
|
|
)
|
|
|
|
// NewWhereParser returns standard parser for `where` section in access rules.
|
|
func NewWhereParser(ctx RuleContext) (predicate.Parser, error) {
|
|
return predicate.NewParser(predicate.Def{
|
|
Operators: predicate.Operators{
|
|
AND: predicate.And,
|
|
OR: predicate.Or,
|
|
NOT: predicate.Not,
|
|
},
|
|
Functions: map[string]interface{}{
|
|
"equals": predicate.Equals,
|
|
"contains": predicate.Contains,
|
|
// system.catype is a function that returns cert authority type,
|
|
// it returns empty values for unrecognized values to
|
|
// pass static rule checks.
|
|
"system.catype": func() (interface{}, error) {
|
|
resource, err := ctx.GetResource()
|
|
if err != nil {
|
|
if trace.IsNotFound(err) {
|
|
return "", nil
|
|
}
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
ca, ok := resource.(CertAuthority)
|
|
if !ok {
|
|
return "", nil
|
|
}
|
|
return string(ca.GetType()), nil
|
|
},
|
|
},
|
|
GetIdentifier: ctx.GetIdentifier,
|
|
GetProperty: GetStringMapValue,
|
|
})
|
|
}
|
|
|
|
// GetStringMapValue is a helper function that returns property
|
|
// from map[string]string or map[string][]string
|
|
// the function returns empty value in case if key not found
|
|
// In case if map is nil, returns empty value as well
|
|
func GetStringMapValue(mapVal, keyVal interface{}) (interface{}, error) {
|
|
key, ok := keyVal.(string)
|
|
if !ok {
|
|
return nil, trace.BadParameter("only string keys are supported")
|
|
}
|
|
switch m := mapVal.(type) {
|
|
case map[string][]string:
|
|
if len(m) == 0 {
|
|
// to return nil with a proper type
|
|
var n []string
|
|
return n, nil
|
|
}
|
|
return m[key], nil
|
|
case wrappers.Traits:
|
|
if len(m) == 0 {
|
|
// to return nil with a proper type
|
|
var n []string
|
|
return n, nil
|
|
}
|
|
return m[key], nil
|
|
case map[string]string:
|
|
if len(m) == 0 {
|
|
return "", nil
|
|
}
|
|
return m[key], nil
|
|
default:
|
|
_, ok := mapVal.(map[string][]string)
|
|
return nil, trace.BadParameter("type %T is not supported, but %v %#v", m, ok, mapVal)
|
|
}
|
|
}
|
|
|
|
// NewActionsParser returns standard parser for 'actions' section in access rules
|
|
func NewActionsParser(ctx RuleContext) (predicate.Parser, error) {
|
|
return predicate.NewParser(predicate.Def{
|
|
Operators: predicate.Operators{},
|
|
Functions: map[string]interface{}{
|
|
"log": NewLogActionFn(ctx),
|
|
},
|
|
GetIdentifier: ctx.GetIdentifier,
|
|
GetProperty: predicate.GetStringMapValue,
|
|
})
|
|
}
|
|
|
|
// NewLogActionFn creates logger functions
|
|
func NewLogActionFn(ctx RuleContext) interface{} {
|
|
l := &LogAction{ctx: ctx}
|
|
writer, ok := ctx.(io.Writer)
|
|
if ok && writer != nil {
|
|
l.writer = writer
|
|
}
|
|
return l.Log
|
|
}
|
|
|
|
// LogAction represents action that will emit log entry
|
|
// when specified in the actions of a matched rule
|
|
type LogAction struct {
|
|
ctx RuleContext
|
|
writer io.Writer
|
|
}
|
|
|
|
// Log logs with specified level and formatting string with arguments
|
|
func (l *LogAction) Log(level, format string, args ...interface{}) predicate.BoolPredicate {
|
|
return func() bool {
|
|
ilevel, err := log.ParseLevel(level)
|
|
if err != nil {
|
|
ilevel = log.DebugLevel
|
|
}
|
|
var writer io.Writer
|
|
if l.writer != nil {
|
|
writer = l.writer
|
|
} else {
|
|
writer = log.StandardLogger().WriterLevel(ilevel)
|
|
}
|
|
writer.Write([]byte(fmt.Sprintf(format, args...)))
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Context is a default rule context used in teleport
|
|
type Context struct {
|
|
// User is currently authenticated user
|
|
User User
|
|
// Resource is an optional resource, in case if the rule
|
|
// checks access to the resource
|
|
Resource Resource
|
|
}
|
|
|
|
// String returns user friendly representation of this context
|
|
func (ctx *Context) String() string {
|
|
return fmt.Sprintf("user %v, resource: %v", ctx.User, ctx.Resource)
|
|
}
|
|
|
|
const (
|
|
// UserIdentifier represents user registered identifier in the rules
|
|
UserIdentifier = "user"
|
|
// ResourceIdentifier represents resource registered identifier in the rules
|
|
ResourceIdentifier = "resource"
|
|
)
|
|
|
|
// GetResource returns resource specified in the context,
|
|
// returns error if not specified.
|
|
func (ctx *Context) GetResource() (Resource, error) {
|
|
if ctx.Resource == nil {
|
|
return nil, trace.NotFound("resource is not set in the context")
|
|
}
|
|
return ctx.Resource, nil
|
|
}
|
|
|
|
// GetIdentifier returns identifier defined in a context
|
|
func (ctx *Context) GetIdentifier(fields []string) (interface{}, error) {
|
|
switch fields[0] {
|
|
case UserIdentifier:
|
|
var user User
|
|
if ctx.User == nil {
|
|
user = emptyUser
|
|
} else {
|
|
user = ctx.User
|
|
}
|
|
return predicate.GetFieldByTag(user, teleport.JSON, fields[1:])
|
|
case ResourceIdentifier:
|
|
var resource Resource
|
|
if ctx.Resource == nil {
|
|
resource = emptyResource
|
|
} else {
|
|
resource = ctx.Resource
|
|
}
|
|
return predicate.GetFieldByTag(resource, "json", fields[1:])
|
|
default:
|
|
return nil, trace.NotFound("%v is not defined", strings.Join(fields, "."))
|
|
}
|
|
}
|
|
|
|
// emptyResource is used when no resource is specified
|
|
var emptyResource = &EmptyResource{}
|
|
|
|
// emptyUser is used when no user is specified
|
|
var emptyUser = &UserV2{}
|
|
|
|
// EmptyResource is used to represent a use case when no resource
|
|
// is specified in the rules matcher
|
|
type EmptyResource struct {
|
|
// Kind is a resource kind
|
|
Kind string `json:"kind"`
|
|
// SubKind is a resource sub kind
|
|
SubKind string `json:"sub_kind,omitempty"`
|
|
// Version is a resource version
|
|
Version string `json:"version"`
|
|
// Metadata is Role metadata
|
|
Metadata Metadata `json:"metadata"`
|
|
}
|
|
|
|
// GetVersion returns resource version
|
|
func (r *EmptyResource) GetVersion() string {
|
|
return r.Version
|
|
}
|
|
|
|
// GetSubKind returns resource sub kind
|
|
func (r *EmptyResource) GetSubKind() string {
|
|
return r.SubKind
|
|
}
|
|
|
|
// SetSubKind sets resource subkind
|
|
func (r *EmptyResource) SetSubKind(s string) {
|
|
r.SubKind = s
|
|
}
|
|
|
|
// GetKind returns resource kind
|
|
func (r *EmptyResource) GetKind() string {
|
|
return r.Kind
|
|
}
|
|
|
|
// GetResourceID returns resource ID
|
|
func (r *EmptyResource) GetResourceID() int64 {
|
|
return r.Metadata.ID
|
|
}
|
|
|
|
// SetResourceID sets resource ID
|
|
func (r *EmptyResource) SetResourceID(id int64) {
|
|
r.Metadata.ID = id
|
|
}
|
|
|
|
// SetExpiry sets expiry time for the object.
|
|
func (r *EmptyResource) SetExpiry(expires time.Time) {
|
|
r.Metadata.SetExpiry(expires)
|
|
}
|
|
|
|
// Expiry returns the expiry time for the object.
|
|
func (r *EmptyResource) Expiry() time.Time {
|
|
return r.Metadata.Expiry()
|
|
}
|
|
|
|
// SetTTL sets TTL header using realtime clock.
|
|
func (r *EmptyResource) SetTTL(clock clockwork.Clock, ttl time.Duration) {
|
|
r.Metadata.SetTTL(clock, ttl)
|
|
}
|
|
|
|
// SetName sets the role name and is a shortcut for SetMetadata().Name.
|
|
func (r *EmptyResource) SetName(s string) {
|
|
r.Metadata.Name = s
|
|
}
|
|
|
|
// GetName gets the role name and is a shortcut for GetMetadata().Name.
|
|
func (r *EmptyResource) GetName() string {
|
|
return r.Metadata.Name
|
|
}
|
|
|
|
// GetMetadata returns role metadata.
|
|
func (r *EmptyResource) GetMetadata() Metadata {
|
|
return r.Metadata
|
|
}
|