node/pkg/p2p: expose network guardian version metric
Fixes https://github.com/certusone/wormhole/issues/305 The logic to do this seemingly simple task is hilariously complex due to the version string being attacker-controlled. Change-Id: Ia1758418a67c082595affe0b7f2bb801e9434733
This commit is contained in:
parent
621962982a
commit
db4d325cb6
|
@ -3,10 +3,14 @@ package p2p
|
|||
import (
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/vaa"
|
||||
"github.com/certusone/wormhole/node/pkg/version"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -20,6 +24,11 @@ var (
|
|||
Name: "wormhole_network_node_errors_count",
|
||||
Help: "Number of errors the given guardian node encountered per network",
|
||||
}, []string{"guardian_addr", "node_id", "node_name", "network"})
|
||||
wormholeNetworkVersion = promauto.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wormhole_network_node_version",
|
||||
Help: "Network version of the given guardian node per network",
|
||||
}, []string{"guardian_addr", "node_id", "node_name", "network", "version"})
|
||||
)
|
||||
|
||||
func collectNodeMetrics(addr common.Address, peerId peer.ID, hb *gossipv1.Heartbeat) {
|
||||
|
@ -35,5 +44,44 @@ func collectNodeMetrics(addr common.Address, peerId peer.ID, hb *gossipv1.Heartb
|
|||
|
||||
wormholeNetworkNodeErrors.WithLabelValues(
|
||||
addr.Hex(), peerId.Pretty(), hb.NodeName, chain.String()).Set(float64(n.ErrorCount))
|
||||
|
||||
wormholeNetworkVersion.WithLabelValues(
|
||||
addr.Hex(), peerId.Pretty(), hb.NodeName, chain.String(),
|
||||
sanitizeVersion(hb.Version, version.Version())).Set(1)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// Parse version string using regular expression.
|
||||
// The version string should be in the format of "vX.Y.Z"
|
||||
// where X, Y and Z are integers. Suffixes are ignored.
|
||||
reVersion = regexp.MustCompile(`^v(\d+)\.(\d+)\.(\d+)`)
|
||||
)
|
||||
|
||||
// sanitizeVersion cleans up the version string to prevent an attacker from executing a cardinality attack.
|
||||
func sanitizeVersion(version string, reference string) string {
|
||||
// Match groups of reVersion
|
||||
components := reVersion.FindStringSubmatch(version)
|
||||
referenceComponents := reVersion.FindStringSubmatch(reference)
|
||||
|
||||
// Compare components of the version string with the reference and ensure
|
||||
// that the distance is less than 5.
|
||||
for i, c := range components {
|
||||
if len(referenceComponents) <= i {
|
||||
return "other"
|
||||
}
|
||||
|
||||
cInt, _ := strconv.Atoi(c)
|
||||
cRefInt, _ := strconv.Atoi(referenceComponents[i])
|
||||
|
||||
if math.Abs(float64(cInt-cRefInt)) > 5 {
|
||||
return "other"
|
||||
}
|
||||
}
|
||||
|
||||
v := reVersion.FindString(version)
|
||||
if v == "" {
|
||||
return "other"
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package p2p
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type sanitizeVersionCase struct {
|
||||
version string
|
||||
ref string
|
||||
want string
|
||||
}
|
||||
|
||||
func Test_sanitizeVersion(t *testing.T) {
|
||||
cases := []sanitizeVersionCase{
|
||||
{version: "v1.0.0", ref: "v1.0.0", want: "v1.0.0"},
|
||||
{version: "v1.0.0-foo", ref: "v1.0.0", want: "v1.0.0"},
|
||||
{version: "v1.0.0-foo", ref: "v1.0.0-bar", want: "v1.0.0"},
|
||||
{version: "v6.0.0-foo", ref: "v1.0.0-bar", want: "v6.0.0"},
|
||||
{version: "v6.1.0-foo", ref: "v1.0.0-bar", want: "v6.1.0"},
|
||||
{version: "v6.1.0-foo", ref: "v4.5.0-bar", want: "v6.1.0"},
|
||||
{version: "v6.1.0.1.1.1", ref: "v4.5.0.2.2.2", want: "v6.1.0"},
|
||||
{version: "v10.1.0-foo", ref: "v1.0.0", want: "other"},
|
||||
{version: "notaversion", ref: "v1.0.0", want: "other"},
|
||||
{version: "v6.1.10000000", ref: "v1.0.0-bar", want: "other"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
got := sanitizeVersion(c.version, c.ref)
|
||||
if got != c.want {
|
||||
t.Errorf("sanitizeVersion(%q, %q) == %q, want %q", c.version, c.ref, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue