From 2184ffa0b61917e59fa40c18d94a48afc4603b5e Mon Sep 17 00:00:00 2001 From: hzp Date: Mon, 13 Jan 2025 17:45:04 +0800 Subject: [PATCH] feat: support prometheus web_basic_auth --- embed/examples/cluster/topology.example.yaml | 2 + embed/templates/config/datasource.yml.tpl | 7 ++- embed/templates/config/web.config.yml.tpl | 2 + embed/templates/scripts/run_prometheus.sh.tpl | 3 ++ pkg/cluster/spec/grafana.go | 5 +- pkg/cluster/spec/monitoring.go | 17 ++++++ pkg/cluster/spec/util.go | 54 +++++++++++++------ pkg/cluster/template/config/datasource.go | 5 +- pkg/cluster/template/scripts/monitoring.go | 38 +++++++++++-- 9 files changed, 108 insertions(+), 25 deletions(-) create mode 100644 embed/templates/config/web.config.yml.tpl diff --git a/embed/examples/cluster/topology.example.yaml b/embed/examples/cluster/topology.example.yaml index a3ec4a9299..186f11c9e4 100644 --- a/embed/examples/cluster/topology.example.yaml +++ b/embed/examples/cluster/topology.example.yaml @@ -353,6 +353,8 @@ monitoring_servers: # port: 9090 # # ng-monitoring servive communication port # ng_port: 12020 + # #prometheus web basic_auth_password + # basic_auth_password: admin # # Prometheus deployment file, startup script, configuration file storage directory. # deploy_dir: "/tidb-deploy/prometheus-8249" # # Prometheus data storage directory. diff --git a/embed/templates/config/datasource.yml.tpl b/embed/templates/config/datasource.yml.tpl index b19985b1a1..9e930ebda0 100644 --- a/embed/templates/config/datasource.yml.tpl +++ b/embed/templates/config/datasource.yml.tpl @@ -9,4 +9,9 @@ datasources: tlsAuth: false tlsAuthWithCACert: false version: 1 - editable: true \ No newline at end of file + editable: true +{{- if .AuthPassword}} + basicAuth: true + basicAuthUser: admin + basicAuthPassword: {{.AuthPassword}} +{{- end}} \ No newline at end of file diff --git a/embed/templates/config/web.config.yml.tpl b/embed/templates/config/web.config.yml.tpl new file mode 100644 index 0000000000..d2467472b0 --- /dev/null +++ b/embed/templates/config/web.config.yml.tpl @@ -0,0 +1,2 @@ +basic_auth_users: + admin: {{.BasicAuthPassword}} \ No newline at end of file diff --git a/embed/templates/scripts/run_prometheus.sh.tpl b/embed/templates/scripts/run_prometheus.sh.tpl index 5d83c25b99..795fe80b3c 100644 --- a/embed/templates/scripts/run_prometheus.sh.tpl +++ b/embed/templates/scripts/run_prometheus.sh.tpl @@ -39,6 +39,9 @@ exec numactl --cpunodebind={{.NumaNode}} --membind={{.NumaNode}} bin/prometheus/ exec bin/prometheus/prometheus \ {{- end}} --config.file="{{.DeployDir}}/conf/prometheus.yml" \ +{{- if .BasicAuthPassword}} + --web.config.file="{{.DeployDir}}/conf/web.config.yml" \ +{{- end}} --web.listen-address=":{{.Port}}" \ --web.external-url="{{.WebExternalURL}}/" \ --web.enable-admin-api \ diff --git a/pkg/cluster/spec/grafana.go b/pkg/cluster/spec/grafana.go index 52940915b6..2c60d4fcfb 100644 --- a/pkg/cluster/spec/grafana.go +++ b/pkg/cluster/spec/grafana.go @@ -275,8 +275,9 @@ func (i *GrafanaInstance) InitConfig( } fp = filepath.Join(paths.Cache, fmt.Sprintf("datasource_%s.yml", i.GetHost())) datasourceCfg := &config.DatasourceConfig{ - ClusterName: clusterName, - URL: fmt.Sprintf("http://%s", utils.JoinHostPort(monitors[0].Host, monitors[0].Port)), + ClusterName: clusterName, + URL: fmt.Sprintf("http://%s", utils.JoinHostPort(monitors[0].Host, monitors[0].Port)), + AuthPassword: monitors[0].BasicAuthPassword, } if err := datasourceCfg.ConfigToFile(fp); err != nil { return err diff --git a/pkg/cluster/spec/monitoring.go b/pkg/cluster/spec/monitoring.go index 8627b1c05c..dbcf0b333b 100644 --- a/pkg/cluster/spec/monitoring.go +++ b/pkg/cluster/spec/monitoring.go @@ -44,6 +44,7 @@ type PrometheusSpec struct { Patched bool `yaml:"patched,omitempty"` IgnoreExporter bool `yaml:"ignore_exporter,omitempty"` Port int `yaml:"port" default:"9090"` + BasicAuthPassword string `yaml:"basic_auth_password,omitempty"` NgPort int `yaml:"ng_port,omitempty" validate:"ng_port:editable"` // ng_port is usable since v5.3.0 and default as 12020 since v5.4.0, so the default value is set in spec.go/AdjustByVersion DeployDir string `yaml:"deploy_dir,omitempty"` DataDir string `yaml:"data_dir,omitempty"` @@ -172,9 +173,15 @@ func (c *MonitorComponent) Instances() []Instance { s.DataDir, }, StatusFn: func(_ context.Context, timeout time.Duration, _ *tls.Config, _ ...string) string { + if s.BasicAuthPassword != "" { + return statusByHostWithAuth(s.BasicAuthPassword, s.GetManageHost(), s.Port, "/-/ready", timeout, nil) + } return statusByHost(s.GetManageHost(), s.Port, "/-/ready", timeout, nil) }, UptimeFn: func(_ context.Context, timeout time.Duration, tlsCfg *tls.Config) time.Duration { + if s.BasicAuthPassword != "" { + return UptimeByHostWithAuth(s.BasicAuthPassword, s.GetManageHost(), s.Port, timeout, tlsCfg) + } return UptimeByHost(s.GetManageHost(), s.Port, timeout, tlsCfg) }, Component: c, @@ -226,6 +233,16 @@ func (i *MonitorInstance) InitConfig( AdditionalArgs: spec.AdditionalArgs, } + // transfer web config + srcfp := filepath.Join(paths.Cache, fmt.Sprintf("web.config_%s_%d.yml", i.GetHost(), i.GetPort())) + if err := cfg.WebConfigToFile(srcfp); err != nil { + return err + } + dstfp := filepath.Join(paths.Deploy, "conf", "web.config.yml") + if err := e.Transfer(ctx, srcfp, dstfp, false, 0, false); err != nil { + return err + } + fp := filepath.Join(paths.Cache, fmt.Sprintf("run_prometheus_%s_%d.sh", i.GetHost(), i.GetPort())) if err := cfg.ConfigToFile(fp); err != nil { return err diff --git a/pkg/cluster/spec/util.go b/pkg/cluster/spec/util.go index 26ba27c5ed..19fb9073e8 100644 --- a/pkg/cluster/spec/util.go +++ b/pkg/cluster/spec/util.go @@ -18,6 +18,7 @@ import ( "context" "crypto/tls" "fmt" + "net/url" "path/filepath" "reflect" "strings" @@ -123,46 +124,56 @@ func LoadClientCert(dir string) (*tls.Config, error) { }.ClientConfig() } -// statusByHost queries current status of the instance by http status api. -func statusByHost(host string, port int, path string, timeout time.Duration, tlsCfg *tls.Config) string { +func getStatusByHost(uri string, timeout time.Duration, tlsCfg *tls.Config) string { if timeout < time.Second { timeout = statusQueryTimeout } - - client := utils.NewHTTPClient(timeout, tlsCfg) - scheme := "http" if tlsCfg != nil { scheme = "https" } - if path == "" { - path = "/" - } - url := fmt.Sprintf("%s://%s%s", scheme, utils.JoinHostPort(host, port), path) + + urlStr := fmt.Sprintf("%s://%s", scheme, uri) + client := utils.NewHTTPClient(timeout, tlsCfg) // body doesn't have any status section needed - body, err := client.Get(context.TODO(), url) + body, err := client.Get(context.TODO(), urlStr) if err != nil || body == nil { return "Down" } return "Up" } -// UptimeByHost queries current uptime of the instance by http Prometheus metric api. -func UptimeByHost(host string, port int, timeout time.Duration, tlsCfg *tls.Config) time.Duration { +// statusByHost queries current status of the instance by http status api. +func statusByHost(host string, port int, path string, timeout time.Duration, tlsCfg *tls.Config) string { + if path == "" { + path = "/" + } + uri := fmt.Sprintf("%s%s", utils.JoinHostPort(host, port), path) + + return getStatusByHost(uri, timeout, tlsCfg) +} + +func statusByHostWithAuth(authPassword string, host string, port int, path string, timeout time.Duration, tlsCfg *tls.Config) string { + if path == "" { + path = "/" + } + uri := fmt.Sprintf("admin:%s@%s%s", url.QueryEscape(authPassword), utils.JoinHostPort(host, port), path) + return getStatusByHost(uri, timeout, tlsCfg) +} + +func getUptimeByHost(uri string, timeout time.Duration, tlsCfg *tls.Config) time.Duration { if timeout < time.Second { timeout = statusQueryTimeout } - scheme := "http" if tlsCfg != nil { scheme = "https" } - url := fmt.Sprintf("%s://%s/metrics", scheme, utils.JoinHostPort(host, port)) + urlStr := fmt.Sprintf("%s://%s", scheme, uri) client := utils.NewHTTPClient(timeout, tlsCfg) - - body, err := client.Get(context.TODO(), url) + body, err := client.Get(context.TODO(), urlStr) if err != nil || body == nil { return 0 } @@ -189,6 +200,17 @@ func UptimeByHost(host string, port int, timeout time.Duration, tlsCfg *tls.Conf return 0 } +// UptimeByHost queries current uptime of the instance by http Prometheus metric api. +func UptimeByHost(host string, port int, timeout time.Duration, tlsCfg *tls.Config) time.Duration { + uri := fmt.Sprintf("%s/metrics", utils.JoinHostPort(host, port)) + return getUptimeByHost(uri, timeout, tlsCfg) +} + +func UptimeByHostWithAuth(authPassword string, host string, port int, timeout time.Duration, tlsCfg *tls.Config) time.Duration { + uri := fmt.Sprintf("admin:%s@%s/metrics", url.QueryEscape(authPassword), utils.JoinHostPort(host, port)) + return getUptimeByHost(uri, timeout, tlsCfg) +} + // Abs returns the absolute path func Abs(user, path string) string { // trim whitespaces before joining diff --git a/pkg/cluster/template/config/datasource.go b/pkg/cluster/template/config/datasource.go index 5455f90224..19e6200026 100644 --- a/pkg/cluster/template/config/datasource.go +++ b/pkg/cluster/template/config/datasource.go @@ -24,8 +24,9 @@ import ( // DatasourceConfig represent the data to generate Datasource config type DatasourceConfig struct { - ClusterName string - URL string + ClusterName string + URL string + AuthPassword string } // ConfigToFile write config content to specific path diff --git a/pkg/cluster/template/scripts/monitoring.go b/pkg/cluster/template/scripts/monitoring.go index ac8c73a6f7..9d355db1c9 100644 --- a/pkg/cluster/template/scripts/monitoring.go +++ b/pkg/cluster/template/scripts/monitoring.go @@ -15,19 +15,22 @@ package scripts import ( "bytes" + "os" "path" "text/template" "github.com/pingcap/tiup/embed" "github.com/pingcap/tiup/pkg/utils" + "golang.org/x/crypto/bcrypt" ) // PrometheusScript represent the data to generate Prometheus config type PrometheusScript struct { - Port int - WebExternalURL string - Retention string - EnableNG bool + Port int + WebExternalURL string + BasicAuthPassword string + Retention string + EnableNG bool DeployDir string DataDir string @@ -58,3 +61,30 @@ func (c *PrometheusScript) ConfigToFile(file string) error { return utils.WriteFile(file, content.Bytes(), 0755) } + +func (c *PrometheusScript) WebConfigToFile(file string) error { + fp := path.Join("templates", "config", "web.config.yml.tpl") + tpl, err := embed.ReadTemplate(fp) + if err != nil { + return err + } + tmpl, err := template.New("web.config").Parse(string(tpl)) + if err != nil { + return err + } + + pswHash, err := bcrypt.GenerateFromPassword([]byte(c.BasicAuthPassword), bcrypt.DefaultCost) + if err != nil { + return err + } + tmp := struct { + BasicAuthPassword string + }{BasicAuthPassword: string(pswHash)} + + content := bytes.NewBufferString("") + if err := tmpl.Execute(content, tmp); err != nil { + return err + } + + return os.WriteFile(file, content.Bytes(), 0755) +}