mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 13:47:19 +00:00
Merge pull request #3221 from nspcc-dev/json-limits
Anti-DOS RPC limitations
This commit is contained in:
commit
61d5b6eb0c
7 changed files with 109 additions and 20 deletions
|
@ -187,6 +187,8 @@ RPC:
|
||||||
MaxFindResultItems: 100
|
MaxFindResultItems: 100
|
||||||
MaxFindStoragePageSize: 50
|
MaxFindStoragePageSize: 50
|
||||||
MaxNEP11Tokens: 100
|
MaxNEP11Tokens: 100
|
||||||
|
MaxRequestBodyBytes: 5242880
|
||||||
|
MaxRequestHeaderBytes: 1048576
|
||||||
MaxWebSocketClients: 64
|
MaxWebSocketClients: 64
|
||||||
SessionEnabled: false
|
SessionEnabled: false
|
||||||
SessionExpirationTime: 15
|
SessionExpirationTime: 15
|
||||||
|
@ -225,6 +227,10 @@ where:
|
||||||
- `MaxFindStoragePageSize` - the maximum number of elements for `findstorage` response per single page.
|
- `MaxFindStoragePageSize` - the maximum number of elements for `findstorage` response per single page.
|
||||||
- `MaxNEP11Tokens` - limit for the number of tokens returned from
|
- `MaxNEP11Tokens` - limit for the number of tokens returned from
|
||||||
`getnep11balances` call.
|
`getnep11balances` call.
|
||||||
|
- `MaxRequestBodyBytes` - the maximum allowed HTTP request body size in bytes
|
||||||
|
(5MB by default).
|
||||||
|
- `MaxRequestHeaderBytes` - the maximum allowed HTTP request header size in bytes
|
||||||
|
(1MB by default).
|
||||||
- `MaxWebSocketClients` - the maximum simultaneous websocket client connection
|
- `MaxWebSocketClients` - the maximum simultaneous websocket client connection
|
||||||
number (64 by default). Attempts to establish additional connections will
|
number (64 by default). Attempts to establish additional connections will
|
||||||
lead to websocket handshake failures. Use "-1" to disable websocket
|
lead to websocket handshake failures. Use "-1" to disable websocket
|
||||||
|
|
|
@ -3,6 +3,7 @@ package config
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -30,6 +31,12 @@ const (
|
||||||
// DefaultMaxNEP11Tokens is the default maximum number of resulting NEP11 tokens
|
// DefaultMaxNEP11Tokens is the default maximum number of resulting NEP11 tokens
|
||||||
// that can be traversed by `getnep11balances` JSON-RPC handler.
|
// that can be traversed by `getnep11balances` JSON-RPC handler.
|
||||||
DefaultMaxNEP11Tokens = 100
|
DefaultMaxNEP11Tokens = 100
|
||||||
|
// DefaultMaxRequestBodyBytes is the default maximum allowed size of HTTP
|
||||||
|
// request body in bytes.
|
||||||
|
DefaultMaxRequestBodyBytes = 5 * 1024 * 1024
|
||||||
|
// DefaultMaxRequestHeaderBytes is the maximum permitted size of the headers
|
||||||
|
// in an HTTP request.
|
||||||
|
DefaultMaxRequestHeaderBytes = http.DefaultMaxHeaderBytes
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version is the version of the node, set at the build time.
|
// Version is the version of the node, set at the build time.
|
||||||
|
|
|
@ -16,6 +16,8 @@ type (
|
||||||
MaxFindResultItems int `yaml:"MaxFindResultItems"`
|
MaxFindResultItems int `yaml:"MaxFindResultItems"`
|
||||||
MaxFindStorageResultItems int `yaml:"MaxFindStoragePageSize"`
|
MaxFindStorageResultItems int `yaml:"MaxFindStoragePageSize"`
|
||||||
MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"`
|
MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"`
|
||||||
|
MaxRequestBodyBytes int `yaml:"MaxRequestBodyBytes"`
|
||||||
|
MaxRequestHeaderBytes int `yaml:"MaxRequestHeaderBytes"`
|
||||||
MaxWebSocketClients int `yaml:"MaxWebSocketClients"`
|
MaxWebSocketClients int `yaml:"MaxWebSocketClients"`
|
||||||
SessionEnabled bool `yaml:"SessionEnabled"`
|
SessionEnabled bool `yaml:"SessionEnabled"`
|
||||||
SessionExpirationTime int `yaml:"SessionExpirationTime"`
|
SessionExpirationTime int `yaml:"SessionExpirationTime"`
|
||||||
|
|
|
@ -115,6 +115,15 @@ func (s *SignerWithWitness) UnmarshalJSON(data []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("not a signer: %w", err)
|
return fmt.Errorf("not a signer: %w", err)
|
||||||
}
|
}
|
||||||
|
if len(aux.AllowedContracts) > transaction.MaxAttributes {
|
||||||
|
return fmt.Errorf("invalid number of AllowedContracts: got %d, allowed %d at max", len(aux.AllowedContracts), transaction.MaxAttributes)
|
||||||
|
}
|
||||||
|
if len(aux.AllowedGroups) > transaction.MaxAttributes {
|
||||||
|
return fmt.Errorf("invalid number of AllowedGroups: got %d, allowed %d at max", len(aux.AllowedGroups), transaction.MaxAttributes)
|
||||||
|
}
|
||||||
|
if len(aux.Rules) > transaction.MaxAttributes {
|
||||||
|
return fmt.Errorf("invalid number of Rules: got %d, allowed %d at max", len(aux.Rules), transaction.MaxAttributes)
|
||||||
|
}
|
||||||
acc, err := util.Uint160DecodeStringLE(strings.TrimPrefix(aux.Account, "0x"))
|
acc, err := util.Uint160DecodeStringLE(strings.TrimPrefix(aux.Account, "0x"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
acc, err = address.StringToUint160(aux.Account)
|
acc, err = address.StringToUint160(aux.Account)
|
||||||
|
|
|
@ -2,10 +2,12 @@ package neorpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -37,4 +39,54 @@ func TestSignerWithWitnessMarshalUnmarshalJSON(t *testing.T) {
|
||||||
actual, err := json.Marshal(s)
|
actual, err := json.Marshal(s)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, string(actual))
|
require.Equal(t, expected, string(actual))
|
||||||
|
|
||||||
|
t.Run("subitems overflow", func(t *testing.T) {
|
||||||
|
checkSubitems := func(t *testing.T, bad any) {
|
||||||
|
data, err := json.Marshal(bad)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = json.Unmarshal(data, &SignerWithWitness{})
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), fmt.Sprintf("got %d, allowed %d at max", transaction.MaxAttributes+1, transaction.MaxAttributes))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("groups", func(t *testing.T) {
|
||||||
|
pk, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
bad := &SignerWithWitness{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
AllowedGroups: make([]*keys.PublicKey, transaction.MaxAttributes+1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i := range bad.AllowedGroups {
|
||||||
|
bad.AllowedGroups[i] = pk.PublicKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSubitems(t, bad)
|
||||||
|
})
|
||||||
|
t.Run("contracts", func(t *testing.T) {
|
||||||
|
bad := &SignerWithWitness{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
AllowedContracts: make([]util.Uint160, transaction.MaxAttributes+1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSubitems(t, bad)
|
||||||
|
})
|
||||||
|
t.Run("rules", func(t *testing.T) {
|
||||||
|
bad := &SignerWithWitness{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
Rules: make([]transaction.WitnessRule, transaction.MaxAttributes+1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i := range bad.Rules {
|
||||||
|
bad.Rules[i] = transaction.WitnessRule{
|
||||||
|
Action: transaction.WitnessAllow,
|
||||||
|
Condition: &transaction.ConditionScriptHash{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSubitems(t, bad)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -394,7 +394,7 @@ func (p *Param) GetSignerWithWitness() (neorpc.SignerWithWitness, error) {
|
||||||
// GetSignersWithWitnesses returns a slice of SignerWithWitness with CalledByEntry
|
// GetSignersWithWitnesses returns a slice of SignerWithWitness with CalledByEntry
|
||||||
// scope from an array of Uint160 or an array of serialized transaction.Signer stored
|
// scope from an array of Uint160 or an array of serialized transaction.Signer stored
|
||||||
// in the parameter.
|
// in the parameter.
|
||||||
func (p Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) {
|
func (p *Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) {
|
||||||
hashes, err := p.GetArray()
|
hashes, err := p.GetArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
|
@ -267,25 +267,6 @@ var rpcWsHandlers = map[string]func(*Server, params.Params, *subscriber) (any, *
|
||||||
// untyped nil or non-nil structure implementing OracleHandler interface.
|
// untyped nil or non-nil structure implementing OracleHandler interface.
|
||||||
func New(chain Ledger, conf config.RPC, coreServer *network.Server,
|
func New(chain Ledger, conf config.RPC, coreServer *network.Server,
|
||||||
orc OracleHandler, log *zap.Logger, errChan chan<- error) Server {
|
orc OracleHandler, log *zap.Logger, errChan chan<- error) Server {
|
||||||
addrs := conf.Addresses
|
|
||||||
httpServers := make([]*http.Server, len(addrs))
|
|
||||||
for i, addr := range addrs {
|
|
||||||
httpServers[i] = &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tlsServers []*http.Server
|
|
||||||
if cfg := conf.TLSConfig; cfg.Enabled {
|
|
||||||
addrs := cfg.Addresses
|
|
||||||
tlsServers = make([]*http.Server, len(addrs))
|
|
||||||
for i, addr := range addrs {
|
|
||||||
tlsServers[i] = &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protoCfg := chain.GetConfig().ProtocolConfiguration
|
protoCfg := chain.GetConfig().ProtocolConfiguration
|
||||||
if conf.SessionEnabled {
|
if conf.SessionEnabled {
|
||||||
if conf.SessionExpirationTime <= 0 {
|
if conf.SessionExpirationTime <= 0 {
|
||||||
|
@ -313,6 +294,14 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server,
|
||||||
conf.MaxNEP11Tokens = config.DefaultMaxNEP11Tokens
|
conf.MaxNEP11Tokens = config.DefaultMaxNEP11Tokens
|
||||||
log.Info("MaxNEP11Tokens is not set or wrong, setting default value", zap.Int("MaxNEP11Tokens", config.DefaultMaxNEP11Tokens))
|
log.Info("MaxNEP11Tokens is not set or wrong, setting default value", zap.Int("MaxNEP11Tokens", config.DefaultMaxNEP11Tokens))
|
||||||
}
|
}
|
||||||
|
if conf.MaxRequestBodyBytes <= 0 {
|
||||||
|
conf.MaxRequestBodyBytes = config.DefaultMaxRequestBodyBytes
|
||||||
|
log.Info("MaxRequestBodyBytes is not set or wong, setting default value", zap.Int("MaxRequestBodyBytes", config.DefaultMaxRequestBodyBytes))
|
||||||
|
}
|
||||||
|
if conf.MaxRequestHeaderBytes <= 0 {
|
||||||
|
conf.MaxRequestHeaderBytes = config.DefaultMaxRequestHeaderBytes
|
||||||
|
log.Info("MaxRequestHeaderBytes is not set or wong, setting default value", zap.Int("MaxRequestHeaderBytes", config.DefaultMaxRequestHeaderBytes))
|
||||||
|
}
|
||||||
if conf.MaxWebSocketClients == 0 {
|
if conf.MaxWebSocketClients == 0 {
|
||||||
conf.MaxWebSocketClients = defaultMaxWebSocketClients
|
conf.MaxWebSocketClients = defaultMaxWebSocketClients
|
||||||
log.Info("MaxWebSocketClients is not set or wrong, setting default value", zap.Int("MaxWebSocketClients", defaultMaxWebSocketClients))
|
log.Info("MaxWebSocketClients is not set or wrong, setting default value", zap.Int("MaxWebSocketClients", defaultMaxWebSocketClients))
|
||||||
|
@ -325,6 +314,28 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server,
|
||||||
if conf.EnableCORSWorkaround {
|
if conf.EnableCORSWorkaround {
|
||||||
wsOriginChecker = func(_ *http.Request) bool { return true }
|
wsOriginChecker = func(_ *http.Request) bool { return true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addrs := conf.Addresses
|
||||||
|
httpServers := make([]*http.Server, len(addrs))
|
||||||
|
for i, addr := range addrs {
|
||||||
|
httpServers[i] = &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
MaxHeaderBytes: conf.MaxRequestHeaderBytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsServers []*http.Server
|
||||||
|
if cfg := conf.TLSConfig; cfg.Enabled {
|
||||||
|
addrs := cfg.Addresses
|
||||||
|
tlsServers = make([]*http.Server, len(addrs))
|
||||||
|
for i, addr := range addrs {
|
||||||
|
tlsServers[i] = &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
MaxHeaderBytes: conf.MaxRequestHeaderBytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Server{
|
return Server{
|
||||||
http: httpServers,
|
http: httpServers,
|
||||||
https: tlsServers,
|
https: tlsServers,
|
||||||
|
@ -474,6 +485,8 @@ func (s *Server) SetOracleHandler(orc OracleHandler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Request) {
|
func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Request) {
|
||||||
|
// Restrict request body before further processing.
|
||||||
|
httpRequest.Body = http.MaxBytesReader(w, httpRequest.Body, int64(s.config.MaxRequestBodyBytes))
|
||||||
req := params.NewRequest()
|
req := params.NewRequest()
|
||||||
|
|
||||||
if httpRequest.URL.Path == "/ws" && httpRequest.Method == "GET" {
|
if httpRequest.URL.Path == "/ws" && httpRequest.Method == "GET" {
|
||||||
|
|
Loading…
Reference in a new issue