Skip to content

Commit

Permalink
ROX-13934: Scanner running in Node Inventory mode (#1116)
Browse files Browse the repository at this point in the history
  • Loading branch information
fredrb authored Mar 20, 2023
1 parent b748a26 commit 581c726
Show file tree
Hide file tree
Showing 13 changed files with 1,484 additions and 46 deletions.
103 changes: 71 additions & 32 deletions api/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ func init() {
}

// NewAPI creates a new gRPC API instantiation
func NewAPI(config Config) API {
func NewAPI(opts ...ConfigOpts) API {
var config Config
for _, opt := range opts {
opt(&config)
}

return &apiImpl{
config: config,
}
Expand Down Expand Up @@ -56,7 +61,7 @@ func (a *apiImpl) connectToLocalEndpoint() (*grpc.ClientConn, error) {
}

func (a *apiImpl) Start() {
grpcServer := grpc.NewServer(grpcmiddleware.WithUnaryServerChain(a.unaryInterceptors()...))
grpcServer := grpc.NewServer(grpcmiddleware.WithUnaryServerChain(a.config.UnaryInterceptors...))
for _, serv := range a.apiServices {
serv.RegisterServiceServer(grpcServer)
}
Expand All @@ -72,26 +77,29 @@ func (a *apiImpl) Start() {

gwHandler := a.muxer(conn)

addr := fmt.Sprintf(":%d", a.config.Port)
log.Infof("Listening on public endpoint: %v", addr)
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal(err)
}
conf, err := mtls.TLSServerConfig()
if err != nil {
log.Fatal(err)
}

lis = tls.NewListener(lis, conf)
handler := httpGrpcRouter(grpcServer, gwHandler)
go func() {
server := http.Server{
Handler: handler,
ErrorLog: golog.New(httpErrorLogger{}, "", golog.LstdFlags),
var publicListener net.Listener
if a.config.PublicEndpoint {
addr := fmt.Sprintf(":%d", a.config.Port)
log.Infof("Listening on public endpoint: %v", addr)
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal(err)
}
log.Fatal(server.Serve(lis))
}()
conf, err := mtls.TLSServerConfig()
if err != nil {
log.Fatal(err)
}

publicListener = tls.NewListener(lis, conf)
handler := httpGrpcRouter(grpcServer, gwHandler)
go func() {
server := http.Server{
Handler: handler,
ErrorLog: golog.New(httpErrorLogger{}, "", golog.LstdFlags),
}
log.Fatal(server.Serve(publicListener))
}()
}
}

// APIService is the service interface
Expand All @@ -115,24 +123,55 @@ type apiImpl struct {

// A Config configures the server.
type Config struct {
Port int
CustomRoutes map[string]http.Handler
Port int
CustomRoutes map[string]http.Handler
UnaryInterceptors []grpc.UnaryServerInterceptor
PublicEndpoint bool
}

func (a *apiImpl) Register(services ...APIService) {
a.apiServices = append(a.apiServices, services...)
// ConfigOpts defines configurations to start a gRPC server.
type ConfigOpts func(cfg *Config)

// WithTLSEndpoint starts the gRPC server with TLS enabled on port.
func WithTLSEndpoint(port int) ConfigOpts {
return func(cfg *Config) {
cfg.Port = port
cfg.PublicEndpoint = true
}
}

// WithDefaultInterceptors should be used when starting Scanner API. This interceptors list contains interceptors
// that check method availability on slim mode and TLS client checks.
func WithDefaultInterceptors() ConfigOpts {
return func(cfg *Config) {
// Interceptors are executed in order.
cfg.UnaryInterceptors = []grpc.UnaryServerInterceptor{
// Ensure the user is authorized before doing anything else.
verifyPeerCertsUnaryServerInterceptor(),
slimModeUnaryServerInterceptor(),
grpcprometheus.UnaryServerInterceptor,
}
}
}

func (a *apiImpl) unaryInterceptors() []grpc.UnaryServerInterceptor {
// Interceptors are executed in order.
return []grpc.UnaryServerInterceptor{
// Ensure the user is authorized before doing anything else.
verifyPeerCertsUnaryServerInterceptor(),
slimModeUnaryServerInterceptor(),
grpcprometheus.UnaryServerInterceptor,
// WithCustomUnaryInterceptors should be used to set custom GRPC interceptors.
func WithCustomUnaryInterceptors(interceptors ...grpc.UnaryServerInterceptor) ConfigOpts {
return func(cfg *Config) {
cfg.UnaryInterceptors = interceptors
}
}

// WithCustomRoutes sets custom HTTP routes.
func WithCustomRoutes(routes map[string]http.Handler) ConfigOpts {
return func(cfg *Config) {
cfg.CustomRoutes = routes
}
}

func (a *apiImpl) Register(services ...APIService) {
a.apiServices = append(a.apiServices, services...)
}

func (a *apiImpl) muxer(localConn *grpc.ClientConn) http.Handler {
mux := http.NewServeMux()
for route, handler := range a.config.CustomRoutes {
Expand Down
59 changes: 59 additions & 0 deletions api/v1/nodeinventory/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package nodeinventory

import (
"context"

"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
apiGRPC "github.com/stackrox/scanner/api/grpc"
v1 "github.com/stackrox/scanner/generated/scanner/api/v1"
"github.com/stackrox/scanner/pkg/nodeinventory"
"google.golang.org/grpc"
)

// Service defines the node scanning service.
type Service interface {
apiGRPC.APIService

v1.NodeInventoryServiceServer
}

// NewService returns the service for node scanning
func NewService(nodeName string) Service {
return &serviceImpl{
inventoryCollector: &nodeinventory.Scanner{},
nodeName: nodeName,
}
}

type serviceImpl struct {
inventoryCollector *nodeinventory.Scanner
nodeName string
}

func (s *serviceImpl) GetNodeInventory(ctx context.Context, req *v1.GetNodeInventoryRequest) (*v1.GetNodeInventoryResponse, error) {
inventoryScan, err := s.inventoryCollector.Scan(s.nodeName)
if err != nil {
log.Errorf("Error running inventoryCollector.Scan(%s): %v", s.nodeName, err)
return nil, errors.New("Internal scanner error: failed to scan node")
}

log.Debugf("InventoryScan: %+v", inventoryScan)

return &v1.GetNodeInventoryResponse{
NodeName: s.nodeName,
Components: inventoryScan.Components,
Notes: inventoryScan.Notes,
}, nil
}

// RegisterServiceServer registers this service with the given gRPC Server.
func (s *serviceImpl) RegisterServiceServer(grpcServer *grpc.Server) {
v1.RegisterNodeInventoryServiceServer(grpcServer, s)
}

// RegisterServiceHandler registers this service with the given gRPC Gateway endpoint.
func (s *serviceImpl) RegisterServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return v1.RegisterNodeInventoryServiceHandler(ctx, mux, conn)
}
42 changes: 36 additions & 6 deletions cmd/clair/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import (
"strings"
"time"

grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
log "github.com/sirupsen/logrus"
"github.com/stackrox/rox/pkg/httputil/proxy"
"github.com/stackrox/rox/pkg/sync"
"github.com/stackrox/scanner/api"
"github.com/stackrox/scanner/api/grpc"
"github.com/stackrox/scanner/api/v1/imagescan"
"github.com/stackrox/scanner/api/v1/nodeinventory"
"github.com/stackrox/scanner/api/v1/nodescan"
"github.com/stackrox/scanner/api/v1/orchestratorscan"
"github.com/stackrox/scanner/api/v1/ping"
Expand Down Expand Up @@ -180,10 +182,10 @@ func Boot(config *Config, slimMode bool) {
serv := server.New(fmt.Sprintf(":%d", config.API.HTTPSPort), db)
go api.RunClairify(serv)

grpcAPI := grpc.NewAPI(grpc.Config{
Port: config.API.GRPCPort,
CustomRoutes: debugRoutes,
})
grpcAPI := grpc.NewAPI(
grpc.WithTLSEndpoint(config.API.GRPCPort),
grpc.WithDefaultInterceptors(),
grpc.WithCustomRoutes(debugRoutes))

grpcAPI.Register(
ping.NewService(),
Expand All @@ -201,14 +203,34 @@ func Boot(config *Config, slimMode bool) {
serv.Close()
}

func bootNodeInventoryScanner() {
if env.NodeName.Value() == "" {
log.Errorf("Cannot start node inventory scanner when %s isn't set. Make sure the environment varialbe is set from value spec.nodeName in Kubernetes",
env.NodeName.EnvVar())
}

grpcAPI := grpc.NewAPI(
grpc.WithCustomRoutes(debugRoutes),
grpc.WithCustomUnaryInterceptors(grpcprometheus.UnaryServerInterceptor))

grpcAPI.Register(
ping.NewService(),
nodeinventory.NewService(env.NodeName.Value()),
)
go grpcAPI.Start()

// Wait for interruption and shutdown gracefully.
waitForSignals(os.Interrupt, unix.SIGTERM)
log.Info("Received interruption, gracefully stopping ...")
}

func main() {
// Parse command-line arguments
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
flagConfigPath := flag.String("config", "/etc/scanner/config.yaml", "Load configuration from the specified file.")
flagNodeInventoryMode := flag.Bool("nodeinventory", false, "Run Scanner binary in Node Inventory mode (this should only be used in Secured Clusters)")
flag.Parse()

proxy.WatchProxyConfig(context.Background(), proxyConfigPath, proxyConfigFile, true)

// Check for dependencies.
for _, bin := range []string{"rpm", "xz"} {
_, err := exec.LookPath(bin)
Expand Down Expand Up @@ -254,6 +276,14 @@ func main() {
// Cleanup any residue temporary files.
ioutils.CleanUpTempFiles()

if *flagNodeInventoryMode {
log.Infof("Running Scanner version %s in Node Inventory mode", version.Version)
bootNodeInventoryScanner()
return
}

proxy.WatchProxyConfig(context.Background(), proxyConfigPath, proxyConfigFile, true)

slimMode := env.SlimMode.Enabled()

scannerName := "Scanner"
Expand Down
Loading

0 comments on commit 581c726

Please sign in to comment.