Skip to content

Commit 4fe221a

Browse files
authored
feat: add flag to disable password auth (#5991)
Adds a flag --disable-password-auth that prevents the password login endpoint from working unless the user has the "owner" (aka. site admin) role. Adds a subcommand `coder server create-admin-user` which creates a user directly in the database with the "owner" role, the "admin" role in every organization, and password auth. This is to avoid lock-out situations where all accounts have the login type set to an identity provider and nobody can login.
1 parent 968d7e4 commit 4fe221a

21 files changed

+1352
-542
lines changed

cli/configssh_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
532532
{
533533
name: "Start/End out of order",
534534
matches: []match{
535-
//{match: "Continue?", write: "yes"},
535+
// {match: "Continue?", write: "yes"},
536536
},
537537
writeConfig: writeConfig{
538538
ssh: strings.Join([]string{
@@ -547,7 +547,7 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
547547
{
548548
name: "Multiple sections",
549549
matches: []match{
550-
//{match: "Continue?", write: "yes"},
550+
// {match: "Continue?", write: "yes"},
551551
},
552552
writeConfig: writeConfig{
553553
ssh: strings.Join([]string{

cli/deployment/config.go

+6
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,12 @@ func newConfig() *codersdk.DeploymentConfig {
550550
Flag: "disable-session-expiry-refresh",
551551
Default: false,
552552
},
553+
DisablePasswordAuth: &codersdk.DeploymentConfigField[bool]{
554+
Name: "Disable Password Authentication",
555+
Usage: "Disable password authentication. This is recommended for security purposes in production deployments that rely on an identity provider. Any user with the owner role will be able to sign in with their password regardless of this setting to avoid potential lock out. If you are locked out of your account, you can use the `coder server create-admin` command to create a new admin user directly in the database.",
556+
Flag: "disable-password-auth",
557+
Default: false,
558+
},
553559
}
554560
}
555561

cli/server.go

+75-55
Original file line numberDiff line numberDiff line change
@@ -563,62 +563,13 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
563563
options.Database = dbfake.New()
564564
options.Pubsub = database.NewPubsubInMemory()
565565
} else {
566-
logger.Debug(ctx, "connecting to postgresql")
567-
sqlDB, err := sql.Open(sqlDriver, cfg.PostgresURL.Value)
566+
sqlDB, err := connectToPostgres(ctx, logger, sqlDriver, cfg.PostgresURL.Value)
568567
if err != nil {
569-
return xerrors.Errorf("dial postgres: %w", err)
568+
return xerrors.Errorf("connect to postgres: %w", err)
570569
}
571-
defer sqlDB.Close()
572-
573-
pingCtx, pingCancel := context.WithTimeout(ctx, 15*time.Second)
574-
defer pingCancel()
575-
576-
err = sqlDB.PingContext(pingCtx)
577-
if err != nil {
578-
return xerrors.Errorf("ping postgres: %w", err)
579-
}
580-
581-
// Ensure the PostgreSQL version is >=13.0.0!
582-
version, err := sqlDB.QueryContext(ctx, "SHOW server_version;")
583-
if err != nil {
584-
return xerrors.Errorf("get postgres version: %w", err)
585-
}
586-
if !version.Next() {
587-
return xerrors.Errorf("no rows returned for version select")
588-
}
589-
var versionStr string
590-
err = version.Scan(&versionStr)
591-
if err != nil {
592-
return xerrors.Errorf("scan version: %w", err)
593-
}
594-
_ = version.Close()
595-
versionStr = strings.Split(versionStr, " ")[0]
596-
if semver.Compare("v"+versionStr, "v13") < 0 {
597-
return xerrors.New("PostgreSQL version must be v13.0.0 or higher!")
598-
}
599-
logger.Debug(ctx, "connected to postgresql", slog.F("version", versionStr))
600-
601-
err = migrations.Up(sqlDB)
602-
if err != nil {
603-
return xerrors.Errorf("migrate up: %w", err)
604-
}
605-
// The default is 0 but the request will fail with a 500 if the DB
606-
// cannot accept new connections, so we try to limit that here.
607-
// Requests will wait for a new connection instead of a hard error
608-
// if a limit is set.
609-
sqlDB.SetMaxOpenConns(10)
610-
// Allow a max of 3 idle connections at a time. Lower values end up
611-
// creating a lot of connection churn. Since each connection uses about
612-
// 10MB of memory, we're allocating 30MB to Postgres connections per
613-
// replica, but is better than causing Postgres to spawn a thread 15-20
614-
// times/sec. PGBouncer's transaction pooling is not the greatest so
615-
// it's not optimal for us to deploy.
616-
//
617-
// This was set to 10 before we started doing HA deployments, but 3 was
618-
// later determined to be a better middle ground as to not use up all
619-
// of PGs default connection limit while simultaneously avoiding a lot
620-
// of connection churn.
621-
sqlDB.SetMaxIdleConns(3)
570+
defer func() {
571+
_ = sqlDB.Close()
572+
}()
622573

623574
options.Database = database.New(sqlDB)
624575
options.Pubsub, err = database.NewPubsub(ctx, sqlDB, cfg.PostgresURL.Value)
@@ -1007,7 +958,8 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
1007958
postgresBuiltinURLCmd.Flags().BoolVar(&pgRawURL, "raw-url", false, "Output the raw connection URL instead of a psql command.")
1008959
postgresBuiltinServeCmd.Flags().BoolVar(&pgRawURL, "raw-url", false, "Output the raw connection URL instead of a psql command.")
1009960

1010-
root.AddCommand(postgresBuiltinURLCmd, postgresBuiltinServeCmd)
961+
createAdminUserCommand := newCreateAdminUserCommand()
962+
root.AddCommand(postgresBuiltinURLCmd, postgresBuiltinServeCmd, createAdminUserCommand)
1011963

1012964
deployment.AttachFlags(root.Flags(), vip, false)
1013965

@@ -1607,3 +1559,71 @@ func buildLogger(cmd *cobra.Command, cfg *codersdk.DeploymentConfig) (slog.Logge
16071559
}
16081560
}, nil
16091561
}
1562+
1563+
func connectToPostgres(ctx context.Context, logger slog.Logger, driver string, dbURL string) (*sql.DB, error) {
1564+
logger.Debug(ctx, "connecting to postgresql")
1565+
sqlDB, err := sql.Open(driver, dbURL)
1566+
if err != nil {
1567+
return nil, xerrors.Errorf("dial postgres: %w", err)
1568+
}
1569+
1570+
ok := false
1571+
defer func() {
1572+
if !ok {
1573+
_ = sqlDB.Close()
1574+
}
1575+
}()
1576+
1577+
pingCtx, pingCancel := context.WithTimeout(ctx, 15*time.Second)
1578+
defer pingCancel()
1579+
1580+
err = sqlDB.PingContext(pingCtx)
1581+
if err != nil {
1582+
return nil, xerrors.Errorf("ping postgres: %w", err)
1583+
}
1584+
1585+
// Ensure the PostgreSQL version is >=13.0.0!
1586+
version, err := sqlDB.QueryContext(ctx, "SHOW server_version;")
1587+
if err != nil {
1588+
return nil, xerrors.Errorf("get postgres version: %w", err)
1589+
}
1590+
if !version.Next() {
1591+
return nil, xerrors.Errorf("no rows returned for version select")
1592+
}
1593+
var versionStr string
1594+
err = version.Scan(&versionStr)
1595+
if err != nil {
1596+
return nil, xerrors.Errorf("scan version: %w", err)
1597+
}
1598+
_ = version.Close()
1599+
versionStr = strings.Split(versionStr, " ")[0]
1600+
if semver.Compare("v"+versionStr, "v13") < 0 {
1601+
return nil, xerrors.New("PostgreSQL version must be v13.0.0 or higher!")
1602+
}
1603+
logger.Debug(ctx, "connected to postgresql", slog.F("version", versionStr))
1604+
1605+
err = migrations.Up(sqlDB)
1606+
if err != nil {
1607+
return nil, xerrors.Errorf("migrate up: %w", err)
1608+
}
1609+
// The default is 0 but the request will fail with a 500 if the DB
1610+
// cannot accept new connections, so we try to limit that here.
1611+
// Requests will wait for a new connection instead of a hard error
1612+
// if a limit is set.
1613+
sqlDB.SetMaxOpenConns(10)
1614+
// Allow a max of 3 idle connections at a time. Lower values end up
1615+
// creating a lot of connection churn. Since each connection uses about
1616+
// 10MB of memory, we're allocating 30MB to Postgres connections per
1617+
// replica, but is better than causing Postgres to spawn a thread 15-20
1618+
// times/sec. PGBouncer's transaction pooling is not the greatest so
1619+
// it's not optimal for us to deploy.
1620+
//
1621+
// This was set to 10 before we started doing HA deployments, but 3 was
1622+
// later determined to be a better middle ground as to not use up all
1623+
// of PGs default connection limit while simultaneously avoiding a lot
1624+
// of connection churn.
1625+
sqlDB.SetMaxIdleConns(3)
1626+
1627+
ok = true
1628+
return sqlDB, nil
1629+
}

0 commit comments

Comments
 (0)