Skip to content

Commit c564d09

Browse files
pkarakaldehaansa
andauthored
feat(otelcol): add support for htpasswd file authentication (#3916)
* feat(otelcol): add support for htpasswd file authentication * feat(otelcol): add client authentication block to basic auth config * fixup! feat(otelcol): add client authentication block to basic auth config * Update changelog --------- Co-authored-by: Sam DeHaan <[email protected]>
1 parent bc3885b commit c564d09

File tree

4 files changed

+460
-82
lines changed

4 files changed

+460
-82
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Main (unreleased)
2020

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

23+
- Add htpasswd file based authentication for `otelcol.auth.basic` (@pkarakal)
24+
2325
### Enhancements
2426

2527
- update promtail converter to use `file_match` block for `loki.source.file` instead of going through `local.file_match`. (@kalleep)

docs/sources/reference/components/otelcol/otelcol.auth.basic.md

Lines changed: 181 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,68 @@ otelcol.auth.basic "<LABEL>" {
3636

3737
## Arguments
3838

39+
{{< admonition type="caution" >}}
40+
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.
41+
{{< /admonition >}}
42+
3943
You can use the following arguments with `otelcol.auth.basic`:
4044

41-
| Name | Type | Description | Default | Required |
42-
| ---------- | -------- | -------------------------------------------------- | ------- | -------- |
43-
| `password` | `secret` | Password to use for basic authentication requests. | | yes |
44-
| `username` | `string` | Username to use for basic authentication requests. | | yes |
45+
| Name | Type | Description | Default | Required |
46+
|------------|----------|-----------------------------------------------------------------|---------|----------|
47+
| `password` | `secret` | (Deprecated) Password to use for basic authentication requests. | | no |
48+
| `username` | `string` | (Deprecated) Username to use for basic authentication requests. | | no |
49+
4550

4651
## Blocks
4752

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

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

61+
62+
[client_auth]: #client_auth
5463
[debug_metrics]: #debug_metrics
64+
[htpasswd]: #htpasswd
65+
66+
### `client_auth`
67+
68+
The `client_auth` block configures credentials that client extensions (such as exporters) use to authenticate to servers.
69+
70+
| Name | Type | Description | Default | Required |
71+
| ---------- | -------- | -------------------------------------------------- | ------- | -------- |
72+
| `password` | `string` | Password to use for basic authentication requests. | | yes |
73+
| `username` | `string` | Username to use for basic authentication requests. | | yes |
74+
75+
{{< admonition type="note" >}}
76+
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.
77+
{{< /admonition >}}
5578

5679
### `debug_metrics`
5780

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

83+
### `htpasswd`
84+
85+
The `htpasswd` block configures how server extensions (such as receivers) authenticate incoming requests using the `htpasswd` format.
86+
87+
| Name | Type | Description | Default | Required |
88+
| -------- | -------- | --------------------------------------------------------------------- | ------- | -------- |
89+
| `file` | `string` | Path to the `htpasswd` file to use for basic authentication requests. | `""` | no |
90+
| `inline` | `string` | The `htpasswd` file content in inline format. | `""` | no |
91+
92+
You can specify either `file`, `inline`, or both.
93+
When you use `inline`, the format should be `username:password` with each user on a new line.
94+
95+
{{< admonition type="note" >}}
96+
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.
97+
This allows authentication using credentials from both the `htpasswd` configuration and the deprecated attributes.
98+
If the same username appears in both the `file` and `inline` content, including appended deprecated credentials, the entry in the `inline` content takes precedence.
99+
{{< /admonition >}}
100+
60101
## Exported fields
61102

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

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

76-
## Example
117+
## Examples
118+
119+
This section includes examples to help you configure basic authentication for exporters and receivers.
120+
121+
### Forward signals to exporters
77122

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

@@ -91,4 +136,134 @@ otelcol.auth.basic "creds" {
91136
}
92137
```
93138

139+
### Authenticating requests for receivers
140+
141+
These examples show how to perform basic authentication using the `client_auth` block for exporters or the `htpasswd` block for receivers.
142+
143+
#### Use client authentication
144+
145+
This example configures [`otelcol.exporter.otlp`][otelcol.exporter.otlp] to use basic authentication with a single username and password combination:
146+
147+
```alloy
148+
otelcol.receiver.otlp "example" {
149+
grpc {
150+
endpoint = "127.0.0.1:4317"
151+
}
152+
153+
output {
154+
metrics = [otelcol.exporter.otlp.default.input]
155+
logs = [otelcol.exporter.otlp.default.input]
156+
traces = [otelcol.exporter.otlp.default.input]
157+
}
158+
}
159+
160+
otelcol.exporter.otlp "default" {
161+
client {
162+
endpoint = "my-otlp-grpc-server:4317"
163+
auth = otelcol.auth.basic.creds.handler
164+
}
165+
}
166+
167+
otelcol.auth.basic "creds" {
168+
client_auth {
169+
username = "demo"
170+
password = sys.env("API_KEY")
171+
}
172+
}
173+
```
174+
175+
{{< admonition type="note" >}}
176+
To migrate from the deprecated `username` and `password` attributes, move them into the `client_auth` block for client authentication.
177+
{{< /admonition >}}
178+
179+
180+
#### Use htpasswd file
181+
182+
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:
183+
184+
```alloy
185+
otelcol.receiver.otlp "example" {
186+
grpc {
187+
endpoint = "127.0.0.1:4317"
188+
189+
auth = otelcol.auth.basic.creds.handler
190+
}
191+
192+
output {
193+
metrics = [otelcol.exporter.debug.default.input]
194+
logs = [otelcol.exporter.debug.default.input]
195+
traces = [otelcol.exporter.debug.default.input]
196+
}
197+
}
198+
199+
otelcol.exporter.debug "default" {}
200+
201+
otelcol.auth.basic "creds" {
202+
htpasswd {
203+
file = "/etc/alloy/.htpasswd"
204+
}
205+
}
206+
```
207+
208+
#### Use htpasswd inline content
209+
210+
This example shows how to specify `htpasswd` content directly in the configuration:
211+
212+
```alloy
213+
otelcol.receiver.otlp "example" {
214+
grpc {
215+
endpoint = "127.0.0.1:4317"
216+
217+
auth = otelcol.auth.basic.creds.handler
218+
}
219+
220+
output {
221+
metrics = [otelcol.exporter.debug.default.input]
222+
logs = [otelcol.exporter.debug.default.input]
223+
traces = [otelcol.exporter.debug.default.input]
224+
}
225+
}
226+
227+
otelcol.exporter.debug "default" {}
228+
229+
otelcol.auth.basic "creds" {
230+
htpasswd {
231+
inline = "user1:password1\nuser2:password2"
232+
}
233+
}
234+
```
235+
236+
{{< admonition type="note" >}}
237+
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.
238+
{{< param "PRODUCT_NAME" >}} appends the deprecated attributes to the `htpasswd` content.
239+
240+
```alloy
241+
otelcol.receiver.otlp "example" {
242+
grpc {
243+
endpoint = "127.0.0.1:4317"
244+
245+
auth = otelcol.auth.basic.creds.handler
246+
}
247+
248+
output {
249+
metrics = [otelcol.exporter.debug.default.input]
250+
logs = [otelcol.exporter.debug.default.input]
251+
traces = [otelcol.exporter.debug.default.input]
252+
}
253+
}
254+
255+
otelcol.exporter.debug "default" {}
256+
257+
otelcol.auth.basic "creds" {
258+
username = "demo"
259+
password = sys.env("API_KEY")
260+
261+
htpasswd {
262+
file = "/etc/alloy/.htpasswd"
263+
}
264+
}
265+
```
266+
{{< /admonition >}}
267+
268+
[otelcol.receiver.otlp]: ../otelcol.receiver.otlp/
94269
[otelcol.exporter.otlp]: ../otelcol.exporter.otlp/

internal/component/otelcol/auth/basic/basic.go

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package basic
33

44
import (
5+
"errors"
56
"fmt"
67

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

19+
var (
20+
errNoCredentialSource = errors.New("no credential source provided")
21+
errNoPasswordProvided = errors.New("no password provided")
22+
)
23+
1824
func init() {
1925
component.Register(component.Registration{
2026
Name: "otelcol.auth.basic",
@@ -29,10 +35,41 @@ func init() {
2935
})
3036
}
3137

38+
type HtpasswdConfig struct {
39+
File string `alloy:"file,attr,optional"`
40+
Inline string `alloy:"inline,attr,optional"`
41+
}
42+
43+
func (c HtpasswdConfig) convert() *basicauthextension.HtpasswdSettings {
44+
return &basicauthextension.HtpasswdSettings{
45+
File: c.File,
46+
Inline: c.Inline,
47+
}
48+
}
49+
50+
type ClientAuthConfig struct {
51+
Username string `alloy:"username,attr"`
52+
Password string `alloy:"password,attr"`
53+
}
54+
55+
func (c ClientAuthConfig) convert() *basicauthextension.ClientAuthSettings {
56+
if c.Username == "" && c.Password == "" {
57+
return nil
58+
}
59+
return &basicauthextension.ClientAuthSettings{
60+
Username: c.Username,
61+
Password: configopaque.String(c.Password),
62+
}
63+
}
64+
3265
// Arguments configures the otelcol.auth.basic component.
3366
type Arguments struct {
34-
Username string `alloy:"username,attr"`
35-
Password alloytypes.Secret `alloy:"password,attr"`
67+
Username string `alloy:"username,attr,optional"` // Deprecated: Use ClientAuth instead
68+
Password alloytypes.Secret `alloy:"password,attr,optional"` // Deprecated: Use ClientAuth instead
69+
70+
ClientAuth *ClientAuthConfig `alloy:"client_auth,block,optional"`
71+
72+
Htpasswd *HtpasswdConfig `alloy:"htpasswd,block,optional"`
3673

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

85+
// Validate implements syntax.Validator
86+
func (args Arguments) Validate() error {
87+
// check if no argument was provided
88+
if args.Username == "" && args.Password == "" && args.Htpasswd == nil && args.ClientAuth == nil {
89+
return errNoCredentialSource
90+
}
91+
// the downstream basicauthextension package supports having both inline
92+
// and htpasswd files, so we should not error out in case both are
93+
// provided
94+
95+
// check if password was not provided when username is provided
96+
if args.Username != "" && args.Password == "" {
97+
return errNoPasswordProvided
98+
}
99+
100+
return nil
101+
}
102+
48103
// ConvertClient implements auth.Arguments.
49104
func (args Arguments) ConvertClient() (otelcomponent.Config, error) {
50-
return &basicauthextension.Config{
51-
ClientAuth: &basicauthextension.ClientAuthSettings{
52-
Username: args.Username,
53-
Password: configopaque.String(args.Password),
54-
},
55-
}, nil
105+
c := &basicauthextension.Config{}
106+
// If the client config is specified, ignore the deprecated
107+
// username and password attributes.
108+
if args.ClientAuth != nil {
109+
c.ClientAuth = args.ClientAuth.convert()
110+
return c, nil
111+
}
112+
113+
c.ClientAuth = &basicauthextension.ClientAuthSettings{
114+
Username: args.Username,
115+
Password: configopaque.String(args.Password),
116+
}
117+
return c, nil
56118
}
57119

58120
// ConvertServer implements auth.Arguments.
59121
func (args Arguments) ConvertServer() (otelcomponent.Config, error) {
60-
return &basicauthextension.Config{
61-
Htpasswd: &basicauthextension.HtpasswdSettings{
62-
Inline: fmt.Sprintf("%s:%s", args.Username, args.Password),
63-
},
64-
}, nil
122+
c := &basicauthextension.Config{
123+
Htpasswd: &basicauthextension.HtpasswdSettings{},
124+
}
125+
if args.Htpasswd != nil {
126+
c.Htpasswd = args.Htpasswd.convert()
127+
}
128+
// Keeping this to avoid breaking existing use cases. Remove this for v2
129+
if args.Username != "" && args.Password != "" {
130+
c.Htpasswd.Inline += fmt.Sprintf("\n%s:%s", args.Username, args.Password)
131+
}
132+
133+
return c, nil
65134
}
66135

67136
// AuthFeatures implements auth.Arguments.

0 commit comments

Comments
 (0)