fix nsnp webhook to validate all fileds in it
such as ipblock, service. And the crd openAPIV3Schema could not validate all scenarios Signed-off-by: Duan Jiong <djduanjiong@gmail.com>
This commit is contained in:
@@ -173,7 +173,7 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{})
|
|||||||
|
|
||||||
klog.Info("registering webhooks to the webhook server")
|
klog.Info("registering webhooks to the webhook server")
|
||||||
hookServer.Register("/validate-email-iam-kubesphere-io-v1alpha2-user", &webhook.Admission{Handler: &user.EmailValidator{Client: mgr.GetClient()}})
|
hookServer.Register("/validate-email-iam-kubesphere-io-v1alpha2-user", &webhook.Admission{Handler: &user.EmailValidator{Client: mgr.GetClient()}})
|
||||||
hookServer.Register("/validate-service-nsnp-kubesphere-io-v1alpha1-network", &webhook.Admission{Handler: &nsnetworkpolicy.ServiceValidator{}})
|
hookServer.Register("/validate-nsnp-kubesphere-io-v1alpha1-network", &webhook.Admission{Handler: &nsnetworkpolicy.NSNPValidator{Client: mgr.GetClient()}})
|
||||||
|
|
||||||
klog.V(0).Info("Starting the controllers.")
|
klog.V(0).Info("Starting the controllers.")
|
||||||
if err = mgr.Start(stopCh); err != nil {
|
if err = mgr.Start(stopCh); err != nil {
|
||||||
|
|||||||
@@ -2,23 +2,23 @@ apiVersion: admissionregistration.k8s.io/v1beta1
|
|||||||
kind: ValidatingWebhookConfiguration
|
kind: ValidatingWebhookConfiguration
|
||||||
metadata:
|
metadata:
|
||||||
creationTimestamp: null
|
creationTimestamp: null
|
||||||
name: kubesphere-nsnp-validate-service
|
name: kubesphere-nsnp-validate
|
||||||
webhooks:
|
webhooks:
|
||||||
- clientConfig:
|
- clientConfig:
|
||||||
caBundle: <caBundle>
|
caBundle: <caBundle>
|
||||||
service:
|
service:
|
||||||
name: kubesphere-controller-manager-service
|
name: kubesphere-controller-manager-service
|
||||||
namespace: kubesphere-system
|
namespace: kubesphere-system
|
||||||
path: /validate-service-nsnp-kubesphere-io-v1alpha1-network
|
path: /validate-nsnp-kubesphere-io-v1alpha1-network
|
||||||
failurePolicy: Fail
|
failurePolicy: Fail
|
||||||
name: validate.nsnp.kubesphere.io
|
name: validate.nsnp.kubesphere.io
|
||||||
rules:
|
rules:
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- ""
|
- network.kubesphere.io
|
||||||
apiVersions:
|
apiVersions:
|
||||||
- v1
|
- v1
|
||||||
operations:
|
operations:
|
||||||
- CREATE
|
- CREATE
|
||||||
- UPDATE
|
- UPDATE
|
||||||
resources:
|
resources:
|
||||||
- services
|
- namespacenetworkpolicies
|
||||||
|
|||||||
@@ -2,37 +2,173 @@ package nsnetworkpolicy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
k8snet "k8s.io/api/networking/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +kubebuilder:webhook:path=/validate-service-nsnp-kubesphere-io-v1alpha1-network,name=validate-v1-service,mutating=false,failurePolicy=fail,groups="",resources=services,verbs=create;update,versions=v1
|
type NSNPValidator struct {
|
||||||
|
Client client.Client
|
||||||
// serviceValidator validates service
|
|
||||||
type ServiceValidator struct {
|
|
||||||
decoder *admission.Decoder
|
decoder *admission.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service must hash label, becasue nsnp will use it
|
func (v *NSNPValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||||
func (v *ServiceValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
|
nsnp := &networkv1alpha1.NamespaceNetworkPolicy{}
|
||||||
service := &corev1.Service{}
|
|
||||||
|
|
||||||
err := v.decoder.Decode(req, service)
|
err := v.decoder.Decode(req, nsnp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return admission.Errored(http.StatusBadRequest, err)
|
return admission.Errored(http.StatusBadRequest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if service.Spec.Selector == nil {
|
allErrs := field.ErrorList{}
|
||||||
return admission.Denied(fmt.Sprintf("missing label"))
|
allErrs = append(allErrs, v.ValidateNSNPSpec(&nsnp.Spec, field.NewPath("spec"))...)
|
||||||
|
|
||||||
|
if len(allErrs) != 0 {
|
||||||
|
return admission.Denied(allErrs.ToAggregate().Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return admission.Allowed("")
|
return admission.Allowed("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServiceValidator) InjectDecoder(d *admission.Decoder) error {
|
func (v *NSNPValidator) InjectDecoder(d *admission.Decoder) error {
|
||||||
a.decoder = d
|
v.decoder = d
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateNetworkPolicyPort validates a NetworkPolicyPort
|
||||||
|
func (v *NSNPValidator) ValidateNetworkPolicyPort(port *k8snet.NetworkPolicyPort, portPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if port.Protocol != nil && *port.Protocol != corev1.ProtocolTCP && *port.Protocol != corev1.ProtocolUDP && *port.Protocol != corev1.ProtocolSCTP {
|
||||||
|
allErrs = append(allErrs, field.NotSupported(portPath.Child("protocol"), *port.Protocol, []string{string(corev1.ProtocolTCP), string(corev1.ProtocolUDP), string(corev1.ProtocolSCTP)}))
|
||||||
|
}
|
||||||
|
if port.Port != nil {
|
||||||
|
if port.Port.Type == intstr.Int {
|
||||||
|
for _, msg := range validation.IsValidPortNum(int(port.Port.IntVal)) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.IntVal, msg))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, msg := range validation.IsValidPortName(port.Port.StrVal) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.StrVal, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *NSNPValidator) ValidateServiceSelector(serviceSelector *networkv1alpha1.ServiceSelector, fldPath *field.Path) field.ErrorList {
|
||||||
|
service := &corev1.Service{}
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
err := v.Client.Get(context.TODO(), client.ObjectKey{Namespace: serviceSelector.Namespace, Name: serviceSelector.Name}, service)
|
||||||
|
if err != nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, serviceSelector, "cannot get service"))
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(service.Spec.Selector) <= 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, serviceSelector, "service should have selector"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR
|
||||||
|
func ValidateCIDR(cidr string) (*net.IPNet, error) {
|
||||||
|
_, net, err := net.ParseCIDR(cidr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return net, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateIPBlock validates a cidr and the except fields of an IpBlock NetworkPolicyPeer
|
||||||
|
func (v *NSNPValidator) ValidateIPBlock(ipb *k8snet.IPBlock, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if len(ipb.CIDR) == 0 || ipb.CIDR == "" {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("cidr"), ""))
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
cidrIPNet, err := ValidateCIDR(ipb.CIDR)
|
||||||
|
if err != nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("cidr"), ipb.CIDR, "not a valid CIDR"))
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
exceptCIDR := ipb.Except
|
||||||
|
for i, exceptIP := range exceptCIDR {
|
||||||
|
exceptPath := fldPath.Child("except").Index(i)
|
||||||
|
exceptCIDR, err := ValidateCIDR(exceptIP)
|
||||||
|
if err != nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(exceptPath, exceptIP, "not a valid CIDR"))
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
if !cidrIPNet.Contains(exceptCIDR.IP) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(exceptPath, exceptCIDR.IP, "not within CIDR range"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateNSNPPeer validates a NetworkPolicyPeer
|
||||||
|
func (v *NSNPValidator) ValidateNSNPPeer(peer *networkv1alpha1.NetworkPolicyPeer, peerPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
numPeers := 0
|
||||||
|
|
||||||
|
if peer.ServiceSelector != nil {
|
||||||
|
numPeers++
|
||||||
|
allErrs = append(allErrs, v.ValidateServiceSelector(peer.ServiceSelector, peerPath.Child("service"))...)
|
||||||
|
}
|
||||||
|
if peer.NamespaceSelector != nil {
|
||||||
|
numPeers++
|
||||||
|
}
|
||||||
|
if peer.IPBlock != nil {
|
||||||
|
numPeers++
|
||||||
|
allErrs = append(allErrs, v.ValidateIPBlock(peer.IPBlock, peerPath.Child("ipBlock"))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if numPeers == 0 {
|
||||||
|
allErrs = append(allErrs, field.Required(peerPath, "must specify a peer"))
|
||||||
|
} else if numPeers > 1 && peer.IPBlock != nil {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(peerPath, "may not specify both ipBlock and another peer"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *NSNPValidator) ValidateNSNPSpec(spec *networkv1alpha1.NamespaceNetworkPolicySpec, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
// Validate ingress rules.
|
||||||
|
for i, ingress := range spec.Ingress {
|
||||||
|
ingressPath := fldPath.Child("ingress").Index(i)
|
||||||
|
for i, port := range ingress.Ports {
|
||||||
|
portPath := ingressPath.Child("ports").Index(i)
|
||||||
|
allErrs = append(allErrs, v.ValidateNetworkPolicyPort(&port, portPath)...)
|
||||||
|
}
|
||||||
|
for i, from := range ingress.From {
|
||||||
|
fromPath := ingressPath.Child("from").Index(i)
|
||||||
|
allErrs = append(allErrs, v.ValidateNSNPPeer(&from, fromPath)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Validate egress rules
|
||||||
|
for i, egress := range spec.Egress {
|
||||||
|
egressPath := fldPath.Child("egress").Index(i)
|
||||||
|
for i, port := range egress.Ports {
|
||||||
|
portPath := egressPath.Child("ports").Index(i)
|
||||||
|
allErrs = append(allErrs, v.ValidateNetworkPolicyPort(&port, portPath)...)
|
||||||
|
}
|
||||||
|
for i, to := range egress.To {
|
||||||
|
toPath := egressPath.Child("to").Index(i)
|
||||||
|
allErrs = append(allErrs, v.ValidateNSNPPeer(&to, toPath)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user