1// Copyright (C) 2019 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package trust_test
5
6import (
7	"context"
8	"crypto/x509"
9	"errors"
10	"fmt"
11	"sync"
12	"testing"
13
14	"github.com/stretchr/testify/assert"
15	"github.com/stretchr/testify/require"
16	"go.uber.org/zap/zaptest"
17
18	"storj.io/common/identity"
19	"storj.io/common/storj"
20	"storj.io/common/testcontext"
21	"storj.io/common/testrand"
22	"storj.io/storj/storagenode/trust"
23)
24
25func TestPoolRequiresCachePath(t *testing.T) {
26	log := zaptest.NewLogger(t)
27	_, err := trust.NewPool(log, newFakeIdentityResolver(), trust.Config{}, nil)
28	require.EqualError(t, err, "trust: cache path cannot be empty")
29}
30
31func TestPoolVerifySatelliteID(t *testing.T) {
32	ctx, pool, source, _ := newPoolTest(t)
33	defer ctx.Cleanup()
34
35	id := testrand.NodeID()
36
37	// Assert the ID is not trusted
38	err := pool.VerifySatelliteID(context.Background(), id)
39	require.EqualError(t, err, fmt.Sprintf("trust: satellite %q is untrusted", id))
40
41	// Refresh the pool with the new trust entry
42	source.entries = []trust.Entry{
43		{
44			SatelliteURL: trust.SatelliteURL{
45				ID:   id,
46				Host: "foo.test",
47				Port: 7777,
48			},
49		},
50	}
51	require.NoError(t, pool.Refresh(context.Background()))
52
53	// Assert the ID is now trusted
54	err = pool.VerifySatelliteID(context.Background(), id)
55	require.NoError(t, err)
56
57	// Refresh the pool after removing the trusted satellite
58	source.entries = nil
59	require.NoError(t, pool.Refresh(context.Background()))
60
61	// Assert the ID is no longer trusted
62	err = pool.VerifySatelliteID(context.Background(), id)
63	require.EqualError(t, err, fmt.Sprintf("trust: satellite %q is untrusted", id))
64}
65
66func TestPoolGetSignee(t *testing.T) {
67	id := testrand.NodeID()
68	url := trust.SatelliteURL{
69		ID:   id,
70		Host: "foo.test",
71		Port: 7777,
72	}
73
74	ctx, pool, source, resolver := newPoolTest(t)
75	defer ctx.Cleanup()
76
77	// ID is untrusted
78	_, err := pool.GetSignee(context.Background(), id)
79	require.EqualError(t, err, fmt.Sprintf("trust: satellite %q is untrusted", id))
80
81	// Refresh the pool with the new trust entry
82	source.entries = []trust.Entry{{SatelliteURL: url}}
83	require.NoError(t, pool.Refresh(context.Background()))
84
85	// Identity is uncached and resolving fails
86	_, err = pool.GetSignee(context.Background(), id)
87	require.EqualError(t, err, "trust: no identity")
88
89	// Now make resolving succeed
90	identity := &identity.PeerIdentity{
91		ID:   id,
92		Leaf: &x509.Certificate{},
93	}
94	resolver.SetIdentity(url.NodeURL(), identity)
95	signee, err := pool.GetSignee(context.Background(), id)
96	require.NoError(t, err)
97	assert.Equal(t, id, signee.ID())
98
99	// Now make resolving fail but ensure we can still get the signee since
100	// the identity is cached.
101	resolver.SetIdentity(url.NodeURL(), nil)
102	signee, err = pool.GetSignee(context.Background(), id)
103	require.NoError(t, err)
104	assert.Equal(t, id, signee.ID())
105
106	// Now update the address on the entry and assert that the identity is
107	// reset in the cache and needs to be refetched (and fails since we've
108	// hampered the resolver)
109	url.Host = "bar.test"
110	source.entries = []trust.Entry{{SatelliteURL: url}}
111	require.NoError(t, pool.Refresh(context.Background()))
112	_, err = pool.GetSignee(context.Background(), id)
113	require.EqualError(t, err, "trust: no identity")
114}
115
116func TestPoolGetSatellites(t *testing.T) {
117	ctx, pool, source, _ := newPoolTest(t)
118	defer ctx.Cleanup()
119
120	id1 := testrand.NodeID()
121	id2 := testrand.NodeID()
122
123	// Refresh the pool with the new trust entry
124	source.entries = []trust.Entry{
125		{
126			SatelliteURL: trust.SatelliteURL{
127				ID:   id1,
128				Host: "foo.test",
129				Port: 7777,
130			},
131		},
132		{
133			SatelliteURL: trust.SatelliteURL{
134				ID:   id2,
135				Host: "bar.test",
136				Port: 7777,
137			},
138		},
139	}
140	require.NoError(t, pool.Refresh(context.Background()))
141
142	expected := []storj.NodeID{id1, id2}
143	actual := pool.GetSatellites(context.Background())
144	assert.ElementsMatch(t, expected, actual)
145}
146
147func TestPoolGetAddress(t *testing.T) {
148	ctx, pool, source, _ := newPoolTest(t)
149	defer ctx.Cleanup()
150
151	id := testrand.NodeID()
152
153	// Assert the ID is not trusted
154	nodeurl, err := pool.GetNodeURL(context.Background(), id)
155	require.EqualError(t, err, fmt.Sprintf("trust: satellite %q is untrusted", id))
156	require.Empty(t, nodeurl)
157
158	// Refresh the pool with the new trust entry
159	source.entries = []trust.Entry{
160		{
161			SatelliteURL: trust.SatelliteURL{
162				ID:   id,
163				Host: "foo.test",
164				Port: 7777,
165			},
166		},
167	}
168	require.NoError(t, pool.Refresh(context.Background()))
169
170	// Assert the ID is now trusted and the correct address is returned
171	nodeurl, err = pool.GetNodeURL(context.Background(), id)
172	require.NoError(t, err)
173	require.Equal(t, id, nodeurl.ID)
174	require.Equal(t, "foo.test:7777", nodeurl.Address)
175
176	// Refresh the pool with an updated trust entry with a new address
177	source.entries = []trust.Entry{
178		{
179			SatelliteURL: trust.SatelliteURL{
180				ID:   id,
181				Host: "bar.test",
182				Port: 7777,
183			},
184		},
185	}
186	require.NoError(t, pool.Refresh(context.Background()))
187
188	// Assert the ID is now trusted and the correct address is returned
189	nodeurl, err = pool.GetNodeURL(context.Background(), id)
190	require.NoError(t, err)
191	require.Equal(t, id, nodeurl.ID)
192	require.Equal(t, "bar.test:7777", nodeurl.Address)
193}
194
195func newPoolTest(t *testing.T) (*testcontext.Context, *trust.Pool, *fakeSource, *fakeIdentityResolver) {
196	ctx := testcontext.New(t)
197
198	source := &fakeSource{}
199
200	resolver := newFakeIdentityResolver()
201
202	log := zaptest.NewLogger(t)
203	pool, err := trust.NewPool(log, resolver, trust.Config{
204		Sources:   []trust.Source{source},
205		CachePath: ctx.File("trust-cache.json"),
206	}, nil)
207	if err != nil {
208		ctx.Cleanup()
209		require.NoError(t, err)
210	}
211
212	return ctx, pool, source, resolver
213}
214
215type fakeIdentityResolver struct {
216	mu         sync.Mutex
217	identities map[storj.NodeURL]*identity.PeerIdentity
218}
219
220func newFakeIdentityResolver() *fakeIdentityResolver {
221	return &fakeIdentityResolver{
222		identities: make(map[storj.NodeURL]*identity.PeerIdentity),
223	}
224}
225
226func (resolver *fakeIdentityResolver) SetIdentity(url storj.NodeURL, identity *identity.PeerIdentity) {
227	resolver.mu.Lock()
228	defer resolver.mu.Unlock()
229	resolver.identities[url] = identity
230}
231
232func (resolver *fakeIdentityResolver) ResolveIdentity(ctx context.Context, url storj.NodeURL) (*identity.PeerIdentity, error) {
233	resolver.mu.Lock()
234	defer resolver.mu.Unlock()
235
236	identity := resolver.identities[url]
237	if identity == nil {
238		return nil, errors.New("no identity")
239	}
240	return identity, nil
241}
242