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"
2020-06-12 04:50:59 +00:00
"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"
2020-06-12 04:50:59 +00:00
"github.com/rancher/steve/pkg/stores/partition"
2020-06-22 16:42:18 +00:00
"github.com/rancher/wrangler/pkg/kv"
2022-10-21 23:41:08 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2020-02-10 17:18:20 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2022-10-21 23:41:08 +00:00
"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 } ,
}
)
2022-10-14 23:17:32 +00:00
// 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
}
2022-10-14 23:17:32 +00:00
// 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
}
2022-10-14 23:17:32 +00:00
// rbacPartitioner is an implementation of the partition.Partioner interface.
2020-06-05 20:30:33 +00:00
type rbacPartitioner struct {
proxyStore * Store
}
2022-10-14 23:17:32 +00:00
// 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
}
}
2022-10-14 23:17:32 +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
}
}
2022-10-21 23:41:08 +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
}
2022-10-14 23:17:32 +00:00
// List returns a list of resources by partition.
2022-10-21 23:41:08 +00:00
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
}
2022-10-14 23:17:32 +00:00
// Watch returns a channel of resources by partition.
2022-10-21 23:41:08 +00:00
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 )
}
2022-10-11 19:28:51 +00:00
// 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
}
2022-10-14 20:21:17 +00:00
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
}