Skip to content

feat: add CODER_OIDC_IGNORE_EMAIL_VERIFIED config knob #5165

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 25, 2022
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
6 changes: 6 additions & 0 deletions cli/deployment/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ func newConfig() *codersdk.DeploymentConfig {
Flag: "oidc-scopes",
Default: []string{oidc.ScopeOpenID, "profile", "email"},
},
IgnoreEmailVerified: &codersdk.DeploymentConfigField[bool]{
Name: "OIDC Ignore Email Verified",
Usage: "Ignore the email_verified claim from the upstream provider.",
Flag: "oidc-ignore-email-verified",
Default: false,
},
},

Telemetry: &codersdk.TelemetryConfig{
Expand Down
28 changes: 21 additions & 7 deletions cli/deployment/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,23 +122,37 @@ func TestConfig(t *testing.T) {
require.Equal(t, config.Trace.Enable.Value, true)
require.Equal(t, config.Trace.HoneycombAPIKey.Value, "my-honeycomb-key")
},
}, {
Name: "OIDC_Defaults",
Env: map[string]string{},
Valid: func(config *codersdk.DeploymentConfig) {
require.Empty(t, config.OIDC.IssuerURL.Value)
require.Empty(t, config.OIDC.EmailDomain.Value)
require.Empty(t, config.OIDC.ClientID.Value)
require.Empty(t, config.OIDC.ClientSecret.Value)
require.True(t, config.OIDC.AllowSignups.Value)
require.ElementsMatch(t, config.OIDC.Scopes.Value, []string{"openid", "email", "profile"})
require.False(t, config.OIDC.IgnoreEmailVerified.Value)
},
}, {
Name: "OIDC",
Env: map[string]string{
"CODER_OIDC_ISSUER_URL": "https://accounts.google.com",
"CODER_OIDC_EMAIL_DOMAIN": "coder.com",
"CODER_OIDC_CLIENT_ID": "client",
"CODER_OIDC_CLIENT_SECRET": "secret",
"CODER_OIDC_ALLOW_SIGNUPS": "false",
"CODER_OIDC_SCOPES": "something,here",
"CODER_OIDC_ISSUER_URL": "https://accounts.google.com",
"CODER_OIDC_EMAIL_DOMAIN": "coder.com",
"CODER_OIDC_CLIENT_ID": "client",
"CODER_OIDC_CLIENT_SECRET": "secret",
"CODER_OIDC_ALLOW_SIGNUPS": "false",
"CODER_OIDC_SCOPES": "something,here",
"CODER_OIDC_IGNORE_EMAIL_VERIFIED": "true",
},
Valid: func(config *codersdk.DeploymentConfig) {
require.Equal(t, config.OIDC.IssuerURL.Value, "https://accounts.google.com")
require.Equal(t, config.OIDC.EmailDomain.Value, "coder.com")
require.Equal(t, config.OIDC.ClientID.Value, "client")
require.Equal(t, config.OIDC.ClientSecret.Value, "secret")
require.Equal(t, config.OIDC.AllowSignups.Value, false)
require.False(t, config.OIDC.AllowSignups.Value)
require.Equal(t, config.OIDC.Scopes.Value, []string{"something", "here"})
require.True(t, config.OIDC.IgnoreEmailVerified.Value)
},
}, {
Name: "GitHub",
Expand Down
4 changes: 4 additions & 0 deletions cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
return xerrors.Errorf("configure oidc client certificates: %w", err)
}

if cfg.OIDC.IgnoreEmailVerified.Value {
logger.Warn(ctx, "coder will not check email_verified for OIDC logins")
}

oidcProvider, err := oidc.NewProvider(ctx, cfg.OIDC.IssuerURL.Value)
if err != nil {
return xerrors.Errorf("configure oidc provider: %w", err)
Expand Down
3 changes: 3 additions & 0 deletions cli/testdata/coder_server_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ Flags:
--oidc-email-domain string Email domain that clients logging in with
OIDC must match.
Consumes $CODER_OIDC_EMAIL_DOMAIN
--oidc-ignore-email-verified Ignore the email_verified claim from the
upstream provider.
Consumes $CODER_OIDC_IGNORE_EMAIL_VERIFIED
--oidc-issuer-url string Issuer URL to use for Login with OIDC.
Consumes $CODER_OIDC_ISSUER_URL
--oidc-scopes strings Scopes to grant when authenticating with
Expand Down
14 changes: 10 additions & 4 deletions coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ type OIDCConfig struct {
// EmailDomain is the domain to enforce when a user authenticates.
EmailDomain string
AllowSignups bool
// IgnoreEmailVerified allows ignoring the email_verified claim
// from an upstream OIDC provider. See #5065 for context.
IgnoreEmailVerified bool
}

func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -264,10 +267,13 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
if ok {
verified, ok := verifiedRaw.(bool)
if ok && !verified {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Verify the %q email address on your OIDC provider to authenticate!", email),
})
return
if !api.OIDCConfig.IgnoreEmailVerified {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Verify the %q email address on your OIDC provider to authenticate!", email),
})
return
}
api.Logger.Warn(ctx, "allowing unverified oidc email %q")
}
}
// The username is a required property in Coder. We make a best-effort
Expand Down
34 changes: 27 additions & 7 deletions coderd/userauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,13 +479,14 @@ func TestUserOIDC(t *testing.T) {
t.Parallel()

for _, tc := range []struct {
Name string
Claims jwt.MapClaims
AllowSignups bool
EmailDomain string
Username string
AvatarURL string
StatusCode int
Name string
Claims jwt.MapClaims
AllowSignups bool
EmailDomain string
Username string
AvatarURL string
StatusCode int
IgnoreEmailVerified bool
}{{
Name: "EmailOnly",
Claims: jwt.MapClaims{
Expand All @@ -502,6 +503,24 @@ func TestUserOIDC(t *testing.T) {
},
AllowSignups: true,
StatusCode: http.StatusForbidden,
}, {
Name: "EmailNotAString",
Claims: jwt.MapClaims{
"email": 3.14159,
"email_verified": false,
},
AllowSignups: true,
StatusCode: http.StatusBadRequest,
}, {
Name: "EmailNotVerifiedIgnored",
Claims: jwt.MapClaims{
"email": "kyle@kwc.io",
"email_verified": false,
},
AllowSignups: true,
StatusCode: http.StatusTemporaryRedirect,
Username: "kyle",
IgnoreEmailVerified: true,
}, {
Name: "NotInRequiredEmailDomain",
Claims: jwt.MapClaims{
Expand Down Expand Up @@ -593,6 +612,7 @@ func TestUserOIDC(t *testing.T) {
config := conf.OIDCConfig()
config.AllowSignups = tc.AllowSignups
config.EmailDomain = tc.EmailDomain
config.IgnoreEmailVerified = tc.IgnoreEmailVerified

client := coderdtest.New(t, &coderdtest.Options{
OIDCConfig: config,
Expand Down
13 changes: 7 additions & 6 deletions codersdk/deploymentconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,13 @@ type OAuth2GithubConfig struct {
}

type OIDCConfig struct {
AllowSignups *DeploymentConfigField[bool] `json:"allow_signups" typescript:",notnull"`
ClientID *DeploymentConfigField[string] `json:"client_id" typescript:",notnull"`
ClientSecret *DeploymentConfigField[string] `json:"client_secret" typescript:",notnull"`
EmailDomain *DeploymentConfigField[string] `json:"email_domain" typescript:",notnull"`
IssuerURL *DeploymentConfigField[string] `json:"issuer_url" typescript:",notnull"`
Scopes *DeploymentConfigField[[]string] `json:"scopes" typescript:",notnull"`
AllowSignups *DeploymentConfigField[bool] `json:"allow_signups" typescript:",notnull"`
ClientID *DeploymentConfigField[string] `json:"client_id" typescript:",notnull"`
ClientSecret *DeploymentConfigField[string] `json:"client_secret" typescript:",notnull"`
EmailDomain *DeploymentConfigField[string] `json:"email_domain" typescript:",notnull"`
IssuerURL *DeploymentConfigField[string] `json:"issuer_url" typescript:",notnull"`
Scopes *DeploymentConfigField[[]string] `json:"scopes" typescript:",notnull"`
IgnoreEmailVerified *DeploymentConfigField[bool] `json:"ignore_email_verified" typescript:",notnull"`
}

type TelemetryConfig struct {
Expand Down
10 changes: 10 additions & 0 deletions docs/admin/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,21 @@ Once complete, run `sudo service coder restart` to reboot Coder.
> When a new user is created, the `preferred_username` claim becomes the username. If this claim is empty, the email address will be stripped of the domain, and become the username (e.g. `example@coder.com` becomes `example`).

If your OpenID Connect provider requires client TLS certificates for authentication, you can configure them like so:

```console
CODER_TLS_CLIENT_CERT_FILE=/path/to/cert.pem
CODER_TLS_CLIENT_KEY_FILE=/path/to/key.pem
```

Coder requires all OIDC email addresses to be verified by default. If the `email_verified` claim is present in the token response from the identity provider, Coder will validate that its value is `true`.
If needed, you can disable this behavior with the following setting:

```console
CODER_OIDC_IGNORE_EMAIL_VERIFIED=true
```

> **Note:** This will cause Coder to implicitly treat all OIDC emails as "verified".

## SCIM (enterprise)

Coder supports user provisioning and deprovisioning via SCIM 2.0 with header
Expand Down
4 changes: 2 additions & 2 deletions loadtest/reconnectingpty/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import (

func Test_Runner(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
t.Skip("PTY is flakey on Windows")
if runtime.GOOS != "linux" {
t.Skip("PTY is flakey on non-Linux platforms")
}

t.Run("OK", func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ export interface OIDCConfig {
readonly email_domain: DeploymentConfigField<string>
readonly issuer_url: DeploymentConfigField<string>
readonly scopes: DeploymentConfigField<string[]>
readonly ignore_email_verified: DeploymentConfigField<boolean>
}

// From codersdk/organizations.go
Expand Down