1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-15 06:49:27 +00:00

Add vai access-control for ext Tokens and Kubeconfigs (#651)

* Add vai access-control for tokens.

* Check for both Token and Kubeconfig resources

* Add a unit test for verifying the generated filters for restricted resources.

* Remove a TODO comment as Tom points out we no longer need it.

* Return error if we can't get userinfo from apiOp.Request.Context

* Stop using camelCase for the user ID label.

* Add a test for the admin user.

* And fold the two user-access tests into a single parameterized test.

* Address reviewer comments.

* post-rebase merge fixes

* WIP - add a comment about determining admin users.

---------

Co-authored-by: Peter Matseykanets <peter.matseykanets@suse.com>
This commit is contained in:
Eric Promislow
2025-06-10 14:28:49 -07:00
committed by GitHub
parent f258ebcf31
commit 7db113a1fd
2 changed files with 168 additions and 28 deletions

View File

@@ -13,34 +13,21 @@ import (
"sync"
"github.com/pkg/errors"
"github.com/rancher/steve/pkg/stores/queryhelper"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"github.com/rancher/apiserver/pkg/apierror"
"github.com/rancher/apiserver/pkg/types"
"github.com/rancher/steve/pkg/accesscontrol"
"github.com/rancher/steve/pkg/sqlcache/informer"
"github.com/rancher/steve/pkg/sqlcache/informer/factory"
"github.com/rancher/steve/pkg/sqlcache/partition"
"github.com/rancher/steve/pkg/sqlcache/sqltypes"
"github.com/rancher/steve/pkg/stores/queryhelper"
"github.com/rancher/wrangler/v3/pkg/data"
"github.com/rancher/wrangler/v3/pkg/kv"
"github.com/rancher/wrangler/v3/pkg/schemas"
"github.com/rancher/wrangler/v3/pkg/schemas/validation"
"github.com/rancher/wrangler/v3/pkg/summary"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"github.com/rancher/steve/pkg/attributes"
controllerschema "github.com/rancher/steve/pkg/controllers/schema"
@@ -50,6 +37,21 @@ import (
metricsStore "github.com/rancher/steve/pkg/stores/metrics"
"github.com/rancher/steve/pkg/stores/sqlpartition/listprocessor"
"github.com/rancher/steve/pkg/stores/sqlproxy/tablelistconvert"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
)
const (
@@ -738,28 +740,50 @@ func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id stri
// - the total number of resources (returned list might be a subset depending on pagination options in apiOp)
// - a continue token, if there are more pages after the returned one
// - an error instead of all of the above if anything went wrong
func (s *Store) ListByPartitions(apiOp *types.APIRequest, schema *types.APISchema, partitions []partition.Partition) (*unstructured.UnstructuredList, int, string, error) {
func (s *Store) ListByPartitions(apiOp *types.APIRequest, apiSchema *types.APISchema, partitions []partition.Partition) (*unstructured.UnstructuredList, int, string, error) {
opts, err := listprocessor.ParseQuery(apiOp, s.namespaceCache)
if err != nil {
return nil, 0, "", err
}
// warnings from inside the informer are discarded
buffer := WarningBuffer{}
client, err := s.clientGetter.TableAdminClient(apiOp, schema, "", &buffer)
client, err := s.clientGetter.TableAdminClient(apiOp, apiSchema, "", &buffer)
if err != nil {
return nil, 0, "", err
}
gvk := attributes.GVK(schema)
fields := getFieldsFromSchema(schema)
gvk := attributes.GVK(apiSchema)
fields := getFieldsFromSchema(apiSchema)
fields = append(fields, getFieldForGVK(gvk)...)
transformFunc := s.transformBuilder.GetTransformFunc(gvk)
tableClient := &tablelistconvert.Client{ResourceInterface: client}
attrs := attributes.GVK(schema)
ns := attributes.Namespaced(schema)
inf, err := s.cacheFactory.CacheFor(s.ctx, fields, transformFunc, tableClient, attrs, ns, controllerschema.IsListWatchable(schema))
attrs := attributes.GVK(apiSchema)
ns := attributes.Namespaced(apiSchema)
inf, err := s.cacheFactory.CacheFor(s.ctx, fields, transformFunc, tableClient, attrs, ns, controllerschema.IsListWatchable(apiSchema))
if err != nil {
return nil, 0, "", err
}
if gvk.Group == "ext.cattle.io" && (gvk.Kind == "Token" || gvk.Kind == "Kubeconfig") {
accessSet := accesscontrol.AccessSetFromAPIRequest(apiOp)
// See https://github.com/rancher/rancher/blob/7266e5e624f0d610c76ab0af33e30f5b72e11f61/pkg/ext/stores/tokens/tokens.go#L1186C2-L1195C3
// for similar code on how we determine if a user is admin
if accessSet == nil || !accessSet.Grants("list", schema.GroupResource{
Resource: "*",
}, "", "") {
user, ok := request.UserFrom(apiOp.Request.Context())
if !ok {
return nil, 0, "", apierror.NewAPIError(validation.MissingRequired, "failed to get user info from the request.Context object")
}
opts.Filters = append(opts.Filters, sqltypes.OrFilter{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "labels", "cattle.io/user-id"},
Matches: []string{user.GetName()},
Op: sqltypes.Eq,
},
},
})
}
}
list, total, continueToken, err := inf.ListByOptions(apiOp.Context(), &opts, partitions, apiOp.Namespace)
if err != nil {