diff --git a/.chloggen/insecure_endpoints.yaml b/.chloggen/insecure_endpoints.yaml
new file mode 100644
index 0000000000..127e63dbd7
--- /dev/null
+++ b/.chloggen/insecure_endpoints.yaml
@@ -0,0 +1,16 @@
+# 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. collector, target allocator, auto-instrumentation, opamp, github action)
+component: opampbridge
+
+# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: "Add TLS configuration support to the OpAMP Bridge, including options to disable TLS or skip certificate verification."
+
+# One or more tracking issues related to the change
+issues: [4921]
+
+# (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:
diff --git a/apis/v1alpha1/opampbridge_types.go b/apis/v1alpha1/opampbridge_types.go
index 54d689242c..4655193d60 100644
--- a/apis/v1alpha1/opampbridge_types.go
+++ b/apis/v1alpha1/opampbridge_types.go
@@ -13,6 +13,9 @@ type OpAMPBridgeSpec struct {
// OpAMP backend Server endpoint
// +required
Endpoint string `json:"endpoint"`
+ // TLS configuration for the connection to the OpAMP backend server.
+ // +optional
+ TLS *OpAMPBridgeTLSConfig `json:"tls,omitempty"`
// Headers is an optional map of headers to use when connecting to the OpAMP Server,
// typically used to set access tokens or other authorization headers.
// +optional
@@ -120,6 +123,16 @@ type AgentDescription struct {
NonIdentifyingAttributes map[string]string `json:"non_identifying_attributes"`
}
+type OpAMPBridgeTLSConfig struct {
+ // Insecure indicates whether the endpoint should use TLS or not.
+ // When true, TLS is completely disabled.
+ // +optional
+ Insecure bool `json:"insecure,omitempty" yaml:"insecure,omitempty"`
+ // InsecureSkipVerify indicates to keep TLS but skip certificate validation.
+ // +optional
+ InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty" yaml:"insecure_skip_verify,omitempty"`
+}
+
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go
index d4a66f98fa..8e62f8c3a6 100644
--- a/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/v1alpha1/zz_generated.deepcopy.go
@@ -811,6 +811,11 @@ func (in *OpAMPBridgeList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OpAMPBridgeSpec) DeepCopyInto(out *OpAMPBridgeSpec) {
*out = *in
+ if in.TLS != nil {
+ in, out := &in.TLS, &out.TLS
+ *out = new(OpAMPBridgeTLSConfig)
+ **out = **in
+ }
if in.Headers != nil {
in, out := &in.Headers, &out.Headers
*out = make(map[string]string, len(*in))
@@ -968,6 +973,21 @@ func (in *OpAMPBridgeStatus) DeepCopy() *OpAMPBridgeStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OpAMPBridgeTLSConfig) DeepCopyInto(out *OpAMPBridgeTLSConfig) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpAMPBridgeTLSConfig.
+func (in *OpAMPBridgeTLSConfig) DeepCopy() *OpAMPBridgeTLSConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(OpAMPBridgeTLSConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OpenShiftRoute) DeepCopyInto(out *OpenShiftRoute) {
*out = *in
diff --git a/bundle/community/manifests/opentelemetry.io_opampbridges.yaml b/bundle/community/manifests/opentelemetry.io_opampbridges.yaml
index 592307181e..906b67f66a 100644
--- a/bundle/community/manifests/opentelemetry.io_opampbridges.yaml
+++ b/bundle/community/manifests/opentelemetry.io_opampbridges.yaml
@@ -872,6 +872,13 @@ spec:
type: object
serviceAccount:
type: string
+ tls:
+ properties:
+ insecure:
+ type: boolean
+ insecure_skip_verify:
+ type: boolean
+ type: object
tolerations:
items:
properties:
diff --git a/bundle/openshift/manifests/opentelemetry.io_opampbridges.yaml b/bundle/openshift/manifests/opentelemetry.io_opampbridges.yaml
index 592307181e..906b67f66a 100644
--- a/bundle/openshift/manifests/opentelemetry.io_opampbridges.yaml
+++ b/bundle/openshift/manifests/opentelemetry.io_opampbridges.yaml
@@ -872,6 +872,13 @@ spec:
type: object
serviceAccount:
type: string
+ tls:
+ properties:
+ insecure:
+ type: boolean
+ insecure_skip_verify:
+ type: boolean
+ type: object
tolerations:
items:
properties:
diff --git a/cmd/operator-opamp-bridge/internal/agent/agent.go b/cmd/operator-opamp-bridge/internal/agent/agent.go
index 7eb93ca5c5..6008a4131c 100644
--- a/cmd/operator-opamp-bridge/internal/agent/agent.go
+++ b/cmd/operator-opamp-bridge/internal/agent/agent.go
@@ -6,8 +6,10 @@ package agent
import (
"bytes"
"context"
+ "crypto/tls"
"errors"
"fmt"
+ "net/url"
"strings"
"time"
@@ -256,8 +258,34 @@ func (agent *Agent) Start() error {
},
RemoteConfigStatus: agent.remoteConfigStatus,
PackagesStateProvider: nil,
- Capabilities: agent.config.GetCapabilities(),
}
+
+ // Configure TLS based on explicit tls settings.
+ if agent.config.TLS != nil {
+ if agent.config.TLS.Insecure {
+ parsedURL, parseErr := url.Parse(settings.OpAMPServerURL)
+ if parseErr != nil {
+ return fmt.Errorf("invalid OpAMP server endpoint %q: %w", settings.OpAMPServerURL, parseErr)
+ }
+
+ if parsedURL.Scheme == "wss" || parsedURL.Scheme == "https" {
+ return fmt.Errorf(
+ "tls.insecure=true requires an insecure endpoint scheme (ws:// or http://), but got %q. "+
+ "Please update the endpoint to use the insecure scheme when tls.insecure is enabled",
+ settings.OpAMPServerURL,
+ )
+ }
+
+ agent.logger.Info("TLS is disabled for the OpAMP client connection (tls.insecure=true). This connection is not encrypted.")
+ } else if agent.config.TLS.InsecureSkipVerify {
+ // TLS enabled but skip certificate verification (standard behavior)
+ settings.TLSConfig = &tls.Config{
+ InsecureSkipVerify: true, //nolint:gosec // User explicitly opted in via tls.insecure_skip_verify.
+ }
+ agent.logger.Info("TLS is enabled for the OpAMP client connection but certificate verification is skipped (tls.insecure_skip_verify=true)")
+ }
+ }
+
err = agent.opampClient.SetAgentDescription(agent.agentDescription)
if err != nil {
return err
@@ -266,6 +294,11 @@ func (agent *Agent) Start() error {
if err != nil {
return err
}
+ capabilities := agent.config.GetCapabilities()
+ err = agent.opampClient.SetCapabilities(&capabilities)
+ if err != nil {
+ return err
+ }
agent.logger.V(3).Info("Starting OpAMP client...")
diff --git a/cmd/operator-opamp-bridge/internal/agent/agent_test.go b/cmd/operator-opamp-bridge/internal/agent/agent_test.go
index 032767ddee..d98dc88e9d 100644
--- a/cmd/operator-opamp-bridge/internal/agent/agent_test.go
+++ b/cmd/operator-opamp-bridge/internal/agent/agent_test.go
@@ -1258,3 +1258,67 @@ func getMessageDataFromConfigFile(filemap map[string]string) (*types.MessageData
}
return toReturn, nil
}
+
+func TestAgent_Start_TLSConfig(t *testing.T) {
+ tests := []struct {
+ name string
+ endpoint string
+ insecure bool
+ insecureSkipVerify bool
+ expectNil bool
+ expectSkip bool
+ expectURL string
+ }{
+ {
+ name: "Insecure (no TLS)",
+ endpoint: "ws://127.0.0.1:4320/v1/opamp",
+ insecure: true,
+ expectNil: true,
+ expectURL: "ws://127.0.0.1:4320/v1/opamp",
+ },
+ {
+ name: "Secure with Skip Verify",
+ endpoint: "wss://127.0.0.1:4320/v1/opamp",
+ insecure: false,
+ insecureSkipVerify: true,
+ expectNil: false,
+ expectSkip: true,
+ expectURL: "wss://127.0.0.1:4320/v1/opamp",
+ },
+ {
+ name: "Secure (verify enabled)",
+ endpoint: "wss://127.0.0.1:4320/v1/opamp",
+ insecure: false,
+ insecureSkipVerify: false,
+ expectNil: true,
+ expectURL: "wss://127.0.0.1:4320/v1/opamp",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ mockClient := &mockOpampClient{}
+ conf := config.NewConfig(logr.Discard())
+ conf.Endpoint = tt.endpoint
+ conf.TLS = &v1alpha1.OpAMPBridgeTLSConfig{
+ Insecure: tt.insecure,
+ InsecureSkipVerify: tt.insecureSkipVerify,
+ }
+ applier := getFakeApplier(t, conf)
+ mp := newMockProxy(nil, nil, nil)
+ agent := NewAgent(l, applier, conf, mockClient, mp)
+
+ err := agent.Start()
+ require.NoError(t, err)
+
+ if tt.expectNil {
+ assert.Nil(t, mockClient.settings.TLSConfig)
+ } else {
+ require.NotNil(t, mockClient.settings.TLSConfig)
+ assert.Equal(t, tt.expectSkip, mockClient.settings.TLSConfig.InsecureSkipVerify)
+ }
+ assert.Equal(t, tt.expectURL, mockClient.settings.OpAMPServerURL)
+ agent.Shutdown()
+ })
+ }
+}
diff --git a/cmd/operator-opamp-bridge/internal/config/config.go b/cmd/operator-opamp-bridge/internal/config/config.go
index 7ddd65fd1b..f5f5b1a8b9 100644
--- a/cmd/operator-opamp-bridge/internal/config/config.go
+++ b/cmd/operator-opamp-bridge/internal/config/config.go
@@ -84,13 +84,14 @@ type Config struct {
instanceId uuid.UUID `yaml:"-"`
// ComponentsAllowed is a list of allowed OpenTelemetry components for each pipeline type (receiver, processor, etc.)
- ComponentsAllowed map[string][]string `yaml:"componentsAllowed,omitempty"`
- Endpoint string `yaml:"endpoint"`
- Headers Headers `yaml:"headers,omitempty"`
- Capabilities map[Capability]bool `yaml:"capabilities"`
- HeartbeatInterval time.Duration `yaml:"heartbeatInterval,omitempty"`
- Name string `yaml:"name,omitempty"`
- AgentDescription AgentDescription `yaml:"description,omitempty"`
+ ComponentsAllowed map[string][]string `yaml:"componentsAllowed,omitempty"`
+ Endpoint string `yaml:"endpoint"`
+ TLS *v1alpha1.OpAMPBridgeTLSConfig `yaml:"tls,omitempty"`
+ Headers Headers `yaml:"headers,omitempty"`
+ Capabilities map[Capability]bool `yaml:"capabilities"`
+ HeartbeatInterval time.Duration `yaml:"heartbeatInterval,omitempty"`
+ Name string `yaml:"name,omitempty"`
+ AgentDescription AgentDescription `yaml:"description,omitempty"`
}
// AgentDescription is copied from the OpAMP Extension in the collector.
diff --git a/config/crd/bases/opentelemetry.io_opampbridges.yaml b/config/crd/bases/opentelemetry.io_opampbridges.yaml
index 4374586f4c..c1c2c8b2dc 100644
--- a/config/crd/bases/opentelemetry.io_opampbridges.yaml
+++ b/config/crd/bases/opentelemetry.io_opampbridges.yaml
@@ -870,6 +870,13 @@ spec:
type: object
serviceAccount:
type: string
+ tls:
+ properties:
+ insecure:
+ type: boolean
+ insecure_skip_verify:
+ type: boolean
+ type: object
tolerations:
items:
properties:
diff --git a/docs/api/opampbridges.md b/docs/api/opampbridges.md
index 1830233b75..8f44b7e69e 100644
--- a/docs/api/opampbridges.md
+++ b/docs/api/opampbridges.md
@@ -253,6 +253,13 @@ default.
the operator will not automatically create a ServiceAccount for the OpAMPBridge.
| Name | +Type | +Description | +Required | +
|---|---|---|---|
| insecure | +boolean | +
+ Insecure indicates whether the endpoint should use TLS or not.
+When true, TLS is completely disabled. + |
+ false | +
| insecure_skip_verify | +boolean | +
+ InsecureSkipVerify indicates to keep TLS but skip certificate validation. + |
+ false | +