Skip to content

feat: add authenticated OpenTelemetry gateway - #231

Open
rsampaio wants to merge 6 commits into
mainfrom
rsampaio-otel-gateway
Open

feat: add authenticated OpenTelemetry gateway#231
rsampaio wants to merge 6 commits into
mainfrom
rsampaio-otel-gateway

Conversation

@rsampaio

@rsampaio rsampaio commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

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

  • Keeps the service SAK in a Kubernetes Secret and an opaque Collector config field.
  • Requires HTTPS enrollment, rejects redirects, bounds response bodies, and supports custom CA/TLS settings.
  • Serializes concurrent JWT refreshes and retries a replayable request at most once after a 401.
  • Runs non-root with a read-only filesystem, dropped capabilities, RuntimeDefault seccomp, and no service-account token.
  • Defaults to two replicas with rolling updates, topology spreading, a PDB, probes, resource limits, and ingress NetworkPolicy.
  • Publishes multi-architecture images to GHCR and builds against OpenTelemetry Collector v0.156.0.

Review guide

  1. Agent routing: cmd/fleetint/run.go, internal/config/, and internal/exporter/ establish collector precedence without forwarding node credentials.
  2. Gateway authentication: otelcol/auth/sakauth/ contains enrollment, token refresh, TLS, and request authentication.
  3. Deployment: deployments/helm/fleet-intelligence-agent/templates/otel-gateway-* contains availability and security controls.
  4. Build/release: otelcol/Dockerfile, otelcol/otelcol-builder.yaml, and GitHub workflows build, test, and publish the collector.
  5. Tests: collector_integration_test.go starts the built Collector and verifies real OTLP metrics/logs forwarding, identity attributes, and backend authentication.

Operational notes

  • Agent-to-gateway OTLP is cluster-local HTTP and is restricted by NetworkPolicy; clusters must use a CNI that enforces NetworkPolicy.
  • Production installations should use otelGateway.enroll.existingSecret rather than passing a SAK through Helm values.
  • The sending queue is in-memory; persistent queueing is not introduced by this change.

Test plan

  • Gateway unit tests with race detector (90.8% statement coverage).
  • End-to-end built-Collector test for SAK enrollment and OTLP metrics/logs forwarding.
  • Core config, backend client, endpoint, and agent-state regression tests.
  • Gateway module go vet and full golangci-lint (0 issues).
  • Standalone Collector configuration validation.
  • Helm lint and rendering with gateway disabled and enabled.
  • Full Linux Docker suite and root lint will run in PR CI; local execution is blocked by Docker socket sandboxing and macOS-only build constraints around DCGM/NVML/procfs.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

Summary by CodeRabbit

  • New Features
    • Added an optional OpenTelemetry gateway to route metrics and logs via a dedicated collector.
    • Extended Helm charts with gateway deployment, authentication, service exposure, and optional network controls.
    • Added support for a configurable collector endpoint to drive gateway routing.
    • Added multi-architecture releases for the fleetint-otelcol image, including automated publishing/tagging.
  • Bug Fixes
    • Improved token protection by consistently masking sensitive metadata values.
    • Added stricter validation for collector/enrollment endpoints and enforced safer URL handling.
  • Tests
    • Added unit tests for endpoint/masking behavior and an end-to-end gateway/auth integration test.
  • Chores
    • Updated CI, release, and build tooling to lint/build/test and publish the new collector image.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d9c33c13-a7a6-4a9c-a936-dfbf0422783f

📥 Commits

Reviewing files that changed from the base of the PR and between e9306b6 and d679bcc.

📒 Files selected for processing (1)
  • otelcol/auth/sakauth/collector_integration_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • otelcol/auth/sakauth/collector_integration_test.go

📝 Walkthrough

Walkthrough

Adds an authenticated OpenTelemetry Collector with SAK-to-JWT enrollment, optional Helm deployment and agent routing, integration validation, multi-architecture image publishing, and nested-module build checks.

Changes

OpenTelemetry gateway

Layer / File(s) Summary
SAK authentication extension
otelcol/auth/sakauth/*
Adds secure enrollment configuration, JWT propagation, customer ID headers, token refresh, unauthorized-request retry coordination, and unit tests.
Collector build and integration validation
otelcol/otelcol-builder.yaml, otelcol/Dockerfile, otelcol/auth/sakauth/collector_integration_test.go, .github/workflows/ci.yml
Defines the custom collector build, packages it in a distroless image, and validates metrics/logs authentication across architectures.
Agent collector routing
internal/config/*, internal/exporter/*, cmd/fleetint/*
Adds collector endpoint validation and environment configuration, routes telemetry through collector paths, excludes the endpoint from generated metadata, and masks SAK tokens.
Helm gateway deployment
deployments/helm/fleet-intelligence-agent/values.yaml, deployments/helm/fleet-intelligence-agent/templates/otel-gateway-*, deployments/helm/fleet-intelligence-agent/templates/daemonset.yaml
Adds conditional collector resources, backend pipelines, secret handling, probes, network policy, disruption budget, and agent endpoint wiring.
Build and release wiring
Makefile, .github/workflows/release.yml, .dockerignore, .gitignore
Extends nested-module checks, passes version metadata to container tests, ignores generated artifacts, and publishes the collector image.

Estimated code review effort: 5 (Critical) | ~120 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 bearer token
  Enrollment-->>Gateway: Return JWT assertion
  Gateway->>Backend: Export OTLP with JWT and Nv-Actor-Id
  Backend-->>Gateway: Return response or refreshed JWT
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding an authenticated OpenTelemetry gateway.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rsampaio-otel-gateway

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b7e7093 and 71498ff.

⛔ Files ignored due to path filters (1)
  • otelcol/auth/sakauth/go.sum is excluded by !**/*.sum
📒 Files selected for processing (29)
  • .dockerignore
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .gitignore
  • Makefile
  • cmd/fleetint/gateway_test.go
  • cmd/fleetint/metadata.go
  • cmd/fleetint/run.go
  • deployments/helm/fleet-intelligence-agent/templates/daemonset.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-configmap.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-deployment.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-networkpolicy.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-pdb.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-secret.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-service.yaml
  • deployments/helm/fleet-intelligence-agent/values.yaml
  • deployments/otel-collector/config.yaml
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/exporter/exporter.go
  • internal/exporter/exporter_test.go
  • otelcol/Dockerfile
  • otelcol/auth/sakauth/collector_integration_test.go
  • otelcol/auth/sakauth/config.go
  • otelcol/auth/sakauth/extension.go
  • otelcol/auth/sakauth/extension_test.go
  • otelcol/auth/sakauth/factory.go
  • otelcol/auth/sakauth/go.mod
  • otelcol/otelcol-builder.yaml

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml
Comment on lines +191 to +197
- 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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:


🏁 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 -n

Repository: 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:


🌐 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:


🌐 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:


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

Comment thread cmd/fleetint/run.go Outdated
Comment thread internal/config/config.go
Comment thread otelcol/auth/sakauth/config.go
Comment thread otelcol/auth/sakauth/extension.go
@@ -0,0 +1,30 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why do we need a pdb here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gateway is 2 replicas on the metrics/logs path and the sending queue is in-memory, so if a drain evicts both at once we drop whatever is in flight. minAvailable: 1 keeps one up.

It only renders when the gateway is enabled and there is a config for that, we can default to false if that is less confusing

Comment thread deployments/otel-collector/config.yaml Outdated
Comment on lines +58 to +66
processors:
memory_limiter:
check_interval: 5s
limit_mib: 400
spike_limit_mib: 100

batch:
timeout: 10s
send_batch_size: 1000

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this memory limiter be configured through helm values? What if we change the OTLP pod memory configuration to a large value say 2GB, do we also need to change this memory limiter?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! There were two configs disagreeing. The one you're looking at wasn't read by Helm, so I deleted it, the ConfigMap is the only one now.

It's configurable via otelGateway.memoryLimiter.* and it's a percentage, so it follows the pod limit.

}
e.mu.Lock()
e.jwt = jwt
e.customerID = extractCustomerID(jwt)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the customer id from the backend?

Comment thread .github/workflows/ci.yml
path: fleetint-${{ matrix.goos }}-${{ matrix.goarch }}
retention-days: 7

build_check_otelcol:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otelcol/auth/sakauth is a separate Go module (it has its own go.mod). In .github/workflows/ci.yml, unit_test runs make test → go test ./... from the root module, which does not descend into nested modules. similar to the lint runs golangci-lint at the root and vuln runs govulncheck ./... at the root. build_check_otelcol only runs the single integration test: run ^TestCollectorGatewayEndToEnd$ and only for amd64). so extension_test.go and config tests are never executed in CI. Should we add something like following in ci to include that?

- name: Test sakauth module
  working-directory: otelcol/auth/sakauth
  run: go test -race ./...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I added to the Makefile so the ci and local tests will include the otelcol directory!

endpoint: 0.0.0.0:{{ .Values.otelGateway.service.port }}

processors:
memory_limiter:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from default values.yaml,

memory_limiter:
  limit_percentage: 80
  spike_limit_percentage: 20

is this limiter computed from the total node mem or the pod limit? if it's the former, there could possibly be OOM issue if the pod is capped at a smaller mem e.g. resources.limits.memory: 512Mi while the node itself is large, say 512G

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pod limit memorylimiter reads the cgroup quota, falling back to node memory only when no limit is set. We default to limits.memory: 512Mi, so 80% is ~410Mi.

The fallback is a real risk with resources: {}, so I added a fail guard plus a note in values.yaml.

rsampaio added 5 commits July 28, 2026 17:34
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>
otelcol/auth/sakauth has its own go.mod, so root-module `./...` targets
never descended into it: its unit and config tests, golangci-lint and
govulncheck were all silently skipped.

Add an OTELCOL_MODULE variable and extend the test, lint and vuln targets
to run there too. Keeping this in the Makefile means `make docker-test`
covers the module as well, since it runs those three targets. The CI lint
job invokes golangci-lint directly rather than through make, so it gets a
matching step.

Also set persist-credentials: false on the build_check_otelcol checkout,
which installs and runs the OTel Collector Builder and its dependencies.

Signed-off-by: Rodrigo Sampaio Vaz <rvaz@nvidia.com>
Validate() rejected tls.insecure but not tls.insecure_skip_verify, which
kept the https:// scheme and so passed every other check while disabling
certificate verification.

The jwt_assertion handler also overwrote the stored token unconditionally,
so an out-of-order response could replace a newer token with an older one.
Install the refreshed token only while the stored one is unchanged.

Signed-off-by: Rodrigo Sampaio Vaz <rvaz@nvidia.com>
The FLEETINT_COLLECTOR_ENDPOINT override logged the raw value, which can
carry credentials in userinfo or a token in the path. Validation rejects
those but runs later, so log only the host.

Validation also missed a bare trailing "?" and "#": url.Parse reports the
first as ForceQuery with an empty RawQuery and drops the second entirely,
so both reached the point where "/v1/metrics" is appended.

Signed-off-by: Rodrigo Sampaio Vaz <rvaz@nvidia.com>
The collector memory_limiter is configured by percentage and derives that
percentage from the container's cgroup memory limit, falling back to total
node memory when none is set. On a large node an unlimited pod would size
the limiter against the node and be OOM-killed before it ever engages, so
reject that at render time.

Document the relationship in values.yaml: the percentages track
resources.limits.memory automatically, so raising the pod limit needs no
second edit.

Delete deployments/otel-collector/config.yaml. It was unreferenced by the
Dockerfile, the builder manifest and the tests, and its hardcoded
limit_mib contradicted the ConfigMap the chart actually deploys.

Signed-off-by: Rodrigo Sampaio Vaz <rvaz@nvidia.com>
@rsampaio
rsampaio force-pushed the rsampaio-otel-gateway branch from 0ac718d to 4e2bb61 Compare July 28, 2026 21:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
.github/workflows/release.yml (1)

142-150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider embedding version metadata into the otelcol image.

Unlike the agent image build (Lines 128-141), this step passes no build-args/OCI labels, so the published fleetint-otelcol image carries no embedded version/revision/build-timestamp for traceability. Would need corresponding ARG/LABEL additions in otelcol/Dockerfile.

🤖 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/release.yml around lines 142 - 150, Update the “Build and
push fleetint-otelcol image” step to pass the same version, revision, and
build-timestamp metadata build arguments and OCI labels used by the agent image,
and add the corresponding ARG/LABEL declarations in otelcol/Dockerfile so the
published image embeds that metadata.
otelcol/Dockerfile (1)

9-9: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider pinning base images by digest.

golang:1.26.5 and gcr.io/distroless/static:nonroot are tag-referenced rather than digest-pinned, so the same tag can resolve to different content over time, weakening build reproducibility for a multi-arch, publicly-published image.

Also applies to: 27-27

🤖 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/Dockerfile` at line 9, Update the Dockerfile’s builder and runtime
base image references, including golang:1.26.5 and
gcr.io/distroless/static:nonroot, to use immutable image digests while retaining
their current tags and multi-architecture support.
deployments/helm/fleet-intelligence-agent/templates/otel-gateway-deployment.yaml (1)

85-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded secret key name for existingSecret.

When otelGateway.enroll.existingSecret is set, the key is hardcoded to sak-token. Operators bringing an existing Secret (the recommended production path per the PR's objectives) may already use a different key name, forcing them to either rename/duplicate the secret or fail silently.

♻️ Suggested addition of a configurable key field
             - name: SAK_TOKEN
               valueFrom:
                 secretKeyRef:
                   name: {{ .Values.otelGateway.enroll.existingSecret | default (printf "%s-otel-gateway-sak" (include "fleet-intelligence-agent.fullname" .)) }}
-                  key: sak-token
+                  key: {{ .Values.otelGateway.enroll.existingSecretKey | default "sak-token" }}
🤖 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
`@deployments/helm/fleet-intelligence-agent/templates/otel-gateway-deployment.yaml`
around lines 85 - 88, Make the secret key reference in the otel gateway
deployment configurable for existing secrets instead of always using sak-token.
Add or reuse an otelGateway enrollment values field for the key, use it in the
secretKeyRef under the existingSecret path, and preserve sak-token as the
default when no override is provided.
🤖 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 `@otelcol/auth/sakauth/extension.go`:
- Around line 148-155: Update refreshJWT’s post-performEnrollment write to
compare e.jwt with the previously captured staleJWT while holding e.mu; install
the enrollment JWT and customer ID only when they still match, otherwise return
the current e.jwt without overwriting it. Add a regression test covering an
in-flight enrollment refresh completing after a newer response-header refresh,
asserting the newer token remains active.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 142-150: Update the “Build and push fleetint-otelcol image” step
to pass the same version, revision, and build-timestamp metadata build arguments
and OCI labels used by the agent image, and add the corresponding ARG/LABEL
declarations in otelcol/Dockerfile so the published image embeds that metadata.

In
`@deployments/helm/fleet-intelligence-agent/templates/otel-gateway-deployment.yaml`:
- Around line 85-88: Make the secret key reference in the otel gateway
deployment configurable for existing secrets instead of always using sak-token.
Add or reuse an otelGateway enrollment values field for the key, use it in the
secretKeyRef under the existingSecret path, and preserve sak-token as the
default when no override is provided.

In `@otelcol/Dockerfile`:
- Line 9: Update the Dockerfile’s builder and runtime base image references,
including golang:1.26.5 and gcr.io/distroless/static:nonroot, to use immutable
image digests while retaining their current tags and multi-architecture support.
🪄 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: d4079e01-ec41-48da-bf03-7f384b296552

📥 Commits

Reviewing files that changed from the base of the PR and between 0ac718d and 4e2bb61.

⛔ Files ignored due to path filters (1)
  • otelcol/auth/sakauth/go.sum is excluded by !**/*.sum
📒 Files selected for processing (28)
  • .dockerignore
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .gitignore
  • Makefile
  • cmd/fleetint/gateway_test.go
  • cmd/fleetint/metadata.go
  • cmd/fleetint/run.go
  • deployments/helm/fleet-intelligence-agent/templates/daemonset.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-configmap.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-deployment.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-networkpolicy.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-pdb.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-secret.yaml
  • deployments/helm/fleet-intelligence-agent/templates/otel-gateway-service.yaml
  • deployments/helm/fleet-intelligence-agent/values.yaml
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/exporter/exporter.go
  • internal/exporter/exporter_test.go
  • otelcol/Dockerfile
  • otelcol/auth/sakauth/collector_integration_test.go
  • otelcol/auth/sakauth/config.go
  • otelcol/auth/sakauth/extension.go
  • otelcol/auth/sakauth/extension_test.go
  • otelcol/auth/sakauth/factory.go
  • otelcol/auth/sakauth/go.mod
  • otelcol/otelcol-builder.yaml
🚧 Files skipped from review as they are similar to previous changes (15)
  • .gitignore
  • cmd/fleetint/metadata.go
  • deployments/helm/fleet-intelligence-agent/templates/daemonset.yaml
  • internal/config/config_test.go
  • otelcol/otelcol-builder.yaml
  • otelcol/auth/sakauth/config.go
  • cmd/fleetint/run.go
  • internal/exporter/exporter_test.go
  • internal/config/config.go
  • internal/exporter/exporter.go
  • otelcol/auth/sakauth/factory.go
  • deployments/helm/fleet-intelligence-agent/values.yaml
  • .dockerignore
  • Makefile
  • otelcol/auth/sakauth/extension_test.go

Comment on lines +148 to +155
jwt, err := e.performEnrollment(ctx)
if err != nil {
return "", err
}
e.mu.Lock()
e.jwt = jwt
e.customerID = extractCustomerID(jwt)
e.mu.Unlock()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent a delayed enrollment refresh from replacing a newer JWT.

refreshJWT checks staleJWT before the network call, but unconditionally writes afterward. A concurrent response-header refresh can install a newer token while enrollment is pending, and this path then regresses it. Only install the enrollment result if e.jwt still equals staleJWT; otherwise return the current token. Add an out-of-order refresh/header regression test.

Proposed fix
 	jwt, err := e.performEnrollment(ctx)
 	if err != nil {
 		return "", err
 	}
+	newCustomerID := extractCustomerID(jwt)
 	e.mu.Lock()
-	e.jwt = jwt
-	e.customerID = extractCustomerID(jwt)
+	if e.jwt == staleJWT {
+		e.jwt = jwt
+		e.customerID = newCustomerID
+	} else {
+		jwt = e.jwt
+	}
 	e.mu.Unlock()
 	return jwt, nil
📝 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.

Suggested change
jwt, err := e.performEnrollment(ctx)
if err != nil {
return "", err
}
e.mu.Lock()
e.jwt = jwt
e.customerID = extractCustomerID(jwt)
e.mu.Unlock()
jwt, err := e.performEnrollment(ctx)
if err != nil {
return "", err
}
newCustomerID := extractCustomerID(jwt)
e.mu.Lock()
if e.jwt == staleJWT {
e.jwt = jwt
e.customerID = newCustomerID
} else {
jwt = e.jwt
}
e.mu.Unlock()
return jwt, nil
🤖 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 148 - 155, Update
refreshJWT’s post-performEnrollment write to compare e.jwt with the previously
captured staleJWT while holding e.mu; install the enrollment JWT and customer ID
only when they still match, otherwise return the current e.jwt without
overwriting it. Add a regression test covering an in-flight enrollment refresh
completing after a newer response-header refresh, asserting the newer token
remains active.

Signed-off-by: Rodrigo Sampaio Vaz <rvaz@nvidia.com>
@rsampaio
rsampaio force-pushed the rsampaio-otel-gateway branch from e9306b6 to d679bcc Compare July 28, 2026 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants