mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
webhook: respect the status error from webhook
today, apiserver generates an internal server error for any call to mutatingwebhook if it gives allowed=false. this is not right as it is really not an intenal error, it can be a forbidden as well if the webhook wants it to be.
This commit is contained in:
parent
25ffbe6338
commit
c2fcdc818b
@ -72,8 +72,9 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr *generic.Version
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
}
|
}
|
||||||
return apierrors.NewInternalError(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert attr.VersionedObject to the internal version in the underlying admission.Attributes
|
// convert attr.VersionedObject to the internal version in the underlying admission.Attributes
|
||||||
|
@ -93,8 +93,12 @@ func TestAdmit(t *testing.T) {
|
|||||||
t.Errorf("%s: expected an error saying %q, but got: %v", tt.Name, tt.ErrorContains, err)
|
t.Errorf("%s: expected an error saying %q, but got: %v", tt.Name, tt.ErrorContains, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, isStatusErr := err.(*errors.StatusError); err != nil && !isStatusErr {
|
if statusErr, isStatusErr := err.(*errors.StatusError); err != nil && !isStatusErr {
|
||||||
t.Errorf("%s: expected a StatusError, got %T", tt.Name, err)
|
t.Errorf("%s: expected a StatusError, got %T", tt.Name, err)
|
||||||
|
} else if isStatusErr {
|
||||||
|
if statusErr.ErrStatus.Code != tt.ExpectStatusCode {
|
||||||
|
t.Errorf("%s: expected status code %d, got %d", tt.Name, tt.ExpectStatusCode, statusErr.ErrStatus.Code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fakeAttr, ok := attr.(*webhooktesting.FakeAttributes)
|
fakeAttr, ok := attr.(*webhooktesting.FakeAttributes)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package testing
|
package testing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -191,6 +192,7 @@ type Test struct {
|
|||||||
ExpectAllow bool
|
ExpectAllow bool
|
||||||
ErrorContains string
|
ErrorContains string
|
||||||
ExpectAnnotations map[string]string
|
ExpectAnnotations map[string]string
|
||||||
|
ExpectStatusCode int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNonMutatingTestCases returns test cases with a given base url.
|
// NewNonMutatingTestCases returns test cases with a given base url.
|
||||||
@ -237,7 +239,8 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||||||
NamespaceSelector: &metav1.LabelSelector{},
|
NamespaceSelector: &metav1.LabelSelector{},
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
ErrorContains: "without explanation",
|
ExpectStatusCode: http.StatusForbidden,
|
||||||
|
ErrorContains: "without explanation",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "match & disallow ii",
|
Name: "match & disallow ii",
|
||||||
@ -248,8 +251,8 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||||||
NamespaceSelector: &metav1.LabelSelector{},
|
NamespaceSelector: &metav1.LabelSelector{},
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
|
ExpectStatusCode: http.StatusForbidden,
|
||||||
ErrorContains: "you shall not pass",
|
ErrorContains: "you shall not pass",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "match & disallow & but allowed because namespaceSelector exempt the ns",
|
Name: "match & disallow & but allowed because namespaceSelector exempt the ns",
|
||||||
@ -334,7 +337,8 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
ExpectAllow: false,
|
ExpectStatusCode: http.StatusInternalServerError,
|
||||||
|
ExpectAllow: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "match & fail (but fail because fail closed)",
|
Name: "match & fail (but fail because fail closed)",
|
||||||
@ -360,7 +364,8 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||||||
FailurePolicy: &policyFail,
|
FailurePolicy: &policyFail,
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
ExpectAllow: false,
|
ExpectStatusCode: http.StatusInternalServerError,
|
||||||
|
ExpectAllow: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "match & allow (url)",
|
Name: "match & allow (url)",
|
||||||
@ -383,7 +388,8 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||||||
NamespaceSelector: &metav1.LabelSelector{},
|
NamespaceSelector: &metav1.LabelSelector{},
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
ErrorContains: "without explanation",
|
ExpectStatusCode: http.StatusForbidden,
|
||||||
|
ErrorContains: "without explanation",
|
||||||
}, {
|
}, {
|
||||||
Name: "absent response and fail open",
|
Name: "absent response and fail open",
|
||||||
Webhooks: []registrationv1beta1.Webhook{{
|
Webhooks: []registrationv1beta1.Webhook{{
|
||||||
@ -406,7 +412,8 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||||||
NamespaceSelector: &metav1.LabelSelector{},
|
NamespaceSelector: &metav1.LabelSelector{},
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
ErrorContains: "Webhook response was absent",
|
ExpectStatusCode: http.StatusInternalServerError,
|
||||||
|
ErrorContains: "Webhook response was absent",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "no match dry run",
|
Name: "no match dry run",
|
||||||
@ -433,8 +440,9 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||||||
SideEffects: &sideEffectsUnknown,
|
SideEffects: &sideEffectsUnknown,
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
IsDryRun: true,
|
IsDryRun: true,
|
||||||
ErrorContains: "does not support dry run",
|
ExpectStatusCode: http.StatusBadRequest,
|
||||||
|
ErrorContains: "does not support dry run",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "match dry run side effects None",
|
Name: "match dry run side effects None",
|
||||||
@ -460,8 +468,9 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||||||
SideEffects: &sideEffectsSome,
|
SideEffects: &sideEffectsSome,
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
IsDryRun: true,
|
IsDryRun: true,
|
||||||
ErrorContains: "does not support dry run",
|
ExpectStatusCode: http.StatusBadRequest,
|
||||||
|
ErrorContains: "does not support dry run",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "match dry run side effects NoneOnDryRun",
|
Name: "match dry run side effects NoneOnDryRun",
|
||||||
@ -561,7 +570,8 @@ func NewMutatingTestCases(url *url.URL) []Test {
|
|||||||
NamespaceSelector: &metav1.LabelSelector{},
|
NamespaceSelector: &metav1.LabelSelector{},
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
ErrorContains: "invalid character",
|
ExpectStatusCode: http.StatusInternalServerError,
|
||||||
|
ErrorContains: "invalid character",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "match & remove label dry run unsupported",
|
Name: "match & remove label dry run unsupported",
|
||||||
@ -573,8 +583,9 @@ func NewMutatingTestCases(url *url.URL) []Test {
|
|||||||
SideEffects: &sideEffectsUnknown,
|
SideEffects: &sideEffectsUnknown,
|
||||||
AdmissionReviewVersions: []string{"v1beta1"},
|
AdmissionReviewVersions: []string{"v1beta1"},
|
||||||
}},
|
}},
|
||||||
IsDryRun: true,
|
IsDryRun: true,
|
||||||
ErrorContains: "does not support dry run",
|
ExpectStatusCode: http.StatusBadRequest,
|
||||||
|
ErrorContains: "does not support dry run",
|
||||||
},
|
},
|
||||||
// No need to test everything with the url case, since only the
|
// No need to test everything with the url case, since only the
|
||||||
// connection is different.
|
// connection is different.
|
||||||
|
@ -66,6 +66,9 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{
|
json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{
|
||||||
Response: &v1beta1.AdmissionResponse{
|
Response: &v1beta1.AdmissionResponse{
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
|
Result: &metav1.Status{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
case "/disallowReason":
|
case "/disallowReason":
|
||||||
@ -75,6 +78,7 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Allowed: false,
|
Allowed: false,
|
||||||
Result: &metav1.Status{
|
Result: &metav1.Status{
|
||||||
Message: "you shall not pass",
|
Message: "you shall not pass",
|
||||||
|
Code: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user