internal/pkg/config/instrumentation.go (99 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. package config import ( "crypto/tls" "crypto/x509" "encoding/pem" "errors" "fmt" "net/url" "os" "github.com/elastic/elastic-agent-libs/transport/tlscommon" apmtransport "go.elastic.co/apm/v2/transport" ) // Instrumentation configures APM Tracing for the `fleet-server`. type Instrumentation struct { Enabled bool `config:"enabled"` TLS InstrumentationTLS `config:"tls"` // Environment specifies the environment name - may be specified with ELASTIC_APM_ENVIRONMENT Environment string `config:"environment"` // APIKey specifies the API key value - may be specified with ELASTIC_APM_API_KEY APIKey string `config:"api_key"` // APIKeyPath specifies the path to the API key secret file APIKeyPath string `config:"api_key_path"` // SecretToken specifies the secret token value - may be specified with ELASTIC_APM_SECRET_TOKEN SecretToken string `config:"secret_token"` // SecretTokenPath specifies the path to the secret token file SecretTokenPath string `config:"secret_token_path"` // Hosts specifies the APM server urls - may be specified with ELASTIC_APM_SERVER_URL Hosts []string `config:"hosts"` // GlobalLabels specifies apm global labels - may be specified with ELASTIC_APM_GLOBAL_LABELS GlobalLabels string `config:"global_labels"` // TransactionSampleRate sets the sample rate - may be specified with ELASTIC_APM_TRANSACTION_SAMPLE_RATE TransactionSampleRate string `config:"transaction_sample_rate"` } type InstrumentationTLS struct { // APM certificate validation skip - may be specified with ELASTIC_APM_VERIFY_SERVER_CERT SkipVerify bool `config:"skip_verify"` // APM server certificate path - may be specified with ELASTIC_APM_SERVER_CERT ServerCertificate string `config:"server_certificate"` // APM server CA path - may be specified with ELASTIC_APM_SERVER_CA_CERT_FILE ServerCA string `config:"server_ca"` } // APMHTTPTransportOptions will return an APM HTTP transport options configuration specifier. func (c *Instrumentation) APMHTTPTransportOptions() (apmtransport.HTTPTransportOptions, error) { hosts := make([]*url.URL, 0, len(c.Hosts)) for _, host := range c.Hosts { u, err := url.Parse(host) if err != nil { return apmtransport.HTTPTransportOptions{}, fmt.Errorf("failed parsing %s: %w", host, err) } hosts = append(hosts, u) } tlsConfig := &tls.Config{ InsecureSkipVerify: c.TLS.SkipVerify, //nolint:gosec // users can disable tls validation } if c.TLS.ServerCertificate != "" { p, err := os.ReadFile(c.TLS.ServerCertificate) if err != nil { return apmtransport.HTTPTransportOptions{}, fmt.Errorf("unable to read instrumentation certificate: %w", err) } block, _ := pem.Decode(p) cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return apmtransport.HTTPTransportOptions{}, fmt.Errorf("unable to parse instrumentation certificate: %w", err) } tlsConfig.InsecureSkipVerify = true tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { return verifyPeerCertificate(rawCerts, cert) } } if c.TLS.ServerCA != "" { pool, errs := tlscommon.LoadCertificateAuthorities([]string{c.TLS.ServerCA}) // FIXME once we update elastic-agent-libs to go 1.20 we can return multiple errors directly with errors.Join() if len(errs) != 0 { return apmtransport.HTTPTransportOptions{}, fmt.Errorf("unable to load instrumentation cas: %w", errors.Join(errs...)) } tlsConfig.RootCAs = pool } apiKey := c.APIKey if c.APIKey == "" && c.APIKeyPath != "" { p, err := os.ReadFile(c.APIKeyPath) if err != nil { return apmtransport.HTTPTransportOptions{}, fmt.Errorf("unable to read API key file: %w", err) } apiKey = string(p) } secretToken := c.SecretToken if c.SecretToken == "" && c.SecretTokenPath != "" { p, err := os.ReadFile(c.SecretTokenPath) if err != nil { return apmtransport.HTTPTransportOptions{}, fmt.Errorf("unable to read secret token file: %w", err) } secretToken = string(p) } return apmtransport.HTTPTransportOptions{ APIKey: apiKey, SecretToken: secretToken, ServerURLs: hosts, TLSClientConfig: tlsConfig, }, nil } // verifyPeerCertificate copied from elastic/apm-agent-go/transport/http.go with the following alterations: // - replaced use of pkg/errors with fmt func verifyPeerCertificate(rawCerts [][]byte, trusted *x509.Certificate) error { if len(rawCerts) == 0 { return fmt.Errorf("missing leaf certificate") } cert, err := x509.ParseCertificate(rawCerts[0]) if err != nil { return fmt.Errorf("failed to parse certificate from server: %w", err) } if !cert.Equal(trusted) { return fmt.Errorf("failed to verify server certificate") } return nil }