Skip to content

Commit 3d0febd

Browse files
kylecarbsammario
andauthored
feat: Add OIDC authentication (#3314)
* feat: Add OIDC authentication * Extract username into a separate package and add OIDC tests * Add test case for invalid tokens * Add test case for username as email * Add OIDC to the frontend * Improve comments from self-review * Add authentication docs * Add telemetry * Update docs/install/auth.md Co-authored-by: Ammar Bandukwala <ammar@ammar.io> * Update docs/install/auth.md Co-authored-by: Ammar Bandukwala <ammar@ammar.io> * Remove username package Co-authored-by: Ammar Bandukwala <ammar@ammar.io>
1 parent 8b17bf9 commit 3d0febd

File tree

28 files changed

+733
-137
lines changed

28 files changed

+733
-137
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"mattn",
4343
"mitchellh",
4444
"moby",
45+
"namesgenerator",
4546
"nfpms",
4647
"nhooyr",
4748
"nolint",

cli/server.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"sync"
2424
"time"
2525

26+
"github.com/coreos/go-oidc/v3/oidc"
2627
"github.com/coreos/go-systemd/daemon"
2728
embeddedpostgres "github.com/fergusstrange/embedded-postgres"
2829
"github.com/google/go-github/v43/github"
@@ -84,6 +85,12 @@ func server() *cobra.Command {
8485
oauth2GithubAllowedOrganizations []string
8586
oauth2GithubAllowedTeams []string
8687
oauth2GithubAllowSignups bool
88+
oidcAllowSignups bool
89+
oidcClientID string
90+
oidcClientSecret string
91+
oidcEmailDomain string
92+
oidcIssuerURL string
93+
oidcScopes []string
8794
telemetryEnable bool
8895
telemetryURL string
8996
tlsCertFile string
@@ -283,6 +290,38 @@ func server() *cobra.Command {
283290
}
284291
}
285292

293+
if oidcClientSecret != "" {
294+
if oidcClientID == "" {
295+
return xerrors.Errorf("OIDC client ID be set!")
296+
}
297+
if oidcIssuerURL == "" {
298+
return xerrors.Errorf("OIDC issuer URL must be set!")
299+
}
300+
301+
oidcProvider, err := oidc.NewProvider(ctx, oidcIssuerURL)
302+
if err != nil {
303+
return xerrors.Errorf("configure oidc provider: %w", err)
304+
}
305+
redirectURL, err := accessURLParsed.Parse("/api/v2/users/oidc/callback")
306+
if err != nil {
307+
return xerrors.Errorf("parse oidc oauth callback url: %w", err)
308+
}
309+
options.OIDCConfig = &coderd.OIDCConfig{
310+
OAuth2Config: &oauth2.Config{
311+
ClientID: oidcClientID,
312+
ClientSecret: oidcClientSecret,
313+
RedirectURL: redirectURL.String(),
314+
Endpoint: oidcProvider.Endpoint(),
315+
Scopes: oidcScopes,
316+
},
317+
Verifier: oidcProvider.Verifier(&oidc.Config{
318+
ClientID: oidcClientID,
319+
}),
320+
EmailDomain: oidcEmailDomain,
321+
AllowSignups: oidcAllowSignups,
322+
}
323+
}
324+
286325
if inMemoryDatabase {
287326
options.Database = databasefake.New()
288327
options.Pubsub = database.NewPubsubInMemory()
@@ -341,6 +380,8 @@ func server() *cobra.Command {
341380
Logger: logger.Named("telemetry"),
342381
URL: telemetryURL,
343382
GitHubOAuth: oauth2GithubClientID != "",
383+
OIDCAuth: oidcClientID != "",
384+
OIDCIssuerURL: oidcIssuerURL,
344385
Prometheus: promEnabled,
345386
STUN: len(stunServers) != 0,
346387
Tunnel: tunnel,
@@ -637,6 +678,18 @@ func server() *cobra.Command {
637678
"Specifies teams inside organizations the user must be a member of to authenticate with GitHub. Formatted as: <organization-name>/<team-slug>.")
638679
cliflag.BoolVarP(root.Flags(), &oauth2GithubAllowSignups, "oauth2-github-allow-signups", "", "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS", false,
639680
"Specifies whether new users can sign up with GitHub.")
681+
cliflag.BoolVarP(root.Flags(), &oidcAllowSignups, "oidc-allow-signups", "", "CODER_OIDC_ALLOW_SIGNUPS", true,
682+
"Specifies whether new users can sign up with OIDC.")
683+
cliflag.StringVarP(root.Flags(), &oidcClientID, "oidc-client-id", "", "CODER_OIDC_CLIENT_ID", "",
684+
"Specifies a client ID to use for OIDC.")
685+
cliflag.StringVarP(root.Flags(), &oidcClientSecret, "oidc-client-secret", "", "CODER_OIDC_CLIENT_SECRET", "",
686+
"Specifies a client secret to use for OIDC.")
687+
cliflag.StringVarP(root.Flags(), &oidcEmailDomain, "oidc-email-domain", "", "CODER_OIDC_EMAIL_DOMAIN", "",
688+
"Specifies an email domain that clients authenticating with OIDC must match.")
689+
cliflag.StringVarP(root.Flags(), &oidcIssuerURL, "oidc-issuer-url", "", "CODER_OIDC_ISSUER_URL", "",
690+
"Specifies an issuer URL to use for OIDC.")
691+
cliflag.StringArrayVarP(root.Flags(), &oidcScopes, "oidc-scopes", "", "CODER_OIDC_SCOPES", []string{oidc.ScopeOpenID, "profile", "email"},
692+
"Specifies scopes to grant when authenticating with OIDC.")
640693
enableTelemetryByDefault := !isTest()
641694
cliflag.BoolVarP(root.Flags(), &telemetryEnable, "telemetry", "", "CODER_TELEMETRY", enableTelemetryByDefault, "Specifies whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product.")
642695
cliflag.StringVarP(root.Flags(), &telemetryURL, "telemetry-url", "", "CODER_TELEMETRY_URL", "https://telemetry.coder.com", "Specifies a URL to send telemetry to.")

coderd/coderd.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type Options struct {
5757
AzureCertificates x509.VerifyOptions
5858
GoogleTokenValidator *idtoken.Validator
5959
GithubOAuth2Config *GithubOAuth2Config
60+
OIDCConfig *OIDCConfig
6061
ICEServers []webrtc.ICEServer
6162
SecureAuthCookie bool
6263
SSHKeygenAlgorithm gitsshkey.Algorithm
@@ -105,6 +106,7 @@ func New(options *Options) *API {
105106
api.workspaceAgentCache = wsconncache.New(api.dialWorkspaceAgent, 0)
106107
oauthConfigs := &httpmw.OAuth2Configs{
107108
Github: options.GithubOAuth2Config,
109+
OIDC: options.OIDCConfig,
108110
}
109111
apiKeyMiddleware := httpmw.ExtractAPIKey(options.Database, oauthConfigs, false)
110112

@@ -259,6 +261,10 @@ func New(options *Options) *API {
259261
r.Get("/callback", api.userOAuth2Github)
260262
})
261263
})
264+
r.Route("/oidc/callback", func(r chi.Router) {
265+
r.Use(httpmw.ExtractOAuth2(options.OIDCConfig))
266+
r.Get("/", api.userOIDC)
267+
})
262268
r.Group(func(r chi.Router) {
263269
r.Use(
264270
apiKeyMiddleware,

coderd/coderd_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
248248

249249
// Has it's own auth
250250
"GET:/api/v2/users/oauth2/github/callback": {NoAuthorize: true},
251+
"GET:/api/v2/users/oidc/callback": {NoAuthorize: true},
251252

252253
// All workspaceagents endpoints do not use rbac
253254
"POST:/api/v2/workspaceagents/aws-instance-identity": {NoAuthorize: true},

coderd/coderdtest/coderdtest.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type Options struct {
6363
Authorizer rbac.Authorizer
6464
AzureCertificates x509.VerifyOptions
6565
GithubOAuth2Config *coderd.GithubOAuth2Config
66+
OIDCConfig *coderd.OIDCConfig
6667
GoogleTokenValidator *idtoken.Validator
6768
SSHKeygenAlgorithm gitsshkey.Algorithm
6869
APIRateLimit int
@@ -189,6 +190,7 @@ func newWithCloser(t *testing.T, options *Options) (*codersdk.Client, io.Closer)
189190
AWSCertificates: options.AWSCertificates,
190191
AzureCertificates: options.AzureCertificates,
191192
GithubOAuth2Config: options.GithubOAuth2Config,
193+
OIDCConfig: options.OIDCConfig,
192194
GoogleTokenValidator: options.GoogleTokenValidator,
193195
SSHKeygenAlgorithm: options.SSHKeygenAlgorithm,
194196
TURNServer: turnServer,

coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dump/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ func main() {
3131
}
3232

3333
cmd := exec.Command(
34+
"docker",
35+
"run",
36+
"--rm",
37+
"--network=host",
38+
"postgres:13",
3439
"pg_dump",
3540
"--schema-only",
3641
connection,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TYPE old_login_type AS ENUM (
2+
'password',
3+
'github'
4+
);
5+
ALTER TABLE api_keys ALTER COLUMN login_type TYPE old_login_type USING (login_type::text::old_login_type);
6+
DROP TYPE login_type;
7+
ALTER TYPE old_login_type RENAME TO login_type;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE TYPE new_login_type AS ENUM (
2+
'password',
3+
'github',
4+
'oidc'
5+
);
6+
ALTER TABLE api_keys ALTER COLUMN login_type TYPE new_login_type USING (login_type::text::new_login_type);
7+
DROP TYPE login_type;
8+
ALTER TYPE new_login_type RENAME TO login_type;

coderd/database/models.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)