Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[receiver/tlscheck] Inital Commit of TLS Check Receiver #35441

Merged
merged 5 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .chloggen/tlscheckreceiver-addition.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: new_component

# 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: Add TLS Check Receiver component to monitor x.509 certificate expiry

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

# (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]
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ receiver/sshcheckreceiver/ @open-teleme
receiver/statsdreceiver/ @open-telemetry/collector-contrib-approvers @jmacd @dmitryax
receiver/syslogreceiver/ @open-telemetry/collector-contrib-approvers @djaglowski @andrzej-stencel
receiver/tcplogreceiver/ @open-telemetry/collector-contrib-approvers @djaglowski
receiver/tlscheckreceiver/ @open-telemetry/collector-contrib-approvers @atoulme @michael-burt
receiver/udplogreceiver/ @open-telemetry/collector-contrib-approvers @djaglowski
receiver/vcenterreceiver/ @open-telemetry/collector-contrib-approvers @djaglowski @schmikei @StefanKurek
receiver/wavefrontreceiver/ @open-telemetry/collector-contrib-approvers @samiura
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ body:
- receiver/statsd
- receiver/syslog
- receiver/tcplog
- receiver/tlscheck
- receiver/udplog
- receiver/vcenter
- receiver/wavefront
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ body:
- receiver/statsd
- receiver/syslog
- receiver/tcplog
- receiver/tlscheck
- receiver/udplog
- receiver/vcenter
- receiver/wavefront
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/other.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ body:
- receiver/statsd
- receiver/syslog
- receiver/tcplog
- receiver/tlscheck
- receiver/udplog
- receiver/vcenter
- receiver/wavefront
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/unmaintained.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ body:
- receiver/statsd
- receiver/syslog
- receiver/tcplog
- receiver/tlscheck
- receiver/udplog
- receiver/vcenter
- receiver/wavefront
Expand Down
1 change: 1 addition & 0 deletions receiver/tlscheckreceiver/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
37 changes: 37 additions & 0 deletions receiver/tlscheckreceiver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# TLS Check Receiver

crobert-1 marked this conversation as resolved.
Show resolved Hide resolved
<!-- status autogenerated section -->
| Status | |
| ------------- |-----------|
| Stability | [development]: metrics |
| Distributions | [contrib] |
| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Ftlscheck%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ftlscheck) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Ftlscheck%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Ftlscheck) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@atoulme](https://www.github.com/atoulme), [@michael-burt](https://www.github.com/michael-burt) |

[development]: https://github.com/open-telemetry/opentelemetry-collector#development
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
<!-- end autogenerated section -->

Emit metrics about x.509 certificates.

## Getting Started

By default, the TLS Check Receiver will emit a single metric, `tlscheck.time_left`, per target. This is measured in seconds until the date and time specified in the `NotAfter` field of the x.509 certificate. After certificate expiration, the metric value will be a negative integer measuring the time in seconds since expiry.

## Example Configuration

```yaml
receivers:
tlscheck:
targets:
- url: https://example.com
- url: https://foobar.com:8080
```

## Certificate Verification

This component does not provide hostname, validity period, path, or CRL / OCSP verification on the certificate.

## Metrics

Details about the metrics produced by this receiver can be found in [metadata.yaml](./metadata.yaml).
63 changes: 63 additions & 0 deletions receiver/tlscheckreceiver/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package tlscheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tlscheckreceiver"

import (
"errors"
"fmt"
"net/url"

"go.opentelemetry.io/collector/receiver/scraperhelper"
"go.uber.org/multierr"

"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tlscheckreceiver/internal/metadata"
)

// 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>]`)
)

// Config defines the configuration for the various elements of the receiver agent.
type Config struct {
scraperhelper.ControllerConfig `mapstructure:",squash"`
metadata.MetricsBuilderConfig `mapstructure:",squash"`
Targets []*targetConfig `mapstructure:"targets"`
}

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

// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the expected behavior (of validate && the receiver) for a non-https URL? I would expect it to be invalid at validate time, as I don't see a good mechanism for returning that information at runtime in a way that would be consumable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point and something definitely overlooked.

For the sake of the PR being open so long and becoming harder to merge, I would love to see this as a follow up PR instead updating the current PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should allow for scheme. We do not need to send an http request here, only establish tcp connection, so scheme doesn't make sense. Related, I think we should change url to host since we are connecting to a host address via tcp, not sending a http request to a url.

if parseErr != nil {
err = multierr.Append(err, fmt.Errorf("%s: %w", errInvalidURL.Error(), parseErr))
}
}

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)
}

for _, target := range cfg.Targets {
MovieStoreGuy marked this conversation as resolved.
Show resolved Hide resolved
err = multierr.Append(err, target.Validate())
}

return err
}
95 changes: 95 additions & 0 deletions receiver/tlscheckreceiver/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package tlscheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tlscheckreceiver"

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/receiver/scraperhelper"
)

func TestValidate(t *testing.T) {
testCases := []struct {
desc string
cfg *Config
expectedErr error
}{
{
desc: "missing url",
cfg: &Config{
Targets: []*targetConfig{},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: errMissingURL,
},
{
desc: "invalid url",
cfg: &Config{
Targets: []*targetConfig{
{
URL: "invalid://endpoint: 12efg",
},
},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: fmt.Errorf("%w: %s", errInvalidURL, `parse "invalid://endpoint: 12efg": invalid port ": 12efg" after host`),
},
{
desc: "invalid config with multiple targets",
cfg: &Config{
Targets: []*targetConfig{
{
URL: "invalid://endpoint: 12efg",
},
{
URL: "https://example.com",
},
},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: fmt.Errorf("%w: %s", errInvalidURL, `parse "invalid://endpoint: 12efg": invalid port ": 12efg" after host`),
},
{
desc: "missing scheme",
cfg: &Config{
Targets: []*targetConfig{
{
URL: "www.opentelemetry.io/docs",
},
},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: fmt.Errorf("%w: %s", errInvalidURL, `parse "www.opentelemetry.io/docs": invalid URI for request`),
},
{
desc: "valid config",
cfg: &Config{
Targets: []*targetConfig{
{
URL: "https://opentelemetry.io",
},
{
URL: "https://opentelemetry.io:80/docs",
},
},
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
},
expectedErr: nil,
},
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
actualErr := tc.cfg.Validate()
if tc.expectedErr != nil {
require.EqualError(t, actualErr, tc.expectedErr.Error())
} else {
require.NoError(t, actualErr)
}

})
}
}
6 changes: 6 additions & 0 deletions receiver/tlscheckreceiver/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

//go:generate mdatagen metadata.yaml

package tlscheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tlscheckreceiver"
34 changes: 34 additions & 0 deletions receiver/tlscheckreceiver/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)

# tlscheck

## Default Metrics

The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:

```yaml
metrics:
<metric_name>:
enabled: false
```

### tlscheck.time_left

Time in seconds until certificate expiry, as specified by `NotAfter` field in the x.509 certificate. Negative values represent time in seconds since expiration.

| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| s | Gauge | Int |

#### Attributes

| Name | Description | Values |
| ---- | ----------- | ------ |
| 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 |
62 changes: 62 additions & 0 deletions receiver/tlscheckreceiver/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package tlscheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tlscheckreceiver"

import (
"context"
"errors"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/scraperhelper"

"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tlscheckreceiver/internal/metadata"
)

var (
errConfigNotTLSCheck = errors.New(`invalid config`)
)

// NewFactory creates a new filestats receiver factory.
func NewFactory() receiver.Factory {
return receiver.NewFactory(
metadata.Type,
newDefaultConfig,
receiver.WithMetrics(newReceiver, metadata.MetricsStability))
}

func newDefaultConfig() component.Config {
return &Config{
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
Targets: []*targetConfig{},
}
}

func newReceiver(
_ context.Context,
settings receiver.Settings,
cfg component.Config,
consumer consumer.Metrics,
) (receiver.Metrics, error) {
tlsCheckConfig, ok := cfg.(*Config)
if !ok {
return nil, errConfigNotTLSCheck
}

mp := newScraper(tlsCheckConfig, settings)
s, err := scraperhelper.NewScraper(metadata.Type, mp.scrape)
if err != nil {
return nil, err
}
opt := scraperhelper.AddScraper(s)

return scraperhelper.NewScraperControllerReceiver(
&tlsCheckConfig.ControllerConfig,
settings,
consumer,
opt,
)
}
Loading