* Fix KeyExpiration when a zero time value has a timezone

When a zero time value is loaded from JSON or a DB in a way that
assigns it the local timezone, it does not roudtrip in JSON as a
value for which IsZero returns true. This causes KeyExpiry to be
treated as a far past value instead of a nilish value.

See https://github.com/golang/go/issues/57040

* Fix whitespace

* Ensure that postgresql is used for all tests when env var is set

* Pass through value of HEADSCALE_INTEGRATION_POSTGRES env var

* Add option to set timezone on headscale container

* Add test for registration with auth key in alternate timezone
This commit is contained in:
Mike Poindexter 2024-09-03 00:22:17 -07:00 committed by GitHub
parent aa0f3d43cc
commit 3101f895a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 91 additions and 7 deletions

View File

@ -45,6 +45,7 @@ jobs:
- TestPingAllByIPPublicDERP - TestPingAllByIPPublicDERP
- TestAuthKeyLogoutAndRelogin - TestAuthKeyLogoutAndRelogin
- TestEphemeral - TestEphemeral
- TestEphemeralInAlternateTimezone
- TestEphemeral2006DeletedTooQuickly - TestEphemeral2006DeletedTooQuickly
- TestPingAllByHostname - TestPingAllByHostname
- TestTaildrop - TestTaildrop

View File

@ -70,6 +70,8 @@ after improving the test harness as part of adopting [#1460](https://github.com/
- Make registration page easier to use on mobile devices - Make registration page easier to use on mobile devices
- Make write-ahead-log default on and configurable for SQLite [#1985](https://github.com/juanfont/headscale/pull/1985) - Make write-ahead-log default on and configurable for SQLite [#1985](https://github.com/juanfont/headscale/pull/1985)
- Add APIs for managing headscale policy. [#1792](https://github.com/juanfont/headscale/pull/1792) - Add APIs for managing headscale policy. [#1792](https://github.com/juanfont/headscale/pull/1792)
- Fix for registering nodes using preauthkeys when running on a postgres database in a non-UTC timezone. [#764](https://github.com/juanfont/headscale/issues/764)
- Make sure integration tests cover postgres for all scenarios
## 0.22.3 (2023-05-12) ## 0.22.3 (2023-05-12)

View File

@ -93,7 +93,7 @@ func tailNode(
User: tailcfg.UserID(node.UserID), User: tailcfg.UserID(node.UserID),
Key: node.NodeKey, Key: node.NodeKey,
KeyExpiry: keyExpiry, KeyExpiry: keyExpiry.UTC(),
Machine: node.MachineKey, Machine: node.MachineKey,
DiscoKey: node.DiscoKey, DiscoKey: node.DiscoKey,
@ -102,7 +102,7 @@ func tailNode(
Endpoints: node.Endpoints, Endpoints: node.Endpoints,
DERP: derp, DERP: derp,
Hostinfo: node.Hostinfo.View(), Hostinfo: node.Hostinfo.View(),
Created: node.CreatedAt, Created: node.CreatedAt.UTC(),
Online: node.IsOnline, Online: node.IsOnline,

View File

@ -1,6 +1,7 @@
package mapper package mapper
import ( import (
"encoding/json"
"net/netip" "net/netip"
"testing" "testing"
"time" "time"
@ -205,3 +206,68 @@ func TestTailNode(t *testing.T) {
}) })
} }
} }
func TestNodeExpiry(t *testing.T) {
tp := func(t time.Time) *time.Time {
return &t
}
tests := []struct {
name string
exp *time.Time
wantTime time.Time
wantTimeZero bool
}{
{
name: "no-expiry",
exp: nil,
wantTimeZero: true,
},
{
name: "zero-expiry",
exp: &time.Time{},
wantTimeZero: true,
},
{
name: "localtime",
exp: tp(time.Time{}.Local()),
wantTimeZero: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
node := &types.Node{
GivenName: "test",
Expiry: tt.exp,
}
tn, err := tailNode(
node,
0,
&policy.ACLPolicy{},
&types.Config{},
)
if err != nil {
t.Fatalf("nodeExpiry() error = %v", err)
}
// Round trip the node through JSON to ensure the time is serialized correctly
seri, err := json.Marshal(tn)
if err != nil {
t.Fatalf("nodeExpiry() error = %v", err)
}
var deseri tailcfg.Node
err = json.Unmarshal(seri, &deseri)
if err != nil {
t.Fatalf("nodeExpiry() error = %v", err)
}
if tt.wantTimeZero {
if !deseri.KeyExpiry.IsZero() {
t.Errorf("nodeExpiry() = %v, want zero", deseri.KeyExpiry)
}
} else if deseri.KeyExpiry != tt.wantTime {
t.Errorf("nodeExpiry() = %v, want %v", deseri.KeyExpiry, tt.wantTime)
}
})
}
}

View File

@ -215,6 +215,14 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) {
} }
func TestEphemeral(t *testing.T) { func TestEphemeral(t *testing.T) {
testEphemeralWithOptions(t, hsic.WithTestName("ephemeral"))
}
func TestEphemeralInAlternateTimezone(t *testing.T) {
testEphemeralWithOptions(t, hsic.WithTestName("ephemeral-tz"), hsic.WithTimezone("America/Los_Angeles"))
}
func testEphemeralWithOptions(t *testing.T, opts ...hsic.Option) {
IntegrationSkip(t) IntegrationSkip(t)
t.Parallel() t.Parallel()
@ -227,7 +235,7 @@ func TestEphemeral(t *testing.T) {
"user2": len(MustTestVersions), "user2": len(MustTestVersions),
} }
headscale, err := scenario.Headscale(hsic.WithTestName("ephemeral")) headscale, err := scenario.Headscale(opts...)
assertNoErrHeadscaleEnv(t, err) assertNoErrHeadscaleEnv(t, err)
for userName, clientCount := range spec { for userName, clientCount := range spec {

View File

@ -211,6 +211,12 @@ func WithTuning(batchTimeout time.Duration, mapSessionChanSize int) Option {
} }
} }
func WithTimezone(timezone string) Option {
return func(hsic *HeadscaleInContainer) {
hsic.env["TZ"] = timezone
}
}
// New returns a new HeadscaleInContainer instance. // New returns a new HeadscaleInContainer instance.
func New( func New(
pool *dockertest.Pool, pool *dockertest.Pool,

View File

@ -26,6 +26,7 @@ run_tests() {
--volume "$PWD:$PWD" -w "$PWD"/integration \ --volume "$PWD:$PWD" -w "$PWD"/integration \
--volume /var/run/docker.sock:/var/run/docker.sock \ --volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$PWD"/control_logs:/tmp/control \ --volume "$PWD"/control_logs:/tmp/control \
-e "HEADSCALE_INTEGRATION_POSTGRES" \
golang:1 \ golang:1 \
go test ./... \ go test ./... \
-failfast \ -failfast \

View File

@ -249,6 +249,10 @@ func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) {
return headscale, nil return headscale, nil
} }
if usePostgresForTest {
opts = append(opts, hsic.WithPostgres())
}
headscale, err := hsic.New(s.pool, s.network, opts...) headscale, err := hsic.New(s.pool, s.network, opts...)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create headscale container: %w", err) return nil, fmt.Errorf("failed to create headscale container: %w", err)
@ -465,10 +469,6 @@ func (s *Scenario) CreateHeadscaleEnv(
tsOpts []tsic.Option, tsOpts []tsic.Option,
opts ...hsic.Option, opts ...hsic.Option,
) error { ) error {
if usePostgresForTest {
opts = append(opts, hsic.WithPostgres())
}
headscale, err := s.Headscale(opts...) headscale, err := s.Headscale(opts...)
if err != nil { if err != nil {
return err return err