Skip to content

Commit 90bb3d6

Browse files
authored
Fix race condition in bearertokenauth extension, the dobule removal of watcher (open-telemetry#44104)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description ```bash 2025-10-02T02:30:59.851Z error [email protected]/bearertokenauth.go:131 fsnotify: can't remove non-existent watch: /var/run/secrets/kubernetes.io/serviceaccount/token {"resource": {"service.instance.id": "db1e09f0-e9b5-421e-b9f2-539af8965363", "service.name": "otelcol", "service.version": "0.135.0"}, "otelcol.component.id": "bearertokenauth", "otelcol.component.kind": "extension"} github.com/open-telemetry/opentelemetry-collector-contrib/extension/bearertokenauthextension.(*bearerTokenAuth).startWatcher github.com/open-telemetry/opentelemetry-collector-contrib/extension/[email protected]/bearertokenauth.go:131 ``` The error means the code called watcher.Remove(path) on a file that the fsnotify watcher was already no longer watching. In the specific case of the Kubernetes service account token, this happens because Kubernetes rotates the token by deleting and replacing the file (it's an atomic update). <!-- Issue number (e.g. open-telemetry#1234) or full URL to issue, if applicable. --> #### Link to tracking issue Fixes <!--Describe what testing was performed and which tests were added.--> #### Testing ``` make otelcontribcol make docker-otelcontribcol docker tag localhost/otelcontribcol:latest pavolloffay/otelcolcontrib-bearertokenfix:1 docker push pavolloffay/otelcolcontrib-bearertokenfix:1 docker run pavolloffay/otelcolcontrib-bearertokenfix:1 ``` <!--Describe the documentation added.--> #### Documentation <!--Please delete paragraphs that you did not use before submitting.--> --------- Signed-off-by: Pavol Loffay <[email protected]>
1 parent 268e34c commit 90bb3d6

File tree

2 files changed

+45
-16
lines changed

2 files changed

+45
-16
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: bug_fix
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
7+
component: extension/bearertokenauth
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "Remove error messages `fsnotify: can't remove non-existent watch` when watching kubernetes SA tokens."
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [44104]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

extension/bearertokenauthextension/bearertokenauth.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"net/http"
1212
"os"
13+
"path/filepath"
1314
"strings"
1415
"sync/atomic"
1516

@@ -106,7 +107,10 @@ func (b *bearerTokenAuth) Start(ctx context.Context, _ component.Host) error {
106107
// start file watcher
107108
go b.startWatcher(ctx, watcher)
108109

109-
return watcher.Add(b.filename)
110+
// Watch the parent directory instead of the file directly to handle atomic replacements
111+
// This eliminates race conditions with fsnotify when files are atomically replaced
112+
watchDir := filepath.Dir(b.filename)
113+
return watcher.Add(watchDir)
110114
}
111115

112116
func (b *bearerTokenAuth) startWatcher(ctx context.Context, watcher *fsnotify.Watcher) {
@@ -122,22 +126,20 @@ func (b *bearerTokenAuth) startWatcher(ctx context.Context, watcher *fsnotify.Wa
122126
if !ok {
123127
continue
124128
}
125-
// NOTE: k8s configmaps uses symlinks, we need this workaround.
126-
// original configmap file is removed.
127-
// SEE: https://martensson.io/go-fsnotify-and-kubernetes-configmaps/
128-
if event.Op == fsnotify.Remove || event.Op == fsnotify.Chmod {
129-
// remove the watcher since the file is removed
130-
if err := watcher.Remove(event.Name); err != nil {
131-
b.logger.Error(err.Error())
132-
}
133-
// add a new watcher pointing to the new symlink/file
134-
if err := watcher.Add(b.filename); err != nil {
135-
b.logger.Error(err.Error())
136-
}
137-
b.refreshToken()
129+
130+
// Only process events for our target file by filtering events
131+
// Since we're watching the parent directory, we get events for all files in it
132+
if event.Name != b.filename {
133+
continue
138134
}
139-
// also allow normal files to be modified and reloaded.
140-
if event.Op == fsnotify.Write {
135+
136+
// Handle file events for our target file
137+
// Since we're watching the directory, we don't need to manage watch add/remove
138+
// The directory watch persists even when files are atomically replaced
139+
if event.Op&fsnotify.Write == fsnotify.Write ||
140+
event.Op&fsnotify.Create == fsnotify.Create ||
141+
event.Op&fsnotify.Remove == fsnotify.Remove ||
142+
event.Op&fsnotify.Chmod == fsnotify.Chmod {
141143
b.refreshToken()
142144
}
143145
}

0 commit comments

Comments
 (0)