@@ -30,14 +30,15 @@ import (
30
30
31
31
"github.com/google/go-cmp/cmp"
32
32
"github.com/stretchr/testify/require"
33
- "sigs.k8s.io/yaml"
34
33
35
34
appsv1 "k8s.io/api/apps/v1"
36
35
v1 "k8s.io/api/core/v1"
36
+ rbacv1 "k8s.io/api/rbac/v1"
37
37
apierrors "k8s.io/apimachinery/pkg/api/errors"
38
38
"k8s.io/apimachinery/pkg/api/meta"
39
39
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
40
40
"k8s.io/apimachinery/pkg/runtime"
41
+ "k8s.io/apimachinery/pkg/runtime/schema"
41
42
"k8s.io/apimachinery/pkg/types"
42
43
"k8s.io/apimachinery/pkg/util/intstr"
43
44
"k8s.io/apimachinery/pkg/util/wait"
@@ -49,7 +50,9 @@ import (
49
50
"k8s.io/client-go/kubernetes/fake"
50
51
restclient "k8s.io/client-go/rest"
51
52
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
53
+ "k8s.io/kubernetes/test/integration/authutil"
52
54
"k8s.io/kubernetes/test/integration/framework"
55
+ "sigs.k8s.io/yaml"
53
56
)
54
57
55
58
func setup (t testing.TB ) (clientset.Interface , kubeapiservertesting.TearDownFunc ) {
@@ -4780,3 +4783,119 @@ func expectManagedFields(t *testing.T, managedFields []metav1.ManagedFieldsEntry
4780
4783
t .Fatalf ("Want:\n %s\n Got:\n %s\n Diff:\n %s" , string (want ), string (got ), diff )
4781
4784
}
4782
4785
}
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: User "panda" cannot create resource "pods" in API group "" in the namespace "default"` )
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