diff --git a/hscontrol/policy/v2/policy.go b/hscontrol/policy/v2/policy.go index 8c07e6cc..bc968c3c 100644 --- a/hscontrol/policy/v2/policy.go +++ b/hscontrol/policy/v2/policy.go @@ -21,6 +21,9 @@ import ( "tailscale.com/util/deephash" ) +// PolicyVersion is the version number of this policy implementation. +const PolicyVersion = 2 + // ErrInvalidTagOwner is returned when a tag owner is not an Alias type. var ErrInvalidTagOwner = errors.New("tag owner is not an Alias") @@ -739,7 +742,7 @@ func (pm *PolicyManager) NodeCanApproveRoute(node types.NodeView, route netip.Pr } func (pm *PolicyManager) Version() int { - return 2 + return PolicyVersion } func (pm *PolicyManager) DebugString() string { diff --git a/hscontrol/policy/v2/utils.go b/hscontrol/policy/v2/utils.go index 3fb0d38b..68c5984b 100644 --- a/hscontrol/policy/v2/utils.go +++ b/hscontrol/policy/v2/utils.go @@ -9,6 +9,9 @@ import ( "tailscale.com/tailcfg" ) +// portRangeParts is the expected number of parts in a port range (start-end). +const portRangeParts = 2 + // Sentinel errors for port and destination parsing. var ( ErrInputMissingColon = errors.New("input must contain a colon character separating destination and port") @@ -63,7 +66,7 @@ func parsePortRange(portDef string) ([]tailcfg.PortRange, error) { rangeParts = slices.DeleteFunc(rangeParts, func(e string) bool { return e == "" }) - if len(rangeParts) != 2 { + if len(rangeParts) != portRangeParts { return nil, ErrInvalidPortRange } diff --git a/hscontrol/util/dns.go b/hscontrol/util/dns.go index bc48f592..c816efc6 100644 --- a/hscontrol/util/dns.go +++ b/hscontrol/util/dns.go @@ -20,6 +20,9 @@ const ( // value related to RFC 1123 and 952. LabelHostnameLength = 63 + + // minNameLength is the minimum length for usernames and hostnames. + minNameLength = 2 ) var invalidDNSRegex = regexp.MustCompile("[^a-z0-9-.]+") @@ -48,7 +51,7 @@ var ( // It cannot contain invalid characters. func ValidateUsername(username string) error { // Ensure the username meets the minimum length requirement - if len(username) < 2 { + if len(username) < minNameLength { return ErrUsernameTooShort } @@ -84,7 +87,7 @@ func ValidateUsername(username string) error { // This function does NOT modify the input - it only validates. // The hostname must already be lowercase and contain only valid characters. func ValidateHostname(name string) error { - if len(name) < 2 { + if len(name) < minNameLength { return fmt.Errorf("%w: %q", ErrHostnameTooShort, name) } if len(name) > LabelHostnameLength { diff --git a/hscontrol/util/string.go b/hscontrol/util/string.go index 0a37ec87..60f99420 100644 --- a/hscontrol/util/string.go +++ b/hscontrol/util/string.go @@ -9,6 +9,9 @@ import ( "tailscale.com/tailcfg" ) +// invalidStringRandomLength is the length of random bytes for invalid string generation. +const invalidStringRandomLength = 8 + // GenerateRandomBytes returns securely generated random bytes. // It will return an error if the system's secure random // number generator fails to function correctly, in which @@ -68,7 +71,7 @@ func MustGenerateRandomStringDNSSafe(size int) string { } func InvalidString() string { - hash, _ := GenerateRandomStringDNSSafe(8) + hash, _ := GenerateRandomStringDNSSafe(invalidStringRandomLength) return "invalid-" + hash } diff --git a/hscontrol/util/util.go b/hscontrol/util/util.go index b4ca0c51..77c83ece 100644 --- a/hscontrol/util/util.go +++ b/hscontrol/util/util.go @@ -24,9 +24,17 @@ var ( // Sentinel errors for traceroute parsing. var ( - ErrTracerouteEmpty = errors.New("empty traceroute output") - ErrTracerouteHeader = errors.New("parsing traceroute header") - ErrTracerouteNotReached = errors.New("traceroute did not reach target") + ErrTracerouteEmpty = errors.New("empty traceroute output") + ErrTracerouteHeader = errors.New("parsing traceroute header") + ErrTracerouteNotReached = errors.New("traceroute did not reach target") +) + +// Regex match group constants for traceroute parsing. +// The regexes capture hostname (group 1) and IP (group 2), plus the full match (group 0). +const ( + hostIPRegexGroups = 3 + nodeKeyPrefixLen = 8 + minTracerouteHeaderMatch = 2 // full match + hostname ) func TailscaleVersionNewerOrEqual(minimum, toCheck string) bool { @@ -111,7 +119,7 @@ func ParseTraceroute(output string) (Traceroute, error) { headerRegex := regexp.MustCompile(`(?i)(?:traceroute|tracing route) to ([^ ]+) (?:\[([^\]]+)\]|\(([^)]+)\))`) headerMatches := headerRegex.FindStringSubmatch(lines[0]) - if len(headerMatches) < 2 { + if len(headerMatches) < minTracerouteHeaderMatch { return Traceroute{}, fmt.Errorf("%w: %s", ErrTracerouteHeader, lines[0]) } @@ -210,12 +218,12 @@ func ParseTraceroute(output string) (Traceroute, error) { hopHostname = "*" // Skip any remaining asterisks remainder = strings.TrimLeft(remainder, "* ") - } else if hostMatch := hostIPRegex.FindStringSubmatch(remainder); len(hostMatch) >= 3 { + } else if hostMatch := hostIPRegex.FindStringSubmatch(remainder); len(hostMatch) >= hostIPRegexGroups { // Format: hostname (IP) hopHostname = hostMatch[1] hopIP, _ = netip.ParseAddr(hostMatch[2]) remainder = strings.TrimSpace(remainder[len(hostMatch[0]):]) - } else if hostMatch := hostIPBracketRegex.FindStringSubmatch(remainder); len(hostMatch) >= 3 { + } else if hostMatch := hostIPBracketRegex.FindStringSubmatch(remainder); len(hostMatch) >= hostIPRegexGroups { // Format: hostname [IP] (Windows) hopHostname = hostMatch[1] hopIP, _ = netip.ParseAddr(hostMatch[2]) @@ -307,8 +315,8 @@ func EnsureHostname(hostinfo *tailcfg.Hostinfo, machineKey, nodeKey string) stri } keyPrefix := key - if len(key) > 8 { - keyPrefix = key[:8] + if len(key) > nodeKeyPrefixLen { + keyPrefix = key[:nodeKeyPrefixLen] } return "node-" + keyPrefix