Skip to content

Commit d76eae4

Browse files
committed
SSA: add integration test to exercise authz
Signed-off-by: Monis Khan <[email protected]>
1 parent dbc2b0a commit d76eae4

File tree

1 file changed

+120
-1
lines changed

1 file changed

+120
-1
lines changed

test/integration/apiserver/apply/apply_test.go

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@ import (
3030

3131
"github.com/google/go-cmp/cmp"
3232
"github.com/stretchr/testify/require"
33-
"sigs.k8s.io/yaml"
3433

3534
appsv1 "k8s.io/api/apps/v1"
3635
v1 "k8s.io/api/core/v1"
36+
rbacv1 "k8s.io/api/rbac/v1"
3737
apierrors "k8s.io/apimachinery/pkg/api/errors"
3838
"k8s.io/apimachinery/pkg/api/meta"
3939
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4040
"k8s.io/apimachinery/pkg/runtime"
41+
"k8s.io/apimachinery/pkg/runtime/schema"
4142
"k8s.io/apimachinery/pkg/types"
4243
"k8s.io/apimachinery/pkg/util/intstr"
4344
"k8s.io/apimachinery/pkg/util/wait"
@@ -49,7 +50,9 @@ import (
4950
"k8s.io/client-go/kubernetes/fake"
5051
restclient "k8s.io/client-go/rest"
5152
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
53+
"k8s.io/kubernetes/test/integration/authutil"
5254
"k8s.io/kubernetes/test/integration/framework"
55+
"sigs.k8s.io/yaml"
5356
)
5457

5558
func setup(t testing.TB) (clientset.Interface, kubeapiservertesting.TearDownFunc) {
@@ -4780,3 +4783,119 @@ func expectManagedFields(t *testing.T, managedFields []metav1.ManagedFieldsEntry
47804783
t.Fatalf("Want:\n%s\nGot:\n%s\nDiff:\n%s", string(want), string(got), diff)
47814784
}
47824785
}
4786+
4787+
// TestCreateOnApplyFailsWithForbidden makes sure that PATCH requests with the apply content type
4788+
// will not create the object if the user does not have both patch and create permissions.
4789+
func TestCreateOnApplyFailsWithForbidden(t *testing.T) {
4790+
// Enable RBAC so we can exercise authorization errors.
4791+
server := kubeapiservertesting.StartTestServerOrDie(t, nil, append([]string{"--authorization-mode=RBAC"}, framework.DefaultTestServerFlags()...), framework.SharedEtcd())
4792+
t.Cleanup(server.TearDownFn)
4793+
4794+
adminClient := clientset.NewForConfigOrDie(server.ClientConfig)
4795+
4796+
pandaConfig := restclient.CopyConfig(server.ClientConfig)
4797+
pandaConfig.Impersonate.UserName = "panda"
4798+
pandaClient := clientset.NewForConfigOrDie(pandaConfig)
4799+
4800+
errPatch := ssaPod(pandaClient)
4801+
4802+
requireForbiddenPodErr(t, errPatch, `pods "test-pod" is forbidden: User "panda" cannot patch resource "pods" in API group "" in the namespace "default"`)
4803+
4804+
createPodRBACAndWait(t, adminClient, "patch")
4805+
4806+
errCreate := ssaPod(pandaClient)
4807+
4808+
requireForbiddenPodErr(t, errCreate, `pods "test-pod" is forbidden: `) // TODO make this error better
4809+
4810+
createPodRBACAndWait(t, adminClient, "create")
4811+
4812+
errNone := ssaPod(pandaClient)
4813+
require.NoError(t, errNone, "pod create via SSA should succeed now that RBAC is correct")
4814+
}
4815+
4816+
func requireForbiddenPodErr(t *testing.T, err error, message string) {
4817+
t.Helper()
4818+
4819+
require.Truef(t, apierrors.IsForbidden(err), "Expected forbidden error but got: %v", err)
4820+
4821+
wantStatusErr := &apierrors.StatusError{ErrStatus: metav1.Status{
4822+
Status: "Failure",
4823+
Message: message,
4824+
Reason: "Forbidden",
4825+
Details: &metav1.StatusDetails{
4826+
Name: "test-pod",
4827+
Kind: "pods",
4828+
},
4829+
Code: http.StatusForbidden,
4830+
}}
4831+
require.Equal(t, wantStatusErr, err, "unexpected status error")
4832+
}
4833+
4834+
func ssaPod(client *clientset.Clientset) error {
4835+
_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
4836+
Namespace("default").
4837+
Resource("pods").
4838+
Name("test-pod").
4839+
Param("fieldManager", "apply_test").
4840+
Body([]byte(`{
4841+
"apiVersion": "v1",
4842+
"kind": "Pod",
4843+
"metadata": {
4844+
"name": "test-pod"
4845+
},
4846+
"spec": {
4847+
"containers": [{
4848+
"name": "test-container",
4849+
"image": "test-image"
4850+
}]
4851+
}
4852+
}`)).
4853+
Do(context.TODO()).
4854+
Get()
4855+
return err
4856+
}
4857+
4858+
func createPodRBACAndWait(t *testing.T, client *clientset.Clientset, verb string) {
4859+
t.Helper()
4860+
4861+
_, err := client.RbacV1().ClusterRoles().Create(context.TODO(), &rbacv1.ClusterRole{
4862+
ObjectMeta: metav1.ObjectMeta{
4863+
Name: fmt.Sprintf("can-%s-pods", verb),
4864+
},
4865+
Rules: []rbacv1.PolicyRule{
4866+
{
4867+
Verbs: []string{verb},
4868+
APIGroups: []string{""},
4869+
Resources: []string{"pods"},
4870+
},
4871+
},
4872+
}, metav1.CreateOptions{})
4873+
require.NoError(t, err)
4874+
4875+
_, err = client.RbacV1().RoleBindings("default").Create(context.TODO(), &rbacv1.RoleBinding{
4876+
ObjectMeta: metav1.ObjectMeta{
4877+
Name: fmt.Sprintf("can-%s-pods", verb),
4878+
},
4879+
Subjects: []rbacv1.Subject{
4880+
{
4881+
Kind: rbacv1.UserKind,
4882+
Name: "panda",
4883+
},
4884+
},
4885+
RoleRef: rbacv1.RoleRef{
4886+
APIGroup: rbacv1.GroupName,
4887+
Kind: "ClusterRole",
4888+
Name: fmt.Sprintf("can-%s-pods", verb),
4889+
},
4890+
}, metav1.CreateOptions{})
4891+
require.NoError(t, err)
4892+
4893+
authutil.WaitForNamedAuthorizationUpdate(t, context.TODO(), client.AuthorizationV1(),
4894+
"panda",
4895+
"default",
4896+
verb,
4897+
"",
4898+
schema.GroupResource{Resource: "pods"},
4899+
true,
4900+
)
4901+
}

0 commit comments

Comments
 (0)