1
0
mirror of https://github.com/rancher/steve.git synced 2025-06-28 15:57:24 +00:00
steve/pkg/stores/proxy/rbac_store.go

171 lines
4.9 KiB
Go
Raw Normal View History

2020-02-10 17:18:20 +00:00
package proxy
import (
2020-06-05 20:30:33 +00:00
"fmt"
2020-02-10 17:18:20 +00:00
"sort"
"github.com/rancher/apiserver/pkg/types"
2020-02-10 17:18:20 +00:00
"github.com/rancher/steve/pkg/accesscontrol"
"github.com/rancher/steve/pkg/attributes"
"github.com/rancher/steve/pkg/stores/partition"
2020-06-22 16:42:18 +00:00
"github.com/rancher/wrangler/pkg/kv"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2020-02-10 17:18:20 +00:00
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch"
2020-02-10 17:18:20 +00:00
)
2020-06-05 20:30:33 +00:00
var (
passthroughPartitions = []partition.Partition{
Partition{Passthrough: true},
}
)
// Partition is an implementation of the partition.Partition interface that uses RBAC to determine how a set of resources should be segregated and accessed.
2020-06-05 20:30:33 +00:00
type Partition struct {
Namespace string
All bool
Passthrough bool
Names sets.String
}
// Name returns the name of the partition, which for this type is the namespace.
2020-06-05 20:30:33 +00:00
func (p Partition) Name() string {
return p.Namespace
}
// rbacPartitioner is an implementation of the partition.Partioner interface.
2020-06-05 20:30:33 +00:00
type rbacPartitioner struct {
proxyStore *Store
}
// Lookup returns the default passthrough partition which is used only for retrieving single resources.
// Listing or watching resources require custom partitions.
2020-06-05 20:30:33 +00:00
func (p *rbacPartitioner) Lookup(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (partition.Partition, error) {
switch verb {
2020-06-12 21:18:13 +00:00
case "create":
fallthrough
2020-06-05 20:30:33 +00:00
case "get":
fallthrough
case "update":
fallthrough
case "delete":
return passthroughPartitions[0], nil
default:
2020-06-12 21:18:13 +00:00
return nil, fmt.Errorf("partition list: invalid verb %s", verb)
2020-06-05 20:30:33 +00:00
}
}
// All returns a slice of partitions applicable to the API schema and the user's access level.
// For watching individual resources or for blanket access permissions, it returns the passthrough partition.
// For more granular permissions, it returns a slice of partitions matching an allowed namespace or resource names.
2020-06-22 16:42:18 +00:00
func (p *rbacPartitioner) All(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) ([]partition.Partition, error) {
2020-06-05 20:30:33 +00:00
switch verb {
case "list":
fallthrough
case "watch":
2020-06-22 16:42:18 +00:00
if id != "" {
ns, name := kv.RSplit(id, "/")
return []partition.Partition{
Partition{
Namespace: ns,
All: false,
Passthrough: false,
Names: sets.NewString(name),
},
}, nil
}
2020-06-05 20:30:33 +00:00
partitions, passthrough := isPassthrough(apiOp, schema, verb)
if passthrough {
return passthroughPartitions, nil
}
sort.Slice(partitions, func(i, j int) bool {
return partitions[i].(Partition).Namespace < partitions[j].(Partition).Namespace
})
return partitions, nil
default:
2020-06-12 21:18:13 +00:00
return nil, fmt.Errorf("parition all: invalid verb %s", verb)
2020-06-05 20:30:33 +00:00
}
}
// Store returns an UnstructuredStore suited to listing and watching resources by partition.
func (p *rbacPartitioner) Store(apiOp *types.APIRequest, partition partition.Partition) (partition.UnstructuredStore, error) {
2020-06-05 20:30:33 +00:00
return &byNameOrNamespaceStore{
Store: p.proxyStore,
partition: partition.(Partition),
}, nil
}
type byNameOrNamespaceStore struct {
2020-02-10 17:18:20 +00:00
*Store
2020-06-05 20:30:33 +00:00
partition Partition
2020-02-10 17:18:20 +00:00
}
// List returns a list of resources by partition.
func (b *byNameOrNamespaceStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) {
2020-06-05 20:30:33 +00:00
if b.partition.Passthrough {
return b.Store.List(apiOp, schema)
}
apiOp.Namespace = b.partition.Namespace
if b.partition.All {
return b.Store.List(apiOp, schema)
}
return b.Store.ByNames(apiOp, schema, b.partition.Names)
2020-02-10 17:18:20 +00:00
}
// Watch returns a channel of resources by partition.
func (b *byNameOrNamespaceStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan watch.Event, error) {
2020-06-05 20:30:33 +00:00
if b.partition.Passthrough {
return b.Store.Watch(apiOp, schema, wr)
}
apiOp.Namespace = b.partition.Namespace
if b.partition.All {
return b.Store.Watch(apiOp, schema, wr)
}
return b.Store.WatchNames(apiOp, schema, wr, b.partition.Names)
}
// isPassthrough determines whether a request can be passed through directly to the underlying store
// or if the results need to be partitioned by namespace and name based on the requester's access.
2020-06-05 20:30:33 +00:00
func isPassthrough(apiOp *types.APIRequest, schema *types.APISchema, verb string) ([]partition.Partition, bool) {
2020-02-10 17:18:20 +00:00
accessListByVerb, _ := attributes.Access(schema).(accesscontrol.AccessListByVerb)
if accessListByVerb.All(verb) {
return nil, true
}
resources := accessListByVerb.Granted(verb)
if apiOp.Namespace != "" {
if resources[apiOp.Namespace].All {
return nil, true
}
return []partition.Partition{
Partition{
Namespace: apiOp.Namespace,
Names: resources[apiOp.Namespace].Names,
},
}, false
2020-02-10 17:18:20 +00:00
}
2020-06-05 20:30:33 +00:00
var result []partition.Partition
2020-02-10 17:18:20 +00:00
if attributes.Namespaced(schema) {
for k, v := range resources {
result = append(result, Partition{
Namespace: k,
All: v.All,
Names: v.Names,
})
}
} else {
for _, v := range resources {
result = append(result, Partition{
All: v.All,
Names: v.Names,
})
}
}
return result, false
}