1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-01 07:27:46 +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

@@ -9,18 +9,18 @@ import (
"testing"
"time"
"github.com/rancher/wrangler/v3/pkg/schemas/validation"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"github.com/rancher/steve/pkg/accesscontrol"
"github.com/rancher/steve/pkg/attributes"
"github.com/rancher/steve/pkg/resources/common"
"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/sqlpartition/listprocessor"
"github.com/rancher/steve/pkg/stores/sqlproxy/tablelistconvert"
"github.com/rancher/wrangler/v3/pkg/schemas/validation"
"go.uber.org/mock/gomock"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/pkg/errors"
"github.com/rancher/apiserver/pkg/apierror"
@@ -29,10 +29,14 @@ import (
"github.com/rancher/wrangler/v3/pkg/schemas"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/authentication/user"
krequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/rest"
@@ -621,6 +625,118 @@ func TestListByPartitions(t *testing.T) {
}
}
func TestListByPartitionWithUserAccess(t *testing.T) {
type testCase struct {
description string
accessSetSetter func(accessSet *accesscontrol.AccessSet)
orFilters []sqltypes.OrFilter
}
var tests []testCase
tests = append(tests, testCase{
description: "client ListByPartitions(), with a specified user for a restricted resource should filter for that user",
accessSetSetter: func(accessSet *accesscontrol.AccessSet) {},
orFilters: []sqltypes.OrFilter{
{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "labels", "cattle.io/user-id"},
Matches: []string{"flip"},
Op: sqltypes.Eq,
},
},
},
},
})
tests = append(tests, testCase{
description: "client ListByPartitions(), with an admin user for a restricted resource will return all items regardless of user",
accessSetSetter: func(accessSet *accesscontrol.AccessSet) {
// admins also get this access-set
accessSet.Add("list",
schema2.GroupResource{Group: accesscontrol.All, Resource: accesscontrol.All},
accesscontrol.Access{Namespace: accesscontrol.All, ResourceName: accesscontrol.All},
)
},
orFilters: []sqltypes.OrFilter{},
})
t.Parallel()
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
nsi := NewMockCache(gomock.NewController(t))
cg := NewMockClientGetter(gomock.NewController(t))
cf := NewMockCacheFactory(gomock.NewController(t))
ri := NewMockResourceInterface(gomock.NewController(t))
bloi := NewMockByOptionsLister(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
inf := &informer.Informer{
ByOptionsLister: bloi,
}
c := factory.Cache{
ByOptionsLister: inf,
}
s := &Store{
ctx: context.Background(),
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
transformBuilder: tb,
}
var partitions []partition.Partition
username := "flip"
targetGroup := "ext.cattle.io"
targetKind := "Token"
accessSet := &accesscontrol.AccessSet{ID: username}
accessSet.Add("list",
schema2.GroupResource{Group: targetGroup, Resource: "token"},
accesscontrol.Access{Namespace: accesscontrol.All, ResourceName: "token"},
)
test.accessSetSetter(accessSet)
apiOpSchemas := &types.APISchemas{}
accesscontrol.SetAccessSetAttribute(apiOpSchemas, accessSet)
theRequest := &http.Request{
URL: &url.URL{},
}
userInfo := user.DefaultInfo{Name: username, UID: "Id"}
requestWithContext := krequest.WithUser(context.Background(), &userInfo)
theRequest = theRequest.WithContext(requestWithContext)
apiOp := &types.APIRequest{
Request: theRequest,
Schemas: apiOpSchemas,
}
theSchema := &types.APISchema{
Schema: &schemas.Schema{Attributes: map[string]interface{}{
"columns": []common.ColumnDefinition{
{
Field: "some.field",
},
},
"verbs": []string{"list", "watch"},
}},
}
gvk := schema2.GroupVersionKind{
Group: targetGroup,
Kind: targetKind,
}
opts := &sqltypes.ListOptions{
Filters: test.orFilters,
Pagination: sqltypes.Pagination{
Page: 1,
},
}
attributes.SetGVK(theSchema, gvk)
cg.EXPECT().TableAdminClient(apiOp, theSchema, "", &WarningBuffer{}).Return(ri, nil)
cf.EXPECT().CacheFor(context.Background(), [][]string{{"some", "field"}, {"id"}, {"metadata", "state", "name"}}, gomock.Any(), &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(theSchema), attributes.Namespaced(theSchema), true).Return(c, nil)
tb.EXPECT().GetTransformFunc(attributes.GVK(theSchema)).Return(func(obj interface{}) (interface{}, error) { return obj, nil })
listToReturn := &unstructured.UnstructuredList{
Items: make([]unstructured.Unstructured, 0, 0),
}
bloi.EXPECT().ListByOptions(apiOp.Context(), opts, partitions, "").Return(listToReturn, len(listToReturn.Items), "", nil)
_, _, _, err := s.ListByPartitions(apiOp, theSchema, partitions)
assert.Nil(t, err)
})
}
}
func TestReset(t *testing.T) {
type testCase struct {
description string