Skip to content

Commit

Permalink
[receiver/tlscheck] Implement Scraper
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-burt committed Oct 22, 2024
1 parent ad6a19f commit 05448b6
Show file tree
Hide file tree
Showing 18 changed files with 351 additions and 294 deletions.
27 changes: 27 additions & 0 deletions .chloggen/tlscheckreceiver-implementation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: tlscheckreceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Implement TLS Check Receiver for host-based checks

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [35842]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
4 changes: 2 additions & 2 deletions receiver/tlscheckreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ By default, the TLS Check Receiver will emit a single metric, `tlscheck.time_lef
receivers:
tlscheck:
targets:
- url: https://example.com
- url: https://foobar.com:8080
- host: example.com:443
- host: foobar.com:8080
```
## Certificate Verification
Expand Down
47 changes: 33 additions & 14 deletions receiver/tlscheckreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package tlscheckreceiver // import "github.com/open-telemetry/opentelemetry-coll
import (
"errors"
"fmt"
"net/url"
"net"
"strconv"
"strings"

"go.opentelemetry.io/collector/receiver/scraperhelper"
"go.uber.org/multierr"
Expand All @@ -16,8 +18,7 @@ import (

// Predefined error responses for configuration validation failures
var (
errMissingURL = errors.New(`"url" must be specified`)
errInvalidURL = errors.New(`"url" must be in the form of <scheme>://<hostname>[:<port>]`)
errInvalidHost = errors.New(`"host" must be in the form of <hostname>:<port>`)
)

// Config defines the configuration for the various elements of the receiver agent.
Expand All @@ -28,31 +29,49 @@ type Config struct {
}

type targetConfig struct {
URL string `mapstructure:"url"`
Host string `mapstructure:"host"`
}

func validatePort(port string) error {
portNum, err := strconv.Atoi(port)
if err != nil {
return fmt.Errorf("provided port is not a number: %s", port)
}
if portNum < 1 || portNum > 65535 {
return fmt.Errorf("provided port is out of valid range (1-65535): %d", portNum)
}
return nil
}

// Validate validates the configuration by checking for missing or invalid fields
func (cfg *targetConfig) Validate() error {
var err error

if cfg.URL == "" {
err = multierr.Append(err, errMissingURL)
} else {
_, parseErr := url.ParseRequestURI(cfg.URL)
if parseErr != nil {
err = multierr.Append(err, fmt.Errorf("%s: %w", errInvalidURL.Error(), parseErr))
}
if cfg.Host == "" {
return ErrMissingTargets
}

if strings.Contains(cfg.Host, "://") {
return fmt.Errorf("host contains a scheme, which is not allowed: %s", cfg.Host)
}

_, port, parseErr := net.SplitHostPort(cfg.Host)
if parseErr != nil {
return fmt.Errorf("%s: %w", errInvalidHost.Error(), parseErr)
}

portParseErr := validatePort(port)
if portParseErr != nil {
return fmt.Errorf("%s: %w", errInvalidHost.Error(), portParseErr)
}

return err
}

// Validate validates the configuration by checking for missing or invalid fields
func (cfg *Config) Validate() error {
var err error

if len(cfg.Targets) == 0 {
err = multierr.Append(err, errMissingURL)
err = multierr.Append(err, ErrMissingTargets)
}

for _, target := range cfg.Targets {
Expand Down
37 changes: 26 additions & 11 deletions receiver/tlscheckreceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,56 +23,71 @@ func TestValidate(t *testing.T) {
Targets: []*targetConfig{},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: errMissingURL,
expectedErr: ErrMissingTargets,
},
{
desc: "invalid url",
desc: "invalid host",
cfg: &Config{
Targets: []*targetConfig{
{
URL: "invalid://endpoint: 12efg",
Host: "endpoint: 12efg",
},
},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: fmt.Errorf("%w: %s", errInvalidURL, `parse "invalid://endpoint: 12efg": invalid port ": 12efg" after host`),
expectedErr: fmt.Errorf("%w: %s", errInvalidHost, "provided port is not a number: 12efg"),
},
{
desc: "invalid config with multiple targets",
cfg: &Config{
Targets: []*targetConfig{
{
URL: "invalid://endpoint: 12efg",
Host: "endpoint: 12efg",
},
{
URL: "https://example.com",
Host: "https://example.com:80",
},
},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: fmt.Errorf("%w: %s", errInvalidURL, `parse "invalid://endpoint: 12efg": invalid port ": 12efg" after host`),
expectedErr: fmt.Errorf("%w: %s", errInvalidHost, `provided port is not a number: 12efg; host contains a scheme, which is not allowed: https://example.com:80`),
},
{
desc: "port out of range",
cfg: &Config{
Targets: []*targetConfig{
{
Host: "www.opentelemetry.io:67000",
},
},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: fmt.Errorf("%w: %s", errInvalidHost, `provided port is out of valid range (1-65535): 67000`),
},
{
desc: "missing scheme",
cfg: &Config{
Targets: []*targetConfig{
{
URL: "www.opentelemetry.io/docs",
Host: "www.opentelemetry.io/docs",
},
},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: fmt.Errorf("%w: %s", errInvalidURL, `parse "www.opentelemetry.io/docs": invalid URI for request`),
expectedErr: fmt.Errorf("%w: %s", errInvalidHost, `address www.opentelemetry.io/docs: missing port in address`),
},
{
desc: "valid config",
cfg: &Config{
Targets: []*targetConfig{
{
URL: "https://opentelemetry.io",
Host: "opentelemetry.io:443",
},
{
Host: "opentelemetry.io:8080",
},
{
URL: "https://opentelemetry.io:80/docs",
Host: "111.222.33.44:10000",
},
},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
Expand Down
7 changes: 1 addition & 6 deletions receiver/tlscheckreceiver/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,4 @@ Time in seconds until certificate expiry, as specified by `NotAfter` field in th
| ---- | ----------- | ------ |
| tlscheck.x509.issuer | The entity that issued the certificate. | Any Str |
| tlscheck.x509.cn | The commonName in the subject of the certificate. | Any Str |

## Resource Attributes

| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| tlscheck.url | Url at which the certificate was accessed. | Any Str | true |
| tlscheck.host | Host at which the certificate was accessed. | Any Str |
9 changes: 6 additions & 3 deletions receiver/tlscheckreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package tlscheckreceiver // import "github.com/open-telemetry/opentelemetry-coll
import (
"context"
"errors"
"time"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
Expand All @@ -19,7 +20,6 @@ var (
errConfigNotTLSCheck = errors.New(`invalid config`)
)

// NewFactory creates a new filestats receiver factory.
func NewFactory() receiver.Factory {
return receiver.NewFactory(
metadata.Type,
Expand All @@ -28,8 +28,11 @@ func NewFactory() receiver.Factory {
}

func newDefaultConfig() component.Config {
cfg := scraperhelper.NewDefaultControllerConfig()
cfg.CollectionInterval = 60 * time.Second

return &Config{
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
ControllerConfig: cfg,
MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
Targets: []*targetConfig{},
}
Expand All @@ -46,7 +49,7 @@ func newReceiver(
return nil, errConfigNotTLSCheck
}

mp := newScraper(tlsCheckConfig, settings)
mp := newScraper(tlsCheckConfig, settings, getConnectionState)
s, err := scraperhelper.NewScraper(metadata.Type, mp.scrape)
if err != nil {
return nil, err
Expand Down
1 change: 0 additions & 1 deletion receiver/tlscheckreceiver/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ require (
go.opentelemetry.io/collector/confmap v1.17.1-0.20241008154146-ea48c09c31ae
go.opentelemetry.io/collector/consumer v0.111.1-0.20241008154146-ea48c09c31ae
go.opentelemetry.io/collector/consumer/consumertest v0.111.1-0.20241008154146-ea48c09c31ae
go.opentelemetry.io/collector/filter v0.111.1-0.20241008154146-ea48c09c31ae
go.opentelemetry.io/collector/pdata v1.17.1-0.20241008154146-ea48c09c31ae
go.opentelemetry.io/collector/receiver v0.111.1-0.20241008154146-ea48c09c31ae
go.uber.org/goleak v1.3.0
Expand Down
2 changes: 0 additions & 2 deletions receiver/tlscheckreceiver/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 2 additions & 44 deletions receiver/tlscheckreceiver/internal/metadata/generated_config.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 05448b6

Please sign in to comment.