Skip to content

feat: TLS and env-based endpoint for jumpstarter-telemetry - #975

Open
bkhizgiy wants to merge 2 commits into
jumpstarter-dev:mainfrom
bkhizgiy:tls_logs
Open

feat: TLS and env-based endpoint for jumpstarter-telemetry#975
bkhizgiy wants to merge 2 commits into
jumpstarter-dev:mainfrom
bkhizgiy:tls_logs

Conversation

@bkhizgiy

@bkhizgiy bkhizgiy commented Aug 5, 2026

Copy link
Copy Markdown
Member

Follow-up to #930

jumpstarter-telemetry now always uses TLS.
Set EXTERNAL_CERT_PEM and EXTERNAL_KEY_PEM to mount operator-provided certs,
falls back to a self-signed cert when absent.
The controller now reads GRPC_TELEMETRY_ENDPOINT from the
environment instead of auto-deriving jumpstarter-telemetry.<ns>:9093
from the ConfigMap loader.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

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
📝 Walkthrough

Walkthrough

The telemetry service now resolves an explicit or environment-derived endpoint. It always starts with TLS. It uses external PEM files or a self-signed certificate with endpoint-derived SANs. Startup, shutdown, and error handling were updated. Tests cover endpoint and TLS behavior.

Changes

Telemetry service

Layer / File(s) Summary
Telemetry endpoint discovery
controller/internal/config/config.go, controller/internal/config/types.go, controller/internal/service/controller_service.go, controller/internal/service/endpoints.go, controller/internal/service/telemetry_service_test.go
Configuration preserves an explicit endpoint and otherwise uses GRPC_TELEMETRY_ENDPOINT. Enabled telemetry returns an internal error when no endpoint resolves.
Shared TLS credential loading
controller/internal/service/tls_credentials.go, controller/internal/service/telemetry_service_test.go
Shared helpers load external PEM files or generate self-signed certificates. gRPC credentials require TLS 1.2 or newer. Tests cover partial, missing, and invalid certificate configuration.
Service TLS startup and lifecycle
controller/internal/service/controller_service.go, controller/internal/service/router_service.go, controller/internal/service/telemetry_service.go, controller/internal/service/telemetry_service_test.go
Controller, router, and telemetry services use shared TLS helpers. Telemetry derives SANs from its endpoint, starts with TLS credentials, closes its listener, and stops on serve errors.
Process startup lifecycle
controller/cmd/telemetry/main.go
Signal handlers are registered before service startup. Documentation describes TLS and endpoint requirements.
TLS startup validation
controller/internal/service/telemetry_service_test.go
Tests validate generated certificate PEM, endpoint and localhost SANs, external certificate loading, and startup errors caused by unavailable TLS files.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TelemetryService
  participant Environment
  participant TLSCredentials
  participant GRPCServer
  TelemetryService->>Environment: Resolve GRPC_TELEMETRY_ENDPOINT
  TelemetryService->>TLSCredentials: Load external or self-signed credentials
  TLSCredentials-->>TelemetryService: Return TLS credentials and certificate PEM
  TelemetryService->>GRPCServer: Start with TLS credentials
  GRPCServer-->>TelemetryService: Return serve error
  TelemetryService->>GRPCServer: Stop server
Loading

Possibly related PRs

Suggested reviewers: bennyz

Poem

A rabbit checks the endpoint bright,
Then wraps telemetry in TLS tonight.
PEM keys guard the service door,
SANs mark hosts from shore to shore.
Signals wait before startup runs—
Secure hops for everyone.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% 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
Title check ✅ Passed The title clearly summarizes the two main changes: mandatory TLS and environment-based telemetry endpoint configuration.
Description check ✅ Passed The description directly explains the mandatory TLS behavior, certificate fallback, and environment-based telemetry endpoint configuration.
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 unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@bkhizgiy

bkhizgiy commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@controller/internal/config/types.go`:
- Around line 38-43: Update the Telemetry.Certificate documentation comment to
remove the claim that an empty value enables system CA verification for
self-signed certificates. State that self-signed mode requires an explicit
unverified-client policy, or that the operator must provide a stable CA
certificate and configure it for verification.

In `@controller/internal/service/controller_service.go`:
- Around line 330-334: The controller and telemetry server resolve different
endpoint sources, causing advertised endpoints to disagree with certificate
SANs. In controller/internal/service/controller_service.go:330-334,
controller/internal/service/telemetry_service.go:226-240, and
controller/cmd/telemetry/main.go:24-25, introduce and use one shared endpoint
configuration for both processes, derive self-signed SANs from that resolved
value, and advertise only that value; add an integration test verifying a TLS
client validates the certificate for the endpoint returned by
GetServiceEndpoints.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a1796af-2663-4c0b-9759-2c325f7c694a

📥 Commits

Reviewing files that changed from the base of the PR and between c18ac85 and 201e180.

📒 Files selected for processing (7)
  • controller/cmd/telemetry/main.go
  • controller/internal/config/config.go
  • controller/internal/config/types.go
  • controller/internal/service/controller_service.go
  • controller/internal/service/endpoints.go
  • controller/internal/service/telemetry_service.go
  • controller/internal/service/telemetry_service_test.go
💤 Files with no reviewable changes (1)
  • controller/internal/config/config.go

Comment thread controller/internal/config/types.go Outdated
Comment thread controller/internal/service/controller_service.go Outdated
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>
Assisted-by: Claude Sonnet 4.5 <claude@anthropic.com>
@bkhizgiy
bkhizgiy marked this pull request as ready for review August 5, 2026 13:42
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@bkhizgiy
bkhizgiy requested review from bennyz and raballew August 6, 2026 07:31
Comment thread controller/internal/service/controller_service.go
Comment thread controller/internal/service/telemetry_service.go
Comment thread controller/internal/service/telemetry_service.go Outdated
Comment thread controller/internal/service/telemetry_service.go
Comment thread controller/internal/service/telemetry_service_test.go Outdated
Comment thread controller/internal/config/config.go
Comment thread controller/internal/service/endpoints.go
Comment thread controller/internal/service/telemetry_service.go Outdated
Comment thread controller/internal/service/telemetry_service.go
Comment thread controller/cmd/telemetry/main.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@controller/internal/service/tls_credentials.go`:
- Around line 40-57: Update the external TLS configuration branch in the
certificate-loading function to detect when exactly one of EXTERNAL_CERT_PEM or
EXTERNAL_KEY_PEM is set and return an error instead of falling back to
self-signed certificate generation. Continue loading and parsing the external
certificate only when both paths are configured, and preserve the existing
behavior when neither is set.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 396acf93-3707-43b2-8d21-0fec2dbfb4f2

📥 Commits

Reviewing files that changed from the base of the PR and between 4816c0d and bb04012.

📒 Files selected for processing (6)
  • controller/internal/config/config.go
  • controller/internal/service/controller_service.go
  • controller/internal/service/router_service.go
  • controller/internal/service/telemetry_service.go
  • controller/internal/service/telemetry_service_test.go
  • controller/internal/service/tls_credentials.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • controller/internal/service/telemetry_service.go
  • controller/internal/service/telemetry_service_test.go

Comment thread controller/internal/service/tls_credentials.go
Assisted-by: Claude <claude@anthropic.com>
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>
Comment on lines +332 to +334
if s.TelemetryConfig.Endpoint == "" {
return nil, status.Error(codes.Internal, "telemetry is enabled but no endpoint is configured; set telemetry.endpoint in the ConfigMap or GRPC_TELEMETRY_ENDPOINT on the controller pod")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The empty-endpoint guard at these lines is only reachable after authenticateExporter succeeds. All existing tests that call svc.GetServiceEndpoints use a failing authenticator.

Comment on lines +280 to +349
func TestTelemetryEndpointResolution(t *testing.T) {
// This tests the resolution logic used by LoadConfiguration:
// cmp.Or(configMapValue, os.Getenv("GRPC_TELEMETRY_ENDPOINT"))
// followed by net.SplitHostPort validation.
tests := []struct {
name string
configValue string
envValue string
wantResult string
wantErr bool
}{
{
name: "ConfigMap value takes precedence",
configValue: "telemetry.ns.svc:9093",
envValue: "env-telemetry:9093",
wantResult: "telemetry.ns.svc:9093",
},
{
name: "env var fallback when ConfigMap is empty",
envValue: "env-telemetry.svc:9093",
wantResult: "env-telemetry.svc:9093",
},
{
name: "both empty yields empty (no error)",
wantResult: "",
},
{
name: "malformed ConfigMap value is rejected",
configValue: "no-port",
wantErr: true,
},
{
name: "malformed env var is rejected",
envValue: "garbage-no-port",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("GRPC_TELEMETRY_ENDPOINT", tt.envValue)

// Simulate the resolution logic from LoadConfiguration.
resolved := tt.configValue
if resolved == "" {
resolved = tt.envValue
}

var err error
if resolved != "" {
if _, _, splitErr := net.SplitHostPort(resolved); splitErr != nil {
err = splitErr
}
}

if tt.wantErr {
if err == nil {
t.Fatalf("expected validation error for resolved=%q, got nil", resolved)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resolved != tt.wantResult {
t.Errorf("resolved = %q, want %q", resolved, tt.wantResult)
}
})
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

TestTelemetryEndpointResolution simulates the LoadConfiguration logic instead of calling it.

Comment on lines +332 to +334
if s.TelemetryConfig.Endpoint == "" {
return nil, status.Error(codes.Internal, "telemetry is enabled but no endpoint is configured; set telemetry.endpoint in the ConfigMap or GRPC_TELEMETRY_ENDPOINT on the controller pod")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A missing endpoint is a known operator configuration gap, not an unexpected server failure. codes.Internal communicates unexpected server-side failure, whereas codes.FailedPrecondition better matches this situation per the gRPC status code semantics.

Comment on lines 39 to 41
func endpointToSAN(endpoint string) ([]string, []net.IP, error) {
host, _, err := net.SplitHostPort(endpoint)
if err != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

endpointToSAN generates a broken TLS certificate when given a port-only endpoint like :9093.

Comment on lines +32 to +33
ctrl.Log.WithName("telemetry").Info("GRPC_TELEMETRY_ENDPOINT is not a valid host:port; ignoring",
"value", ep, "error", err)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldnt this be error level?

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.

2 participants