diff --git a/cli/configssh_test.go b/cli/configssh_test.go index 046f763285414..343a6af43dc58 100644 --- a/cli/configssh_test.go +++ b/cli/configssh_test.go @@ -532,7 +532,7 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) { { name: "Start/End out of order", matches: []match{ - //{match: "Continue?", write: "yes"}, + // {match: "Continue?", write: "yes"}, }, writeConfig: writeConfig{ ssh: strings.Join([]string{ @@ -547,7 +547,7 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) { { name: "Multiple sections", matches: []match{ - //{match: "Continue?", write: "yes"}, + // {match: "Continue?", write: "yes"}, }, writeConfig: writeConfig{ ssh: strings.Join([]string{ diff --git a/cli/deployment/config.go b/cli/deployment/config.go index fadab9f8c4e91..d5bb01db8e6ba 100644 --- a/cli/deployment/config.go +++ b/cli/deployment/config.go @@ -550,6 +550,12 @@ func newConfig() *codersdk.DeploymentConfig { Flag: "disable-session-expiry-refresh", Default: false, }, + DisablePasswordAuth: &codersdk.DeploymentConfigField[bool]{ + Name: "Disable Password Authentication", + 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.", + Flag: "disable-password-auth", + Default: false, + }, } } diff --git a/cli/server.go b/cli/server.go index 8f13ca1ccf010..00e857677062d 100644 --- a/cli/server.go +++ b/cli/server.go @@ -563,62 +563,13 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co options.Database = dbfake.New() options.Pubsub = database.NewPubsubInMemory() } else { - logger.Debug(ctx, "connecting to postgresql") - sqlDB, err := sql.Open(sqlDriver, cfg.PostgresURL.Value) + sqlDB, err := connectToPostgres(ctx, logger, sqlDriver, cfg.PostgresURL.Value) if err != nil { - return xerrors.Errorf("dial postgres: %w", err) + return xerrors.Errorf("connect to postgres: %w", err) } - defer sqlDB.Close() - - pingCtx, pingCancel := context.WithTimeout(ctx, 15*time.Second) - defer pingCancel() - - err = sqlDB.PingContext(pingCtx) - if err != nil { - return xerrors.Errorf("ping postgres: %w", err) - } - - // Ensure the PostgreSQL version is >=13.0.0! - version, err := sqlDB.QueryContext(ctx, "SHOW server_version;") - if err != nil { - return xerrors.Errorf("get postgres version: %w", err) - } - if !version.Next() { - return xerrors.Errorf("no rows returned for version select") - } - var versionStr string - err = version.Scan(&versionStr) - if err != nil { - return xerrors.Errorf("scan version: %w", err) - } - _ = version.Close() - versionStr = strings.Split(versionStr, " ")[0] - if semver.Compare("v"+versionStr, "v13") < 0 { - return xerrors.New("PostgreSQL version must be v13.0.0 or higher!") - } - logger.Debug(ctx, "connected to postgresql", slog.F("version", versionStr)) - - err = migrations.Up(sqlDB) - if err != nil { - return xerrors.Errorf("migrate up: %w", err) - } - // The default is 0 but the request will fail with a 500 if the DB - // cannot accept new connections, so we try to limit that here. - // Requests will wait for a new connection instead of a hard error - // if a limit is set. - sqlDB.SetMaxOpenConns(10) - // Allow a max of 3 idle connections at a time. Lower values end up - // creating a lot of connection churn. Since each connection uses about - // 10MB of memory, we're allocating 30MB to Postgres connections per - // replica, but is better than causing Postgres to spawn a thread 15-20 - // times/sec. PGBouncer's transaction pooling is not the greatest so - // it's not optimal for us to deploy. - // - // This was set to 10 before we started doing HA deployments, but 3 was - // later determined to be a better middle ground as to not use up all - // of PGs default connection limit while simultaneously avoiding a lot - // of connection churn. - sqlDB.SetMaxIdleConns(3) + defer func() { + _ = sqlDB.Close() + }() options.Database = database.New(sqlDB) 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 postgresBuiltinURLCmd.Flags().BoolVar(&pgRawURL, "raw-url", false, "Output the raw connection URL instead of a psql command.") postgresBuiltinServeCmd.Flags().BoolVar(&pgRawURL, "raw-url", false, "Output the raw connection URL instead of a psql command.") - root.AddCommand(postgresBuiltinURLCmd, postgresBuiltinServeCmd) + createAdminUserCommand := newCreateAdminUserCommand() + root.AddCommand(postgresBuiltinURLCmd, postgresBuiltinServeCmd, createAdminUserCommand) deployment.AttachFlags(root.Flags(), vip, false) @@ -1607,3 +1559,71 @@ func buildLogger(cmd *cobra.Command, cfg *codersdk.DeploymentConfig) (slog.Logge } }, nil } + +func connectToPostgres(ctx context.Context, logger slog.Logger, driver string, dbURL string) (*sql.DB, error) { + logger.Debug(ctx, "connecting to postgresql") + sqlDB, err := sql.Open(driver, dbURL) + if err != nil { + return nil, xerrors.Errorf("dial postgres: %w", err) + } + + ok := false + defer func() { + if !ok { + _ = sqlDB.Close() + } + }() + + pingCtx, pingCancel := context.WithTimeout(ctx, 15*time.Second) + defer pingCancel() + + err = sqlDB.PingContext(pingCtx) + if err != nil { + return nil, xerrors.Errorf("ping postgres: %w", err) + } + + // Ensure the PostgreSQL version is >=13.0.0! + version, err := sqlDB.QueryContext(ctx, "SHOW server_version;") + if err != nil { + return nil, xerrors.Errorf("get postgres version: %w", err) + } + if !version.Next() { + return nil, xerrors.Errorf("no rows returned for version select") + } + var versionStr string + err = version.Scan(&versionStr) + if err != nil { + return nil, xerrors.Errorf("scan version: %w", err) + } + _ = version.Close() + versionStr = strings.Split(versionStr, " ")[0] + if semver.Compare("v"+versionStr, "v13") < 0 { + return nil, xerrors.New("PostgreSQL version must be v13.0.0 or higher!") + } + logger.Debug(ctx, "connected to postgresql", slog.F("version", versionStr)) + + err = migrations.Up(sqlDB) + if err != nil { + return nil, xerrors.Errorf("migrate up: %w", err) + } + // The default is 0 but the request will fail with a 500 if the DB + // cannot accept new connections, so we try to limit that here. + // Requests will wait for a new connection instead of a hard error + // if a limit is set. + sqlDB.SetMaxOpenConns(10) + // Allow a max of 3 idle connections at a time. Lower values end up + // creating a lot of connection churn. Since each connection uses about + // 10MB of memory, we're allocating 30MB to Postgres connections per + // replica, but is better than causing Postgres to spawn a thread 15-20 + // times/sec. PGBouncer's transaction pooling is not the greatest so + // it's not optimal for us to deploy. + // + // This was set to 10 before we started doing HA deployments, but 3 was + // later determined to be a better middle ground as to not use up all + // of PGs default connection limit while simultaneously avoiding a lot + // of connection churn. + sqlDB.SetMaxIdleConns(3) + + ok = true + return sqlDB, nil +} diff --git a/cli/server_createadminuser.go b/cli/server_createadminuser.go new file mode 100644 index 0000000000000..d21a7f07cce1e --- /dev/null +++ b/cli/server_createadminuser.go @@ -0,0 +1,262 @@ +//go:build !slim + +package cli + +import ( + "fmt" + "os" + "os/signal" + "sort" + + "github.com/google/uuid" + "github.com/spf13/cobra" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" + "github.com/coder/coder/cli/cliui" + "github.com/coder/coder/coderd/database" + "github.com/coder/coder/coderd/gitsshkey" + "github.com/coder/coder/coderd/httpapi" + "github.com/coder/coder/coderd/rbac" + "github.com/coder/coder/coderd/userpassword" + "github.com/coder/coder/codersdk" +) + +func newCreateAdminUserCommand() *cobra.Command { + var ( + newUserDBURL string + newUserSSHKeygenAlgorithm string + newUserUsername string + newUserEmail string + newUserPassword string + ) + createAdminUserCommand := &cobra.Command{ + Use: "create-admin-user", + Short: "Create a new admin user with the given username, email and password and adds it to every organization.", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + sshKeygenAlgorithm, err := gitsshkey.ParseAlgorithm(newUserSSHKeygenAlgorithm) + if err != nil { + return xerrors.Errorf("parse ssh keygen algorithm %q: %w", newUserSSHKeygenAlgorithm, err) + } + + if val, exists := os.LookupEnv("CODER_POSTGRES_URL"); exists { + newUserDBURL = val + } + if val, exists := os.LookupEnv("CODER_SSH_KEYGEN_ALGORITHM"); exists { + newUserSSHKeygenAlgorithm = val + } + if val, exists := os.LookupEnv("CODER_USERNAME"); exists { + newUserUsername = val + } + if val, exists := os.LookupEnv("CODER_EMAIL"); exists { + newUserEmail = val + } + if val, exists := os.LookupEnv("CODER_PASSWORD"); exists { + newUserPassword = val + } + + cfg := createConfig(cmd) + logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr())) + if ok, _ := cmd.Flags().GetBool(varVerbose); ok { + logger = logger.Leveled(slog.LevelDebug) + } + + ctx, cancel := signal.NotifyContext(ctx, InterruptSignals...) + defer cancel() + + if newUserDBURL == "" { + cmd.Printf("Using built-in PostgreSQL (%s)\n", cfg.PostgresPath()) + url, closePg, err := startBuiltinPostgres(ctx, cfg, logger) + if err != nil { + return err + } + defer func() { + _ = closePg() + }() + newUserDBURL = url + } + + sqlDB, err := connectToPostgres(ctx, logger, "postgres", newUserDBURL) + if err != nil { + return xerrors.Errorf("connect to postgres: %w", err) + } + defer func() { + _ = sqlDB.Close() + }() + db := database.New(sqlDB) + + validateInputs := func(username, email, password string) error { + // Use the validator tags so we match the API's validation. + req := codersdk.CreateUserRequest{ + Username: "username", + Email: "email@coder.com", + Password: "ValidPa$$word123!", + OrganizationID: uuid.New(), + } + if username != "" { + req.Username = username + } + if email != "" { + req.Email = email + } + if password != "" { + req.Password = password + } + + return httpapi.Validate.Struct(req) + } + + if newUserUsername == "" { + newUserUsername, err = cliui.Prompt(cmd, cliui.PromptOptions{ + Text: "Username", + Validate: func(val string) error { + if val == "" { + return xerrors.New("username cannot be empty") + } + return validateInputs(val, "", "") + }, + }) + if err != nil { + return err + } + } + if newUserEmail == "" { + newUserEmail, err = cliui.Prompt(cmd, cliui.PromptOptions{ + Text: "Email", + Validate: func(val string) error { + if val == "" { + return xerrors.New("email cannot be empty") + } + return validateInputs("", val, "") + }, + }) + if err != nil { + return err + } + } + if newUserPassword == "" { + newUserPassword, err = cliui.Prompt(cmd, cliui.PromptOptions{ + Text: "Password", + Secret: true, + Validate: func(val string) error { + if val == "" { + return xerrors.New("password cannot be empty") + } + return validateInputs("", "", val) + }, + }) + if err != nil { + return err + } + + // Prompt again. + _, err = cliui.Prompt(cmd, cliui.PromptOptions{ + Text: "Confirm password", + Secret: true, + Validate: func(val string) error { + if val != newUserPassword { + return xerrors.New("passwords do not match") + } + return nil + }, + }) + if err != nil { + return err + } + } + + err = validateInputs(newUserUsername, newUserEmail, newUserPassword) + if err != nil { + return xerrors.Errorf("validate inputs: %w", err) + } + + hashedPassword, err := userpassword.Hash(newUserPassword) + if err != nil { + return xerrors.Errorf("hash password: %w", err) + } + + // Create the user. + var newUser database.User + err = db.InTx(func(tx database.Store) error { + orgs, err := tx.GetOrganizations(ctx) + if err != nil { + return xerrors.Errorf("get organizations: %w", err) + } + + // Sort organizations by name so that test output is consistent. + sort.Slice(orgs, func(i, j int) bool { + return orgs[i].Name < orgs[j].Name + }) + + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Creating user...") + newUser, err = tx.InsertUser(ctx, database.InsertUserParams{ + ID: uuid.New(), + Email: newUserEmail, + Username: newUserUsername, + HashedPassword: []byte(hashedPassword), + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + RBACRoles: []string{rbac.RoleOwner()}, + LoginType: database.LoginTypePassword, + }) + if err != nil { + return xerrors.Errorf("insert user: %w", err) + } + + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Generating user SSH key...") + privateKey, publicKey, err := gitsshkey.Generate(sshKeygenAlgorithm) + if err != nil { + return xerrors.Errorf("generate user gitsshkey: %w", err) + } + _, err = tx.InsertGitSSHKey(ctx, database.InsertGitSSHKeyParams{ + UserID: newUser.ID, + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + PrivateKey: privateKey, + PublicKey: publicKey, + }) + if err != nil { + return xerrors.Errorf("insert user gitsshkey: %w", err) + } + + for _, org := range orgs { + _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Adding user to organization %q (%s) as admin...\n", org.Name, org.ID.String()) + _, err := tx.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{ + OrganizationID: org.ID, + UserID: newUser.ID, + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + Roles: []string{rbac.RoleOrgAdmin(org.ID)}, + }) + if err != nil { + return xerrors.Errorf("insert organization member: %w", err) + } + } + + return nil + }, nil) + if err != nil { + return err + } + + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "") + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "User created successfully.") + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "ID: "+newUser.ID.String()) + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Username: "+newUser.Username) + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Email: "+newUser.Email) + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Password: ********") + + return nil + }, + } + createAdminUserCommand.Flags().StringVar(&newUserDBURL, "postgres-url", "", "URL of a PostgreSQL database. If empty, the built-in PostgreSQL deployment will be used (Coder must not be already running in this case). Consumes $CODER_POSTGRES_URL.") + createAdminUserCommand.Flags().StringVar(&newUserSSHKeygenAlgorithm, "ssh-keygen-algorithm", "ed25519", "The algorithm to use for generating ssh keys. Accepted values are \"ed25519\", \"ecdsa\", or \"rsa4096\". Consumes $CODER_SSH_KEYGEN_ALGORITHM.") + createAdminUserCommand.Flags().StringVar(&newUserUsername, "username", "", "The username of the new user. If not specified, you will be prompted via stdin. Consumes $CODER_USERNAME.") + createAdminUserCommand.Flags().StringVar(&newUserEmail, "email", "", "The email of the new user. If not specified, you will be prompted via stdin. Consumes $CODER_EMAIL.") + createAdminUserCommand.Flags().StringVar(&newUserPassword, "password", "", "The password of the new user. If not specified, you will be prompted via stdin. Consumes $CODER_PASSWORD.") + + return createAdminUserCommand +} diff --git a/cli/server_createadminuser_test.go b/cli/server_createadminuser_test.go new file mode 100644 index 0000000000000..f51a5ef5d451e --- /dev/null +++ b/cli/server_createadminuser_test.go @@ -0,0 +1,269 @@ +package cli_test + +import ( + "context" + "database/sql" + "fmt" + "runtime" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/cli/clitest" + "github.com/coder/coder/coderd/database" + "github.com/coder/coder/coderd/database/postgres" + "github.com/coder/coder/coderd/rbac" + "github.com/coder/coder/coderd/userpassword" + "github.com/coder/coder/pty/ptytest" + "github.com/coder/coder/testutil" +) + +//nolint:paralleltest, tparallel +func TestServerCreateAdminUser(t *testing.T) { + const ( + username = "dean" + email = "dean@example.com" + password = "SecurePa$$word123" + ) + + verifyUser := func(t *testing.T, dbURL, username, email, password string) { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + sqlDB, err := sql.Open("postgres", dbURL) + require.NoError(t, err) + defer sqlDB.Close() + db := database.New(sqlDB) + + pingCtx, pingCancel := context.WithTimeout(ctx, testutil.WaitShort) + defer pingCancel() + _, err = db.Ping(pingCtx) + require.NoError(t, err, "ping db") + + user, err := db.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{ + Email: email, + }) + require.NoError(t, err) + require.Equal(t, username, user.Username, "username does not match") + require.Equal(t, email, user.Email, "email does not match") + + ok, err := userpassword.Compare(string(user.HashedPassword), password) + require.NoError(t, err) + require.True(t, ok, "password does not match") + + require.EqualValues(t, []string{rbac.RoleOwner()}, user.RBACRoles, "user does not have owner role") + + // Check that user is admin in every org. + orgs, err := db.GetOrganizations(ctx) + require.NoError(t, err) + orgIDs := make(map[uuid.UUID]struct{}, len(orgs)) + for _, org := range orgs { + orgIDs[org.ID] = struct{}{} + } + + orgMemberships, err := db.GetOrganizationMembershipsByUserID(ctx, user.ID) + require.NoError(t, err) + orgIDs2 := make(map[uuid.UUID]struct{}, len(orgMemberships)) + for _, membership := range orgMemberships { + orgIDs2[membership.OrganizationID] = struct{}{} + assert.Equal(t, []string{rbac.RoleOrgAdmin(membership.OrganizationID)}, membership.Roles, "user is not org admin") + } + + require.Equal(t, orgIDs, orgIDs2, "user is not in all orgs") + } + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + if runtime.GOOS != "linux" || testing.Short() { + // Skip on non-Linux because it spawns a PostgreSQL instance. + t.SkipNow() + } + connectionURL, closeFunc, err := postgres.Open() + require.NoError(t, err) + defer closeFunc() + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + sqlDB, err := sql.Open("postgres", connectionURL) + require.NoError(t, err) + defer sqlDB.Close() + db := database.New(sqlDB) + + pingCtx, pingCancel := context.WithTimeout(ctx, testutil.WaitShort) + defer pingCancel() + _, err = db.Ping(pingCtx) + require.NoError(t, err, "ping db") + + // Insert a few orgs. + org1Name, org1ID := "org1", uuid.New() + org2Name, org2ID := "org2", uuid.New() + _, err = db.InsertOrganization(ctx, database.InsertOrganizationParams{ + ID: org1ID, + Name: org1Name, + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + }) + require.NoError(t, err) + _, err = db.InsertOrganization(ctx, database.InsertOrganizationParams{ + ID: org2ID, + Name: org2Name, + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + }) + require.NoError(t, err) + + root, _ := clitest.New(t, + "server", "create-admin-user", + "--postgres-url", connectionURL, + "--ssh-keygen-algorithm", "ed25519", + "--username", username, + "--email", email, + "--password", password, + ) + pty := ptytest.New(t) + root.SetOutput(pty.Output()) + root.SetErr(pty.Output()) + errC := make(chan error, 1) + go func() { + err := root.ExecuteContext(ctx) + t.Log("root.ExecuteContext() returned:", err) + errC <- err + }() + + pty.ExpectMatch("Creating user...") + pty.ExpectMatch("Generating user SSH key...") + pty.ExpectMatch(fmt.Sprintf("Adding user to organization %q (%s) as admin...", org1Name, org1ID.String())) + pty.ExpectMatch(fmt.Sprintf("Adding user to organization %q (%s) as admin...", org2Name, org2ID.String())) + pty.ExpectMatch("User created successfully.") + pty.ExpectMatch(username) + pty.ExpectMatch(email) + pty.ExpectMatch("****") + + require.NoError(t, <-errC) + + verifyUser(t, connectionURL, username, email, password) + }) + + //nolint:paralleltest + t.Run("Env", func(t *testing.T) { + if runtime.GOOS != "linux" || testing.Short() { + // Skip on non-Linux because it spawns a PostgreSQL instance. + t.SkipNow() + } + connectionURL, closeFunc, err := postgres.Open() + require.NoError(t, err) + defer closeFunc() + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + t.Setenv("CODER_POSTGRES_URL", connectionURL) + t.Setenv("CODER_SSH_KEYGEN_ALGORITHM", "ecdsa") + t.Setenv("CODER_USERNAME", username) + t.Setenv("CODER_EMAIL", email) + t.Setenv("CODER_PASSWORD", password) + + root, _ := clitest.New(t, "server", "create-admin-user") + pty := ptytest.New(t) + root.SetOutput(pty.Output()) + root.SetErr(pty.Output()) + errC := make(chan error, 1) + go func() { + err := root.ExecuteContext(ctx) + t.Log("root.ExecuteContext() returned:", err) + errC <- err + }() + + pty.ExpectMatch("User created successfully.") + pty.ExpectMatch(username) + pty.ExpectMatch(email) + pty.ExpectMatch("****") + + require.NoError(t, <-errC) + + verifyUser(t, connectionURL, username, email, password) + }) + + t.Run("Stdin", func(t *testing.T) { + t.Parallel() + + if runtime.GOOS != "linux" || testing.Short() { + // Skip on non-Linux because it spawns a PostgreSQL instance. + t.SkipNow() + } + connectionURL, closeFunc, err := postgres.Open() + require.NoError(t, err) + defer closeFunc() + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + root, _ := clitest.New(t, + "server", "create-admin-user", + "--postgres-url", connectionURL, + "--ssh-keygen-algorithm", "rsa4096", + ) + pty := ptytest.New(t) + root.SetIn(pty.Input()) + root.SetOutput(pty.Output()) + root.SetErr(pty.Output()) + errC := make(chan error, 1) + go func() { + err := root.ExecuteContext(ctx) + t.Log("root.ExecuteContext() returned:", err) + errC <- err + }() + + pty.ExpectMatch("> Username") + pty.WriteLine(username) + pty.ExpectMatch("> Email") + pty.WriteLine(email) + pty.ExpectMatch("> Password") + pty.WriteLine(password) + pty.ExpectMatch("> Confirm password") + pty.WriteLine(password) + + pty.ExpectMatch("User created successfully.") + pty.ExpectMatch(username) + pty.ExpectMatch(email) + pty.ExpectMatch("****") + + require.NoError(t, <-errC) + + verifyUser(t, connectionURL, username, email, password) + }) + + t.Run("Validates", func(t *testing.T) { + t.Parallel() + + if runtime.GOOS != "linux" || testing.Short() { + // Skip on non-Linux because it spawns a PostgreSQL instance. + t.SkipNow() + } + connectionURL, closeFunc, err := postgres.Open() + require.NoError(t, err) + defer closeFunc() + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + root, _ := clitest.New(t, + "server", "create-admin-user", + "--postgres-url", connectionURL, + "--ssh-keygen-algorithm", "rsa4096", + "--username", "$", + "--email", "not-an-email", + "--password", "x", + ) + pty := ptytest.New(t) + root.SetOutput(pty.Output()) + root.SetErr(pty.Output()) + + err = root.ExecuteContext(ctx) + require.Error(t, err) + require.ErrorContains(t, err, "'email' failed on the 'email' tag") + require.ErrorContains(t, err, "'username' failed on the 'username' tag") + }) +} diff --git a/cli/server_slim.go b/cli/server_slim.go index ae6f9cbaea091..5b35d7ba4fcef 100644 --- a/cli/server_slim.go +++ b/cli/server_slim.go @@ -47,12 +47,34 @@ func Server(vip *viper.Viper, _ func(context.Context, *coderd.Options) (*coderd. }, } + var ( + newUserDBURL string + newUserSSHKeygenAlgorithm string + newUserUsername string + newUserEmail string + newUserPassword string + ) + createAdminUserCommand := &cobra.Command{ + Use: "create-admin-user", + Short: "Create a new admin user with the given username, email and password and adds it to every organization.", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + serverUnsupported(cmd.ErrOrStderr()) + return nil + }, + } + // We still have to attach the flags to the commands so users don't get // an error when they try to use them. postgresBuiltinURLCmd.Flags().BoolVar(&pgRawURL, "raw-url", false, "Output the raw connection URL instead of a psql command.") postgresBuiltinServeCmd.Flags().BoolVar(&pgRawURL, "raw-url", false, "Output the raw connection URL instead of a psql command.") + createAdminUserCommand.Flags().StringVar(&newUserDBURL, "postgres-url", "", "URL of a PostgreSQL database. If empty, the built-in PostgreSQL deployment will be used (Coder must not be already running in this case). Consumes $CODER_POSTGRES_URL.") + createAdminUserCommand.Flags().StringVar(&newUserSSHKeygenAlgorithm, "ssh-keygen-algorithm", "ed25519", "The algorithm to use for generating ssh keys. Accepted values are \"ed25519\", \"ecdsa\", or \"rsa4096\". Consumes $CODER_SSH_KEYGEN_ALGORITHM.") + createAdminUserCommand.Flags().StringVar(&newUserUsername, "username", "", "The username of the new user. If not specified, you will be prompted via stdin. Consumes $CODER_USERNAME.") + createAdminUserCommand.Flags().StringVar(&newUserEmail, "email", "", "The email of the new user. If not specified, you will be prompted via stdin. Consumes $CODER_EMAIL.") + createAdminUserCommand.Flags().StringVar(&newUserPassword, "password", "", "The password of the new user. If not specified, you will be prompted via stdin. Consumes $CODER_PASSWORD.") - root.AddCommand(postgresBuiltinURLCmd, postgresBuiltinServeCmd) + root.AddCommand(postgresBuiltinURLCmd, postgresBuiltinServeCmd, createAdminUserCommand) deployment.AttachFlags(root.Flags(), vip, false) diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 12eb4bb7ed681..ea59437409b5d 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -6,316 +6,362 @@ Usage: coder server [command] Commands: + create-admin-user Create a new admin user with the given username, email and password and adds it to every organization. postgres-builtin-serve Run the built-in PostgreSQL deployment. postgres-builtin-url Output the connection URL for the built-in PostgreSQL deployment. Flags: - --access-url string External URL to access your deployment. - This must be accessible by all - provisioned workspaces. - Consumes $CODER_ACCESS_URL - --api-rate-limit int Maximum number of requests per minute - allowed to the API per user, or per IP - address for unauthenticated users. - Negative values mean no rate limit. Some - API endpoints have separate strict rate - limits regardless of this value to - prevent denial-of-service or brute force - attacks. - Consumes $CODER_API_RATE_LIMIT (default 512) - --cache-dir string The directory to cache temporary files. - If unspecified and $CACHE_DIRECTORY is - set, it will be used for compatibility - with systemd. - Consumes $CODER_CACHE_DIRECTORY (default - "~/.cache/coder") - --dangerous-allow-path-app-sharing Allow workspace apps that are not served - from subdomains to be shared. Path-based - app sharing is DISABLED by default for - security purposes. Path-based apps can - make requests to the Coder API and pose a - security risk when the workspace serves - malicious JavaScript. Path-based apps can - be disabled entirely with - --disable-path-apps for further security. - Consumes - $CODER_DANGEROUS_ALLOW_PATH_APP_SHARING - --dangerous-allow-path-app-site-owner-access Allow site-owners to access workspace - apps from workspaces they do not own. - Owners cannot access path-based apps they - do not own by default. Path-based apps - can make requests to the Coder API and - pose a security risk when the workspace - serves malicious JavaScript. Path-based - apps can be disabled entirely with - --disable-path-apps for further security. - Consumes - $CODER_DANGEROUS_ALLOW_PATH_APP_SITE_OWNER_ACCESS - --dangerous-disable-rate-limits Disables all rate limits. This is not - recommended in production. - Consumes $CODER_RATE_LIMIT_DISABLE_ALL - --derp-config-path string Path to read a DERP mapping from. See: - https://tailscale.com/kb/1118/custom-derp-servers/ - Consumes $CODER_DERP_CONFIG_PATH - --derp-config-url string URL to fetch a DERP mapping on startup. - See: - https://tailscale.com/kb/1118/custom-derp-servers/ - Consumes $CODER_DERP_CONFIG_URL - --derp-server-enable Whether to enable or disable the embedded - DERP relay server. - Consumes $CODER_DERP_SERVER_ENABLE - (default true) - --derp-server-region-code string Region code to use for the embedded DERP - server. - Consumes $CODER_DERP_SERVER_REGION_CODE - (default "coder") - --derp-server-region-id int Region ID to use for the embedded DERP - server. - Consumes $CODER_DERP_SERVER_REGION_ID - (default 999) - --derp-server-region-name string Region name that for the embedded DERP - server. - Consumes $CODER_DERP_SERVER_REGION_NAME - (default "Coder Embedded Relay") - --derp-server-stun-addresses strings Addresses for STUN servers to establish - P2P connections. Set empty to disable P2P - connections. - Consumes - $CODER_DERP_SERVER_STUN_ADDRESSES - (default [stun.l.google.com:19302]) - --disable-path-apps Disable workspace apps that are not - served from subdomains. Path-based apps - can make requests to the Coder API and - pose a security risk when the workspace - serves malicious JavaScript. This is - recommended for security purposes if a - --wildcard-access-url is configured. - Consumes $CODER_DISABLE_PATH_APPS - --disable-session-expiry-refresh Disable automatic session expiry bumping - due to activity. This forces all sessions - to become invalid after the session - expiry duration has been reached. - Consumes $CODER_DISABLE_SESSION_EXPIRY_REFRESH - --experiments strings Enable one or more experiments. These are - not ready for production. Separate - multiple experiments with commas, or - enter '*' to opt-in to all available - experiments. - Consumes $CODER_EXPERIMENTS - -h, --help help for server - --http-address string HTTP bind address of the server. Unset to - disable the HTTP endpoint. - Consumes $CODER_HTTP_ADDRESS (default - "127.0.0.1:3000") - --log-human string Output human-readable logs to a given - file. - Consumes $CODER_LOGGING_HUMAN (default - "/dev/stderr") - --log-json string Output JSON logs to a given file. - Consumes $CODER_LOGGING_JSON - --log-stackdriver string Output Stackdriver compatible logs to a - given file. - Consumes $CODER_LOGGING_STACKDRIVER - --max-token-lifetime duration The maximum lifetime duration users can - specify when creating an API token. - Consumes $CODER_MAX_TOKEN_LIFETIME - (default 720h0m0s) - --oauth2-github-allow-everyone Allow all logins, setting this option - means allowed orgs and teams must be - empty. - Consumes $CODER_OAUTH2_GITHUB_ALLOW_EVERYONE - --oauth2-github-allow-signups Whether new users can sign up with - GitHub. - Consumes $CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS - --oauth2-github-allowed-orgs strings Organizations the user must be a member - of to Login with GitHub. - Consumes $CODER_OAUTH2_GITHUB_ALLOWED_ORGS - --oauth2-github-allowed-teams strings Teams inside organizations the user must - be a member of to Login with GitHub. - Structured as: - /. - Consumes $CODER_OAUTH2_GITHUB_ALLOWED_TEAMS - --oauth2-github-client-id string Client ID for Login with GitHub. - Consumes $CODER_OAUTH2_GITHUB_CLIENT_ID - --oauth2-github-client-secret string Client secret for Login with GitHub. - Consumes $CODER_OAUTH2_GITHUB_CLIENT_SECRET - --oauth2-github-enterprise-base-url string Base URL of a GitHub Enterprise - deployment to use for Login with GitHub. - Consumes - $CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL - --oidc-allow-signups Whether new users can sign up with OIDC. - Consumes $CODER_OIDC_ALLOW_SIGNUPS - (default true) - --oidc-client-id string Client ID to use for Login with OIDC. - Consumes $CODER_OIDC_CLIENT_ID - --oidc-client-secret string Client secret to use for Login with OIDC. - Consumes $CODER_OIDC_CLIENT_SECRET - --oidc-email-domain strings Email domains that clients logging in - with OIDC must match. - Consumes $CODER_OIDC_EMAIL_DOMAIN - --oidc-icon-url string URL pointing to the icon to use on the - OepnID Connect login button - Consumes $CODER_OIDC_ICON_URL - --oidc-ignore-email-verified Ignore the email_verified claim from the - upstream provider. - Consumes $CODER_OIDC_IGNORE_EMAIL_VERIFIED - --oidc-issuer-url string Issuer URL to use for Login with OIDC. - Consumes $CODER_OIDC_ISSUER_URL - --oidc-scopes strings Scopes to grant when authenticating with - OIDC. - Consumes $CODER_OIDC_SCOPES (default - [openid,profile,email]) - --oidc-sign-in-text string The text to show on the OpenID Connect - sign in button - Consumes $CODER_OIDC_SIGN_IN_TEXT - (default "OpenID Connect") - --oidc-username-field string OIDC claim field to use as the username. - Consumes $CODER_OIDC_USERNAME_FIELD - (default "preferred_username") - --postgres-url string URL of a PostgreSQL database. If empty, - PostgreSQL binaries will be downloaded - from Maven - (https://repo1.maven.org/maven2) and - store all data in the config root. Access - the built-in database with "coder server - postgres-builtin-url". - Consumes $CODER_PG_CONNECTION_URL - --pprof-address string The bind address to serve pprof. - Consumes $CODER_PPROF_ADDRESS (default - "127.0.0.1:6060") - --pprof-enable Serve pprof metrics on the address - defined by pprof address. - Consumes $CODER_PPROF_ENABLE - --prometheus-address string The bind address to serve prometheus - metrics. - Consumes $CODER_PROMETHEUS_ADDRESS - (default "127.0.0.1:2112") - --prometheus-enable Serve prometheus metrics on the address - defined by prometheus address. - Consumes $CODER_PROMETHEUS_ENABLE - --provisioner-daemon-poll-interval duration Time to wait before polling for a new - job. - Consumes - $CODER_PROVISIONER_DAEMON_POLL_INTERVAL - (default 1s) - --provisioner-daemon-poll-jitter duration Random jitter added to the poll interval. - Consumes - $CODER_PROVISIONER_DAEMON_POLL_JITTER - (default 100ms) - --provisioner-daemons int Number of provisioner daemons to create - on start. If builds are stuck in queued - state for a long time, consider - increasing this. - Consumes $CODER_PROVISIONER_DAEMONS - (default 3) - --provisioner-force-cancel-interval duration Time to force cancel provisioning tasks - that are stuck. - Consumes - $CODER_PROVISIONER_FORCE_CANCEL_INTERVAL - (default 10m0s) - --proxy-trusted-headers strings Headers to trust for forwarding IP - addresses. e.g. Cf-Connecting-Ip, - True-Client-Ip, X-Forwarded-For - Consumes $CODER_PROXY_TRUSTED_HEADERS - --proxy-trusted-origins strings Origin addresses to respect - "proxy-trusted-headers". e.g. - 192.168.1.0/24 - Consumes $CODER_PROXY_TRUSTED_ORIGINS - --redirect-to-access-url Specifies whether to redirect requests - that do not match the access URL host. - Consumes $CODER_REDIRECT_TO_ACCESS_URL - --secure-auth-cookie Controls if the 'Secure' property is set - on browser session cookies. - Consumes $CODER_SECURE_AUTH_COOKIE - --session-duration duration The token expiry duration for browser - sessions. Sessions may last longer if - they are actively making requests, but - this functionality can be disabled via - --disable-session-expiry-refresh. - Consumes $CODER_MAX_SESSION_EXPIRY - (default 24h0m0s) - --ssh-keygen-algorithm string The algorithm to use for generating ssh - keys. Accepted values are "ed25519", - "ecdsa", or "rsa4096". - Consumes $CODER_SSH_KEYGEN_ALGORITHM - (default "ed25519") - --swagger-enable Expose the swagger endpoint via /swagger. - Consumes $CODER_SWAGGER_ENABLE - --telemetry Whether telemetry is enabled or not. - Coder collects anonymized usage data to - help improve our product. - Consumes $CODER_TELEMETRY_ENABLE - --telemetry-trace Whether Opentelemetry traces are sent to - Coder. Coder collects anonymized - application tracing to help improve our - product. Disabling telemetry also - disables this option. - Consumes $CODER_TELEMETRY_TRACE - --tls-address string HTTPS bind address of the server. - Consumes $CODER_TLS_ADDRESS (default - "127.0.0.1:3443") - --tls-cert-file strings Path to each certificate for TLS. It - requires a PEM-encoded file. To configure - the listener to use a CA certificate, - concatenate the primary certificate and - the CA certificate together. The primary - certificate should appear first in the - combined file. - Consumes $CODER_TLS_CERT_FILE - --tls-client-auth string Policy the server will follow for TLS - Client Authentication. Accepted values - are "none", "request", "require-any", - "verify-if-given", or - "require-and-verify". - Consumes $CODER_TLS_CLIENT_AUTH (default - "none") - --tls-client-ca-file string PEM-encoded Certificate Authority file - used for checking the authenticity of - client - Consumes $CODER_TLS_CLIENT_CA_FILE - --tls-client-cert-file string Path to certificate for client TLS - authentication. It requires a PEM-encoded - file. - Consumes $CODER_TLS_CLIENT_CERT_FILE - --tls-client-key-file string Path to key for client TLS - authentication. It requires a PEM-encoded - file. - Consumes $CODER_TLS_CLIENT_KEY_FILE - --tls-enable Whether TLS will be enabled. - Consumes $CODER_TLS_ENABLE - --tls-key-file strings Paths to the private keys for each of the - certificates. It requires a PEM-encoded - file. - Consumes $CODER_TLS_KEY_FILE - --tls-min-version string Minimum supported version of TLS. - Accepted values are "tls10", "tls11", - "tls12" or "tls13" - Consumes $CODER_TLS_MIN_VERSION (default - "tls12") - --trace Whether application tracing data is - collected. It exports to a backend - configured by environment variables. See: - https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md - Consumes $CODER_TRACE_ENABLE - --trace-honeycomb-api-key string Enables trace exporting to Honeycomb.io - using the provided API Key. - Consumes $CODER_TRACE_HONEYCOMB_API_KEY - --trace-logs Enables capturing of logs as events in - traces. This is useful for debugging, but - may result in a very large amount of - events being sent to the tracing backend - which may incur significant costs. If the - verbose flag was supplied, debug-level - logs will be included. - Consumes $CODER_TRACE_CAPTURE_LOGS - --update-check Periodically check for new releases of - Coder and inform the owner. The check is - performed once per day. - Consumes $CODER_UPDATE_CHECK - --wildcard-access-url string Specifies the wildcard hostname to use - for workspace applications in the form - "*.example.com". - Consumes $CODER_WILDCARD_ACCESS_URL + --access-url string External URL to access your + deployment. This must be accessible + by all provisioned workspaces. + Consumes $CODER_ACCESS_URL + --api-rate-limit int Maximum number of requests per + minute allowed to the API per user, + or per IP address for + unauthenticated users. Negative + values mean no rate limit. Some API + endpoints have separate strict rate + limits regardless of this value to + prevent denial-of-service or brute + force attacks. + Consumes $CODER_API_RATE_LIMIT + (default 512) + --cache-dir string The directory to cache temporary + files. If unspecified and + $CACHE_DIRECTORY is set, it will be + used for compatibility with systemd. + Consumes $CODER_CACHE_DIRECTORY + (default "~/.cache/coder") + --dangerous-allow-path-app-sharing Allow workspace apps that are not + served from subdomains to be shared. + Path-based app sharing is DISABLED + by default for security purposes. + Path-based apps can make requests to + the Coder API and pose a security + risk when the workspace serves + malicious JavaScript. Path-based + apps can be disabled entirely with + --disable-path-apps for further + security. + Consumes + $CODER_DANGEROUS_ALLOW_PATH_APP_SHARING + --dangerous-allow-path-app-site-owner-access Allow site-owners to access + workspace apps from workspaces they + do not own. Owners cannot access + path-based apps they do not own by + default. Path-based apps can make + requests to the Coder API and pose a + security risk when the workspace + serves malicious JavaScript. + Path-based apps can be disabled + entirely with --disable-path-apps + for further security. + Consumes + $CODER_DANGEROUS_ALLOW_PATH_APP_SITE_OWNER_ACCESS + --dangerous-disable-rate-limits Disables all rate limits. This is + not recommended in production. + Consumes $CODER_RATE_LIMIT_DISABLE_ALL + --derp-config-path string Path to read a DERP mapping from. + See: + https://tailscale.com/kb/1118/custom-derp-servers/ + Consumes $CODER_DERP_CONFIG_PATH + --derp-config-url string URL to fetch a DERP mapping on + startup. See: + https://tailscale.com/kb/1118/custom-derp-servers/ + Consumes $CODER_DERP_CONFIG_URL + --derp-server-enable Whether to enable or disable the + embedded DERP relay server. + Consumes $CODER_DERP_SERVER_ENABLE + (default true) + --derp-server-region-code string Region code to use for the embedded + DERP server. + Consumes + $CODER_DERP_SERVER_REGION_CODE + (default "coder") + --derp-server-region-id int Region ID to use for the embedded + DERP server. + Consumes + $CODER_DERP_SERVER_REGION_ID + (default 999) + --derp-server-region-name string Region name that for the embedded + DERP server. + Consumes + $CODER_DERP_SERVER_REGION_NAME + (default "Coder Embedded Relay") + --derp-server-stun-addresses strings Addresses for STUN servers to + establish P2P connections. Set empty + to disable P2P connections. + Consumes + $CODER_DERP_SERVER_STUN_ADDRESSES + (default [stun.l.google.com:19302]) + --disable-password-auth coder server create-admin 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. + Consumes $CODER_DISABLE_PASSWORD_AUTH + --disable-path-apps Disable workspace apps that are not + served from subdomains. Path-based + apps can make requests to the Coder + API and pose a security risk when + the workspace serves malicious + JavaScript. This is recommended for + security purposes if a + --wildcard-access-url is configured. + Consumes $CODER_DISABLE_PATH_APPS + --disable-session-expiry-refresh Disable automatic session expiry + bumping due to activity. This forces + all sessions to become invalid after + the session expiry duration has been + reached. + Consumes + $CODER_DISABLE_SESSION_EXPIRY_REFRESH + --experiments strings Enable one or more experiments. + These are not ready for production. + Separate multiple experiments with + commas, or enter '*' to opt-in to + all available experiments. + Consumes $CODER_EXPERIMENTS + -h, --help help for server + --http-address string HTTP bind address of the server. + Unset to disable the HTTP endpoint. + Consumes $CODER_HTTP_ADDRESS + (default "127.0.0.1:3000") + --log-human string Output human-readable logs to a + given file. + Consumes $CODER_LOGGING_HUMAN + (default "/dev/stderr") + --log-json string Output JSON logs to a given file. + Consumes $CODER_LOGGING_JSON + --log-stackdriver string Output Stackdriver compatible logs + to a given file. + Consumes $CODER_LOGGING_STACKDRIVER + --max-token-lifetime duration The maximum lifetime duration users + can specify when creating an API + token. + Consumes $CODER_MAX_TOKEN_LIFETIME + (default 720h0m0s) + --oauth2-github-allow-everyone Allow all logins, setting this + option means allowed orgs and teams + must be empty. + Consumes + $CODER_OAUTH2_GITHUB_ALLOW_EVERYONE + --oauth2-github-allow-signups Whether new users can sign up with + GitHub. + Consumes + $CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS + --oauth2-github-allowed-orgs strings Organizations the user must be a + member of to Login with GitHub. + Consumes + $CODER_OAUTH2_GITHUB_ALLOWED_ORGS + --oauth2-github-allowed-teams strings Teams inside organizations the user + must be a member of to Login with + GitHub. Structured as: + /. + Consumes + $CODER_OAUTH2_GITHUB_ALLOWED_TEAMS + --oauth2-github-client-id string Client ID for Login with GitHub. + Consumes $CODER_OAUTH2_GITHUB_CLIENT_ID + --oauth2-github-client-secret string Client secret for Login with GitHub. + Consumes + $CODER_OAUTH2_GITHUB_CLIENT_SECRET + --oauth2-github-enterprise-base-url string Base URL of a GitHub Enterprise + deployment to use for Login with + GitHub. + Consumes + $CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL + --oidc-allow-signups Whether new users can sign up with + OIDC. + Consumes $CODER_OIDC_ALLOW_SIGNUPS + (default true) + --oidc-client-id string Client ID to use for Login with + OIDC. + Consumes $CODER_OIDC_CLIENT_ID + --oidc-client-secret string Client secret to use for Login with + OIDC. + Consumes $CODER_OIDC_CLIENT_SECRET + --oidc-email-domain strings Email domains that clients logging + in with OIDC must match. + Consumes $CODER_OIDC_EMAIL_DOMAIN + --oidc-icon-url string URL pointing to the icon to use on + the OepnID Connect login button + Consumes $CODER_OIDC_ICON_URL + --oidc-ignore-email-verified Ignore the email_verified claim from + the upstream provider. + Consumes + $CODER_OIDC_IGNORE_EMAIL_VERIFIED + --oidc-issuer-url string Issuer URL to use for Login with + OIDC. + Consumes $CODER_OIDC_ISSUER_URL + --oidc-scopes strings Scopes to grant when authenticating + with OIDC. + Consumes $CODER_OIDC_SCOPES (default + [openid,profile,email]) + --oidc-sign-in-text string The text to show on the OpenID + Connect sign in button + Consumes $CODER_OIDC_SIGN_IN_TEXT + (default "OpenID Connect") + --oidc-username-field string OIDC claim field to use as the + username. + Consumes $CODER_OIDC_USERNAME_FIELD + (default "preferred_username") + --postgres-url string URL of a PostgreSQL database. If + empty, PostgreSQL binaries will be + downloaded from Maven + (https://repo1.maven.org/maven2) and + store all data in the config root. + Access the built-in database with + "coder server postgres-builtin-url". + Consumes $CODER_PG_CONNECTION_URL + --pprof-address string The bind address to serve pprof. + Consumes $CODER_PPROF_ADDRESS + (default "127.0.0.1:6060") + --pprof-enable Serve pprof metrics on the address + defined by pprof address. + Consumes $CODER_PPROF_ENABLE + --prometheus-address string The bind address to serve prometheus + metrics. + Consumes $CODER_PROMETHEUS_ADDRESS + (default "127.0.0.1:2112") + --prometheus-enable Serve prometheus metrics on the + address defined by prometheus + address. + Consumes $CODER_PROMETHEUS_ENABLE + --provisioner-daemon-poll-interval duration Time to wait before polling for a + new job. + Consumes + $CODER_PROVISIONER_DAEMON_POLL_INTERVAL (default 1s) + --provisioner-daemon-poll-jitter duration Random jitter added to the poll + interval. + Consumes + $CODER_PROVISIONER_DAEMON_POLL_JITTER (default 100ms) + --provisioner-daemons int Number of provisioner daemons to + create on start. If builds are stuck + in queued state for a long time, + consider increasing this. + Consumes $CODER_PROVISIONER_DAEMONS + (default 3) + --provisioner-force-cancel-interval duration Time to force cancel provisioning + tasks that are stuck. + Consumes + $CODER_PROVISIONER_FORCE_CANCEL_INTERVAL (default 10m0s) + --proxy-trusted-headers strings Headers to trust for forwarding IP + addresses. e.g. Cf-Connecting-Ip, + True-Client-Ip, X-Forwarded-For + Consumes $CODER_PROXY_TRUSTED_HEADERS + --proxy-trusted-origins strings Origin addresses to respect + "proxy-trusted-headers". e.g. + 192.168.1.0/24 + Consumes $CODER_PROXY_TRUSTED_ORIGINS + --redirect-to-access-url Specifies whether to redirect + requests that do not match the + access URL host. + Consumes $CODER_REDIRECT_TO_ACCESS_URL + --secure-auth-cookie Controls if the 'Secure' property is + set on browser session cookies. + Consumes $CODER_SECURE_AUTH_COOKIE + --session-duration duration The token expiry duration for + browser sessions. Sessions may last + longer if they are actively making + requests, but this functionality can + be disabled via + --disable-session-expiry-refresh. + Consumes $CODER_MAX_SESSION_EXPIRY + (default 24h0m0s) + --ssh-keygen-algorithm string The algorithm to use for generating + ssh keys. Accepted values are + "ed25519", "ecdsa", or "rsa4096". + Consumes $CODER_SSH_KEYGEN_ALGORITHM + (default "ed25519") + --swagger-enable Expose the swagger endpoint via + /swagger. + Consumes $CODER_SWAGGER_ENABLE + --telemetry Whether telemetry is enabled or not. + Coder collects anonymized usage data + to help improve our product. + Consumes $CODER_TELEMETRY_ENABLE + --telemetry-trace Whether Opentelemetry traces are + sent to Coder. Coder collects + anonymized application tracing to + help improve our product. Disabling + telemetry also disables this option. + Consumes $CODER_TELEMETRY_TRACE + --tls-address string HTTPS bind address of the server. + Consumes $CODER_TLS_ADDRESS (default + "127.0.0.1:3443") + --tls-cert-file strings Path to each certificate for TLS. It + requires a PEM-encoded file. To + configure the listener to use a CA + certificate, concatenate the primary + certificate and the CA certificate + together. The primary certificate + should appear first in the combined + file. + Consumes $CODER_TLS_CERT_FILE + --tls-client-auth string Policy the server will follow for + TLS Client Authentication. Accepted + values are "none", "request", + "require-any", "verify-if-given", or + "require-and-verify". + Consumes $CODER_TLS_CLIENT_AUTH + (default "none") + --tls-client-ca-file string PEM-encoded Certificate Authority + file used for checking the + authenticity of client + Consumes $CODER_TLS_CLIENT_CA_FILE + --tls-client-cert-file string Path to certificate for client TLS + authentication. It requires a + PEM-encoded file. + Consumes $CODER_TLS_CLIENT_CERT_FILE + --tls-client-key-file string Path to key for client TLS + authentication. It requires a + PEM-encoded file. + Consumes $CODER_TLS_CLIENT_KEY_FILE + --tls-enable Whether TLS will be enabled. + Consumes $CODER_TLS_ENABLE + --tls-key-file strings Paths to the private keys for each + of the certificates. It requires a + PEM-encoded file. + Consumes $CODER_TLS_KEY_FILE + --tls-min-version string Minimum supported version of TLS. + Accepted values are "tls10", + "tls11", "tls12" or "tls13" + Consumes $CODER_TLS_MIN_VERSION + (default "tls12") + --trace Whether application tracing data is + collected. It exports to a backend + configured by environment variables. + See: + https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md + Consumes $CODER_TRACE_ENABLE + --trace-honeycomb-api-key string Enables trace exporting to + Honeycomb.io using the provided API + Key. + Consumes $CODER_TRACE_HONEYCOMB_API_KEY + --trace-logs Enables capturing of logs as events + in traces. This is useful for + debugging, but may result in a very + large amount of events being sent to + the tracing backend which may incur + significant costs. If the verbose + flag was supplied, debug-level logs + will be included. + Consumes $CODER_TRACE_CAPTURE_LOGS + --update-check Periodically check for new releases + of Coder and inform the owner. The + check is performed once per day. + Consumes $CODER_UPDATE_CHECK + --wildcard-access-url string Specifies the wildcard hostname to + use for workspace applications in + the form "*.example.com". + Consumes $CODER_WILDCARD_ACCESS_URL Global Flags: --global-config coder Path to the global coder config directory. diff --git a/cli/testdata/coder_server_create-admin-user_--help.golden b/cli/testdata/coder_server_create-admin-user_--help.golden new file mode 100644 index 0000000000000..1ff1e939ae6ee --- /dev/null +++ b/cli/testdata/coder_server_create-admin-user_--help.golden @@ -0,0 +1,36 @@ +Create a new admin user with the given username, email and password and adds it to every organization. + +Usage: + coder server create-admin-user [flags] + +Flags: + --email string The email of the new user. If not specified, you will be + prompted via stdin. Consumes $CODER_EMAIL. + -h, --help help for create-admin-user + --password string The password of the new user. If not specified, you will + be prompted via stdin. Consumes $CODER_PASSWORD. + --postgres-url string URL of a PostgreSQL database. If empty, the built-in + PostgreSQL deployment will be used (Coder must not be + already running in this case). Consumes $CODER_POSTGRES_URL. + --ssh-keygen-algorithm string The algorithm to use for generating ssh keys. Accepted + values are "ed25519", "ecdsa", or "rsa4096". Consumes + $CODER_SSH_KEYGEN_ALGORITHM. (default "ed25519") + --username string The username of the new user. If not specified, you will + be prompted via stdin. Consumes $CODER_USERNAME. + +Global Flags: + --global-config coder Path to the global coder config directory. + Consumes $CODER_CONFIG_DIR (default "~/.config/coderv2") + --header stringArray HTTP headers added to all requests. Provide as "Key=Value". + Consumes $CODER_HEADER + --no-feature-warning Suppress warnings about unlicensed features. + Consumes $CODER_NO_FEATURE_WARNING + --no-version-warning Suppress warning when client and server versions do not match. + Consumes $CODER_NO_VERSION_WARNING + --token string Specify an authentication token. For security reasons setting + CODER_SESSION_TOKEN is preferred. + Consumes $CODER_SESSION_TOKEN + --url string URL to a deployment. + Consumes $CODER_URL + -v, --verbose Enable verbose output. + Consumes $CODER_VERBOSE diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 174f96f00e08d..fed385c8450b6 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6012,6 +6012,9 @@ const docTemplate = `{ "derp": { "$ref": "#/definitions/codersdk.DERP" }, + "disable_password_auth": { + "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + }, "disable_path_apps": { "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index dddc939d06970..3fbc09a87cdbd 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5343,6 +5343,9 @@ "derp": { "$ref": "#/definitions/codersdk.DERP" }, + "disable_password_auth": { + "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + }, "disable_path_apps": { "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" }, diff --git a/coderd/httpapi/httpapi.go b/coderd/httpapi/httpapi.go index fb55cbb9ff5c7..942419182dfc1 100644 --- a/coderd/httpapi/httpapi.go +++ b/coderd/httpapi/httpapi.go @@ -18,15 +18,15 @@ import ( "github.com/coder/coder/codersdk" ) -var validate *validator.Validate +var Validate *validator.Validate // This init is used to create a validator and register validation-specific // functionality for the HTTP API. // // A single validator instance is used, because it caches struct parsing. func init() { - validate = validator.New() - validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + Validate = validator.New() + Validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" @@ -44,7 +44,7 @@ func init() { return valid == nil } for _, tag := range []string{"username", "template_name", "workspace_name"} { - err := validate.RegisterValidation(tag, nameValidator) + err := Validate.RegisterValidation(tag, nameValidator) if err != nil { panic(err) } @@ -59,7 +59,7 @@ func init() { valid := TemplateDisplayNameValid(str) return valid == nil } - err := validate.RegisterValidation("template_display_name", templateDisplayNameValidator) + err := Validate.RegisterValidation("template_display_name", templateDisplayNameValidator) if err != nil { panic(err) } @@ -144,7 +144,7 @@ func Read(ctx context.Context, rw http.ResponseWriter, r *http.Request, value in }) return false } - err = validate.Struct(value) + err = Validate.Struct(value) var validationErrors validator.ValidationErrors if errors.As(err, &validationErrors) { apiErrors := make([]codersdk.ValidationError, 0, len(validationErrors)) diff --git a/coderd/userauth.go b/coderd/userauth.go index a5a8a9a069309..a1f4b4efd5597 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -62,8 +62,10 @@ func (api *API) userAuthMethods(rw http.ResponseWriter, r *http.Request) { } httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.AuthMethods{ - Password: codersdk.AuthMethod{Enabled: true}, - Github: codersdk.AuthMethod{Enabled: api.GithubOAuth2Config != nil}, + Password: codersdk.AuthMethod{ + Enabled: !api.DeploymentConfig.DisablePasswordAuth.Value, + }, + Github: codersdk.AuthMethod{Enabled: api.GithubOAuth2Config != nil}, OIDC: codersdk.OIDCAuthMethod{ AuthMethod: codersdk.AuthMethod{Enabled: api.OIDCConfig != nil}, SignInText: signInText, diff --git a/coderd/users.go b/coderd/users.go index 6c237f421c2d8..a1d536e86c30a 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -1028,6 +1028,24 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) { return } + // If password authentication is disabled and the user does not have the + // owner role, block the request. + if api.DeploymentConfig.DisablePasswordAuth.Value { + permitted := false + for _, role := range user.RBACRoles { + if role == rbac.RoleOwner() { + permitted = true + break + } + } + if !permitted { + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ + Message: "Password authentication is disabled. Only administrators can sign in with password authentication.", + }) + return + } + } + if user.LoginType != database.LoginTypePassword { httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: fmt.Sprintf("Incorrect login type, attempting to use %q but user is of login type %q", database.LoginTypePassword, user.LoginType), diff --git a/coderd/users_test.go b/coderd/users_test.go index 7e6932073b5bd..2fcb196c23f91 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -86,35 +86,6 @@ func TestFirstUser(t *testing.T) { require.NoError(t, err) <-called }) - - t.Run("LastSeenAt", func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() - - client := coderdtest.New(t, nil) - firstUserResp := coderdtest.CreateFirstUser(t, client) - - firstUser, err := client.User(ctx, firstUserResp.UserID.String()) - require.NoError(t, err) - - _ = coderdtest.CreateAnotherUser(t, client, firstUserResp.OrganizationID) - - allUsersRes, err := client.Users(ctx, codersdk.UsersRequest{}) - require.NoError(t, err) - - require.Len(t, allUsersRes.Users, 2) - - // We sent the "GET Users" request with the first user, but the second user - // should be Never since they haven't performed a request. - for _, user := range allUsersRes.Users { - if user.ID == firstUser.ID { - require.WithinDuration(t, firstUser.LastSeenAt, database.Now(), testutil.WaitShort) - } else { - require.Zero(t, user.LastSeenAt) - } - } - }) } func TestPostLogin(t *testing.T) { @@ -191,6 +162,56 @@ func TestPostLogin(t *testing.T) { require.Contains(t, apiErr.Message, "suspended") }) + t.Run("DisabledPasswordAuth", func(t *testing.T) { + t.Parallel() + + dc := coderdtest.DeploymentConfig(t) + dc.DisablePasswordAuth.Value = true + client := coderdtest.New(t, &coderdtest.Options{ + DeploymentConfig: dc, + }) + + first := coderdtest.CreateFirstUser(t, client) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + // With a user account. + const password = "testpass" + user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{ + Email: "test+user-@coder.com", + Username: "user", + Password: password, + OrganizationID: first.OrganizationID, + }) + require.NoError(t, err) + + userClient := codersdk.New(client.URL) + _, err = userClient.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{ + Email: user.Email, + Password: password, + }) + require.Error(t, err) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusForbidden, apiErr.StatusCode()) + require.Contains(t, apiErr.Message, "Password authentication is disabled") + + // Promote the user account to an owner. + _, err = client.UpdateUserRoles(ctx, user.ID.String(), codersdk.UpdateRoles{ + Roles: []string{rbac.RoleOwner(), rbac.RoleMember()}, + }) + require.NoError(t, err) + + // Login with the user account. + res, err := userClient.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{ + Email: user.Email, + Password: password, + }) + require.NoError(t, err) + require.NotEmpty(t, res.SessionToken) + }) + t.Run("Success", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) @@ -437,6 +458,35 @@ func TestPostUsers(t *testing.T) { require.Len(t, auditor.AuditLogs, 1) assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[0].Action) }) + + t.Run("LastSeenAt", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + client := coderdtest.New(t, nil) + firstUserResp := coderdtest.CreateFirstUser(t, client) + + firstUser, err := client.User(ctx, firstUserResp.UserID.String()) + require.NoError(t, err) + + _ = coderdtest.CreateAnotherUser(t, client, firstUserResp.OrganizationID) + + allUsersRes, err := client.Users(ctx, codersdk.UsersRequest{}) + require.NoError(t, err) + + require.Len(t, allUsersRes.Users, 2) + + // We sent the "GET Users" request with the first user, but the second user + // should be Never since they haven't performed a request. + for _, user := range allUsersRes.Users { + if user.ID == firstUser.ID { + require.WithinDuration(t, firstUser.LastSeenAt, database.Now(), testutil.WaitShort) + } else { + require.Zero(t, user.LastSeenAt) + } + } + }) } func TestUpdateUserProfile(t *testing.T) { diff --git a/codersdk/deployment.go b/codersdk/deployment.go index e81f0448dff23..00b6238b63cd8 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -144,6 +144,7 @@ type DeploymentConfig struct { DisablePathApps *DeploymentConfigField[bool] `json:"disable_path_apps" typescript:",notnull"` SessionDuration *DeploymentConfigField[time.Duration] `json:"max_session_expiry" typescript:",notnull"` DisableSessionExpiryRefresh *DeploymentConfigField[bool] `json:"disable_session_expiry_refresh" typescript:",notnull"` + DisablePasswordAuth *DeploymentConfigField[bool] `json:"disable_password_auth" typescript:",notnull"` // DEPRECATED: Use HTTPAddress or TLS.Address instead. Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` diff --git a/docs/api/general.md b/docs/api/general.md index 961d7c1dcb350..423619d3c8265 100644 --- a/docs/api/general.md +++ b/docs/api/general.md @@ -289,6 +289,17 @@ curl -X GET http://coder-server:8080/api/v2/config/deployment \ } } }, + "disable_password_auth": { + "default": true, + "enterprise": true, + "flag": "string", + "hidden": true, + "name": "string", + "secret": true, + "shorthand": "string", + "usage": "string", + "value": true + }, "disable_path_apps": { "default": true, "enterprise": true, diff --git a/docs/api/schemas.md b/docs/api/schemas.md index b0e70b7a0d3c4..7ccdecad8271d 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -1647,6 +1647,17 @@ CreateParameterRequest is a structure used to create a new parameter value for a } } }, + "disable_password_auth": { + "default": true, + "enterprise": true, + "flag": "string", + "hidden": true, + "name": "string", + "secret": true, + "shorthand": "string", + "usage": "string", + "value": true + }, "disable_path_apps": { "default": true, "enterprise": true, @@ -2438,6 +2449,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a | `cache_directory` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | | `dangerous` | [codersdk.DangerousConfig](#codersdkdangerousconfig) | false | | | | `derp` | [codersdk.DERP](#codersdkderp) | false | | | +| `disable_password_auth` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | | `disable_path_apps` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | | `disable_session_expiry_refresh` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | | `experimental` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | Experimental Use Experiments instead. | diff --git a/docs/cli/coder_server.md b/docs/cli/coder_server.md index 087f11233964f..f6d42d755d755 100644 --- a/docs/cli/coder_server.md +++ b/docs/cli/coder_server.md @@ -9,147 +9,149 @@ coder server [flags] ### Options ``` - --access-url string External URL to access your deployment. This must be accessible by all provisioned workspaces. - Consumes $CODER_ACCESS_URL - --api-rate-limit int Maximum number of requests per minute allowed to the API per user, or per IP address for unauthenticated users. Negative values mean no rate limit. Some API endpoints have separate strict rate limits regardless of this value to prevent denial-of-service or brute force attacks. - Consumes $CODER_API_RATE_LIMIT (default 512) - --cache-dir string The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd. - Consumes $CODER_CACHE_DIRECTORY (default "~/.cache/coder") - --dangerous-allow-path-app-sharing Allow workspace apps that are not served from subdomains to be shared. Path-based app sharing is DISABLED by default for security purposes. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. Path-based apps can be disabled entirely with --disable-path-apps for further security. - Consumes $CODER_DANGEROUS_ALLOW_PATH_APP_SHARING - --dangerous-allow-path-app-site-owner-access Allow site-owners to access workspace apps from workspaces they do not own. Owners cannot access path-based apps they do not own by default. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. Path-based apps can be disabled entirely with --disable-path-apps for further security. - Consumes $CODER_DANGEROUS_ALLOW_PATH_APP_SITE_OWNER_ACCESS - --dangerous-disable-rate-limits Disables all rate limits. This is not recommended in production. - Consumes $CODER_RATE_LIMIT_DISABLE_ALL - --derp-config-path string Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp-servers/ - Consumes $CODER_DERP_CONFIG_PATH - --derp-config-url string URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/ - Consumes $CODER_DERP_CONFIG_URL - --derp-server-enable Whether to enable or disable the embedded DERP relay server. - Consumes $CODER_DERP_SERVER_ENABLE (default true) - --derp-server-region-code string Region code to use for the embedded DERP server. - Consumes $CODER_DERP_SERVER_REGION_CODE (default "coder") - --derp-server-region-id int Region ID to use for the embedded DERP server. - Consumes $CODER_DERP_SERVER_REGION_ID (default 999) - --derp-server-region-name string Region name that for the embedded DERP server. - Consumes $CODER_DERP_SERVER_REGION_NAME (default "Coder Embedded Relay") - --derp-server-stun-addresses strings Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections. - Consumes $CODER_DERP_SERVER_STUN_ADDRESSES (default [stun.l.google.com:19302]) - --disable-path-apps Disable workspace apps that are not served from subdomains. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. This is recommended for security purposes if a --wildcard-access-url is configured. - Consumes $CODER_DISABLE_PATH_APPS - --disable-session-expiry-refresh Disable automatic session expiry bumping due to activity. This forces all sessions to become invalid after the session expiry duration has been reached. - Consumes $CODER_DISABLE_SESSION_EXPIRY_REFRESH - --experiments strings Enable one or more experiments. These are not ready for production. Separate multiple experiments with commas, or enter '*' to opt-in to all available experiments. - Consumes $CODER_EXPERIMENTS - -h, --help help for server - --http-address string HTTP bind address of the server. Unset to disable the HTTP endpoint. - Consumes $CODER_HTTP_ADDRESS (default "127.0.0.1:3000") - --log-human string Output human-readable logs to a given file. - Consumes $CODER_LOGGING_HUMAN (default "/dev/stderr") - --log-json string Output JSON logs to a given file. - Consumes $CODER_LOGGING_JSON - --log-stackdriver string Output Stackdriver compatible logs to a given file. - Consumes $CODER_LOGGING_STACKDRIVER - --max-token-lifetime duration The maximum lifetime duration users can specify when creating an API token. - Consumes $CODER_MAX_TOKEN_LIFETIME (default 720h0m0s) - --oauth2-github-allow-everyone Allow all logins, setting this option means allowed orgs and teams must be empty. - Consumes $CODER_OAUTH2_GITHUB_ALLOW_EVERYONE - --oauth2-github-allow-signups Whether new users can sign up with GitHub. - Consumes $CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS - --oauth2-github-allowed-orgs strings Organizations the user must be a member of to Login with GitHub. - Consumes $CODER_OAUTH2_GITHUB_ALLOWED_ORGS - --oauth2-github-allowed-teams strings Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /. - Consumes $CODER_OAUTH2_GITHUB_ALLOWED_TEAMS - --oauth2-github-client-id string Client ID for Login with GitHub. - Consumes $CODER_OAUTH2_GITHUB_CLIENT_ID - --oauth2-github-client-secret string Client secret for Login with GitHub. - Consumes $CODER_OAUTH2_GITHUB_CLIENT_SECRET - --oauth2-github-enterprise-base-url string Base URL of a GitHub Enterprise deployment to use for Login with GitHub. - Consumes $CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL - --oidc-allow-signups Whether new users can sign up with OIDC. - Consumes $CODER_OIDC_ALLOW_SIGNUPS (default true) - --oidc-client-id string Client ID to use for Login with OIDC. - Consumes $CODER_OIDC_CLIENT_ID - --oidc-client-secret string Client secret to use for Login with OIDC. - Consumes $CODER_OIDC_CLIENT_SECRET - --oidc-email-domain strings Email domains that clients logging in with OIDC must match. - Consumes $CODER_OIDC_EMAIL_DOMAIN - --oidc-icon-url string URL pointing to the icon to use on the OepnID Connect login button - Consumes $CODER_OIDC_ICON_URL - --oidc-ignore-email-verified Ignore the email_verified claim from the upstream provider. - Consumes $CODER_OIDC_IGNORE_EMAIL_VERIFIED - --oidc-issuer-url string Issuer URL to use for Login with OIDC. - Consumes $CODER_OIDC_ISSUER_URL - --oidc-scopes strings Scopes to grant when authenticating with OIDC. - Consumes $CODER_OIDC_SCOPES (default [openid,profile,email]) - --oidc-sign-in-text string The text to show on the OpenID Connect sign in button - Consumes $CODER_OIDC_SIGN_IN_TEXT (default "OpenID Connect") - --oidc-username-field string OIDC claim field to use as the username. - Consumes $CODER_OIDC_USERNAME_FIELD (default "preferred_username") - --postgres-url string URL of a PostgreSQL database. If empty, PostgreSQL binaries will be downloaded from Maven (https://repo1.maven.org/maven2) and store all data in the config root. Access the built-in database with "coder server postgres-builtin-url". - Consumes $CODER_PG_CONNECTION_URL - --pprof-address string The bind address to serve pprof. - Consumes $CODER_PPROF_ADDRESS (default "127.0.0.1:6060") - --pprof-enable Serve pprof metrics on the address defined by pprof address. - Consumes $CODER_PPROF_ENABLE - --prometheus-address string The bind address to serve prometheus metrics. - Consumes $CODER_PROMETHEUS_ADDRESS (default "127.0.0.1:2112") - --prometheus-enable Serve prometheus metrics on the address defined by prometheus address. - Consumes $CODER_PROMETHEUS_ENABLE - --provisioner-daemon-poll-interval duration Time to wait before polling for a new job. - Consumes $CODER_PROVISIONER_DAEMON_POLL_INTERVAL (default 1s) - --provisioner-daemon-poll-jitter duration Random jitter added to the poll interval. - Consumes $CODER_PROVISIONER_DAEMON_POLL_JITTER (default 100ms) - --provisioner-daemons int Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this. - Consumes $CODER_PROVISIONER_DAEMONS (default 3) - --provisioner-force-cancel-interval duration Time to force cancel provisioning tasks that are stuck. - Consumes $CODER_PROVISIONER_FORCE_CANCEL_INTERVAL (default 10m0s) - --proxy-trusted-headers strings Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, True-Client-Ip, X-Forwarded-For - Consumes $CODER_PROXY_TRUSTED_HEADERS - --proxy-trusted-origins strings Origin addresses to respect "proxy-trusted-headers". e.g. 192.168.1.0/24 - Consumes $CODER_PROXY_TRUSTED_ORIGINS - --redirect-to-access-url Specifies whether to redirect requests that do not match the access URL host. - Consumes $CODER_REDIRECT_TO_ACCESS_URL - --secure-auth-cookie Controls if the 'Secure' property is set on browser session cookies. - Consumes $CODER_SECURE_AUTH_COOKIE - --session-duration duration The token expiry duration for browser sessions. Sessions may last longer if they are actively making requests, but this functionality can be disabled via --disable-session-expiry-refresh. - Consumes $CODER_MAX_SESSION_EXPIRY (default 24h0m0s) - --ssh-keygen-algorithm string The algorithm to use for generating ssh keys. Accepted values are "ed25519", "ecdsa", or "rsa4096". - Consumes $CODER_SSH_KEYGEN_ALGORITHM (default "ed25519") - --swagger-enable Expose the swagger endpoint via /swagger. - Consumes $CODER_SWAGGER_ENABLE - --telemetry Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product. - Consumes $CODER_TELEMETRY_ENABLE (default true) - --telemetry-trace Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option. - Consumes $CODER_TELEMETRY_TRACE (default true) - --tls-address string HTTPS bind address of the server. - Consumes $CODER_TLS_ADDRESS (default "127.0.0.1:3443") - --tls-cert-file strings Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file. - Consumes $CODER_TLS_CERT_FILE - --tls-client-auth string Policy the server will follow for TLS Client Authentication. Accepted values are "none", "request", "require-any", "verify-if-given", or "require-and-verify". - Consumes $CODER_TLS_CLIENT_AUTH (default "none") - --tls-client-ca-file string PEM-encoded Certificate Authority file used for checking the authenticity of client - Consumes $CODER_TLS_CLIENT_CA_FILE - --tls-client-cert-file string Path to certificate for client TLS authentication. It requires a PEM-encoded file. - Consumes $CODER_TLS_CLIENT_CERT_FILE - --tls-client-key-file string Path to key for client TLS authentication. It requires a PEM-encoded file. - Consumes $CODER_TLS_CLIENT_KEY_FILE - --tls-enable Whether TLS will be enabled. - Consumes $CODER_TLS_ENABLE - --tls-key-file strings Paths to the private keys for each of the certificates. It requires a PEM-encoded file. - Consumes $CODER_TLS_KEY_FILE - --tls-min-version string Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13" - Consumes $CODER_TLS_MIN_VERSION (default "tls12") - --trace Whether application tracing data is collected. It exports to a backend configured by environment variables. See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md - Consumes $CODER_TRACE_ENABLE - --trace-honeycomb-api-key string Enables trace exporting to Honeycomb.io using the provided API Key. - Consumes $CODER_TRACE_HONEYCOMB_API_KEY - --trace-logs Enables capturing of logs as events in traces. This is useful for debugging, but may result in a very large amount of events being sent to the tracing backend which may incur significant costs. If the verbose flag was supplied, debug-level logs will be included. - Consumes $CODER_TRACE_CAPTURE_LOGS - --update-check Periodically check for new releases of Coder and inform the owner. The check is performed once per day. - Consumes $CODER_UPDATE_CHECK - --wildcard-access-url string Specifies the wildcard hostname to use for workspace applications in the form "*.example.com". - Consumes $CODER_WILDCARD_ACCESS_URL + --access-url string External URL to access your deployment. This must be accessible by all provisioned workspaces. + Consumes $CODER_ACCESS_URL + --api-rate-limit int Maximum number of requests per minute allowed to the API per user, or per IP address for unauthenticated users. Negative values mean no rate limit. Some API endpoints have separate strict rate limits regardless of this value to prevent denial-of-service or brute force attacks. + Consumes $CODER_API_RATE_LIMIT (default 512) + --cache-dir string The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd. + Consumes $CODER_CACHE_DIRECTORY (default "~/.cache/coder") + --dangerous-allow-path-app-sharing Allow workspace apps that are not served from subdomains to be shared. Path-based app sharing is DISABLED by default for security purposes. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. Path-based apps can be disabled entirely with --disable-path-apps for further security. + Consumes $CODER_DANGEROUS_ALLOW_PATH_APP_SHARING + --dangerous-allow-path-app-site-owner-access Allow site-owners to access workspace apps from workspaces they do not own. Owners cannot access path-based apps they do not own by default. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. Path-based apps can be disabled entirely with --disable-path-apps for further security. + Consumes $CODER_DANGEROUS_ALLOW_PATH_APP_SITE_OWNER_ACCESS + --dangerous-disable-rate-limits Disables all rate limits. This is not recommended in production. + Consumes $CODER_RATE_LIMIT_DISABLE_ALL + --derp-config-path string Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp-servers/ + Consumes $CODER_DERP_CONFIG_PATH + --derp-config-url string URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/ + Consumes $CODER_DERP_CONFIG_URL + --derp-server-enable Whether to enable or disable the embedded DERP relay server. + Consumes $CODER_DERP_SERVER_ENABLE (default true) + --derp-server-region-code string Region code to use for the embedded DERP server. + Consumes $CODER_DERP_SERVER_REGION_CODE (default "coder") + --derp-server-region-id int Region ID to use for the embedded DERP server. + Consumes $CODER_DERP_SERVER_REGION_ID (default 999) + --derp-server-region-name string Region name that for the embedded DERP server. + Consumes $CODER_DERP_SERVER_REGION_NAME (default "Coder Embedded Relay") + --derp-server-stun-addresses strings Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections. + Consumes $CODER_DERP_SERVER_STUN_ADDRESSES (default [stun.l.google.com:19302]) + --disable-password-auth coder server create-admin 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. + Consumes $CODER_DISABLE_PASSWORD_AUTH + --disable-path-apps Disable workspace apps that are not served from subdomains. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. This is recommended for security purposes if a --wildcard-access-url is configured. + Consumes $CODER_DISABLE_PATH_APPS + --disable-session-expiry-refresh Disable automatic session expiry bumping due to activity. This forces all sessions to become invalid after the session expiry duration has been reached. + Consumes $CODER_DISABLE_SESSION_EXPIRY_REFRESH + --experiments strings Enable one or more experiments. These are not ready for production. Separate multiple experiments with commas, or enter '*' to opt-in to all available experiments. + Consumes $CODER_EXPERIMENTS + -h, --help help for server + --http-address string HTTP bind address of the server. Unset to disable the HTTP endpoint. + Consumes $CODER_HTTP_ADDRESS (default "127.0.0.1:3000") + --log-human string Output human-readable logs to a given file. + Consumes $CODER_LOGGING_HUMAN (default "/dev/stderr") + --log-json string Output JSON logs to a given file. + Consumes $CODER_LOGGING_JSON + --log-stackdriver string Output Stackdriver compatible logs to a given file. + Consumes $CODER_LOGGING_STACKDRIVER + --max-token-lifetime duration The maximum lifetime duration users can specify when creating an API token. + Consumes $CODER_MAX_TOKEN_LIFETIME (default 720h0m0s) + --oauth2-github-allow-everyone Allow all logins, setting this option means allowed orgs and teams must be empty. + Consumes $CODER_OAUTH2_GITHUB_ALLOW_EVERYONE + --oauth2-github-allow-signups Whether new users can sign up with GitHub. + Consumes $CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS + --oauth2-github-allowed-orgs strings Organizations the user must be a member of to Login with GitHub. + Consumes $CODER_OAUTH2_GITHUB_ALLOWED_ORGS + --oauth2-github-allowed-teams strings Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /. + Consumes $CODER_OAUTH2_GITHUB_ALLOWED_TEAMS + --oauth2-github-client-id string Client ID for Login with GitHub. + Consumes $CODER_OAUTH2_GITHUB_CLIENT_ID + --oauth2-github-client-secret string Client secret for Login with GitHub. + Consumes $CODER_OAUTH2_GITHUB_CLIENT_SECRET + --oauth2-github-enterprise-base-url string Base URL of a GitHub Enterprise deployment to use for Login with GitHub. + Consumes $CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL + --oidc-allow-signups Whether new users can sign up with OIDC. + Consumes $CODER_OIDC_ALLOW_SIGNUPS (default true) + --oidc-client-id string Client ID to use for Login with OIDC. + Consumes $CODER_OIDC_CLIENT_ID + --oidc-client-secret string Client secret to use for Login with OIDC. + Consumes $CODER_OIDC_CLIENT_SECRET + --oidc-email-domain strings Email domains that clients logging in with OIDC must match. + Consumes $CODER_OIDC_EMAIL_DOMAIN + --oidc-icon-url string URL pointing to the icon to use on the OepnID Connect login button + Consumes $CODER_OIDC_ICON_URL + --oidc-ignore-email-verified Ignore the email_verified claim from the upstream provider. + Consumes $CODER_OIDC_IGNORE_EMAIL_VERIFIED + --oidc-issuer-url string Issuer URL to use for Login with OIDC. + Consumes $CODER_OIDC_ISSUER_URL + --oidc-scopes strings Scopes to grant when authenticating with OIDC. + Consumes $CODER_OIDC_SCOPES (default [openid,profile,email]) + --oidc-sign-in-text string The text to show on the OpenID Connect sign in button + Consumes $CODER_OIDC_SIGN_IN_TEXT (default "OpenID Connect") + --oidc-username-field string OIDC claim field to use as the username. + Consumes $CODER_OIDC_USERNAME_FIELD (default "preferred_username") + --postgres-url string URL of a PostgreSQL database. If empty, PostgreSQL binaries will be downloaded from Maven (https://repo1.maven.org/maven2) and store all data in the config root. Access the built-in database with "coder server postgres-builtin-url". + Consumes $CODER_PG_CONNECTION_URL + --pprof-address string The bind address to serve pprof. + Consumes $CODER_PPROF_ADDRESS (default "127.0.0.1:6060") + --pprof-enable Serve pprof metrics on the address defined by pprof address. + Consumes $CODER_PPROF_ENABLE + --prometheus-address string The bind address to serve prometheus metrics. + Consumes $CODER_PROMETHEUS_ADDRESS (default "127.0.0.1:2112") + --prometheus-enable Serve prometheus metrics on the address defined by prometheus address. + Consumes $CODER_PROMETHEUS_ENABLE + --provisioner-daemon-poll-interval duration Time to wait before polling for a new job. + Consumes $CODER_PROVISIONER_DAEMON_POLL_INTERVAL (default 1s) + --provisioner-daemon-poll-jitter duration Random jitter added to the poll interval. + Consumes $CODER_PROVISIONER_DAEMON_POLL_JITTER (default 100ms) + --provisioner-daemons int Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this. + Consumes $CODER_PROVISIONER_DAEMONS (default 3) + --provisioner-force-cancel-interval duration Time to force cancel provisioning tasks that are stuck. + Consumes $CODER_PROVISIONER_FORCE_CANCEL_INTERVAL (default 10m0s) + --proxy-trusted-headers strings Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, True-Client-Ip, X-Forwarded-For + Consumes $CODER_PROXY_TRUSTED_HEADERS + --proxy-trusted-origins strings Origin addresses to respect "proxy-trusted-headers". e.g. 192.168.1.0/24 + Consumes $CODER_PROXY_TRUSTED_ORIGINS + --redirect-to-access-url Specifies whether to redirect requests that do not match the access URL host. + Consumes $CODER_REDIRECT_TO_ACCESS_URL + --secure-auth-cookie Controls if the 'Secure' property is set on browser session cookies. + Consumes $CODER_SECURE_AUTH_COOKIE + --session-duration duration The token expiry duration for browser sessions. Sessions may last longer if they are actively making requests, but this functionality can be disabled via --disable-session-expiry-refresh. + Consumes $CODER_MAX_SESSION_EXPIRY (default 24h0m0s) + --ssh-keygen-algorithm string The algorithm to use for generating ssh keys. Accepted values are "ed25519", "ecdsa", or "rsa4096". + Consumes $CODER_SSH_KEYGEN_ALGORITHM (default "ed25519") + --swagger-enable Expose the swagger endpoint via /swagger. + Consumes $CODER_SWAGGER_ENABLE + --telemetry Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product. + Consumes $CODER_TELEMETRY_ENABLE (default true) + --telemetry-trace Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option. + Consumes $CODER_TELEMETRY_TRACE (default true) + --tls-address string HTTPS bind address of the server. + Consumes $CODER_TLS_ADDRESS (default "127.0.0.1:3443") + --tls-cert-file strings Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file. + Consumes $CODER_TLS_CERT_FILE + --tls-client-auth string Policy the server will follow for TLS Client Authentication. Accepted values are "none", "request", "require-any", "verify-if-given", or "require-and-verify". + Consumes $CODER_TLS_CLIENT_AUTH (default "none") + --tls-client-ca-file string PEM-encoded Certificate Authority file used for checking the authenticity of client + Consumes $CODER_TLS_CLIENT_CA_FILE + --tls-client-cert-file string Path to certificate for client TLS authentication. It requires a PEM-encoded file. + Consumes $CODER_TLS_CLIENT_CERT_FILE + --tls-client-key-file string Path to key for client TLS authentication. It requires a PEM-encoded file. + Consumes $CODER_TLS_CLIENT_KEY_FILE + --tls-enable Whether TLS will be enabled. + Consumes $CODER_TLS_ENABLE + --tls-key-file strings Paths to the private keys for each of the certificates. It requires a PEM-encoded file. + Consumes $CODER_TLS_KEY_FILE + --tls-min-version string Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13" + Consumes $CODER_TLS_MIN_VERSION (default "tls12") + --trace Whether application tracing data is collected. It exports to a backend configured by environment variables. See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md + Consumes $CODER_TRACE_ENABLE + --trace-honeycomb-api-key string Enables trace exporting to Honeycomb.io using the provided API Key. + Consumes $CODER_TRACE_HONEYCOMB_API_KEY + --trace-logs Enables capturing of logs as events in traces. This is useful for debugging, but may result in a very large amount of events being sent to the tracing backend which may incur significant costs. If the verbose flag was supplied, debug-level logs will be included. + Consumes $CODER_TRACE_CAPTURE_LOGS + --update-check Periodically check for new releases of Coder and inform the owner. The check is performed once per day. + Consumes $CODER_UPDATE_CHECK + --wildcard-access-url string Specifies the wildcard hostname to use for workspace applications in the form "*.example.com". + Consumes $CODER_WILDCARD_ACCESS_URL ``` ### Options inherited from parent commands @@ -174,5 +176,6 @@ coder server [flags] ### SEE ALSO - [coder](coder.md) - +- [coder server create-admin-user](coder_server_create-admin-user.md) - Create a new admin user with the given username, email and password and adds it to every organization. - [coder server postgres-builtin-serve](coder_server_postgres-builtin-serve.md) - Run the built-in PostgreSQL deployment. - [coder server postgres-builtin-url](coder_server_postgres-builtin-url.md) - Output the connection URL for the built-in PostgreSQL deployment. diff --git a/docs/cli/coder_server_create-admin-user.md b/docs/cli/coder_server_create-admin-user.md new file mode 100644 index 0000000000000..d287cc12bbb9d --- /dev/null +++ b/docs/cli/coder_server_create-admin-user.md @@ -0,0 +1,41 @@ +## coder server create-admin-user + +Create a new admin user with the given username, email and password and adds it to every organization. + +``` +coder server create-admin-user [flags] +``` + +### Options + +``` + --email string The email of the new user. If not specified, you will be prompted via stdin. Consumes $CODER_EMAIL. + -h, --help help for create-admin-user + --password string The password of the new user. If not specified, you will be prompted via stdin. Consumes $CODER_PASSWORD. + --postgres-url string URL of a PostgreSQL database. If empty, the built-in PostgreSQL deployment will be used (Coder must not be already running in this case). Consumes $CODER_POSTGRES_URL. + --ssh-keygen-algorithm string The algorithm to use for generating ssh keys. Accepted values are "ed25519", "ecdsa", or "rsa4096". Consumes $CODER_SSH_KEYGEN_ALGORITHM. (default "ed25519") + --username string The username of the new user. If not specified, you will be prompted via stdin. Consumes $CODER_USERNAME. +``` + +### Options inherited from parent commands + +``` + --global-config coder Path to the global coder config directory. + Consumes $CODER_CONFIG_DIR (default "~/.config/coderv2") + --header stringArray HTTP headers added to all requests. Provide as "Key=Value". + Consumes $CODER_HEADER + --no-feature-warning Suppress warnings about unlicensed features. + Consumes $CODER_NO_FEATURE_WARNING + --no-version-warning Suppress warning when client and server versions do not match. + Consumes $CODER_NO_VERSION_WARNING + --token string Specify an authentication token. For security reasons setting CODER_SESSION_TOKEN is preferred. + Consumes $CODER_SESSION_TOKEN + --url string URL to a deployment. + Consumes $CODER_URL + -v, --verbose Enable verbose output. + Consumes $CODER_VERBOSE +``` + +### SEE ALSO + +- [coder server](coder_server.md) - Start a Coder server diff --git a/docs/manifest.json b/docs/manifest.json index d674f22052bf9..ecfff14d19bb5 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -509,6 +509,10 @@ "title": "server", "path": "./cli/coder_server.md" }, + { + "title": "server create-admin-user", + "path": "./cli/coder_server_create-admin-user.md" + }, { "title": "server postgres-builtin-serve", "path": "./cli/coder_server_postgres-builtin-serve.md" diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index e922a7e23d2b7..ada04f1c46215 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -327,6 +327,7 @@ export interface DeploymentConfig { readonly disable_path_apps: DeploymentConfigField readonly max_session_expiry: DeploymentConfigField readonly disable_session_expiry_refresh: DeploymentConfigField + readonly disable_password_auth: DeploymentConfigField readonly address: DeploymentConfigField readonly experimental: DeploymentConfigField }