kubernetes/kops · error

either SourceRanges or SourceTags should be specified when…

Error message

either SourceRanges or SourceTags should be specified when Disabled is false

What it means

FirewallRule.Normalize validates the spec before apply: with Disabled=false, a rule with neither SourceRanges nor SourceTags is rejected, because GCE would interpret it as allowing 0.0.0.0/0 — almost never intended. kOps fails fast instead of opening the rule to the internet.

Solutions

  1. Add explicit sourceRanges entries (e.g. 0.0.0.0/0 only if you truly mean public)
  2. Add sourceTags to restrict by instance tag instead
  3. Set disabled: true if the rule is intentionally a placeholder
  4. Re-run kops update after fixing the manifest

Example fix

// before
firewallRule:
  name: my-rule
  disabled: false
// after
firewallRule:
  name: my-rule
  disabled: false
  sourceRanges: ["10.0.0.0/8"]
Defensive patterns

Strategy: validation

Validate before calling

func validateFirewallSource(rule FirewallRuleSpec) error {
  if !rule.Disabled && len(rule.SourceRanges) == 0 && len(rule.SourceTags) == 0 {
    return fmt.Errorf("firewall rule %s: set sourceRanges or sourceTags, or disabled: true", rule.Name)
  }
  return nil
}

Prevention

When it happens

Trigger: A cluster spec defines a GCE firewallRule (e.g. in a cluster manifest) with disabled: false (or unset) and both sourceRanges and sourceTags empty.

Common situations: Authoring a new firewall rule and forgetting to fill sourceRanges; templating that renders empty ranges; copying a rule and deleting its source fields while re-enabling it.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/f060adbbeeb57b07. Report an issue: GitHub.

Appendix: source

Thrown at upup/pkg/fi/cloudup/gcetasks/firewallrule.go:103

	// Ignore "system" fields
	actual.Lifecycle = e.Lifecycle
	actual.Family = e.Family

	return actual, nil
}

func (e *FirewallRule) Run(c *fi.CloudupContext) error {
	return fi.CloudupDefaultDeltaRunMethod(e, c)
}

// Normalize applies some validation that isn't technically required,
// but avoids some problems with surprising behaviours.
func (e *FirewallRule) Normalize(c *fi.CloudupContext) error {
	if !e.Disabled {
		// Treat it as an error if SourceRanges _and_ SourceTags empty with Disabled=false
		// this is interpreted as SourceRanges="0.0.0.0/0", which is likely not what was intended.
		if len(e.SourceRanges) == 0 && len(e.SourceTags) == 0 {
			return fmt.Errorf("either SourceRanges or SourceTags should be specified when Disabled is false")
		}
	}

	// Treat it as an error if SourceRanges _and_ SourceTags both set;
	// this is interpreted as OR, not AND, which is likely not what was intended.
	if len(e.SourceRanges) != 0 && len(e.SourceTags) != 0 {
		return fmt.Errorf("SourceRanges and SourceTags should not both be specified")
	}

	name := fi.ValueOf(e.Name)

	// Make sure we've split the ipv4 / ipv6 addresses.
	// A single firewall rule can't mix ipv4 and ipv6 addresses, so we split them into two rules.
	for _, sourceRange := range e.SourceRanges {
		_, cidr, err := net.ParseCIDR(sourceRange)
		if err != nil {
			return fmt.Errorf("sourceRange %q is not valid: %w", sourceRange, err)
		}

View on GitHub (pinned to 4c8573c808)