Skip to content

Commit

Permalink
packetbeat/beater: make Npcap installation lazy
Browse files Browse the repository at this point in the history
In the fleet-managed case New is called without a complete config, which means
that we have no stream to tell us not to install the Npcap DLL.

Defer installation until we have a valid config with streams configured so that
we can determine whether the user has blocked install and whether we need the DLL.
  • Loading branch information
efd6 committed Jun 27, 2023
1 parent 27763e8 commit 5aaa950
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 15 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ automatic splitting at root level, if root level element is an array. {pull}3415

- Fix double channel close panic when reloading. {pull}35324[35324]
- Fix BPF filter setting not being applied to sniffers. {issue}35363[35363] {pull}35484[35484]
- Fix handling of Npcap installation options from Fleet. {pull}35541[35541]
- Fix handling of Npcap installation options from Fleet. {pull}35541[35541] {pull}[]

*Winlogbeat*

Expand Down
23 changes: 18 additions & 5 deletions packetbeat/beater/install_npcap.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,22 @@ import (
"os"
"path/filepath"
"runtime"
"sync"
"time"

"github.com/elastic/beats/v7/libbeat/beat"
"github.com/elastic/beats/v7/packetbeat/npcap"
conf "github.com/elastic/elastic-agent-libs/config"
"github.com/elastic/elastic-agent-libs/logp"
)

const installTimeout = 120 * time.Second

func installNpcap(b *beat.Beat) error {
// muInstall protects use of npcap.Installer. The only writes to npcap.Installer
// are here and during init in x-pack/packetbeat/npcap/npcap_windows.go
var muInstall sync.Mutex

func installNpcap(b *beat.Beat, cfg *conf.C) error {
if !b.Info.ElasticLicensed {
return nil
}
Expand All @@ -54,7 +60,7 @@ func installNpcap(b *beat.Beat) error {
return nil
}

canInstall, err := canInstallNpcap(b)
canInstall, err := canInstallNpcap(b, cfg)
if err != nil {
return err
}
Expand All @@ -67,6 +73,8 @@ func installNpcap(b *beat.Beat) error {
ctx, cancel := context.WithTimeout(context.Background(), installTimeout)
defer cancel()

muInstall.Lock()
defer muInstall.Unlock()
if npcap.Installer == nil {
return nil
}
Expand Down Expand Up @@ -95,7 +103,7 @@ func installNpcap(b *beat.Beat) error {
// configurations from agent normalised to the internal packetbeat format by this point.
// In the case that the beat is managed, any data stream that has npcap.never_install
// set to true will result in a block on the installation.
func canInstallNpcap(b *beat.Beat) (bool, error) {
func canInstallNpcap(b *beat.Beat, rawcfg *conf.C) (bool, error) {
type npcapInstallCfg struct {
NeverInstall bool `config:"npcap.never_install"`
}
Expand All @@ -105,10 +113,15 @@ func canInstallNpcap(b *beat.Beat) (bool, error) {
var cfg struct {
Streams []npcapInstallCfg `config:"streams"`
}
err := b.BeatConfig.Unpack(&cfg)
err := rawcfg.Unpack(&cfg)
if err != nil {
return false, fmt.Errorf("failed to unpack npcap config from agent configuration: %w", err)
}
if len(cfg.Streams) == 0 {
// We have no stream to monitor, so we don't need to install
// anything. We may be in the middle of a config check.
return false, nil
}
for _, c := range cfg.Streams {
if c.NeverInstall {
return false, nil
Expand All @@ -119,7 +132,7 @@ func canInstallNpcap(b *beat.Beat) (bool, error) {

// Packetbeat case.
var cfg npcapInstallCfg
err := b.BeatConfig.Unpack(&cfg)
err := rawcfg.Unpack(&cfg)
if err != nil {
return false, fmt.Errorf("failed to unpack npcap config from packetbeat configuration: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion packetbeat/beater/install_npcap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func TestCanInstallNpcap(t *testing.T) {
BeatConfig: cfg,
Manager: boolManager{managed: test.managed},
}
got, err := canInstallNpcap(b)
got, err := canInstallNpcap(b, b.BeatConfig)
if err != nil {
t.Errorf("unexpected error from canInstallNpcap: %v", err)
}
Expand Down
8 changes: 0 additions & 8 deletions packetbeat/beater/packetbeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,6 @@ func New(b *beat.Beat, rawConfig *conf.C) (beat.Beater, error) {
configurator = initialConfig().FromStatic
}

// Install Npcap if needed. This need to happen before any other
// work on Windows, including config checking, because that involves
// probing interfaces.
err := installNpcap(b)
if err != nil {
return nil, err
}

factory := newProcessorFactory(b.Info.Name, make(chan error, maxSniffers), b, configurator)
if err := factory.CheckConfig(rawConfig); err != nil {
return nil, err
Expand Down
18 changes: 18 additions & 0 deletions packetbeat/beater/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ func (p *processorFactory) Create(pipeline beat.PipelineConnector, cfg *conf.C)
logp.Err("Failed to generate ID from config: %v, %v", err, config)
return nil, err
}
if len(config.Interfaces) != 0 {
// Install Npcap if needed. This needs to happen before any other
// work on Windows, including config checking, because that involves
// probing interfaces.
//
// Users may block installation of Npcap, so we defer the install
// until we have a configuration that will tell us if it has been
// blocked. To do this we must have a valid config.
//
// When Packetbeat is managed by fleet we will only have this if
// Create has been called via the agent Reload process. We take
// the opportunity to not install the DLL if there is no configured
// interface.
err := installNpcap(p.beat, cfg)
if err != nil {
return nil, err
}
}

publisher, err := publish.NewTransactionPublisher(
p.beat.Info.Name,
Expand Down

0 comments on commit 5aaa950

Please sign in to comment.