experiment: link public key to github automatically

This commit is contained in:
Garrett Delfosse 2024-04-28 21:00:15 +00:00
parent 15157c1c40
commit 28c498927d
5 changed files with 96 additions and 23 deletions

View File

@ -524,28 +524,8 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
}
}
extAuthEnv, err := ReadExternalAuthProvidersFromEnv(os.Environ())
if err != nil {
return xerrors.Errorf("read external auth providers from env: %w", err)
}
promRegistry := prometheus.NewRegistry()
oauthInstrument := promoauth.NewFactory(promRegistry)
vals.ExternalAuthConfigs.Value = append(vals.ExternalAuthConfigs.Value, extAuthEnv...)
externalAuthConfigs, err := externalauth.ConvertConfig(
oauthInstrument,
vals.ExternalAuthConfigs.Value,
vals.AccessURL.Value(),
)
if err != nil {
return xerrors.Errorf("convert external auth config: %w", err)
}
for _, c := range externalAuthConfigs {
logger.Debug(
ctx, "loaded external auth config",
slog.F("id", c.ID),
)
}
realIPConfig, err := httpmw.ParseRealIPConfig(vals.ProxyTrustedHeaders, vals.ProxyTrustedOrigins)
if err != nil {
@ -567,7 +547,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
Pubsub: pubsub.NewInMemory(),
CacheDir: cacheDir,
GoogleTokenValidator: googleTokenValidator,
ExternalAuthConfigs: externalAuthConfigs,
RealIPConfig: realIPConfig,
SecureAuthCookie: vals.SecureAuthCookie.Value(),
SSHKeygenAlgorithm: sshKeygenAlgorithm,
@ -696,6 +675,27 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
options.Database = dbmetrics.New(options.Database, options.PrometheusRegistry)
}
extAuthEnv, err := ReadExternalAuthProvidersFromEnv(os.Environ())
if err != nil {
return xerrors.Errorf("read external auth providers from env: %w", err)
}
vals.ExternalAuthConfigs.Value = append(vals.ExternalAuthConfigs.Value, extAuthEnv...)
externalAuthConfigs, err := externalauth.ConvertConfig(
oauthInstrument,
vals.ExternalAuthConfigs.Value,
options.Database,
vals.AccessURL.Value(),
)
if err != nil {
return xerrors.Errorf("convert external auth config: %w", err)
}
for _, c := range externalAuthConfigs {
logger.Debug(
ctx, "loaded external auth config",
slog.F("id", c.ID),
)
}
var deploymentID string
err = options.Database.InTx(func(tx database.Store) error {
// This will block until the lock is acquired, and will be

View File

@ -300,6 +300,17 @@ func (api *API) externalAuthCallback(externalAuthConfig *externalauth.Config) ht
}
}
if externalAuthConfig.CallbackFunc != nil {
err = externalAuthConfig.CallbackFunc(ctx, state.Token.AccessToken)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to run external auth callback action.",
Detail: err.Error(),
})
return
}
}
redirect := state.Redirect
if redirect == "" {
// This is a nicely rendered screen on the frontend. Passing the query param lets the

View File

@ -0,0 +1,48 @@
package externalauth
import (
"context"
"fmt"
"net/url"
"github.com/google/go-github/v61/github"
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/database"
)
type githubCallback struct {
accessURL *url.URL
db database.Store
}
func (c githubCallback) LinkPublicKey(ctx context.Context, userID uuid.UUID, token string) error {
client := github.NewClient(nil).WithAuthToken(token)
ghKeys, _, err := client.Users.ListKeys(ctx, "", nil)
if err != nil {
return xerrors.Errorf("list github keys: %w", err)
}
dbKey, err := c.db.GetGitSSHKey(ctx, userID)
if err != nil {
return xerrors.Errorf("get git ssh key: %w", err)
}
for _, key := range ghKeys {
if key.GetKey() == dbKey.PublicKey {
return nil
}
}
_, _, err = client.Users.CreateKey(ctx, &github.Key{
Key: &dbKey.PublicKey,
Title: github.String(fmt.Sprintf("%s Workspaces", c.accessURL.String())),
})
if err != nil {
return xerrors.Errorf("create github key: %w", err)
}
return nil
}

View File

@ -18,6 +18,7 @@ import (
"golang.org/x/xerrors"
"github.com/google/go-github/v43/github"
"github.com/google/uuid"
"github.com/sqlc-dev/pqtype"
xgithub "golang.org/x/oauth2/github"
@ -74,6 +75,7 @@ type Config struct {
// AppInstallationsURL is an API endpoint that returns a list of
// installations for the user. This is used for GitHub Apps.
AppInstallationsURL string
CallbackFunc func(ctx context.Context, userID uuid.UUID, token string) error
}
// GenerateTokenExtra generates the extra token data to store in the database.
@ -442,7 +444,7 @@ func (c *DeviceAuth) formatDeviceCodeURL() (string, error) {
// ConvertConfig converts the SDK configuration entry format
// to the parsed and ready-to-consume in coderd provider type.
func ConvertConfig(instrument *promoauth.Factory, entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([]*Config, error) {
func ConvertConfig(instrument *promoauth.Factory, entries []codersdk.ExternalAuthConfig, db database.Store, accessURL *url.URL) ([]*Config, error) {
ids := map[string]struct{}{}
configs := []*Config{}
for _, entry := range entries {
@ -538,6 +540,17 @@ func ConvertConfig(instrument *promoauth.Factory, entries []codersdk.ExternalAut
}
}
if entry.LinkPublicKey {
if entry.Type == string(codersdk.EnhancedExternalAuthProviderGitHub) {
cfg.CallbackFunc = func(ctx context.Context, userID uuid.UUID, token string) error {
return githubCallback{
accessURL: accessURL,
db: db,
}.LinkPublicKey(ctx, userID, token)
}
}
}
configs = append(configs, cfg)
}
return configs, nil

View File

@ -402,7 +402,8 @@ type ExternalAuthConfig struct {
// DisplayName is shown in the UI to identify the auth config.
DisplayName string `json:"display_name" yaml:"display_name"`
// DisplayIcon is a URL to an icon to display in the UI.
DisplayIcon string `json:"display_icon" yaml:"display_icon"`
DisplayIcon string `json:"display_icon" yaml:"display_icon"`
LinkPublicKey bool `json:"link_public_key" yaml:"link_public_key"`
}
type ProvisionerConfig struct {