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 }