1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-09 03:09:50 +00:00

#47483 - Adding NonResourceURLs support to AccessStore (#299)

* adding NonResourceURLs support to access_store

* added tests to AccessSet NonResourceURLs handling

* change on test script suggested by @tomleb + go mod tidy

* added nonresource to ext api authorization

* added NonResourceURLs implementation in Authorizes + test

* removed non-resource-url tests from the main test

* added new tests for non-resource-urls

* removed unused test data

* changed nonResourceKey to point to struct{}

* addressed comments from @tomleb

* addressed more comments

* fixing typo

* check for empty accessSet
This commit is contained in:
Felipe Gehrke
2024-11-04 23:47:48 -03:00
committed by GitHub
parent 2175e090fe
commit 6ee8201c8d
10 changed files with 588 additions and 39 deletions

View File

@@ -2,6 +2,7 @@ package ext
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
@@ -15,9 +16,11 @@ import (
"github.com/rancher/lasso/pkg/controller"
"github.com/rancher/steve/pkg/accesscontrol"
"github.com/rancher/steve/pkg/accesscontrol/fake"
wrbacv1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/rbac/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -311,6 +314,66 @@ func (s *ExtensionAPIServerSuite) TestAuthorization() {
},
expectedStatusCode: http.StatusForbidden,
},
{
name: "authorized access to non-resource url",
user: &user.DefaultInfo{
Name: "openapi-v2-only",
},
createRequest: func() *http.Request {
return httptest.NewRequest(http.MethodGet, "/openapi/v2", nil)
},
expectedStatusCode: http.StatusOK,
},
{
name: "unauthorized verb to non-resource url",
user: &user.DefaultInfo{
Name: "openapi-v2-only",
},
createRequest: func() *http.Request {
return httptest.NewRequest(http.MethodPost, "/openapi/v2", nil)
},
expectedStatusCode: http.StatusForbidden,
},
{
name: "unauthorized access to non-resource url (user can access only openapi/v2)",
user: &user.DefaultInfo{
Name: "openapi-v2-only",
},
createRequest: func() *http.Request {
return httptest.NewRequest(http.MethodGet, "/openapi/v3", nil)
},
expectedStatusCode: http.StatusForbidden,
},
{
name: "authorized user can access both openapi v2 and v3 (v2)",
user: &user.DefaultInfo{
Name: "openapi-v2-v3",
},
createRequest: func() *http.Request {
return httptest.NewRequest(http.MethodGet, "/openapi/v2", nil)
},
expectedStatusCode: http.StatusOK,
},
{
name: "authorized user can access both openapi v2 and v3 (v3)",
user: &user.DefaultInfo{
Name: "openapi-v2-v3",
},
createRequest: func() *http.Request {
return httptest.NewRequest(http.MethodGet, "/openapi/v3", nil)
},
expectedStatusCode: http.StatusOK,
},
{
name: "authorized user can access url based in wildcard rule",
user: &user.DefaultInfo{
Name: "openapi-v2-v3",
},
createRequest: func() *http.Request {
return httptest.NewRequest(http.MethodGet, "/openapi/v3/apis/ext.cattle.io/v1", nil)
},
expectedStatusCode: http.StatusOK,
},
}
for _, test := range tests {
@@ -342,3 +405,152 @@ func (s *ExtensionAPIServerSuite) TestAuthorization() {
})
}
}
func TestAuthorization_NonResourceURLs(t *testing.T) {
type input struct {
ctx context.Context
attrs authorizer.Attributes
}
type expected struct {
authorized authorizer.Decision
reason string
err error
}
sampleReadOnlyUser := &user.DefaultInfo{
Name: "read-only-user",
}
sampleReadOnlyAccessSet := func() *accesscontrol.AccessSet {
accessSet := &accesscontrol.AccessSet{}
accessSet.AddNonResourceURLs([]string{
"get",
}, []string{
"/metrics",
"/healthz",
})
return accessSet
}()
sampleReadWriteUser := &user.DefaultInfo{
Name: "read-write-user",
}
sampleReadWriteAccessSet := func() *accesscontrol.AccessSet {
accessSet := &accesscontrol.AccessSet{}
accessSet.AddNonResourceURLs([]string{
"get", "post",
}, []string{
"/metrics",
"/healthz",
})
return accessSet
}()
tests := []struct {
name string
input input
expected expected
mockUsername *user.DefaultInfo
mockAccessSet *accesscontrol.AccessSet
}{
{
name: "authorized read-only user to read data",
input: input{
ctx: context.TODO(),
attrs: authorizer.AttributesRecord{
User: sampleReadOnlyUser,
ResourceRequest: false,
Path: "/healthz",
Verb: "get",
},
},
expected: expected{
authorized: authorizer.DecisionAllow,
reason: "",
err: nil,
},
mockUsername: sampleReadOnlyUser,
mockAccessSet: sampleReadOnlyAccessSet,
},
{
name: "unauthorized read-only user to write data",
input: input{
ctx: context.TODO(),
attrs: authorizer.AttributesRecord{
User: sampleReadOnlyUser,
ResourceRequest: false,
Path: "/metrics",
Verb: "post",
},
},
expected: expected{
authorized: authorizer.DecisionDeny,
reason: "",
err: nil,
},
mockUsername: sampleReadOnlyUser,
mockAccessSet: sampleReadOnlyAccessSet,
},
{
name: "authorized read-write user to read data",
input: input{
ctx: context.TODO(),
attrs: authorizer.AttributesRecord{
User: sampleReadWriteUser,
ResourceRequest: false,
Path: "/metrics",
Verb: "get",
},
},
expected: expected{
authorized: authorizer.DecisionAllow,
reason: "",
err: nil,
},
mockUsername: sampleReadWriteUser,
mockAccessSet: sampleReadWriteAccessSet,
},
{
name: "authorized read-write user to write data",
input: input{
ctx: context.TODO(),
attrs: authorizer.AttributesRecord{
User: sampleReadWriteUser,
ResourceRequest: false,
Path: "/metrics",
Verb: "post",
},
},
expected: expected{
authorized: authorizer.DecisionAllow,
reason: "",
err: nil,
},
mockUsername: sampleReadWriteUser,
mockAccessSet: sampleReadWriteAccessSet,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
crtl := gomock.NewController(t)
asl := fake.NewMockAccessSetLookup(crtl)
asl.EXPECT().AccessFor(tt.mockUsername).Return(tt.mockAccessSet)
auth := NewAccessSetAuthorizer(asl)
authorized, reason, err := auth.Authorize(tt.input.ctx, tt.input.attrs)
require.Equal(t, tt.expected.authorized, authorized)
require.Equal(t, tt.expected.reason, reason)
if tt.expected.err != nil {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}