Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

packetbeat/beater: make Npcap installation lazy #35935

Merged
merged 4 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ automatic splitting at root level, if root level element is an array. {pull}3415

*Packetbeat*

- Fix handling of Npcap installation options from Fleet. {pull}35541[35541] {pull}35935[35935]

*Winlogbeat*

Expand Down
2 changes: 1 addition & 1 deletion packetbeat/_meta/config/windows_npcap.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
#packetbeat.npcap:
# # If a specific local version of Npcap is required, installation by packetbeat
# # can be blocked by setting never_install to true. No action is taken if this
# # option is set to true.
# # option is set to true unless no Npcap is already installed.
# never_install: false
{{- end -}}
52 changes: 40 additions & 12 deletions packetbeat/beater/install_npcap.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"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,19 +61,29 @@ func installNpcap(b *beat.Beat) error {
return nil
}

canInstall, err := canInstallNpcap(b)
if err != nil {
return err
}
log := logp.NewLogger("npcap_install")
if !canInstall {
log.Warn("npcap installation/upgrade disabled by user")
return nil
// Only check whether we have been requested to never_install if there
// is already an Npcap installation present. This should not be necessary,
// but the start-up logic of packetbeat is tightly coupled to the presence
// of a backing sniffer. This should really not be necessary, but the changes
// to modify this behaviour are non-trivial, so just avoid the issue.
isInstalled := strings.HasPrefix(npcap.Version(), "Npcap version")
if isInstalled {
canInstall, err := canInstallNpcap(b, cfg, log)
if err != nil {
return err
}
if !canInstall {
log.Warn("npcap installation/upgrade disabled by user")
return nil
}
}

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,22 +112,30 @@ 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, log *logp.Logger) (bool, error) {
type npcapInstallCfg struct {
NeverInstall bool `config:"npcap.never_install"`
Type string `config:"type"`
NeverInstall bool `config:"npcap.never_install"`
}

// Agent managed case.
if b.Manager.Enabled() {
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.
log.Debug("cannot install because no configured stream")
return false, nil
}
for _, c := range cfg.Streams {
if c.NeverInstall {
log.Debugf("cannot install because %s has never_install set to true", c.Type)
return false, nil
}
}
Expand All @@ -119,9 +144,12 @@ 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)
}
if cfg.NeverInstall {
log.Debugf("cannot install because %s has never_install set to true", cfg.Type)
}
return !cfg.NeverInstall, err
}
6 changes: 3 additions & 3 deletions packetbeat/beater/install_npcap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/elastic/beats/v7/libbeat/beat"
"github.com/elastic/beats/v7/libbeat/management"
"github.com/elastic/elastic-agent-libs/config"
"github.com/elastic/elastic-agent-libs/logp"
)

var canInstallNpcapTests = []struct {
Expand Down Expand Up @@ -210,10 +211,9 @@ func TestCanInstallNpcap(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
b := &beat.Beat{
BeatConfig: cfg,
Manager: boolManager{managed: test.managed},
Manager: boolManager{managed: test.managed},
}
got, err := canInstallNpcap(b)
got, err := canInstallNpcap(b, cfg, logp.NewLogger("npcap_install_test"))
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
2 changes: 1 addition & 1 deletion packetbeat/docs/packetbeat-options.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ On Windows {beatname} requires an Npcap DLL installation. This is provided by {b
for users of the Elastic Licenced version. In some cases users may wish to use
their own installed version. In order to do this the `packetbeat.npcap.never_install`
option can be used. Setting this option to `true` will not attempt to install the
bundled Npcap library on start-up.
bundled Npcap library on start-up unless no Npcap is already installed.

[source,yaml]
------------------------------------------------------------------------------
Expand Down
Loading