feat: add authenticated OpenTelemetry gateway#231
Conversation
Add an optional custom OpenTelemetry Collector that centralizes SAK-to-JWT authentication and forwards agent metrics and logs while preserving direct enrollment for inventory and attestation. Harden the gateway for production with current Collector dependencies, secure token and TLS handling, highly available Helm resources, GHCR publishing, and end-to-end OTLP integration coverage. Signed-off-by: Rodrigo Sampaio Vaz <rvaz@nvidia.com>
📝 WalkthroughWalkthroughAdds an optional OpenTelemetry gateway with SAK-to-JWT authentication, collector routing from the agent, Helm deployment resources, a custom multi-architecture collector image, CI integration coverage, and release publishing. ChangesOpenTelemetry authentication and collector
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Agent as fleet-intelligence-agent
participant Gateway as fleetint-otelcol
participant Enrollment as enrollment endpoint
participant Backend as OTLP backend
Agent->>Gateway: Send OTLP metrics and logs
Gateway->>Enrollment: Enroll with SAK
Enrollment-->>Gateway: Return JWT assertion
Gateway->>Backend: Export metrics and logs with JWT
Backend-->>Gateway: Return response or refreshed JWT
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci.yml:
- Around line 188-189: Update the actions/checkout@v4 step in the build job to
set persist-credentials to false, ensuring the checkout action does not store
the GITHUB_TOKEN in git configuration before third-party tooling runs.
- Around line 191-197: Update the “Set up Go” workflow step to use
actions/setup-go@v6 instead of v4, keeping its existing go-version configuration
unchanged.
In `@cmd/fleetint/run.go`:
- Around line 225-227: Update the FLEETINT_COLLECTOR_ENDPOINT handling in the
environment override block so Infow does not log the raw endpoint value. Log
only that the collector endpoint override was enabled, or pass a safely redacted
endpoint while preserving assignment of the full value to he.CollectorEndpoint.
In `@internal/config/config.go`:
- Around line 181-190: Update the collector_endpoint validation around
parsed.ForceQuery and the raw endpoint string to reject bare trailing ? and #
characters, while preserving the existing credential, query, fragment, and
HTTP(S) checks. Add regression tests covering http://collector.example? and
http://collector.example#.
In `@otelcol/auth/sakauth/config.go`:
- Around line 57-59: Update the SAK enrollment TLS validation in the
configuration validation method containing the c.TLS.Insecure check to also
reject c.TLS.InsecureSkipVerify. Return the same validation error for either
insecure TLS setting, ensuring enrollment requires HTTPS certificate
verification.
In `@otelcol/auth/sakauth/extension.go`:
- Around line 246-252: Guard the response-token update in the request flow
around the jwt_assertion handling so it only applies when the shared rt.ext.jwt
still equals the request’s usedJWT; otherwise leave the newer token and
customerID unchanged. Add a regression test covering concurrent or out-of-order
responses, including a 401 refresh, to verify stale responses cannot overwrite
the newest JWT.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 9c891f07-c5ab-4125-b79f-f13e6e4bb1f6
⛔ Files ignored due to path filters (1)
otelcol/auth/sakauth/go.sumis excluded by!**/*.sum
📒 Files selected for processing (29)
.dockerignore.github/workflows/ci.yml.github/workflows/release.yml.gitignoreMakefilecmd/fleetint/gateway_test.gocmd/fleetint/metadata.gocmd/fleetint/run.godeployments/helm/fleet-intelligence-agent/templates/daemonset.yamldeployments/helm/fleet-intelligence-agent/templates/otel-gateway-configmap.yamldeployments/helm/fleet-intelligence-agent/templates/otel-gateway-deployment.yamldeployments/helm/fleet-intelligence-agent/templates/otel-gateway-networkpolicy.yamldeployments/helm/fleet-intelligence-agent/templates/otel-gateway-pdb.yamldeployments/helm/fleet-intelligence-agent/templates/otel-gateway-secret.yamldeployments/helm/fleet-intelligence-agent/templates/otel-gateway-service.yamldeployments/helm/fleet-intelligence-agent/values.yamldeployments/otel-collector/config.yamlinternal/config/config.gointernal/config/config_test.gointernal/exporter/exporter.gointernal/exporter/exporter_test.gootelcol/Dockerfileotelcol/auth/sakauth/collector_integration_test.gootelcol/auth/sakauth/config.gootelcol/auth/sakauth/extension.gootelcol/auth/sakauth/extension_test.gootelcol/auth/sakauth/factory.gootelcol/auth/sakauth/go.modotelcol/otelcol-builder.yaml
| - name: Checkout code | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Disable credential persistence on checkout for this build job.
This job installs and runs third-party build tooling (go install .../builder@v0.156.0, then compiles further transitive dependencies via ocb). Without persist-credentials: false, the GITHUB_TOKEN is persisted in git config and readable by any code executed later in the job, widening the blast radius if a fetched dependency is compromised.
🔒 Proposed fix
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false |
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 188-189: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml around lines 188 - 189, Update the
actions/checkout@v4 step in the build job to set persist-credentials to false,
ensuring the checkout action does not store the GITHUB_TOKEN in git
configuration before third-party tooling runs.
Source: Linters/SAST tools
| - name: Set up Go | ||
| uses: actions/setup-go@v4 | ||
| with: | ||
| go-version: ${{ env.GO_VERSION }} | ||
|
|
||
| - name: Install OTel Collector Builder | ||
| run: go install go.opentelemetry.io/collector/cmd/builder@v0.156.0 |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the workflow trigger and relevant job section.
file=".github/workflows/ci.yml"
echo "== File lines around trigger =="
nl -ba "$file" | sed -n '1,120p'
echo
echo "== File lines around setup-go step =="
nl -ba "$file" | sed -n '170,220p'Repository: NVIDIA/fleet-intelligence-agent
Length of output: 241
🌐 Web query:
actions/setup-go v6 cache default enabled cache behavior workflow cache poisoning GitHub Actions
💡 Result:
The actions/setup-go action (including v6) has caching enabled by default [1][2]. The action supports caching for Go modules and build outputs to improve performance, using the cache input which defaults to true [1][3]. Regarding cache poisoning, this is a known security concern in GitHub Actions environments [4][5]. Because actions/setup-go historically enables read-write caching by default, untrusted workflows—such as those triggered by pull requests from forks or external contributors—can potentially write malicious artifacts to the shared cache [4][6]. These tainted artifacts can then be restored by subsequent, trusted workflow runs on the main branch, leading to a supply chain vulnerability [4][5]. To mitigate this risk, several strategies are employed: 1. Manual Disabling: In sensitive workflows, such as release or deployment pipelines, it is standard practice to explicitly set cache: false to prevent the action from writing to the cache [5][6]. 2. Read-Only Cache Support: Recent updates to actions/setup-go include the addition of a cache-write input [4]. By setting cache-write: false, users can allow the action to restore existing caches while skipping the post-step cache save, effectively implementing a read-only cache mode [4]. This allows workflows (like PR checks) to benefit from existing dependencies without risking the integrity of the cache for future runs [4]. When designing workflows, it is recommended to evaluate whether full read-write caching is necessary or if a read-only configuration provides sufficient protection against cache poisoning [4][5].
Citations:
- 1: https://github.com/actions/setup-go?tab=readme-ov-file
- 2: https://github.com/actions/setup-go/tree/v6.3.0
- 3: https://github.com/actions/setup-go/tree/v6.0.0
- 4: Add cache-write input for read-only cache mode actions/setup-go#732
- 5: Disable Go module caching in release workflow to prevent cache poisoning github/gh-aw#5872
- 6: fix: disable Go module cache in agentic setup actions to prevent cache poisoning github/gh-aw#19865
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file=".github/workflows/ci.yml"
echo "== Trigger and top-level workflow lines =="
sed -n '1,90p' "$file" | cat -n
echo
echo "== Step around actions/setup-go =="
sed -n '180,205p' "$file" | cat -nRepository: NVIDIA/fleet-intelligence-agent
Length of output: 4316
🌐 Web query:
actions/setup-go v4 deprecated Node runtime current major v6 release notes
💡 Result:
The actions/setup-go action has progressed through several major versions, primarily driven by the deprecation and end-of-life cycles of Node.js runtimes on GitHub Actions runners [1][2][3]. Key points regarding the transition and v6 release: 1. Node.js Runtime Updates: Older versions of the action used outdated Node.js runtimes. Specifically, v5 upgraded the runtime from Node 16 to Node 20 [2]. The current major release, v6, further upgrades the Node.js runtime from Node 20 to Node 24 [3]. 2. Deprecation Context: The deprecation of Node 20 on GitHub Actions runners was announced in September 2025, as Node 20 reached its end-of-life (EOL) in April 2026 [1]. Users are encouraged to update to actions that support newer Node.js runtimes, such as v6 of setup-go [4][3]. 3. v6 Release Notes: The v6.0.0 release (published September 4, 2025) includes the upgrade to Node 24 and improvements to toolchain handling [3]. Because of the runtime change, users must ensure their runners are on version v2.327.1 or later to maintain compatibility [4][3]. Additional enhancements in the v6.x series have included updated download sources for Go distributions and support for.tool-versions files [5]. In summary, the transition from v4 to v6 involves significant updates to the underlying Node.js runtime (from Node 16 in v4/early versions to Node 24 in v6) to maintain compliance with GitHub Actions runner lifecycle requirements [2][3].
Citations:
- 1: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
- 2: https://github.com/actions/setup-go/releases/tag/v5.0.0
- 3: https://github.com/actions/setup-go/releases/tag/v6.0.0
- 4: https://github.com/actions/setup-go?tab=readme-ov-file
- 5: https://github.com/actions/setup-go/releases/tag/v6.1.0
🌐 Web query:
GitHub Actions pull_request fork cache save actions/setup-go default cache poisoning trusted push restore
💡 Result:
When using GitHub Actions, specifically with pull_request events from forks or pull_request_target workflows, understanding cache access and security risks is critical. Cache Access Limitations By default, GitHub Actions enforces cache isolation [1][2]. A workflow run triggered by a pull request from a fork has restricted access to the cache: - It can restore existing cache entries available to the base branch or default branch [1]. - It is generally restricted from creating or overwriting cache entries to prevent unauthorized cache modifications [3][1]. When a cache write is denied in these scenarios, the actions/cache step reports a warning but does not fail the job [3][4]. Security Risks: Cache Poisoning "Cache poisoning" is a security concern where an attacker attempts to inject malicious data into the cache [5][6][7]. - Risk with pull_request_target: This trigger runs in the context of the base repository and has access to secrets and write permissions [8][4][6]. If a workflow using pull_request_target is misconfigured to check out and execute code from an untrusted fork (the "Pwn Request" pattern), that code runs with the privileges of the base repository [5][6][7]. - Mechanism: Even if you restrict the job's GITHUB_TOKEN permissions (e.g., contents: read), the cache save step uses an internal, runner-specific token to authenticate with the cache service [5][6][7]. Consequently, if malicious code runs during the workflow, it can write arbitrary data to the cache under a deterministic key that a future, trusted workflow might restore [6][7]. This can lead to downstream supply-chain compromises [5][7]. Best Practices for actions/setup-go and Caching The actions/setup-go action uses actions/cache internally [9][10]. To maintain security: - Avoid Unsafe Saves: Do not allow cache writes in workflows that execute untrusted fork code (such as those using pull_request_target that check out the PR's head ref) [5][11]. - Use Restore-Only Logic: In untrusted contexts, configure workflows to be "restore-only" [5]. You can implement custom logic to detect if a PR is from a fork and modify your cache keys to prevent collision with trusted branches [12]. - Rely on Trusted Triggers: Only perform cache saves in workflows triggered by trusted events, such as push to your default branch [8][4]. For further implementation guidance, refer to the documentation on securing pull_request_target workflows and the specific behavior of the actions/cache action [8][3][4].
Citations:
- 1: Cache not caching on pull_request actions/cache#633
- 2: https://github.com/orgs/community/discussions/194493
- 3: https://github.com/actions/cache/?tab=readme-ov-file
- 4: https://docs.github.com/en/enterprise-cloud@latest/actions/reference/security/securely-using-pull_request_target
- 5: https://saschb2b.com/blog/pull-request-target-trap
- 6: https://www.trackr.live/2026/06/27/github-actions-cache-poisoning-fork-trust-boundary/
- 7: https://www.endorlabs.com/learn/how-a-misconfigured-ci-workflow-became-an-npm-supply-chain-compromise
- 8: https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target
- 9: https://github.com/actions/setup-go/blob/main/README.md
- 10: https://github.com/actions/setup-go
- 11: Cache/save should not run automatically from a pull_request_trigger actions/cache#1756
- 12: https://github.com/ag-ui-protocol/ag-ui/blob/d53c4ef6/.github/workflows/unit-go-sdk.yml
🌐 Web query:
GitHub Actions cache forked pull requests read-only save disabled official docs
💡 Result:
GitHub Actions restricts cache access for workflows triggered by pull requests from forks to ensure security and prevent cache poisoning [1][2]. In these scenarios, workflows have read-only access to the cache, meaning they can restore existing cache entries but cannot save or create new ones [1][3]. When a workflow attempts to save a cache while in a read-only state: - The job does not fail [1][3]. - The actions/cache action reports a warning (e.g., "cache write denied") in the workflow logs [1][3]. - The step completes successfully without writing any data to the cache [1][3]. This behavior is a security measure applied to untrusted triggers, including pull requests from forks, to prevent unauthorized modification of the shared cache [4][2]. For workflows that require populating the cache, GitHub recommends using trusted triggers, such as events on the base repository's default branch (e.g., push events) [2]. As of June 2026, GitHub has further reinforced read-only cache protections for workflow events that can be triggered without explicit write permissions [4].
Citations:
- 1: https://github.com/actions/cache/?tab=readme-ov-file
- 2: https://docs.github.com/en/enterprise-server@3.19/actions/reference/security/securely-using-pull_request_target
- 3: https://github.com/actions/cache/blob/main/README.md
- 4: https://github.blog/changelog/2026-06-26-read-only-actions-cache-for-untrusted-triggers/
Update actions/setup-go to v6. actions/setup-go@v4 is behind the current major and should be bumped to avoid Node-runtime deprecation issues.
🧰 Tools
🪛 actionlint (1.7.12)
[error] 192-192: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 zizmor (1.26.1)
[error] 192-192: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml around lines 191 - 197, Update the “Set up Go”
workflow step to use actions/setup-go@v6 instead of v4, keeping its existing
go-version configuration unchanged.
Source: Linters/SAST tools
| if val := os.Getenv("FLEETINT_COLLECTOR_ENDPOINT"); val != "" { | ||
| he.CollectorEndpoint = val | ||
| log.Logger.Infow("set OTel gateway collector endpoint from env", "collector_endpoint", val) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not log the raw collector endpoint.
FLEETINT_COLLECTOR_ENDPOINT is runtime-controlled, but the full value is written to logs. A misconfigured value containing userinfo, a token in the path, or other sensitive material would leak into application logs. Log only that the override was enabled, or emit a safely redacted value.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/fleetint/run.go` around lines 225 - 227, Update the
FLEETINT_COLLECTOR_ENDPOINT handling in the environment override block so Infow
does not log the raw endpoint value. Log only that the collector endpoint
override was enabled, or pass a safely redacted endpoint while preserving
assignment of the full value to he.CollectorEndpoint.
| if endpoint := config.HealthExporter.CollectorEndpoint; endpoint != "" { | ||
| parsed, err := url.Parse(endpoint) | ||
| if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Host == "" { | ||
| return fmt.Errorf("collector_endpoint must be an absolute HTTP(S) URL") | ||
| } | ||
| if parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" { | ||
| return fmt.Errorf("collector_endpoint must not contain credentials, a query, or a fragment") | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/sh
go doc net/url.URL.ForceQueryRepository: NVIDIA/fleet-intelligence-agent
Length of output: 466
🏁 Script executed:
#!/bin/sh
set -eu
printf '\n## internal/config/config.go (around the cited lines)\n'
sed -n '150,230p' internal/config/config.go
printf '\n## Search for refreshConfigFromMetadata and collector_endpoint tests\n'
rg -n "refreshConfigFromMetadata|collector_endpoint|ForceQuery|RawQuery|Fragment" internal -g '!**/vendor/**'Repository: NVIDIA/fleet-intelligence-agent
Length of output: 5035
🏁 Script executed:
#!/bin/sh
set -eu
printf '\n## internal/exporter/exporter.go (refreshConfigFromMetadata)\n'
sed -n '260,360p' internal/exporter/exporter.go
printf '\n## internal/endpoint/endpoint.go (related URL validation)\n'
sed -n '1,220p' internal/endpoint/endpoint.go
printf '\n## internal/config/config_test.go around collector_endpoint cases\n'
sed -n '280,360p' internal/config/config_test.goRepository: NVIDIA/fleet-intelligence-agent
Length of output: 12909
🏁 Script executed:
#!/bin/sh
set -eu
cat <<'GO' >/tmp/check_url.go
package main
import (
"fmt"
"net/url"
)
func main() {
for _, raw := range []string{
"http://collector.example?",
"http://collector.example#",
"http://collector.example",
} {
u, err := url.Parse(raw)
fmt.Printf("RAW=%q err=%v\n", raw, err)
if err == nil {
fmt.Printf(" Scheme=%q Host=%q Path=%q RawQuery=%q Fragment=%q ForceQuery=%v\n",
u.Scheme, u.Host, u.Path, u.RawQuery, u.Fragment, u.ForceQuery)
fmt.Printf(" String=%q\n", u.String())
}
}
}
GO
go run /tmp/check_url.goRepository: NVIDIA/fleet-intelligence-agent
Length of output: 672
Reject bare ? and # in collector_endpoint. url.Parse("http://collector.example?") sets ForceQuery, and a trailing # is dropped by Parse, so both bypass the current checks and later get appended to /v1/... incorrectly. Check parsed.ForceQuery and the raw input for a trailing #; add regression tests for http://collector.example? and http://collector.example#.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/config/config.go` around lines 181 - 190, Update the
collector_endpoint validation around parsed.ForceQuery and the raw endpoint
string to reject bare trailing ? and # characters, while preserving the existing
credential, query, fragment, and HTTP(S) checks. Add regression tests covering
http://collector.example? and http://collector.example#.
| if c.TLS.Insecure { | ||
| return fmt.Errorf("sakauth: tls.insecure is not allowed; enroll_endpoint must use HTTPS") | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
(cd otelcol/auth/sakauth && go doc go.opentelemetry.io/collector/config/configtls.ClientConfig) |
grep -F 'InsecureSkipVerify'Repository: NVIDIA/fleet-intelligence-agent
Length of output: 313
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '\n## config.go excerpt\n'
sed -n '1,140p' otelcol/auth/sakauth/config.go
printf '\n## InsecureSkipVerify references\n'
rg -n "InsecureSkipVerify|insecure_skip_verify" .Repository: NVIDIA/fleet-intelligence-agent
Length of output: 2764
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
sed -n '110,190p' otelcol/auth/sakauth/collector_integration_test.goRepository: NVIDIA/fleet-intelligence-agent
Length of output: 2308
Reject tls.insecure_skip_verify for SAK enrollment.
tls.insecure is blocked, but HTTPS with certificate verification disabled still lets a MITM capture the SAK and enrollment JWT. Reject tls.insecure_skip_verify here too.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@otelcol/auth/sakauth/config.go` around lines 57 - 59, Update the SAK
enrollment TLS validation in the configuration validation method containing the
c.TLS.Insecure check to also reject c.TLS.InsecureSkipVerify. Return the same
validation error for either insecure TLS setting, ensuring enrollment requires
HTTPS certificate verification.
| // The backend proactively sends a refreshed JWT in the response header when | ||
| // the current token is near expiry (>80% of max age). Store it immediately. | ||
| if newJWT := resp.Header.Get("jwt_assertion"); newJWT != "" { | ||
| rt.ext.mu.Lock() | ||
| rt.ext.jwt = newJWT | ||
| rt.ext.customerID = extractCustomerID(newJWT) | ||
| rt.ext.mu.Unlock() |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Prevent stale responses from overwriting a newer JWT.
Concurrent requests can complete out of order. A delayed response using usedJWT can currently replace a newer token installed by another response or 401 refresh, causing subsequent exports to regress to stale credentials.
Only apply the response token while the shared JWT still equals usedJWT, and add an out-of-order response regression test.
Proposed fix
if newJWT := resp.Header.Get("jwt_assertion"); newJWT != "" {
+ newCustomerID := extractCustomerID(newJWT)
rt.ext.mu.Lock()
- rt.ext.jwt = newJWT
- rt.ext.customerID = extractCustomerID(newJWT)
+ if rt.ext.jwt == usedJWT {
+ rt.ext.jwt = newJWT
+ rt.ext.customerID = newCustomerID
+ }
rt.ext.mu.Unlock()
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // The backend proactively sends a refreshed JWT in the response header when | |
| // the current token is near expiry (>80% of max age). Store it immediately. | |
| if newJWT := resp.Header.Get("jwt_assertion"); newJWT != "" { | |
| rt.ext.mu.Lock() | |
| rt.ext.jwt = newJWT | |
| rt.ext.customerID = extractCustomerID(newJWT) | |
| rt.ext.mu.Unlock() | |
| // The backend proactively sends a refreshed JWT in the response header when | |
| // the current token is near expiry (>80% of max age). Store it immediately. | |
| if newJWT := resp.Header.Get("jwt_assertion"); newJWT != "" { | |
| newCustomerID := extractCustomerID(newJWT) | |
| rt.ext.mu.Lock() | |
| if rt.ext.jwt == usedJWT { | |
| rt.ext.jwt = newJWT | |
| rt.ext.customerID = newCustomerID | |
| } | |
| rt.ext.mu.Unlock() |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@otelcol/auth/sakauth/extension.go` around lines 246 - 252, Guard the
response-token update in the request flow around the jwt_assertion handling so
it only applies when the shared rt.ext.jwt still equals the request’s usedJWT;
otherwise leave the newer token and customerID unchanged. Add a regression test
covering concurrent or out-of-order responses, including a 401 refresh, to
verify stale responses cannot overwrite the newest JWT.
Description
Adds an optional, production-hardened OpenTelemetry gateway deployment for Fleet Intelligence. Agents route OTLP metrics and logs through a per-cluster custom Collector, which owns backend SAK-to-JWT authentication; direct agent enrollment remains unchanged for inventory and attestation.
The gateway is disabled by default and does not change bare-metal or existing Kubernetes behavior unless
otelGateway.enabled=true.Security and reliability
Review guide
cmd/fleetint/run.go,internal/config/, andinternal/exporter/establish collector precedence without forwarding node credentials.otelcol/auth/sakauth/contains enrollment, token refresh, TLS, and request authentication.deployments/helm/fleet-intelligence-agent/templates/otel-gateway-*contains availability and security controls.otelcol/Dockerfile,otelcol/otelcol-builder.yaml, and GitHub workflows build, test, and publish the collector.collector_integration_test.gostarts the built Collector and verifies real OTLP metrics/logs forwarding, identity attributes, and backend authentication.Operational notes
otelGateway.enroll.existingSecretrather than passing a SAK through Helm values.Test plan
90.8%statement coverage).go vetand fullgolangci-lint(0 issues).Checklist
Summary by CodeRabbit
New Features
Bug Fixes