Skip to content

Commit 4651c5b

Browse files
committed
fix: validates CONTROL_PLANE_ENDPOINT_IP and NUTANIX_ENDPOINT are distinct
1 parent 11bff42 commit 4651c5b

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2024 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package cluster
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"net"
10+
"net/http"
11+
"net/url"
12+
13+
v1 "k8s.io/api/admission/v1"
14+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
15+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
16+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
17+
18+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
19+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
20+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/utils"
21+
)
22+
23+
type nutanixValidator struct {
24+
client ctrlclient.Client
25+
decoder admission.Decoder
26+
}
27+
28+
func NewNutanixValidator(
29+
client ctrlclient.Client, decoder admission.Decoder,
30+
) *nutanixValidator {
31+
return &nutanixValidator{
32+
client: client,
33+
decoder: decoder,
34+
}
35+
}
36+
37+
func (a *nutanixValidator) Validator() admission.HandlerFunc {
38+
return a.validate
39+
}
40+
41+
func (a *nutanixValidator) validate(
42+
ctx context.Context,
43+
req admission.Request,
44+
) admission.Response {
45+
if req.Operation == v1.Delete {
46+
return admission.Allowed("")
47+
}
48+
49+
cluster := &clusterv1.Cluster{}
50+
err := a.decoder.Decode(req, cluster)
51+
if err != nil {
52+
return admission.Errored(http.StatusBadRequest, err)
53+
}
54+
55+
if cluster.Spec.Topology == nil {
56+
return admission.Allowed("")
57+
}
58+
59+
if utils.GetProvider(cluster) != "nutanix" {
60+
return admission.Allowed("")
61+
}
62+
63+
clusterConfig, err := variables.UnmarshalClusterConfigVariable(cluster.Spec.Topology.Variables)
64+
if err != nil {
65+
return admission.Denied(
66+
fmt.Errorf("failed to unmarshal cluster topology variable %q: %w",
67+
v1alpha1.ClusterConfigVariableName,
68+
err).Error(),
69+
)
70+
}
71+
72+
if clusterConfig.Nutanix != nil {
73+
// Check if Prism Central and Control Plane IP are same.
74+
if err := checkIfPrismCentralAndControlPlaneIPSame(
75+
clusterConfig.Nutanix.PrismCentralEndpoint.URL,
76+
clusterConfig.Nutanix.ControlPlaneEndpoint.Host,
77+
); err != nil {
78+
return admission.Denied(err.Error())
79+
}
80+
}
81+
82+
return admission.Allowed("")
83+
}
84+
85+
// checkIfPrismCentralAndControlPlaneIPSame checks if Prism Central and Control Plane IP are same.
86+
// It compares strictly IP addresses(no FQDN) and doesn't involve any network calls.
87+
// This is a temporary check until we have a better way to handle this by reserving IPs
88+
// using IPAM provider.
89+
func checkIfPrismCentralAndControlPlaneIPSame(
90+
pcRawURL string,
91+
controlPlaneEndpointHost string,
92+
) error {
93+
controlPlaneEndpointIP := net.ParseIP(controlPlaneEndpointHost)
94+
if controlPlaneEndpointIP == nil {
95+
// controlPlaneEndpointIP is strictly accepted as an IP address from user so
96+
// if it is not an IP address, it is invalid.
97+
return fmt.Errorf("invalid Nutanix control plane endpoint IP %q",
98+
controlPlaneEndpointHost)
99+
}
100+
101+
pcURL, err := url.ParseRequestURI(pcRawURL)
102+
if err != nil {
103+
return fmt.Errorf("failed to parse Prism Central URL %q: %w",
104+
pcURL,
105+
err)
106+
}
107+
108+
pcHost, _, err := net.SplitHostPort(pcURL.Host)
109+
if err != nil {
110+
return fmt.Errorf("failed to parse Prism Central host %q: %w",
111+
pcURL.Host,
112+
err)
113+
}
114+
115+
pcIP := net.ParseIP(pcHost)
116+
// PC URL can contain IP/FQDN, so compare only if PC is an IP address.
117+
if pcIP != nil && pcIP.Equal(controlPlaneEndpointIP) {
118+
return fmt.Errorf("prism central and control plane endpoint cannot have the same IP %q",
119+
pcIP)
120+
}
121+
122+
return nil
123+
}

pkg/webhook/cluster/vaiidator.go renamed to pkg/webhook/cluster/validator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ import (
1111
func NewValidator(client ctrlclient.Client, decoder admission.Decoder) admission.Handler {
1212
return admission.MultiValidatingHandler(
1313
NewClusterUUIDLabeler(client, decoder).Validator(),
14+
NewNutanixValidator(client, decoder).Validator(),
1415
)
1516
}

0 commit comments

Comments
 (0)