Skip to content

Commit

Permalink
Add password file option to JMX config. (amazon-contributing#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
jefchien authored Feb 16, 2024
1 parent 591e510 commit b64e298
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 20 deletions.
23 changes: 23 additions & 0 deletions .chloggen-aws/jmx-update-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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: jmxreceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Adds a `password_file` option to the JMX receiver config.

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

# (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: Allows the passwords to be read in through a separate file instead of being set directly in the |
receiver YAML. Performs config validation to check if file exists, can be read, and is owner-only accessible. |
Also sets authentication related fields to `omitempty`.

# e.g. '[aws]'
# Include 'aws' if the change is done done by cwa
# Default: '[user]'
change_logs: [aws]
43 changes: 29 additions & 14 deletions receiver/jmxreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,36 @@ type Config struct {
// The exporter settings for
OTLPExporterConfig otlpExporterConfig `mapstructure:"otlp"`
// The JMX username
Username string `mapstructure:"username"`
Username string `mapstructure:"username,omitempty"`
// The JMX password
Password configopaque.String `mapstructure:"password"`
Password configopaque.String `mapstructure:"password,omitempty"`
// The JMX password file. Retrieves the Password based on the Username.
PasswordFile string `mapstructure:"password_file,omitempty"`
// The keystore path for SSL
KeystorePath string `mapstructure:"keystore_path"`
KeystorePath string `mapstructure:"keystore_path,omitempty"`
// The keystore password for SSL
KeystorePassword configopaque.String `mapstructure:"keystore_password"`
KeystorePassword configopaque.String `mapstructure:"keystore_password,omitempty"`
// The keystore type for SSL
KeystoreType string `mapstructure:"keystore_type"`
KeystoreType string `mapstructure:"keystore_type,omitempty"`
// The truststore path for SSL
TruststorePath string `mapstructure:"truststore_path"`
TruststorePath string `mapstructure:"truststore_path,omitempty"`
// The truststore password for SSL
TruststorePassword configopaque.String `mapstructure:"truststore_password"`
TruststorePassword configopaque.String `mapstructure:"truststore_password,omitempty"`
// The truststore type for SSL
TruststoreType string `mapstructure:"truststore_type"`
TruststoreType string `mapstructure:"truststore_type,omitempty"`
// The JMX remote profile. Should be one of:
// `"SASL/PLAIN"`, `"SASL/DIGEST-MD5"`, `"SASL/CRAM-MD5"`, `"TLS SASL/PLAIN"`, `"TLS SASL/DIGEST-MD5"`, or
// `"TLS SASL/CRAM-MD5"`, though no enforcement is applied.
RemoteProfile string `mapstructure:"remote_profile"`
RemoteProfile string `mapstructure:"remote_profile,omitempty"`
// The SASL/DIGEST-MD5 realm
Realm string `mapstructure:"realm"`
// Array of additional JARs to be added to the the class path when launching the JMX Metric Gatherer JAR
AdditionalJars []string `mapstructure:"additional_jars"`
Realm string `mapstructure:"realm,omitempty"`
// Array of additional JARs to be added to the class path when launching the JMX Metric Gatherer JAR
AdditionalJars []string `mapstructure:"additional_jars,omitempty"`
// Map of resource attributes used by the Java SDK Autoconfigure to set resource attributes
ResourceAttributes map[string]string `mapstructure:"resource_attributes"`
ResourceAttributes map[string]string `mapstructure:"resource_attributes,omitempty"`
// Log level used by the JMX metric gatherer. Should be one of:
// `"trace"`, `"debug"`, `"info"`, `"warn"`, `"error"`, `"off"`
LogLevel string `mapstructure:"log_level"`
LogLevel string `mapstructure:"log_level,omitempty"`
}

// We don't embed the existing OTLP Exporter config as most fields are unsupported
Expand Down Expand Up @@ -263,6 +265,19 @@ func (c *Config) Validate() error {
}
}

if c.PasswordFile != "" {
info, err := os.Stat(c.PasswordFile)
if err != nil {
return fmt.Errorf("`password_file` is inaccessible: %w", err)
}
switch info.Mode().Perm() {
// Matches JMX agent requirements for password file.
case 0400, 0600:
default:
return fmt.Errorf("`password_file` read access must be restricted to owner-only: %s", c.PasswordFile)
}
}

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion receiver/jmxreceiver/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jmxrec
go 1.20

require (
github.com/magiconair/properties v1.8.7
github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.89.0
github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.89.0
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.89.0
Expand Down Expand Up @@ -52,7 +53,6 @@ require (
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
github.com/knadh/koanf/v2 v2.0.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
Expand Down
46 changes: 41 additions & 5 deletions receiver/jmxreceiver/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strconv"
"strings"

"github.com/magiconair/properties"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/consumer"
Expand All @@ -23,8 +24,15 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jmxreceiver/internal/subprocess"
)

// jmxMainClass the class containing the main function for the JMX Metric Gatherer JAR
const jmxMainClass = "io.opentelemetry.contrib.jmxmetrics.JmxMetrics"
const (
// jmxMainClass the class containing the main function for the JMX Metric Gatherer JAR
jmxMainClass = "io.opentelemetry.contrib.jmxmetrics.JmxMetrics"

// The role names can be used in conjunction with the password file to avoid configuring them directly in the
// receiver config.
roleNameKeyStore = "keystore"
roleNameTrustStore = "truststore"
)

var _ receiver.Metrics = (*jmxMetricReceiver)(nil)

Expand Down Expand Up @@ -157,9 +165,9 @@ func (jmx *jmxMetricReceiver) buildOTLPReceiver() (receiver.Metrics, error) {
func (jmx *jmxMetricReceiver) buildJMXMetricGathererConfig() (string, error) {
config := map[string]string{}
failedToParse := `failed to parse Endpoint "%s": %w`
parsed, err := url.Parse(jmx.config.Endpoint)
if err != nil {
return "", fmt.Errorf(failedToParse, jmx.config.Endpoint, err)
parsed, parseErr := url.Parse(jmx.config.Endpoint)
if parseErr != nil {
return "", fmt.Errorf(failedToParse, jmx.config.Endpoint, parseErr)
}

if !(parsed.Scheme == "service" && strings.HasPrefix(parsed.Opaque, "jmx:")) {
Expand Down Expand Up @@ -191,12 +199,23 @@ func (jmx *jmxMetricReceiver) buildJMXMetricGathererConfig() (string, error) {
config["otel.exporter.otlp.headers"] = jmx.config.OTLPExporterConfig.headersToString()
}

var passwordMap map[string]string
if jmx.config.PasswordFile != "" {
var err error
passwordMap, err = parsePasswordFile(jmx.config.PasswordFile)
if err != nil {
jmx.logger.Error("Unable to retrieve password from file", zap.Error(err))
}
}

if jmx.config.Username != "" {
config["otel.jmx.username"] = jmx.config.Username
}

if jmx.config.Password != "" {
config["otel.jmx.password"] = string(jmx.config.Password)
} else if password, ok := passwordMap[jmx.config.Username]; ok && jmx.config.Username != "" {
config["otel.jmx.password"] = password
}

if jmx.config.RemoteProfile != "" {
Expand All @@ -212,6 +231,8 @@ func (jmx *jmxMetricReceiver) buildJMXMetricGathererConfig() (string, error) {
}
if jmx.config.KeystorePassword != "" {
config["javax.net.ssl.keyStorePassword"] = string(jmx.config.KeystorePassword)
} else if password, ok := passwordMap[roleNameKeyStore]; ok {
config["javax.net.ssl.keyStorePassword"] = password
}
if jmx.config.KeystoreType != "" {
config["javax.net.ssl.keyStoreType"] = jmx.config.KeystoreType
Expand All @@ -221,6 +242,8 @@ func (jmx *jmxMetricReceiver) buildJMXMetricGathererConfig() (string, error) {
}
if jmx.config.TruststorePassword != "" {
config["javax.net.ssl.trustStorePassword"] = string(jmx.config.TruststorePassword)
} else if password, ok := passwordMap[roleNameTrustStore]; ok {
config["javax.net.ssl.trustStorePassword"] = password
}
if jmx.config.TruststoreType != "" {
config["javax.net.ssl.trustStoreType"] = jmx.config.TruststoreType
Expand Down Expand Up @@ -259,3 +282,16 @@ func (jmx *jmxMetricReceiver) buildJMXMetricGathererConfig() (string, error) {

return strings.Join(content, "\n"), nil
}

func parsePasswordFile(path string) (map[string]string, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
loader := properties.Loader{Encoding: properties.UTF8, DisableExpansion: true}
p, err := loader.LoadBytes(content)
if err != nil {
return nil, err
}
return p.Map(), nil
}
32 changes: 32 additions & 0 deletions receiver/jmxreceiver/receiver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package jmxreceiver

import (
"context"
"os"
"path/filepath"
"testing"
"time"

Expand Down Expand Up @@ -36,6 +38,13 @@ func TestReceiver(t *testing.T) {
}

func TestBuildJMXMetricGathererConfig(t *testing.T) {
passwordFileContents := `
myusername mypassword
keystore: keypass
truststore = trustpass
`
passwordFilePath := filepath.Join(t.TempDir(), "test.properties")
require.NoError(t, os.WriteFile(passwordFilePath, []byte(passwordFileContents), 0600))
tests := []struct {
name string
config *Config
Expand Down Expand Up @@ -97,6 +106,29 @@ otel.metrics.exporter = otlp
otel.resource.attributes = abc=123,one=two`,
"",
},
{
"handles password file",
&Config{
Endpoint: "myhost:12345",
TargetSystem: "mytargetsystem",
OTLPExporterConfig: otlpExporterConfig{
Endpoint: "https://myotlpendpoint",
},
Username: "myusername",
PasswordFile: passwordFilePath,
},
`javax.net.ssl.keyStorePassword = keypass
javax.net.ssl.trustStorePassword = trustpass
otel.exporter.otlp.endpoint = https://myotlpendpoint
otel.exporter.otlp.timeout = 0
otel.jmx.interval.milliseconds = 0
otel.jmx.password = mypassword
otel.jmx.service.url = service:jmx:rmi:///jndi/rmi://myhost:12345/jmxrmi
otel.jmx.target.system = mytargetsystem
otel.jmx.username = myusername
otel.metrics.exporter = otlp`,
"",
},
{
"errors on portless endpoint",
&Config{
Expand Down

0 comments on commit b64e298

Please sign in to comment.