doProbe makes an HTTP GET to a URL constructed from the webhook's clientConfig.Service fields — service name and namespace come directly from the MutatingWebhookConfiguration object with no allowlist. An attacker who can create or modify webhook configurations can make the operator send HTTP requests to arbitrary cluster-internal services.
URL construction:
|
func getReadinessProbeURL(config admissionv1.WebhookClientConfig) (string, error) { |
|
switch { |
|
case config.URL != nil: |
|
return "", errors.New("only webhooks pointing to a Service are supported") |
|
|
|
case config.Service != nil: |
|
svc := config.Service |
|
port := 443 |
|
if svc.Port != nil { |
|
port = int(*svc.Port) |
|
} |
|
return fmt.Sprintf("https://%s.%s.svc:%d/ready", svc.Name, svc.Namespace, port), nil |
|
|
|
default: |
|
return "", errors.New("no URL or Service specified in WebhookClientConfig") |
|
} |
|
} |
HTTP request execution:
|
func doProbe(ctx context.Context, webhook *admissionv1.MutatingWebhookConfiguration) (bool, error) { |
|
log := logf.FromContext(ctx) |
|
if len(webhook.Webhooks) == 0 { |
|
return false, errors.New("mutatingwebhookconfiguration contains no webhooks") |
|
} |
|
clientConfig := webhook.Webhooks[0].ClientConfig |
|
if clientConfig.Service == nil { |
|
return false, errors.New("missing webhooks[].clientConfig.service") |
|
} |
|
|
|
if len(clientConfig.CABundle) == 0 { |
|
return false, errors.New("webhooks[].clientConfig.caBundle hasn't been set; check if the remote istiod can access this cluster") |
|
} |
|
caCertPool := x509.NewCertPool() |
|
if ok := caCertPool.AppendCertsFromPEM(clientConfig.CABundle); !ok { |
|
return false, errors.New("failed to append CA bundle to cert pool") |
|
} |
|
|
|
httpClient := http.Client{ |
|
Timeout: getTimeout(webhook), |
|
Transport: &http.Transport{ |
|
DialContext: customDialContext, |
|
TLSClientConfig: &tls.Config{ |
|
RootCAs: caCertPool, |
|
MinVersion: tls.VersionTLS12, |
|
}, |
|
}, |
|
} |
|
|
|
url, err := getReadinessProbeURL(clientConfig) |
|
if err != nil { |
|
return false, err |
|
} |
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) |
|
if err != nil { |
|
return false, err |
|
} |
|
|
|
log.V(3).Info("Executing readiness probe on remote control plane", "url", req.URL.String()) |
|
resp, err := httpClient.Do(req) |
|
if err != nil { |
|
log.V(3).Info("Probe failed", "error", err) |
|
return false, err |
|
} |
|
log.V(3).Info("Probe response", "response", resp.StatusCode) |
|
|
|
return resp.StatusCode == http.StatusOK, nil |
|
} |
The result is written back to annotations:
|
func (r *Reconciler) Reconcile(ctx context.Context, webhook *admissionv1.MutatingWebhookConfiguration) (ctrl.Result, error) { |
|
log := logf.FromContext(ctx) |
|
|
|
isReady, err := r.probe(ctx, webhook) |
|
reason := "" |
|
if err != nil { |
|
log.V(3).Error(err, "Probe failed") |
|
reason = err.Error() |
|
} |
|
|
|
if webhook.Annotations == nil { |
|
webhook.Annotations = make(map[string]string) |
|
} |
|
webhook.Annotations[constants.WebhookReadinessProbeStatusAnnotationKey] = strconv.FormatBool(isReady) |
|
webhook.Annotations[constants.WebhookReadinessProbeStatusReasonAnnotationKey] = reason |
|
|
|
err = r.Client.Update(ctx, webhook) |
|
if err != nil { |
|
return ctrl.Result{}, err |
|
} |
|
return ctrl.Result{RequeueAfter: getPeriod(webhook)}, nil |
|
} |
Suggested Fix
Restrict target services to the expected Istio control plane namespace(s), or validate the webhook configuration before probing.
From code inspection (2026-07-05, main @ 86fac7a). Severity: 🟠 Medium
doProbemakes an HTTP GET to a URL constructed from the webhook'sclientConfig.Servicefields — service name and namespace come directly from theMutatingWebhookConfigurationobject with no allowlist. An attacker who can create or modify webhook configurations can make the operator send HTTP requests to arbitrary cluster-internal services.URL construction:
sail-operator/controllers/webhook/webhook_controller.go
Lines 153 to 169 in 86fac7a
HTTP request execution:
sail-operator/controllers/webhook/webhook_controller.go
Lines 103 to 151 in 86fac7a
The result is written back to annotations:
sail-operator/controllers/webhook/webhook_controller.go
Lines 80 to 101 in 86fac7a
Suggested Fix
Restrict target services to the expected Istio control plane namespace(s), or validate the webhook configuration before probing.
From code inspection (2026-07-05, main @ 86fac7a). Severity: 🟠 Medium