From 4a9ad48f1d41e1fd10704f6e01a1f78403d2fb68 Mon Sep 17 00:00:00 2001 From: Patryk Kalinowski Date: Thu, 28 Mar 2024 13:28:10 +0100 Subject: [PATCH] Add support for basic auth (#2) --- main.go | 61 ++++++++++++---------------- prometheus_to_cloudwatch.go | 81 ++++++++++++------------------------- 2 files changed, 51 insertions(+), 91 deletions(-) diff --git a/main.go b/main.go index 6958e8f..b34142a 100644 --- a/main.go +++ b/main.go @@ -26,16 +26,15 @@ var ( cloudWatchPublishTimeout = flag.String("cloudwatch_publish_timeout", os.Getenv("CLOUDWATCH_PUBLISH_TIMEOUT"), "CloudWatch publish timeout in seconds") prometheusScrapeInterval = flag.String("prometheus_scrape_interval", os.Getenv("PROMETHEUS_SCRAPE_INTERVAL"), "Prometheus scrape interval in seconds") prometheusScrapeUrl = flag.String("prometheus_scrape_url", os.Getenv("PROMETHEUS_SCRAPE_URL"), "Prometheus scrape URL") - certPath = flag.String("cert_path", os.Getenv("CERT_PATH"), "Path to SSL Certificate file (when using SSL for `prometheus_scrape_url`)") - keyPath = flag.String("key_path", os.Getenv("KEY_PATH"), "Path to Key file (when using SSL for `prometheus_scrape_url`)") - skipServerCertCheck = flag.String("accept_invalid_cert", os.Getenv("ACCEPT_INVALID_CERT"), "Accept any certificate during TLS handshake. Insecure, use only for testing") - additionalDimension = flag.String("additional_dimension", os.Getenv("ADDITIONAL_DIMENSION"), "Additional dimension specified by NAME=VALUE") + additionalDimensions = flag.String("additional_dimensions", os.Getenv("ADDITIONAL_DIMENSIONS"), "Additional dimension specified by NAME=VALUE") replaceDimensions = flag.String("replace_dimensions", os.Getenv("REPLACE_DIMENSIONS"), "replace dimensions specified by NAME=VALUE,...") includeMetrics = flag.String("include_metrics", os.Getenv("INCLUDE_METRICS"), "Only publish the specified metrics (comma-separated list of glob patterns, e.g. 'up,http_*')") excludeMetrics = flag.String("exclude_metrics", os.Getenv("EXCLUDE_METRICS"), "Never publish the specified metrics (comma-separated list of glob patterns, e.g. 'tomcat_*')") includeDimensionsForMetrics = flag.String("include_dimensions_for_metrics", os.Getenv("INCLUDE_DIMENSIONS_FOR_METRICS"), "Only publish the specified dimensions for metrics (semi-colon-separated key values of comma-separated dimensions of METRIC=dim1,dim2;, e.g. 'flink_jobmanager=job_id')") excludeDimensionsForMetrics = flag.String("exclude_dimensions_for_metrics", os.Getenv("EXCLUDE_DIMENSIONS_FOR_METRICS"), "Never publish the specified dimensions for metrics (semi-colon-separated key values of comma-separated dimensions of METRIC=dim1,dim2;, e.g. 'flink_jobmanager=job,host;zk_up=host,pod;')") forceHighRes = flag.Bool("force_high_res", defaultForceHighRes, "Publish all metrics with high resolution, even when original metrics don't have the label "+cwHighResLabel) + basicAuthUsername = flag.String("basic_auth_username", os.Getenv("BASIC_AUTH_USERNAME"), "") + basicAuthPassword = flag.String("basic_auth_password", os.Getenv("BASIC_AUTH_PASSWORD"), "") ) // kevValMustParse takes a string and exits with a message if it cannot parse as KEY=VALUE @@ -104,26 +103,19 @@ func main() { flag.PrintDefaults() log.Fatal("prometheus-to-cloudwatch: Error: -prometheus_scrape_url or PROMETHEUS_SCRAPE_URL required") } - if (*certPath != "" && *keyPath == "") || (*certPath == "" && *keyPath != "") { - flag.PrintDefaults() - log.Fatal("prometheus-to-cloudwatch: Error: when using SSL, both -prometheus_cert_path and -prometheus_key_path are required. If not using SSL, do not provide any of them") - } - - var skipCertCheck = true var err error - if *skipServerCertCheck != "" { - if skipCertCheck, err = strconv.ParseBool(*skipServerCertCheck); err != nil { - log.Fatal("prometheus-to-cloudwatch: Error: ", err) + var additionalDims = map[string]string{} + if *additionalDimensions != "" { + kvs := strings.Split(*additionalDimensions, ",") + if len(kvs) > 0 { + for _, rd := range kvs { + key, val := keyValMustParse(rd, "-additionalDimension must be formatted as NAME=VALUE,...") + additionalDims[key] = val + } } } - var additionalDimensions = map[string]string{} - if *additionalDimension != "" { - key, val := keyValMustParse(*additionalDimension, "-additionalDimension must be formatted as NAME=VALUE") - additionalDimensions[key] = val - } - var replaceDims = map[string]string{} if *replaceDimensions != "" { kvs := strings.Split(*replaceDimensions, ",") @@ -168,22 +160,21 @@ func main() { } config := &Config{ - CloudWatchNamespace: *cloudWatchNamespace, - CloudWatchRegion: *cloudWatchRegion, - PrometheusScrapeUrl: *prometheusScrapeUrl, - PrometheusCertPath: *certPath, - PrometheusKeyPath: *keyPath, - PrometheusSkipServerCertCheck: skipCertCheck, - AwsAccessKeyId: *awsAccessKeyId, - AwsSecretAccessKey: *awsSecretAccessKey, - AwsSessionToken: *awsSessionToken, - AdditionalDimensions: additionalDimensions, - ReplaceDimensions: replaceDims, - IncludeMetrics: includeMetricsList, - ExcludeMetrics: excludeMetricsList, - ExcludeDimensionsForMetrics: excludeDimensionsForMetricsList, - IncludeDimensionsForMetrics: includeDimensionsForMetricsList, - ForceHighRes: *forceHighRes, + CloudWatchNamespace: *cloudWatchNamespace, + CloudWatchRegion: *cloudWatchRegion, + PrometheusScrapeUrl: *prometheusScrapeUrl, + AwsAccessKeyId: *awsAccessKeyId, + AwsSecretAccessKey: *awsSecretAccessKey, + AwsSessionToken: *awsSessionToken, + AdditionalDimensions: additionalDims, + ReplaceDimensions: replaceDims, + IncludeMetrics: includeMetricsList, + ExcludeMetrics: excludeMetricsList, + ExcludeDimensionsForMetrics: excludeDimensionsForMetricsList, + IncludeDimensionsForMetrics: includeDimensionsForMetricsList, + ForceHighRes: *forceHighRes, + BasicAuthUsername: *basicAuthUsername, + BasicAuthPassword: *basicAuthPassword, } if *prometheusScrapeInterval != "" { diff --git a/prometheus_to_cloudwatch.go b/prometheus_to_cloudwatch.go index 2f4bdaf..ea33d91 100644 --- a/prometheus_to_cloudwatch.go +++ b/prometheus_to_cloudwatch.go @@ -4,7 +4,6 @@ import ( "bytes" "compress/gzip" "context" - "crypto/tls" "errors" "fmt" "io" @@ -82,15 +81,6 @@ type Config struct { // Prometheus scrape URL PrometheusScrapeUrl string - // Path to Certificate file - PrometheusCertPath string - - // Path to Key file - PrometheusKeyPath string - - // Accept any certificate during TLS handshake. Insecure, use only for testing - PrometheusSkipServerCertCheck bool - // Additional dimensions to send to CloudWatch AdditionalDimensions map[string]string @@ -111,24 +101,26 @@ type Config struct { // ForceHighRes forces all exported metrics to be sent as custom high-resolution metrics. ForceHighRes bool + + BasicAuthUsername string + BasicAuthPassword string } // Bridge pushes metrics to AWS CloudWatch type Bridge struct { - cloudWatchPublishInterval time.Duration - cloudWatchNamespace string - cw *cloudwatch.CloudWatch - prometheusScrapeUrl string - prometheusCertPath string - prometheusKeyPath string - prometheusSkipServerCertCheck bool - additionalDimensions map[string]string - replaceDimensions map[string]string - includeMetrics []glob.Glob - excludeMetrics []glob.Glob - includeDimensionsForMetrics []MatcherWithStringSet - excludeDimensionsForMetrics []MatcherWithStringSet - forceHighRes bool + cloudWatchPublishInterval time.Duration + cloudWatchNamespace string + cw *cloudwatch.CloudWatch + prometheusScrapeUrl string + additionalDimensions map[string]string + replaceDimensions map[string]string + includeMetrics []glob.Glob + excludeMetrics []glob.Glob + includeDimensionsForMetrics []MatcherWithStringSet + excludeDimensionsForMetrics []MatcherWithStringSet + forceHighRes bool + basicAuthUsername string + basicAuthPassword string } // NewBridge initializes and returns a pointer to a Bridge using the @@ -146,9 +138,6 @@ func NewBridge(c *Config) (*Bridge, error) { } b.prometheusScrapeUrl = c.PrometheusScrapeUrl - b.prometheusCertPath = c.PrometheusCertPath - b.prometheusKeyPath = c.PrometheusKeyPath - b.prometheusSkipServerCertCheck = c.PrometheusSkipServerCertCheck b.additionalDimensions = c.AdditionalDimensions b.replaceDimensions = c.ReplaceDimensions b.includeMetrics = c.IncludeMetrics @@ -156,6 +145,8 @@ func NewBridge(c *Config) (*Bridge, error) { b.includeDimensionsForMetrics = c.IncludeDimensionsForMetrics b.excludeDimensionsForMetrics = c.ExcludeDimensionsForMetrics b.forceHighRes = c.ForceHighRes + b.basicAuthUsername = c.BasicAuthUsername + b.basicAuthPassword = c.BasicAuthPassword if c.CloudWatchPublishInterval > 0 { b.cloudWatchPublishInterval = c.CloudWatchPublishInterval @@ -205,7 +196,7 @@ func (b *Bridge) Run(ctx context.Context) { case <-ticker.C: mfChan := make(chan *dto.MetricFamily, 1024) - go fetchMetricFamilies(b.prometheusScrapeUrl, mfChan, b.prometheusCertPath, b.prometheusKeyPath, b.prometheusSkipServerCertCheck) + go fetchMetricFamilies(b.prometheusScrapeUrl, mfChan, b.basicAuthUsername, b.basicAuthPassword) var metricFamilies []*dto.MetricFamily for mf := range mfChan { @@ -227,9 +218,9 @@ func (b *Bridge) Run(ctx context.Context) { } // NOTE: The CloudWatch API has the following limitations: -// - Max 40kb request size -// - Single namespace per request -// - Max 10 dimensions per metric +// - Max 40kb request size +// - Single namespace per request +// - Max 10 dimensions per metric func (b *Bridge) publishMetricsToCloudWatch(mfs []*dto.MetricFamily) (count int, e error) { vec, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{Timestamp: model.Now()}, mfs...) @@ -491,39 +482,17 @@ func getUnit(m model.Metric) string { // It returns after all MetricFamilies have been sent func fetchMetricFamilies( url string, ch chan<- *dto.MetricFamily, - certificate string, key string, - skipServerCertCheck bool, + username string, password string, ) { defer close(ch) - var transport *http.Transport - if certificate != "" && key != "" { - cert, err := tls.LoadX509KeyPair(certificate, key) - if err != nil { - log.Fatal("prometheus-to-cloudwatch: Error: ", err) - } - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - InsecureSkipVerify: skipServerCertCheck, - } - tlsConfig.BuildNameToCertificate() - transport = &http.Transport{TLSClientConfig: tlsConfig} - } else { - transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: skipServerCertCheck}, - } - } - client := &http.Client{Transport: transport} - decodeContent(client, url, ch) - client.CloseIdleConnections() -} -func decodeContent(client *http.Client, url string, ch chan<- *dto.MetricFamily) { req, err := http.NewRequest("GET", url, nil) if err != nil { log.Fatalf("prometheus-to-cloudwatch: Error: creating GET request for URL %q failed: %s", url, err) } req.Header.Add("Accept", acceptHeader) - resp, err := client.Do(req) + req.SetBasicAuth(username, password) + resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatalf("prometheus-to-cloudwatch: Error: executing GET request for URL %q failed: %s", url, err) }