mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-18 16:23:06 +03:00
Compare commits
9 Commits
docs-secur
...
docs/check
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81842a0e91 | ||
|
|
83ef694e9c | ||
|
|
f6830298dc | ||
|
|
f16bcb1355 | ||
|
|
22802101e0 | ||
|
|
00420e16f9 | ||
|
|
6c3c548ddb | ||
|
|
d52de359d5 | ||
|
|
892f4aced2 |
@@ -131,16 +131,13 @@ func (ac *authContext) initFromBasicAuthConfig(ba *BasicAuthConfig) error {
|
||||
if ba.Username == "" {
|
||||
return fmt.Errorf("missing `username` in `basic_auth` section")
|
||||
}
|
||||
if ba.Password != "" {
|
||||
ac.getAuthHeader = func() string {
|
||||
// See https://en.wikipedia.org/wiki/Basic_access_authentication
|
||||
token := ba.Username + ":" + ba.Password
|
||||
token64 := base64.StdEncoding.EncodeToString([]byte(token))
|
||||
return "Basic " + token64
|
||||
}
|
||||
ac.authDigest = fmt.Sprintf("basic(username=%q, password=%q)", ba.Username, ba.Password)
|
||||
return nil
|
||||
ac.getAuthHeader = func() string {
|
||||
// See https://en.wikipedia.org/wiki/Basic_access_authentication
|
||||
token := ba.Username + ":" + ba.Password
|
||||
token64 := base64.StdEncoding.EncodeToString([]byte(token))
|
||||
return "Basic " + token64
|
||||
}
|
||||
ac.authDigest = fmt.Sprintf("basic(username=%q, password=%q)", ba.Username, ba.Password)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ const (
|
||||
vmAddr = "vm-addr"
|
||||
vmUser = "vm-user"
|
||||
vmPassword = "vm-password"
|
||||
vmHeaders = "vm-headers"
|
||||
vmBearerToken = "vm-bearer-token"
|
||||
vmAccountID = "vm-account-id"
|
||||
vmConcurrency = "vm-concurrency"
|
||||
vmCompress = "vm-compress"
|
||||
@@ -112,6 +114,16 @@ var (
|
||||
Usage: "VictoriaMetrics password for basic auth",
|
||||
EnvVars: []string{"VM_PASSWORD"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: vmHeaders,
|
||||
Usage: "Optional HTTP headers to send with each request to the corresponding destination address. \n" +
|
||||
"For example, --vm-headers='My-Auth:foobar' would send 'My-Auth: foobar' HTTP header with every request to the corresponding destination address. \n" +
|
||||
"Multiple headers must be delimited by '^^': --vm-headers='header1:value1^^header2:value2'",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: vmBearerToken,
|
||||
Usage: "Optional bearer auth token to use for the corresponding --vm-addr",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: vmAccountID,
|
||||
Usage: "AccountID is an arbitrary 32-bit integer identifying namespace for data ingestion (aka tenant). \n" +
|
||||
|
||||
@@ -457,7 +457,7 @@ func main() {
|
||||
auth.WithBearer(c.String(vmNativeDstBearerToken)),
|
||||
auth.WithHeaders(c.String(vmNativeDstHeaders)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initialize auth config for destination: %s", dstAddr)
|
||||
return fmt.Errorf("error initialize auth config for destination: %s: %s", dstAddr, err)
|
||||
}
|
||||
|
||||
// create TLS config
|
||||
@@ -596,11 +596,18 @@ func initConfigVM(c *cli.Context) (vm.Config, error) {
|
||||
return vm.Config{}, fmt.Errorf("failed to create backoff object: %w", err)
|
||||
}
|
||||
|
||||
authCfg, err := auth.Generate(
|
||||
auth.WithBasicAuth(c.String(vmUser), c.String(vmPassword)),
|
||||
auth.WithBearer(c.String(vmBearerToken)),
|
||||
auth.WithHeaders(c.String(vmHeaders)))
|
||||
if err != nil {
|
||||
return vm.Config{}, fmt.Errorf("error initialize auth config for destination: %s: %s", addr, err)
|
||||
}
|
||||
|
||||
return vm.Config{
|
||||
Addr: addr,
|
||||
Transport: tr,
|
||||
User: c.String(vmUser),
|
||||
Password: c.String(vmPassword),
|
||||
AuthCfg: authCfg,
|
||||
Concurrency: uint8(c.Int(vmConcurrency)),
|
||||
Compress: c.Bool(vmCompress),
|
||||
AccountID: c.String(vmAccountID),
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmctl/auth"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmctl/backoff"
|
||||
@@ -27,6 +28,8 @@ type Config struct {
|
||||
// --httpListenAddr value for single node version
|
||||
// --httpListenAddr value of vmselect component for cluster version
|
||||
Addr string
|
||||
|
||||
AuthCfg *auth.Config
|
||||
// Transport allows specifying custom http.Transport
|
||||
Transport *http.Transport
|
||||
// Concurrency defines number of worker
|
||||
@@ -40,10 +43,6 @@ type Config struct {
|
||||
// BatchSize defines how many samples
|
||||
// importer collects before sending the import request
|
||||
BatchSize int
|
||||
// User name for basic auth
|
||||
User string
|
||||
// Password for basic auth
|
||||
Password string
|
||||
// SignificantFigures defines the number of significant figures to leave
|
||||
// in metric values before importing.
|
||||
// Zero value saves all the significant decimal places
|
||||
@@ -65,11 +64,10 @@ type Config struct {
|
||||
// see https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-time-series-data
|
||||
type Importer struct {
|
||||
addr string
|
||||
authCfg *auth.Config
|
||||
client *http.Client
|
||||
importPath string
|
||||
compress bool
|
||||
user string
|
||||
password string
|
||||
|
||||
close chan struct{}
|
||||
input chan *TimeSeries
|
||||
@@ -148,8 +146,7 @@ func NewImporter(ctx context.Context, cfg Config) (*Importer, error) {
|
||||
client: client,
|
||||
importPath: importPath,
|
||||
compress: cfg.Compress,
|
||||
user: cfg.User,
|
||||
password: cfg.Password,
|
||||
authCfg: cfg.AuthCfg,
|
||||
rl: limiter.NewLimiter(cfg.RateLimit),
|
||||
close: make(chan struct{}),
|
||||
input: make(chan *TimeSeries, cfg.Concurrency*4),
|
||||
@@ -304,8 +301,8 @@ func (im *Importer) Ping() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create request to %q: %w", im.addr, err)
|
||||
}
|
||||
if im.user != "" {
|
||||
req.SetBasicAuth(im.user, im.password)
|
||||
if im.authCfg != nil {
|
||||
im.authCfg.SetHeaders(req, true)
|
||||
}
|
||||
resp, err := im.client.Do(req)
|
||||
if err != nil {
|
||||
@@ -334,8 +331,8 @@ func (im *Importer) Import(tsBatch []*TimeSeries) error {
|
||||
im.importRequestsErrorsTotal.Inc()
|
||||
return fmt.Errorf("cannot create request to %q: %w", im.addr, err)
|
||||
}
|
||||
if im.user != "" {
|
||||
req.SetBasicAuth(im.user, im.password)
|
||||
if im.authCfg != nil {
|
||||
im.authCfg.SetHeaders(req, true)
|
||||
}
|
||||
if im.compress {
|
||||
req.Header.Set("Content-Encoding", "gzip")
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
nethttputil "net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -29,6 +27,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/vmalertproxy"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -38,7 +37,10 @@ var (
|
||||
resetCacheAuthKey = flagutil.NewPassword("search.resetCacheAuthKey", "Optional authKey for resetting rollup cache via /internal/resetRollupResultCache call. It could be passed via authKey query arg. It overrides -httpAuth.*")
|
||||
logSlowQueryDuration = flag.Duration("search.logSlowQueryDuration", 5*time.Second, "Log queries with execution time exceeding this value. Zero disables slow query logging. "+
|
||||
"See also -search.logQueryMemoryUsage")
|
||||
vmalertProxyURL = flag.String("vmalert.proxyURL", "", "Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules")
|
||||
|
||||
vmalertProxyURL = flag.String("vmalert.proxyURL", "", "Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , "+
|
||||
"then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules . "+
|
||||
"See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmalert")
|
||||
)
|
||||
|
||||
var slowQueries = metrics.NewCounter(`vm_slow_queries_total`)
|
||||
@@ -55,8 +57,8 @@ func Init(vmselectMaxConcurrentRequests int, vmselectMaxQueueDuration time.Durat
|
||||
concurrencyLimitCh = make(chan struct{}, maxConcurrentRequests)
|
||||
|
||||
initVMUIConfig()
|
||||
initVMAlertProxy()
|
||||
|
||||
vmalertproxy.Init(*vmalertProxyURL)
|
||||
flagutil.RegisterSecretFlag("vmalert.proxyURL")
|
||||
}
|
||||
|
||||
@@ -514,10 +516,11 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path
|
||||
if len(*vmalertProxyURL) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, "%s", `{"status":"error","msg":"for accessing vmalert flag '-vmalert.proxyURL' must be configured"}`)
|
||||
fmt.Fprintf(w, "%s", `{"status":"error","msg":"the '-vmalert.proxyURL' command-line must be configured; `+
|
||||
`see https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmalert"}`)
|
||||
return true
|
||||
}
|
||||
proxyVMAlertRequests(w, r, path)
|
||||
vmalertproxy.HandleRequest(w, r, path)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -555,7 +558,7 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path
|
||||
case "/api/v1/rules", "/rules":
|
||||
rulesRequests.Inc()
|
||||
if len(*vmalertProxyURL) > 0 {
|
||||
proxyVMAlertRequests(w, r, path)
|
||||
vmalertproxy.HandleRequest(w, r, path)
|
||||
return true
|
||||
}
|
||||
// Return dumb placeholder for https://prometheus.io/docs/prometheus/latest/querying/api/#rules
|
||||
@@ -565,7 +568,7 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path
|
||||
case "/api/v1/alerts", "/alerts":
|
||||
alertsRequests.Inc()
|
||||
if len(*vmalertProxyURL) > 0 {
|
||||
proxyVMAlertRequests(w, r, path)
|
||||
vmalertproxy.HandleRequest(w, r, path)
|
||||
return true
|
||||
}
|
||||
// Return dumb placeholder for https://prometheus.io/docs/prometheus/latest/querying/api/#alerts
|
||||
@@ -575,7 +578,7 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path
|
||||
case "/api/v1/notifiers", "/notifiers":
|
||||
notifiersRequests.Inc()
|
||||
if len(*vmalertProxyURL) > 0 {
|
||||
proxyVMAlertRequests(w, r, path)
|
||||
vmalertproxy.HandleRequest(w, r, path)
|
||||
return true
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -722,48 +725,7 @@ var (
|
||||
metricNamesStatsResetErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/api/v1/admin/status/metric_names_stats/reset"}`)
|
||||
)
|
||||
|
||||
func proxyVMAlertRequests(w http.ResponseWriter, r *http.Request, path string) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil || err == http.ErrAbortHandler {
|
||||
// Suppress http.ErrAbortHandler panic.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1353
|
||||
return
|
||||
}
|
||||
// Forward other panics to the caller.
|
||||
panic(err)
|
||||
}()
|
||||
req := r.Clone(r.Context())
|
||||
req.URL.Path = strings.TrimPrefix(path, "prometheus")
|
||||
req.Host = vmalertProxyHost
|
||||
|
||||
if strings.HasPrefix(r.Header.Get(`User-Agent`), `Grafana`) {
|
||||
// Grafana currently supports only Prometheus-style alerts. If other alert types
|
||||
// (e.g. logs or traces) are returned, it may fail with "Error loading alerts".
|
||||
//
|
||||
// Grafana queries the vmalert API directly, bypassing the VictoriaMetrics datasource,
|
||||
// so query params (such as datasource_type) cannot be enforced on the Grafana side.
|
||||
//
|
||||
// To ensure compatibility, we detect Grafana requests via the User-Agent and enforce
|
||||
// `datasource_type=prometheus`.
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/VictoriaMetrics/victoriametrics-datasource/issues/329#issuecomment-3847585443
|
||||
// - https://github.com/VictoriaMetrics/victoriametrics-datasource/issues/59
|
||||
q := req.URL.Query()
|
||||
q.Set("datasource_type", "prometheus")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
req.RequestURI = ""
|
||||
}
|
||||
|
||||
vmalertProxy.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
var (
|
||||
vmalertProxyHost string
|
||||
vmalertProxy *nethttputil.ReverseProxy
|
||||
vmuiConfig string
|
||||
)
|
||||
var vmuiConfig string
|
||||
|
||||
func initVMUIConfig() {
|
||||
var cfg struct {
|
||||
@@ -795,16 +757,3 @@ func initVMUIConfig() {
|
||||
}
|
||||
vmuiConfig = string(data)
|
||||
}
|
||||
|
||||
// initVMAlertProxy must be called after flag.Parse(), since it uses command-line flags.
|
||||
func initVMAlertProxy() {
|
||||
if len(*vmalertProxyURL) == 0 {
|
||||
return
|
||||
}
|
||||
proxyURL, err := url.Parse(*vmalertProxyURL)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot parse -vmalert.proxyURL=%q: %s", *vmalertProxyURL, err)
|
||||
}
|
||||
vmalertProxyHost = proxyURL.Host
|
||||
vmalertProxy = nethttputil.NewSingleHostReverseProxy(proxyURL)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
@@ -525,6 +526,7 @@ func DeleteHandler(startTime time.Time, r *http.Request) error {
|
||||
if deletedCount > 0 {
|
||||
promql.ResetRollupResultCache()
|
||||
}
|
||||
logger.Infof("/api/v1/admin/tsdb/delete_series has been called for %q. Deleted %d series.", sq.FiltersString(), deletedCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,17 @@ docs-image:
|
||||
--platform $(DOCKER_PLATFORM) \
|
||||
vmdocs
|
||||
|
||||
docs-check-links: docs-image
|
||||
rm -rf vmdocs/public
|
||||
docker run \
|
||||
--rm \
|
||||
--platform $(DOCKER_PLATFORM) \
|
||||
-v ./vmdocs:/opt/docs \
|
||||
$(shell for d in ./docs/*/; do printf ' -v %s:/opt/docs/content/%s' "$${d}" "$$(basename $${d})"; done) \
|
||||
--entrypoint /bin/sh \
|
||||
vmdocs-docker-package \
|
||||
-c "yarn install && hugo --minify && yarn run check-links"
|
||||
|
||||
docs-debug: docs docs-image
|
||||
docker run \
|
||||
--rm \
|
||||
|
||||
@@ -1712,21 +1712,30 @@ The following versions of VictoriaMetrics receive regular security fixes:
|
||||
| [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-releases/) | ✅ |
|
||||
| other releases | ❌ |
|
||||
|
||||
### Software Bill of Materials (SBOM)
|
||||
|
||||
Every VictoriaMetrics container{{% available_from "v1.137.0" %}} image published to
|
||||
[Docker Hub](https://hub.docker.com/u/victoriametrics) and [Quay.io](https://quay.io/organization/victoriametrics) include an [SPDX](https://spdx.dev/) SBOM attestation generated automatically by BuildKit during `docker buildx build`.
|
||||
|
||||
To inspect the SBOM for an image:
|
||||
|
||||
```sh
|
||||
docker buildx imagetools inspect \
|
||||
docker.io/victoriametrics/victoria-metrics:latest \
|
||||
--format "{{ json .SBOM }}"
|
||||
```
|
||||
|
||||
To scan an image using its SBOM attestation with [Trivy](https://github.com/aquasecurity/trivy):
|
||||
|
||||
```sh
|
||||
trivy image --sbom-sources oci \
|
||||
docker.io/victoriametrics/victoria-metrics:latest
|
||||
```
|
||||
|
||||
### Reporting a Vulnerability
|
||||
|
||||
Please report any security issues to <security@victoriametrics.com>
|
||||
|
||||
### CVE handling policy
|
||||
|
||||
**Source code:** Go dependencies are scanned by [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) in CI.
|
||||
All vulnerabilities must be fixed before next scheduled release and backported to [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-releases/).
|
||||
|
||||
**Docker images:** CVE findings in [Alpine](https://security.alpinelinux.org/) base image pose minimal risk since VictoriaMetrics binaries are statically compiled with no OS dependencies.
|
||||
When detected, only the Alpine base tag is updated.
|
||||
Releases proceed as planned even if upstream fixes are not yet available.
|
||||
For maximum security, hardened [scratch](https://hub.docker.com/_/scratch)-based images are also provided.
|
||||
All images are continuously scanned by Docker Hub and verified before release using [grype](https://github.com/anchore/grype).
|
||||
|
||||
### General security recommendations:
|
||||
|
||||
* All the VictoriaMetrics components must run in protected private networks without direct access from untrusted networks such as Internet.
|
||||
@@ -1740,24 +1749,14 @@ All images are continuously scanned by Docker Hub and verified before release us
|
||||
* Set reasonable [`Content-Security-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) header value to mitigate [XSS attacks](https://en.wikipedia.org/wiki/Cross-site_scripting). See `-http.header.csp` flag.
|
||||
* Set reasonable [`X-Frame-Options`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) header value to mitigate [clickjacking attacks](https://en.wikipedia.org/wiki/Clickjacking), for example `DENY`. See `-http.header.frameOptions` flag.
|
||||
|
||||
There are the following security-related command-line flags for all components with HTTP API:
|
||||
VictoriaMetrics provides the following security-related command-line flags:
|
||||
|
||||
* `-tls`, `-tlsCertFile` and `-tlsKeyFile` for switching from HTTP to HTTPS at `-httpListenAddr`.
|
||||
* `-tls`, `-tlsCertFile` and `-tlsKeyFile` for switching from HTTP to HTTPS at `-httpListenAddr` (TCP port 8428 is listened by default).
|
||||
[Enterprise version of VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/enterprise/) supports automatic issuing of TLS certificates.
|
||||
See [these docs](#automatic-issuing-of-tls-certificates).
|
||||
* `-mtls` and `-mtlsCAFile` for enabling [mTLS](https://en.wikipedia.org/wiki/Mutual_authentication) for requests to `-httpListenAddr`. See [these docs](#mtls-protection).
|
||||
* `-httpAuth.username` and `-httpAuth.password` for protecting all the HTTP endpoints
|
||||
with [HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
|
||||
* `-http.header.hsts`, `-http.header.csp`, and `-http.header.frameOptions` for serving `Strict-Transport-Security`, `Content-Security-Policy`
|
||||
and `X-Frame-Options` HTTP response headers.
|
||||
|
||||
### Protecting service endpoints
|
||||
|
||||
All VictoriaMetrics components expose internal metrics in Prometheus exposition format at `/metrics` page for [#Monitoring](https://docs.victoriametrics.com/victoriametrics/#monitoring).
|
||||
Consider limiting access to `/metrics` page to trusted networks only.
|
||||
|
||||
There are other service endpoints that might require protection:
|
||||
|
||||
* `-deleteAuthKey` for protecting the `/api/v1/admin/tsdb/delete_series` endpoint. See [how to delete time series](#how-to-delete-time-series).
|
||||
* `-snapshotAuthKey` for protecting the `/snapshot*` endpoints. See [how to work with snapshots](#how-to-work-with-snapshots).
|
||||
* `-forceFlushAuthKey` for protecting the `/internal/force_flush` endpoint. See [force flush docs](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#forced-flush).
|
||||
@@ -1769,6 +1768,8 @@ There are other service endpoints that might require protection:
|
||||
* `-pprofAuthKey` for protecting the `/debug/pprof/*` endpoints, which can be used for [profiling](#profiling).
|
||||
* `-metricNamesStatsResetAuthKey` for protecting the `/api/v1/admin/status/metric_names_stats/reset` endpoint, used for [Metric Names Tracker](#track-ingested-metrics-usage).
|
||||
* `-denyQueryTracing` for disallowing [query tracing](#query-tracing).
|
||||
* `-http.header.hsts`, `-http.header.csp`, and `-http.header.frameOptions` for serving `Strict-Transport-Security`, `Content-Security-Policy`
|
||||
and `X-Frame-Options` HTTP response headers.
|
||||
|
||||
Explicitly set internal network interface for TCP and UDP ports for data ingestion with Graphite and OpenTSDB formats.
|
||||
For example, substitute `-graphiteListenAddr=:2003` with `-graphiteListenAddr=<internal_iface_ip>:2003`. This protects from unexpected requests from untrusted network interfaces.
|
||||
@@ -1776,6 +1777,17 @@ For example, substitute `-graphiteListenAddr=:2003` with `-graphiteListenAddr=<i
|
||||
See also [security recommendation for VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#security)
|
||||
and [the general security page at VictoriaMetrics website](https://victoriametrics.com/security/).
|
||||
|
||||
### CVE handling policy
|
||||
|
||||
**Source code:** Go dependencies are scanned by [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) in CI.
|
||||
All vulnerabilities must be fixed before next scheduled release and backported to [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-releases/).
|
||||
|
||||
**Docker images:** CVE findings in [Alpine](https://security.alpinelinux.org/) base image pose minimal risk since VictoriaMetrics binaries are statically compiled with no OS dependencies.
|
||||
When detected, only the Alpine base tag is updated.
|
||||
Releases proceed as planned even if upstream fixes are not yet available.
|
||||
For maximum security, hardened [scratch](https://hub.docker.com/_/scratch)-based images are also provided.
|
||||
All images are continuously scanned by Docker Hub and verified before release using [grype](https://github.com/anchore/grype).
|
||||
|
||||
### mTLS protection
|
||||
|
||||
By default `VictoriaMetrics` accepts http requests at `8428` port (this port can be changed via `-httpListenAddr` command-line flags).
|
||||
@@ -1805,26 +1817,6 @@ This functionality can be evaluated for free according to [these docs](https://d
|
||||
|
||||
See also [security recommendations](#security).
|
||||
|
||||
### Software Bill of Materials (SBOM)
|
||||
|
||||
Every VictoriaMetrics container{{% available_from "v1.137.0" %}} image published to
|
||||
[Docker Hub](https://hub.docker.com/u/victoriametrics) and [Quay.io](https://quay.io/organization/victoriametrics) include an [SPDX](https://spdx.dev/) SBOM attestation generated automatically by BuildKit during `docker buildx build`.
|
||||
|
||||
To inspect the SBOM for an image:
|
||||
|
||||
```sh
|
||||
docker buildx imagetools inspect \
|
||||
docker.io/victoriametrics/victoria-metrics:latest \
|
||||
--format "{{ json .SBOM }}"
|
||||
```
|
||||
|
||||
To scan an image using its SBOM attestation with [Trivy](https://github.com/aquasecurity/trivy):
|
||||
|
||||
```sh
|
||||
trivy image --sbom-sources oci \
|
||||
docker.io/victoriametrics/victoria-metrics:latest
|
||||
```
|
||||
|
||||
## Tuning
|
||||
|
||||
* No need in tuning for VictoriaMetrics - it uses reasonable defaults for command-line flags,
|
||||
|
||||
@@ -26,7 +26,12 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
|
||||
|
||||
## tip
|
||||
|
||||
* BUGFIX: [stream aggregation](https://docs.victoriametrics.com/victoriametrics/stream-aggregation/): fix issue with producing aggregated samples with identical timestamps between flushes. See PR [#10808](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10808) for details.
|
||||
* FEATURE: all VictoriaMetrics components: add `-http.header.disableServerHostname` command-line flag for disabling the `X-Server-Hostname` HTTP response header. See [#11067](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11067). Thanks to @zasdaym for contribution.
|
||||
* FEATURE: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): log calls to [/api/v1/admin/tsdb/delete_series](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1admintsdbdelete_series) API handler. This should help to identify events of metrics deletion from the database. See [#11104](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11104).
|
||||
* FEATURE: [vmctl](https://docs.victoriametrics.com/victoriametrics/vmctl/): add `-vm-headers` and `-vm-bearer-token` flags for authenticating requests to the VictoriaMetrics import destination. The flags are available in `opentsdb`, `influx`, `remote-read`, `prometheus`, `mimir`, and `thanos` vmctl sub-commands. See [#8897](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8897).
|
||||
|
||||
* BUGFIX: [stream aggregation](https://docs.victoriametrics.com/victoriametrics/stream-aggregation/): fix issue with producing aggregated samples with identical timestamps between flushes. See [#10808](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10808).
|
||||
* BUGFIX: [vmbackup](https://docs.victoriametrics.com/vmbackup/), [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/): do not fail backup list if directory is absent while using `fs://` destination to align with other protocols. See [6c3c548](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/6c3c548ddb0385b749e731f52276f130e2a4e4a8)
|
||||
|
||||
## [v1.145.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.145.0)
|
||||
|
||||
|
||||
@@ -95,6 +95,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
@@ -621,7 +623,7 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/
|
||||
-version
|
||||
Show VictoriaMetrics version
|
||||
-vmalert.proxyURL string
|
||||
Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules
|
||||
Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules . See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmalert
|
||||
-vmui.customDashboardsPath string
|
||||
Optional path to vmui dashboards. See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards
|
||||
-vmui.defaultTimezone string
|
||||
|
||||
@@ -74,6 +74,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/vmagent/ .
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
|
||||
@@ -115,6 +115,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/vmalert/ .
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
|
||||
@@ -59,6 +59,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/vmauth/ .
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
|
||||
@@ -400,6 +400,8 @@ Run `vmbackup -help` in order to see all the available options:
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
|
||||
@@ -575,6 +575,8 @@ command-line flags:
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
|
||||
@@ -496,6 +496,8 @@ Below is the list of configuration flags (it can be viewed by running `./vmgatew
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
|
||||
@@ -76,6 +76,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/cluster-victori
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
|
||||
@@ -102,6 +102,8 @@ Run `vmrestore -help` in order to see all the available options:
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
|
||||
@@ -75,6 +75,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/cluster-victori
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
@@ -323,7 +325,7 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/cluster-victori
|
||||
-version
|
||||
Show VictoriaMetrics version
|
||||
-vmalert.proxyURL string
|
||||
Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules
|
||||
Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules . See https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#vmalert
|
||||
-vmstorageDialTimeout duration
|
||||
Timeout for establishing RPC connections from vmselect to vmstorage. See also -vmstorageUserTimeout (default 3s)
|
||||
-vmstorageUserTimeout duration
|
||||
|
||||
@@ -68,6 +68,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/cluster-victori
|
||||
Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth
|
||||
-http.header.csp string
|
||||
Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"
|
||||
-http.header.disableServerHostname
|
||||
Whether to disable 'X-Server-Hostname' header in HTTP responses
|
||||
-http.header.frameOptions string
|
||||
Value for 'X-Frame-Options' header
|
||||
-http.header.hsts string
|
||||
|
||||
@@ -64,9 +64,10 @@ var (
|
||||
connTimeout = flag.Duration("http.connTimeout", 2*time.Minute, "Incoming connections to -httpListenAddr are closed after the configured timeout. "+
|
||||
"This may help evenly spreading load among a cluster of services behind TCP-level load balancer. Zero value disables closing of incoming connections")
|
||||
|
||||
headerHSTS = flag.String("http.header.hsts", "", "Value for 'Strict-Transport-Security' header, recommended: 'max-age=31536000; includeSubDomains'")
|
||||
headerFrameOptions = flag.String("http.header.frameOptions", "", "Value for 'X-Frame-Options' header")
|
||||
headerCSP = flag.String("http.header.csp", "", `Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"`)
|
||||
headerHSTS = flag.String("http.header.hsts", "", "Value for 'Strict-Transport-Security' header, recommended: 'max-age=31536000; includeSubDomains'")
|
||||
headerFrameOptions = flag.String("http.header.frameOptions", "", "Value for 'X-Frame-Options' header")
|
||||
headerCSP = flag.String("http.header.csp", "", `Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"`)
|
||||
headerDisableServerHostname = flag.Bool("http.header.disableServerHostname", false, "Whether to disable 'X-Server-Hostname' header in HTTP responses")
|
||||
|
||||
disableCORS = flag.Bool("http.disableCORS", false, `Disable CORS for all origins (*)`)
|
||||
)
|
||||
@@ -329,7 +330,9 @@ func handlerWrapper(w http.ResponseWriter, r *http.Request, rh RequestHandler) {
|
||||
if *headerCSP != "" {
|
||||
h.Add("Content-Security-Policy", *headerCSP)
|
||||
}
|
||||
h.Add("X-Server-Hostname", hostname)
|
||||
if !*headerDisableServerHostname {
|
||||
h.Add("X-Server-Hostname", hostname)
|
||||
}
|
||||
requestsTotal.Inc()
|
||||
if whetherToCloseConn(r) {
|
||||
connTimeoutClosedConns.Inc()
|
||||
|
||||
@@ -228,4 +228,30 @@ func TestHandlerWrapper(t *testing.T) {
|
||||
if got := h.Get("Content-Security-Policy"); got != cspHeader {
|
||||
t.Fatalf("unexpected CSP header; got %q; want %q", got, cspHeader)
|
||||
}
|
||||
if got := h.Get("X-Server-Hostname"); got != hostname {
|
||||
t.Fatalf("unexpected X-Server-Hostname header; got %q; want %q", got, hostname)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerWrapperDisableServerHostnameHeader(t *testing.T) {
|
||||
origDisableServerHostname := *headerDisableServerHostname
|
||||
*headerDisableServerHostname = true
|
||||
defer func() {
|
||||
*headerDisableServerHostname = origDisableServerHostname
|
||||
}()
|
||||
|
||||
req, _ := http.NewRequest("GET", "/health", nil)
|
||||
|
||||
srv := &server{s: &http.Server{}}
|
||||
w := &httptest.ResponseRecorder{}
|
||||
|
||||
handlerWrapper(w, req, func(w http.ResponseWriter, r *http.Request) bool {
|
||||
return builtinRoutesHandler(srv, r, w, func(_ http.ResponseWriter, _ *http.Request) bool {
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
if got := w.Header().Get("X-Server-Hostname"); got != "" {
|
||||
t.Fatalf("unexpected X-Server-Hostname header; got %q; want empty value", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ func TestGetTimeSuccess(t *testing.T) {
|
||||
f("292277025-08-18T07:12:54.999999999Z", maxTimeMsecs)
|
||||
f("1562529662.324", 1562529662324)
|
||||
f("1223372036.855", 1223372036855)
|
||||
|
||||
// relative duration that resolves to a timestamp before 1970
|
||||
f("-9223372036.854", minTimeMsecs)
|
||||
}
|
||||
|
||||
func TestGetTimeError(t *testing.T) {
|
||||
@@ -63,8 +66,8 @@ func TestGetTimeError(t *testing.T) {
|
||||
t.Fatalf("unexpected error in NewRequest: %s", err)
|
||||
}
|
||||
|
||||
if _, err := GetTime(r, "s", 123); err == nil {
|
||||
t.Fatalf("expecting non-nil error in GetTime(%q)", s)
|
||||
if msec, err := GetTime(r, "s", 123); err == nil {
|
||||
t.Fatalf("expecting non-nil error in GetTime(%q); got %d", s, msec)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +87,6 @@ func TestGetTimeError(t *testing.T) {
|
||||
f("123md")
|
||||
f("-12.3md")
|
||||
|
||||
// relative duration that resolves to a timestamp before 1970
|
||||
f("-9223372036.854")
|
||||
// relative duration outside the allowed range
|
||||
f("-9223372036.855")
|
||||
}
|
||||
|
||||
@@ -468,15 +468,21 @@ func (tf *TagFilter) Unmarshal(src []byte) ([]byte, error) {
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// String returns string representation of the search query.
|
||||
// String returns string representation of the search query: tag filters and time range.
|
||||
func (sq *SearchQuery) String() string {
|
||||
start := TimestampToHumanReadableFormat(sq.MinTimestamp)
|
||||
end := TimestampToHumanReadableFormat(sq.MaxTimestamp)
|
||||
a := sq.FiltersString()
|
||||
return fmt.Sprintf("filters=%s, timeRange=[%s..%s]", a, start, end)
|
||||
}
|
||||
|
||||
// FiltersString returns string representation of the tag filters.
|
||||
func (sq *SearchQuery) FiltersString() []string {
|
||||
a := make([]string, len(sq.TagFilterss))
|
||||
for i, tfs := range sq.TagFilterss {
|
||||
a[i] = tagFiltersToString(tfs)
|
||||
}
|
||||
start := TimestampToHumanReadableFormat(sq.MinTimestamp)
|
||||
end := TimestampToHumanReadableFormat(sq.MaxTimestamp)
|
||||
return fmt.Sprintf("filters=%s, timeRange=[%s..%s]", a, start, end)
|
||||
return a
|
||||
}
|
||||
|
||||
func tagFiltersToString(tfs []TagFilter) string {
|
||||
|
||||
@@ -19,7 +19,7 @@ func ParseDuration(s string) (time.Duration, error) {
|
||||
return 0, err
|
||||
}
|
||||
if ms < minValidMilli || maxValidMilli < ms {
|
||||
return 0, fmt.Errorf("duration %q must be in the range [%v, %v]", s, minDuration, maxDuration)
|
||||
return 0, fmt.Errorf("duration %q must be in the range [%s, %s]", s, minDuration, maxDuration)
|
||||
}
|
||||
return time.Duration(ms) * time.Millisecond, nil
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ func ParseTimeMsec(s string) (int64, error) {
|
||||
// See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#timestamp-formats
|
||||
//
|
||||
// If s doesn't contain timezone information, then the local timezone is used.
|
||||
// The time must be in the range [1970-01-01T00:00:00Z, 2262-04-11T23:47:16Z].
|
||||
//
|
||||
// It returns unix timestamp in nanoseconds.
|
||||
func ParseTimeAt(s string, currentTimestamp int64) (int64, error) {
|
||||
@@ -71,14 +70,10 @@ func ParseTimeAt(s string, currentTimestamp int64) (int64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if d > 0 {
|
||||
if d < 0 {
|
||||
d = -d
|
||||
}
|
||||
nsec := currentTimestamp + int64(d)
|
||||
if nsec < 0 {
|
||||
return 0, fmt.Errorf("time %s (%v) must be in the range [%v, %v]", sOrig, time.Unix(0, nsec).UTC(), minTime, maxTime)
|
||||
}
|
||||
return nsec, nil
|
||||
return subInt64NoOverflow(currentTimestamp, int64(d)), nil
|
||||
}
|
||||
if len(s) == 4 {
|
||||
// Parse YYYY
|
||||
@@ -115,22 +110,28 @@ func ParseTimeAt(s string, currentTimestamp int64) (int64, error) {
|
||||
return parseTimeAt(time.RFC3339, sOrig, 0, sOrig)
|
||||
}
|
||||
|
||||
var (
|
||||
minTime = time.Unix(0, 0).UTC()
|
||||
maxTime = time.Unix(0, math.MaxInt64).UTC()
|
||||
)
|
||||
|
||||
func parseTimeAt(layout, value string, tzOffsetNanos int64, sOrig string) (int64, error) {
|
||||
func parseTimeAt(layout, value string, tzOffsetNsec int64, sOrig string) (int64, error) {
|
||||
t, err := time.Parse(layout, value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
tzOffset := time.Duration(tzOffsetNanos)
|
||||
t = t.UTC().Add(tzOffset)
|
||||
if t.Before(minTime) || t.After(maxTime) {
|
||||
return 0, fmt.Errorf("time %s (%v) must be in the range [%v, %v]", sOrig, t, minTime, maxTime)
|
||||
nsec := t.UnixNano()
|
||||
|
||||
return subInt64NoOverflow(nsec, -tzOffsetNsec), nil
|
||||
}
|
||||
|
||||
func subInt64NoOverflow(a, b int64) int64 {
|
||||
if b >= 0 {
|
||||
if a < math.MinInt64+b {
|
||||
return math.MinInt64
|
||||
}
|
||||
return a - b
|
||||
}
|
||||
return t.UnixNano(), nil
|
||||
|
||||
if a > math.MaxInt64+b {
|
||||
return math.MaxInt64
|
||||
}
|
||||
return a - b
|
||||
}
|
||||
|
||||
// TryParseUnixTimestamp parses s as unix timestamp in seconds, milliseconds, microseconds or nanoseconds and returns the parsed timestamp in nanoseconds.
|
||||
|
||||
@@ -210,6 +210,7 @@ func TestParseTimeAtLimits(t *testing.T) {
|
||||
|
||||
f := func(s string, wantTime time.Time) {
|
||||
t.Helper()
|
||||
|
||||
got, err := ParseTimeAt(s, now.UnixNano())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
@@ -231,42 +232,38 @@ func TestParseTimeAtLimits(t *testing.T) {
|
||||
west := location(t, "Etc/GMT+12") // UTC-12:00
|
||||
var s string
|
||||
|
||||
// min timestamp
|
||||
f("0", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
s = fmt.Sprintf("-%d", now.Unix())
|
||||
f(s, time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
s = fmt.Sprintf("now-%d", now.Unix())
|
||||
f(s, time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
|
||||
// min year
|
||||
f("1970Z", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
f("1971+14:00", time.Date(1971, 1, 1, 0, 0, 0, 0, east))
|
||||
f("1970-12:00", time.Date(1970, 1, 1, 0, 0, 0, 0, west))
|
||||
f("1678Z", time.Date(1678, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
f("1678+14:00", time.Date(1678, 1, 1, 0, 0, 0, 0, east))
|
||||
f("1678-12:00", time.Date(1678, 1, 1, 0, 0, 0, 0, west))
|
||||
|
||||
// min month
|
||||
f("1970-01Z", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
f("1970-02+14:00", time.Date(1970, 2, 1, 0, 0, 0, 0, east))
|
||||
f("1970-01-12:00", time.Date(1970, 1, 1, 0, 0, 0, 0, west))
|
||||
f("1677-10Z", time.Date(1677, 10, 1, 0, 0, 0, 0, time.UTC))
|
||||
f("1677-10+14:00", time.Date(1677, 10, 1, 0, 0, 0, 0, east))
|
||||
f("1677-10-12:00", time.Date(1677, 10, 1, 0, 0, 0, 0, west))
|
||||
|
||||
// min day
|
||||
f("1970-01-01Z", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
f("1970-01-02+14:00", time.Date(1970, 1, 2, 0, 0, 0, 0, east))
|
||||
f("1970-01-01-12:00", time.Date(1970, 1, 1, 0, 0, 0, 0, west))
|
||||
f("1677-09-22Z", time.Date(1677, 9, 22, 0, 0, 0, 0, time.UTC))
|
||||
f("1677-09-22+14:00", time.Date(1677, 9, 22, 0, 0, 0, 0, east))
|
||||
f("1677-09-22-12:00", time.Date(1677, 9, 22, 0, 0, 0, 0, west))
|
||||
|
||||
// min hour
|
||||
f("1970-01-01T00Z", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
f("1970-01-01T14+14:00", time.Date(1970, 1, 1, 14, 0, 0, 0, east))
|
||||
f("1969-12-31T12-12:00", time.Date(1969, 12, 31, 12, 0, 0, 0, west))
|
||||
f("1677-09-21T01Z", time.Date(1677, 9, 21, 1, 0, 0, 0, time.UTC))
|
||||
f("1677-09-21T15+14:00", time.Date(1677, 9, 21, 15, 0, 0, 0, east))
|
||||
f("1677-09-21T01+14:00", time.Unix(0, math.MinInt64))
|
||||
f("1677-09-21T01-12:00", time.Date(1677, 9, 21, 1, 0, 0, 0, west))
|
||||
|
||||
// min minute
|
||||
f("1970-01-01T00:00Z", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
f("1970-01-01T14:00+14:00", time.Date(1970, 1, 1, 14, 0, 0, 0, east))
|
||||
f("1969-12-31T12:00-12:00", time.Date(1969, 12, 31, 12, 0, 0, 0, west))
|
||||
f("1677-09-21T00:12Z", time.Date(1677, 9, 21, 0, 12, 0, 0, time.UTC))
|
||||
f("1677-09-21T15:12Z+14:00", time.Date(1677, 9, 21, 15, 12, 0, 0, east))
|
||||
f("1677-09-21T00:13Z+14:00", time.Unix(0, math.MinInt64))
|
||||
f("1677-09-21T00:13Z-12:00", time.Date(1677, 9, 21, 0, 13, 0, 0, west))
|
||||
|
||||
// min second
|
||||
f("1970-01-01T00:00:00Z", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
f("1970-01-01T14:00:00+14:00", time.Date(1970, 1, 1, 14, 0, 0, 0, east))
|
||||
f("1969-12-31T12:00:00-12:00", time.Date(1969, 12, 31, 12, 0, 0, 0, west))
|
||||
f("1677-09-21T00:12:43Z", time.Date(1677, 9, 21, 0, 12, 43, 0, time.UTC))
|
||||
f("1677-09-21T15:12:43Z+14:00", time.Date(1677, 9, 21, 15, 12, 43, 0, east))
|
||||
f("1677-09-21T00:12:44Z+14:00", time.Unix(0, math.MinInt64))
|
||||
f("1677-09-21T00:12:44Z-12:00", time.Date(1677, 9, 21, 0, 12, 44, 0, west))
|
||||
|
||||
// max year
|
||||
f("2262Z", time.Date(2262, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
@@ -280,23 +277,26 @@ func TestParseTimeAtLimits(t *testing.T) {
|
||||
|
||||
// max day
|
||||
f("2262-04-11Z", time.Date(2262, 4, 11, 0, 0, 0, 0, time.UTC))
|
||||
f("2262-04-12+14:00", time.Date(2262, 4, 12, 0, 0, 0, 0, east))
|
||||
f("2262-04-11+14:00", time.Date(2262, 4, 11, 0, 0, 0, 0, east))
|
||||
f("2262-04-11-12:00", time.Date(2262, 4, 11, 0, 0, 0, 0, west))
|
||||
|
||||
// max hour
|
||||
f("2262-04-11T23Z", time.Date(2262, 4, 11, 23, 0, 0, 0, time.UTC))
|
||||
f("2262-04-12T13+14:00", time.Date(2262, 4, 12, 13, 0, 0, 0, east))
|
||||
f("2262-04-11T23+14:00", time.Date(2262, 4, 11, 23, 0, 0, 0, east))
|
||||
f("2262-04-11T11-12:00", time.Date(2262, 4, 11, 11, 0, 0, 0, west))
|
||||
f("2262-04-11T23-12:00", time.Unix(0, math.MaxInt64))
|
||||
|
||||
// max minute
|
||||
f("2262-04-11T23:47Z", time.Date(2262, 4, 11, 23, 47, 0, 0, time.UTC))
|
||||
f("2262-04-12T13:47+14:00", time.Date(2262, 4, 12, 13, 47, 0, 0, east))
|
||||
f("2262-04-11T23:47+14:00", time.Date(2262, 4, 11, 23, 47, 0, 0, east))
|
||||
f("2262-04-11T11:47-12:00", time.Date(2262, 4, 11, 11, 47, 0, 0, west))
|
||||
f("2262-04-11T23:47-12:00", time.Unix(0, math.MaxInt64))
|
||||
|
||||
// max second
|
||||
f("2262-04-11T23:47:16Z", time.Date(2262, 4, 11, 23, 47, 16, 0, time.UTC))
|
||||
f("2262-04-12T13:47:16+14:00", time.Date(2262, 4, 12, 13, 47, 16, 0, east))
|
||||
f("2262-04-11T23:47:16+14:00", time.Date(2262, 4, 11, 23, 47, 16, 0, east))
|
||||
f("2262-04-11T11:47:16-12:00", time.Date(2262, 4, 11, 11, 47, 16, 0, west))
|
||||
f("2262-04-11T23:47:16-12:00", time.Unix(0, math.MaxInt64))
|
||||
|
||||
// max timestamp
|
||||
s = fmt.Sprintf("%d", int64(maxValidSecond))
|
||||
@@ -324,85 +324,6 @@ func TestParseTimeAtLimits(t *testing.T) {
|
||||
f(s, time.Date(1970, 4, 17, 18, 2, 52, 36_854_776, time.UTC))
|
||||
}
|
||||
|
||||
func TestParseTimeAtOutsideLimits(t *testing.T) {
|
||||
now := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
got, err := ParseTimeAt(s, now.UnixNano())
|
||||
if err == nil {
|
||||
t.Fatalf("expected error but got %d", got)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "must be in the range") {
|
||||
t.Fatalf("expected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// min timestamp
|
||||
f(fmt.Sprintf("-%d", now.Unix()+1))
|
||||
f(fmt.Sprintf("now-%d", now.Unix()+1))
|
||||
|
||||
// min year
|
||||
f("1969Z")
|
||||
f("1970+14:00")
|
||||
f("1969-12:00")
|
||||
|
||||
// min month
|
||||
f("1969-12Z")
|
||||
f("1970-01+14:00")
|
||||
f("1969-12-12:00")
|
||||
|
||||
// min day
|
||||
f("1969-12-31Z")
|
||||
f("1970-01-01+14:00")
|
||||
f("1969-12-31-12:00")
|
||||
|
||||
// min hour
|
||||
f("1969-12-31T23Z")
|
||||
f("1970-01-01T13+14:00")
|
||||
f("1969-12-31T11-12:00")
|
||||
|
||||
// min minute
|
||||
f("1969-12-31T23:59Z")
|
||||
f("1970-01-01T13:59+14:00")
|
||||
f("1969-12-31T11:59-12:00")
|
||||
|
||||
// min second
|
||||
f("1969-12-31T23:59:59Z")
|
||||
f("1970-01-01T13:59:59+14:00")
|
||||
f("1969-12-31T11:59:59-12:00")
|
||||
|
||||
// max year
|
||||
f("2263Z")
|
||||
f("2263+14:00")
|
||||
f("2263-12:00")
|
||||
|
||||
// max month
|
||||
f("2262-05Z")
|
||||
f("2262-05+14:00")
|
||||
f("2262-05-12:00")
|
||||
|
||||
// max day
|
||||
f("2262-04-12Z")
|
||||
f("2262-04-13+14:00")
|
||||
f("2262-04-12-12:00")
|
||||
|
||||
// max hour
|
||||
f("2262-04-12T00Z")
|
||||
f("2262-04-12T14+14:00")
|
||||
f("2262-04-11T12-12:00")
|
||||
|
||||
// max minute
|
||||
f("2262-04-11T23:48Z")
|
||||
f("2262-04-12T13:48+14:00")
|
||||
f("2262-04-11T11:48-12:00")
|
||||
|
||||
// max second
|
||||
f("2262-04-11T23:47:17Z")
|
||||
f("2262-04-12T13:47:17+14:00")
|
||||
f("2262-04-11T11:47:17-12:00")
|
||||
}
|
||||
|
||||
func TestParseTimeAtOutsideLimits_Nanos(t *testing.T) {
|
||||
now := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
@@ -434,7 +355,6 @@ func TestParseTimeMsecFailure(t *testing.T) {
|
||||
}
|
||||
|
||||
f("")
|
||||
f("2263")
|
||||
f("23-45:50")
|
||||
f("1223-fo:ba")
|
||||
f("1223-12:ba")
|
||||
|
||||
68
lib/vmalertproxy/vmalertproxy.go
Normal file
68
lib/vmalertproxy/vmalertproxy.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package vmalertproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// Init initializes proxying requests to the given proxyURL when calling HandleRequest.
|
||||
//
|
||||
// Init must be called after flag.Parse(), since it uses command-line flags.
|
||||
func Init(proxyURL string) {
|
||||
if len(proxyURL) == 0 {
|
||||
return
|
||||
}
|
||||
pu, err := url.Parse(proxyURL)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot parse -vmalert.proxyURL=%q: %s", proxyURL, err)
|
||||
}
|
||||
vmalertProxyHost = pu.Host
|
||||
vmalertProxy = httputil.NewSingleHostReverseProxy(pu)
|
||||
}
|
||||
|
||||
// HandleRequest proxies the given request path to vmalert at proxyURL passed to Init().
|
||||
func HandleRequest(w http.ResponseWriter, r *http.Request, path string) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil || err == http.ErrAbortHandler {
|
||||
// Suppress http.ErrAbortHandler panic.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1353
|
||||
return
|
||||
}
|
||||
// Forward other panics to the caller.
|
||||
panic(err)
|
||||
}()
|
||||
req := r.Clone(r.Context())
|
||||
req.URL.Path = path
|
||||
req.Host = vmalertProxyHost
|
||||
|
||||
if strings.HasPrefix(r.Header.Get(`User-Agent`), `Grafana`) {
|
||||
// Grafana currently supports only Prometheus-style alerts. If other alert types
|
||||
// (e.g. logs or traces) are returned, it may fail with "Error loading alerts".
|
||||
//
|
||||
// Grafana queries the vmalert API directly, bypassing the VictoriaMetrics datasource,
|
||||
// so query params (such as datasource_type) cannot be enforced on the Grafana side.
|
||||
//
|
||||
// To ensure compatibility, we detect Grafana requests via the User-Agent and enforce
|
||||
// `datasource_type=prometheus`.
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/VictoriaMetrics/victoriametrics-datasource/issues/329#issuecomment-3847585443
|
||||
// - https://github.com/VictoriaMetrics/victoriametrics-datasource/issues/59
|
||||
q := req.URL.Query()
|
||||
q.Set("datasource_type", "prometheus")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
req.RequestURI = ""
|
||||
}
|
||||
|
||||
vmalertProxy.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
var (
|
||||
vmalertProxyHost string
|
||||
vmalertProxy *httputil.ReverseProxy
|
||||
)
|
||||
Reference in New Issue
Block a user