Skip to content
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Main (unreleased)

- (_Experimental_) Add `pyroscope.enrich` component to enrich profiles using labels from `discovery.*` components. (@AndreZiviani)

- Add htpasswd file based authentication for `otelcol.auth.basic` (@pkarakal)

### Enhancements

- update promtail converter to use `file_match` block for `loki.source.file` instead of going through `local.file_match`. (@kalleep)
Expand Down
187 changes: 181 additions & 6 deletions docs/sources/reference/components/otelcol/otelcol.auth.basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,68 @@ otelcol.auth.basic "<LABEL>" {

## Arguments

{{< admonition type="caution" >}}
Don't use the top-level `username` and `password` arguments for new configurations as they are deprecated. Use the `client_auth` block for client authentication and the `htpasswd` block for server authentication instead.
{{< /admonition >}}

You can use the following arguments with `otelcol.auth.basic`:

| Name | Type | Description | Default | Required |
| ---------- | -------- | -------------------------------------------------- | ------- | -------- |
| `password` | `secret` | Password to use for basic authentication requests. | | yes |
| `username` | `string` | Username to use for basic authentication requests. | | yes |
| Name | Type | Description | Default | Required |
|------------|----------|-----------------------------------------------------------------|---------|----------|
| `password` | `secret` | (Deprecated) Password to use for basic authentication requests. | | no |
| `username` | `string` | (Deprecated) Username to use for basic authentication requests. | | no |


## Blocks

You can use the following block with `otelcol.auth.basic`:

| Block | Description | Required |
| -------------------------------- | -------------------------------------------------------------------------- | -------- |
|----------------------------------|----------------------------------------------------------------------------|----------|
| [`client_auth`][client_auth] | Configures client authentication credentials for exporters. | no |
| [`debug_metrics`][debug_metrics] | Configures the metrics that this component generates to monitor its state. | no |
| [`htpasswd`][htpasswd] | Configures server authentication using htpasswd format for receivers. | no |


[client_auth]: #client_auth
[debug_metrics]: #debug_metrics
[htpasswd]: #htpasswd

### `client_auth`

The `client_auth` block configures credentials that client extensions (such as exporters) use to authenticate to servers.

| Name | Type | Description | Default | Required |
| ---------- | -------- | -------------------------------------------------- | ------- | -------- |
| `password` | `string` | Password to use for basic authentication requests. | | yes |
| `username` | `string` | Username to use for basic authentication requests. | | yes |

{{< admonition type="note" >}}
When you specify both the `client_auth` block and the deprecated top-level `username` and `password` attributes, the `client_auth` block takes precedence and {{< param "PRODUCT_NAME" >}} ignores the top-level attributes for client authentication.
{{< /admonition >}}

### `debug_metrics`

{{< docs/shared lookup="reference/components/otelcol-debug-metrics-block.md" source="alloy" version="<ALLOY_VERSION>" >}}

### `htpasswd`

The `htpasswd` block configures how server extensions (such as receivers) authenticate incoming requests using the `htpasswd` format.

| Name | Type | Description | Default | Required |
| -------- | -------- | --------------------------------------------------------------------- | ------- | -------- |
| `file` | `string` | Path to the `htpasswd` file to use for basic authentication requests. | `""` | no |
| `inline` | `string` | The `htpasswd` file content in inline format. | `""` | no |

You can specify either `file`, `inline`, or both.
When you use `inline`, the format should be `username:password` with each user on a new line.

{{< admonition type="note" >}}
When you specify both the `htpasswd` block and the deprecated top-level `username` and `password` attributes, {{< param "PRODUCT_NAME" >}} automatically appends the deprecated credentials to the `inline` content.
This allows authentication using credentials from both the `htpasswd` configuration and the deprecated attributes.
If the same username appears in both the `file` and `inline` content, including appended deprecated credentials, the entry in the `inline` content takes precedence.
{{< /admonition >}}

## Exported fields

The following fields are exported and can be referenced by other components:
Expand All @@ -73,7 +114,11 @@ The following fields are exported and can be referenced by other components:

`otelcol.auth.basic` doesn't expose any component-specific debug information.

## Example
## Examples

This section includes examples to help you configure basic authentication for exporters and receivers.

### Forward signals to exporters

This example configures [`otelcol.exporter.otlp`][otelcol.exporter.otlp] to use basic authentication:

Expand All @@ -91,4 +136,134 @@ otelcol.auth.basic "creds" {
}
```

### Authenticating requests for receivers

These examples show how to perform basic authentication using the `client_auth` block for exporters or the `htpasswd` block for receivers.

#### Use client authentication
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the difference between this example and the Forward signals to exporters example?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The difference is that the first (forward signals to exporters uses the deprecated username and password top-level arguments whereas this uses the new client authentication block. Since I have split this into client and server authentication examples, I think I should just move this a below Forward signals to exporters.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think pairing them together would be better, but similar to the other thread I'm not a fan of showing deprecated fields in config examples in most cases. Let's see what our docs team says.


This example configures [`otelcol.exporter.otlp`][otelcol.exporter.otlp] to use basic authentication with a single username and password combination:

```alloy
otelcol.receiver.otlp "example" {
grpc {
endpoint = "127.0.0.1:4317"
}

output {
metrics = [otelcol.exporter.otlp.default.input]
logs = [otelcol.exporter.otlp.default.input]
traces = [otelcol.exporter.otlp.default.input]
}
}

otelcol.exporter.otlp "default" {
client {
endpoint = "my-otlp-grpc-server:4317"
auth = otelcol.auth.basic.creds.handler
}
}

otelcol.auth.basic "creds" {
client_auth {
username = "demo"
password = sys.env("API_KEY")
}
}
```

{{< admonition type="note" >}}
To migrate from the deprecated `username` and `password` attributes, move them into the `client_auth` block for client authentication.
{{< /admonition >}}


#### Use htpasswd file

This example configures [`otelcol.receiver.otlp`][otelcol.receiver.otlp] to use basic authentication using an `htpasswd` file containing the users to use for basic authentication:

```alloy
otelcol.receiver.otlp "example" {
grpc {
endpoint = "127.0.0.1:4317"

auth = otelcol.auth.basic.creds.handler
}

output {
metrics = [otelcol.exporter.debug.default.input]
logs = [otelcol.exporter.debug.default.input]
traces = [otelcol.exporter.debug.default.input]
}
}

otelcol.exporter.debug "default" {}

otelcol.auth.basic "creds" {
htpasswd {
file = "/etc/alloy/.htpasswd"
}
}
```

#### Use htpasswd inline content

This example shows how to specify `htpasswd` content directly in the configuration:

```alloy
otelcol.receiver.otlp "example" {
grpc {
endpoint = "127.0.0.1:4317"

auth = otelcol.auth.basic.creds.handler
}

output {
metrics = [otelcol.exporter.debug.default.input]
logs = [otelcol.exporter.debug.default.input]
traces = [otelcol.exporter.debug.default.input]
}
}

otelcol.exporter.debug "default" {}

otelcol.auth.basic "creds" {
htpasswd {
inline = "user1:password1\nuser2:password2"
}
}
```

{{< admonition type="note" >}}
To make the migration from the deprecated `username` and `password` attributes easier, you can specify both the deprecated attributes and the `htpasswd` block in the same configuration.
{{< param "PRODUCT_NAME" >}} appends the deprecated attributes to the `htpasswd` content.

```alloy
otelcol.receiver.otlp "example" {
grpc {
endpoint = "127.0.0.1:4317"

auth = otelcol.auth.basic.creds.handler
}

output {
metrics = [otelcol.exporter.debug.default.input]
logs = [otelcol.exporter.debug.default.input]
traces = [otelcol.exporter.debug.default.input]
}
}

otelcol.exporter.debug "default" {}

otelcol.auth.basic "creds" {
username = "demo"
password = sys.env("API_KEY")

htpasswd {
file = "/etc/alloy/.htpasswd"
}
}
```
{{< /admonition >}}

[otelcol.receiver.otlp]: ../otelcol.receiver.otlp/
[otelcol.exporter.otlp]: ../otelcol.exporter.otlp/
95 changes: 82 additions & 13 deletions internal/component/otelcol/auth/basic/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package basic

import (
"errors"
"fmt"

"github.com/grafana/alloy/internal/component"
Expand All @@ -15,6 +16,11 @@ import (
"go.opentelemetry.io/collector/pipeline"
)

var (
errNoCredentialSource = errors.New("no credential source provided")
errNoPasswordProvided = errors.New("no password provided")
)

func init() {
component.Register(component.Registration{
Name: "otelcol.auth.basic",
Expand All @@ -29,10 +35,41 @@ func init() {
})
}

type HtpasswdConfig struct {
File string `alloy:"file,attr,optional"`
Inline string `alloy:"inline,attr,optional"`
}

func (c HtpasswdConfig) convert() *basicauthextension.HtpasswdSettings {
return &basicauthextension.HtpasswdSettings{
File: c.File,
Inline: c.Inline,
}
}

type ClientAuthConfig struct {
Username string `alloy:"username,attr"`
Password string `alloy:"password,attr"`
}

func (c ClientAuthConfig) convert() *basicauthextension.ClientAuthSettings {
if c.Username == "" && c.Password == "" {
return nil
}
return &basicauthextension.ClientAuthSettings{
Username: c.Username,
Password: configopaque.String(c.Password),
}
}

// Arguments configures the otelcol.auth.basic component.
type Arguments struct {
Username string `alloy:"username,attr"`
Password alloytypes.Secret `alloy:"password,attr"`
Username string `alloy:"username,attr,optional"` // Deprecated: Use ClientAuth instead
Password alloytypes.Secret `alloy:"password,attr,optional"` // Deprecated: Use ClientAuth instead

ClientAuth *ClientAuthConfig `alloy:"client_auth,block,optional"`

Htpasswd *HtpasswdConfig `alloy:"htpasswd,block,optional"`

// DebugMetrics configures component internal metrics. Optional.
DebugMetrics otelcolCfg.DebugMetricsArguments `alloy:"debug_metrics,block,optional"`
Expand All @@ -45,23 +82,55 @@ func (args *Arguments) SetToDefault() {
args.DebugMetrics.SetToDefault()
}

// Validate implements syntax.Validator
func (args Arguments) Validate() error {
// check if no argument was provided
if args.Username == "" && args.Password == "" && args.Htpasswd == nil && args.ClientAuth == nil {
return errNoCredentialSource
}
// the downstream basicauthextension package supports having both inline
// and htpasswd files, so we should not error out in case both are
// provided

// check if password was not provided when username is provided
if args.Username != "" && args.Password == "" {
return errNoPasswordProvided
}

return nil
}

// ConvertClient implements auth.Arguments.
func (args Arguments) ConvertClient() (otelcomponent.Config, error) {
return &basicauthextension.Config{
ClientAuth: &basicauthextension.ClientAuthSettings{
Username: args.Username,
Password: configopaque.String(args.Password),
},
}, nil
c := &basicauthextension.Config{}
// If the client config is specified, ignore the deprecated
// username and password attributes.
if args.ClientAuth != nil {
c.ClientAuth = args.ClientAuth.convert()
return c, nil
}

c.ClientAuth = &basicauthextension.ClientAuthSettings{
Username: args.Username,
Password: configopaque.String(args.Password),
}
return c, nil
}

// ConvertServer implements auth.Arguments.
func (args Arguments) ConvertServer() (otelcomponent.Config, error) {
return &basicauthextension.Config{
Htpasswd: &basicauthextension.HtpasswdSettings{
Inline: fmt.Sprintf("%s:%s", args.Username, args.Password),
},
}, nil
c := &basicauthextension.Config{
Htpasswd: &basicauthextension.HtpasswdSettings{},
Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be better if this is only created if args.HtpasswdFile is not empty.

}
if args.Htpasswd != nil {
c.Htpasswd = args.Htpasswd.convert()
}
// Keeping this to avoid breaking existing use cases. Remove this for v2
if args.Username != "" && args.Password != "" {
c.Htpasswd.Inline += fmt.Sprintf("\n%s:%s", args.Username, args.Password)
}

return c, nil
}

// AuthFeatures implements auth.Arguments.
Expand Down
Loading
Loading