Skip to content

Commit

Permalink
Disable network layer if 'didmethods' does not contain 'nuts' (#3449)
Browse files Browse the repository at this point in the history
* disable network layer if 'didmethods' does not contain 'nuts'
  • Loading branch information
gerardsn authored Oct 8, 2024
1 parent 7854e88 commit a738631
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 18 deletions.
17 changes: 17 additions & 0 deletions network/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/labstack/echo/v4"
"github.com/nuts-foundation/nuts-node/audit"
"github.com/nuts-foundation/nuts-node/network/log"
"net/http"
"time"

"github.com/nuts-foundation/nuts-node/core"
Expand All @@ -42,10 +43,19 @@ type Wrapper struct {

func (a *Wrapper) Routes(router core.EchoRouter) {
RegisterHandlers(router, NewStrictHandler(a, []StrictMiddlewareFunc{
func(f StrictHandlerFunc, operationID string) StrictHandlerFunc { // fast fail if did:nuts is disabled
return func(ctx echo.Context, args interface{}) (interface{}, error) {
if a.Service.Disabled() {
return nil, network.ErrDIDNutsDisabled
}
return f(ctx, args)
}
},
func(f StrictHandlerFunc, operationID string) StrictHandlerFunc {
return func(ctx echo.Context, request interface{}) (response interface{}, err error) {
ctx.Set(core.OperationIDContextKey, operationID)
ctx.Set(core.ModuleNameContextKey, network.ModuleName)
ctx.Set(core.StatusCodeResolverContextKey, a)
return f(ctx, request)
}
},
Expand All @@ -55,6 +65,13 @@ func (a *Wrapper) Routes(router core.EchoRouter) {
}))
}

// ResolveStatusCode maps errors returned by this API to specific HTTP status codes.
func (a *Wrapper) ResolveStatusCode(err error) int {
return core.ResolveStatusCode(err, map[error]int{
network.ErrDIDNutsDisabled: http.StatusBadRequest,
})
}

// ListTransactions lists all transactions
func (a *Wrapper) ListTransactions(_ context.Context, request ListTransactionsRequestObject) (ListTransactionsResponseObject, error) {
// Parse the start/end params, which have default values
Expand Down
5 changes: 2 additions & 3 deletions network/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,13 @@ type Transactions interface {
DiscoverServices(updatedDID did.DID)
// AddressBook returns the list of contacts in the address book.
AddressBook() []transport.Contact
// Disabled returns true if core.ServerConfig.DIDMethods does not contain 'nuts'
Disabled() bool
}

// EventType defines a type for specifying the kind of events that can be published/subscribed on the Network.
type EventType string

// AnyPayloadType is a wildcard that matches with any payload type.
const AnyPayloadType = "*"

// Receiver defines a callback function for processing transactions/payloads received by the DAG.
type Receiver func(transaction dag.Transaction, payload []byte) error

Expand Down
14 changes: 14 additions & 0 deletions network/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 54 additions & 6 deletions network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"errors"
"fmt"
"net"
"slices"
"strings"
"sync/atomic"
"time"
Expand Down Expand Up @@ -66,11 +67,15 @@ const (
newNodeConnectionDelay = 5 * time.Minute
)

// ErrDIDNutsDisabled is returned from certain API methods when the core.ServerConfig.DIDMethods does not contain "nuts"
var ErrDIDNutsDisabled = errors.New("network operations not supported; did:nuts support not configured")

// defaultBBoltOptions are given to bbolt, allows for package local adjustments during test
var defaultBBoltOptions = bbolt.DefaultOptions

// Network implements Transactions interface and Engine functions.
type Network struct {
disabled bool // node is running without did:nuts support
config Config
certificate tls.Certificate
trustStore *core.TrustStore
Expand All @@ -96,6 +101,9 @@ type Network struct {

// CheckHealth performs health checks for the network engine.
func (n *Network) CheckHealth() map[string]core.Health {
if n.disabled {
return nil
}
results := make(map[string]core.Health)
if n.certificate.Leaf != nil {
results[healthTLS] = n.checkNodeTLSHealth()
Expand Down Expand Up @@ -134,6 +142,9 @@ func (n *Network) checkNodeTLSHealth() core.Health {
}

func (n *Network) Migrate() error {
if n.disabled {
return nil
}
return n.state.Migrate()
}

Expand Down Expand Up @@ -167,6 +178,10 @@ func NewNetworkInstance(

// Configure configures the Network subsystem
func (n *Network) Configure(config core.ServerConfig) error {
if !slices.Contains(config.DIDMethods, "nuts") {
n.disabled = true
return nil
}
var err error
dagStore, err := n.storeProvider.GetKVStore("data", storage.PersistentStorageClass)
if err != nil {
Expand Down Expand Up @@ -269,11 +284,7 @@ func (n *Network) Configure(config core.ServerConfig) error {
} else {
// Not allowed in strict mode for security reasons: only intended for demo/workshop purposes.
if config.Strictmode {
if len(n.config.BootstrapNodes) == 0 && n.assumeNewNode {
log.Logger().Info("It appears the gRPC network will not be used (no bootstrap nodes and an empty network state), so disabled TLS is accepted even with strict mode enabled.")
} else {
return errors.New("disabling TLS in strict mode is not allowed")
}
return errors.New("disabling TLS in strict mode is not allowed")
}
authenticator = grpc.NewDummyAuthenticator(nil)
}
Expand Down Expand Up @@ -313,7 +324,7 @@ func (n *Network) Configure(config core.ServerConfig) error {
}

func (n *Network) DiscoverServices(updatedDID did.DID) {
if !n.config.EnableDiscovery {
if n.disabled || !n.config.EnableDiscovery {
return
}
document, _, err := n.didStore.Resolve(updatedDID, nil)
Expand Down Expand Up @@ -363,6 +374,9 @@ func (n *Network) Config() interface{} {

// Start initiates the Network subsystem
func (n *Network) Start() error {
if n.disabled {
return nil
}
startTime := time.Now()
n.startTime.Store(&startTime)

Expand Down Expand Up @@ -571,6 +585,9 @@ func (n *Network) selfTestNutsCommAddress(nutsComm transport.NutsCommURL) error
// The receiver is called when a transaction is added to the DAG.
// It's only called if the given dag.NotificationFilter's match.
func (n *Network) Subscribe(name string, subscriber dag.ReceiverFn, options ...SubscriberOption) error {
if n.disabled {
return nil
}
notifierOptions := make([]dag.NotifierOption, len(options))
for i, o := range options {
notifierOptions[i] = o()
Expand Down Expand Up @@ -609,12 +626,18 @@ func (n *Network) CleanupSubscriberEvents(subscriberName, errorPrefix string) er

// GetTransaction retrieves the transaction for the given reference. If the transaction is not known, an error is returned.
func (n *Network) GetTransaction(transactionRef hash.SHA256Hash) (dag.Transaction, error) {
if n.disabled {
return nil, ErrDIDNutsDisabled
}
return n.state.GetTransaction(context.Background(), transactionRef)
}

// GetTransactionPayload retrieves the transaction Payload for the given transaction. If the transaction or Payload is not found
// nil is returned.
func (n *Network) GetTransactionPayload(transactionRef hash.SHA256Hash) ([]byte, error) {
if n.disabled {
return nil, ErrDIDNutsDisabled
}
transaction, err := n.state.GetTransaction(context.Background(), transactionRef)
if err != nil {
if errors.Is(err, dag.ErrTransactionNotFound) {
Expand All @@ -628,11 +651,17 @@ func (n *Network) GetTransactionPayload(transactionRef hash.SHA256Hash) ([]byte,

// ListTransactionsInRange returns all transactions known to this Network instance with lamport clock value between startInclusive and endExclusive.
func (n *Network) ListTransactionsInRange(startInclusive uint32, endExclusive uint32) ([]dag.Transaction, error) {
if n.disabled {
return nil, ErrDIDNutsDisabled
}
return n.state.FindBetweenLC(context.Background(), startInclusive, endExclusive)
}

// CreateTransaction creates a new transaction from the given template.
func (n *Network) CreateTransaction(ctx context.Context, template Template) (dag.Transaction, error) {
if n.disabled {
return nil, ErrDIDNutsDisabled
}
payloadHash := hash.SHA256Sum(template.Payload)
log.Logger().
WithField(core.LogFieldTransactionType, template.Type).
Expand Down Expand Up @@ -743,6 +772,9 @@ func (n *Network) calculateLamportClock(ctx context.Context, prevs []hash.SHA256

// Shutdown cleans up any leftover go routines
func (n *Network) Shutdown() error {
if n.disabled {
return nil
}
// Stop protocols and connection manager
for _, prot := range n.protocols {
prot.Stop()
Expand All @@ -758,6 +790,9 @@ func (n *Network) Shutdown() error {

// Diagnostics collects and returns diagnostics for the Network engine.
func (n *Network) Diagnostics() []core.DiagnosticResult {
if n.disabled {
return nil
}
var results = make([]core.DiagnosticResult, 0)
// Connection manager and protocols
results = append(results, core.DiagnosticResultMap{Title: "connections", Items: n.connectionManager.Diagnostics()})
Expand All @@ -778,6 +813,9 @@ func (n *Network) Diagnostics() []core.DiagnosticResult {

// PeerDiagnostics returns a map containing diagnostic information of the node's peers. The key contains the remote peer's ID.
func (n *Network) PeerDiagnostics() map[transport.PeerID]transport.Diagnostics {
if n.disabled {
return nil
}
result := make(map[transport.PeerID]transport.Diagnostics, 0)
// We assume higher protocol versions (later in the slice) have better/more accurate diagnostics,
// so for now they're copied over diagnostics of earlier versions, unless the entry is empty for that peer.
Expand All @@ -793,15 +831,25 @@ func (n *Network) PeerDiagnostics() map[transport.PeerID]transport.Diagnostics {
}

func (n *Network) AddressBook() []transport.Contact {
if n.disabled {
return nil
}
return n.connectionManager.Contacts()
}

func (n *Network) Disabled() bool {
return n.disabled
}

// ReprocessReport describes the reprocess exection.
type ReprocessReport struct {
// reserved for future use
}

func (n *Network) Reprocess(ctx context.Context, contentType string) (*ReprocessReport, error) {
if n.disabled {
return nil, ErrDIDNutsDisabled
}
log.Logger().Infof("Starting reprocess of %s", contentType)

_, js, err := n.eventPublisher.Pool().Acquire(ctx)
Expand Down
7 changes: 3 additions & 4 deletions network/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,13 @@ func TestNetwork_Configure(t *testing.T) {

assert.EqualError(t, err, "disabling TLS in strict mode is not allowed")
})
t.Run("ok - TLS disabled in strict mode, with empty node", func(t *testing.T) {
t.Run("ok - network disabled if not in supported"+
" DIDMethods", func(t *testing.T) {
ctrl := gomock.NewController(t)
ctx := createNetwork(t, ctrl)
ctx.protocol.EXPECT().Configure(gomock.Any())
ctx.network.connectionManager = nil

err := ctx.network.Configure(core.TestServerConfig(func(config *core.ServerConfig) {
config.Datadir = io.TestDirectory(t)
config.DIDMethods = []string{"web"}
}))

require.NoError(t, err)
Expand Down
1 change: 1 addition & 0 deletions vcr/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func newMockContext(t *testing.T) mockContext {
tx.EXPECT().Subscribe("vcr_vcs", gomock.Any(), gomock.Any())
tx.EXPECT().Subscribe("vcr_revocations", gomock.Any(), gomock.Any())
tx.EXPECT().CleanupSubscriberEvents("vcr_vcs", gomock.Any())
tx.EXPECT().Disabled().AnyTimes()
didResolver := resolver.NewMockDIDResolver(ctrl)
documentOwner := didsubject.NewMockDocumentOwner(ctrl)
vdrInstance := vdr.NewMockVDR(ctrl)
Expand Down
13 changes: 11 additions & 2 deletions vcr/vcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ func (c *vcr) Configure(config core.ServerConfig) error {
c.keyResolver = resolver.DIDKeyResolver{Resolver: didResolver}
c.serviceResolver = resolver.DIDServiceResolver{Resolver: didResolver}

networkPublisher := issuer.NewNetworkPublisher(c.network, didResolver, c.keyStore)
tlsConfig, err := c.pkiProvider.CreateTLSConfig(config.TLS) // returns nil if TLS is disabled
if err != nil {
return err
Expand All @@ -225,11 +224,18 @@ func (c *vcr) Configure(config core.ServerConfig) error {
c.openidSessionStore = c.storageClient.GetSessionDatabase()
}

var networkPublisher issuer.Publisher
if !c.network.Disabled() {
networkPublisher = issuer.NewNetworkPublisher(c.network, didResolver, c.keyStore)
}

status := revocation.NewStatusList2021(c.storageClient.GetSQLDatabase(), client.NewWithCache(config.HTTPClient.Timeout), config.URL)
c.issuer = issuer.NewIssuer(c.issuerStore, c, networkPublisher, openidHandlerFn, didResolver, c.keyStore, c.jsonldManager, c.trustConfig, status)
c.verifier = verifier.NewVerifier(c.verifierStore, didResolver, c.keyResolver, c.jsonldManager, c.trustConfig, status)

c.ambassador = NewAmbassador(c.network, c, c.verifier, c.eventManager)
if !c.network.Disabled() {
c.ambassador = NewAmbassador(c.network, c, c.verifier, c.eventManager)
}

// Create holder/wallet
c.wallet = holder.NewSQLWallet(c.keyResolver, c.keyStore, c.verifier, c.jsonldManager, c.storageClient)
Expand Down Expand Up @@ -269,6 +275,9 @@ func (c *vcr) createCredentialsStore() error {
}

func (c *vcr) Start() error {
if c.ambassador == nil { // did:nuts / network layer is disabled
return nil
}
// start listening for new credentials
_ = c.ambassador.Configure()

Expand Down
4 changes: 3 additions & 1 deletion vcr/vcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ func TestVCR_Configure(t *testing.T) {
vdrInstance.EXPECT().Resolver().AnyTimes()
pkiProvider := pki.NewMockProvider(ctrl)
pkiProvider.EXPECT().CreateTLSConfig(gomock.Any()).Return(nil, nil).AnyTimes()
instance := NewVCRInstance(nil, vdrInstance, nil, jsonld.NewTestJSONLDManager(t), nil, storage.NewTestStorageEngine(t), pkiProvider).(*vcr)
networkInstance := network.NewMockTransactions(ctrl)
networkInstance.EXPECT().Disabled().AnyTimes()
instance := NewVCRInstance(nil, vdrInstance, networkInstance, jsonld.NewTestJSONLDManager(t), nil, storage.NewTestStorageEngine(t), pkiProvider).(*vcr)
instance.config.OpenID4VCI.Enabled = true

err := instance.Configure(core.TestServerConfig(func(config *core.ServerConfig) {
Expand Down
14 changes: 12 additions & 2 deletions vdr/vdr.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,10 @@ func (r *Module) Configure(config core.ServerConfig) error {
}
}

r.networkAmbassador = didnuts.NewAmbassador(r.network, r.store, r.eventManager)
// only create ambassador when did:nuts is enabled
if slices.Contains(r.supportedDIDMethods, "nuts") {
r.networkAmbassador = didnuts.NewAmbassador(r.network, r.store, r.eventManager)
}
db := r.storageInstance.GetSQLDatabase()

r.didResolver.(*resolver.DIDResolverRouter).Register(didjwk.MethodName, didjwk.NewResolver())
Expand Down Expand Up @@ -202,10 +205,17 @@ func (r *Module) Configure(config core.ServerConfig) error {
r.Manager = didsubject.New(db, methodManagers, r.keyStore, r.supportedDIDMethods)

// Initiate the routines for auto-updating the data.
return r.networkAmbassador.Configure()
if r.networkAmbassador != nil {
return r.networkAmbassador.Configure()
}
return nil
}

func (r *Module) Start() error {
// nothing to start if did:nuts is disabled
if r.networkAmbassador == nil {
return nil
}
err := r.networkAmbassador.Start()
if err != nil {
return err
Expand Down

0 comments on commit a738631

Please sign in to comment.