Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: instrument external oauth2 requests#11519

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Emyrk merged 20 commits intomainfromstevenmasley/oauth_instrument
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
20 commits
Select commitHold shift + click to select a range
b7f13fa
chore: instrument external oauth2 requests
EmyrkJan 9, 2024
fd1e012
Add a unit test
EmyrkJan 9, 2024
a6de1e3
remove internet based request
EmyrkJan 9, 2024
005883a
use ctx
EmyrkJan 9, 2024
2f77f99
typo
EmyrkJan 9, 2024
3377a9b
Work on instrumenting related oauth2 calls
EmyrkJan 9, 2024
07fd10d
remove print
EmyrkJan 9, 2024
117a405
typos
EmyrkJan 9, 2024
73abae6
Fixup some unit tests
EmyrkJan 9, 2024
e5e190d
fixup test cases
EmyrkJan 9, 2024
d4b36d3
remove oidc instrument
EmyrkJan 9, 2024
8964297
typos
EmyrkJan 9, 2024
2ba7a5c
more test coverage
EmyrkJan 9, 2024
8963aaa
left debug
EmyrkJan 9, 2024
bfa427f
Add comments
EmyrkJan 9, 2024
9d1c76c
use consts for source labels
EmyrkJan 10, 2024
c149f8f
Spell out config
EmyrkJan 10, 2024
30c459f
fix compile
EmyrkJan 10, 2024
cd98806
Use req with context
EmyrkJan 10, 2024
85e2d91
no panic
EmyrkJan 10, 2024
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
10 changes: 5 additions & 5 deletionscli/create_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -767,11 +767,11 @@ func TestCreateWithGitAuth(t *testing.T) {

client := coderdtest.New(t, &coderdtest.Options{
ExternalAuthConfigs: []*externalauth.Config{{
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
DisplayName: "GitHub",
InstrumentedOAuth2Config: &testutil.OAuth2Config{},
ID:"github",
Regex:regexp.MustCompile(`github\.com`),
Type:codersdk.EnhancedExternalAuthProviderGitHub.String(),
DisplayName:"GitHub",
}},
IncludeProvisionerDaemon: true,
})
Expand Down
24 changes: 18 additions & 6 deletionscli/server.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,7 @@ import (
"github.com/coder/coder/v2/coderd/oauthpki"
"github.com/coder/coder/v2/coderd/prometheusmetrics"
"github.com/coder/coder/v2/coderd/prometheusmetrics/insights"
"github.com/coder/coder/v2/coderd/promoauth"
"github.com/coder/coder/v2/coderd/schedule"
"github.com/coder/coder/v2/coderd/telemetry"
"github.com/coder/coder/v2/coderd/tracing"
Expand DownExpand Up@@ -133,7 +134,7 @@ func createOIDCConfig(ctx context.Context, vals *codersdk.DeploymentValues) (*co
Scopes: vals.OIDC.Scopes,
}

var useCfghttpmw.OAuth2Config = oauthCfg
var useCfgpromoauth.OAuth2Config = oauthCfg
if vals.OIDC.ClientKeyFile != "" {
// PKI authentication is done in the params. If a
// counter example is found, we can add a config option to
Expand DownExpand Up@@ -523,8 +524,11 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
return xerrors.Errorf("read external auth providers from env: %w", err)
}

promRegistry := prometheus.NewRegistry()
oauthInstrument := promoauth.NewFactory(promRegistry)
vals.ExternalAuthConfigs.Value = append(vals.ExternalAuthConfigs.Value, extAuthEnv...)
externalAuthConfigs, err := externalauth.ConvertConfig(
oauthInstrument,
vals.ExternalAuthConfigs.Value,
vals.AccessURL.Value(),
)
Expand DownExpand Up@@ -571,7 +575,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
// the DeploymentValues instead, this just serves to indicate the source of each
// option. This is just defensive to prevent accidentally leaking.
DeploymentOptions: codersdk.DeploymentOptionsWithoutSecrets(opts),
PrometheusRegistry:prometheus.NewRegistry(),
PrometheusRegistry:promRegistry,
APIRateLimit: int(vals.RateLimit.API.Value()),
LoginRateLimit: loginRateLimit,
FilesRateLimit: filesRateLimit,
Expand DownExpand Up@@ -617,7 +621,9 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
}

if vals.OAuth2.Github.ClientSecret != "" {
options.GithubOAuth2Config, err = configureGithubOAuth2(vals.AccessURL.Value(),
options.GithubOAuth2Config, err = configureGithubOAuth2(
oauthInstrument,
vals.AccessURL.Value(),
vals.OAuth2.Github.ClientID.String(),
vals.OAuth2.Github.ClientSecret.String(),
vals.OAuth2.Github.AllowSignups.Value(),
Expand All@@ -636,6 +642,12 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
logger.Warn(ctx, "coder will not check email_verified for OIDC logins")
}

// This OIDC config is **not** being instrumented with the
// oauth2 instrument wrapper. If we implement the missing
// oidc methods, then we can instrument it.
// Missing:
//- Userinfo
//- Verify
oc, err := createOIDCConfig(ctx, vals)
if err != nil {
return xerrors.Errorf("create oidc config: %w", err)
Expand DownExpand Up@@ -1737,7 +1749,7 @@ func configureCAPool(tlsClientCAFile string, tlsConfig *tls.Config) error {
}

//nolint:revive // Ignore flag-parameter: parameter 'allowEveryone' seems to be a control flag, avoid control coupling (revive)
func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, allowSignups, allowEveryone bool, allowOrgs []string, rawTeams []string, enterpriseBaseURL string) (*coderd.GithubOAuth2Config, error) {
func configureGithubOAuth2(instrument *promoauth.Factory,accessURL *url.URL, clientID, clientSecret string, allowSignups, allowEveryone bool, allowOrgs []string, rawTeams []string, enterpriseBaseURL string) (*coderd.GithubOAuth2Config, error) {
redirectURL, err := accessURL.Parse("/api/v2/users/oauth2/github/callback")
if err != nil {
return nil, xerrors.Errorf("parse github oauth callback url: %w", err)
Expand DownExpand Up@@ -1790,7 +1802,7 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
}

return &coderd.GithubOAuth2Config{
OAuth2Config: &oauth2.Config{
OAuth2Config:instrument.New("github-login",&oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: endpoint,
Expand All@@ -1800,7 +1812,7 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
"read:org",
"user:email",
},
},
}),
AllowSignups: allowSignups,
AllowEveryone: allowEveryone,
AllowOrganizations: allowOrgs,
Expand Down
52 changes: 48 additions & 4 deletionscoderd/coderdtest/oidctest/idp.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ import (
"github.com/go-jose/go-jose/v3"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
Expand All@@ -33,6 +34,7 @@ import (
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/coderd"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/promoauth"
"github.com/coder/coder/v2/coderd/util/syncmap"
"github.com/coder/coder/v2/codersdk"
)
Expand DownExpand Up@@ -223,6 +225,10 @@ func (f *FakeIDP) WellknownConfig() ProviderJSON {
return f.provider
}

func (f *FakeIDP) IssuerURL() *url.URL {
return f.issuerURL
}

func (f *FakeIDP) updateIssuerURL(t testing.TB, issuer string) {
t.Helper()

Expand DownExpand Up@@ -397,6 +403,44 @@ func (f *FakeIDP) ExternalLogin(t testing.TB, client *codersdk.Client, opts ...f
_ = res.Body.Close()
}

// CreateAuthCode emulates a user clicking "allow" on the IDP page. When doing
// unit tests, it's easier to skip this step sometimes. It does make an actual
// request to the IDP, so it should be equivalent to doing this "manually" with
// actual requests.
func (f *FakeIDP) CreateAuthCode(t testing.TB, state string, opts ...func(r *http.Request)) string {
// We need to store some claims, because this is also an OIDC provider, and
// it expects some claims to be present.
f.stateToIDTokenClaims.Store(state, jwt.MapClaims{})

u := f.cfg.AuthCodeURL(state)
r, err := http.NewRequestWithContext(context.Background(), http.MethodPost, u, nil)
require.NoError(t, err, "failed to create auth request")

for _, opt := range opts {
opt(r)
}

rw := httptest.NewRecorder()
f.handler.ServeHTTP(rw, r)
resp := rw.Result()
defer resp.Body.Close()

require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode, "expected redirect")
to := resp.Header.Get("Location")
require.NotEmpty(t, to, "expected redirect location")

toURL, err := url.Parse(to)
require.NoError(t, err, "failed to parse redirect location")

code := toURL.Query().Get("code")
require.NotEmpty(t, code, "expected code in redirect location")

newState := toURL.Query().Get("state")
require.Equal(t, state, newState, "expected state to match")

return code
}

// OIDCCallback will emulate the IDP redirecting back to the Coder callback.
// This is helpful if no Coderd exists because the IDP needs to redirect to
// something.
Expand DownExpand Up@@ -901,9 +945,10 @@ func (f *FakeIDP) ExternalAuthConfig(t testing.TB, id string, custom *ExternalAu
handle(email, rw, r)
}
}
instrumentF := promoauth.NewFactory(prometheus.NewRegistry())
cfg := &externalauth.Config{
OAuth2Config: f.OIDCConfig(t, nil),
ID: id,
InstrumentedOAuth2Config: instrumentF.New(f.clientID, f.OIDCConfig(t, nil)),
ID:id,
// No defaults for these fields by omitting the type
Type: "",
DisplayIcon: f.WellknownConfig().UserInfoURL,
Expand All@@ -920,10 +965,10 @@ func (f *FakeIDP) ExternalAuthConfig(t testing.TB, id string, custom *ExternalAu
// OIDCConfig returns the OIDC config to use for Coderd.
func (f *FakeIDP) OIDCConfig(t testing.TB, scopes []string, opts ...func(cfg *coderd.OIDCConfig)) *coderd.OIDCConfig {
t.Helper()

if len(scopes) == 0 {
scopes = []string{"openid", "email", "profile"}
}

oauthCfg := &oauth2.Config{
ClientID: f.clientID,
ClientSecret: f.clientSecret,
Expand DownExpand Up@@ -966,7 +1011,6 @@ func (f *FakeIDP) OIDCConfig(t testing.TB, scopes []string, opts ...func(cfg *co
}

f.cfg = oauthCfg

return cfg
}

Expand Down
57 changes: 30 additions & 27 deletionscoderd/externalauth/externalauth.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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.
Expand DownExpand Up@@ -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
}
Expand DownExpand Up@@ -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
}
Expand DownExpand Up@@ -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
Expand All@@ -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)
Copy link
Member

Choose a reason for hiding this comment

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

Noticed we never check HTTP status code forresp, should we?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I am not sure, I did not write this part. Have not checked the payloads myself here.

req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
Expand DownExpand Up@@ -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 {
Expand DownExpand Up@@ -453,7 +455,7 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
Scopes: entry.Scopes,
}

var oauthConfig OAuth2Config = oc
var oauthConfigpromoauth.OAuth2Config = oc
// Azure DevOps uses JWT token authentication!
if entry.Type == string(codersdk.EnhancedExternalAuthProviderAzureDevops) {
oauthConfig = &jwtConfig{oc}
Expand All@@ -463,24 +465,25 @@ 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 {
if entry.DeviceCodeURL == "" {
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,
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp