github.com/grafana/synthetic-monitoring-agent@v0.64.2/pkg/pb/synthetic_monitoring/checks_extra.go (about)

     1  // Copyright 2020 Grafana Labs
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  // Package synthetic_monitoring provides access to types and methods
    15  // that allow for the production and consumption of protocol buffer
    16  // messages used to communicate with synthetic-monitoring-api.
    17  package synthetic_monitoring
    18  
    19  //go:generate ../../../scripts/enumer -type=CheckType -trimprefix=CheckType -transform=lower -output=string.go
    20  //go:generate ../../../scripts/enumer -type=MultiHttpEntryAssertionType,MultiHttpEntryAssertionSubjectVariant,MultiHttpEntryAssertionConditionVariant,MultiHttpEntryVariableType -trimprefix=MultiHttpEntryAssertionType_,MultiHttpEntryAssertionSubjectVariant_,MultiHttpEntryAssertionConditionVariant_,MultiHttpEntryVariableType_ -transform=upper -output=multihttp_string.go
    21  
    22  import (
    23  	"cmp"
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"mime"
    28  	"net"
    29  	"net/url"
    30  	"regexp"
    31  	"slices"
    32  	"strconv"
    33  	"strings"
    34  	"time"
    35  
    36  	"github.com/rs/zerolog"
    37  	"golang.org/x/net/http/httpguts"
    38  )
    39  
    40  var (
    41  	ErrInvalidTenantId        = errors.New("invalid tenant ID")
    42  	ErrInvalidCheckProbes     = errors.New("invalid check probes")
    43  	ErrInvalidCheckTarget     = errors.New("invalid check target")
    44  	ErrInvalidCheckJob        = errors.New("invalid check job")
    45  	ErrInvalidCheckFrequency  = errors.New("invalid check frequency")
    46  	ErrInvalidCheckTimeout    = errors.New("invalid check timeout")
    47  	ErrInvalidCheckLabelName  = errors.New("invalid check label name")
    48  	ErrInvalidCheckLabelValue = errors.New("invalid check label value")
    49  	ErrInvalidLabelName       = errors.New("invalid label name")
    50  	ErrInvalidLabelValue      = errors.New("invalid label value")
    51  	ErrDuplicateLabelName     = errors.New("duplicate label name")
    52  	ErrInvalidTargetValue     = errors.New("invalid target value")
    53  
    54  	ErrInvalidCheckSettings = errors.New("invalid check settings")
    55  
    56  	ErrInvalidFQDNLength        = errors.New("invalid FQHN length")
    57  	ErrInvalidFQHNElements      = errors.New("invalid number of elements in FQHN")
    58  	ErrInvalidFQDNElementLength = errors.New("invalid FQHN element length")
    59  	ErrInvalidFQHNElement       = errors.New("invalid FQHN element")
    60  
    61  	ErrInvalidPingHostname    = errors.New("invalid ping hostname")
    62  	ErrInvalidPingPayloadSize = errors.New("invalid ping payload size")
    63  	ErrInvalidPingPacketCount = errors.New("invalid ping packet count")
    64  
    65  	ErrInvalidDnsName             = errors.New("invalid DNS name")
    66  	ErrInvalidDnsNameElement      = errors.New("invalid DNS name element")
    67  	ErrInvalidDnsServer           = errors.New("invalid DNS server")
    68  	ErrInvalidDnsPort             = errors.New("invalid DNS port")
    69  	ErrInvalidDnsProtocolString   = errors.New("invalid DNS protocol string")
    70  	ErrInvalidDnsProtocolValue    = errors.New("invalid DNS protocol value")
    71  	ErrInvalidDnsRecordTypeString = errors.New("invalid DNS record type string")
    72  	ErrInvalidDnsRecordTypeValue  = errors.New("invalid DNS record type value")
    73  
    74  	ErrInvalidHttpUrl                          = errors.New("invalid HTTP URL")
    75  	ErrInvalidHttpMethodString                 = errors.New("invalid HTTP method string")
    76  	ErrInvalidHttpMethodValue                  = errors.New("invalid HTTP method value")
    77  	ErrInvalidHttpUrlHost                      = errors.New("invalid HTTP URL host")
    78  	ErrInvalidHttpHeaders                      = errors.New("invalid HTTP headers")
    79  	ErrInvalidHttpFailIfBodyMatchesRegexp      = errors.New("invalid HTTP fail if body matches regexp")
    80  	ErrInvalidHttpFailIfBodyNotMatchesRegexp   = errors.New("invalid HTTP fail if body not matches regexp")
    81  	ErrInvalidHttpFailIfHeaderMatchesRegexp    = errors.New("invalid HTTP fail if header matches regexp")
    82  	ErrInvalidHttpFailIfHeaderNotMatchesRegexp = errors.New("invalid HTTP fail if header not matches regexp")
    83  	ErrInvalidHttpBearerToken                  = errors.New("invalid HTTP bearer token for secret manager")
    84  	ErrInvalidHttpBasicAuthPassword            = errors.New("invalid HTTP basic auth password for secret manager")
    85  	ErrHttpUrlContainsPassword                 = errors.New("HTTP URL contains username and password")
    86  	ErrHttpUrlContainsUsername                 = errors.New("HTTP URL contains username")
    87  	ErrInvalidProxyConnectHeaders              = errors.New("invalid HTTP proxy connect headers")
    88  	ErrInvalidProxyUrl                         = errors.New("invalid proxy URL")
    89  	ErrInvalidProxySettings                    = errors.New("invalid proxy settings")
    90  
    91  	ErrInvalidTracerouteHostname = errors.New("invalid traceroute hostname")
    92  
    93  	ErrInvalidK6Script = errors.New("invalid K6 script")
    94  
    95  	ErrInvalidMultiHttpTargets = errors.New("invalid multi-http targets")
    96  
    97  	ErrTooManyMultiHttpTargets         = errors.New("too many multi-http targets")
    98  	ErrTooManyMultiHttpAssertions      = errors.New("too many multi-http assertions")
    99  	ErrTooManyMultiHttpVariables       = errors.New("too many multi-http variables")
   100  	ErrMultiHttpVariableNamesNotUnique = errors.New("multi-http variable names must be unique")
   101  
   102  	ErrInvalidHostname = errors.New("invalid hostname")
   103  	ErrInvalidPort     = errors.New("invalid port")
   104  
   105  	ErrInvalidIpVersionString = errors.New("invalid ip version string")
   106  	ErrInvalidIpVersionValue  = errors.New("invalid ip version value")
   107  
   108  	ErrInvalidCompressionAlgorithmString = errors.New("invalid compression algorithm string")
   109  	ErrInvalidCompressionAlgorithmValue  = errors.New("invalid compression algorithm value")
   110  
   111  	ErrInvalidProbeName              = errors.New("invalid probe name")
   112  	ErrInvalidProbeReservedLabelName = errors.New("invalid probe, reserved label name")
   113  	ErrInvalidProbeLabelName         = errors.New("invalid probe label name")
   114  	ErrInvalidProbeLabelValue        = errors.New("invalid probe label value")
   115  	ErrTooManyProbeLabels            = errors.New("too many probe labels")
   116  	ErrInvalidProbeLatitude          = errors.New("invalid probe latitude")
   117  	ErrInvalidProbeLongitude         = errors.New("invalid probe longitude")
   118  
   119  	ErrInvalidHttpRequestBodyContentType = errors.New("invalid HTTP request body content type")
   120  	ErrInvalidHttpRequestBodyPayload     = errors.New("invalid HTTP request body payload")
   121  	ErrInvalidQueryFieldName             = errors.New("invalid query field name")
   122  
   123  	ErrInvalidMultiHttpAssertion                     = errors.New("invalid multi-http assertion")
   124  	ErrInvalidMultiHttpEntryVariable                 = errors.New("invalid multi-http variable")
   125  	ErrInvalidMultiHttpAssertionMissingValue         = errors.New("invalid multi-http assertion, missing value")
   126  	ErrInvalidMultiHttpAssertionExpressionNotAllowed = errors.New("invalid multi-http assertion, expression not allowed")
   127  	ErrInvalidMultiHttpAssertionMissingHeaderName    = errors.New("invalid multi-http assertion, missing header name")
   128  )
   129  
   130  const (
   131  	HealthCheckInterval = 90 * time.Second
   132  	HealthCheckTimeout  = 30 * time.Second
   133  )
   134  
   135  // systemLabels is the read-only set of label names reserved by the
   136  // synthetic-monitoring-agent. The API enforces this reservation: user-defined
   137  // labels with these names are rejected at check create/update and when a
   138  // tenant's LabelMode changes to LABEL_MODE_DUAL_WRITE or LABEL_MODE_UNPREFIXED.
   139  // A tenant therefore cannot enter a non-PREFIXED mode while holding a label
   140  // whose name collides with the current reserved set.
   141  //
   142  // The agent also asserts this reservation as a backstop: in DUAL_WRITE and
   143  // UNPREFIXED modes it drops un-prefixed user labels whose names are reserved,
   144  // rather than letting them win, so a stray reserved name cannot overwrite an
   145  // intrinsic per-series metric label (`le`, `phase`, ...). Prefixed forms
   146  // (`label_*`) and PREFIXED-mode labels are unaffected. See buildUserLabels in
   147  // internal/scraper for the collision-resolution contract.
   148  //
   149  // This map must not be modified at runtime. Use IsSystemLabel to query it.
   150  //
   151  // Intentionally excluded labels not useful in the SM context; the agent omits
   152  // them from metrics and logs:
   153  //
   154  //   - from_cache, from_prefetch_cache, from_service_worker, resource_type
   155  //   - region: the agent renames this to sm_region (see sm_region in this map).
   156  //
   157  // Tenants are free to define any of these as user labels without collision
   158  // risk.
   159  //
   160  // PLEASE KEEP THIS SORTED.
   161  var systemLabels = []string{
   162  	"check",
   163  	"check_name",
   164  	"cipher",
   165  	"config_version",
   166  	"execution_id",
   167  	"fingerprint_sha256",
   168  	"frequency",
   169  	"geohash",
   170  	"instance",
   171  	"issuer",
   172  	"job",
   173  	"le",
   174  	"method",
   175  	"name",
   176  	"phase",
   177  	"probe",
   178  	"probe_success",
   179  	"proto",
   180  	"rating",
   181  	"result",
   182  	"scenario",
   183  	"serialnumber",
   184  	"sm_region",
   185  	"source",
   186  	"status",
   187  	"subject",
   188  	"subjectalternative",
   189  	"tls_version",
   190  	"url",
   191  	"version",
   192  }
   193  
   194  // Take the above slice and turn it into a map for faster lookups.
   195  var systemLabelsSet = func() map[string]struct{} {
   196  	set := make(map[string]struct{})
   197  
   198  	for _, label := range systemLabels {
   199  		set[label] = struct{}{}
   200  	}
   201  
   202  	return set
   203  }()
   204  
   205  // SystemLabelCount is the number of reserved system label names.
   206  func SystemLabelCount() int { return len(systemLabels) }
   207  
   208  // IsSystemLabel reports whether name is reserved by the synthetic-monitoring-agent.
   209  func IsSystemLabel(name string) bool {
   210  	_, found := systemLabelsSet[name]
   211  
   212  	return found
   213  }
   214  
   215  // SystemLabelNames returns a slice of all reserved system label names.
   216  //
   217  // The returned slice is always sorted alphabetically.
   218  //
   219  // The result is a new slice on each call; callers are free to use it as they
   220  // see fit.
   221  func SystemLabelNames() []string { return slices.Clone(systemLabels) }
   222  
   223  const (
   224  	MaxMetricLabels          = 22   // Prometheus allows for 32 labels, but limit to 22.
   225  	MaxLogLabels             = 15   // Loki allows a maximum of 15 labels.
   226  	MaxProbeLabels           = 3    // 3 for probes, leaving 7 for internal use.
   227  	maxValidLabelValueLength = 2048 // This is the actual max label value length.
   228  	MaxLabelValueLength      = 128  // Keep this number low so that the UI remains usable.
   229  	MaxPingPackets           = 10   // Allow 10 packets per ping.
   230  	MaxMultiHttpTargets      = 10   // Max targets per multi-http check.
   231  	MaxMultiHttpAssertions   = 5    // Max assertions per multi-http target.
   232  	MaxMultiHttpVariables    = 5    // Max variables per multi-http target.
   233  
   234  	// Frequencies
   235  	maxCheckFrequency      = time.Hour       // Maximum value for the check's frequency (1 hour)
   236  	minCheckFrequency      = time.Second     // Minimum default value for the check's frequency (1 second)
   237  	minTracerouteFrequency = 2 * time.Minute // Minimum value for the traceroute check's frequency (2 min)
   238  	minK6Frequency         = time.Minute     // Minimum value for k6-class check's frequency (1 min)
   239  
   240  	// Timeouts
   241  	minCheckTimeout      = minCheckFrequency
   242  	maxCheckTimeout      = time.Minute      // Maximum value for the check's timeout (1 minute)
   243  	minScriptedTimeout   = minCheckTimeout  // Minimum timeout for scripted checks (1 second)
   244  	MaxScriptedTimeout   = 3 * time.Minute  // Maximum timeout for scripted checks (180 second)
   245  	minTracerouteTimeout = 30 * time.Second // Minimum timeout for traceroute checks (30 second)
   246  	maxTracerouteTimeout = 30 * time.Second // Maximum timeout for traceroute checks (30 second)
   247  )
   248  
   249  const (
   250  	// These constants specify the maximum number of labels set by the agent
   251  	// for any metric and log stream for all supported probes, as well as the
   252  	// max number of labels set for the sm_check_info metric.
   253  	// These are constant per agent version but might vary between versions.
   254  	// They can be queried through MaxAgentMetricLabels(), MaxAgentLogLabels()
   255  	// and MaxAgentCheckInfoLabels() exported functions. These are required in
   256  	// order to calculate how many check labels can be set without exceeding
   257  	// specific tenant limits.
   258  
   259  	maxAgentMetricLabels    = 9 // Max metric labels set by the agent for any check type
   260  	maxAgentLogLabels       = 7 // Max log labels set by the agent for any check type
   261  	maxAgentCheckInfoLabels = 9 // Max labels set by the agent for sm_check_info metric
   262  )
   263  
   264  // internalMarker is a reserved value that cannot be used for Check.Job or Check.Target fields.
   265  // This value is reserved for potential use by the Synthetic Monitoring agent or API
   266  // to create special internal checks or metrics that are distinct from user-defined ones.
   267  // User-provided checks with this marker will be rejected during validation.
   268  const internalMarker = "__internal__"
   269  
   270  // MaxAgentMetricLabels returns the maximum number of labels set by the agent
   271  // to any metric.
   272  func MaxAgentMetricLabels() int {
   273  	return maxAgentMetricLabels
   274  }
   275  
   276  // MaxAgentLogLabels returns the maximum number of labels set by the agent
   277  // to any log stream.
   278  func MaxAgentLogLabels() int {
   279  	return maxAgentLogLabels
   280  }
   281  
   282  // MaxAgentCheckInfoLabels returns the maximum number of labels set by the agent
   283  // for sm_check_info metric.
   284  func MaxAgentCheckInfoLabels() int {
   285  	return maxAgentCheckInfoLabels
   286  }
   287  
   288  type validatable interface {
   289  	Validate() error
   290  }
   291  
   292  func validateCollection[T validatable](collection []T) error {
   293  	for _, item := range collection {
   294  		if err := item.Validate(); err != nil {
   295  			return err
   296  		}
   297  	}
   298  
   299  	return nil
   300  }
   301  
   302  // CheckType represents the type of the associated check
   303  type CheckType int32
   304  
   305  const (
   306  	CheckTypeDns        CheckType = 0
   307  	CheckTypeHttp       CheckType = 1
   308  	CheckTypePing       CheckType = 2
   309  	CheckTypeTcp        CheckType = 3
   310  	CheckTypeTraceroute CheckType = 4
   311  	CheckTypeScripted   CheckType = 5
   312  	CheckTypeMultiHttp  CheckType = 6
   313  	CheckTypeGrpc       CheckType = 7
   314  	CheckTypeBrowser    CheckType = 8
   315  )
   316  
   317  func CheckTypeFromString(in string) (CheckType, bool) {
   318  	ct, err := CheckTypeString(in)
   319  	if err != nil {
   320  		return 0, false
   321  	}
   322  
   323  	return ct, true
   324  }
   325  
   326  func (c Check) Type() CheckType {
   327  	switch {
   328  	case c.Settings.Dns != nil:
   329  		return CheckTypeDns
   330  
   331  	case c.Settings.Http != nil:
   332  		return CheckTypeHttp
   333  
   334  	case c.Settings.Ping != nil:
   335  		return CheckTypePing
   336  
   337  	case c.Settings.Tcp != nil:
   338  		return CheckTypeTcp
   339  
   340  	case c.Settings.Traceroute != nil:
   341  		return CheckTypeTraceroute
   342  
   343  	case c.Settings.Scripted != nil:
   344  		return CheckTypeScripted
   345  
   346  	case c.Settings.Multihttp != nil:
   347  		return CheckTypeMultiHttp
   348  
   349  	case c.Settings.Grpc != nil:
   350  		return CheckTypeGrpc
   351  
   352  	case c.Settings.Browser != nil:
   353  		return CheckTypeBrowser
   354  
   355  	default:
   356  		panic("unhandled check type")
   357  	}
   358  }
   359  
   360  func (c Check) Class() CheckClass {
   361  	return c.Type().Class()
   362  }
   363  
   364  func (c CheckType) Class() CheckClass {
   365  	switch c {
   366  	case CheckTypeDns, CheckTypeHttp, CheckTypePing, CheckTypeTcp, CheckTypeTraceroute, CheckTypeGrpc:
   367  		return CheckClass_PROTOCOL
   368  
   369  	case CheckTypeScripted, CheckTypeMultiHttp:
   370  		return CheckClass_SCRIPTED
   371  
   372  	case CheckTypeBrowser:
   373  		return CheckClass_BROWSER
   374  
   375  	default:
   376  		panic("unhandled check class")
   377  	}
   378  }
   379  
   380  func (c Check) Validate() error {
   381  	if c.TenantId == BadID {
   382  		return ErrInvalidTenantId
   383  	}
   384  
   385  	if len(c.Probes) == 0 {
   386  		return ErrInvalidCheckProbes
   387  	}
   388  
   389  	if len(c.Target) == 0 || c.Target == internalMarker {
   390  		return ErrInvalidCheckTarget
   391  	}
   392  
   393  	if len(c.Job) == 0 || c.Job == internalMarker {
   394  		return ErrInvalidCheckJob
   395  	}
   396  
   397  	if err := c.validateFrequency(); err != nil {
   398  		return err
   399  	}
   400  
   401  	if err := c.validateTimeout(); err != nil {
   402  		return err
   403  	}
   404  
   405  	if err := validateLabels(c.Labels); err != nil {
   406  		return err
   407  	}
   408  
   409  	if err := c.Settings.Validate(); err != nil {
   410  		return err
   411  	}
   412  
   413  	if err := c.validateTarget(); err != nil {
   414  		return err
   415  	}
   416  
   417  	return nil
   418  }
   419  
   420  func (c Check) validateTarget() error {
   421  	// All targets must be valid label values.
   422  	if len(c.Target) > maxValidLabelValueLength {
   423  		return ErrInvalidTargetValue
   424  	}
   425  
   426  	switch c.Type() {
   427  	case CheckTypeDns:
   428  		if err := validateDnsTarget(c.Target); err != nil {
   429  			return ErrInvalidDnsName
   430  		}
   431  
   432  	case CheckTypeHttp:
   433  		return validateHttpUrl(c.Target)
   434  
   435  	case CheckTypePing:
   436  		if err := validateHost(c.Target); err != nil {
   437  			return ErrInvalidPingHostname
   438  		}
   439  
   440  	case CheckTypeTcp:
   441  		return validateHostPort(c.Target)
   442  
   443  	case CheckTypeTraceroute:
   444  		if err := validateHost(c.Target); err != nil {
   445  			return ErrInvalidTracerouteHostname
   446  		}
   447  
   448  	case CheckTypeScripted:
   449  		return nil
   450  
   451  	case CheckTypeMultiHttp:
   452  		// TODO(mem): checks MUST have a target, but in this case it's
   453  		// not true that the target must be a valid URL.
   454  		// validation of URLs is the responsibility of the MultihttpEntryRequest
   455  		return nil
   456  
   457  	case CheckTypeGrpc:
   458  		return validateHostPort(c.Target)
   459  
   460  	case CheckTypeBrowser:
   461  		return nil
   462  
   463  	default:
   464  		panic("unhandled check type")
   465  	}
   466  
   467  	return nil
   468  }
   469  
   470  func (c Check) validateFrequency() error {
   471  	var (
   472  		minFrequency = minCheckFrequency.Milliseconds()
   473  		maxFrequency = maxCheckFrequency.Milliseconds()
   474  	)
   475  
   476  	// Some check types have different allowed values for the frequency.
   477  
   478  	switch c.Type() {
   479  	case CheckTypeTraceroute:
   480  		minFrequency = minTracerouteFrequency.Milliseconds()
   481  
   482  	case CheckTypeScripted, CheckTypeMultiHttp, CheckTypeBrowser:
   483  		minFrequency = minK6Frequency.Milliseconds()
   484  	}
   485  
   486  	if !inClosedRange(c.Frequency, minFrequency, maxFrequency) {
   487  		return ErrInvalidCheckFrequency
   488  	}
   489  
   490  	return nil
   491  }
   492  
   493  func (c Check) validateTimeout() error {
   494  	return validateTimeout(c.Type(), c.Timeout, c.Frequency)
   495  }
   496  
   497  func validateLabels(labels []Label) error {
   498  	seenLabels := make(map[string]struct{})
   499  
   500  	for _, label := range labels {
   501  		if _, found := seenLabels[label.Name]; found {
   502  			return fmt.Errorf("label name %s: %w", label.Name, ErrDuplicateLabelName)
   503  		}
   504  
   505  		seenLabels[label.Name] = struct{}{}
   506  
   507  		if err := label.Validate(); err != nil {
   508  			return err
   509  		}
   510  	}
   511  
   512  	return nil
   513  }
   514  
   515  func (c Check) ConfigVersion() string {
   516  	return strconv.FormatInt(int64(c.Modified*1000000000), 10)
   517  }
   518  
   519  func (c AdHocCheck) Type() CheckType {
   520  	switch {
   521  	case c.Settings.Dns != nil:
   522  		return CheckTypeDns
   523  
   524  	case c.Settings.Http != nil:
   525  		return CheckTypeHttp
   526  
   527  	case c.Settings.Ping != nil:
   528  		return CheckTypePing
   529  
   530  	case c.Settings.Tcp != nil:
   531  		return CheckTypeTcp
   532  
   533  	case c.Settings.Traceroute != nil:
   534  		return CheckTypeTraceroute
   535  
   536  	case c.Settings.Scripted != nil:
   537  		return CheckTypeScripted
   538  
   539  	case c.Settings.Multihttp != nil:
   540  		return CheckTypeMultiHttp
   541  
   542  	case c.Settings.Grpc != nil:
   543  		return CheckTypeGrpc
   544  
   545  	case c.Settings.Browser != nil:
   546  		return CheckTypeBrowser
   547  
   548  	default:
   549  		panic("unhandled check type")
   550  	}
   551  }
   552  
   553  func (c AdHocCheck) Validate() error {
   554  	if c.TenantId < 0 {
   555  		return ErrInvalidTenantId
   556  	}
   557  
   558  	if len(c.Probes) == 0 {
   559  		return ErrInvalidCheckProbes
   560  	}
   561  
   562  	if len(c.Target) == 0 || c.Target == internalMarker {
   563  		return ErrInvalidCheckTarget
   564  	}
   565  
   566  	if err := c.validateTimeout(); err != nil {
   567  		return err
   568  	}
   569  
   570  	if err := c.Settings.Validate(); err != nil {
   571  		return err
   572  	}
   573  
   574  	if err := c.validateTarget(); err != nil {
   575  		return err
   576  	}
   577  
   578  	return nil
   579  }
   580  
   581  func (c AdHocCheck) validateTimeout() error {
   582  	// Ad-hoc checks don't have a frequency, so we pass the timeout value instead.
   583  	return validateTimeout(c.Type(), c.Timeout, c.Timeout)
   584  }
   585  
   586  func (c AdHocCheck) validateTarget() error {
   587  	switch c.Type() {
   588  	case CheckTypeDns:
   589  		if err := validateDnsTarget(c.Target); err != nil {
   590  			return ErrInvalidDnsName
   591  		}
   592  
   593  	case CheckTypeHttp:
   594  		return validateHttpUrl(c.Target)
   595  
   596  	case CheckTypePing:
   597  		if err := validateHost(c.Target); err != nil {
   598  			return ErrInvalidPingHostname
   599  		}
   600  
   601  	case CheckTypeTcp:
   602  		return validateHostPort(c.Target)
   603  
   604  	case CheckTypeTraceroute:
   605  		if err := validateHost(c.Target); err != nil {
   606  			return ErrInvalidTracerouteHostname
   607  		}
   608  
   609  	case CheckTypeScripted:
   610  		return nil
   611  
   612  	case CheckTypeMultiHttp:
   613  		return nil
   614  
   615  	case CheckTypeGrpc:
   616  		return validateHostPort(c.Target)
   617  
   618  	case CheckTypeBrowser:
   619  		return nil
   620  
   621  	default:
   622  		panic("unhandled check type")
   623  	}
   624  
   625  	return nil
   626  }
   627  
   628  func (s CheckSettings) Validate() error {
   629  	var validateFn func() error
   630  
   631  	settingsCount := 0
   632  
   633  	if s.Ping != nil {
   634  		settingsCount++
   635  		validateFn = s.Ping.Validate
   636  	}
   637  
   638  	if s.Http != nil {
   639  		settingsCount++
   640  		validateFn = s.Http.Validate
   641  	}
   642  
   643  	if s.Dns != nil {
   644  		settingsCount++
   645  		validateFn = s.Dns.Validate
   646  	}
   647  
   648  	if s.Tcp != nil {
   649  		settingsCount++
   650  		validateFn = s.Tcp.Validate
   651  	}
   652  
   653  	if s.Traceroute != nil {
   654  		settingsCount++
   655  		validateFn = s.Traceroute.Validate
   656  	}
   657  
   658  	if s.Scripted != nil {
   659  		settingsCount++
   660  		validateFn = s.Scripted.Validate
   661  	}
   662  
   663  	if s.Multihttp != nil {
   664  		settingsCount++
   665  		validateFn = s.Multihttp.Validate
   666  	}
   667  
   668  	if s.Grpc != nil {
   669  		settingsCount++
   670  		validateFn = s.Grpc.Validate
   671  	}
   672  
   673  	if s.Browser != nil {
   674  		settingsCount++
   675  		validateFn = s.Browser.Validate
   676  	}
   677  
   678  	if settingsCount != 1 {
   679  		return ErrInvalidCheckSettings
   680  	}
   681  
   682  	return validateFn()
   683  }
   684  
   685  func (s *PingSettings) Validate() error {
   686  	if s.PayloadSize < 0 || s.PayloadSize > 65499 {
   687  		return ErrInvalidPingPayloadSize
   688  	}
   689  
   690  	if s.PacketCount < 0 || s.PacketCount > MaxPingPackets {
   691  		return ErrInvalidPingPacketCount
   692  	}
   693  
   694  	return nil
   695  }
   696  
   697  //nolint:gocyclo
   698  func (s *HttpSettings) Validate() error {
   699  	for _, h := range s.Headers {
   700  		fields := strings.SplitN(h, ":", 2)
   701  		if len(fields) < 2 {
   702  			return ErrInvalidHttpHeaders
   703  		}
   704  
   705  		// remove optional leading and trailing whitespace
   706  		fields[1] = strings.TrimSpace(fields[1])
   707  
   708  		if !httpguts.ValidHeaderFieldName(fields[0]) {
   709  			return ErrInvalidHttpHeaders
   710  		}
   711  
   712  		if !httpguts.ValidHeaderFieldValue(fields[1]) {
   713  			return ErrInvalidHttpHeaders
   714  		}
   715  	}
   716  
   717  	if len(s.ProxyURL) > 0 {
   718  		u, err := url.Parse(s.ProxyURL)
   719  		if err != nil {
   720  			return ErrInvalidProxyUrl
   721  		}
   722  
   723  		if u.Scheme != "http" && u.Scheme != "https" {
   724  			return ErrInvalidProxyUrl
   725  		}
   726  	}
   727  
   728  	if len(s.ProxyConnectHeaders) > 0 && len(s.ProxyURL) == 0 {
   729  		return ErrInvalidProxySettings
   730  	}
   731  
   732  	for _, h := range s.ProxyConnectHeaders {
   733  		fields := strings.SplitN(h, ":", 2)
   734  		if len(fields) < 2 {
   735  			return ErrInvalidProxyConnectHeaders
   736  		}
   737  
   738  		// remove optional leading and trailing whitespace
   739  		fields[1] = strings.TrimSpace(fields[1])
   740  
   741  		if !httpguts.ValidHeaderFieldName(fields[0]) {
   742  			return ErrInvalidProxyConnectHeaders
   743  		}
   744  
   745  		if !httpguts.ValidHeaderFieldValue(fields[1]) {
   746  			return ErrInvalidProxyConnectHeaders
   747  		}
   748  	}
   749  
   750  	for _, reg := range s.FailIfBodyMatchesRegexp {
   751  		_, err := regexp.Compile(reg)
   752  		if err != nil {
   753  			return ErrInvalidHttpFailIfBodyMatchesRegexp
   754  		}
   755  	}
   756  
   757  	for _, reg := range s.FailIfBodyNotMatchesRegexp {
   758  		_, err := regexp.Compile(reg)
   759  		if err != nil {
   760  			return ErrInvalidHttpFailIfBodyNotMatchesRegexp
   761  		}
   762  	}
   763  
   764  	for _, match := range s.FailIfHeaderMatchesRegexp {
   765  		_, err := regexp.Compile(match.Regexp)
   766  		if err != nil {
   767  			return ErrInvalidHttpFailIfHeaderMatchesRegexp
   768  		}
   769  	}
   770  
   771  	for _, match := range s.FailIfHeaderNotMatchesRegexp {
   772  		_, err := regexp.Compile(match.Regexp)
   773  		if err != nil {
   774  			return ErrInvalidHttpFailIfHeaderNotMatchesRegexp
   775  		}
   776  	}
   777  
   778  	return nil
   779  }
   780  
   781  func (s *DnsSettings) Validate() error {
   782  	if len(s.Server) == 0 || validateHost(s.Server) != nil {
   783  		return ErrInvalidDnsServer
   784  	}
   785  
   786  	if s.Port < 0 || s.Port > 65535 {
   787  		return ErrInvalidDnsPort
   788  	}
   789  
   790  	return nil
   791  }
   792  
   793  func (s *TcpSettings) Validate() error {
   794  	return nil
   795  }
   796  
   797  func (s *TracerouteSettings) Validate() error {
   798  	return nil
   799  }
   800  
   801  func (s *ScriptedSettings) Validate() error {
   802  	if len(s.Script) == 0 {
   803  		return ErrInvalidK6Script
   804  	}
   805  
   806  	return nil
   807  }
   808  
   809  func (s *MultiHttpSettings) Validate() error {
   810  	if len(s.Entries) == 0 {
   811  		return ErrInvalidMultiHttpTargets
   812  	}
   813  
   814  	if len(s.Entries) > MaxMultiHttpTargets {
   815  		return ErrTooManyMultiHttpTargets
   816  	}
   817  
   818  	if err := validateCollection(s.Entries); err != nil {
   819  		return err
   820  	}
   821  
   822  	return nil
   823  }
   824  
   825  func (s *GrpcSettings) Validate() error {
   826  	return nil
   827  }
   828  
   829  func (s *BrowserSettings) Validate() error {
   830  	if len(s.Script) == 0 {
   831  		return ErrInvalidK6Script
   832  	}
   833  
   834  	return nil
   835  }
   836  
   837  func hasUniqueValues[U any, V comparable](slice []U, fn func(U) V) bool {
   838  	set := make(map[V]struct{})
   839  
   840  	for _, elem := range slice {
   841  		value := fn(elem)
   842  		if _, found := set[value]; found {
   843  			return false
   844  		}
   845  
   846  		set[value] = struct{}{}
   847  	}
   848  
   849  	return true
   850  }
   851  
   852  func (e *MultiHttpEntry) Validate() error {
   853  	if e.Request == nil {
   854  		return ErrInvalidMultiHttpTargets
   855  	}
   856  
   857  	if err := e.Request.Validate(); err != nil {
   858  		return err
   859  	}
   860  
   861  	if len(e.Assertions) > MaxMultiHttpAssertions {
   862  		return ErrTooManyMultiHttpAssertions
   863  	}
   864  
   865  	if len(e.Variables) > MaxMultiHttpVariables {
   866  		return ErrTooManyMultiHttpVariables
   867  	}
   868  
   869  	if err := validateCollection(e.Assertions); err != nil {
   870  		return err
   871  	}
   872  
   873  	if err := validateCollection(e.Variables); err != nil {
   874  		return err
   875  	}
   876  
   877  	if !hasUniqueValues(e.Variables, func(v *MultiHttpEntryVariable) string { return v.Name }) {
   878  		return ErrMultiHttpVariableNamesNotUnique
   879  	}
   880  
   881  	return nil
   882  }
   883  
   884  func (h HttpHeader) Validate() error {
   885  	if !httpguts.ValidHeaderFieldName(h.Name) {
   886  		return ErrInvalidHttpHeaders
   887  	}
   888  
   889  	if !httpguts.ValidHeaderFieldValue(h.Value) {
   890  		return ErrInvalidHttpHeaders
   891  	}
   892  
   893  	return nil
   894  }
   895  
   896  func (f QueryField) Validate() error {
   897  	if len(f.Name) == 0 {
   898  		return ErrInvalidQueryFieldName
   899  	}
   900  
   901  	// the value might be empty
   902  
   903  	// The name can be anything. TODO(mem): is this true?
   904  
   905  	return nil
   906  }
   907  
   908  func (r *MultiHttpEntryRequest) Validate() error {
   909  	if r == nil {
   910  		return nil
   911  	}
   912  
   913  	if err := r.Method.Validate(); err != nil {
   914  		return err
   915  	}
   916  
   917  	if !strings.Contains(r.Url, "${") {
   918  		if err := validateHttpUrl(r.Url); err != nil {
   919  			return err
   920  		}
   921  	}
   922  
   923  	// TODO(mem): do something with HttpVersion?
   924  
   925  	if err := r.Body.Validate(); err != nil {
   926  		return err
   927  	}
   928  
   929  	if err := validateCollection(r.Headers); err != nil {
   930  		return err
   931  	}
   932  
   933  	if err := validateCollection(r.QueryFields); err != nil {
   934  		return err
   935  	}
   936  
   937  	return nil
   938  }
   939  
   940  // Validate verifies that the MultiHttpEntryAssertion is valid.
   941  //
   942  // Because of the structure represents multiple orthogonal variants, this
   943  // function has to branch based on the type.
   944  //
   945  //nolint:gocyclo
   946  func (a *MultiHttpEntryAssertion) Validate() error {
   947  	if a == nil {
   948  		return nil
   949  	}
   950  
   951  	if _, found := MultiHttpEntryAssertionType_name[int32(a.Type)]; !found {
   952  		// this should never happen
   953  		return ErrInvalidMultiHttpAssertion
   954  	}
   955  
   956  	if _, found := MultiHttpEntryAssertionSubjectVariant_name[int32(a.Subject)]; !found {
   957  		// this should never happen
   958  		return ErrInvalidMultiHttpAssertion
   959  	}
   960  
   961  	if _, found := MultiHttpEntryAssertionConditionVariant_name[int32(a.Condition)]; !found {
   962  		// this should never happen
   963  		return ErrInvalidMultiHttpAssertion
   964  	}
   965  
   966  	switch a.Type {
   967  	case MultiHttpEntryAssertionType_TEXT:
   968  		// Value is required
   969  		if len(a.Value) == 0 {
   970  			return ErrInvalidMultiHttpAssertionMissingValue
   971  		}
   972  
   973  		// Expression is not allowed for subjects other than response headers.
   974  		if a.Subject != MultiHttpEntryAssertionSubjectVariant_RESPONSE_HEADERS && len(a.Expression) != 0 {
   975  			return ErrInvalidMultiHttpAssertionExpressionNotAllowed
   976  		}
   977  
   978  	case MultiHttpEntryAssertionType_JSON_PATH_VALUE:
   979  		// Subject must not be set
   980  		if a.Subject != 0 {
   981  			return ErrInvalidMultiHttpAssertion
   982  		}
   983  
   984  		// Value is required
   985  		if len(a.Value) == 0 {
   986  			return ErrInvalidMultiHttpAssertion
   987  		}
   988  
   989  		// Expression is required
   990  		if len(a.Expression) == 0 {
   991  			return ErrInvalidMultiHttpAssertion
   992  		}
   993  
   994  		// Condition is covered above
   995  
   996  	case MultiHttpEntryAssertionType_JSON_PATH_ASSERTION:
   997  		// Subject must not be set
   998  		if a.Subject != 0 {
   999  			return ErrInvalidMultiHttpAssertion
  1000  		}
  1001  
  1002  		// Condition must not be set
  1003  		if a.Condition != 0 {
  1004  			return ErrInvalidMultiHttpAssertion
  1005  		}
  1006  
  1007  		// Value must not be set
  1008  		if len(a.Value) != 0 {
  1009  			return ErrInvalidMultiHttpAssertion
  1010  		}
  1011  
  1012  		// Expression is required
  1013  		if len(a.Expression) == 0 {
  1014  			return ErrInvalidMultiHttpAssertion
  1015  		}
  1016  
  1017  	case MultiHttpEntryAssertionType_REGEX_ASSERTION:
  1018  		// Condition must not be set
  1019  		if a.Condition != 0 {
  1020  			return ErrInvalidMultiHttpAssertion
  1021  		}
  1022  
  1023  		// Value must not be set
  1024  		if len(a.Value) != 0 {
  1025  			return ErrInvalidMultiHttpAssertion
  1026  		}
  1027  
  1028  		// Expression is required
  1029  		if len(a.Expression) == 0 {
  1030  			return ErrInvalidMultiHttpAssertion
  1031  		}
  1032  	}
  1033  
  1034  	return nil
  1035  }
  1036  
  1037  func (v *MultiHttpEntryVariable) Validate() error {
  1038  	// 1. Type is valid
  1039  	if _, found := MultiHttpEntryVariableType_name[int32(v.Type)]; !found {
  1040  		return ErrInvalidMultiHttpEntryVariable
  1041  	}
  1042  
  1043  	// 2. Name is not empty
  1044  	if len(v.Name) == 0 {
  1045  		return ErrInvalidMultiHttpEntryVariable
  1046  	}
  1047  
  1048  	// 3. Expression is not empty
  1049  	if len(v.Expression) == 0 {
  1050  		return ErrInvalidMultiHttpEntryVariable
  1051  	}
  1052  
  1053  	switch v.Type {
  1054  	case MultiHttpEntryVariableType_JSON_PATH:
  1055  		// 4. attribute must be empty
  1056  		if len(v.Attribute) != 0 {
  1057  			return ErrInvalidMultiHttpEntryVariable
  1058  		}
  1059  
  1060  	case MultiHttpEntryVariableType_REGEX:
  1061  		// 4. attribute must be empty
  1062  		if len(v.Attribute) != 0 {
  1063  			return ErrInvalidMultiHttpEntryVariable
  1064  		}
  1065  
  1066  	case MultiHttpEntryVariableType_CSS_SELECTOR:
  1067  		// 4. attribute might be empty
  1068  	}
  1069  
  1070  	return nil
  1071  }
  1072  
  1073  func (b *HttpRequestBody) Validate() error {
  1074  	if b == nil {
  1075  		return nil
  1076  	}
  1077  
  1078  	if len(b.ContentType) == 0 {
  1079  		return ErrInvalidHttpRequestBodyContentType
  1080  	}
  1081  
  1082  	if !httpguts.ValidHeaderFieldValue(b.ContentType) {
  1083  		return ErrInvalidHttpRequestBodyContentType
  1084  	}
  1085  
  1086  	for v := range strings.SplitSeq(b.ContentType, ",") {
  1087  		_, _, err := mime.ParseMediaType(v)
  1088  		if err != nil {
  1089  			return ErrInvalidHttpRequestBodyContentType
  1090  		}
  1091  	}
  1092  
  1093  	if len(b.ContentEncoding) > 0 && !httpguts.ValidHeaderFieldValue(b.ContentEncoding) {
  1094  		return ErrInvalidHttpRequestBodyContentType
  1095  	}
  1096  
  1097  	// Payload can be empty, since Content-Length can be 0.
  1098  	// https://datatracker.ietf.org/doc/html/rfc9110#section-8.6
  1099  
  1100  	return nil
  1101  }
  1102  
  1103  func (p *Probe) Validate() error {
  1104  	if p.TenantId < 0 {
  1105  		return ErrInvalidTenantId
  1106  	}
  1107  
  1108  	if p.Name == "" {
  1109  		return ErrInvalidProbeName
  1110  	}
  1111  
  1112  	if len(p.Labels) > MaxProbeLabels {
  1113  		return ErrTooManyProbeLabels
  1114  	}
  1115  
  1116  	for _, label := range p.Labels {
  1117  		if err := label.Validate(); err != nil {
  1118  			return err
  1119  		}
  1120  	}
  1121  
  1122  	if p.Latitude < -90 || p.Latitude > 90 {
  1123  		return ErrInvalidProbeLatitude
  1124  	}
  1125  
  1126  	if p.Longitude < -180 || p.Longitude > 180 {
  1127  		return ErrInvalidProbeLongitude
  1128  	}
  1129  
  1130  	return nil
  1131  }
  1132  
  1133  func (l Label) Validate() error {
  1134  	if err := validateLabelValue(l.Name); err != nil {
  1135  		return ErrInvalidLabelName
  1136  	}
  1137  
  1138  	// This bit is lifted from Prometheus code, except that
  1139  	// Prometheus accepts /^[a-zA-Z_][a-zA-Z0-9_]*$/ and we accept
  1140  	// /^[a-zA-Z0-9_]+$/ because these names are going to be
  1141  	// prefixed with "label_".
  1142  	for _, b := range l.Name {
  1143  		if (b < 'a' || b > 'z') && (b < 'A' || b > 'Z') && b != '_' && (b < '0' || b > '9') {
  1144  			return ErrInvalidLabelName
  1145  		}
  1146  	}
  1147  
  1148  	return validateLabelValue(l.Value)
  1149  }
  1150  
  1151  func validateLabelValue(v string) error {
  1152  	if len(v) == 0 || len(v) > MaxLabelValueLength {
  1153  		return ErrInvalidLabelValue
  1154  	}
  1155  
  1156  	return nil
  1157  }
  1158  
  1159  func lookupValue(v int32, m map[int32]string) []byte {
  1160  	if str, ok := m[v]; ok {
  1161  		return []byte(`"` + str + `"`)
  1162  	}
  1163  
  1164  	return nil
  1165  }
  1166  
  1167  func lookupString(b []byte, m map[string]int32) (int32, bool) {
  1168  	str := string(b)
  1169  
  1170  	switch str {
  1171  	case ``:
  1172  		return 0, true
  1173  	case `""`:
  1174  		return 0, true
  1175  	case `null`:
  1176  		return 0, true
  1177  	}
  1178  
  1179  	in, err := strconv.Unquote(str)
  1180  	if err != nil {
  1181  		return 0, false
  1182  	}
  1183  
  1184  	// first try a direct lookup in the known values
  1185  	if v, ok := m[in]; ok {
  1186  		return v, true
  1187  	}
  1188  
  1189  	// not found, try again doing an case-insensitive search
  1190  
  1191  	in = strings.ToLower(in)
  1192  
  1193  	for str, v := range m {
  1194  		if strings.ToLower(str) == in {
  1195  			return v, true
  1196  		}
  1197  	}
  1198  
  1199  	return 0, false
  1200  }
  1201  
  1202  func (v IpVersion) MarshalJSON() ([]byte, error) {
  1203  	if b := lookupValue(int32(v), IpVersion_name); b != nil {
  1204  		return b, nil
  1205  	}
  1206  
  1207  	return nil, ErrInvalidIpVersionValue
  1208  }
  1209  
  1210  func (out *IpVersion) UnmarshalJSON(b []byte) error {
  1211  	if v, found := lookupString(b, IpVersion_value); found {
  1212  		*out = IpVersion(v)
  1213  		return nil
  1214  	}
  1215  
  1216  	return ErrInvalidIpVersionString
  1217  }
  1218  
  1219  // ToIpProtocol converts the IpVersion setting into a pair of IP
  1220  // protocol and fallback option.
  1221  func (v IpVersion) ToIpProtocol() (string, bool) {
  1222  	switch v {
  1223  	case IpVersion_V4:
  1224  		return "ip4", false
  1225  
  1226  	case IpVersion_V6:
  1227  		return "ip6", false
  1228  
  1229  	case IpVersion_Any:
  1230  		return "ip6", true
  1231  	}
  1232  
  1233  	return "", false
  1234  }
  1235  
  1236  func (v CompressionAlgorithm) MarshalJSON() ([]byte, error) {
  1237  	if b := lookupValue(int32(v), CompressionAlgorithm_name); b != nil {
  1238  		return b, nil
  1239  	}
  1240  
  1241  	return nil, ErrInvalidCompressionAlgorithmValue
  1242  }
  1243  
  1244  func (out *CompressionAlgorithm) UnmarshalJSON(b []byte) error {
  1245  	if v, found := lookupString(b, CompressionAlgorithm_value); found {
  1246  		*out = CompressionAlgorithm(v)
  1247  		return nil
  1248  	}
  1249  
  1250  	return ErrInvalidCompressionAlgorithmString
  1251  }
  1252  
  1253  func (v HttpMethod) Validate() error {
  1254  	if _, found := HttpMethod_name[int32(v)]; !found {
  1255  		return ErrInvalidHttpMethodValue
  1256  	}
  1257  
  1258  	return nil
  1259  }
  1260  
  1261  func (v HttpMethod) MarshalJSON() ([]byte, error) {
  1262  	if b := lookupValue(int32(v), HttpMethod_name); b != nil {
  1263  		return b, nil
  1264  	}
  1265  
  1266  	return nil, ErrInvalidHttpMethodValue
  1267  }
  1268  
  1269  func (out *HttpMethod) UnmarshalJSON(b []byte) error {
  1270  	if v, found := lookupString(b, HttpMethod_value); found {
  1271  		*out = HttpMethod(v)
  1272  		return nil
  1273  	}
  1274  
  1275  	return ErrInvalidHttpMethodString
  1276  }
  1277  
  1278  func (v DnsRecordType) MarshalJSON() ([]byte, error) {
  1279  	if b := lookupValue(int32(v), DnsRecordType_name); b != nil {
  1280  		return b, nil
  1281  	}
  1282  
  1283  	return nil, ErrInvalidDnsRecordTypeValue
  1284  }
  1285  
  1286  func (out *DnsRecordType) UnmarshalJSON(b []byte) error {
  1287  	if v, found := lookupString(b, DnsRecordType_value); found {
  1288  		*out = DnsRecordType(v)
  1289  		return nil
  1290  	}
  1291  
  1292  	return ErrInvalidDnsRecordTypeString
  1293  }
  1294  
  1295  func (v DnsProtocol) MarshalJSON() ([]byte, error) {
  1296  	if b := lookupValue(int32(v), DnsProtocol_name); b != nil {
  1297  		return b, nil
  1298  	}
  1299  
  1300  	return nil, ErrInvalidDnsProtocolValue
  1301  }
  1302  
  1303  func (out *DnsProtocol) UnmarshalJSON(b []byte) error {
  1304  	if v, found := lookupString(b, DnsProtocol_value); found {
  1305  		*out = DnsProtocol(v)
  1306  		return nil
  1307  	}
  1308  
  1309  	return ErrInvalidDnsProtocolString
  1310  }
  1311  
  1312  func validateHost(target string) error {
  1313  	if ip := net.ParseIP(target); ip != nil {
  1314  		return nil
  1315  	}
  1316  
  1317  	return checkFQHN(target)
  1318  }
  1319  
  1320  // validateDnsTarget checks that the provided target is a valid DNS
  1321  // target, meaning it's either "localhost" exactly or a fully qualified
  1322  // domain name (with a full stop at the end). To accept something like
  1323  // "org" it has to be specified as "org.".
  1324  func validateDnsTarget(target string) error {
  1325  	labels := strings.Split(target, ".")
  1326  	switch len(labels) {
  1327  	case 1:
  1328  		if target == "localhost" {
  1329  			return nil
  1330  		}
  1331  
  1332  		// no dots, not "localhost", this is invalid
  1333  		return ErrInvalidDnsName
  1334  
  1335  	default:
  1336  		if labels[len(labels)-1] == "" {
  1337  			// last label is empty, so the target is of the
  1338  			// form "foo.bar."; drop the last label
  1339  			labels = labels[:len(labels)-1]
  1340  		}
  1341  
  1342  		for i, label := range labels {
  1343  			err := validateDnsLabel(label, i == len(labels)-1)
  1344  			if err != nil {
  1345  				return err
  1346  			}
  1347  		}
  1348  
  1349  		return nil
  1350  	}
  1351  }
  1352  
  1353  func validateHostPort(target string) error {
  1354  	if host, port, err := net.SplitHostPort(target); err != nil {
  1355  		return ErrInvalidCheckTarget
  1356  	} else if validateHost(host) != nil {
  1357  		return ErrInvalidHostname
  1358  	} else if n, err := strconv.ParseUint(port, 10, 16); err != nil || n == 0 {
  1359  		return ErrInvalidPort
  1360  	}
  1361  
  1362  	return nil
  1363  }
  1364  
  1365  func validateHttpUrl(target string) error {
  1366  	if len(target) != len(strings.TrimSpace(target)) {
  1367  		return ErrInvalidHttpUrl
  1368  	}
  1369  
  1370  	u, err := url.Parse(target)
  1371  	if err != nil {
  1372  		return ErrInvalidHttpUrl
  1373  	}
  1374  
  1375  	if _, isSet := u.User.Password(); isSet {
  1376  		return ErrHttpUrlContainsPassword
  1377  	}
  1378  
  1379  	if u.User.Username() != "" {
  1380  		return ErrHttpUrlContainsUsername
  1381  	}
  1382  
  1383  	if u.Scheme != "http" && u.Scheme != "https" {
  1384  		return ErrInvalidHttpUrl
  1385  	}
  1386  
  1387  	if len(u.Host) == 0 {
  1388  		return ErrInvalidHostname
  1389  	}
  1390  
  1391  	hasPort := func(h string) bool {
  1392  		for l := len(h) - 1; l > 0; l-- {
  1393  			switch h[l] {
  1394  			case ':':
  1395  				return true
  1396  			case ']', '.':
  1397  				return false
  1398  			}
  1399  		}
  1400  
  1401  		return false
  1402  	}
  1403  
  1404  	hostport := u.Host
  1405  
  1406  	if (u.Host[0] == '[' && u.Host[len(u.Host)-1] == ']') || !hasPort(u.Host) {
  1407  		if u.Scheme == "https" {
  1408  			hostport += ":443"
  1409  		} else {
  1410  			hostport += ":80"
  1411  		}
  1412  	}
  1413  
  1414  	return validateHostPort(hostport)
  1415  }
  1416  
  1417  // checkFQHN validates that the provided fully qualified hostname
  1418  // follows RFC 1034, section 3.5
  1419  // (https://tools.ietf.org/html/rfc1034#section-3.5) and RFC 1123,
  1420  // section 2.1 (https://tools.ietf.org/html/rfc1123#section-2.1).
  1421  //
  1422  // This assumes that the *hostname* part of the FQHN follows the same
  1423  // rules.
  1424  //
  1425  // Note that if there are any IDNA transformations going on, they need
  1426  // to happen _before_ calling this function.
  1427  func checkFQHN(fqhn string) error {
  1428  	if len(fqhn) == 0 || len(fqhn) > 255 {
  1429  		return ErrInvalidFQDNLength
  1430  	}
  1431  
  1432  	labels := strings.Split(fqhn, ".")
  1433  
  1434  	if len(labels) < 2 {
  1435  		return ErrInvalidFQHNElements
  1436  	}
  1437  
  1438  	for i, label := range labels {
  1439  		err := validateFQHNLabel(label, i == len(labels)-1)
  1440  		if err != nil {
  1441  			return err
  1442  		}
  1443  	}
  1444  
  1445  	return nil
  1446  }
  1447  
  1448  func validateFQHNLabel(label string, isLast bool) error {
  1449  	isLetter := func(r rune) bool {
  1450  		return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
  1451  	}
  1452  
  1453  	isDigit := func(r rune) bool {
  1454  		return (r >= '0' && r <= '9')
  1455  	}
  1456  
  1457  	isDash := func(r rune) bool {
  1458  		return (r == '-')
  1459  	}
  1460  
  1461  	if len(label) == 0 || len(label) > 63 {
  1462  		return ErrInvalidFQDNElementLength
  1463  	}
  1464  
  1465  	runes := []rune(label)
  1466  
  1467  	// labels must start with a letter or digit (RFC 1123);
  1468  	// reading the RFC strictly, it's likely that the
  1469  	// intention was that _only_ the host name could begin
  1470  	// with a letter or a digit, but since any portion of
  1471  	// the FQHN could be a host name, accept it anywhere.
  1472  	if r := runes[0]; !isLetter(r) && !isDigit(r) {
  1473  		return ErrInvalidFQHNElement
  1474  	}
  1475  
  1476  	// labels must end with a letter or digit
  1477  	if r := runes[len(runes)-1]; !isLetter(r) && !isDigit(r) {
  1478  		return ErrInvalidFQHNElement
  1479  	}
  1480  
  1481  	// these checks allow for all-numeric FQHNs, but the
  1482  	// very last label (the TLD) MUST NOT be all numeric
  1483  	// because that allows for 256.256.256.256 to be a FQHN,
  1484  	// not an invalid IP address, and down that path lies
  1485  	// madness.
  1486  	if isLast {
  1487  		allDigits := true
  1488  
  1489  		for _, r := range runes {
  1490  			if !isDigit(r) {
  1491  				allDigits = false
  1492  				break
  1493  			}
  1494  		}
  1495  
  1496  		if allDigits {
  1497  			return ErrInvalidFQHNElement
  1498  		}
  1499  	}
  1500  
  1501  	for _, r := range runes {
  1502  		// the only valid characters are [-A-Za-z0-9].
  1503  		if !isLetter(r) && !isDigit(r) && !isDash(r) {
  1504  			return ErrInvalidFQHNElement
  1505  		}
  1506  	}
  1507  
  1508  	return nil
  1509  }
  1510  
  1511  // validateDnsLabel checks that `label` conforms to a minimal set of rules
  1512  // regarding the components of a DNS entry, namely that they are not empty,
  1513  // they are not longer than 63 characters. This follows RFC 2181.
  1514  func validateDnsLabel(label string, isLast bool) error {
  1515  	// We are looking at a UTF-8 string, the len function reports the
  1516  	// number of ASCII characters, not the number of runes.
  1517  	if len(label) == 0 || len(label) > 63 {
  1518  		return ErrInvalidDnsNameElement
  1519  	}
  1520  
  1521  	return nil
  1522  }
  1523  
  1524  func validateTimeout(checkType CheckType, timeout, frequency int64) error {
  1525  	var (
  1526  		minTimeout int64
  1527  		maxTimeout int64
  1528  	)
  1529  
  1530  	switch checkType {
  1531  	case CheckTypeTraceroute:
  1532  		minTimeout = minTracerouteTimeout.Milliseconds()
  1533  		maxTimeout = min(frequency, maxTracerouteTimeout.Milliseconds())
  1534  
  1535  	case CheckTypeScripted, CheckTypeMultiHttp, CheckTypeBrowser:
  1536  		// This is experimental. A large timeout means we have more
  1537  		// checks lingering around. timeout must be less or equal than
  1538  		// frequency (otherwise we can end up running overlapping
  1539  		// checks)
  1540  		minTimeout = minScriptedTimeout.Milliseconds()
  1541  		maxTimeout = min(frequency, MaxScriptedTimeout.Milliseconds())
  1542  
  1543  	default:
  1544  		// timeout must be within the defined limits, and it must be
  1545  		// less than frequency (otherwise we can end up running
  1546  		// overlapping checks)
  1547  		minTimeout = minCheckTimeout.Milliseconds()
  1548  		maxTimeout = min(frequency, maxCheckTimeout.Milliseconds())
  1549  	}
  1550  
  1551  	if !inClosedRange(timeout, minTimeout, maxTimeout) {
  1552  		return ErrInvalidCheckTimeout
  1553  	}
  1554  
  1555  	return nil
  1556  }
  1557  
  1558  // inClosedRange returns true if the value `v` is in [lower, upper].
  1559  func inClosedRange[T cmp.Ordered](v, lower, upper T) bool {
  1560  	return v >= lower && v <= upper
  1561  }
  1562  
  1563  const loopbackIPv4 = "127.0.0.1"
  1564  
  1565  func GetCheckInstance(checkType CheckType) Check {
  1566  	validCheckCases := map[CheckType]Check{
  1567  		CheckTypeDns: {
  1568  			Id:        1,
  1569  			TenantId:  1,
  1570  			Target:    "www.example.org",
  1571  			Job:       "job",
  1572  			Frequency: 1000,
  1573  			Timeout:   1000,
  1574  			Probes:    []int64{1},
  1575  			Settings: CheckSettings{
  1576  				Dns: &DnsSettings{
  1577  					Server: loopbackIPv4,
  1578  				},
  1579  			},
  1580  		},
  1581  		CheckTypeHttp: {
  1582  			Id:        1,
  1583  			TenantId:  1,
  1584  			Target:    "http://www.example.org",
  1585  			Job:       "job",
  1586  			Frequency: 1000,
  1587  			Timeout:   1000,
  1588  			Probes:    []int64{1},
  1589  			Settings: CheckSettings{
  1590  				Http: &HttpSettings{},
  1591  			},
  1592  		},
  1593  		CheckTypePing: {
  1594  			Id:        1,
  1595  			TenantId:  1,
  1596  			Target:    loopbackIPv4,
  1597  			Job:       "job",
  1598  			Frequency: 1000,
  1599  			Timeout:   1000,
  1600  			Probes:    []int64{1},
  1601  			Settings: CheckSettings{
  1602  				Ping: &PingSettings{},
  1603  			},
  1604  		},
  1605  		CheckTypeTcp: {
  1606  			Id:        1,
  1607  			TenantId:  1,
  1608  			Target:    "127.0.0.1:9000",
  1609  			Job:       "job",
  1610  			Frequency: 1000,
  1611  			Timeout:   1000,
  1612  			Probes:    []int64{1},
  1613  			Settings: CheckSettings{
  1614  				Tcp: &TcpSettings{},
  1615  			},
  1616  		},
  1617  		CheckTypeTraceroute: {
  1618  			Id:        1,
  1619  			TenantId:  1,
  1620  			Target:    loopbackIPv4,
  1621  			Job:       "job",
  1622  			Frequency: 120000,
  1623  			Timeout:   30000,
  1624  			Probes:    []int64{1},
  1625  			Settings: CheckSettings{
  1626  				Traceroute: &TracerouteSettings{},
  1627  			},
  1628  		},
  1629  		CheckTypeScripted: {
  1630  			Id:        1,
  1631  			TenantId:  1,
  1632  			Target:    "http://www.example.org",
  1633  			Job:       "job",
  1634  			Frequency: 60000,
  1635  			Timeout:   10000,
  1636  			Probes:    []int64{1},
  1637  			Settings: CheckSettings{
  1638  				Scripted: &ScriptedSettings{
  1639  					Script: []byte("// test"),
  1640  				},
  1641  			},
  1642  		},
  1643  		CheckTypeMultiHttp: {
  1644  			Id:        1,
  1645  			TenantId:  1,
  1646  			Target:    "http://www.example.org",
  1647  			Job:       "job",
  1648  			Frequency: 60000,
  1649  			Timeout:   10000,
  1650  			Probes:    []int64{1},
  1651  			Settings: CheckSettings{
  1652  				Multihttp: &MultiHttpSettings{
  1653  					Entries: []*MultiHttpEntry{
  1654  						{
  1655  							Request: &MultiHttpEntryRequest{
  1656  								Method: HttpMethod_GET,
  1657  								Url:    "http://www.example.org/",
  1658  							},
  1659  						},
  1660  					},
  1661  				},
  1662  			},
  1663  		},
  1664  		CheckTypeGrpc: {
  1665  			Id:        1,
  1666  			TenantId:  1,
  1667  			Target:    "127.0.0.1:9000",
  1668  			Job:       "job",
  1669  			Frequency: 60000,
  1670  			Timeout:   10000,
  1671  			Probes:    []int64{1},
  1672  			Settings: CheckSettings{
  1673  				Grpc: &GrpcSettings{},
  1674  			},
  1675  		},
  1676  		CheckTypeBrowser: {
  1677  			Id:        1,
  1678  			TenantId:  1,
  1679  			Target:    "http://www.example.org",
  1680  			Job:       "job",
  1681  			Frequency: 60000,
  1682  			Timeout:   10000,
  1683  			Probes:    []int64{1},
  1684  			Settings: CheckSettings{
  1685  				Browser: &BrowserSettings{
  1686  					Script: []byte("// test"),
  1687  				},
  1688  			},
  1689  		},
  1690  	}
  1691  
  1692  	instance, known := validCheckCases[checkType]
  1693  
  1694  	if !known {
  1695  		panic("unknown check type")
  1696  	}
  1697  
  1698  	return instance
  1699  }
  1700  
  1701  func (ri RemoteInfo) MarshalZerologObject(e *zerolog.Event) {
  1702  	e.Str("name", ri.Name).
  1703  		Str("url", ri.Url).
  1704  		Str("username", ri.Username).
  1705  		Str("password", "<encrypted>")
  1706  }
  1707  
  1708  func (ri RemoteInfo) MarshalJSON() ([]byte, error) {
  1709  	type T RemoteInfo
  1710  
  1711  	tmp := T(ri)
  1712  
  1713  	tmp.Password = `<encrypted>`
  1714  
  1715  	return json.Marshal(tmp)
  1716  }