From 58b532ae3c791bf13cd62a5c405a9fb4d4566bd0 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 20 Jan 2026 16:28:35 +0000 Subject: [PATCH] all: apply lint auto-fixes and update test expectations Apply additional golangci-lint auto-fixes (wsl_v5, formatting) and update SSH policy test error message expectations to match the new sentinel error formats introduced in the err113 fixes. --- cmd/headscale/cli/policy.go | 3 ++- cmd/headscale/cli/users.go | 2 +- cmd/hi/doctor.go | 3 ++- cmd/hi/run.go | 3 ++- hscontrol/db/db.go | 30 ++++++++++++++++-------- hscontrol/db/sqliteconfig/config.go | 3 ++- hscontrol/db/text_serialiser.go | 6 ++--- hscontrol/db/users.go | 6 +++-- hscontrol/derp/server/derp_server.go | 4 +++- hscontrol/handlers.go | 3 ++- hscontrol/mapper/batcher_lockfree.go | 3 ++- hscontrol/mapper/batcher_test.go | 8 ++++--- hscontrol/noise.go | 3 ++- hscontrol/policy/policy_test.go | 4 ++-- hscontrol/policy/v2/policy_test.go | 2 +- hscontrol/policy/v2/types_test.go | 8 +++---- hscontrol/poll.go | 6 +++-- hscontrol/state/maprequest_test.go | 1 - hscontrol/state/state.go | 9 ++++--- hscontrol/types/config.go | 23 ++++++++++-------- hscontrol/types/node.go | 3 ++- hscontrol/types/users_test.go | 6 +++-- hscontrol/util/dns.go | 12 +++++----- hscontrol/util/prompt.go | 1 + hscontrol/util/prompt_test.go | 2 ++ hscontrol/util/util.go | 3 ++- integration/api_auth_test.go | 12 ++++++---- integration/auth_key_test.go | 2 +- integration/auth_web_flow_test.go | 3 +-- integration/derp_verify_endpoint_test.go | 3 ++- integration/dns_test.go | 1 + 31 files changed, 110 insertions(+), 68 deletions(-) diff --git a/cmd/headscale/cli/policy.go b/cmd/headscale/cli/policy.go index f3921a64..4cdfe126 100644 --- a/cmd/headscale/cli/policy.go +++ b/cmd/headscale/cli/policy.go @@ -35,7 +35,8 @@ func init() { checkPolicy.Flags().StringP("file", "f", "", "Path to a policy file in HuJSON format") - if err := checkPolicy.MarkFlagRequired("file"); err != nil { + err := checkPolicy.MarkFlagRequired("file") + if err != nil { log.Fatal().Err(err).Msg("") } diff --git a/cmd/headscale/cli/users.go b/cmd/headscale/cli/users.go index 084548a9..086a82b6 100644 --- a/cmd/headscale/cli/users.go +++ b/cmd/headscale/cli/users.go @@ -16,7 +16,7 @@ import ( // Sentinel errors for CLI commands. var ( - ErrNameOrIDRequired = errors.New("--name or --identifier flag is required") + ErrNameOrIDRequired = errors.New("--name or --identifier flag is required") ErrMultipleUsersFoundUseID = errors.New("unable to determine user, query returned multiple users, use ID") ) diff --git a/cmd/hi/doctor.go b/cmd/hi/doctor.go index 2bfc41fd..b1cad514 100644 --- a/cmd/hi/doctor.go +++ b/cmd/hi/doctor.go @@ -321,7 +321,8 @@ func checkRequiredFiles() DoctorResult { for _, file := range requiredFiles { cmd := exec.CommandContext(context.Background(), "test", "-e", file) - if err := cmd.Run(); err != nil { + err := cmd.Run() + if err != nil { missingFiles = append(missingFiles, file) } } diff --git a/cmd/hi/run.go b/cmd/hi/run.go index e6c52634..881be20f 100644 --- a/cmd/hi/run.go +++ b/cmd/hi/run.go @@ -49,7 +49,8 @@ func runIntegrationTest(env *command.Env) error { log.Printf("Running pre-flight system checks...") } - if err := runDoctorCheck(env.Context()); err != nil { + err := runDoctorCheck(env.Context()) + if err != nil { return fmt.Errorf("pre-flight checks failed: %w", err) } diff --git a/hscontrol/db/db.go b/hscontrol/db/db.go index ff9379c1..5a3364c9 100644 --- a/hscontrol/db/db.go +++ b/hscontrol/db/db.go @@ -261,7 +261,8 @@ AND auth_key_id NOT IN ( if err == nil && routesExists { log.Info().Msg("Dropping leftover routes table") - if err := tx.Exec("DROP TABLE routes").Error; err != nil { + err := tx.Exec("DROP TABLE routes").Error + if err != nil { return fmt.Errorf("dropping routes table: %w", err) } } @@ -294,7 +295,8 @@ AND auth_key_id NOT IN ( _ = tx.Exec("DROP TABLE IF EXISTS " + table + "_old").Error // Rename current table to _old - if err := tx.Exec("ALTER TABLE " + table + " RENAME TO " + table + "_old").Error; err != nil { + err := tx.Exec("ALTER TABLE " + table + " RENAME TO " + table + "_old").Error + if err != nil { return fmt.Errorf("renaming table %s to %s_old: %w", table, table, err) } } @@ -368,7 +370,8 @@ AND auth_key_id NOT IN ( } for _, createSQL := range tableCreationSQL { - if err := tx.Exec(createSQL).Error; err != nil { + err := tx.Exec(createSQL).Error + if err != nil { return fmt.Errorf("creating new table: %w", err) } } @@ -397,7 +400,8 @@ AND auth_key_id NOT IN ( } for _, copySQL := range dataCopySQL { - if err := tx.Exec(copySQL).Error; err != nil { + err := tx.Exec(copySQL).Error + if err != nil { return fmt.Errorf("copying data: %w", err) } } @@ -420,14 +424,16 @@ AND auth_key_id NOT IN ( } for _, indexSQL := range indexes { - if err := tx.Exec(indexSQL).Error; err != nil { + err := tx.Exec(indexSQL).Error + if err != nil { return fmt.Errorf("creating index: %w", err) } } // Drop old tables only after everything succeeds for _, table := range tablesToRename { - if err := tx.Exec("DROP TABLE IF EXISTS " + table + "_old").Error; err != nil { + err := tx.Exec("DROP TABLE IF EXISTS " + table + "_old").Error + if err != nil { log.Warn().Str("table", table+"_old").Err(err).Msg("Failed to drop old table, but migration succeeded") } } @@ -946,18 +952,21 @@ func runMigrations(cfg types.DatabaseConfig, dbConn *gorm.DB, migrations *gormig if needsFKDisabled { // Disable foreign keys for this migration - if err := dbConn.Exec("PRAGMA foreign_keys = OFF").Error; err != nil { + err := dbConn.Exec("PRAGMA foreign_keys = OFF").Error + if err != nil { return fmt.Errorf("disabling foreign keys for migration %s: %w", migrationID, err) } } else { // Ensure foreign keys are enabled for this migration - if err := dbConn.Exec("PRAGMA foreign_keys = ON").Error; err != nil { + err := dbConn.Exec("PRAGMA foreign_keys = ON").Error + if err != nil { return fmt.Errorf("enabling foreign keys for migration %s: %w", migrationID, err) } } // Run up to this specific migration (will only run the next pending migration) - if err := migrations.MigrateTo(migrationID); err != nil { + err := migrations.MigrateTo(migrationID) + if err != nil { return fmt.Errorf("running migration %s: %w", migrationID, err) } } @@ -1009,7 +1018,8 @@ func runMigrations(cfg types.DatabaseConfig, dbConn *gorm.DB, migrations *gormig } } else { // PostgreSQL can run all migrations in one block - no foreign key issues - if err := migrations.Migrate(); err != nil { + err := migrations.Migrate() + if err != nil { return err } } diff --git a/hscontrol/db/sqliteconfig/config.go b/hscontrol/db/sqliteconfig/config.go index e6386718..0088fe86 100644 --- a/hscontrol/db/sqliteconfig/config.go +++ b/hscontrol/db/sqliteconfig/config.go @@ -365,7 +365,8 @@ func (c *Config) Validate() error { // ToURL builds a properly encoded SQLite connection string using _pragma parameters // compatible with modernc.org/sqlite driver. func (c *Config) ToURL() (string, error) { - if err := c.Validate(); err != nil { + err := c.Validate() + if err != nil { return "", fmt.Errorf("invalid config: %w", err) } diff --git a/hscontrol/db/text_serialiser.go b/hscontrol/db/text_serialiser.go index b1d294ea..7a9f7010 100644 --- a/hscontrol/db/text_serialiser.go +++ b/hscontrol/db/text_serialiser.go @@ -12,9 +12,9 @@ import ( // Sentinel errors for text serialisation. var ( - ErrTextUnmarshalFailed = errors.New("failed to unmarshal text value") - ErrUnsupportedType = errors.New("unsupported type") - ErrTextMarshalerOnly = errors.New("only encoding.TextMarshaler is supported") + ErrTextUnmarshalFailed = errors.New("failed to unmarshal text value") + ErrUnsupportedType = errors.New("unsupported type") + ErrTextMarshalerOnly = errors.New("only encoding.TextMarshaler is supported") ) // Got from https://github.com/xdg-go/strum/blob/main/types.go diff --git a/hscontrol/db/users.go b/hscontrol/db/users.go index be073999..8af317dc 100644 --- a/hscontrol/db/users.go +++ b/hscontrol/db/users.go @@ -28,7 +28,8 @@ func (hsdb *HSDatabase) CreateUser(user types.User) (*types.User, error) { // CreateUser creates a new User. Returns error if could not be created // or another user already exists. func CreateUser(tx *gorm.DB, user types.User) (*types.User, error) { - if err := util.ValidateHostname(user.Name); err != nil { + err := util.ValidateHostname(user.Name) + if err != nil { return nil, err } if err := tx.Create(&user).Error; err != nil { @@ -164,7 +165,8 @@ func ListUsers(tx *gorm.DB, where ...*types.User) ([]types.User, error) { } users := []types.User{} - if err := tx.Where(user).Find(&users).Error; err != nil { + err := tx.Where(user).Find(&users).Error + if err != nil { return nil, err } diff --git a/hscontrol/derp/server/derp_server.go b/hscontrol/derp/server/derp_server.go index 562061e2..6a66a7e6 100644 --- a/hscontrol/derp/server/derp_server.go +++ b/hscontrol/derp/server/derp_server.go @@ -74,6 +74,7 @@ func (d *DERPServer) GenerateRegion() (tailcfg.DERPRegion, error) { if err != nil { return tailcfg.DERPRegion{}, err } + var ( host string port int @@ -416,7 +417,8 @@ type DERPVerifyTransport struct { func (t *DERPVerifyTransport) RoundTrip(req *http.Request) (*http.Response, error) { buf := new(bytes.Buffer) - if err := t.handleVerifyRequest(req, buf); err != nil { + err := t.handleVerifyRequest(req, buf) + if err != nil { log.Error().Caller().Err(err).Msg("Failed to handle client verify request: ") return nil, err diff --git a/hscontrol/handlers.go b/hscontrol/handlers.go index ef214536..2e809061 100644 --- a/hscontrol/handlers.go +++ b/hscontrol/handlers.go @@ -154,7 +154,8 @@ func (h *Headscale) KeyHandler( } writer.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(writer).Encode(resp); err != nil { + err := json.NewEncoder(writer).Encode(resp) + if err != nil { log.Error().Err(err).Msg("failed to encode key response") } diff --git a/hscontrol/mapper/batcher_lockfree.go b/hscontrol/mapper/batcher_lockfree.go index 2580b7f4..d4b5ff6f 100644 --- a/hscontrol/mapper/batcher_lockfree.go +++ b/hscontrol/mapper/batcher_lockfree.go @@ -667,7 +667,8 @@ func (mc *multiChannelNodeConn) send(data *tailcfg.MapResponse) error { Str("conn.id", conn.id).Int("connection_index", i). Msg("send: attempting to send to connection") - if err := conn.send(data); err != nil { + err := conn.send(data) + if err != nil { lastErr = err failedConnections = append(failedConnections, i) diff --git a/hscontrol/mapper/batcher_test.go b/hscontrol/mapper/batcher_test.go index 4f950d15..7cc746a4 100644 --- a/hscontrol/mapper/batcher_test.go +++ b/hscontrol/mapper/batcher_test.go @@ -123,9 +123,9 @@ const ( // Channel configuration. NORMAL_BUFFER_SIZE = 50 - SMALL_BUFFER_SIZE = 3 - TINY_BUFFER_SIZE = 1 // For maximum contention - LARGE_BUFFER_SIZE = 200 + SMALL_BUFFER_SIZE = 3 + TINY_BUFFER_SIZE = 1 // For maximum contention + LARGE_BUFFER_SIZE = 200 ) // TestData contains all test entities created for a test scenario. @@ -1145,6 +1145,7 @@ func XTestBatcherChannelClosingRace(t *testing.T) { wg.Go(func() { runtime.Gosched() // Yield to introduce timing variability + _ = batcher.AddNode(testNode.n.ID, ch2, tailcfg.CapabilityVersion(100)) }) @@ -1747,6 +1748,7 @@ func XTestBatcherScalability(t *testing.T) { for i := range testNodes { node := testNodes[i] _ = batcher.AddNode(node.n.ID, node.ch, tailcfg.CapabilityVersion(100)) + connectedNodesMutex.Lock() connectedNodes[node.n.ID] = true diff --git a/hscontrol/noise.go b/hscontrol/noise.go index d8e83154..67021d31 100644 --- a/hscontrol/noise.go +++ b/hscontrol/noise.go @@ -283,7 +283,8 @@ func (ns *noiseServer) NoiseRegistrationHandler( writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusOK) - if err := json.NewEncoder(writer).Encode(registerResponse); err != nil { + err := json.NewEncoder(writer).Encode(registerResponse) + if err != nil { log.Error().Caller().Err(err).Msg("NoiseRegistrationHandler: failed to encode RegisterResponse") return } diff --git a/hscontrol/policy/policy_test.go b/hscontrol/policy/policy_test.go index ee4818aa..a46f30d2 100644 --- a/hscontrol/policy/policy_test.go +++ b/hscontrol/policy/policy_test.go @@ -1214,7 +1214,7 @@ func TestSSHPolicyRules(t *testing.T) { ] }`, expectErr: true, - errorMessage: `invalid SSH action "invalid", must be one of: accept, check`, + errorMessage: `invalid SSH action: "invalid", must be one of: accept, check`, }, { name: "invalid-check-period", @@ -1249,7 +1249,7 @@ func TestSSHPolicyRules(t *testing.T) { ] }`, expectErr: true, - errorMessage: "autogroup \"autogroup:invalid\" is not supported", + errorMessage: `autogroup not supported for SSH: "autogroup:invalid" for SSH user`, }, { name: "autogroup-nonroot-should-use-wildcard-with-root-excluded", diff --git a/hscontrol/policy/v2/policy_test.go b/hscontrol/policy/v2/policy_test.go index 3e3c70f5..dc5969b5 100644 --- a/hscontrol/policy/v2/policy_test.go +++ b/hscontrol/policy/v2/policy_test.go @@ -106,7 +106,7 @@ func TestInvalidateAutogroupSelfCache(t *testing.T) { require.NoError(t, err) } - require.Equal(t, len(initialNodes), len(pm.filterRulesMap)) + require.Len(t, pm.filterRulesMap, len(initialNodes)) tests := []struct { name string diff --git a/hscontrol/policy/v2/types_test.go b/hscontrol/policy/v2/types_test.go index b5e5a210..8164f576 100644 --- a/hscontrol/policy/v2/types_test.go +++ b/hscontrol/policy/v2/types_test.go @@ -3131,8 +3131,8 @@ func TestACL_UnmarshalJSON_WithCommentFields(t *testing.T) { require.NoError(t, err) assert.Equal(t, tt.expected.Action, acl.Action) assert.Equal(t, tt.expected.Protocol, acl.Protocol) - assert.Equal(t, len(tt.expected.Sources), len(acl.Sources)) - assert.Equal(t, len(tt.expected.Destinations), len(acl.Destinations)) + assert.Len(t, acl.Sources, len(tt.expected.Sources)) + assert.Len(t, acl.Destinations, len(tt.expected.Destinations)) // Compare sources for i, expectedSrc := range tt.expected.Sources { @@ -3179,8 +3179,8 @@ func TestACL_UnmarshalJSON_Roundtrip(t *testing.T) { // Should be equal assert.Equal(t, original.Action, unmarshaled.Action) assert.Equal(t, original.Protocol, unmarshaled.Protocol) - assert.Equal(t, len(original.Sources), len(unmarshaled.Sources)) - assert.Equal(t, len(original.Destinations), len(unmarshaled.Destinations)) + assert.Len(t, unmarshaled.Sources, len(original.Sources)) + assert.Len(t, unmarshaled.Destinations, len(original.Destinations)) } func TestACL_UnmarshalJSON_PolicyIntegration(t *testing.T) { diff --git a/hscontrol/poll.go b/hscontrol/poll.go index d3c9f1ef..464d252d 100644 --- a/hscontrol/poll.go +++ b/hscontrol/poll.go @@ -249,7 +249,8 @@ func (m *mapSession) serveLongPoll() { return } - if err := m.writeMap(update); err != nil { + err := m.writeMap(update) + if err != nil { m.errf(err, "cannot write update to client") return } @@ -258,7 +259,8 @@ func (m *mapSession) serveLongPoll() { m.resetKeepAlive() case <-m.keepAliveTicker.C: - if err := m.writeMap(&keepAlive); err != nil { + err := m.writeMap(&keepAlive) + if err != nil { m.errf(err, "cannot write keep alive") return } diff --git a/hscontrol/state/maprequest_test.go b/hscontrol/state/maprequest_test.go index ce6804e4..8a842e49 100644 --- a/hscontrol/state/maprequest_test.go +++ b/hscontrol/state/maprequest_test.go @@ -133,4 +133,3 @@ func TestNetInfoPreservationInRegistrationFlow(t *testing.T) { assert.Equal(t, 7, result.PreferredDERP, "Should preserve DERP region from existing node") }) } - diff --git a/hscontrol/state/state.go b/hscontrol/state/state.go index bb929faa..c720c271 100644 --- a/hscontrol/state/state.go +++ b/hscontrol/state/state.go @@ -187,7 +187,8 @@ func NewState(cfg *types.Config) (*State, error) { func (s *State) Close() error { s.nodeStore.Stop() - if err := s.db.Close(); err != nil { + err := s.db.Close() + if err != nil { return fmt.Errorf("closing database: %w", err) } @@ -741,7 +742,8 @@ func (s *State) SetApprovedRoutes(nodeID types.NodeID, routes []netip.Prefix) (t // RenameNode changes the display name of a node. func (s *State) RenameNode(nodeID types.NodeID, newName string) (types.NodeView, change.Change, error) { - if err := util.ValidateHostname(newName); err != nil { + err := util.ValidateHostname(newName) + if err != nil { return types.NodeView{}, change.Change{}, fmt.Errorf("renaming node: %w", err) } @@ -1214,7 +1216,8 @@ func (s *State) createAndSaveNewNode(params newNodeParams) (types.NodeView, erro // New node - database first to get ID, then NodeStore savedNode, err := hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) { - if err := tx.Save(&nodeToRegister).Error; err != nil { + err := tx.Save(&nodeToRegister).Error + if err != nil { return nil, fmt.Errorf("failed to save node: %w", err) } diff --git a/hscontrol/types/config.go b/hscontrol/types/config.go index 5a32147d..d57943f6 100644 --- a/hscontrol/types/config.go +++ b/hscontrol/types/config.go @@ -29,16 +29,16 @@ const ( PKCEMethodPlain string = "plain" PKCEMethodS256 string = "S256" - defaultNodeStoreBatchSize = 100 - defaultWALAutocheckpoint = 1000 // SQLite default + defaultNodeStoreBatchSize = 100 + defaultWALAutocheckpoint = 1000 // SQLite default ) var ( - errOidcMutuallyExclusive = errors.New("oidc_client_secret and oidc_client_secret_path are mutually exclusive") - errServerURLSuffix = errors.New("server_url cannot be part of base_domain in a way that could make the DERP and headscale server unreachable") - errServerURLSame = errors.New("server_url cannot use the same domain as base_domain in a way that could make the DERP and headscale server unreachable") - errInvalidPKCEMethod = errors.New("pkce.method must be either 'plain' or 'S256'") - errNoPrefixConfigured = errors.New("no IPv4 or IPv6 prefix configured, minimum one prefix is required") + errOidcMutuallyExclusive = errors.New("oidc_client_secret and oidc_client_secret_path are mutually exclusive") + errServerURLSuffix = errors.New("server_url cannot be part of base_domain in a way that could make the DERP and headscale server unreachable") + errServerURLSame = errors.New("server_url cannot use the same domain as base_domain in a way that could make the DERP and headscale server unreachable") + errInvalidPKCEMethod = errors.New("pkce.method must be either 'plain' or 'S256'") + errNoPrefixConfigured = errors.New("no IPv4 or IPv6 prefix configured, minimum one prefix is required") errInvalidAllocationStrategy = errors.New("invalid prefixes.allocation strategy") ) @@ -406,7 +406,8 @@ func LoadConfig(path string, isFile bool) error { viper.SetDefault("prefixes.allocation", string(IPAllocationStrategySequential)) if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { + var configFileNotFoundError viper.ConfigFileNotFoundError + if errors.As(err, &configFileNotFoundError) { log.Warn().Msg("No config file found, using defaults") return nil } @@ -446,7 +447,8 @@ func validateServerConfig() error { depr.fatal("oidc.map_legacy_users") if viper.GetBool("oidc.enabled") { - if err := validatePKCEMethod(viper.GetString("oidc.pkce.method")); err != nil { + err := validatePKCEMethod(viper.GetString("oidc.pkce.method")) + if err != nil { return err } } @@ -984,7 +986,8 @@ func LoadServerConfig() (*Config, error) { // - Control plane runs on login.tailscale.com/controlplane.tailscale.com // - MagicDNS (BaseDomain) for users is on a *.ts.net domain per tailnet (e.g. tail-scale.ts.net) if dnsConfig.BaseDomain != "" { - if err := isSafeServerURL(serverURL, dnsConfig.BaseDomain); err != nil { + err := isSafeServerURL(serverURL, dnsConfig.BaseDomain) + if err != nil { return nil, err } } diff --git a/hscontrol/types/node.go b/hscontrol/types/node.go index ea96284c..e1fa5794 100644 --- a/hscontrol/types/node.go +++ b/hscontrol/types/node.go @@ -573,7 +573,8 @@ func (node *Node) ApplyHostnameFromHostInfo(hostInfo *tailcfg.Hostinfo) { } newHostname := strings.ToLower(hostInfo.Hostname) - if err := util.ValidateHostname(newHostname); err != nil { + err := util.ValidateHostname(newHostname) + if err != nil { log.Warn(). Str("node.id", node.ID.String()). Str("current_hostname", node.Hostname). diff --git a/hscontrol/types/users_test.go b/hscontrol/types/users_test.go index acd88434..35c84a26 100644 --- a/hscontrol/types/users_test.go +++ b/hscontrol/types/users_test.go @@ -66,7 +66,8 @@ func TestUnmarshallOIDCClaims(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got OIDCClaims - if err := json.Unmarshal([]byte(tt.jsonstr), &got); err != nil { + err := json.Unmarshal([]byte(tt.jsonstr), &got) + if err != nil { t.Errorf("UnmarshallOIDCClaims() error = %v", err) return } @@ -482,7 +483,8 @@ func TestOIDCClaimsJSONToUser(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got OIDCClaims - if err := json.Unmarshal([]byte(tt.jsonstr), &got); err != nil { + err := json.Unmarshal([]byte(tt.jsonstr), &got) + if err != nil { t.Errorf("TestOIDCClaimsJSONToUser() error = %v", err) return } diff --git a/hscontrol/util/dns.go b/hscontrol/util/dns.go index c816efc6..8ec40790 100644 --- a/hscontrol/util/dns.go +++ b/hscontrol/util/dns.go @@ -31,17 +31,17 @@ var ErrInvalidHostName = errors.New("invalid hostname") // Sentinel errors for username validation. var ( - ErrUsernameTooShort = errors.New("username must be at least 2 characters long") + ErrUsernameTooShort = errors.New("username must be at least 2 characters long") ErrUsernameMustStartLetter = errors.New("username must start with a letter") - ErrUsernameTooManyAt = errors.New("username cannot contain more than one '@'") - ErrUsernameInvalidChar = errors.New("username contains invalid character") + ErrUsernameTooManyAt = errors.New("username cannot contain more than one '@'") + ErrUsernameInvalidChar = errors.New("username contains invalid character") ) // Sentinel errors for hostname validation. var ( - ErrHostnameTooShort = errors.New("hostname too short, must be at least 2 characters") - ErrHostnameHyphenEnds = errors.New("hostname cannot start or end with a hyphen") - ErrHostnameDotEnds = errors.New("hostname cannot start or end with a dot") + ErrHostnameTooShort = errors.New("hostname too short, must be at least 2 characters") + ErrHostnameHyphenEnds = errors.New("hostname cannot start or end with a hyphen") + ErrHostnameDotEnds = errors.New("hostname cannot start or end with a dot") ) // ValidateUsername checks if a username is valid. diff --git a/hscontrol/util/prompt.go b/hscontrol/util/prompt.go index 410f6c2e..5f0adede 100644 --- a/hscontrol/util/prompt.go +++ b/hscontrol/util/prompt.go @@ -14,6 +14,7 @@ func YesNo(msg string) bool { fmt.Fprint(os.Stderr, msg+" [y/n] ") var resp string + _, _ = fmt.Scanln(&resp) resp = strings.ToLower(resp) diff --git a/hscontrol/util/prompt_test.go b/hscontrol/util/prompt_test.go index c6fcb702..ac405f8c 100644 --- a/hscontrol/util/prompt_test.go +++ b/hscontrol/util/prompt_test.go @@ -106,6 +106,7 @@ func TestYesNo(t *testing.T) { // Check that the prompt was written to stderr var stderrBuf bytes.Buffer + _, _ = io.Copy(&stderrBuf, stderrR) stderrR.Close() @@ -149,6 +150,7 @@ func TestYesNoPromptMessage(t *testing.T) { // Check that the custom message was included in the prompt var stderrBuf bytes.Buffer + _, _ = io.Copy(&stderrBuf, stderrR) stderrR.Close() diff --git a/hscontrol/util/util.go b/hscontrol/util/util.go index 77c83ece..8e777349 100644 --- a/hscontrol/util/util.go +++ b/hscontrol/util/util.go @@ -323,7 +323,8 @@ func EnsureHostname(hostinfo *tailcfg.Hostinfo, machineKey, nodeKey string) stri } lowercased := strings.ToLower(hostinfo.Hostname) - if err := ValidateHostname(lowercased); err == nil { + err := ValidateHostname(lowercased) + if err == nil { return lowercased } diff --git a/integration/api_auth_test.go b/integration/api_auth_test.go index ed4a1f4d..989cb9d4 100644 --- a/integration/api_auth_test.go +++ b/integration/api_auth_test.go @@ -79,7 +79,7 @@ func TestAPIAuthenticationBypass(t *testing.T) { t.Run("HTTP_NoAuthHeader", func(t *testing.T) { // Test 1: Request without any Authorization header // Expected: Should return 401 with ONLY "Unauthorized" text, no user data - req, err := http.NewRequestWithContext(context.Background(), "GET", apiURL, nil) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, apiURL, nil) require.NoError(t, err) resp, err := client.Do(req) @@ -131,7 +131,7 @@ func TestAPIAuthenticationBypass(t *testing.T) { t.Run("HTTP_InvalidAuthHeader", func(t *testing.T) { // Test 2: Request with invalid Authorization header (missing "Bearer " prefix) // Expected: Should return 401 with ONLY "Unauthorized" text, no user data - req, err := http.NewRequestWithContext(context.Background(), "GET", apiURL, nil) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, apiURL, nil) require.NoError(t, err) req.Header.Set("Authorization", "InvalidToken") @@ -165,7 +165,7 @@ func TestAPIAuthenticationBypass(t *testing.T) { // Test 3: Request with Bearer prefix but invalid token // Expected: Should return 401 with ONLY "Unauthorized" text, no user data // Note: Both malformed and properly formatted invalid tokens should return 401 - req, err := http.NewRequestWithContext(context.Background(), "GET", apiURL, nil) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, apiURL, nil) require.NoError(t, err) req.Header.Set("Authorization", "Bearer invalid-token-12345") @@ -198,7 +198,7 @@ func TestAPIAuthenticationBypass(t *testing.T) { t.Run("HTTP_ValidAPIKey", func(t *testing.T) { // Test 4: Request with valid API key // Expected: Should return 200 with user data (this is the authorized case) - req, err := http.NewRequestWithContext(context.Background(), "GET", apiURL, nil) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, apiURL, nil) require.NoError(t, err) req.Header.Set("Authorization", "Bearer "+validAPIKey) @@ -294,6 +294,7 @@ func TestAPIAuthenticationBypassCurl(t *testing.T) { ) var responseBodySb295 strings.Builder + for _, line := range lines { if after, ok := strings.CutPrefix(line, "HTTP_CODE:"); ok { httpCode = after @@ -301,6 +302,7 @@ func TestAPIAuthenticationBypassCurl(t *testing.T) { responseBodySb295.WriteString(line) } } + responseBody += responseBodySb295.String() // Should return 401 @@ -345,6 +347,7 @@ func TestAPIAuthenticationBypassCurl(t *testing.T) { ) var responseBodySb344 strings.Builder + for _, line := range lines { if after, ok := strings.CutPrefix(line, "HTTP_CODE:"); ok { httpCode = after @@ -352,6 +355,7 @@ func TestAPIAuthenticationBypassCurl(t *testing.T) { responseBodySb344.WriteString(line) } } + responseBody += responseBodySb344.String() assert.Equal(t, "401", httpCode) diff --git a/integration/auth_key_test.go b/integration/auth_key_test.go index 0bced1ed..7e7747e6 100644 --- a/integration/auth_key_test.go +++ b/integration/auth_key_test.go @@ -355,7 +355,7 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) { status, err := client.Status() assert.NoError(ct, err, "Failed to get status for client %s", client.Hostname()) assert.Equal(ct, "user1@test.no", status.User[status.Self.UserID].LoginName, "Client %s should be logged in as user1 after user switch, got %s", client.Hostname(), status.User[status.Self.UserID].LoginName) - }, 30*time.Second, 2*time.Second, fmt.Sprintf("validating %s is logged in as user1 after auth key user switch", client.Hostname())) + }, 30*time.Second, 2*time.Second, "validating %s is logged in as user1 after auth key user switch", client.Hostname()) } } diff --git a/integration/auth_web_flow_test.go b/integration/auth_web_flow_test.go index 256d7e4d..6dbf6dfe 100644 --- a/integration/auth_web_flow_test.go +++ b/integration/auth_web_flow_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "net/netip" "slices" "testing" @@ -364,7 +363,7 @@ func TestAuthWebFlowLogoutAndReloginNewUser(t *testing.T) { status, err := client.Status() assert.NoError(ct, err, "Failed to get status for client %s", client.Hostname()) assert.Equal(ct, "user1@test.no", status.User[status.Self.UserID].LoginName, "Client %s should be logged in as user1 after web flow user switch, got %s", client.Hostname(), status.User[status.Self.UserID].LoginName) - }, 30*time.Second, 2*time.Second, fmt.Sprintf("validating %s is logged in as user1 after web flow user switch", client.Hostname())) + }, 30*time.Second, 2*time.Second, "validating %s is logged in as user1 after web flow user switch", client.Hostname()) } // Test connectivity after user switch diff --git a/integration/derp_verify_endpoint_test.go b/integration/derp_verify_endpoint_test.go index c92a25ee..bd4cf6a9 100644 --- a/integration/derp_verify_endpoint_test.go +++ b/integration/derp_verify_endpoint_test.go @@ -109,7 +109,8 @@ func DERPVerify( defer c.Close() var result error - if err := c.Connect(t.Context()); err != nil { + err := c.Connect(t.Context()) + if err != nil { result = fmt.Errorf("client Connect: %w", err) } diff --git a/integration/dns_test.go b/integration/dns_test.go index 3432eb9b..08250e7b 100644 --- a/integration/dns_test.go +++ b/integration/dns_test.go @@ -199,6 +199,7 @@ func TestResolveMagicDNSExtraRecordsPath(t *testing.T) { }, }) require.NoError(t, err) + command := []string{"echo", fmt.Sprintf("'%s'", string(b4)), ">", erPath} _, err = hs.Execute([]string{"bash", "-c", strings.Join(command, " ")}) require.NoError(t, err)