Skip to content

Commit

Permalink
The EigenDA Node Reachability Scanner allows Operators to initiate po…
Browse files Browse the repository at this point in the history
…rt scans from the EigenDA backend to validate e2e reachability.

```
┌─────────────────────────────────────────────┐               ┌─────────────────────────────────────────────┐
│                                             │               │                                             │
│            EigenDA Operator Node            │               │            EigenDA Operator Node            │
│                                             │               │                                             │
│                                             │               │                                             │
└───────┬────────▲────────────────▲───────────┘               └───────┬─────────────────────────────────────┘
┌───────┴────────┴────────────────┴───────────┐               ┌───────┴─────────────────────────────────────┐
│       Operator Firewall Allowing 32005      │               │       Operator Firewall Blocking 32005      │
└───────┬────────┬────────────────┬───────────┘               └───────┬─────────────────────────────────────┘
        │        │                │                                   │        X                X
    PortCheck    │                │                               PortCheck    │                │
     Request     │                │                                Request     │                │
      32005      │                │                                 32005      │                │
        │        │                │                                   │        │                │
        │        │                │                                   │        │                │
        │        │                │                                   │        │                │
        │        │                │                                   │        │                │
        │  Reachability       Dispersal                               │  Reachability       Dispersal
        │      Check           Request                                │      Check           Request
        │      32005            32005                                 │      32005            32005
┌───────┴────────┴────────────────┴───────────┐               ┌───────┴────────┴────────────────┴───────────┐
│                  Nat Gateway                │               │                  Nat Gateway                │
└───────┬────────┬────────────────┬───────────┘               └───────┬────────┬────────────────┬───────────┘
┌───────┼────────┼────────────────┼───────────┐               ┌───────┼────────┼────────────────┼───────────┐
│       │        │                │           │               │       │        │                │           │
│       ▼        │                │           │               │       ▼        │                │           │
│ ┌──────────────┴────┐┌──────────┴────────┐  │               │ ┌──────────────┴────┐┌──────────┴────────┐  │
│ │                   ││                   │  │               │ │                   ││                   │  │
│ │      scanner      ││     disperser     │  │               │ │      scanner      ││     disperser     │  │
│ │                   ││                   │  │               │ │                   ││                   │  │
│ └───────────────────┘└───────────────────┘  │               │ └───────────────────┘└───────────────────┘  │
│                EigenDA VPC                  │               │                EigenDA VPC                  │
└─────────────────────────────────────────────┘               └─────────────────────────────────────────────┘
```

```
NAME:
   scanner - EigenDA Node Reachability Scanner

USAGE:
   scanner [global options] command [command options] [arguments...]

VERSION:
   1.0.0--

DESCRIPTION:
   Service for checking the reachabilty of operator nodes

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --scanner.port value          Server listening port (default: "32001") [$SCANNER_PORT]
   --scanner.metrics-port value  Metrics listening port (default: "9091") [$SCANNER_METRICS_PORT]
   --scanner.timeout value       Amount of time to wait for port scan to complete (default: "5s") [$SCANNER_TIMEOUT]
   --scanner.log.level value     The lowest log level that will be output. Accepted options are "debug", "info", "warn", "error" (default: "info") [$SCANNER_LOG_LEVEL]
   --scanner.log.path value      Path to file where logs will be written [$SCANNER_LOG_PATH]
   --scanner.log.format value    The format of the log file. Accepted options are 'json' and 'text' (default: "json") [$SCANNER_LOG_FORMAT]
   --help, -h                    show help
   --version, -v                 print the version
```

Lint

Refactor
  • Loading branch information
pschork committed Apr 25, 2024
1 parent cb32efd commit e2236b4
Show file tree
Hide file tree
Showing 14 changed files with 504 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.21.1
require (
github.com/Layr-Labs/eigenda/api v0.0.0
github.com/Layr-Labs/eigensdk-go v0.1.6-0.20240414172936-84d5bc10f72f
github.com/Ullaakut/nmap v2.0.2+incompatible
github.com/aws/aws-sdk-go-v2 v1.26.0
github.com/aws/aws-sdk-go-v2/credentials v1.17.9
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.12
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/Ullaakut/nmap v2.0.2+incompatible h1:edw45QpSQBQ2B/Hqfg86Bt5rrK79tp/fAcqIHyNSdQs=
github.com/Ullaakut/nmap v2.0.2+incompatible/go.mod h1:fkC066hwfcoKwlI7DS2ARTggSVtBTZYCjVH1TzuTMaQ=
github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40=
github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
Expand Down
5 changes: 5 additions & 0 deletions tools/npc/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export SCANNER_LOG_PATH=scanner.log

export SCANNER_LOG_LEVEL=debug
export SCANNER_LOG_FORMAT=text

1 change: 1 addition & 0 deletions tools/npc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npc
18 changes: 18 additions & 0 deletions tools/npc/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
build: clean proto
go mod tidy
go build -o ./bin/npc ./cmd

proto:
protoc --go_out=. npc.proto
protoc --go-grpc_out=. npc.proto

docker:
cd ../../ && docker build . -t npc -f tools/npc/cmd/Dockerfile

clean:
rm -rf ./bin ./npc

lint:
golint -set_exit_status ./...
go tool fix ./..
golangci-lint run
3 changes: 3 additions & 0 deletions tools/npc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# EigenDA Node Reachability Scanner

The EigenDA Node Reachability Scanner allows Operators to initiate port scans from the EigenDA backend to validate e2e reachability.
19 changes: 19 additions & 0 deletions tools/npc/cmd/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM golang:1.21.1-alpine3.18 as builder

RUN apk add --no-cache make musl-dev linux-headers gcc git jq bash

COPY ./tools/npc /app/tools/npc
COPY api /app/api
COPY common /app/common
COPY go.mod /app
COPY go.sum /app

WORKDIR /app/tools/npc

RUN go build -o ./bin/npc ./cmd

FROM alpine:3.18

COPY --from=builder /app/tools/npc/bin/npc /usr/local/bin

ENTRYPOINT ["npc"]
66 changes: 66 additions & 0 deletions tools/npc/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"context"
"fmt"
"log"
"os"

"github.com/urfave/cli"

"github.com/Layr-Labs/eigenda/common"
"github.com/Layr-Labs/eigenda/tools/npc"
"github.com/Layr-Labs/eigenda/tools/npc/flags"
"github.com/Layr-Labs/eigenda/tools/npc/grpc"
)

func main() {
app := cli.NewApp()
app.Flags = flags.Flags
app.Version = fmt.Sprintf("%s-%s-%s", npc.SemVer, npc.GitCommit, npc.GitDate)
app.Name = npc.AppName
app.Usage = "EigenDA Node Port Check Scanner "
app.Description = "Service for checking the reachabilty of operator nodes"

app.Action = NpcMain
err := app.Run(os.Args)
if err != nil {
log.Fatalf("application failed: %v", err)
}

select {}
}

// NpcMain func
func NpcMain(ctx *cli.Context) error {
log.Println("Initializing node npc")
config, err := npc.NewConfig(ctx)
if err != nil {
return err
}

logger, err := common.NewLogger(config.LoggerConfig)
if err != nil {
return err
}

// Create the npc.
npc, err := npc.NewScanner(config, logger)
if err != nil {
return err
}

err = npc.Start(context.Background())
if err != nil {
npc.Logger.Error("could not start node npc", "error", err)
return err
}

// Creates the GRPC server.
server := grpc.NewServer(config, npc, logger)
if err := server.Start(); err != nil {
log.Fatalf("Failed to start server: %v", err)
}

return nil
}
47 changes: 47 additions & 0 deletions tools/npc/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package npc

import (
"time"

"github.com/Layr-Labs/eigenda/common"
"github.com/Layr-Labs/eigenda/tools/npc/flags"
"github.com/urfave/cli"
)

// Npc
const (
AppName = "npc"
SemVer = "1.0.0"
GitCommit = ""
GitDate = ""
)

// Config contains all of the configuration information for a npc
type Config struct {
ServerPort string
MetricsPort string
Timeout time.Duration

LoggerConfig common.LoggerConfig
}

// NewConfig parses the Config from the provided flags or environment variables and
// returns a Config.
func NewConfig(ctx *cli.Context) (*Config, error) {
timeout, err := time.ParseDuration(ctx.GlobalString(flags.TimeoutFlag.Name))
if err != nil {
return &Config{}, err
}

loggerConfig, err := common.ReadLoggerCLIConfig(ctx, flags.FlagPrefix)
if err != nil {
return nil, err
}

return &Config{
ServerPort: ctx.GlobalString(flags.ServerPortFlag.Name),
MetricsPort: ctx.GlobalString(flags.MetricsPortFlag.Name),
Timeout: timeout,
LoggerConfig: *loggerConfig,
}, nil
}
58 changes: 58 additions & 0 deletions tools/npc/flags/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package flags

import (
"github.com/Layr-Labs/eigenda/common"
"github.com/urfave/cli"
)

// constants
const (
FlagPrefix = "npc"
EnvVarPrefix = "NPC"
)

var (
/* Required Flags */

// ServerPortFlag used to serve requests
ServerPortFlag = cli.StringFlag{
Name: common.PrefixFlag(FlagPrefix, "port"),
Usage: "Server listening port",
Value: "32001",
Required: false,
EnvVar: common.PrefixEnvVar(EnvVarPrefix, "PORT"),
}
// MetricsPortFlag used to serve metrics
MetricsPortFlag = cli.StringFlag{
Name: common.PrefixFlag(FlagPrefix, "metrics-port"),
Usage: "Metrics listening port",
Required: false,
Value: "9091",
EnvVar: common.PrefixEnvVar(EnvVarPrefix, "METRICS_PORT"),
}
// TimeoutFlag determines scan timeout
TimeoutFlag = cli.StringFlag{
Name: common.PrefixFlag(FlagPrefix, "timeout"),
Usage: "Amount of time to wait for port scan to complete",
Required: false,
Value: "5s",
EnvVar: common.PrefixEnvVar(EnvVarPrefix, "TIMEOUT"),
}
)

var requiredFlags = []cli.Flag{
ServerPortFlag,
MetricsPortFlag,
TimeoutFlag,
}

var optionalFlags = []cli.Flag{}

// init
func init() {
Flags = append(requiredFlags, optionalFlags...)
Flags = append(Flags, common.LoggerCLIFlags(EnvVarPrefix, FlagPrefix)...)
}

// Flags contains the list of configuration options available to the binary.
var Flags []cli.Flag
150 changes: 150 additions & 0 deletions tools/npc/grpc/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package grpc

import (
"context"
"fmt"
"log"
"net"
"time"

"github.com/Layr-Labs/eigenda/tools/npc"
pb "github.com/Layr-Labs/eigenda/tools/npc/npc/grpc"

"github.com/Layr-Labs/eigensdk-go/logging"
"github.com/Ullaakut/nmap"

"google.golang.org/grpc"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/reflection"
)

const localhost = "0.0.0.0"

// Server implements the Npc scanner proto APIs.
type Server struct {
pb.UnimplementedReachabilityServer

npc *npc.Scanner
config *npc.Config
logger logging.Logger
}

// NewServer func
func NewServer(config *npc.Config, npc *npc.Scanner, logger logging.Logger) *Server {
return &Server{
config: config,
logger: logger,
npc: npc,
}
}

// Start func
func (s *Server) Start() error {

addr := fmt.Sprintf("%s:%s", localhost, s.config.ServerPort)
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("failed to start tcp listener: %v", err)
}

gs := grpc.NewServer()
reflection.Register(gs)
pb.RegisterReachabilityServer(gs, s)

s.logger.Info("port", s.config.ServerPort, "address", lis.Addr().String(), "GRPC Listening")
if err := gs.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
return nil
}

// PortCheck func
func (s *Server) PortCheck(ctx context.Context, in *pb.PortCheckRequest) (*pb.PortCheckResponse, error) {

p, ok := peer.FromContext(ctx)
if !ok {
log.Println("Could not get peer from context")
return &pb.PortCheckResponse{Status: "error", Msg: "Could not get peer ip from context"}, nil
}
peerAddr := p.Addr.String()
ip, _, err := net.SplitHostPort(peerAddr)
if err != nil {
log.Printf("Could not split host and port: %v", err)
return &pb.PortCheckResponse{Status: "error", Msg: "Could not parse host ip"}, nil
}

var timeout = 5 * time.Second
_, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

scan, err := nmap.NewScanner(
nmap.WithTargets(ip),
nmap.WithPorts(fmt.Sprintf("%d", in.Port)),
)
if err != nil {
//log.Fatalf("unable to create nmap npc: %v", err)
return &pb.PortCheckResponse{Status: "error", Msg: "Port scan failed", Host: ip, Port: in.Port}, nil
}

result, warnings, err := scan.Run()
if len(warnings) > 0 {
log.Printf("run finished with warnings: %s\n", warnings) // Warnings are non-critical errors from nmap.
}
if err != nil {
//log.Fatalf("unable to run nmap scan: %v", err)
return &pb.PortCheckResponse{Status: "error", Msg: "Port scan failed", Host: ip, Port: in.Port}, nil
}

// Use the results to print an example output
host := result.Hosts[0]
if len(host.Ports) == 0 || len(host.Addresses) == 0 {
return &pb.PortCheckResponse{Status: "error", Msg: "No scan results"}, nil
}

port := host.Ports[0]
s.logger.Info("Port Check", ip, port.ID, port.State)
fmt.Printf("Host scanned in %.2f seconds\n", result.Stats.Finished.Elapsed)
return &pb.PortCheckResponse{Status: port.State.String(), Msg: "", Port: in.Port, Host: ip}, nil
}

// HostPortCheck func
func (s *Server) HostPortCheck(ctx context.Context, in *pb.HostPortCheckRequest) (*pb.PortCheckResponse, error) {

if in.Host == "" {
//log.Fatalf("unable to create nmap npc: %v", err)
return &pb.PortCheckResponse{Status: "error", Msg: "Host must be specified", Host: in.Host, Port: in.Port}, nil
}
var timeout = 5 * time.Second
_, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

scan, err := nmap.NewScanner(
nmap.WithTargets(in.Host),
nmap.WithPorts(fmt.Sprintf("%d", in.Port)),
)
if err != nil {
//log.Fatalf("unable to create nmap scanner: %v", err)
return &pb.PortCheckResponse{Status: "error", Msg: "Port scan failed", Host: in.Host, Port: in.Port}, nil
}

result, warnings, err := scan.Run()
if len(warnings) > 0 {
log.Printf("run finished with warnings: %s\n", warnings) // Warnings are non-critical errors from nmap.
}
if err != nil {
//log.Fatalf("unable to run nmap scan: %v", err)
return &pb.PortCheckResponse{Status: "error", Msg: "Port scan failed", Host: in.Host, Port: in.Port}, nil
}

// Use the results to print an example output
host := result.Hosts[0]
if len(host.Ports) == 0 || len(host.Addresses) == 0 {
return &pb.PortCheckResponse{Status: "error", Msg: "No scan results"}, nil
}

port := host.Ports[0]

fmt.Printf("\tPort %d %s\n", port.ID, port.State)
fmt.Printf("Host scanned in %.2f seconds\n", result.Stats.Finished.Elapsed)
return &pb.PortCheckResponse{Status: port.State.String(), Msg: "", Port: in.Port, Host: in.Host}, nil
}
Loading

0 comments on commit e2236b4

Please sign in to comment.