1// Copyright (C) 2020 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package preflight_test
5
6import (
7	"context"
8	"net"
9	"strconv"
10	"testing"
11	"time"
12
13	"github.com/stretchr/testify/require"
14	"go.uber.org/zap/zaptest"
15	"golang.org/x/sync/errgroup"
16
17	"storj.io/common/identity/testidentity"
18	"storj.io/common/pb"
19	"storj.io/common/peertls/extensions"
20	"storj.io/common/peertls/tlsopts"
21	"storj.io/common/rpc"
22	"storj.io/common/testcontext"
23	"storj.io/storj/private/server"
24	"storj.io/storj/private/testplanet"
25	"storj.io/storj/storagenode"
26	"storj.io/storj/storagenode/preflight"
27	"storj.io/storj/storagenode/trust"
28)
29
30type mockServer struct {
31	localTime time.Time
32	pb.DRPCNodeServer
33}
34
35func TestLocalTime_InSync(t *testing.T) {
36	testplanet.Run(t, testplanet.Config{
37		SatelliteCount: 1, StorageNodeCount: 1, UplinkCount: 0,
38		Reconfigure: testplanet.Reconfigure{
39			StorageNode: func(index int, config *storagenode.Config) {
40				config.Preflight.LocalTimeCheck = true
41			},
42		},
43	}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
44		storagenode := planet.StorageNodes[0]
45		err := storagenode.Preflight.LocalTime.Check(ctx)
46		require.NoError(t, err)
47	})
48}
49
50func TestLocalTime_OutOfSync(t *testing.T) {
51	ctx := testcontext.New(t)
52	defer ctx.Cleanup()
53
54	log := zaptest.NewLogger(t)
55
56	// set up mock satellite server configuration
57	mockSatID, err := testidentity.NewTestIdentity(ctx)
58	require.NoError(t, err)
59	config := server.Config{
60		Address:        "127.0.0.1:0",
61		PrivateAddress: "127.0.0.1:0",
62
63		Config: tlsopts.Config{
64			PeerIDVersions: "*",
65			Extensions: extensions.Config{
66				Revocation:          false,
67				WhitelistSignedLeaf: false,
68			},
69		},
70	}
71	mockSatTLSOptions, err := tlsopts.NewOptions(mockSatID, config.Config, nil)
72	require.NoError(t, err)
73
74	t.Run("Less than 30m", func(t *testing.T) {
75		// register mock GetTime endpoint to mock server
76		var group errgroup.Group
77		defer ctx.Check(group.Wait)
78
79		contactServer, err := server.New(log, mockSatTLSOptions, config)
80		require.NoError(t, err)
81		defer ctx.Check(contactServer.Close)
82
83		err = pb.DRPCRegisterNode(contactServer.DRPC(), &mockServer{
84			localTime: time.Now().Add(-25 * time.Minute),
85		})
86		require.NoError(t, err)
87
88		group.Go(func() error {
89			return contactServer.Run(ctx)
90		})
91
92		// get mock server address
93		_, portStr, err := net.SplitHostPort(contactServer.Addr().String())
94		require.NoError(t, err)
95		port, err := strconv.Atoi(portStr)
96		require.NoError(t, err)
97		url := trust.SatelliteURL{
98			ID:   mockSatID.ID,
99			Host: "127.0.0.1",
100			Port: port,
101		}
102		require.NoError(t, err)
103
104		// set up storagenode client
105		source, err := trust.NewStaticURLSource(url.String())
106		require.NoError(t, err)
107
108		identity, err := testidentity.NewTestIdentity(ctx)
109		require.NoError(t, err)
110		tlsOptions, err := tlsopts.NewOptions(identity, config.Config, nil)
111		require.NoError(t, err)
112		dialer := rpc.NewDefaultDialer(tlsOptions)
113		pool, err := trust.NewPool(log, trust.Dialer(dialer), trust.Config{
114			Sources:   []trust.Source{source},
115			CachePath: ctx.File("trust-cache.json"),
116		}, nil)
117		require.NoError(t, err)
118		err = pool.Refresh(ctx)
119		require.NoError(t, err)
120
121		// should not return any error when node's clock is off no more than 30m
122		localtime := preflight.NewLocalTime(log, preflight.Config{
123			LocalTimeCheck: true,
124		}, pool, dialer)
125		err = localtime.Check(ctx)
126		require.NoError(t, err)
127
128	})
129
130	t.Run("More than 30m", func(t *testing.T) {
131		// register mock GetTime endpoint to mock server
132		var group errgroup.Group
133		defer ctx.Check(group.Wait)
134
135		contactServer, err := server.New(log, mockSatTLSOptions, config)
136		require.NoError(t, err)
137		defer ctx.Check(contactServer.Close)
138
139		err = pb.DRPCRegisterNode(contactServer.DRPC(), &mockServer{
140			localTime: time.Now().Add(-31 * time.Minute),
141		})
142		require.NoError(t, err)
143
144		group.Go(func() error {
145			return contactServer.Run(ctx)
146		})
147
148		// get mock server address
149		_, portStr, err := net.SplitHostPort(contactServer.Addr().String())
150		require.NoError(t, err)
151		port, err := strconv.Atoi(portStr)
152		require.NoError(t, err)
153		url := trust.SatelliteURL{
154			ID:   mockSatID.ID,
155			Host: "127.0.0.1",
156			Port: port,
157		}
158		require.NoError(t, err)
159
160		// set up storagenode client
161		source, err := trust.NewStaticURLSource(url.String())
162		require.NoError(t, err)
163
164		identity, err := testidentity.NewTestIdentity(ctx)
165		require.NoError(t, err)
166		tlsOptions, err := tlsopts.NewOptions(identity, config.Config, nil)
167		require.NoError(t, err)
168		dialer := rpc.NewDefaultDialer(tlsOptions)
169		pool, err := trust.NewPool(log, trust.Dialer(dialer), trust.Config{
170			Sources:   []trust.Source{source},
171			CachePath: ctx.File("trust-cache.json"),
172		}, nil)
173		require.NoError(t, err)
174		err = pool.Refresh(ctx)
175		require.NoError(t, err)
176
177		// should return an error when node's clock is off by more than 30m with all trusted satellites
178		localtime := preflight.NewLocalTime(log, preflight.Config{
179			LocalTimeCheck: true,
180		}, pool, dialer)
181		err = localtime.Check(ctx)
182		require.Error(t, err)
183	})
184}
185
186func (mock *mockServer) GetTime(ctx context.Context, req *pb.GetTimeRequest) (*pb.GetTimeResponse, error) {
187	return &pb.GetTimeResponse{
188		Timestamp: mock.localTime,
189	}, nil
190}
191