mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
add url path for admission webhooks
This commit is contained in:
parent
244a8fb9e8
commit
33deaedaf6
@ -199,6 +199,10 @@ type AdmissionHookClientConfig struct {
|
|||||||
// ports open, port 443 will be used if it is open, otherwise it is an error.
|
// ports open, port 443 will be used if it is open, otherwise it is an error.
|
||||||
// Required
|
// Required
|
||||||
Service ServiceReference
|
Service ServiceReference
|
||||||
|
|
||||||
|
// URLPath is an optional field that specifies the URL path to use when posting the AdmissionReview object.
|
||||||
|
URLPath string
|
||||||
|
|
||||||
// CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate.
|
// CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate.
|
||||||
// Required
|
// Required
|
||||||
CABundle []byte
|
CABundle []byte
|
||||||
|
@ -63,6 +63,7 @@ func autoConvert_v1alpha1_AdmissionHookClientConfig_To_admissionregistration_Adm
|
|||||||
if err := Convert_v1alpha1_ServiceReference_To_admissionregistration_ServiceReference(&in.Service, &out.Service, s); err != nil {
|
if err := Convert_v1alpha1_ServiceReference_To_admissionregistration_ServiceReference(&in.Service, &out.Service, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
out.URLPath = in.URLPath
|
||||||
out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle))
|
out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -76,6 +77,7 @@ func autoConvert_admissionregistration_AdmissionHookClientConfig_To_v1alpha1_Adm
|
|||||||
if err := Convert_admissionregistration_ServiceReference_To_v1alpha1_ServiceReference(&in.Service, &out.Service, s); err != nil {
|
if err := Convert_admissionregistration_ServiceReference_To_v1alpha1_ServiceReference(&in.Service, &out.Service, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
out.URLPath = in.URLPath
|
||||||
out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle))
|
out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -182,6 +182,24 @@ func validateExternalAdmissionHook(hook *admissionregistration.ExternalAdmission
|
|||||||
if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
|
if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
|
||||||
allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
|
allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(hook.ClientConfig.URLPath) != 0 {
|
||||||
|
if !strings.HasPrefix(hook.ClientConfig.URLPath, "/") || !strings.HasSuffix(hook.ClientConfig.URLPath, "/") {
|
||||||
|
allErrors = append(allErrors, field.Invalid(fldPath.Child("clientConfig", "urlPath"), hook.ClientConfig.URLPath, "must start and end with a '/'"))
|
||||||
|
}
|
||||||
|
steps := strings.Split(hook.ClientConfig.URLPath[1:len(hook.ClientConfig.URLPath)-1], "/")
|
||||||
|
for i, step := range steps {
|
||||||
|
if len(step) == 0 {
|
||||||
|
allErrors = append(allErrors, field.Invalid(fldPath.Child("clientConfig", "urlPath"), hook.ClientConfig.URLPath, fmt.Sprintf("segment[%d] may not be empty", i)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
failures := validation.IsDNS1123Subdomain(step)
|
||||||
|
for _, failure := range failures {
|
||||||
|
allErrors = append(allErrors, field.Invalid(fldPath.Child("clientConfig", "urlPath"), hook.ClientConfig.URLPath, fmt.Sprintf("segment[%d]: %v", i, failure)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return allErrors
|
return allErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,6 +482,58 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedError: `externalAdmissionHooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`,
|
expectedError: `externalAdmissionHooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "URLPath must start with slash",
|
||||||
|
config: getExternalAdmissionHookConfiguration(
|
||||||
|
[]admissionregistration.ExternalAdmissionHook{
|
||||||
|
{
|
||||||
|
Name: "webhook.k8s.io",
|
||||||
|
ClientConfig: admissionregistration.AdmissionHookClientConfig{
|
||||||
|
URLPath: "foo/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
expectedError: `clientConfig.urlPath: Invalid value: "foo/": must start and end with a '/'`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URLPath must end with slash",
|
||||||
|
config: getExternalAdmissionHookConfiguration(
|
||||||
|
[]admissionregistration.ExternalAdmissionHook{
|
||||||
|
{
|
||||||
|
Name: "webhook.k8s.io",
|
||||||
|
ClientConfig: admissionregistration.AdmissionHookClientConfig{
|
||||||
|
URLPath: "/foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
expectedError: `clientConfig.urlPath: Invalid value: "/foo": must start and end with a '/'`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URLPath no empty step",
|
||||||
|
config: getExternalAdmissionHookConfiguration(
|
||||||
|
[]admissionregistration.ExternalAdmissionHook{
|
||||||
|
{
|
||||||
|
Name: "webhook.k8s.io",
|
||||||
|
ClientConfig: admissionregistration.AdmissionHookClientConfig{
|
||||||
|
URLPath: "/foo//bar/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
expectedError: `clientConfig.urlPath: Invalid value: "/foo//bar/": segment[1] may not be empty`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URLPath no non-subdomain",
|
||||||
|
config: getExternalAdmissionHookConfiguration(
|
||||||
|
[]admissionregistration.ExternalAdmissionHook{
|
||||||
|
{
|
||||||
|
Name: "webhook.k8s.io",
|
||||||
|
ClientConfig: admissionregistration.AdmissionHookClientConfig{
|
||||||
|
URLPath: "/apis/foo.bar/v1alpha1/--bad/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
expectedError: `clientConfig.urlPath: Invalid value: "/apis/foo.bar/v1alpha1/--bad/": segment[3]: a DNS-1123 subdomain`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
errs := ValidateExternalAdmissionHookConfiguration(test.config)
|
errs := ValidateExternalAdmissionHookConfiguration(test.config)
|
||||||
|
@ -45,6 +45,8 @@ import (
|
|||||||
admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||||
|
|
||||||
// install the clientgo admission API for use with api registry
|
// install the clientgo admission API for use with api registry
|
||||||
|
"path"
|
||||||
|
|
||||||
_ "k8s.io/kubernetes/pkg/apis/admission/install"
|
_ "k8s.io/kubernetes/pkg/apis/admission/install"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -286,7 +288,7 @@ func (a *GenericAdmissionWebhook) hookClient(h *v1alpha1.ExternalAdmissionHook)
|
|||||||
// TODO: cache these instead of constructing one each time
|
// TODO: cache these instead of constructing one each time
|
||||||
cfg := &rest.Config{
|
cfg := &rest.Config{
|
||||||
Host: u.Host,
|
Host: u.Host,
|
||||||
APIPath: u.Path,
|
APIPath: path.Join(u.Path, h.ClientConfig.URLPath),
|
||||||
TLSClientConfig: rest.TLSClientConfig{
|
TLSClientConfig: rest.TLSClientConfig{
|
||||||
ServerName: h.ClientConfig.Service.Name + "." + h.ClientConfig.Service.Namespace + ".svc",
|
ServerName: h.ClientConfig.Service.Name + "." + h.ClientConfig.Service.Namespace + ".svc",
|
||||||
CAData: h.ClientConfig.CABundle,
|
CAData: h.ClientConfig.CABundle,
|
||||||
|
@ -54,7 +54,6 @@ func (f *fakeHookSource) Run(stopCh <-chan struct{}) {}
|
|||||||
|
|
||||||
type fakeServiceResolver struct {
|
type fakeServiceResolver struct {
|
||||||
base url.URL
|
base url.URL
|
||||||
path string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) {
|
func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) {
|
||||||
@ -62,7 +61,6 @@ func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL,
|
|||||||
return nil, fmt.Errorf("couldn't resolve service location")
|
return nil, fmt.Errorf("couldn't resolve service location")
|
||||||
}
|
}
|
||||||
u := f.base
|
u := f.base
|
||||||
u.Path = f.path
|
|
||||||
return &u, nil
|
return &u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,13 +126,17 @@ func TestAdmit(t *testing.T) {
|
|||||||
expectAllow bool
|
expectAllow bool
|
||||||
errorContains string
|
errorContains string
|
||||||
}
|
}
|
||||||
ccfg := registrationv1alpha1.AdmissionHookClientConfig{
|
ccfg := func(urlPath string) registrationv1alpha1.AdmissionHookClientConfig {
|
||||||
Service: registrationv1alpha1.ServiceReference{
|
return registrationv1alpha1.AdmissionHookClientConfig{
|
||||||
Name: "webhook-test",
|
Service: registrationv1alpha1.ServiceReference{
|
||||||
Namespace: "default",
|
Name: "webhook-test",
|
||||||
},
|
Namespace: "default",
|
||||||
CABundle: caCert,
|
},
|
||||||
|
URLPath: urlPath,
|
||||||
|
CABundle: caCert,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
matchEverythingRules := []registrationv1alpha1.RuleWithOperations{{
|
matchEverythingRules := []registrationv1alpha1.RuleWithOperations{{
|
||||||
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.OperationAll},
|
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.OperationAll},
|
||||||
Rule: registrationv1alpha1.Rule{
|
Rule: registrationv1alpha1.Rule{
|
||||||
@ -152,109 +154,102 @@ func TestAdmit(t *testing.T) {
|
|||||||
hookSource: fakeHookSource{
|
hookSource: fakeHookSource{
|
||||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||||
Name: "nomatch",
|
Name: "nomatch",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("disallow"),
|
||||||
Rules: []registrationv1alpha1.RuleWithOperations{{
|
Rules: []registrationv1alpha1.RuleWithOperations{{
|
||||||
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.Create},
|
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.Create},
|
||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
path: "disallow",
|
|
||||||
expectAllow: true,
|
expectAllow: true,
|
||||||
},
|
},
|
||||||
"match & allow": {
|
"match & allow": {
|
||||||
hookSource: fakeHookSource{
|
hookSource: fakeHookSource{
|
||||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||||
Name: "allow",
|
Name: "allow",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("allow"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
path: "allow",
|
|
||||||
expectAllow: true,
|
expectAllow: true,
|
||||||
},
|
},
|
||||||
"match & disallow": {
|
"match & disallow": {
|
||||||
hookSource: fakeHookSource{
|
hookSource: fakeHookSource{
|
||||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||||
Name: "disallow",
|
Name: "disallow",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("disallow"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
path: "disallow",
|
|
||||||
errorContains: "without explanation",
|
errorContains: "without explanation",
|
||||||
},
|
},
|
||||||
"match & disallow ii": {
|
"match & disallow ii": {
|
||||||
hookSource: fakeHookSource{
|
hookSource: fakeHookSource{
|
||||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||||
Name: "disallowReason",
|
Name: "disallowReason",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("disallowReason"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
path: "disallowReason",
|
|
||||||
errorContains: "you shall not pass",
|
errorContains: "you shall not pass",
|
||||||
},
|
},
|
||||||
"match & fail (but allow because fail open)": {
|
"match & fail (but allow because fail open)": {
|
||||||
hookSource: fakeHookSource{
|
hookSource: fakeHookSource{
|
||||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||||
Name: "internalErr A",
|
Name: "internalErr A",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("internalErr"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
FailurePolicy: &policyIgnore,
|
FailurePolicy: &policyIgnore,
|
||||||
}, {
|
}, {
|
||||||
Name: "internalErr B",
|
Name: "internalErr B",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("internalErr"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
FailurePolicy: &policyIgnore,
|
FailurePolicy: &policyIgnore,
|
||||||
}, {
|
}, {
|
||||||
Name: "internalErr C",
|
Name: "internalErr C",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("internalErr"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
FailurePolicy: &policyIgnore,
|
FailurePolicy: &policyIgnore,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
path: "internalErr",
|
|
||||||
expectAllow: true,
|
expectAllow: true,
|
||||||
},
|
},
|
||||||
"match & fail (but allow because fail open on nil)": {
|
"match & fail (but allow because fail open on nil)": {
|
||||||
hookSource: fakeHookSource{
|
hookSource: fakeHookSource{
|
||||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||||
Name: "internalErr A",
|
Name: "internalErr A",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("internalErr"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
}, {
|
}, {
|
||||||
Name: "internalErr B",
|
Name: "internalErr B",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("internalErr"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
}, {
|
}, {
|
||||||
Name: "internalErr C",
|
Name: "internalErr C",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("internalErr"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
path: "internalErr",
|
|
||||||
expectAllow: true,
|
expectAllow: true,
|
||||||
},
|
},
|
||||||
"match & fail (but fail because fail closed)": {
|
"match & fail (but fail because fail closed)": {
|
||||||
hookSource: fakeHookSource{
|
hookSource: fakeHookSource{
|
||||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||||
Name: "internalErr A",
|
Name: "internalErr A",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("internalErr"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
FailurePolicy: &policyFail,
|
FailurePolicy: &policyFail,
|
||||||
}, {
|
}, {
|
||||||
Name: "internalErr B",
|
Name: "internalErr B",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("internalErr"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
FailurePolicy: &policyFail,
|
FailurePolicy: &policyFail,
|
||||||
}, {
|
}, {
|
||||||
Name: "internalErr C",
|
Name: "internalErr C",
|
||||||
ClientConfig: ccfg,
|
ClientConfig: ccfg("internalErr"),
|
||||||
Rules: matchEverythingRules,
|
Rules: matchEverythingRules,
|
||||||
FailurePolicy: &policyFail,
|
FailurePolicy: &policyFail,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
path: "internalErr",
|
|
||||||
expectAllow: false,
|
expectAllow: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -262,7 +257,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
for name, tt := range table {
|
for name, tt := range table {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
wh.hookSource = &tt.hookSource
|
wh.hookSource = &tt.hookSource
|
||||||
wh.serviceResolver = fakeServiceResolver{base: *serverURL, path: tt.path}
|
wh.serviceResolver = fakeServiceResolver{base: *serverURL}
|
||||||
wh.SetScheme(legacyscheme.Scheme)
|
wh.SetScheme(legacyscheme.Scheme)
|
||||||
|
|
||||||
err = wh.Admit(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, name, resource, subResource, operation, &userInfo))
|
err = wh.Admit(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, name, resource, subResource, operation, &userInfo))
|
||||||
|
@ -203,6 +203,10 @@ type AdmissionHookClientConfig struct {
|
|||||||
// ports open, port 443 will be used if it is open, otherwise it is an error.
|
// ports open, port 443 will be used if it is open, otherwise it is an error.
|
||||||
// Required
|
// Required
|
||||||
Service ServiceReference `json:"service" protobuf:"bytes,1,opt,name=service"`
|
Service ServiceReference `json:"service" protobuf:"bytes,1,opt,name=service"`
|
||||||
|
|
||||||
|
// URLPath is an optional field that specifies the URL path to use when posting the AdmissionReview object.
|
||||||
|
URLPath string `json:"urlPath" protobuf:"bytes,3,opt,name=urlPath"`
|
||||||
|
|
||||||
// CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate.
|
// CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate.
|
||||||
// Required
|
// Required
|
||||||
CABundle []byte `json:"caBundle" protobuf:"bytes,2,opt,name=caBundle"`
|
CABundle []byte `json:"caBundle" protobuf:"bytes,2,opt,name=caBundle"`
|
||||||
|
Loading…
Reference in New Issue
Block a user