mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: instrument external oauth2 requests (#11519)
* chore: instrument external oauth2 requests External requests made by oauth2 configs are now instrumented into prometheus metrics.
This commit is contained in:
@@ -22,19 +22,14 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/coderd/promoauth"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/retry"
|
||||
)
|
||||
|
||||
type OAuth2Config interface {
|
||||
AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string
|
||||
Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error)
|
||||
TokenSource(context.Context, *oauth2.Token) oauth2.TokenSource
|
||||
}
|
||||
|
||||
// Config is used for authentication for Git operations.
|
||||
type Config struct {
|
||||
OAuth2Config
|
||||
promoauth.InstrumentedOAuth2Config
|
||||
// ID is a unique identifier for the authenticator.
|
||||
ID string
|
||||
// Type is the type of provider.
|
||||
@@ -192,12 +187,8 @@ func (c *Config) ValidateToken(ctx context.Context, token string) (bool, *coders
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
cli := http.DefaultClient
|
||||
if v, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
|
||||
cli = v
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
res, err := cli.Do(req)
|
||||
res, err := c.InstrumentedOAuth2Config.Do(ctx, promoauth.SourceValidateToken, req)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
@@ -247,7 +238,7 @@ func (c *Config) AppInstallations(ctx context.Context, token string) ([]codersdk
|
||||
return nil, false, err
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
res, err := c.InstrumentedOAuth2Config.Do(ctx, promoauth.SourceAppInstallations, req)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -287,6 +278,8 @@ func (c *Config) AppInstallations(ctx context.Context, token string) ([]codersdk
|
||||
}
|
||||
|
||||
type DeviceAuth struct {
|
||||
// Config is provided for the http client method.
|
||||
Config promoauth.InstrumentedOAuth2Config
|
||||
ClientID string
|
||||
TokenURL string
|
||||
Scopes []string
|
||||
@@ -307,8 +300,17 @@ func (c *DeviceAuth) AuthorizeDevice(ctx context.Context) (*codersdk.ExternalAut
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
do := http.DefaultClient.Do
|
||||
if c.Config != nil {
|
||||
// The cfg can be nil in unit tests.
|
||||
do = func(req *http.Request) (*http.Response, error) {
|
||||
return c.Config.Do(ctx, promoauth.SourceAuthorizeDevice, req)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := do(req)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -401,7 +403,7 @@ func (c *DeviceAuth) formatDeviceCodeURL() (string, error) {
|
||||
|
||||
// ConvertConfig converts the SDK configuration entry format
|
||||
// to the parsed and ready-to-consume in coderd provider type.
|
||||
func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([]*Config, error) {
|
||||
func ConvertConfig(instrument *promoauth.Factory, entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([]*Config, error) {
|
||||
ids := map[string]struct{}{}
|
||||
configs := []*Config{}
|
||||
for _, entry := range entries {
|
||||
@@ -453,7 +455,7 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
|
||||
Scopes: entry.Scopes,
|
||||
}
|
||||
|
||||
var oauthConfig OAuth2Config = oc
|
||||
var oauthConfig promoauth.OAuth2Config = oc
|
||||
// Azure DevOps uses JWT token authentication!
|
||||
if entry.Type == string(codersdk.EnhancedExternalAuthProviderAzureDevops) {
|
||||
oauthConfig = &jwtConfig{oc}
|
||||
@@ -463,17 +465,17 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
OAuth2Config: oauthConfig,
|
||||
ID: entry.ID,
|
||||
Regex: regex,
|
||||
Type: entry.Type,
|
||||
NoRefresh: entry.NoRefresh,
|
||||
ValidateURL: entry.ValidateURL,
|
||||
AppInstallationsURL: entry.AppInstallationsURL,
|
||||
AppInstallURL: entry.AppInstallURL,
|
||||
DisplayName: entry.DisplayName,
|
||||
DisplayIcon: entry.DisplayIcon,
|
||||
ExtraTokenKeys: entry.ExtraTokenKeys,
|
||||
InstrumentedOAuth2Config: instrument.New(entry.ID, oauthConfig),
|
||||
ID: entry.ID,
|
||||
Regex: regex,
|
||||
Type: entry.Type,
|
||||
NoRefresh: entry.NoRefresh,
|
||||
ValidateURL: entry.ValidateURL,
|
||||
AppInstallationsURL: entry.AppInstallationsURL,
|
||||
AppInstallURL: entry.AppInstallURL,
|
||||
DisplayName: entry.DisplayName,
|
||||
DisplayIcon: entry.DisplayIcon,
|
||||
ExtraTokenKeys: entry.ExtraTokenKeys,
|
||||
}
|
||||
|
||||
if entry.DeviceFlow {
|
||||
@@ -481,6 +483,7 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
|
||||
return nil, xerrors.Errorf("external auth provider %q: device auth url must be provided", entry.ID)
|
||||
}
|
||||
cfg.DeviceAuth = &DeviceAuth{
|
||||
Config: cfg,
|
||||
ClientID: entry.ClientID,
|
||||
TokenURL: oc.Endpoint.TokenURL,
|
||||
Scopes: entry.Scopes,
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbmem"
|
||||
"github.com/coder/coder/v2/coderd/externalauth"
|
||||
"github.com/coder/coder/v2/coderd/promoauth"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
@@ -94,7 +96,7 @@ func TestRefreshToken(t *testing.T) {
|
||||
t.Run("FalseIfTokenSourceFails", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
config := &externalauth.Config{
|
||||
OAuth2Config: &testutil.OAuth2Config{
|
||||
InstrumentedOAuth2Config: &testutil.OAuth2Config{
|
||||
TokenSourceFunc: func() (*oauth2.Token, error) {
|
||||
return nil, xerrors.New("failure")
|
||||
},
|
||||
@@ -301,9 +303,10 @@ func TestRefreshToken(t *testing.T) {
|
||||
|
||||
func TestExchangeWithClientSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
instrument := promoauth.NewFactory(prometheus.NewRegistry())
|
||||
// This ensures a provider that requires the custom
|
||||
// client secret exchange works.
|
||||
configs, err := externalauth.ConvertConfig([]codersdk.ExternalAuthConfig{{
|
||||
configs, err := externalauth.ConvertConfig(instrument, []codersdk.ExternalAuthConfig{{
|
||||
// JFrog just happens to require this custom type.
|
||||
|
||||
Type: codersdk.EnhancedExternalAuthProviderJFrog.String(),
|
||||
@@ -335,6 +338,8 @@ func TestExchangeWithClientSecret(t *testing.T) {
|
||||
|
||||
func TestConvertYAML(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
instrument := promoauth.NewFactory(prometheus.NewRegistry())
|
||||
for _, tc := range []struct {
|
||||
Name string
|
||||
Input []codersdk.ExternalAuthConfig
|
||||
@@ -387,7 +392,7 @@ func TestConvertYAML(t *testing.T) {
|
||||
tc := tc
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
output, err := externalauth.ConvertConfig(tc.Input, &url.URL{})
|
||||
output, err := externalauth.ConvertConfig(instrument, tc.Input, &url.URL{})
|
||||
if tc.Error != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.Error)
|
||||
@@ -399,7 +404,7 @@ func TestConvertYAML(t *testing.T) {
|
||||
|
||||
t.Run("CustomScopesAndEndpoint", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
config, err := externalauth.ConvertConfig([]codersdk.ExternalAuthConfig{{
|
||||
config, err := externalauth.ConvertConfig(instrument, []codersdk.ExternalAuthConfig{{
|
||||
Type: string(codersdk.EnhancedExternalAuthProviderGitLab),
|
||||
ClientID: "id",
|
||||
ClientSecret: "secret",
|
||||
@@ -433,10 +438,12 @@ func setupOauth2Test(t *testing.T, settings testConfig) (*oidctest.FakeIDP, *ext
|
||||
append([]oidctest.FakeIDPOpt{}, settings.FakeIDPOpts...)...,
|
||||
)
|
||||
|
||||
f := promoauth.NewFactory(prometheus.NewRegistry())
|
||||
config := &externalauth.Config{
|
||||
OAuth2Config: fake.OIDCConfig(t, nil, settings.CoderOIDCConfigOpts...),
|
||||
ID: providerID,
|
||||
ValidateURL: fake.WellknownConfig().UserInfoURL,
|
||||
InstrumentedOAuth2Config: f.New("test-oauth2",
|
||||
fake.OIDCConfig(t, nil, settings.CoderOIDCConfigOpts...)),
|
||||
ID: providerID,
|
||||
ValidateURL: fake.WellknownConfig().UserInfoURL,
|
||||
}
|
||||
settings.ExternalAuthOpt(config)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user