Skip to content

Commit 114900a

Browse files
authored
Merge pull request kubernetes#126523 from enj/enj/i/ssa_authz_create_err
SSA: improve create authz error message
2 parents fa75c8c + bff6ce4 commit 114900a

File tree

3 files changed

+134
-14
lines changed

3 files changed

+134
-14
lines changed

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,24 @@ var sanitizer = strings.NewReplacer(`&`, "&amp;", `<`, "&lt;", `>`, "&gt;")
3434

3535
// Forbidden renders a simple forbidden error
3636
func Forbidden(ctx context.Context, attributes authorizer.Attributes, w http.ResponseWriter, req *http.Request, reason string, s runtime.NegotiatedSerializer) {
37-
msg := sanitizer.Replace(forbiddenMessage(attributes))
3837
w.Header().Set("X-Content-Type-Options", "nosniff")
38+
gv := schema.GroupVersion{Group: attributes.GetAPIGroup(), Version: attributes.GetAPIVersion()}
39+
ErrorNegotiated(ForbiddenStatusError(attributes, reason), s, gv, w, req)
40+
}
41+
42+
func ForbiddenStatusError(attributes authorizer.Attributes, reason string) *apierrors.StatusError {
43+
msg := sanitizer.Replace(forbiddenMessage(attributes))
3944

40-
var errMsg string
45+
var errMsg error
4146
if len(reason) == 0 {
42-
errMsg = fmt.Sprintf("%s", msg)
47+
errMsg = fmt.Errorf("%s", msg)
4348
} else {
44-
errMsg = fmt.Sprintf("%s: %s", msg, reason)
49+
errMsg = fmt.Errorf("%s: %s", msg, reason)
4550
}
46-
gv := schema.GroupVersion{Group: attributes.GetAPIGroup(), Version: attributes.GetAPIVersion()}
51+
4752
gr := schema.GroupResource{Group: attributes.GetAPIGroup(), Resource: attributes.GetResource()}
48-
ErrorNegotiated(apierrors.NewForbidden(gr, attributes.GetName(), fmt.Errorf(errMsg)), s, gv, w, req)
53+
54+
return apierrors.NewForbidden(gr, attributes.GetName(), errMsg)
4955
}
5056

5157
func forbiddenMessage(attributes authorizer.Attributes) string {

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"k8s.io/apiserver/pkg/endpoints/handlers/finisher"
4040
requestmetrics "k8s.io/apiserver/pkg/endpoints/handlers/metrics"
4141
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
42+
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
4243
"k8s.io/apiserver/pkg/endpoints/request"
4344
"k8s.io/apiserver/pkg/registry/rest"
4445
"k8s.io/apiserver/pkg/util/dryrun"
@@ -275,13 +276,7 @@ func withAuthorization(validate rest.ValidateObjectFunc, a authorizer.Authorizer
275276
}
276277

277278
// The user is not authorized to perform this action, so we need to build the error response
278-
gr := schema.GroupResource{
279-
Group: attributes.GetAPIGroup(),
280-
Resource: attributes.GetResource(),
281-
}
282-
name := attributes.GetName()
283-
err := fmt.Errorf("%v", authorizerReason)
284-
return errors.NewForbidden(gr, name, err)
279+
return responsewriters.ForbiddenStatusError(attributes, authorizerReason)
285280
}
286281
}
287282

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: 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

Comments
 (0)