Skip to content

Commit

Permalink
Add support for basic auth (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrislav authored Mar 28, 2024
1 parent 0f73679 commit 4a9ad48
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 91 deletions.
61 changes: 26 additions & 35 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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, ",")
Expand Down Expand Up @@ -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 != "" {
Expand Down
81 changes: 25 additions & 56 deletions prometheus_to_cloudwatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"compress/gzip"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -146,16 +138,15 @@ 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
b.excludeMetrics = c.ExcludeMetrics
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
Expand Down Expand Up @@ -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 {
Expand All @@ -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...)

Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit 4a9ad48

Please sign in to comment.