mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: Add support for GitHub Enterprise authentication (#3422)
This was manually tested with GitHub Enterprise v3.6.0-rc1.
This commit is contained in:
@ -87,6 +87,7 @@ func server() *cobra.Command {
|
|||||||
oauth2GithubAllowedOrganizations []string
|
oauth2GithubAllowedOrganizations []string
|
||||||
oauth2GithubAllowedTeams []string
|
oauth2GithubAllowedTeams []string
|
||||||
oauth2GithubAllowSignups bool
|
oauth2GithubAllowSignups bool
|
||||||
|
oauth2GithubEnterpriseBaseURL string
|
||||||
oidcAllowSignups bool
|
oidcAllowSignups bool
|
||||||
oidcClientID string
|
oidcClientID string
|
||||||
oidcClientSecret string
|
oidcClientSecret string
|
||||||
@ -286,7 +287,7 @@ func server() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if oauth2GithubClientSecret != "" {
|
if oauth2GithubClientSecret != "" {
|
||||||
options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed, oauth2GithubClientID, oauth2GithubClientSecret, oauth2GithubAllowSignups, oauth2GithubAllowedOrganizations, oauth2GithubAllowedTeams)
|
options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed, oauth2GithubClientID, oauth2GithubClientSecret, oauth2GithubAllowSignups, oauth2GithubAllowedOrganizations, oauth2GithubAllowedTeams, oauth2GithubEnterpriseBaseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("configure github oauth2: %w", err)
|
return xerrors.Errorf("configure github oauth2: %w", err)
|
||||||
}
|
}
|
||||||
@ -695,6 +696,8 @@ 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>.")
|
"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,
|
cliflag.BoolVarP(root.Flags(), &oauth2GithubAllowSignups, "oauth2-github-allow-signups", "", "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS", false,
|
||||||
"Specifies whether new users can sign up with GitHub.")
|
"Specifies whether new users can sign up with GitHub.")
|
||||||
|
cliflag.StringVarP(root.Flags(), &oauth2GithubEnterpriseBaseURL, "oauth2-github-enterprise-base-url", "", "CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL", "",
|
||||||
|
"Specifies the base URL of a GitHub Enterprise instance to use for oauth2.")
|
||||||
cliflag.BoolVarP(root.Flags(), &oidcAllowSignups, "oidc-allow-signups", "", "CODER_OIDC_ALLOW_SIGNUPS", true,
|
cliflag.BoolVarP(root.Flags(), &oidcAllowSignups, "oidc-allow-signups", "", "CODER_OIDC_ALLOW_SIGNUPS", true,
|
||||||
"Specifies whether new users can sign up with OIDC.")
|
"Specifies whether new users can sign up with OIDC.")
|
||||||
cliflag.StringVarP(root.Flags(), &oidcClientID, "oidc-client-id", "", "CODER_OIDC_CLIENT_ID", "",
|
cliflag.StringVarP(root.Flags(), &oidcClientID, "oidc-client-id", "", "CODER_OIDC_CLIENT_ID", "",
|
||||||
@ -972,7 +975,7 @@ func configureTLS(listener net.Listener, tlsMinVersion, tlsClientAuth, tlsCertFi
|
|||||||
return tls.NewListener(listener, tlsConfig), nil
|
return tls.NewListener(listener, tlsConfig), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, allowSignups bool, allowOrgs []string, rawTeams []string) (*coderd.GithubOAuth2Config, error) {
|
func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, allowSignups bool, allowOrgs []string, rawTeams []string, enterpriseBaseURL string) (*coderd.GithubOAuth2Config, error) {
|
||||||
redirectURL, err := accessURL.Parse("/api/v2/users/oauth2/github/callback")
|
redirectURL, err := accessURL.Parse("/api/v2/users/oauth2/github/callback")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("parse github oauth callback url: %w", err)
|
return nil, xerrors.Errorf("parse github oauth callback url: %w", err)
|
||||||
@ -988,11 +991,38 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
|
|||||||
Slug: parts[1],
|
Slug: parts[1],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
createClient := func(client *http.Client) (*github.Client, error) {
|
||||||
|
if enterpriseBaseURL != "" {
|
||||||
|
return github.NewEnterpriseClient(enterpriseBaseURL, "", client)
|
||||||
|
}
|
||||||
|
return github.NewClient(client), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := xgithub.Endpoint
|
||||||
|
if enterpriseBaseURL != "" {
|
||||||
|
enterpriseURL, err := url.Parse(enterpriseBaseURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("parse enterprise base url: %w", err)
|
||||||
|
}
|
||||||
|
authURL, err := enterpriseURL.Parse("/login/oauth/authorize")
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("parse enterprise auth url: %w", err)
|
||||||
|
}
|
||||||
|
tokenURL, err := enterpriseURL.Parse("/login/oauth/access_token")
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("parse enterprise token url: %w", err)
|
||||||
|
}
|
||||||
|
endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: authURL.String(),
|
||||||
|
TokenURL: tokenURL.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &coderd.GithubOAuth2Config{
|
return &coderd.GithubOAuth2Config{
|
||||||
OAuth2Config: &oauth2.Config{
|
OAuth2Config: &oauth2.Config{
|
||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
ClientSecret: clientSecret,
|
ClientSecret: clientSecret,
|
||||||
Endpoint: xgithub.Endpoint,
|
Endpoint: endpoint,
|
||||||
RedirectURL: redirectURL.String(),
|
RedirectURL: redirectURL.String(),
|
||||||
Scopes: []string{
|
Scopes: []string{
|
||||||
"read:user",
|
"read:user",
|
||||||
@ -1004,15 +1034,27 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
|
|||||||
AllowOrganizations: allowOrgs,
|
AllowOrganizations: allowOrgs,
|
||||||
AllowTeams: allowTeams,
|
AllowTeams: allowTeams,
|
||||||
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
||||||
user, _, err := github.NewClient(client).Users.Get(ctx, "")
|
api, err := createClient(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user, _, err := api.Users.Get(ctx, "")
|
||||||
return user, err
|
return user, err
|
||||||
},
|
},
|
||||||
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
||||||
emails, _, err := github.NewClient(client).Users.ListEmails(ctx, &github.ListOptions{})
|
api, err := createClient(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
emails, _, err := api.Users.ListEmails(ctx, &github.ListOptions{})
|
||||||
return emails, err
|
return emails, err
|
||||||
},
|
},
|
||||||
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
||||||
memberships, _, err := github.NewClient(client).Organizations.ListOrgMemberships(ctx, &github.ListOrgMembershipsOptions{
|
api, err := createClient(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
memberships, _, err := api.Organizations.ListOrgMemberships(ctx, &github.ListOrgMembershipsOptions{
|
||||||
State: "active",
|
State: "active",
|
||||||
ListOptions: github.ListOptions{
|
ListOptions: github.ListOptions{
|
||||||
PerPage: 100,
|
PerPage: 100,
|
||||||
@ -1021,7 +1063,11 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
|
|||||||
return memberships, err
|
return memberships, err
|
||||||
},
|
},
|
||||||
TeamMembership: func(ctx context.Context, client *http.Client, org, teamSlug, username string) (*github.Membership, error) {
|
TeamMembership: func(ctx context.Context, client *http.Client, org, teamSlug, username string) (*github.Membership, error) {
|
||||||
team, _, err := github.NewClient(client).Teams.GetTeamMembershipBySlug(ctx, org, teamSlug, username)
|
api, err := createClient(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
team, _, err := api.Teams.GetTeamMembershipBySlug(ctx, org, teamSlug, username)
|
||||||
return team, err
|
return team, err
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -436,6 +436,39 @@ func TestServer(t *testing.T) {
|
|||||||
cancelFunc()
|
cancelFunc()
|
||||||
<-serverErr
|
<-serverErr
|
||||||
})
|
})
|
||||||
|
t.Run("GitHubOAuth", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
fakeRedirect := "https://fake-url.com"
|
||||||
|
root, cfg := clitest.New(t,
|
||||||
|
"server",
|
||||||
|
"--in-memory",
|
||||||
|
"--address", ":0",
|
||||||
|
"--oauth2-github-client-id", "fake",
|
||||||
|
"--oauth2-github-client-secret", "fake",
|
||||||
|
"--oauth2-github-enterprise-base-url", fakeRedirect,
|
||||||
|
)
|
||||||
|
serverErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
serverErr <- root.ExecuteContext(ctx)
|
||||||
|
}()
|
||||||
|
accessURL := waitAccessURL(t, cfg)
|
||||||
|
client := codersdk.New(accessURL)
|
||||||
|
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
githubURL, err := accessURL.Parse("/api/v2/users/oauth2/github")
|
||||||
|
require.NoError(t, err)
|
||||||
|
res, err := client.HTTPClient.Get(githubURL.String())
|
||||||
|
require.NoError(t, err)
|
||||||
|
fakeURL, err := res.Location()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, strings.HasPrefix(fakeURL.String(), fakeRedirect), fakeURL.String())
|
||||||
|
cancelFunc()
|
||||||
|
<-serverErr
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateTLSCertificate(t testing.TB) (certPath, keyPath string) {
|
func generateTLSCertificate(t testing.TB) (certPath, keyPath string) {
|
||||||
|
@ -25,6 +25,8 @@ server:
|
|||||||
coder server --oauth2-github-allow-signups=true --oauth2-github-allowed-orgs="your-org" --oauth2-github-client-id="8d1...e05" --oauth2-github-client-secret="57ebc9...02c24c"
|
coder server --oauth2-github-allow-signups=true --oauth2-github-allowed-orgs="your-org" --oauth2-github-client-id="8d1...e05" --oauth2-github-client-secret="57ebc9...02c24c"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> For GitHub Enterprise support, specify the `--oauth2-github-enterprise-base-url` flag.
|
||||||
|
|
||||||
Alternatively, if you are running Coder as a system service, you can achieve the
|
Alternatively, if you are running Coder as a system service, you can achieve the
|
||||||
same result as the command above by adding the following environment variables
|
same result as the command above by adding the following environment variables
|
||||||
to the `/etc/coder.d/coder.env` file:
|
to the `/etc/coder.d/coder.env` file:
|
||||||
|
Reference in New Issue
Block a user