1package daemonmux
2
3import (
4 "sync"
5 "testing"
6
7 "github.com/bnema/vev/internal/ports"
8 "github.com/stretchr/testify/require"
9)
10
11func TestServerBindingsClosedSetCloneAndDuplicates(t *testing.T) {
12 first := testPolicy()
13 second := first
14 second.Transport = "ssh-stdio"
15 entries := []ServerPolicyAdmission{localAcceptance(first), remoteAcceptance(second)}
16 binding, err := NewServerBindings(testIdentity(), testIncarnation(), entries)
17 require.NoError(t, err)
18 entries[0] = localAcceptance(second)
19 require.True(t, binding.Accepts(first), "constructor must clone its authority")
20 require.True(t, binding.Accepts(second))
21 variation := second
22 variation.Trust += "-other"
23 require.False(t, binding.Accepts(variation))
24 _, err = NewServerBindings(testIdentity(), testIncarnation(), []ServerPolicyAdmission{localAcceptance(first), remoteAcceptance(first)})
25 require.ErrorIs(t, err, ErrInvalidBinding)
26 _, err = NewServerBindings(testIdentity(), testIncarnation(), nil)
27 require.ErrorIs(t, err, ErrInvalidBinding)
28}
29
30func TestServerBindingsConcurrentAdmission(t *testing.T) {
31 policy := testPolicy()
32 binding, err := NewServerBindings(testIdentity(), testIncarnation(), []ServerPolicyAdmission{localAcceptance(policy)})
33 require.NoError(t, err)
34 var wg sync.WaitGroup
35 for range 100 {
36 wg.Add(1)
37 go func() { defer wg.Done(); require.True(t, binding.Accepts(policy)) }()
38 }
39 wg.Wait()
40}
41
42// TestServerBindingsSinglePolicyShorthandMatchesSetConstructor proves the
43// fixtures-only shorthand is exactly the one-member closed set: same authority,
44// same decisions, same exposure through Policy().
45func TestServerBindingsSinglePolicyShorthandMatchesSetConstructor(t *testing.T) {
46 shorthand, err := NewServerBinding(testIdentity(), testIncarnation(), testPolicy())
47 require.NoError(t, err)
48 set, err := NewServerBindings(testIdentity(), testIncarnation(), []ServerPolicyAdmission{localAcceptance(testPolicy())})
49 require.NoError(t, err)
50
51 require.Equal(t, set.Identity(), shorthand.Identity())
52 require.Equal(t, set.Incarnation(), shorthand.Incarnation())
53 require.Equal(t, set.Policy(), shorthand.Policy())
54 require.NoError(t, shorthand.Validate())
55 for _, candidate := range []ports.BrokerPolicy{testPolicy(), func() ports.BrokerPolicy { p := testPolicy(); p.Transport = "other"; return p }()} {
56 require.Equal(t, set.Accepts(candidate), shorthand.Accepts(candidate))
57 }
58}
59
60// TestServerBindingsAcceptedReturnsProvisionedMember proves admission answers
61// with the member the configuration provisioned and never with the request: no
62// merging, inheritance, echo, or fallback can widen the configured authority.
63// The answered entry carries the provisioned locality alongside its policy.
64func TestServerBindingsAcceptedReturnsProvisionedMember(t *testing.T) {
65 first := testPolicy()
66 second := first
67 second.Transport = "ssh-stdio"
68 second.Trust = "pinned"
69 binding, err := NewServerBindings(testIdentity(), testIncarnation(), []ServerPolicyAdmission{localAcceptance(first), remoteAcceptance(second)})
70 require.NoError(t, err)
71
72 accepted, ok := binding.Accepted(second)
73 require.True(t, ok)
74 require.Equal(t, remoteAcceptance(second), accepted, "admission answers the provisioned member, not the request")
75 require.Equal(t, ports.SessionOriginRemote, accepted.Origin)
76
77 local, ok := binding.Accepted(first)
78 require.True(t, ok)
79 require.Equal(t, localAcceptance(first), local)
80 require.Equal(t, ports.SessionOriginLocal, local.Origin)
81
82 // A request differing in exactly one field is refused outright: it can
83 // never be answered by a member it was not provisioned as.
84 variations := []func(*ports.BrokerPolicy){
85 func(p *ports.BrokerPolicy) { p.ProtocolVersion++ },
86 func(p *ports.BrokerPolicy) { p.CatalogSchemaVersion++ },
87 func(p *ports.BrokerPolicy) { p.Transport = "other" },
88 func(p *ports.BrokerPolicy) { p.Trust = "other" },
89 func(p *ports.BrokerPolicy) { p.Isolation = "other" },
90 }
91 for _, mutate := range variations {
92 requested := second
93 mutate(&requested)
94 accepted, ok := binding.Accepted(requested)
95 require.False(t, ok, "a one-field variation must not be admitted")
96 require.Equal(t, ServerPolicyAdmission{}, accepted)
97 }
98}
99
100// TestServerBindingsPolicyExposesNoMultiMemberAuthority proves Policy() answers
101// only a genuine single-member authority: a multi-member binding exposes the
102// zero value rather than one of its members, so a caller can never mistake an
103// arbitrary member for the authority of the whole daemon.
104func TestServerBindingsPolicyExposesNoMultiMemberAuthority(t *testing.T) {
105 first := testPolicy()
106 second := first
107 second.Transport = "ssh-stdio"
108 binding, err := NewServerBindings(testIdentity(), testIncarnation(), []ServerPolicyAdmission{localAcceptance(first), remoteAcceptance(second)})
109 require.NoError(t, err)
110 require.Equal(t, ports.BrokerPolicy{}, binding.Policy())
111 require.True(t, binding.Accepts(first))
112 require.True(t, binding.Accepts(second))
113}
114
115// TestServerBindingsRefusesInvalidMemberAmongValid proves validation is
116// all-or-nothing: one invalid member refuses the whole closed set instead of
117// publishing a partially valid authority. An entry with an unknown or invalid
118// locality is refused the same way, so an accepted member always names a
119// provisioned origin.
120func TestServerBindingsRefusesInvalidMemberAmongValid(t *testing.T) {
121 invalid := testPolicy()
122 invalid.ProtocolVersion = 0
123
124 tests := []struct {
125 name string
126 entries []ServerPolicyAdmission
127 }{
128 {name: "invalid member first", entries: []ServerPolicyAdmission{localAcceptance(invalid), remoteAcceptance(testPolicy())}},
129 {name: "invalid member last", entries: []ServerPolicyAdmission{localAcceptance(testPolicy()), remoteAcceptance(invalid)}},
130 {name: "zero policy member", entries: []ServerPolicyAdmission{localAcceptance(testPolicy()), remoteAcceptance(ports.BrokerPolicy{})}},
131 {name: "unknown origin", entries: []ServerPolicyAdmission{{Policy: testPolicy(), Origin: ports.SessionOriginUnknown}}},
132 {name: "invalid origin", entries: []ServerPolicyAdmission{{Policy: testPolicy(), Origin: ports.SessionConnectionOrigin(9)}}},
133 }
134 for _, tt := range tests {
135 t.Run(tt.name, func(t *testing.T) {
136 _, err := NewServerBindings(testIdentity(), testIncarnation(), tt.entries)
137 require.ErrorIs(t, err, ErrInvalidBinding)
138 })
139 }
140}
141
142// TestServerBindingsRefusesInvalidAuthority proves an empty identity and a zero
143// incarnation are refused regardless of how many valid policies follow.
144func TestServerBindingsRefusesInvalidAuthority(t *testing.T) {
145 tests := []struct {
146 name string
147 identity ports.BrokerDaemonIdentity
148 incarnation ports.BrokerDaemonIncarnation
149 }{
150 {name: "empty identity", identity: "", incarnation: testIncarnation()},
151 {name: "zero incarnation", identity: testIdentity(), incarnation: ports.BrokerDaemonIncarnation{}},
152 }
153 for _, tt := range tests {
154 t.Run(tt.name, func(t *testing.T) {
155 _, err := NewServerBindings(tt.identity, tt.incarnation, []ServerPolicyAdmission{localAcceptance(testPolicy())})
156 require.ErrorIs(t, err, ErrInvalidBinding)
157 })
158 }
159}
160
161// TestServerBindingsOriginShorthandMatchesSetConstructor proves Origin()
162// answers only a genuine single-member authority, while a multi-member binding
163// exposes the unknown origin rather than an arbitrary member's locality.
164func TestServerBindingsOriginShorthandMatchesSetConstructor(t *testing.T) {
165 shorthand, err := NewServerBinding(testIdentity(), testIncarnation(), testPolicy())
166 require.NoError(t, err)
167 require.Equal(t, ports.SessionOriginLocal, shorthand.Origin())
168
169 first := testPolicy()
170 second := first
171 second.Transport = "ssh-stdio"
172 multi, err := NewServerBindings(testIdentity(), testIncarnation(), []ServerPolicyAdmission{localAcceptance(first), remoteAcceptance(second)})
173 require.NoError(t, err)
174 require.Equal(t, ports.SessionOriginUnknown, multi.Origin())
175}
176
177// TestServerBindingsConcurrentMultiMemberAdmission proves a shared closed set is
178// safe to admit against from many handshakes at once, resolving each concurrent
179// request to its own provisioned member.
180func TestServerBindingsConcurrentMultiMemberAdmission(t *testing.T) {
181 first := testPolicy()
182 second := first
183 second.Transport = "ssh-stdio"
184 third := first
185 third.Isolation = "system"
186 binding, err := NewServerBindings(testIdentity(), testIncarnation(), []ServerPolicyAdmission{localAcceptance(first), remoteAcceptance(second), remoteAcceptance(third)})
187 require.NoError(t, err)
188
189 candidates := []ServerPolicyAdmission{localAcceptance(first), remoteAcceptance(second), remoteAcceptance(third)}
190 var wg sync.WaitGroup
191 for i := range 150 {
192 wg.Add(1)
193 go func(index int) {
194 defer wg.Done()
195 want := candidates[index%len(candidates)]
196 accepted, ok := binding.Accepted(want.Policy)
197 require.True(t, ok)
198 require.Equal(t, want, accepted)
199 }(i)
200 }
201 wg.Wait()
202}