Skip to content

Commit 565b954

Browse files
Add validation webhook to validate bgp as number
1 parent aa97bd6 commit 565b954

File tree

8 files changed

+615
-21
lines changed

8 files changed

+615
-21
lines changed

PROJECT

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ resources:
138138
kind: BGP
139139
path: github.com/ironcore-dev/network-operator/api/core/v1alpha1
140140
version: v1alpha1
141+
webhooks:
142+
validation: true
143+
webhookVersion: v1
141144
- api:
142145
crdVersion: v1
143146
namespaced: true
@@ -146,6 +149,9 @@ resources:
146149
kind: BGPPeer
147150
path: github.com/ironcore-dev/network-operator/api/core/v1alpha1
148151
version: v1alpha1
152+
webhooks:
153+
validation: true
154+
webhookVersion: v1
149155
- api:
150156
crdVersion: v1
151157
namespaced: true

cmd/main.go

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -460,26 +460,6 @@ func main() {
460460
os.Exit(1)
461461
}
462462

463-
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
464-
if err := webhookv1alpha1.SetupVRFWebhookWithManager(mgr); err != nil {
465-
setupLog.Error(err, "unable to create webhook", "webhook", "VRF")
466-
os.Exit(1)
467-
}
468-
}
469-
470-
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
471-
if err := webhookv1alpha1.SetupInterfaceWebhookWithManager(mgr); err != nil {
472-
setupLog.Error(err, "unable to create webhook", "webhook", "Interface")
473-
os.Exit(1)
474-
}
475-
}
476-
477-
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
478-
if err := webhookv1alpha1.SetupPrefixSetWebhookWithManager(mgr); err != nil {
479-
setupLog.Error(err, "unable to create webhook", "webhook", "PrefixSet")
480-
os.Exit(1)
481-
}
482-
}
483463
if err := (&corecontroller.RoutingPolicyReconciler{
484464
Client: mgr.GetClient(),
485465
Scheme: mgr.GetScheme(),
@@ -502,6 +482,33 @@ func main() {
502482
os.Exit(1)
503483
}
504484

485+
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
486+
if err := webhookv1alpha1.SetupVRFWebhookWithManager(mgr); err != nil {
487+
setupLog.Error(err, "unable to create webhook", "webhook", "VRF")
488+
os.Exit(1)
489+
}
490+
491+
if err := webhookv1alpha1.SetupInterfaceWebhookWithManager(mgr); err != nil {
492+
setupLog.Error(err, "unable to create webhook", "webhook", "Interface")
493+
os.Exit(1)
494+
}
495+
496+
if err := webhookv1alpha1.SetupPrefixSetWebhookWithManager(mgr); err != nil {
497+
setupLog.Error(err, "unable to create webhook", "webhook", "PrefixSet")
498+
os.Exit(1)
499+
}
500+
501+
if err := webhookv1alpha1.SetupBGPWebhookWithManager(mgr); err != nil {
502+
setupLog.Error(err, "unable to create webhook", "webhook", "BGP")
503+
os.Exit(1)
504+
}
505+
506+
if err := webhookv1alpha1.SetupBGPPeerWebhookWithManager(mgr); err != nil {
507+
setupLog.Error(err, "unable to create webhook", "webhook", "BGPPeer")
508+
os.Exit(1)
509+
}
510+
}
511+
505512
// +kubebuilder:scaffold:builder
506513

507514
if metricsCertWatcher != nil {

config/webhook/manifests.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,46 @@ kind: ValidatingWebhookConfiguration
44
metadata:
55
name: validating-webhook-configuration
66
webhooks:
7+
- admissionReviewVersions:
8+
- v1
9+
clientConfig:
10+
service:
11+
name: webhook-service
12+
namespace: system
13+
path: /validate-networking-metal-ironcore-dev-v1alpha1-bgp
14+
failurePolicy: Fail
15+
name: bgp-v1alpha1.kb.io
16+
rules:
17+
- apiGroups:
18+
- networking.metal.ironcore.dev
19+
apiVersions:
20+
- v1alpha1
21+
operations:
22+
- CREATE
23+
- UPDATE
24+
resources:
25+
- bgp
26+
sideEffects: None
27+
- admissionReviewVersions:
28+
- v1
29+
clientConfig:
30+
service:
31+
name: webhook-service
32+
namespace: system
33+
path: /validate-networking-metal-ironcore-dev-v1alpha1-bgppeer
34+
failurePolicy: Fail
35+
name: bgppeer-v1alpha1.kb.io
36+
rules:
37+
- apiGroups:
38+
- networking.metal.ironcore.dev
39+
apiVersions:
40+
- v1alpha1
41+
operations:
42+
- CREATE
43+
- UPDATE
44+
resources:
45+
- bgppeers
46+
sideEffects: None
747
- admissionReviewVersions:
848
- v1
949
clientConfig:
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"math"
10+
"strconv"
11+
"strings"
12+
13+
"k8s.io/apimachinery/pkg/runtime"
14+
"k8s.io/apimachinery/pkg/util/intstr"
15+
ctrl "sigs.k8s.io/controller-runtime"
16+
logf "sigs.k8s.io/controller-runtime/pkg/log"
17+
"sigs.k8s.io/controller-runtime/pkg/webhook"
18+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
19+
20+
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
21+
)
22+
23+
// log is for logging in this package.
24+
var bgplog = logf.Log.WithName("bgp-resource")
25+
26+
// SetupBGPWebhookWithManager registers the webhook for BGP in the manager.
27+
func SetupBGPWebhookWithManager(mgr ctrl.Manager) error {
28+
return ctrl.NewWebhookManagedBy(mgr).For(&v1alpha1.BGP{}).
29+
WithValidator(&BGPCustomValidator{}).
30+
Complete()
31+
}
32+
33+
// +kubebuilder:webhook:path=/validate-networking-metal-ironcore-dev-v1alpha1-bgp,mutating=false,failurePolicy=Fail,sideEffects=None,groups=networking.metal.ironcore.dev,resources=bgp,verbs=create;update,versions=v1alpha1,name=bgp-v1alpha1.kb.io,admissionReviewVersions=v1
34+
35+
// BGPCustomValidator struct is responsible for validating the BGP resource
36+
// when it is created, updated, or deleted.
37+
type BGPCustomValidator struct{}
38+
39+
var _ webhook.CustomValidator = &BGPCustomValidator{}
40+
41+
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type BGP.
42+
func (v *BGPCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
43+
bgp, ok := obj.(*v1alpha1.BGP)
44+
if !ok {
45+
return nil, fmt.Errorf("expected a BGP object but got %T", obj)
46+
}
47+
48+
bgplog.Info("Validation for BGP upon creation", "name", bgp.GetName())
49+
50+
return nil, validateASNumber(bgp.Spec.ASNumber)
51+
}
52+
53+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type BGP.
54+
func (v *BGPCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
55+
bgp, ok := newObj.(*v1alpha1.BGP)
56+
if !ok {
57+
return nil, fmt.Errorf("expected a BGP object for the newObj but got %T", newObj)
58+
}
59+
60+
bgplog.Info("Validation for BGP upon update", "name", bgp.GetName())
61+
62+
return nil, validateASNumber(bgp.Spec.ASNumber)
63+
}
64+
65+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type BGP.
66+
func (v *BGPCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
67+
return nil, nil
68+
}
69+
70+
// validateASNumber performs validation on the autonomous system number (ASN).
71+
// It ensures the ASN is within valid ranges for both plain and dotted notation as per [RFC 5396].
72+
// [RFC 5396](https://datatracker.ietf.org/doc/html/rfc5396)
73+
func validateASNumber(asn intstr.IntOrString) error {
74+
// If the value is an integer, validate plain format only
75+
if asn.Type == intstr.Int {
76+
asnValue := asn.IntVal
77+
if asnValue < 1 {
78+
return fmt.Errorf("AS number %d must be >=1 (for larger values use string format to support full range 1-4294967295)", asnValue)
79+
}
80+
return nil
81+
}
82+
83+
// If the value is a string, it can be either plain or dotted notation
84+
asnStr := asn.StrVal
85+
86+
// Try to parse as plain format first
87+
if !strings.Contains(asnStr, ".") {
88+
asnValue, err := strconv.ParseInt(asnStr, 10, 64)
89+
if err != nil {
90+
return fmt.Errorf("invalid AS number format %q: %w", asnStr, err)
91+
}
92+
if asnValue < 1 || asnValue > math.MaxUint32 {
93+
return fmt.Errorf("AS number %d is out of valid range (1-4294967295)", asnValue)
94+
}
95+
return nil
96+
}
97+
98+
// Parse as dotted notation (high.low)
99+
parts := strings.Split(asnStr, ".")
100+
if len(parts) != 2 {
101+
return fmt.Errorf("invalid AS number dotted notation %q: must be in format high.low", asnStr)
102+
}
103+
104+
high, err := strconv.ParseInt(parts[0], 10, 64)
105+
if err != nil {
106+
return fmt.Errorf("invalid AS number dotted notation %q: high part is not a valid number: %w", asnStr, err)
107+
}
108+
if high < 1 || high > math.MaxUint16 {
109+
return fmt.Errorf("invalid AS number dotted notation %q: high part %d is out of valid range (1-65535)", asnStr, high)
110+
}
111+
112+
low, err := strconv.ParseInt(parts[1], 10, 64)
113+
if err != nil {
114+
return fmt.Errorf("invalid AS number dotted notation %q: low part is not a valid number: %w", asnStr, err)
115+
}
116+
if low < 0 || low > math.MaxUint16 {
117+
return fmt.Errorf("invalid AS number dotted notation %q: low part %d is out of valid range (0-65535)", asnStr, low)
118+
}
119+
120+
return nil
121+
}

0 commit comments

Comments
 (0)