Skip to content

Commit

Permalink
[receiver/mysql] expose tls config (#29269)
Browse files Browse the repository at this point in the history
**Description:** 
Using the mysqlreceiver, we were getting the following error as our
MySQL server on AWS RDS requires secure transport for all connections by
setting `require_secure_transport=ON` per
https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/mysql-ssl-connections.html#mysql-ssl-connections.require-ssl

**Example log message**
`2023-10-31T10:53:30.239Z error scraperhelper/scrapercontroller.go:200
Error scraping metrics {"kind": "receiver", "name": "mysql",
"data_type": "metrics", "error": "Error 3159 (HY000): Connections using
insecure transport are prohibited while --require_secure_transport=ON.;
", "scraper": "mysql"}`
  • Loading branch information
s-v-a-n authored Nov 17, 2023
1 parent c3b2997 commit 7337273
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 5 deletions.
28 changes: 28 additions & 0 deletions .chloggen/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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: mysqlreceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: "expose tls in mysqlreceiver"

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [29269]

# (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: "If `tls` is not set, the default is to disable TLS connections."


# 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: []
4 changes: 4 additions & 0 deletions receiver/mysqlreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Collecting most metrics requires the ability to execute `SHOW GLOBAL STATUS`.

The following settings are optional:
- `endpoint`: (default = `localhost:3306`)
- `tls`: Defines the TLS configuration to use. If `tls` is not set, the default is to disable TLS connections.
- `insecure`: (default = `false`) Set this to `true` to disable TLS connections.
- `insecure_skip_verify`: (default = `false`) Set this to `true` to enable TLS but not verify the certificate.
- `server_name_override`: This sets the ServerName in the TLSConfig.
- `username`: (default = `root`)
- `password`: The password to the username.
- `allow_native_passwords`: (default = `true`)
Expand Down
19 changes: 17 additions & 2 deletions receiver/mysqlreceiver/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,29 @@ type ReplicaStatusStats struct {

var _ client = (*mySQLClient)(nil)

func newMySQLClient(conf *Config) client {
func newMySQLClient(conf *Config) (client, error) {
tls, err := conf.TLS.LoadTLSConfig()
if err != nil {
return nil, err
}
tlsConfig := ""
if tls != nil {
err := mysql.RegisterTLSConfig("custom", tls)
if err != nil {
return nil, err
}
tlsConfig = "custom"
}

driverConf := mysql.Config{
User: conf.Username,
Passwd: string(conf.Password),
Net: conf.Transport,
Addr: conf.Endpoint,
DBName: conf.Database,
AllowNativePasswords: conf.AllowNativePasswords,
TLS: tls,
TLSConfig: tlsConfig,
}
connStr := driverConf.FormatDSN()

Expand All @@ -179,7 +194,7 @@ func newMySQLClient(conf *Config) client {
statementEventsDigestTextLimit: conf.StatementEvents.DigestTextLimit,
statementEventsLimit: conf.StatementEvents.Limit,
statementEventsTimeLimit: conf.StatementEvents.TimeLimit,
}
}, nil
}

func (c *mySQLClient) Connect() error {
Expand Down
19 changes: 19 additions & 0 deletions receiver/mysqlreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (

"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/receiver/scraperhelper"

"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mysqlreceiver/internal/metadata"
Expand All @@ -26,6 +28,7 @@ type Config struct {
Database string `mapstructure:"database,omitempty"`
AllowNativePasswords bool `mapstructure:"allow_native_passwords,omitempty"`
confignet.NetAddr `mapstructure:",squash"`
TLS configtls.TLSClientSetting `mapstructure:"tls,omitempty"`
MetricsBuilderConfig metadata.MetricsBuilderConfig `mapstructure:",squash"`
StatementEvents StatementEventsConfig `mapstructure:"statement_events"`
}
Expand All @@ -35,3 +38,19 @@ type StatementEventsConfig struct {
Limit int `mapstructure:"limit"`
TimeLimit time.Duration `mapstructure:"time_limit"`
}

func (cfg *Config) Unmarshal(componentParser *confmap.Conf) error {
if componentParser == nil {
// Nothing to do if there is no config given.
return nil
}

// Change the default to Insecure = true as we don't want to break
// existing deployments which does not use TLS by default.
if !componentParser.IsSet("tls") {
cfg.TLS = configtls.TLSClientSetting{}
cfg.TLS.Insecure = true
}

return componentParser.Unmarshal(cfg, confmap.WithErrorUnused())
}
26 changes: 26 additions & 0 deletions receiver/mysqlreceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@ func TestLoadConfig(t *testing.T) {
expected.Password = "${env:MYSQL_PASSWORD}"
expected.Database = "otel"
expected.CollectionInterval = 10 * time.Second
// This defaults to true when tls is omitted from the configmap.
expected.TLS.Insecure = true

require.Equal(t, expected, cfg)
}

func TestLoadConfigDefaultTLS(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)

factory := NewFactory()
cfg := factory.CreateDefaultConfig()

sub, err := cm.Sub(component.NewIDWithName(metadata.Type, "").String() + "/default_tls")
require.NoError(t, err)
require.NoError(t, component.UnmarshalConfig(sub, cfg))

expected := factory.CreateDefaultConfig().(*Config)
expected.Endpoint = "localhost:3306"
expected.Username = "otel"
expected.Password = "${env:MYSQL_PASSWORD}"
expected.Database = "otel"
expected.CollectionInterval = 10 * time.Second
// This defaults to false when tls is defined in the configmap.
expected.TLS.Insecure = false
expected.TLS.ServerName = "localhost"

require.Equal(t, expected, cfg)
}
2 changes: 2 additions & 0 deletions receiver/mysqlreceiver/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
go.opentelemetry.io/collector/component v0.89.0
go.opentelemetry.io/collector/config/confignet v0.89.0
go.opentelemetry.io/collector/config/configopaque v0.89.0
go.opentelemetry.io/collector/config/configtls v0.89.0
go.opentelemetry.io/collector/confmap v0.89.0
go.opentelemetry.io/collector/consumer v0.89.0
go.opentelemetry.io/collector/pdata v1.0.0-rcv0018
Expand All @@ -35,6 +36,7 @@ require (
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
Expand Down
4 changes: 4 additions & 0 deletions receiver/mysqlreceiver/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 49 additions & 1 deletion receiver/mysqlreceiver/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (

const mysqlPort = "3306"

func TestIntegration(t *testing.T) {
func TestIntegrationWithoutTLS(t *testing.T) {
scraperinttest.NewIntegrationTest(
NewFactory(),
scraperinttest.WithContainerRequest(
Expand Down Expand Up @@ -52,6 +52,54 @@ func TestIntegration(t *testing.T) {
rCfg.Endpoint = net.JoinHostPort(ci.Host(t), ci.MappedPort(t, mysqlPort))
rCfg.Username = "otel"
rCfg.Password = "otel"
// disable TLS connection
rCfg.TLS.Insecure = true
}),
scraperinttest.WithCompareOptions(
pmetrictest.IgnoreResourceAttributeValue("mysql.instance.endpoint"),
pmetrictest.IgnoreMetricValues(),
pmetrictest.IgnoreMetricDataPointsOrder(),
pmetrictest.IgnoreStartTimestamp(),
pmetrictest.IgnoreTimestamp(),
),
).Run(t)
}

func TestIntegrationWithTLS(t *testing.T) {
scraperinttest.NewIntegrationTest(
NewFactory(),
scraperinttest.WithContainerRequest(
testcontainers.ContainerRequest{
Image: "mysql:8.0.33",
// enable auto TLS certs AND require TLS connections only !
Cmd: []string{"--auto_generate_certs=ON", "--require_secure_transport=ON"},
ExposedPorts: []string{mysqlPort},
WaitingFor: wait.ForListeningPort(mysqlPort).
WithStartupTimeout(2 * time.Minute),
Env: map[string]string{
"MYSQL_ROOT_PASSWORD": "otel",
"MYSQL_DATABASE": "otel",
"MYSQL_USER": "otel",
"MYSQL_PASSWORD": "otel",
},
Files: []testcontainers.ContainerFile{
{
HostFilePath: filepath.Join("testdata", "integration", "init.sh"),
ContainerFilePath: "/docker-entrypoint-initdb.d/init.sh",
FileMode: 700,
},
},
}),
scraperinttest.WithCustomConfig(
func(t *testing.T, cfg component.Config, ci *scraperinttest.ContainerInfo) {
rCfg := cfg.(*Config)
rCfg.CollectionInterval = time.Second
rCfg.Endpoint = net.JoinHostPort(ci.Host(t), ci.MappedPort(t, mysqlPort))
rCfg.Username = "otel"
rCfg.Password = "otel"
// mysql container is using self-signed certs
// InsecureSkipVerify will enable TLS but not verify the certificate.
rCfg.TLS.InsecureSkipVerify = true
}),
scraperinttest.WithCompareOptions(
pmetrictest.IgnoreResourceAttributeValue("mysql.instance.endpoint"),
Expand Down
7 changes: 5 additions & 2 deletions receiver/mysqlreceiver/scraper.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@ func newMySQLScraper(

// start starts the scraper by initializing the db client connection.
func (m *mySQLScraper) start(_ context.Context, _ component.Host) error {
sqlclient := newMySQLClient(m.config)
sqlclient, err := newMySQLClient(m.config)
if err != nil {
return err
}

err := sqlclient.Connect()
err = sqlclient.Connect()
if err != nil {
return err
}
Expand Down
8 changes: 8 additions & 0 deletions receiver/mysqlreceiver/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ mysql:
password: ${env:MYSQL_PASSWORD}
database: otel
collection_interval: 10s
mysql/default_tls:
endpoint: localhost:3306
username: otel
password: ${env:MYSQL_PASSWORD}
database: otel
collection_interval: 10s
tls: # specified, but use default values
server_name_override: localhost

0 comments on commit 7337273

Please sign in to comment.