From 677a55dd3649d8c43beaf375a9afec55c88d644d Mon Sep 17 00:00:00 2001 From: Chunlin Yang Date: Wed, 19 Jan 2022 17:01:58 +0800 Subject: [PATCH] amtool to support http_config to access alertmanager (#2764) * Support http_config for amtool Co-authored-by: Julien Pivotto Co-authored-by: Simon Pasquier Signed-off-by: clyang82 Signed-off-by: Sinuhe Tellez --- cli/config/http_config.go | 39 +++++++++++++ cli/config/http_config_test.go | 55 +++++++++++++++++++ cli/config/testdata/http_config.bad.yml | 2 + .../testdata/http_config.basic_auth.good.yml | 3 + cli/config/testdata/http_config.good.yml | 4 ++ cli/root.go | 53 +++++++----------- 6 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 cli/config/http_config.go create mode 100644 cli/config/http_config_test.go create mode 100644 cli/config/testdata/http_config.bad.yml create mode 100644 cli/config/testdata/http_config.basic_auth.good.yml create mode 100644 cli/config/testdata/http_config.good.yml diff --git a/cli/config/http_config.go b/cli/config/http_config.go new file mode 100644 index 0000000000..b94ab10158 --- /dev/null +++ b/cli/config/http_config.go @@ -0,0 +1,39 @@ +// Copyright 2021 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "io/ioutil" + "path/filepath" + + promconfig "github.com/prometheus/common/config" + "gopkg.in/yaml.v2" +) + +// LoadHTTPConfigFile returns HTTPClientConfig for the given http_config file +func LoadHTTPConfigFile(filename string) (*promconfig.HTTPClientConfig, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + httpConfig := &promconfig.HTTPClientConfig{} + err = yaml.UnmarshalStrict(b, httpConfig) + if err != nil { + return nil, err + } + httpConfig.SetDirectory(filepath.Dir(filepath.Dir(filename))) + + return httpConfig, nil +} diff --git a/cli/config/http_config_test.go b/cli/config/http_config_test.go new file mode 100644 index 0000000000..d226525162 --- /dev/null +++ b/cli/config/http_config_test.go @@ -0,0 +1,55 @@ +// Copyright 2021 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "strings" + "testing" +) + +func TestInvalidHTTPConfig(t *testing.T) { + _, err := LoadHTTPConfigFile("testdata/http_config.bad.yml") + errMsg := `authorization type cannot be set to "basic", use "basic_auth" instead` + if !strings.Contains(err.Error(), errMsg) { + t.Errorf("Expected error for invalid HTTP client configuration to contain %q but got: %s", errMsg, err) + } +} + +func TestValidHTTPConfig(t *testing.T) { + cfg, err := LoadHTTPConfigFile("testdata/http_config.good.yml") + if err != nil { + t.Fatalf("Error loading HTTP client config: %v", err) + } + + proxyURL := "http://remote.host" + if cfg.ProxyURL.String() != proxyURL { + t.Fatalf("Expected proxy_url is %q but got: %s", proxyURL, cfg.ProxyURL.String()) + } +} + +func TestValidBasicAuthHTTPConfig(t *testing.T) { + cfg, err := LoadHTTPConfigFile("testdata/http_config.basic_auth.good.yml") + if err != nil { + t.Fatalf("Error loading HTTP client config: %v", err) + } + + if cfg.BasicAuth.Username != "user" { + t.Fatalf("Expected username is %q but got: %s", "user", cfg.BasicAuth.Username) + } + + password := string(cfg.BasicAuth.Password) + if password != "password" { + t.Fatalf("Expected password is %q but got: %s", "password", password) + } +} diff --git a/cli/config/testdata/http_config.bad.yml b/cli/config/testdata/http_config.bad.yml new file mode 100644 index 0000000000..1f1f326686 --- /dev/null +++ b/cli/config/testdata/http_config.bad.yml @@ -0,0 +1,2 @@ +authorization: + type: Basic diff --git a/cli/config/testdata/http_config.basic_auth.good.yml b/cli/config/testdata/http_config.basic_auth.good.yml new file mode 100644 index 0000000000..cb3ba6a9e5 --- /dev/null +++ b/cli/config/testdata/http_config.basic_auth.good.yml @@ -0,0 +1,3 @@ +basic_auth: + username: user + password: password diff --git a/cli/config/testdata/http_config.good.yml b/cli/config/testdata/http_config.good.yml new file mode 100644 index 0000000000..21583ef1d5 --- /dev/null +++ b/cli/config/testdata/http_config.good.yml @@ -0,0 +1,4 @@ +authorization: + type: Bearer + credentials: theanswertothegreatquestionoflifetheuniverseandeverythingisfortytwo +proxy_url: "http://remote.host" diff --git a/cli/root.go b/cli/root.go index d987b9b628..95b3510346 100644 --- a/cli/root.go +++ b/cli/root.go @@ -14,9 +14,7 @@ package cli import ( - "crypto/tls" "fmt" - "net/http" "net/url" "os" "path" @@ -40,7 +38,7 @@ var ( alertmanagerURL *url.URL output string timeout time.Duration - tlsConfig *tls.Config + httpConfigFile string versionCheck bool configFiles = []string{os.ExpandEnv("$HOME/.config/amtool/config.yml"), "/etc/amtool/config.yml"} @@ -85,8 +83,8 @@ func NewAlertmanagerClient(amURL *url.URL) *client.Alertmanager { cr := clientruntime.New(address, path.Join(amURL.Path, defaultAmApiv2path), schemes) - cr.Transport = &http.Transport{ - TLSClientConfig: tlsConfig, + if amURL.User != nil && httpConfigFile != "" { + kingpin.Fatalf("basic authentication and http.config.file are mutually exclusive") } if amURL.User != nil { @@ -94,6 +92,20 @@ func NewAlertmanagerClient(amURL *url.URL) *client.Alertmanager { cr.DefaultAuthentication = clientruntime.BasicAuth(amURL.User.Username(), password) } + if httpConfigFile != "" { + var err error + httpConfig, err := config.LoadHTTPConfigFile(httpConfigFile) + if err != nil { + kingpin.Fatalf("failed to load HTTP config file: %v", err) + } + + httpclient, err := promconfig.NewClientFromConfig(*httpConfig, "amtool") + if err != nil { + kingpin.Fatalf("failed to create a new HTTP client: %v", err) + } + cr = clientruntime.NewWithClient(address, path.Join(amURL.Path, defaultAmApiv2path), schemes, httpclient) + } + c := client.New(cr, strfmt.Default) if !versionCheck { @@ -117,7 +129,6 @@ func NewAlertmanagerClient(amURL *url.URL) *client.Alertmanager { func Execute() { var ( app = kingpin.New("amtool", helpRoot).UsageWriter(os.Stdout) - tls = promconfig.TLSConfig{} ) format.InitFormatFlags(app) @@ -126,20 +137,12 @@ func Execute() { app.Flag("alertmanager.url", "Alertmanager to talk to").URLVar(&alertmanagerURL) app.Flag("output", "Output formatter (simple, extended, json)").Short('o').Default("simple").EnumVar(&output, "simple", "extended", "json") app.Flag("timeout", "Timeout for the executed command").Default("30s").DurationVar(&timeout) - app.Flag("tls.certfile", "TLS client certificate file").PlaceHolder("").ExistingFileVar(&tls.CertFile) - app.Flag("tls.keyfile", "TLS client private key file").PlaceHolder("").ExistingFileVar(&tls.KeyFile) - app.Flag("tls.cafile", "TLS trusted certificate authorities file").PlaceHolder("").ExistingFileVar(&tls.CAFile) - app.Flag("tls.servername", "ServerName to verify hostname of alertmanager").PlaceHolder("").StringVar(&tls.ServerName) - app.Flag("tls.insecure.skip.verify", "Skip TLS certificate verification").Default("false").BoolVar(&tls.InsecureSkipVerify) + app.Flag("http.config.file", "HTTP client configuration file for amtool to connect to Alertmanager.").PlaceHolder("").ExistingFileVar(&httpConfigFile) app.Flag("version-check", "Check alertmanager version. Use --no-version-check to disable.").Default("true").BoolVar(&versionCheck) app.Version(version.Print("amtool")) app.GetFlag("help").Short('h') app.UsageTemplate(kingpin.CompactUsageTemplate) - app.PreAction(func(pc *kingpin.ParseContext) (err error) { - tlsConfig, err = promconfig.NewTLSConfig(&tls) - return err - }) resolver, err := config.NewResolver(configFiles, legacyFlags) if err != nil { @@ -191,22 +194,8 @@ static configuration: date.format Sets the output format for dates. Defaults to "2006-01-02 15:04:05 MST" - tls.certfile - TLS client certificate file for mutual-TLS authentication. - Requires tls.keyfile to be useful. - - tls.keyfile - TLS client private key file for mutual-TLS authentication. - Requires tls.certfile to be useful. - - tls.cafile - TLS trusted certificate authorities file. - - tls.servername - ServerName to verify hostname of alertmanager. - - tls.insecure.skip.verify - Skips TLS certificate verification for all HTTPS requests. - Defaults to false. + http.config.file + HTTP client configuration file for amtool to connect to Alertmanager. + The format is https://prometheus.io/docs/alerting/latest/configuration/#http_config. ` )