Skip to content

Commit 3e20fda

Browse files
authored
Add OAuth2 Support in Webhook Receivers (#338)
This change introduces HTTPClientConfig with an OAuth2 field to support the client_credentials grant type in webhook receivers. The structure mirrors Alertmanager’s http_config to align with upstream conventions, with the goal of extending this approach to other notifier integrations and standardizing common HTTP client configuration across notifier types.
1 parent 0223509 commit 3e20fda

File tree

13 files changed

+1075
-56
lines changed

13 files changed

+1075
-56
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ require (
1919
github.com/prometheus/client_golang v1.20.4
2020
github.com/prometheus/common v0.60.1
2121
github.com/stretchr/testify v1.9.0
22+
golang.org/x/net v0.29.0
23+
golang.org/x/oauth2 v0.23.0
2224
golang.org/x/sync v0.8.0
2325
gopkg.in/mail.v2 v2.3.1
2426
gopkg.in/yaml.v3 v3.0.1
@@ -97,8 +99,6 @@ require (
9799
go.opentelemetry.io/otel/trace v1.30.0 // indirect
98100
golang.org/x/crypto v0.27.0 // indirect
99101
golang.org/x/mod v0.17.0 // indirect
100-
golang.org/x/net v0.29.0 // indirect
101-
golang.org/x/oauth2 v0.23.0 // indirect
102102
golang.org/x/sys v0.25.0 // indirect
103103
golang.org/x/text v0.18.0 // indirect
104104
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect

http/client.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"github.com/benbjohnson/clock"
1616
"github.com/go-kit/log"
1717
"github.com/go-kit/log/level"
18+
"golang.org/x/oauth2"
19+
1820
commoncfg "github.com/prometheus/common/config"
1921

2022
"github.com/grafana/alerting/receivers"
@@ -23,19 +25,21 @@ import (
2325
var ErrInvalidMethod = errors.New("webhook only supports HTTP methods PUT or POST")
2426

2527
type clientConfiguration struct {
26-
userAgent string
27-
dialer net.Dialer // We use Dialer here instead of DialContext as our mqtt client doesn't support DialContext.
28-
customDialer bool
28+
userAgent string
29+
dialer net.Dialer // We use Dialer here instead of DialContext as our mqtt client doesn't support DialContext.
30+
customDialer bool
31+
httpClientConfig HTTPClientConfig
2932
}
3033

3134
// defaultDialTimeout is the default timeout for the dialer, 30 seconds to match http.DefaultTransport.
3235
const defaultDialTimeout = 30 * time.Second
3336

3437
type Client struct {
35-
cfg clientConfiguration
38+
cfg clientConfiguration
39+
oauth2TokenSource oauth2.TokenSource
3640
}
3741

38-
func NewClient(opts ...ClientOption) *Client {
42+
func NewClient(opts ...ClientOption) (*Client, error) {
3943
cfg := clientConfiguration{
4044
userAgent: "Grafana",
4145
dialer: net.Dialer{},
@@ -49,9 +53,25 @@ func NewClient(opts ...ClientOption) *Client {
4953
// Mostly defensive to ensure that timeout semantics don't change when given a custom dialer without a timeout.
5054
cfg.dialer.Timeout = defaultDialTimeout
5155
}
52-
return &Client{
56+
57+
client := &Client{
5358
cfg: cfg,
5459
}
60+
61+
if cfg.httpClientConfig.OAuth2 != nil {
62+
if err := ValidateOAuth2Config(cfg.httpClientConfig.OAuth2); err != nil {
63+
return nil, fmt.Errorf("invalid OAuth2 configuration: %w", err)
64+
}
65+
// If the user has provided an OAuth2 config, we need to prepare the OAuth2 token source. This needs to
66+
// be stored outside of the request so that the token expiration/re-use will work as expected.
67+
tokenSource, err := NewOAuth2TokenSource(cfg)
68+
if err != nil {
69+
return nil, err
70+
}
71+
client.oauth2TokenSource = tokenSource
72+
}
73+
74+
return client, nil
5575
}
5676

5777
type ClientOption func(*clientConfiguration)
@@ -69,6 +89,14 @@ func WithDialer(dialer net.Dialer) ClientOption {
6989
}
7090
}
7191

92+
func WithHTTPClientConfig(config *HTTPClientConfig) ClientOption {
93+
return func(c *clientConfiguration) {
94+
if config != nil {
95+
c.httpClientConfig = *config
96+
}
97+
}
98+
}
99+
72100
func ToHTTPClientOption(option ...ClientOption) []commoncfg.HTTPClientOption {
73101
cfg := clientConfiguration{}
74102
for _, opt := range option {
@@ -140,6 +168,11 @@ func (ns *Client) SendWebhook(ctx context.Context, l log.Logger, webhook *receiv
140168
}
141169
}
142170

171+
if ns.oauth2TokenSource != nil {
172+
level.Debug(l).Log("msg", "Adding OAuth2 roundtripper to client")
173+
client.Transport = NewOAuth2RoundTripper(ns.oauth2TokenSource, client.Transport)
174+
}
175+
143176
resp, err := client.Do(request)
144177
if err != nil {
145178
return redactURL(err)

0 commit comments

Comments
 (0)