- Notifications
You must be signed in to change notification settings - Fork928
feat: Add OIDC authentication#3314
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
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
50ee7ad
7eb897a
424579e
a49b491
6eae627
292d9f6
e6619ff
3a20472
5f7176c
c2a4481
4b71655
7b487be
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -42,6 +42,7 @@ | ||
"mattn", | ||
"mitchellh", | ||
"moby", | ||
"namesgenerator", | ||
"nfpms", | ||
"nhooyr", | ||
"nolint", | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -23,6 +23,7 @@ import ( | ||
"sync" | ||
"time" | ||
"github.com/coreos/go-oidc/v3/oidc" | ||
"github.com/coreos/go-systemd/daemon" | ||
embeddedpostgres "github.com/fergusstrange/embedded-postgres" | ||
"github.com/google/go-github/v43/github" | ||
@@ -84,6 +85,12 @@ func server() *cobra.Command { | ||
oauth2GithubAllowedOrganizations []string | ||
oauth2GithubAllowedTeams []string | ||
oauth2GithubAllowSignups bool | ||
oidcAllowSignups bool | ||
oidcClientID string | ||
oidcClientSecret string | ||
oidcEmailDomain string | ||
oidcIssuerURL string | ||
oidcScopes []string | ||
telemetryEnable bool | ||
telemetryURL string | ||
tlsCertFile string | ||
@@ -282,6 +289,38 @@ func server() *cobra.Command { | ||
} | ||
} | ||
if oidcClientSecret != "" { | ||
kylecarbs marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
if oidcClientID == "" { | ||
return xerrors.Errorf("OIDC client ID be set!") | ||
} | ||
if oidcIssuerURL == "" { | ||
return xerrors.Errorf("OIDC issuer URL must be set!") | ||
} | ||
oidcProvider, err := oidc.NewProvider(ctx, oidcIssuerURL) | ||
if err != nil { | ||
return xerrors.Errorf("configure oidc provider: %w", err) | ||
} | ||
redirectURL, err := accessURLParsed.Parse("/api/v2/users/oidc/callback") | ||
if err != nil { | ||
return xerrors.Errorf("parse oidc oauth callback url: %w", err) | ||
} | ||
options.OIDCConfig = &coderd.OIDCConfig{ | ||
OAuth2Config: &oauth2.Config{ | ||
ClientID: oidcClientID, | ||
ClientSecret: oidcClientSecret, | ||
RedirectURL: redirectURL.String(), | ||
Endpoint: oidcProvider.Endpoint(), | ||
Scopes: oidcScopes, | ||
}, | ||
Verifier: oidcProvider.Verifier(&oidc.Config{ | ||
ClientID: oidcClientID, | ||
}), | ||
EmailDomain: oidcEmailDomain, | ||
AllowSignups: oidcAllowSignups, | ||
} | ||
} | ||
if inMemoryDatabase { | ||
options.Database = databasefake.New() | ||
options.Pubsub = database.NewPubsubInMemory() | ||
@@ -340,6 +379,8 @@ func server() *cobra.Command { | ||
Logger: logger.Named("telemetry"), | ||
URL: telemetryURL, | ||
GitHubOAuth: oauth2GithubClientID != "", | ||
OIDCAuth: oidcClientID != "", | ||
OIDCIssuerURL: oidcIssuerURL, | ||
Prometheus: promEnabled, | ||
STUN: len(stunServers) != 0, | ||
Tunnel: tunnel, | ||
@@ -636,6 +677,18 @@ func server() *cobra.Command { | ||
"Specifies teams inside organizations the user must be a member of to authenticate with GitHub. Formatted as: <organization-name>/<team-slug>.") | ||
cliflag.BoolVarP(root.Flags(), &oauth2GithubAllowSignups, "oauth2-github-allow-signups", "", "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS", false, | ||
"Specifies whether new users can sign up with GitHub.") | ||
cliflag.BoolVarP(root.Flags(), &oidcAllowSignups, "oidc-allow-signups", "", "CODER_OIDC_ALLOW_SIGNUPS", true, | ||
"Specifies whether new users can sign up with OIDC.") | ||
cliflag.StringVarP(root.Flags(), &oidcClientID, "oidc-client-id", "", "CODER_OIDC_CLIENT_ID", "", | ||
"Specifies a client ID to use for OIDC.") | ||
cliflag.StringVarP(root.Flags(), &oidcClientSecret, "oidc-client-secret", "", "CODER_OIDC_CLIENT_SECRET", "", | ||
"Specifies a client secret to use for OIDC.") | ||
cliflag.StringVarP(root.Flags(), &oidcEmailDomain, "oidc-email-domain", "", "CODER_OIDC_EMAIL_DOMAIN", "", | ||
"Specifies an email domain that clients authenticating with OIDC must match.") | ||
cliflag.StringVarP(root.Flags(), &oidcIssuerURL, "oidc-issuer-url", "", "CODER_OIDC_ISSUER_URL", "", | ||
"Specifies an issuer URL to use for OIDC.") | ||
cliflag.StringArrayVarP(root.Flags(), &oidcScopes, "oidc-scopes", "", "CODER_OIDC_SCOPES", []string{oidc.ScopeOpenID, "profile", "email"}, | ||
"Specifies scopes to grant when authenticating with OIDC.") | ||
enableTelemetryByDefault := !isTest() | ||
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.") | ||
cliflag.StringVarP(root.Flags(), &telemetryURL, "telemetry-url", "", "CODER_TELEMETRY_URL", "https://telemetry.coder.com", "Specifies a URL to send telemetry to.") | ||
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -31,6 +31,11 @@ func main() { | ||
} | ||
cmd := exec.Command( | ||
"docker", | ||
"run", | ||
"--rm", | ||
"--network=host", | ||
"postgres:13", | ||
Comment on lines +34 to +38 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. This was a bit out of scope, but makes everything use Docker, which makes changing our versions simpler! | ||
"pg_dump", | ||
"--schema-only", | ||
connection, | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
CREATE TYPE old_login_type AS ENUM ( | ||
'password', | ||
'github' | ||
); | ||
ALTER TABLE api_keys ALTER COLUMN login_type TYPE old_login_type USING (login_type::text::old_login_type); | ||
DROP TYPE login_type; | ||
ALTER TYPE old_login_type RENAME TO login_type; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
CREATE TYPE new_login_type AS ENUM ( | ||
'password', | ||
'github', | ||
'oidc' | ||
); | ||
ALTER TABLE api_keys ALTER COLUMN login_type TYPE new_login_type USING (login_type::text::new_login_type); | ||
DROP TYPE login_type; | ||
ALTER TYPE new_login_type RENAME TO login_type; |
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -81,71 +81,6 @@ func TestRead(t *testing.T) { | ||
}) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. These have been moved to the | ||
func WebsocketCloseMsg(t *testing.T) { | ||
t.Parallel() | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package httpapi | ||
import ( | ||
"regexp" | ||
"strings" | ||
"github.com/moby/moby/pkg/namesgenerator" | ||
) | ||
var ( | ||
usernameValid = regexp.MustCompile("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$") | ||
usernameReplace = regexp.MustCompile("[^a-zA-Z0-9-]*") | ||
) | ||
// UsernameValid returns whether the input string is a valid username. | ||
func UsernameValid(str string) bool { | ||
if len(str) > 32 { | ||
return false | ||
} | ||
if len(str) < 1 { | ||
return false | ||
} | ||
return usernameValid.MatchString(str) | ||
} | ||
// UsernameFrom returns a best-effort username from the provided string. | ||
// | ||
// It first attempts to validate the incoming string, which will | ||
// be returned if it is valid. It then will attempt to extract | ||
// the username from an email address. If no success happens during | ||
// these steps, a random username will be returned. | ||
func UsernameFrom(str string) string { | ||
if UsernameValid(str) { | ||
return str | ||
} | ||
emailAt := strings.LastIndex(str, "@") | ||
if emailAt >= 0 { | ||
str = str[:emailAt] | ||
} | ||
str = usernameReplace.ReplaceAllString(str, "") | ||
if UsernameValid(str) { | ||
return str | ||
} | ||
return strings.ReplaceAll(namesgenerator.GetRandomName(1), "_", "-") | ||
} |
Uh oh!
There was an error while loading.Please reload this page.