Skip to content

Commit 62c2377

Browse files
committed
Add NGINX App Protect instance watcher
1 parent 7aab2ee commit 62c2377

File tree

2 files changed

+53
-21
lines changed

2 files changed

+53
-21
lines changed

internal/watcher/instance/nginx-app-protect-instance-watcher.go

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type NginxAppProtectInstanceWatcher struct {
4040
watcher *fsnotify.Watcher
4141
instancesChannel chan<- InstanceUpdatesMessage
4242
nginxAppProtectInstance *mpi.Instance
43+
filesBeingWatcher map[string]bool
4344
version string
4445
release string
4546
attackSignatureVersion string
@@ -49,7 +50,8 @@ type NginxAppProtectInstanceWatcher struct {
4950

5051
func NewNginxAppProtectInstanceWatcher(agentConfig *config.Config) *NginxAppProtectInstanceWatcher {
5152
return &NginxAppProtectInstanceWatcher{
52-
agentConfig: agentConfig,
53+
agentConfig: agentConfig,
54+
filesBeingWatcher: make(map[string]bool),
5355
}
5456
}
5557

@@ -85,6 +87,8 @@ func (w *NginxAppProtectInstanceWatcher) Watch(ctx context.Context, instancesCha
8587

8688
return
8789
case <-instanceWatcherTicker.C:
90+
// Need to keep watching directories in case NAP gets installed a while after NGINX Agent is started
91+
w.watchDirectories(ctx)
8892
w.checkForUpdates(ctx)
8993
case event := <-w.watcher.Events:
9094
w.handleEvent(ctx, event)
@@ -96,29 +100,52 @@ func (w *NginxAppProtectInstanceWatcher) Watch(ctx context.Context, instancesCha
96100

97101
func (w *NginxAppProtectInstanceWatcher) watchDirectories(ctx context.Context) {
98102
for _, versionFile := range versionFiles {
99-
if err := w.watcher.Add(versionFile); err != nil {
103+
if !w.filesBeingWatcher[versionFile] {
104+
if _, fileOs := os.Stat(versionFile); fileOs != nil && !os.IsNotExist(fileOs) {
105+
w.filesBeingWatcher[versionFile] = false
106+
continue
107+
}
108+
109+
w.addWatcher(ctx, versionFile)
110+
w.filesBeingWatcher[versionFile] = true
111+
112+
// On startup we need to read the files initially if they are discovered for the first time
113+
w.readVersionFile(ctx, versionFile)
114+
}
115+
}
116+
}
117+
118+
func (w *NginxAppProtectInstanceWatcher) addWatcher(ctx context.Context, versionFile string) {
119+
if err := w.watcher.Add(versionFile); err != nil {
120+
slog.ErrorContext(
121+
ctx,
122+
"Failed to add NGINX App Protect file watcher",
123+
"file", versionFile, "error", err,
124+
)
125+
removeError := w.watcher.Remove(versionFile)
126+
if removeError != nil {
100127
slog.ErrorContext(
101128
ctx,
102-
"Failed to add NGINX App Protect file watcher",
103-
"file", versionFile, "error", err,
129+
"Failed to remove NGINX App Protect file watcher",
130+
"file", versionFile, "error", removeError,
104131
)
105-
removeError := w.watcher.Remove(versionFile)
106-
if removeError != nil {
107-
slog.ErrorContext(
108-
ctx,
109-
"Failed to remove NGINX App Protect file watcher",
110-
"file", versionFile, "error", removeError,
111-
)
112-
}
113132
}
114133
}
134+
}
115135

116-
// Check if files exists on startup
117-
w.version = w.readFile(ctx, versionFilePath)
118-
w.release = w.readFile(ctx, releaseFilePath)
119-
w.attackSignatureVersion = w.readFile(ctx, attackSignatureVersionFilePath)
120-
w.threatCampaignVersion = w.readFile(ctx, threatCampaignVersionFilePath)
121-
w.enforcerEngineVersion = w.readFile(ctx, enforcerEngineVersionFilePath)
136+
func (w *NginxAppProtectInstanceWatcher) readVersionFile(ctx context.Context, versionFile string) {
137+
switch {
138+
case versionFile == versionFilePath:
139+
w.version = w.readFile(ctx, versionFilePath)
140+
case versionFile == releaseFilePath:
141+
w.release = w.readFile(ctx, releaseFilePath)
142+
case versionFile == threatCampaignVersionFilePath:
143+
w.threatCampaignVersion = w.readFile(ctx, threatCampaignVersionFilePath)
144+
case versionFile == enforcerEngineVersionFilePath:
145+
w.enforcerEngineVersion = w.readFile(ctx, enforcerEngineVersionFilePath)
146+
case versionFile == attackSignatureVersionFilePath:
147+
w.attackSignatureVersion = w.readFile(ctx, attackSignatureVersionFilePath)
148+
}
122149
}
123150

124151
func (w *NginxAppProtectInstanceWatcher) handleEvent(ctx context.Context, event fsnotify.Event) {

internal/watcher/instance/nginx-app-protect-instance-watcher_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package instance
77

88
import (
99
"context"
10+
"log/slog"
1011
"os"
1112
"testing"
1213
"time"
@@ -23,6 +24,8 @@ import (
2324
"github.com/stretchr/testify/require"
2425
)
2526

27+
const timeout = 5 * time.Second
28+
2629
func TestNginxAppProtectInstanceWatcher_Watch(t *testing.T) {
2730
ctx, cancel := context.WithCancel(context.Background())
2831
defer cancel()
@@ -88,6 +91,8 @@ func TestNginxAppProtectInstanceWatcher_Watch(t *testing.T) {
8891
},
8992
)
9093

94+
slog.SetLogLoggerLevel(slog.LevelDebug)
95+
9196
go nginxAppProtectInstanceWatcher.Watch(ctx, instancesChannel)
9297

9398
t.Run("Test 1: New instance", func(t *testing.T) {
@@ -101,7 +106,7 @@ func TestNginxAppProtectInstanceWatcher_Watch(t *testing.T) {
101106
proto.Equal(instanceUpdates.InstanceUpdates.NewInstances[0], expectedInstance),
102107
"expected %s, actual %s", expectedInstance, instanceUpdates.InstanceUpdates.NewInstances[0],
103108
)
104-
case <-time.After(5 * time.Second):
109+
case <-time.After(timeout):
105110
t.Fatalf("Timed out waiting for instance updates")
106111
}
107112
})
@@ -122,7 +127,7 @@ func TestNginxAppProtectInstanceWatcher_Watch(t *testing.T) {
122127
proto.Equal(instanceUpdates.InstanceUpdates.UpdatedInstances[0], expectedInstance),
123128
"expected %s, actual %s", expectedInstance, instanceUpdates.InstanceUpdates.UpdatedInstances[0],
124129
)
125-
case <-time.After(5 * time.Second):
130+
case <-time.After(timeout):
126131
t.Fatalf("Timed out waiting for instance updates")
127132
}
128133
})
@@ -139,7 +144,7 @@ func TestNginxAppProtectInstanceWatcher_Watch(t *testing.T) {
139144
proto.Equal(instanceUpdates.InstanceUpdates.DeletedInstances[0], expectedInstance),
140145
"expected %s, actual %s", expectedInstance, instanceUpdates.InstanceUpdates.DeletedInstances[0],
141146
)
142-
case <-time.After(5 * time.Second):
147+
case <-time.After(timeout):
143148
t.Fatalf("Timed out waiting for instance updates")
144149
}
145150
})

0 commit comments

Comments
 (0)