{"record":{"id":"91a6e1560fd6e9ba","repo":"kubernetes/kops","slug":"expected-protocol-portspec-in-firewall-rule-q","errorCode":null,"errorMessage":"expected protocol[:portspec] in firewall rule %q","messagePattern":"expected protocol\\[:portspec\\] in firewall rule %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"upup/pkg/fi/cloudup/gcetasks/firewallrule.go","lineNumber":157,"sourceCode":"\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (_ *FirewallRule) CheckChanges(a, e, changes *FirewallRule) error {\n\tif e.Network == nil {\n\t\treturn fi.RequiredField(\"Network\")\n\t}\n\treturn nil\n}\n\nfunc parseFirewallAllowed(rule string) (*compute.FirewallAllowed, error) {\n\to := &compute.FirewallAllowed{}\n\n\ttokens := strings.Split(rule, \":\")\n\tif len(tokens) < 1 || len(tokens) > 2 {\n\t\treturn nil, fmt.Errorf(\"expected protocol[:portspec] in firewall rule %q\", rule)\n\t}\n\n\to.IPProtocol = tokens[0]\n\tif len(tokens) == 1 {\n\t\treturn o, nil\n\t}\n\n\to.Ports = []string{tokens[1]}\n\treturn o, nil\n}\n\nfunc serializeFirewallAllowed(r *compute.FirewallAllowed) string {\n\tif len(r.Ports) == 0 {\n\t\treturn r.IPProtocol\n\t}\n\n\tvar tokens []string\n\tfor _, ports := range r.Ports {","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/upup/pkg/fi/cloudup/gcetasks/firewallrule.go#L139-L175","documentation":"parseFirewallAllowed() splits a firewall 'allowed' entry on ':' and expects the form protocol[:portspec]. This error fires when splitting yields more than two tokens (or zero), e.g. a stray colon or malformed string, so the rule cannot be parsed into a compute.FirewallAllowed.","triggerScenarios":"An allowed rule string in the cluster spec contains more than one ':' character, e.g. 'tcp:80:90' or 'tcp:'-like typos; strings.Split always returns at least one token, so len>2 is the only real trigger.","commonSituations":"Typos in port ranges ('tcp:1-65535:extra'), copy-pasted rules from other clouds, accidental trailing colons, or concatenating spec fragments.","solutions":["Fix the allowed entry to contain at most one colon: 'tcp' or 'tcp:22' or 'tcp:3000-4000'.","For multiple port ranges, use separate entries or a comma-joined portspec as supported by the spec, not extra colons.","Check the cluster spec YAML for stray characters in the firewall section."],"exampleFix":"// before\nrule: \"tcp:80:443\"\n// after\nrule: \"tcp:80,443\"","handlingStrategy":"validation","validationCode":"func validateFirewallAllowed(rule string) error {\n  parts := strings.Split(rule, \":\")\n  if len(parts) < 1 || len(parts) > 2 {\n    return fmt.Errorf(\"rule %q must be protocol[:portspec]\", rule)\n  }\n  if len(parts) == 2 {\n    for _, p := range strings.Split(parts[1], \",\") {\n      if !portSpecRe.MatchString(p) {\n        return fmt.Errorf(\"bad port %q in %q\", p, rule)\n      }\n    }\n  }\n  return nil\n}\nvar portSpecRe = regexp.MustCompile(`^\\d+(-\\d+)?$`)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use exactly one colon per allowed entry; put multiple ports in a comma-separated portspec.","Validate firewall spec entries with a regex before running kops update.","Never concatenate rule fragments from different clouds."],"tags":["gce","firewall","parsing","validation"],"backgroundTag":"firewall-portspec-parse-error","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}