Skip to content

Commit f09088e

Browse files
committed
go/oasis-test-runner: Add new e2e observer test
Test needs to run in SGX mode else keymanager's access list constructed using registered nodes is ignored.
1 parent fd0f549 commit f09088e

5 files changed

Lines changed: 218 additions & 2 deletions

File tree

.buildkite/code.pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ steps:
262262
--scenario e2e/runtime/rofl
263263
--scenario e2e/runtime/trust-root/.+
264264
--scenario e2e/runtime/keymanager-.+
265+
--scenario e2e/runtime/observer
265266
env:
266267
# Unsafe flags needed as the trust-root test rebuilds the enclave with embedded trust root data.
267268
OASIS_UNSAFE_SKIP_AVR_VERIFY: "1"

.changelog/6495.trivial.md

Whitespace-only changes.

go/oasis-test-runner/oasis/oasis.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,7 @@ func (n *Node) setProvisionedIdentity(seed string) error {
458458
return err
459459
}
460460

461-
if n.entity != nil {
462-
// Client nodes may need a provisioned identity. They never need an entity, however.
461+
if n.entity != nil { // client nodes don't have entity id.
463462
if err := n.entity.addNode(nodeSigner); err != nil {
464463
return err
465464
}
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
package runtime
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/oasisprotocol/oasis-core/go/common/cbor"
9+
"github.com/oasisprotocol/oasis-core/go/common/node"
10+
controlAPI "github.com/oasisprotocol/oasis-core/go/control/api"
11+
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env"
12+
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis"
13+
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/scenario"
14+
registry "github.com/oasisprotocol/oasis-core/go/registry/api"
15+
roothash "github.com/oasisprotocol/oasis-core/go/roothash/api"
16+
runtimeClient "github.com/oasisprotocol/oasis-core/go/runtime/client/api"
17+
)
18+
19+
const (
20+
observerTestKey = "observer_test_key"
21+
observerTestValue = "observer_test_value"
22+
)
23+
24+
// ObserverMode is the observer mode e2e test scenario.
25+
var ObserverMode scenario.Scenario = newObserverModeImpl()
26+
27+
type observerModeImpl struct {
28+
Scenario
29+
}
30+
31+
func newObserverModeImpl() scenario.Scenario {
32+
return &observerModeImpl{
33+
Scenario: *NewScenario(
34+
"observer",
35+
NewTestClient().WithScenario(NewTestClientScenario([]any{
36+
InsertKeyValueTx{
37+
Key: observerTestKey,
38+
Value: observerTestValue,
39+
Kind: encryptedWithSecretsTxKind,
40+
},
41+
})),
42+
),
43+
}
44+
}
45+
46+
func (sc *observerModeImpl) Clone() scenario.Scenario {
47+
return &observerModeImpl{
48+
Scenario: *sc.Scenario.Clone().(*Scenario),
49+
}
50+
}
51+
52+
func (sc *observerModeImpl) Fixture() (*oasis.NetworkFixture, error) {
53+
f, err := sc.Scenario.Fixture()
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
// Add additional entity on top of two inherited from the default scenario.
59+
f.Entities = append(f.Entities, oasis.EntityCfg{})
60+
61+
// Only entity 1 may register with the observer role.
62+
f.Runtimes[1].AdmissionPolicy = oasis.RuntimeAdmissionPolicyFixture{
63+
PerRole: map[node.RolesMask]oasis.PerRoleAdmissionPolicyFixture{
64+
node.RoleObserver: {
65+
EntityWhitelist: &oasis.EntityWhitelistRoleAdmissionPolicyFixture{
66+
Entities: map[int]registry.EntityWhitelistRoleConfig{
67+
1: {},
68+
},
69+
},
70+
},
71+
},
72+
}
73+
74+
f.Observers = append(f.Observers, oasis.ObserverFixture{
75+
Entity: 1,
76+
Runtimes: []int{1},
77+
RuntimeProvisioner: f.Clients[0].RuntimeProvisioner,
78+
})
79+
f.Observers = append(f.Observers, oasis.ObserverFixture{
80+
Entity: 2,
81+
Runtimes: []int{1},
82+
RuntimeProvisioner: f.Clients[0].RuntimeProvisioner,
83+
})
84+
85+
return f, nil
86+
}
87+
88+
func (sc *observerModeImpl) Run(ctx context.Context, childEnv *env.Env) error {
89+
if err := sc.StartNetworkAndWaitForClientSync(ctx); err != nil {
90+
return err
91+
}
92+
93+
if err := sc.StartTestClient(ctx, childEnv); err != nil {
94+
return err
95+
}
96+
if err := sc.WaitTestClient(); err != nil {
97+
return err
98+
}
99+
100+
whitelisted := sc.Net.Observers()[0]
101+
nonWhitelisted := sc.Net.Observers()[1]
102+
103+
defaultClientCtrl := sc.Net.ClientController()
104+
whitelistedCtrl, err := oasis.NewController(whitelisted.SocketPath())
105+
if err != nil {
106+
return fmt.Errorf("failed to create controller for whitelisted observer: %w", err)
107+
}
108+
defer whitelistedCtrl.Close()
109+
nonWhitelistedCtrl, err := oasis.NewController(nonWhitelisted.SocketPath())
110+
if err != nil {
111+
return fmt.Errorf("failed to create controller for non-whitelisted observer: %w", err)
112+
}
113+
defer nonWhitelistedCtrl.Close()
114+
115+
// Sanity: ensure whitelisted observer has registered and non-whitelisted failed to prevent false positives.
116+
if err = sc.waitObserverRegistration(ctx, whitelistedCtrl, true); err != nil {
117+
return err
118+
}
119+
if err = sc.waitObserverRegistration(ctx, nonWhitelistedCtrl, false); err != nil {
120+
return err
121+
}
122+
123+
sc.Logger.Info("ensuring client cannot query encrypted key")
124+
if _, err = sc.queryEncryptedKey(ctx, defaultClientCtrl, observerTestKey); err == nil {
125+
return fmt.Errorf("client unexpectedly queried encrypted key successfully")
126+
}
127+
128+
sc.Logger.Info("ensuring whitelisted observer can query encrypted key")
129+
value, err := sc.queryEncryptedKey(ctx, whitelistedCtrl, observerTestKey)
130+
if err != nil {
131+
return fmt.Errorf("whitelisted observer failed querying encrypted key: %w", err)
132+
}
133+
if value != observerTestValue {
134+
return fmt.Errorf("whitelisted observer query returned unexpected value (got: %s, want: %s)", value, observerTestValue)
135+
}
136+
137+
sc.Logger.Info("checking non-whitelisted observer cannot query encrypted key")
138+
if _, err = sc.queryEncryptedKey(ctx, nonWhitelistedCtrl, observerTestKey); err == nil {
139+
return fmt.Errorf("non-whitelisted observer unexpectedly queried encrypted key successfully")
140+
}
141+
142+
return nil
143+
}
144+
145+
func (sc *observerModeImpl) queryEncryptedKey(ctx context.Context, ctrl *oasis.Controller, key string) (string, error) {
146+
resp, err := ctrl.RuntimeClient.Query(ctx, &runtimeClient.QueryRequest{
147+
RuntimeID: KeyValueRuntimeID,
148+
Round: roothash.RoundLatest,
149+
Method: "enc_get",
150+
Args: cbor.Marshal(GetCall{Key: key}),
151+
})
152+
if err != nil {
153+
return "", fmt.Errorf("failed to query encrypted key: %w", err)
154+
}
155+
156+
var rsp string
157+
if err = cbor.Unmarshal(resp.Data, &rsp); err != nil {
158+
return "", err
159+
}
160+
161+
return rsp, nil
162+
}
163+
164+
func (sc *observerModeImpl) waitObserverRegistration(ctx context.Context, ctrl *oasis.Controller, mustRegister bool) error {
165+
sc.Logger.Info("waiting for observer registration result", "must_register", mustRegister)
166+
167+
reg, err := sc.waitObserverRegistrationAttempt(ctx, ctrl)
168+
if err != nil {
169+
return err
170+
}
171+
172+
// Must not register
173+
if !mustRegister {
174+
if reg.LastAttemptSuccessful {
175+
return fmt.Errorf("observer unexpectedly registered successfully")
176+
}
177+
178+
sc.Logger.Info("observer registration failed as expected")
179+
return nil
180+
}
181+
182+
// Must register
183+
if !reg.LastAttemptSuccessful {
184+
return fmt.Errorf("observer failed to register: %s", reg.LastAttemptErrorMessage)
185+
}
186+
if !reg.Descriptor.HasRoles(node.RoleObserver) {
187+
return fmt.Errorf("observer registered without observer role: %s", reg.Descriptor.Roles)
188+
}
189+
sc.Logger.Info("observer registered as expected")
190+
return nil
191+
}
192+
193+
func (sc *observerModeImpl) waitObserverRegistrationAttempt(ctx context.Context, ctrl *oasis.Controller) (*controlAPI.RegistrationStatus, error) {
194+
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
195+
defer cancel()
196+
197+
for {
198+
status, err := ctrl.GetStatus(ctx)
199+
if err != nil {
200+
return nil, fmt.Errorf("failed to get observer status: %w", err)
201+
}
202+
203+
reg := status.Registration
204+
if reg != nil && !reg.LastAttempt.IsZero() {
205+
return reg, nil
206+
}
207+
208+
select {
209+
case <-ctx.Done():
210+
return nil, fmt.Errorf("observer did not attempt registration within timeout: %w", ctx.Err())
211+
case <-time.After(time.Second):
212+
}
213+
}
214+
}

go/oasis-test-runner/scenario/e2e/runtime/scenario.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,8 @@ func RegisterScenarios() error {
419419
// it is identical to the txsource-multi-short, only using fewer nodes
420420
// due to SGX CI instance resource constrains.
421421
TxSourceMultiShortSGX,
422+
// Observer tests
423+
ObserverMode,
422424
} {
423425
if err := cmd.RegisterNondefault(s); err != nil {
424426
return err

0 commit comments

Comments
 (0)