diff --git a/.chloggen/mx-psi_internal-localhostgate.yaml b/.chloggen/mx-psi_internal-localhostgate.yaml new file mode 100755 index 000000000000..08f6e66d0c22 --- /dev/null +++ b/.chloggen/mx-psi_internal-localhostgate.yaml @@ -0,0 +1,29 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: all + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: "Add `component.UseLocalHostAsDefaultHost` feature gate that changes default endpoints from 0.0.0.0 to localhost" + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [30702] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: | + This change affects the following components: + - TBF + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/internal/common/localhostgate/featuregate.go b/internal/common/localhostgate/featuregate.go new file mode 100644 index 000000000000..b0dd2956eae6 --- /dev/null +++ b/internal/common/localhostgate/featuregate.go @@ -0,0 +1,69 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// package localhostgate defines a feature gate that controls whether server-like receivers and extensions use localhost as the default host for their endpoints. +// This package is duplicated across core and contrib to avoid exposing the feature gate as part of the public API. +// To do this we define a `registerOrLoad` helper and try to register the gate in both modules. +// IMPORTANT NOTE: ANY CHANGES TO THIS PACKAGE MUST BE MIRRORED IN THE CORE COUNTERPART. +// See https://github.com/open-telemetry/opentelemetry-collector/blob/main/internal/localhostgate/featuregate.go +package localhostgate // import "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/localhostgate" + +import ( + "errors" + "fmt" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/featuregate" +) + +const UseLocalHostAsDefaultHostID = "component.UseLocalHostAsDefaultHost" + +// useLocalHostAsDefaultHostfeatureGate is the feature gate that controls whether +// server-like receivers and extensions such as the OTLP receiver use localhost as the default host for their endpoints. +var useLocalHostAsDefaultHostfeatureGate = mustRegisterOrLoad( + featuregate.GlobalRegistry(), + UseLocalHostAsDefaultHostID, + featuregate.StageAlpha, + featuregate.WithRegisterDescription("controls whether server-like receivers and extensions such as the OTLP receiver use localhost as the default host for their endpoints"), +) + +// mustRegisterOrLoad tries to register the feature gate and loads it if it already exists. +// It panics on any other error. +func mustRegisterOrLoad(reg *featuregate.Registry, id string, stage featuregate.Stage, opts ...featuregate.RegisterOption) *featuregate.Gate { + gate, err := reg.Register(id, stage, opts...) + + if errors.Is(err, featuregate.ErrAlreadyRegistered) { + // Gate is already registered; find it. + // Only a handful of feature gates are registered, so it's fine to iterate over all of them. + reg.VisitAll(func(g *featuregate.Gate) { + if g.ID() == id { + gate = g + return + } + }) + } else if err != nil { + panic(err) + } + + return gate +} + +// EndpointForPort gets the endpoint for a given port using localhost or 0.0.0.0 depending on the feature gate. +func EndpointForPort(port int) string { + host := "localhost" + if !useLocalHostAsDefaultHostfeatureGate.IsEnabled() { + host = "0.0.0.0" + } + return fmt.Sprintf("%s:%d", host, port) +} + +// LogAboutUseLocalHostAsDefault logs about the upcoming change from 0.0.0.0 to localhost on server-like components. +func LogAboutUseLocalHostAsDefault(logger *zap.Logger) { + if !useLocalHostAsDefaultHostfeatureGate.IsEnabled() { + logger.Warn( + "The default endpoints for all servers in components will change to use localhost instead of 0.0.0.0 in a future version. Use the feature gate to preview the new default.", + zap.String("feature gate ID", UseLocalHostAsDefaultHostID), + ) + } +} diff --git a/internal/common/localhostgate/featuregate_test.go b/internal/common/localhostgate/featuregate_test.go new file mode 100644 index 000000000000..ef09519ac9ca --- /dev/null +++ b/internal/common/localhostgate/featuregate_test.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package localhostgate + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/featuregate" +) + +func setFeatureGateForTest(t testing.TB, gate *featuregate.Gate, enabled bool) func() { + originalValue := gate.IsEnabled() + require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), enabled)) + return func() { + require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), originalValue)) + } +} + +func TestEndpointForPort(t *testing.T) { + tests := []struct { + port int + enabled bool + endpoint string + }{ + { + port: 4317, + enabled: false, + endpoint: "0.0.0.0:4317", + }, + { + port: 4317, + enabled: true, + endpoint: "localhost:4317", + }, + { + port: 0, + enabled: false, + endpoint: "0.0.0.0:0", + }, + { + port: 0, + enabled: true, + endpoint: "localhost:0", + }, + } + + for _, tt := range tests { + t.Run(tt.endpoint, func(t *testing.T) { + defer setFeatureGateForTest(t, useLocalHostAsDefaultHostfeatureGate, tt.enabled)() + assert.Equal(t, EndpointForPort(tt.port), tt.endpoint) + }) + } +}