cmd/k8s-operator: validate Service tags, catch duplicate Tailscale Services (#16058)

Validate that any tags that users have specified via tailscale.com/tags
annotation are valid Tailscale ACL tags.
Validate that no more than one HA Tailscale Kubernetes Services in a single cluster refer
to the same Tailscale Service.

Updates tailscale/tailscale#16054
Updates tailscale/tailscale#16035

Signed-off-by: Irbe Krumina <irbe@tailscale.com>
This commit is contained in:
Irbe Krumina
2025-05-23 12:23:58 +01:00
committed by GitHub
parent 7a5af6e6e7
commit 00a7dd180a
6 changed files with 122 additions and 25 deletions

View File

@@ -660,14 +660,9 @@ func (r *HAIngressReconciler) validateIngress(ctx context.Context, ing *networki
var errs []error
// Validate tags if present
if tstr, ok := ing.Annotations[AnnotationTags]; ok {
tags := strings.Split(tstr, ",")
for _, tag := range tags {
tag = strings.TrimSpace(tag)
if err := tailcfg.CheckTag(tag); err != nil {
errs = append(errs, fmt.Errorf("tailscale.com/tags annotation contains invalid tag %q: %w", tag, err))
}
}
violations := tagViolations(ing)
if len(violations) > 0 {
errs = append(errs, fmt.Errorf("Ingress contains invalid tags: %v", strings.Join(violations, ",")))
}
// Validate TLS configuration
@@ -699,8 +694,8 @@ func (r *HAIngressReconciler) validateIngress(ctx context.Context, ing *networki
return errors.Join(errs...)
}
for _, i := range ingList.Items {
if r.shouldExpose(&i) && hostnameForIngress(&i) == hostname && i.Name != ing.Name {
errs = append(errs, fmt.Errorf("found duplicate Ingress %q for hostname %q - multiple Ingresses for the same hostname in the same cluster are not allowed", i.Name, hostname))
if r.shouldExpose(&i) && hostnameForIngress(&i) == hostname && i.UID != ing.UID {
errs = append(errs, fmt.Errorf("found duplicate Ingress %q for hostname %q - multiple Ingresses for the same hostname in the same cluster are not allowed", client.ObjectKeyFromObject(&i), hostname))
}
}
return errors.Join(errs...)
@@ -1113,3 +1108,22 @@ func isErrorTailscaleServiceNotFound(err error) bool {
ok := errors.As(err, &errResp)
return ok && errResp.Status == http.StatusNotFound
}
func tagViolations(obj client.Object) []string {
var violations []string
if obj == nil {
return nil
}
tags, ok := obj.GetAnnotations()[AnnotationTags]
if !ok {
return nil
}
for _, tag := range strings.Split(tags, ",") {
tag = strings.TrimSpace(tag)
if err := tailcfg.CheckTag(tag); err != nil {
violations = append(violations, fmt.Sprintf("invalid tag %q: %v", tag, err))
}
}
return violations
}