diff --git a/.golangci.yaml b/.golangci.yaml index 5ab873d4..2defdc88 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -30,7 +30,6 @@ linters: # We should strive to enable these: - wrapcheck - - goerr113 - dupl - makezero diff --git a/app.go b/app.go index d4dc8e71..08b67fe0 100644 --- a/app.go +++ b/app.go @@ -52,6 +52,11 @@ const ( Sqlite = "sqlite3" updateInterval = 5000 HTTPReadTimeout = 30 * time.Second + + errUnsupportedDatabase = Error("unsupported DB") + errUnsupportedLetsEncryptChallengeType = Error( + "unknown value for Lets Encrypt challenge type", + ) ) // Config contains the initial Headscale configuration. @@ -166,7 +171,7 @@ func NewHeadscale(cfg Config) (*Headscale, error) { case Sqlite: dbString = cfg.DBpath default: - return nil, errors.New("unsupported DB") + return nil, errUnsupportedDatabase } app := Headscale{ @@ -626,7 +631,7 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { return certManager.TLSConfig(), nil default: - return nil, errors.New("unknown value for TLSLetsEncryptChallengeType") + return nil, errUnsupportedLetsEncryptChallengeType } } else if h.cfg.TLSCertPath == "" { if !strings.HasPrefix(h.cfg.ServerURL, "http://") { diff --git a/cmd/headscale/cli/namespaces.go b/cmd/headscale/cli/namespaces.go index 28958ecd..361e9be3 100644 --- a/cmd/headscale/cli/namespaces.go +++ b/cmd/headscale/cli/namespaces.go @@ -4,6 +4,7 @@ import ( "fmt" survey "github.com/AlecAivazis/survey/v2" + "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/pterm/pterm" "github.com/rs/zerolog/log" @@ -19,6 +20,10 @@ func init() { namespaceCmd.AddCommand(renameNamespaceCmd) } +const ( + errMissingParameter = headscale.Error("missing parameters") +) + var namespaceCmd = &cobra.Command{ Use: "namespaces", Short: "Manage the namespaces of Headscale", @@ -29,7 +34,7 @@ var createNamespaceCmd = &cobra.Command{ Short: "Creates a new namespace", Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - return fmt.Errorf("missing parameters") + return errMissingParameter } return nil @@ -71,7 +76,7 @@ var destroyNamespaceCmd = &cobra.Command{ Short: "Destroys a namespace", Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - return fmt.Errorf("missing parameters") + return errMissingParameter } return nil @@ -197,7 +202,7 @@ var renameNamespaceCmd = &cobra.Command{ Args: func(cmd *cobra.Command, args []string) error { expectedArguments := 2 if len(args) < expectedArguments { - return fmt.Errorf("missing parameters") + return errMissingParameter } return nil diff --git a/cmd/headscale/cli/preauthkeys.go b/cmd/headscale/cli/preauthkeys.go index d14193f6..9d5c838f 100644 --- a/cmd/headscale/cli/preauthkeys.go +++ b/cmd/headscale/cli/preauthkeys.go @@ -178,7 +178,7 @@ var expirePreAuthKeyCmd = &cobra.Command{ Short: "Expire a preauthkey", Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - return fmt.Errorf("missing parameters") + return errMissingParameter } return nil diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index ed4b823c..958fb893 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -81,6 +81,7 @@ func LoadConfig(path string) error { errorText += "Fatal config error: server_url must start with https:// or http://\n" } if errorText != "" { + //nolint return errors.New(strings.TrimSuffix(errorText, "\n")) } else { return nil @@ -305,6 +306,8 @@ func getHeadscaleApp() (*headscale.Headscale, error) { // to avoid races minInactivityTimeout, _ := time.ParseDuration("65s") if viper.GetDuration("ephemeral_node_inactivity_timeout") <= minInactivityTimeout { + // TODO: Find a better way to return this text + //nolint err := fmt.Errorf( "ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s", viper.GetString("ephemeral_node_inactivity_timeout"), diff --git a/db.go b/db.go index 503eedcf..51363256 100644 --- a/db.go +++ b/db.go @@ -9,7 +9,10 @@ import ( "gorm.io/gorm/logger" ) -const dbVersion = "1" +const ( + dbVersion = "1" + errValueNotFound = Error("not found") +) // KV is a key-value store in a psql table. For future use... type KV struct { @@ -92,7 +95,7 @@ func (h *Headscale) getValue(key string) (string, error) { result.Error, gorm.ErrRecordNotFound, ) { - return "", errors.New("not found") + return "", errValueNotFound } return row.Value, nil diff --git a/machine.go b/machine.go index a728e28c..dcd79700 100644 --- a/machine.go +++ b/machine.go @@ -20,6 +20,12 @@ import ( "tailscale.com/types/wgkey" ) +const ( + errMachineNotFound = Error("machine not found") + errMachineAlreadyRegistered = Error("machine already registered") + errMachineRouteIsNotAvailable = Error("route is not available on machine") +) + // Machine is a Headscale client. type Machine struct { ID uint64 `gorm:"primary_key"` @@ -248,7 +254,7 @@ func (h *Headscale) GetMachine(namespace string, name string) (*Machine, error) } } - return nil, fmt.Errorf("machine not found") + return nil, errMachineNotFound } // GetMachineByID finds a Machine by ID and returns the Machine struct. @@ -615,7 +621,7 @@ func (h *Headscale) RegisterMachine( result.Error, gorm.ErrRecordNotFound, ) { - return nil, errors.New("Machine not found") + return nil, errMachineNotFound } log.Trace(). @@ -624,7 +630,7 @@ func (h *Headscale) RegisterMachine( Msg("Attempting to register machine") if machine.isAlreadyRegistered() { - err := errors.New("Machine already registered") + err := errMachineAlreadyRegistered log.Error(). Caller(). Err(err). @@ -740,9 +746,9 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error { for _, newRoute := range newRoutes { if !containsIPPrefix(availableRoutes, newRoute) { return fmt.Errorf( - "route (%s) is not available on node %s", + "route (%s) is not available on node %s: %w", machine.Name, - newRoute, + newRoute, errMachineRouteIsNotAvailable, ) } } diff --git a/preauth_keys.go b/preauth_keys.go index 2d242c55..50bc4746 100644 --- a/preauth_keys.go +++ b/preauth_keys.go @@ -16,6 +16,7 @@ const ( errPreAuthKeyNotFound = Error("AuthKey not found") errPreAuthKeyExpired = Error("AuthKey expired") errSingleUseAuthKeyHasBeenUsed = Error("AuthKey has already been used") + errNamespaceMismatch = Error("namespace mismatch") ) // PreAuthKey describes a pre-authorization key usable in a particular namespace. @@ -87,7 +88,7 @@ func (h *Headscale) GetPreAuthKey(namespace string, key string) (*PreAuthKey, er } if pak.Namespace.Name != namespace { - return nil, errors.New("Namespace mismatch") + return nil, errNamespaceMismatch } return pak, nil diff --git a/routes.go b/routes.go index 9845b76d..448095a5 100644 --- a/routes.go +++ b/routes.go @@ -2,12 +2,15 @@ package headscale import ( "encoding/json" - "fmt" "gorm.io/datatypes" "inet.af/netaddr" ) +const ( + errRouteIsNotAvailable = Error("route is not available") +) + // Deprecated: use machine function instead // GetAdvertisedNodeRoutes returns the subnet routes advertised by a node (identified by // namespace and node name). @@ -129,7 +132,7 @@ func (h *Headscale) EnableNodeRoute( } if !available { - return fmt.Errorf("route (%s) is not available on node %s", nodeName, routeStr) + return errRouteIsNotAvailable } routes, err := json.Marshal(enabledRoutes) diff --git a/utils.go b/utils.go index 6d62667d..9f7849e2 100644 --- a/utils.go +++ b/utils.go @@ -20,6 +20,12 @@ import ( "tailscale.com/types/wgkey" ) +const ( + errCannotDecryptReponse = Error("cannot decrypt response") + errResponseMissingNonce = Error("response missing nonce") + errCouldNotAllocateIP = Error("could not find any suitable IP") +) + // Error is used to compare errors as per https://dave.cheney.net/2016/04/07/constant-errors type Error string @@ -46,7 +52,7 @@ func decodeMsg( } // fmt.Println(string(decrypted)) if err := json.Unmarshal(decrypted, output); err != nil { - return fmt.Errorf("response: %w", err) + return err } return nil @@ -55,7 +61,7 @@ func decodeMsg( func decryptMsg(msg []byte, pubKey *wgkey.Key, privKey *wgkey.Private) ([]byte, error) { var nonce [24]byte if len(msg) < len(nonce)+1 { - return nil, fmt.Errorf("response missing nonce, len=%d", len(msg)) + return nil, errResponseMissingNonce } copy(nonce[:], msg) msg = msg[len(nonce):] @@ -63,7 +69,7 @@ func decryptMsg(msg []byte, pubKey *wgkey.Key, privKey *wgkey.Private) ([]byte, pub, pri := (*[32]byte)(pubKey), (*[32]byte)(privKey) decrypted, ok := box.Open(nil, msg, &nonce, pub, pri) if !ok { - return nil, fmt.Errorf("cannot decrypt response") + return nil, errCannotDecryptReponse } return decrypted, nil @@ -106,7 +112,7 @@ func (h *Headscale) getAvailableIP() (*netaddr.IP, error) { for { if !ipPrefix.Contains(ip) { - return nil, fmt.Errorf("could not find any suitable IP in %s", ipPrefix) + return nil, errCouldNotAllocateIP } // Some OS (including Linux) does not like when IPs ends with 0 or 255, which @@ -143,7 +149,7 @@ func (h *Headscale) getUsedIPs() ([]netaddr.IP, error) { if addr != "" { ip, err := netaddr.ParseIP(addr) if err != nil { - return nil, fmt.Errorf("failed to parse ip from database, %w", err) + return nil, fmt.Errorf("failed to parse ip from database: %w", err) } ips[index] = ip