File
Blob: tun/server/delegation_rpc_test.go
| 1 | package server |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | "time" |
| 8 | |
| 9 | "go.miragespace.co/specter/spec/mocks" |
| 10 | "go.miragespace.co/specter/spec/protocol" |
| 11 | "go.miragespace.co/specter/spec/rpc" |
| 12 | "go.miragespace.co/specter/spec/transport" |
| 13 | "go.miragespace.co/specter/spec/tun" |
| 14 | |
| 15 | "github.com/stretchr/testify/mock" |
| 16 | "github.com/stretchr/testify/require" |
| 17 | "github.com/twitchtv/twirp" |
| 18 | ) |
| 19 | |
| 20 | func TestDelegationLifecycle(t *testing.T) { |
| 21 | t.Run("owner authentication", func(t *testing.T) { |
| 22 | s, n, _, cert := sessionFixture(t) |
| 23 | tp := mocks.SelfTransport() |
| 24 | tp.WithCertificate(cert) |
| 25 | router := transport.NewStreamRouter(s.Logger, nil, tp) |
| 26 | s.AttachRouter(t.Context(), router) |
| 27 | go router.Accept(t.Context()) |
| 28 | cli := rpc.DynamicTunnelClient(rpc.DisablePooling(t.Context()), tp) |
| 29 | n.On("Get", mock.Anything, mock.Anything).Return(nil, nil) |
| 30 | ctx := rpc.WithNode(t.Context(), &protocol.Node{Address: "client"}) |
| 31 | _, err := cli.MintDelegation(ctx, &protocol.MintDelegationRequest{Hostname: "test"}) |
| 32 | require.Equal(t, twirp.Unauthenticated, err.(twirp.Error).Code()) |
| 33 | _, err = cli.ListDelegations(ctx, &protocol.ListDelegationsRequest{}) |
| 34 | require.Equal(t, twirp.Unauthenticated, err.(twirp.Error).Code()) |
| 35 | _, err = cli.RevokeDelegation(ctx, &protocol.RevokeDelegationRequest{}) |
| 36 | require.Equal(t, twirp.Unauthenticated, err.(twirp.Error).Code()) |
| 37 | _, err = cli.PublishTunnel(ctx, &protocol.PublishTunnelRequest{ |
| 38 | Hostname: "test", |
| 39 | Servers: []*protocol.Node{{Address: "server"}}, |
| 40 | }) |
| 41 | require.Equal(t, twirp.Unauthenticated, err.(twirp.Error).Code()) |
| 42 | n.AssertNotCalled(t, "Acquire", mock.Anything, mock.Anything, mock.Anything) |
| 43 | n.AssertNotCalled(t, "Put", mock.Anything, mock.Anything, mock.Anything) |
| 44 | }) |
| 45 | t.Run("mint list revoke", func(t *testing.T) { |
| 46 | s, n, _, cert := sessionFixture(t) |
| 47 | ctx, _ := sessionContext(t, cert) |
| 48 | owner, _, err := extractAuthenticated(ctx) |
| 49 | require.NoError(t, err) |
| 50 | index := []byte(tun.ClientDelegationsPrefix(owner)) |
| 51 | n.On("PrefixContains", mock.Anything, []byte(tun.ClientHostnamesPrefix(owner)), []byte("test")).Return(true, nil) |
| 52 | n.On("PrefixList", mock.Anything, index).Return([][]byte{}, nil).Once() |
| 53 | var rec protocol.DelegationRecord |
| 54 | appended := n.On("PrefixAppend", mock.Anything, index, mock.Anything).Return(nil).Once() |
| 55 | n.On("Put", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { |
| 56 | require.NoError(t, rec.UnmarshalVT(args.Get(2).([]byte))) |
| 57 | require.Equal(t, []byte(tun.DelegationKey(rec.Id)), args.Get(1)) |
| 58 | }).Return(nil).Once().NotBefore(appended) |
| 59 | minted, err := s.MintDelegation(ctx, &protocol.MintDelegationRequest{Hostname: "test"}) |
| 60 | require.NoError(t, err) |
| 61 | secret, err := tun.ParseDelegationToken(minted.Token) |
| 62 | require.NoError(t, err) |
| 63 | require.Equal(t, tun.DelegationID(secret), minted.Grant.Id) |
| 64 | require.Zero(t, minted.Grant.ExpiresAt) |
| 65 | missing := strings.Repeat("0", 64) |
| 66 | n.On("PrefixList", mock.Anything, index).Return([][]byte{[]byte(rec.Id), []byte(missing)}, nil).Once() |
| 67 | data, _ := rec.MarshalVT() |
| 68 | n.On("Get", mock.Anything, []byte(tun.DelegationKey(rec.Id))).Return(data, nil).Twice() |
| 69 | n.On("Get", mock.Anything, []byte(tun.DelegationKey(missing))).Return(nil, nil).Once() |
| 70 | listed, err := s.ListDelegations(ctx, &protocol.ListDelegationsRequest{}) |
| 71 | require.NoError(t, err) |
| 72 | require.Len(t, listed.Grants, 2) |
| 73 | require.True(t, listed.Grants[1].Incomplete) |
| 74 | require.NotContains(t, listed.String(), "tg1_") |
| 75 | deleted := n.On("Delete", mock.Anything, []byte(tun.DelegationKey(rec.Id))).Return(nil).Once() |
| 76 | n.On("PrefixRemove", mock.Anything, index, []byte(rec.Id)).Return(errors.New("index unavailable")).Once().NotBefore(deleted) |
| 77 | revoked, err := s.RevokeDelegation(ctx, &protocol.RevokeDelegationRequest{Id: rec.Id}) |
| 78 | require.NoError(t, err) |
| 79 | require.True(t, revoked.Revoked) |
| 80 | require.NotEmpty(t, revoked.IndexError) |
| 81 | n.On("Delete", mock.Anything, []byte(tun.DelegationKey(rec.Id))).Return(nil).Once() |
| 82 | n.On("Get", mock.Anything, []byte(tun.DelegationKey(rec.Id))).Return(nil, nil).Once() |
| 83 | n.On("PrefixContains", mock.Anything, index, []byte(rec.Id)).Return(true, nil).Once() |
| 84 | n.On("PrefixRemove", mock.Anything, index, []byte(rec.Id)).Return(nil).Once() |
| 85 | revoked, err = s.RevokeDelegation(ctx, &protocol.RevokeDelegationRequest{Id: rec.Id}) |
| 86 | require.NoError(t, err) |
| 87 | require.True(t, revoked.Revoked) |
| 88 | require.Empty(t, revoked.IndexError) |
| 89 | _, err = s.MintDelegation(ctx, &protocol.MintDelegationRequest{ |
| 90 | Hostname: "test", |
| 91 | ExpiresAt: time.Now().Add(-time.Hour).Unix(), |
| 92 | }) |
| 93 | require.Equal(t, twirp.InvalidArgument, err.(twirp.Error).Code()) |
| 94 | n.On("PrefixList", mock.Anything, index).Return(make([][]byte, 128), nil).Once() |
| 95 | _, err = s.MintDelegation(ctx, &protocol.MintDelegationRequest{Hostname: "test"}) |
| 96 | require.Equal(t, twirp.ResourceExhausted, err.(twirp.Error).Code()) |
| 97 | n.AssertExpectations(t) |
| 98 | }) |
| 99 | } |