webhook/pkg/auth/escalation_test.go

242 lines
6.9 KiB
Go

package auth_test
import (
"context"
"errors"
"reflect"
"testing"
"github.com/rancher/webhook/pkg/admission"
"github.com/rancher/webhook/pkg/auth"
"github.com/stretchr/testify/suite"
v1 "k8s.io/api/admission/v1"
authenticationv1 "k8s.io/api/authentication/v1"
authorizationv1 "k8s.io/api/authorization/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
k8fake "k8s.io/client-go/kubernetes/typed/authorization/v1/fake"
k8testing "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/registry/rbac/validation"
)
var errExpected = errors.New("expected test error")
type EscalationSuite struct {
suite.Suite
ruleReadPods rbacv1.PolicyRule
ruleReadServices rbacv1.PolicyRule
ruleWriteNodes rbacv1.PolicyRule
ruleAdmin rbacv1.PolicyRule
adminCR *rbacv1.ClusterRole
writeNodeCR *rbacv1.ClusterRole
readServiceRole *rbacv1.Role
}
func TestEscalation(t *testing.T) {
suite.Run(t, new(EscalationSuite))
}
func (e *EscalationSuite) SetupSuite() {
e.ruleReadPods = rbacv1.PolicyRule{
Verbs: []string{"GET", "WATCH"},
APIGroups: []string{"v1"},
Resources: []string{"pods"},
}
e.ruleReadServices = rbacv1.PolicyRule{
Verbs: []string{"GET", "WATCH"},
APIGroups: []string{"v1"},
Resources: []string{"services"},
}
e.ruleWriteNodes = rbacv1.PolicyRule{
Verbs: []string{"PUT", "CREATE", "UPDATE"},
APIGroups: []string{"v1"},
Resources: []string{"nodes"},
}
e.ruleAdmin = rbacv1.PolicyRule{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"*"},
}
e.adminCR = &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: "admin-role",
},
Rules: []rbacv1.PolicyRule{e.ruleAdmin},
}
e.writeNodeCR = &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "write-role"},
Rules: []rbacv1.PolicyRule{e.ruleWriteNodes, e.ruleReadPods},
}
e.readServiceRole = &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "read-service"},
Rules: []rbacv1.PolicyRule{e.ruleReadServices},
}
}
func (e *EscalationSuite) newDefaultRequest(userName string) *admission.Request {
return &admission.Request{
AdmissionRequest: v1.AdmissionRequest{
UID: "1",
Name: "default",
Namespace: "namespace1",
Operation: v1.Create,
UserInfo: authenticationv1.UserInfo{
Username: userName,
UID: "u-1",
Extra: map[string]authenticationv1.ExtraValue{"extra": []string{"v1", "v2"}},
},
},
Context: context.Background(),
}
}
func (e *EscalationSuite) TestConfirmNoEscalation() {
const adminUser = "admin-user"
const testUser = "test-user"
roles := []*rbacv1.Role{e.readServiceRole}
clusterRoles := []*rbacv1.ClusterRole{e.adminCR}
roleBindings := []*rbacv1.RoleBinding{
{
ObjectMeta: metav1.ObjectMeta{Namespace: e.readServiceRole.Namespace},
Subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: testUser},
},
RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "Role", Name: e.readServiceRole.Name},
},
}
clusterRoleBindings := []*rbacv1.ClusterRoleBinding{
{
Subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: adminUser},
},
RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: e.adminCR.Name},
},
}
resolver, _ := validation.NewTestRuleResolver(roles, roleBindings, clusterRoles, clusterRoleBindings)
type args struct {
request *admission.Request
rules []rbacv1.PolicyRule
}
tests := []struct {
name string
args args
wantErr bool
}{
// No escalation occurred
{
name: "Admin no escalation",
args: args{
request: e.newDefaultRequest(adminUser),
rules: []rbacv1.PolicyRule{e.ruleReadPods, e.ruleReadServices, e.ruleWriteNodes},
},
},
{
name: "testUser denied escalation",
args: args{
request: e.newDefaultRequest(testUser),
rules: []rbacv1.PolicyRule{e.ruleReadPods, e.ruleReadServices, e.ruleWriteNodes},
},
wantErr: true,
},
// Denied Escalation attemptß
}
for i := range tests {
test := tests[i]
e.Run(test.name, func() {
err := auth.ConfirmNoEscalation(test.args.request, test.args.rules, test.args.request.Namespace, resolver)
resp := &v1.AdmissionResponse{
UID: test.args.request.UID,
Allowed: false,
Result: &metav1.Status{},
}
auth.SetEscalationResponse(resp, err)
if test.wantErr {
e.Error(err, "expected tests to have error.")
e.False(resp.Allowed, "Response allowed incorrectly set to true.")
e.NotEmpty(resp.Result.Message, "Response message was not set.")
e.NotEmpty(resp.Result.Status, "Response status was not set.")
} else {
e.NoError(err, "unexpected error in test.")
e.True(resp.Allowed, "Response allowed incorrectly set to false")
}
})
}
}
func (e *EscalationSuite) TestRequestUserHasVerb() {
gvr := schema.GroupVersionResource{
Group: "management.cattle.io",
Version: "v3",
Resource: "roletemplates",
}
const namespace = "namespace1"
const testUser = "testUser"
const unknownUser = "unknownUser"
const errorUser = "errorUser"
goodRequest := e.newDefaultRequest(testUser)
k8Fake := &k8testing.Fake{}
fakeAuth := &k8fake.FakeAuthorizationV1{Fake: k8Fake}
fakeSAR := fakeAuth.SubjectAccessReviews()
k8Fake.AddReactor("create", "subjectaccessreviews", func(action k8testing.Action) (handled bool, ret runtime.Object, err error) {
createAction := action.(k8testing.CreateActionImpl)
review := createAction.GetObject().(*authorizationv1.SubjectAccessReview)
spec := review.Spec
if spec.User == errorUser {
return true, nil, errExpected
}
review.Status.Allowed = spec.User == testUser &&
spec.UID == goodRequest.UserInfo.UID &&
reflect.DeepEqual(spec.Groups, goodRequest.UserInfo.Groups) &&
spec.ResourceAttributes.Version == gvr.Version &&
spec.ResourceAttributes.Group == gvr.Group &&
spec.ResourceAttributes.Resource == gvr.Resource &&
spec.ResourceAttributes.Namespace == namespace &&
spec.ResourceAttributes.Verb == "escalate" &&
spec.ResourceAttributes.Name == "test-resource"
return true, review, nil
})
tests := []struct {
name string
request *admission.Request
want bool
wantErr bool
}{
{
name: "escalate verb present",
request: goodRequest,
want: true,
wantErr: false,
},
{
name: "escalate verb not present",
request: e.newDefaultRequest(unknownUser),
want: false,
wantErr: false,
},
{
name: "escalate check error",
request: e.newDefaultRequest(errorUser),
want: false,
wantErr: true,
},
}
for i := range tests {
test := tests[i]
e.Run(test.name, func() {
got, err := auth.RequestUserHasVerb(test.request, gvr, fakeSAR, "escalate", "test-resource", namespace)
if test.wantErr {
e.Error(err, "expected tests to have error.")
} else {
e.NoError(err, "unexpected error in test.")
}
if got != test.want {
e.Fail("Incorrect response allowed result", "got=%s wanted=%s", got, test.want)
}
})
}
}