mirror of
https://github.com/juanfont/headscale.git
synced 2026-02-07 20:04:00 +01:00
hscontrol/policy/v2: fix err113 lint issues (batch 5)
Add sentinel errors and use %w wrapping for all dynamic errors in the policy v2 package. This includes: - types.go: ~35 new sentinel errors for validation messages - utils.go: 8 new sentinel errors for port parsing - utils_test.go: Updated to use sentinel errors Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
71b1a43b6e
commit
744ba66131
@ -51,6 +51,55 @@ var (
|
||||
ErrACLAutogroupSelfInvalidSource = errors.New("autogroup:self destination requires sources to be users, groups, or autogroup:member only")
|
||||
)
|
||||
|
||||
// Policy validation errors.
|
||||
var (
|
||||
ErrUnknownAliasType = errors.New("unknown alias type")
|
||||
ErrUnknownAutoApprover = errors.New("unknown auto approver type")
|
||||
ErrUnknownOwnerType = errors.New("unknown owner type")
|
||||
ErrInvalidUsername = errors.New("username must contain @")
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrMultipleUsersFound = errors.New("multiple users found")
|
||||
ErrInvalidGroupFormat = errors.New("group must start with 'group:'")
|
||||
ErrInvalidTagFormat = errors.New("tag must start with 'tag:'")
|
||||
ErrInvalidHostname = errors.New("invalid hostname")
|
||||
ErrHostResolve = errors.New("error resolving host")
|
||||
ErrInvalidPrefix = errors.New("invalid prefix")
|
||||
ErrInvalidAutogroup = errors.New("invalid autogroup")
|
||||
ErrUnknownAutogroup = errors.New("unknown autogroup")
|
||||
ErrHostportMissingColon = errors.New("hostport must contain a colon")
|
||||
ErrTypeNotSupported = errors.New("type not supported")
|
||||
ErrInvalidAlias = errors.New("invalid alias format")
|
||||
ErrInvalidAutoApprover = errors.New("invalid auto approver format")
|
||||
ErrInvalidOwner = errors.New("invalid owner format")
|
||||
ErrGroupNotDefined = errors.New("group not defined in policy")
|
||||
ErrInvalidGroupMember = errors.New("invalid group member type")
|
||||
ErrGroupValueNotArray = errors.New("group value must be an array of users")
|
||||
ErrNestedGroups = errors.New("nested groups are not allowed")
|
||||
ErrInvalidHostIP = errors.New("hostname contains invalid IP address")
|
||||
ErrTagNotDefined = errors.New("tag not defined in policy")
|
||||
ErrAutoApproverNotAlias = errors.New("auto approver is not an alias")
|
||||
ErrInvalidACLAction = errors.New("invalid ACL action")
|
||||
ErrInvalidSSHAction = errors.New("invalid SSH action")
|
||||
ErrInvalidProtocolNumber = errors.New("invalid protocol number")
|
||||
ErrProtocolLeadingZero = errors.New("leading 0 not permitted in protocol number")
|
||||
ErrProtocolOutOfRange = errors.New("protocol number out of range (0-255)")
|
||||
ErrAutogroupNotSupported = errors.New("autogroup not supported in headscale")
|
||||
ErrAutogroupInternetSrc = errors.New("autogroup:internet can only be used in ACL destinations")
|
||||
ErrAutogroupSelfSrc = errors.New("autogroup:self can only be used in ACL destinations")
|
||||
ErrAutogroupNotSupportedACLSrc = errors.New("autogroup not supported for ACL sources")
|
||||
ErrAutogroupNotSupportedACLDst = errors.New("autogroup not supported for ACL destinations")
|
||||
ErrAutogroupNotSupportedSSHSrc = errors.New("autogroup not supported for SSH sources")
|
||||
ErrAutogroupNotSupportedSSHDst = errors.New("autogroup not supported for SSH destinations")
|
||||
ErrAutogroupNotSupportedSSHUsr = errors.New("autogroup not supported for SSH user")
|
||||
ErrHostNotDefined = errors.New("host not defined in policy")
|
||||
ErrSSHSourceAliasNotSupported = errors.New("alias not supported for SSH source")
|
||||
ErrSSHDestAliasNotSupported = errors.New("alias not supported for SSH destination")
|
||||
ErrUnknownSSHDestAlias = errors.New("unknown SSH destination alias type")
|
||||
ErrUnknownSSHSrcAlias = errors.New("unknown SSH source alias type")
|
||||
ErrUnknownField = errors.New("unknown field")
|
||||
ErrProtocolNoSpecificPorts = errors.New("protocol does not support specific ports")
|
||||
)
|
||||
|
||||
type Asterix int
|
||||
|
||||
func (a Asterix) Validate() error {
|
||||
@ -90,7 +139,7 @@ func (a AliasWithPorts) MarshalJSON() ([]byte, error) {
|
||||
case Asterix:
|
||||
alias = "*"
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown alias type: %T", v)
|
||||
return nil, fmt.Errorf("%w: %T", ErrUnknownAliasType, v)
|
||||
}
|
||||
|
||||
// If no ports are specified
|
||||
@ -141,7 +190,7 @@ func (u Username) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("username must contain @, got: %q", u)
|
||||
return fmt.Errorf("%w, got: %q", ErrInvalidUsername, u)
|
||||
}
|
||||
|
||||
func (u *Username) String() string {
|
||||
@ -203,11 +252,11 @@ func (u Username) resolveUser(users types.Users) (types.User, error) {
|
||||
}
|
||||
|
||||
if len(potentialUsers) == 0 {
|
||||
return types.User{}, fmt.Errorf("user with token %q not found", u.String())
|
||||
return types.User{}, fmt.Errorf("%w: token %q", ErrUserNotFound, u.String())
|
||||
}
|
||||
|
||||
if len(potentialUsers) > 1 {
|
||||
return types.User{}, fmt.Errorf("multiple users with token %q found: %s", u.String(), potentialUsers.String())
|
||||
return types.User{}, fmt.Errorf("%w: token %q found: %s", ErrMultipleUsersFound, u.String(), potentialUsers.String())
|
||||
}
|
||||
|
||||
return potentialUsers[0], nil
|
||||
@ -251,7 +300,7 @@ func (g Group) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf(`group must start with "group:", got: %q`, g)
|
||||
return fmt.Errorf("%w, got: %q", ErrInvalidGroupFormat, g)
|
||||
}
|
||||
|
||||
func (g *Group) UnmarshalJSON(b []byte) error {
|
||||
@ -318,7 +367,7 @@ func (t Tag) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf(`tag has to start with "tag:", got: %q`, t)
|
||||
return fmt.Errorf("%w, got: %q", ErrInvalidTagFormat, t)
|
||||
}
|
||||
|
||||
func (t *Tag) UnmarshalJSON(b []byte) error {
|
||||
@ -370,7 +419,7 @@ func (h Host) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("hostname %q is invalid", h)
|
||||
return fmt.Errorf("%w: %q", ErrInvalidHostname, h)
|
||||
}
|
||||
|
||||
func (h *Host) UnmarshalJSON(b []byte) error {
|
||||
@ -392,7 +441,7 @@ func (h Host) Resolve(p *Policy, _ types.Users, nodes views.Slice[types.NodeView
|
||||
|
||||
pref, ok := p.Hosts[h]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("resolving host: %q", h)
|
||||
return nil, fmt.Errorf("%w: %q", ErrHostResolve, h)
|
||||
}
|
||||
|
||||
err := pref.Validate()
|
||||
@ -429,7 +478,7 @@ func (p Prefix) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("prefix %q is invalid", p)
|
||||
return fmt.Errorf("%w: %q", ErrInvalidPrefix, p)
|
||||
}
|
||||
|
||||
func (p Prefix) String() string {
|
||||
@ -533,7 +582,7 @@ func (ag AutoGroup) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("autogroup is invalid, got: %q, must be one of %v", ag, autogroups)
|
||||
return fmt.Errorf("%w: got %q, must be one of %v", ErrInvalidAutogroup, ag, autogroups)
|
||||
}
|
||||
|
||||
func (ag *AutoGroup) UnmarshalJSON(b []byte) error {
|
||||
@ -595,7 +644,7 @@ func (ag AutoGroup) Resolve(p *Policy, users types.Users, nodes views.Slice[type
|
||||
return nil, ErrAutogroupSelfRequiresPerNodeResolution
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown autogroup %q", ag)
|
||||
return nil, fmt.Errorf("%w: %q", ErrUnknownAutogroup, ag)
|
||||
}
|
||||
}
|
||||
|
||||
@ -653,7 +702,7 @@ func (ve *AliasWithPorts) UnmarshalJSON(b []byte) error {
|
||||
|
||||
ve.Ports = ports
|
||||
} else {
|
||||
return errors.New(`hostport must contain a colon (":")`)
|
||||
return ErrHostportMissingColon
|
||||
}
|
||||
|
||||
ve.Alias, err = parseAlias(vs)
|
||||
@ -666,7 +715,7 @@ func (ve *AliasWithPorts) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("type %T not supported", vs)
|
||||
return fmt.Errorf("%w: %T", ErrTypeNotSupported, vs)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -721,15 +770,7 @@ func parseAlias(vs string) (Alias, error) {
|
||||
return ptr.To(Host(vs)), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`Invalid alias %q. An alias must be one of the following types:
|
||||
- wildcard (*)
|
||||
- user (containing an "@")
|
||||
- group (starting with "group:")
|
||||
- tag (starting with "tag:")
|
||||
- autogroup (starting with "autogroup:")
|
||||
- host
|
||||
|
||||
Please check the format and try again.`, vs)
|
||||
return nil, fmt.Errorf("%w: %q", ErrInvalidAlias, vs)
|
||||
}
|
||||
|
||||
// AliasEnc is used to deserialize a Alias.
|
||||
@ -791,7 +832,7 @@ func (a Aliases) MarshalJSON() ([]byte, error) {
|
||||
case Asterix:
|
||||
aliases[i] = "*"
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown alias type: %T", v)
|
||||
return nil, fmt.Errorf("%w: %T", ErrUnknownAliasType, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -877,7 +918,7 @@ func (aa AutoApprovers) MarshalJSON() ([]byte, error) {
|
||||
case *Group:
|
||||
approvers[i] = string(*v)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown auto approver type: %T", v)
|
||||
return nil, fmt.Errorf("%w: %T", ErrUnknownAutoApprover, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -894,12 +935,7 @@ func parseAutoApprover(s string) (AutoApprover, error) {
|
||||
return ptr.To(Tag(s)), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`Invalid AutoApprover %q. An alias must be one of the following types:
|
||||
- user (containing an "@")
|
||||
- group (starting with "group:")
|
||||
- tag (starting with "tag:")
|
||||
|
||||
Please check the format and try again.`, s)
|
||||
return nil, fmt.Errorf("%w: %q", ErrInvalidAutoApprover, s)
|
||||
}
|
||||
|
||||
// AutoApproverEnc is used to deserialize a AutoApprover.
|
||||
@ -976,7 +1012,7 @@ func (o Owners) MarshalJSON() ([]byte, error) {
|
||||
case *Tag:
|
||||
owners[i] = string(*v)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown owner type: %T", v)
|
||||
return nil, fmt.Errorf("%w: %T", ErrUnknownOwnerType, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -993,12 +1029,7 @@ func parseOwner(s string) (Owner, error) {
|
||||
return ptr.To(Tag(s)), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`Invalid Owner %q. An alias must be one of the following types:
|
||||
- user (containing an "@")
|
||||
- group (starting with "group:")
|
||||
- tag (starting with "tag:")
|
||||
|
||||
Please check the format and try again.`, s)
|
||||
return nil, fmt.Errorf("%w: %q", ErrInvalidOwner, s)
|
||||
}
|
||||
|
||||
type Usernames []Username
|
||||
@ -1017,7 +1048,7 @@ func (g Groups) Contains(group *Group) error {
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(`Group %q is not defined in the Policy, please define or remove the reference to it`, group)
|
||||
return fmt.Errorf("%w: %q", ErrGroupNotDefined, group)
|
||||
}
|
||||
|
||||
// UnmarshalJSON overrides the default JSON unmarshalling for Groups to ensure
|
||||
@ -1056,15 +1087,15 @@ func (g *Groups) UnmarshalJSON(b []byte) error {
|
||||
if str, ok := item.(string); ok {
|
||||
stringSlice = append(stringSlice, str)
|
||||
} else {
|
||||
return fmt.Errorf(`group "%s" contains invalid member type, expected string but got %T`, key, item)
|
||||
return fmt.Errorf("%w: group %q expected string but got %T", ErrInvalidGroupMember, key, item)
|
||||
}
|
||||
}
|
||||
|
||||
rawGroups[key] = stringSlice
|
||||
case string:
|
||||
return fmt.Errorf(`group "%s" value must be an array of users, got string: "%s"`, key, v)
|
||||
return fmt.Errorf("%w: group %q got string: %q", ErrGroupValueNotArray, key, v)
|
||||
default:
|
||||
return fmt.Errorf(`group "%s" value must be an array of users, got %T`, key, v)
|
||||
return fmt.Errorf("%w: group %q got %T", ErrGroupValueNotArray, key, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1081,7 +1112,7 @@ func (g *Groups) UnmarshalJSON(b []byte) error {
|
||||
err := username.Validate()
|
||||
if err != nil {
|
||||
if isGroup(u) {
|
||||
return fmt.Errorf("nested groups are not allowed, found %q inside %q", u, group)
|
||||
return fmt.Errorf("%w: found %q inside %q", ErrNestedGroups, u, group)
|
||||
}
|
||||
|
||||
return err
|
||||
@ -1121,7 +1152,7 @@ func (h *Hosts) UnmarshalJSON(b []byte) error {
|
||||
|
||||
err = prefix.parseString(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`hostname "%s" contains an invalid IP address: "%s"`, key, value)
|
||||
return fmt.Errorf("%w: hostname %q address %q", ErrInvalidHostIP, key, value)
|
||||
}
|
||||
|
||||
(*h)[host] = prefix
|
||||
@ -1170,7 +1201,7 @@ func (to TagOwners) MarshalJSON() ([]byte, error) {
|
||||
case *Tag:
|
||||
ownerStrs[i] = string(*v)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown owner type: %T", v)
|
||||
return nil, fmt.Errorf("%w: %T", ErrUnknownOwnerType, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1194,7 +1225,7 @@ func (to TagOwners) Contains(tagOwner *Tag) error {
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(`tag %q is not defined in the policy, please define or remove the reference to it`, tagOwner)
|
||||
return fmt.Errorf("%w: %q", ErrTagNotDefined, tagOwner)
|
||||
}
|
||||
|
||||
type AutoApproverPolicy struct {
|
||||
@ -1247,7 +1278,7 @@ func resolveAutoApprovers(p *Policy, users types.Users, nodes views.Slice[types.
|
||||
aa, ok := autoApprover.(Alias)
|
||||
if !ok {
|
||||
// Should never happen
|
||||
return nil, nil, fmt.Errorf("autoApprover %v is not an Alias", autoApprover)
|
||||
return nil, nil, fmt.Errorf("%w: %v", ErrAutoApproverNotAlias, autoApprover)
|
||||
}
|
||||
// If it does not resolve, that means the autoApprover is not associated with any IP addresses.
|
||||
ips, _ := aa.Resolve(p, users, nodes)
|
||||
@ -1262,7 +1293,7 @@ func resolveAutoApprovers(p *Policy, users types.Users, nodes views.Slice[types.
|
||||
aa, ok := autoApprover.(Alias)
|
||||
if !ok {
|
||||
// Should never happen
|
||||
return nil, nil, fmt.Errorf("autoApprover %v is not an Alias", autoApprover)
|
||||
return nil, nil, fmt.Errorf("%w: %v", ErrAutoApproverNotAlias, autoApprover)
|
||||
}
|
||||
// If it does not resolve, that means the autoApprover is not associated with any IP addresses.
|
||||
ips, _ := aa.Resolve(p, users, nodes)
|
||||
@ -1319,7 +1350,7 @@ func (a *Action) UnmarshalJSON(b []byte) error {
|
||||
case "accept":
|
||||
*a = ActionAccept
|
||||
default:
|
||||
return fmt.Errorf("invalid action %q, must be %q", str, ActionAccept)
|
||||
return fmt.Errorf("%w: %q, must be %q", ErrInvalidACLAction, str, ActionAccept)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1344,7 +1375,7 @@ func (a *SSHAction) UnmarshalJSON(b []byte) error {
|
||||
case "check":
|
||||
*a = SSHActionCheck
|
||||
default:
|
||||
return fmt.Errorf("invalid SSH action %q, must be one of: accept, check", str)
|
||||
return fmt.Errorf("%w: %q, must be one of: accept, check", ErrInvalidSSHAction, str)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1497,16 +1528,16 @@ func (p Protocol) validate() error {
|
||||
|
||||
// Check for leading zeros (not allowed by Tailscale)
|
||||
if str == "0" || (len(str) > 1 && str[0] == '0') {
|
||||
return fmt.Errorf("leading 0 not permitted in protocol number \"%s\"", str)
|
||||
return fmt.Errorf("%w: %q", ErrProtocolLeadingZero, str)
|
||||
}
|
||||
|
||||
protocolNumber, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid protocol %q: must be a known protocol name or valid protocol number 0-255", p)
|
||||
return fmt.Errorf("%w: %q must be a known protocol name or valid protocol number 0-255", ErrInvalidProtocolNumber, p)
|
||||
}
|
||||
|
||||
if protocolNumber < 0 || protocolNumber > 255 {
|
||||
return fmt.Errorf("protocol number %d out of range (0-255)", protocolNumber)
|
||||
return fmt.Errorf("%w: %d", ErrProtocolOutOfRange, protocolNumber)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1626,7 +1657,7 @@ func validateAutogroupSupported(ag *AutoGroup) error {
|
||||
}
|
||||
|
||||
if slices.Contains(autogroupNotSupported, *ag) {
|
||||
return fmt.Errorf("autogroup %q is not supported in headscale", *ag)
|
||||
return fmt.Errorf("%w: %q", ErrAutogroupNotSupported, *ag)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1638,15 +1669,15 @@ func validateAutogroupForSrc(src *AutoGroup) error {
|
||||
}
|
||||
|
||||
if src.Is(AutoGroupInternet) {
|
||||
return errors.New(`"autogroup:internet" used in source, it can only be used in ACL destinations`)
|
||||
return ErrAutogroupInternetSrc
|
||||
}
|
||||
|
||||
if src.Is(AutoGroupSelf) {
|
||||
return errors.New(`"autogroup:self" used in source, it can only be used in ACL destinations`)
|
||||
return ErrAutogroupSelfSrc
|
||||
}
|
||||
|
||||
if !slices.Contains(autogroupForSrc, *src) {
|
||||
return fmt.Errorf("autogroup %q is not supported for ACL sources, can be %v", *src, autogroupForSrc)
|
||||
return fmt.Errorf("%w: %q, can be %v", ErrAutogroupNotSupportedACLSrc, *src, autogroupForSrc)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1658,7 +1689,7 @@ func validateAutogroupForDst(dst *AutoGroup) error {
|
||||
}
|
||||
|
||||
if !slices.Contains(autogroupForDst, *dst) {
|
||||
return fmt.Errorf("autogroup %q is not supported for ACL destinations, can be %v", *dst, autogroupForDst)
|
||||
return fmt.Errorf("%w: %q, can be %v", ErrAutogroupNotSupportedACLDst, *dst, autogroupForDst)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1670,11 +1701,11 @@ func validateAutogroupForSSHSrc(src *AutoGroup) error {
|
||||
}
|
||||
|
||||
if src.Is(AutoGroupInternet) {
|
||||
return errors.New(`"autogroup:internet" used in SSH source, it can only be used in ACL destinations`)
|
||||
return ErrAutogroupInternetSrc
|
||||
}
|
||||
|
||||
if !slices.Contains(autogroupForSSHSrc, *src) {
|
||||
return fmt.Errorf("autogroup %q is not supported for SSH sources, can be %v", *src, autogroupForSSHSrc)
|
||||
return fmt.Errorf("%w: %q, can be %v", ErrAutogroupNotSupportedSSHSrc, *src, autogroupForSSHSrc)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1686,11 +1717,11 @@ func validateAutogroupForSSHDst(dst *AutoGroup) error {
|
||||
}
|
||||
|
||||
if dst.Is(AutoGroupInternet) {
|
||||
return errors.New(`"autogroup:internet" used in SSH destination, it can only be used in ACL destinations`)
|
||||
return ErrAutogroupInternetSrc
|
||||
}
|
||||
|
||||
if !slices.Contains(autogroupForSSHDst, *dst) {
|
||||
return fmt.Errorf("autogroup %q is not supported for SSH sources, can be %v", *dst, autogroupForSSHDst)
|
||||
return fmt.Errorf("%w: %q, can be %v", ErrAutogroupNotSupportedSSHDst, *dst, autogroupForSSHDst)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1702,7 +1733,7 @@ func validateAutogroupForSSHUser(user *AutoGroup) error {
|
||||
}
|
||||
|
||||
if !slices.Contains(autogroupForSSHUser, *user) {
|
||||
return fmt.Errorf("autogroup %q is not supported for SSH user, can be %v", *user, autogroupForSSHUser)
|
||||
return fmt.Errorf("%w: %q, can be %v", ErrAutogroupNotSupportedSSHUsr, *user, autogroupForSSHUser)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1829,7 +1860,7 @@ func (p *Policy) validate() error {
|
||||
case *Host:
|
||||
h := src
|
||||
if !p.Hosts.exist(*h) {
|
||||
errs = append(errs, fmt.Errorf(`host %q is not defined in the policy, please define or remove the reference to it`, *h))
|
||||
errs = append(errs, fmt.Errorf("%w: %q", ErrHostNotDefined, *h))
|
||||
}
|
||||
case *AutoGroup:
|
||||
ag := src
|
||||
@ -1866,7 +1897,7 @@ func (p *Policy) validate() error {
|
||||
switch h := dst.Alias.(type) {
|
||||
case *Host:
|
||||
if !p.Hosts.exist(*h) {
|
||||
errs = append(errs, fmt.Errorf(`host %q is not defined in the policy, please define or remove the reference to it`, *h))
|
||||
errs = append(errs, fmt.Errorf("%w: %q", ErrHostNotDefined, *h))
|
||||
}
|
||||
case *AutoGroup:
|
||||
err := validateAutogroupSupported(h)
|
||||
@ -2106,10 +2137,7 @@ func (a *SSHSrcAliases) UnmarshalJSON(b []byte) error {
|
||||
case *Username, *Group, *Tag, *AutoGroup:
|
||||
(*a)[i] = alias.Alias
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"alias %T is not supported for SSH source",
|
||||
alias.Alias,
|
||||
)
|
||||
return fmt.Errorf("%w: %T", ErrSSHSourceAliasNotSupported, alias.Alias)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2134,10 +2162,7 @@ func (a *SSHDstAliases) UnmarshalJSON(b []byte) error {
|
||||
"'autogroup:tagged' for tagged devices, or specific tags/users",
|
||||
ErrSSHWildcardDestination)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"alias %T is not supported for SSH destination",
|
||||
alias.Alias,
|
||||
)
|
||||
return fmt.Errorf("%w: %T", ErrSSHDestAliasNotSupported, alias.Alias)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2166,7 +2191,7 @@ func (a SSHDstAliases) MarshalJSON() ([]byte, error) {
|
||||
// with a proper error message explaining alternatives
|
||||
aliases[i] = "*"
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown SSH destination alias type: %T", v)
|
||||
return nil, fmt.Errorf("%w: %T", ErrUnknownSSHDestAlias, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2193,7 +2218,7 @@ func (a SSHSrcAliases) MarshalJSON() ([]byte, error) {
|
||||
case Asterix:
|
||||
aliases[i] = "*"
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown SSH source alias type: %T", v)
|
||||
return nil, fmt.Errorf("%w: %T", ErrUnknownSSHSrcAlias, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2272,7 +2297,7 @@ func unmarshalPolicy(b []byte) (*Policy, error) {
|
||||
ptr := serr.JSONPointer
|
||||
name := ptr.LastToken()
|
||||
|
||||
return nil, fmt.Errorf("unknown field %q", name)
|
||||
return nil, fmt.Errorf("%w: %q", ErrUnknownField, name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("parsing policy from bytes: %w", err)
|
||||
@ -2300,7 +2325,7 @@ func validateProtocolPortCompatibility(protocol Protocol, destinations []AliasWi
|
||||
for _, portRange := range dst.Ports {
|
||||
// Check if it's not a wildcard port (0-65535)
|
||||
if portRange.First != 0 || portRange.Last != 65535 {
|
||||
return fmt.Errorf("protocol %q does not support specific ports; only \"*\" is allowed", protocol)
|
||||
return fmt.Errorf("%w: %q, only \"*\" is allowed", ErrProtocolNoSpecificPorts, protocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,18 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
// Port parsing errors.
|
||||
var (
|
||||
ErrInputMissingColon = errors.New("input must contain a colon character separating destination and port")
|
||||
ErrInputStartsWithColon = errors.New("input cannot start with a colon character")
|
||||
ErrInputEndsWithColon = errors.New("input cannot end with a colon character")
|
||||
ErrInvalidPortRangeFormat = errors.New("invalid port range format")
|
||||
ErrPortRangeInverted = errors.New("invalid port range: first port is greater than last port")
|
||||
ErrPortMustBePositive = errors.New("first port must be >0, or use '*' for wildcard")
|
||||
ErrInvalidPortNumber = errors.New("invalid port number")
|
||||
ErrPortNumberOutOfRange = errors.New("port number out of range")
|
||||
)
|
||||
|
||||
// splitDestinationAndPort takes an input string and returns the destination and port as a tuple, or an error if the input is invalid.
|
||||
func splitDestinationAndPort(input string) (string, string, error) {
|
||||
// Find the last occurrence of the colon character
|
||||
@ -16,15 +28,15 @@ func splitDestinationAndPort(input string) (string, string, error) {
|
||||
|
||||
// Check if the colon character is present and not at the beginning or end of the string
|
||||
if lastColonIndex == -1 {
|
||||
return "", "", errors.New("input must contain a colon character separating destination and port")
|
||||
return "", "", ErrInputMissingColon
|
||||
}
|
||||
|
||||
if lastColonIndex == 0 {
|
||||
return "", "", errors.New("input cannot start with a colon character")
|
||||
return "", "", ErrInputStartsWithColon
|
||||
}
|
||||
|
||||
if lastColonIndex == len(input)-1 {
|
||||
return "", "", errors.New("input cannot end with a colon character")
|
||||
return "", "", ErrInputEndsWithColon
|
||||
}
|
||||
|
||||
// Split the string into destination and port based on the last colon
|
||||
@ -52,7 +64,7 @@ func parsePortRange(portDef string) ([]tailcfg.PortRange, error) {
|
||||
return e == ""
|
||||
})
|
||||
if len(rangeParts) != 2 {
|
||||
return nil, errors.New("invalid port range format")
|
||||
return nil, ErrInvalidPortRangeFormat
|
||||
}
|
||||
|
||||
first, err := parsePort(rangeParts[0])
|
||||
@ -66,7 +78,7 @@ func parsePortRange(portDef string) ([]tailcfg.PortRange, error) {
|
||||
}
|
||||
|
||||
if first > last {
|
||||
return nil, errors.New("invalid port range: first port is greater than last port")
|
||||
return nil, ErrPortRangeInverted
|
||||
}
|
||||
|
||||
portRanges = append(portRanges, tailcfg.PortRange{First: first, Last: last})
|
||||
@ -77,7 +89,7 @@ func parsePortRange(portDef string) ([]tailcfg.PortRange, error) {
|
||||
}
|
||||
|
||||
if port < 1 {
|
||||
return nil, errors.New("first port must be >0, or use '*' for wildcard")
|
||||
return nil, ErrPortMustBePositive
|
||||
}
|
||||
|
||||
portRanges = append(portRanges, tailcfg.PortRange{First: port, Last: port})
|
||||
@ -91,11 +103,11 @@ func parsePortRange(portDef string) ([]tailcfg.PortRange, error) {
|
||||
func parsePort(portStr string) (uint16, error) {
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return 0, errors.New("invalid port number")
|
||||
return 0, ErrInvalidPortNumber
|
||||
}
|
||||
|
||||
if port < 0 || port > 65535 {
|
||||
return 0, errors.New("port number out of range")
|
||||
return 0, ErrPortNumberOutOfRange
|
||||
}
|
||||
|
||||
return uint16(port), nil
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@ -24,9 +23,9 @@ func TestParseDestinationAndPort(t *testing.T) {
|
||||
{"tag:api-server:443", "tag:api-server", "443", nil},
|
||||
{"example-host-1:*", "example-host-1", "*", nil},
|
||||
{"hostname:80-90", "hostname", "80-90", nil},
|
||||
{"invalidinput", "", "", errors.New("input must contain a colon character separating destination and port")},
|
||||
{":invalid", "", "", errors.New("input cannot start with a colon character")},
|
||||
{"invalid:", "", "", errors.New("input cannot end with a colon character")},
|
||||
{"invalidinput", "", "", ErrInputMissingColon},
|
||||
{":invalid", "", "", ErrInputStartsWithColon},
|
||||
{"invalid:", "", "", ErrInputEndsWithColon},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user