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

Commit50ee7ad

Browse files
committed
feat: Add OIDC authentication
1 parent1f2ead8 commit50ee7ad

File tree

13 files changed

+222
-2
lines changed

13 files changed

+222
-2
lines changed

‎cli/server.go

Lines changed: 45 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
oauth2GithubAllowSignupsbool
88+
oidcAllowSignupsbool
89+
oidcClientIDstring
90+
oidcClientSecretstring
91+
oidcEmailDomainstring
92+
oidcIssuerURLstring
93+
oidcScopes []string
8794
telemetryEnablebool
8895
telemetryURLstring
8996
tlsCertFilestring
@@ -282,6 +289,32 @@ func server() *cobra.Command {
282289
}
283290
}
284291

292+
ifoidcClientSecret!="" {
293+
oidcProvider,err:=oidc.NewProvider(ctx,oidcIssuerURL)
294+
iferr!=nil {
295+
returnxerrors.Errorf("configure oidc provider: %w",err)
296+
}
297+
redirectURL,err:=accessURLParsed.Parse("/api/v2/users/oidc/callback")
298+
iferr!=nil {
299+
returnxerrors.Errorf("parse oidc oauth callback url: %w",err)
300+
}
301+
options.OIDCConfig=&coderd.OIDCConfig{
302+
OAuth2Config:&oauth2.Config{
303+
ClientID:oidcClientID,
304+
ClientSecret:oidcClientSecret,
305+
RedirectURL:redirectURL.String(),
306+
Endpoint:oidcProvider.Endpoint(),
307+
Scopes:oidcScopes,
308+
},
309+
Provider:oidcProvider,
310+
Verifier:oidcProvider.Verifier(&oidc.Config{
311+
ClientID:oidcClientID,
312+
}),
313+
EmailDomain:oidcEmailDomain,
314+
AllowSignups:oidcAllowSignups,
315+
}
316+
}
317+
285318
ifinMemoryDatabase {
286319
options.Database=databasefake.New()
287320
options.Pubsub=database.NewPubsubInMemory()
@@ -636,6 +669,18 @@ func server() *cobra.Command {
636669
"Specifies teams inside organizations the user must be a member of to authenticate with GitHub. Formatted as: <organization-name>/<team-slug>.")
637670
cliflag.BoolVarP(root.Flags(),&oauth2GithubAllowSignups,"oauth2-github-allow-signups","","CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS",false,
638671
"Specifies whether new users can sign up with GitHub.")
672+
cliflag.BoolVarP(root.Flags(),&oidcAllowSignups,"oidc-allow-signups","","CODER_OIDC_ALLOW_SIGNUPS",true,
673+
"Specifies whether new users can sign up with OIDC.")
674+
cliflag.StringVarP(root.Flags(),&oidcClientID,"oidc-client-id","","CODER_OIDC_CLIENT_ID","",
675+
"Specifies a client ID to use for OIDC.")
676+
cliflag.StringVarP(root.Flags(),&oidcClientSecret,"oidc-client-secret","","CODER_OIDC_CLIENT_SECRET","",
677+
"Specifies a client secret to use for OIDC.")
678+
cliflag.StringVarP(root.Flags(),&oidcEmailDomain,"oidc-email-domain","","CODER_OIDC_EMAIL_DOMAIN","",
679+
"Specifies an email domain that clients authenticating with OIDC must match.")
680+
cliflag.StringVarP(root.Flags(),&oidcIssuerURL,"oidc-issuer-url","","CODER_OIDC_ISSUER_URL","",
681+
"Specifies an issuer URL to use for OIDC.")
682+
cliflag.StringArrayVarP(root.Flags(),&oidcScopes,"oidc-scopes","","CODER_OIDC_SCOPES", []string{oidc.ScopeOpenID,"profile","email"},
683+
"Specifies scopes to grant when authenticating with OIDC.")
639684
enableTelemetryByDefault:=!isTest()
640685
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.")
641686
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
SecureAuthCookiebool
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/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more aboutcustomizing 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+
CREATETYPEold_login_typeAS ENUM (
2+
'password',
3+
'github'
4+
);
5+
ALTERTABLE api_keys ALTER COLUMN login_type TYPE old_login_type USING (login_type::text::old_login_type);
6+
DROPTYPE login_type;
7+
ALTERTYPE 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+
CREATETYPEnew_login_typeAS ENUM (
2+
'password',
3+
'github',
4+
'oidc'
5+
);
6+
ALTERTABLE api_keys ALTER COLUMN login_type TYPE new_login_type USING (login_type::text::new_login_type);
7+
DROPTYPE login_type;
8+
ALTERTYPE 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 aboutcustomizing how changed files appear on GitHub.

‎coderd/httpmw/apikey.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func AuthorizationUserRoles(r *http.Request) database.GetAuthorizationUserRolesR
4949
// This should be extended to support other authentication types in the future.
5050
typeOAuth2Configsstruct {
5151
GithubOAuth2Config
52+
OIDCOAuth2Config
5253
}
5354

5455
const (
@@ -155,6 +156,8 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
155156
switchkey.LoginType {
156157
casedatabase.LoginTypeGithub:
157158
oauthConfig=oauth.Github
159+
casedatabase.LoginTypeOIDC:
160+
oauthConfig=oauth.OIDC
158161
default:
159162
write(http.StatusInternalServerError, codersdk.Response{
160163
Message:internalErrorMessage,

‎coderd/userauth.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"errors"
77
"fmt"
88
"net/http"
9+
"strings"
910

11+
"github.com/coreos/go-oidc/v3/oidc"
1012
"github.com/google/go-github/v43/github"
1113
"github.com/google/uuid"
1214
"golang.org/x/oauth2"
@@ -40,10 +42,18 @@ func (api *API) userAuthMethods(rw http.ResponseWriter, _ *http.Request) {
4042
httpapi.Write(rw,http.StatusOK, codersdk.AuthMethods{
4143
Password:true,
4244
Github:api.GithubOAuth2Config!=nil,
45+
OIDC:api.OIDCConfig!=nil,
4346
})
4447
}
4548

4649
func (api*API)userOAuth2Github(rw http.ResponseWriter,r*http.Request) {
50+
ifapi.GithubOAuth2Config==nil {
51+
httpapi.Write(rw,http.StatusPreconditionRequired, codersdk.Response{
52+
Message:"GitHub authentication is not enabled!",
53+
})
54+
return
55+
}
56+
4757
state:=httpmw.OAuth2(r)
4858

4959
oauthClient:=oauth2.NewClient(r.Context(),oauth2.StaticTokenSource(state.Token))
@@ -205,3 +215,126 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
205215
}
206216
http.Redirect(rw,r,redirect,http.StatusTemporaryRedirect)
207217
}
218+
219+
typeOIDCConfigstruct {
220+
httpmw.OAuth2Config
221+
222+
Provider*oidc.Provider
223+
Verifier*oidc.IDTokenVerifier
224+
// EmailDomain is an optional domain to require when authenticating.
225+
EmailDomainstring
226+
AllowSignupsbool
227+
}
228+
229+
func (api*API)userOIDC(rw http.ResponseWriter,r*http.Request) {
230+
ifapi.OIDCConfig==nil {
231+
httpapi.Write(rw,http.StatusPreconditionRequired, codersdk.Response{
232+
Message:"OpenID Connect authentication is not enabled!",
233+
})
234+
return
235+
}
236+
237+
state:=httpmw.OAuth2(r)
238+
239+
// See the example here: https://github.com/coreos/go-oidc
240+
rawIDToken,ok:=state.Token.Extra("id_token").(string)
241+
if!ok {
242+
httpapi.Write(rw,http.StatusBadRequest, codersdk.Response{
243+
Message:"id_token not found in response payload. Ensure your OIDC callback is configured correctly!",
244+
})
245+
return
246+
}
247+
248+
idToken,err:=api.OIDCConfig.Verifier.Verify(r.Context(),rawIDToken)
249+
iferr!=nil {
250+
httpapi.Write(rw,http.StatusBadRequest, codersdk.Response{
251+
Message:"Failed to verify OIDC token.",
252+
Detail:err.Error(),
253+
})
254+
return
255+
}
256+
257+
varclaimsstruct {
258+
Emailstring`json:"email"`
259+
Verifiedbool`json:"email_verified"`
260+
Usernamestring`json:"preferred_username"`
261+
}
262+
err=idToken.Claims(&claims)
263+
iferr!=nil {
264+
httpapi.Write(rw,http.StatusInternalServerError, codersdk.Response{
265+
Message:"Failed to extract OIDC claims.",
266+
Detail:err.Error(),
267+
})
268+
return
269+
}
270+
if!claims.Verified {
271+
httpapi.Write(rw,http.StatusForbidden, codersdk.Response{
272+
Message:fmt.Sprintf("Verify the %q email address on your OIDC provider to authenticate!",claims.Email),
273+
})
274+
return
275+
}
276+
ifapi.OIDCConfig.EmailDomain!="" {
277+
if!strings.HasSuffix(claims.Email,api.OIDCConfig.EmailDomain) {
278+
httpapi.Write(rw,http.StatusForbidden, codersdk.Response{
279+
Message:fmt.Sprintf("Your email %q is not a part of the %q domain!",claims.Email,api.OIDCConfig.EmailDomain),
280+
})
281+
return
282+
}
283+
}
284+
285+
varuser database.User
286+
user,err=api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
287+
Email:claims.Email,
288+
})
289+
iferrors.Is(err,sql.ErrNoRows) {
290+
if!api.OIDCConfig.AllowSignups {
291+
httpapi.Write(rw,http.StatusForbidden, codersdk.Response{
292+
Message:"Signups are disabled for OIDC authentication!",
293+
})
294+
return
295+
}
296+
varorganizationID uuid.UUID
297+
organizations,_:=api.Database.GetOrganizations(r.Context())
298+
iflen(organizations)>0 {
299+
// Add the user to the first organization. Once multi-organization
300+
// support is added, we should enable a configuration map of user
301+
// email to organization.
302+
organizationID=organizations[0].ID
303+
}
304+
user,_,err=api.createUser(r.Context(), codersdk.CreateUserRequest{
305+
Email:claims.Email,
306+
Username:claims.Username,
307+
OrganizationID:organizationID,
308+
})
309+
iferr!=nil {
310+
httpapi.Write(rw,http.StatusInternalServerError, codersdk.Response{
311+
Message:"Internal error creating user.",
312+
Detail:err.Error(),
313+
})
314+
return
315+
}
316+
}
317+
iferr!=nil {
318+
httpapi.Write(rw,http.StatusInternalServerError, codersdk.Response{
319+
Message:"Failed to get user by email.",
320+
Detail:err.Error(),
321+
})
322+
}
323+
324+
_,created:=api.createAPIKey(rw,r, database.InsertAPIKeyParams{
325+
UserID:user.ID,
326+
LoginType:database.LoginTypeOIDC,
327+
OAuthAccessToken:state.Token.AccessToken,
328+
OAuthRefreshToken:state.Token.RefreshToken,
329+
OAuthExpiry:state.Token.Expiry,
330+
})
331+
if!created {
332+
return
333+
}
334+
335+
redirect:=state.Redirect
336+
ifredirect=="" {
337+
redirect="/"
338+
}
339+
http.Redirect(rw,r,redirect,http.StatusTemporaryRedirect)
340+
}

‎codersdk/users.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ type CreateOrganizationRequest struct {
174174
typeAuthMethodsstruct {
175175
Passwordbool`json:"password"`
176176
Githubbool`json:"github"`
177+
OIDCbool`json:"oidc"`
177178
}
178179

179180
// HasFirstUser returns whether the first user has been created.

‎go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ require (
5252
github.com/charmbracelet/lipglossv0.5.0
5353
github.com/cli/safeexecv1.0.0
5454
github.com/coder/retryv1.3.0
55+
github.com/coreos/go-oidc/v3v3.2.0
5556
github.com/coreos/go-systemdv0.0.0-20191104093116-d3cd4ed1dbcf
5657
github.com/creack/ptyv1.1.18
5758
github.com/elastic/go-sysinfov1.8.1
@@ -135,7 +136,10 @@ require (
135136
tailscale.comv1.26.2
136137
)
137138

138-
requiregithub.com/googleapis/enterprise-certificate-proxyv0.1.0// indirect
139+
require (
140+
github.com/googleapis/enterprise-certificate-proxyv0.1.0// indirect
141+
gopkg.in/square/go-jose.v2v2.6.0// indirect
142+
)
139143

140144
require (
141145
github.com/Azure/go-ansitermv0.0.0-20210617225240-d185dfc1b5a1// indirect

‎go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,8 @@ github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmeka
464464
github.com/coreos/go-iptablesv0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
465465
github.com/coreos/go-iptablesv0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
466466
github.com/coreos/go-oidcv2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
467+
github.com/coreos/go-oidc/v3v3.2.0 h1:2eR2MGR7thBXSQ2YbODlF0fcmgtliLCfr9iX6RW11fc=
468+
github.com/coreos/go-oidc/v3v3.2.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
467469
github.com/coreos/go-semverv0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
468470
github.com/coreos/go-semverv0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
469471
github.com/coreos/go-systemdv0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@@ -2115,6 +2117,7 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
21152117
golang.org/x/netv0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
21162118
golang.org/x/netv0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
21172119
golang.org/x/netv0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
2120+
golang.org/x/netv0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
21182121
golang.org/x/netv0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
21192122
golang.org/x/netv0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
21202123
golang.org/x/netv0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -2778,6 +2781,8 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
27782781
gopkg.in/square/go-jose.v2v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
27792782
gopkg.in/square/go-jose.v2v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
27802783
gopkg.in/square/go-jose.v2v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
2784+
gopkg.in/square/go-jose.v2v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
2785+
gopkg.in/square/go-jose.v2v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
27812786
gopkg.in/tomb.v1v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
27822787
gopkg.in/warnings.v0v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
27832788
gopkg.in/yaml.v1v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=

‎site/src/api/typesGenerated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface AgentGitSSHKey {
2828
exportinterfaceAuthMethods{
2929
readonlypassword:boolean
3030
readonlygithub:boolean
31+
readonlyoidc:boolean
3132
}
3233

3334
// From codersdk/workspaceagents.go

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp