From a5a6a0dd23c506aed396e16cf9d14baef9952280 Mon Sep 17 00:00:00 2001 From: Noah S-C Date: Wed, 19 Jun 2024 15:02:55 +0100 Subject: [PATCH] feat(sg): command to add default site-admin with predefined access token (#63320) Adds a subcommand to `sg db` called `default-site-admin` that creates a site-admin user with user:pass `sourcegraph:sourcegraph` and a predefined hard-coded token `sgp_local_f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0` ## Test plan `go run ./dev/sg -- db default-site-admin` with clean database `" "` after having run that (when everything should be set) `" "` when user exists but token doesnt ## Changelog --- dev/sg/BUILD.bazel | 1 + dev/sg/sg_db.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/dev/sg/BUILD.bazel b/dev/sg/BUILD.bazel index e6a84fdd282..10b49e20345 100644 --- a/dev/sg/BUILD.bazel +++ b/dev/sg/BUILD.bazel @@ -97,6 +97,7 @@ go_library( "//internal/download", "//internal/encryption", "//internal/extsvc", + "//internal/hashutil", "//internal/lazyregexp", "//internal/observation", "//internal/types", diff --git a/dev/sg/sg_db.go b/dev/sg/sg_db.go index 6f31d4437d4..27e96f04c4c 100644 --- a/dev/sg/sg_db.go +++ b/dev/sg/sg_db.go @@ -3,15 +3,18 @@ package main import ( "context" "database/sql" + "encoding/hex" "encoding/json" "fmt" "net/url" + "os" "strings" "time" "github.com/gomodule/redigo/redis" "github.com/google/go-github/v55/github" "github.com/jackc/pgx/v4" + "github.com/keegancsmith/sqlf" "github.com/urfave/cli/v2" "golang.org/x/oauth2" @@ -20,6 +23,7 @@ import ( "github.com/sourcegraph/sourcegraph/dev/sg/internal/category" "github.com/sourcegraph/sourcegraph/dev/sg/internal/db" "github.com/sourcegraph/sourcegraph/dev/sg/internal/std" + "github.com/sourcegraph/sourcegraph/internal/accesstoken" "github.com/sourcegraph/sourcegraph/internal/database" "github.com/sourcegraph/sourcegraph/internal/database/basestore" connections "github.com/sourcegraph/sourcegraph/internal/database/connections/live" @@ -31,6 +35,7 @@ import ( "github.com/sourcegraph/sourcegraph/internal/database/postgresdsn" "github.com/sourcegraph/sourcegraph/internal/encryption" "github.com/sourcegraph/sourcegraph/internal/extsvc" + "github.com/sourcegraph/sourcegraph/internal/hashutil" "github.com/sourcegraph/sourcegraph/internal/observation" "github.com/sourcegraph/sourcegraph/internal/types" "github.com/sourcegraph/sourcegraph/lib/cliutil/exit" @@ -180,6 +185,12 @@ sg db add-access-token -username=foo }, Action: dbAddAccessTokenAction, }, + + { + Name: "default-site-admin", + Usage: "Create a predefined site-admin user with a preset access token", + Action: dbDefaultSiteAdmin, + }, }, } ) @@ -284,6 +295,74 @@ func dbAddAccessTokenAction(cmd *cli.Context) error { }) } +// equivalent to "f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0" +var magicTokenSuffix = [20]byte{240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240} + +func dbDefaultSiteAdmin(cmd *cli.Context) error { + logger := log.Scoped("dbAddDefaultAccessTokenAction") + ttyOutput := output.NewOutput(os.Stdout, output.OutputOpts{}) + + conf, _ := getConfig() + if conf == nil { + return errors.New("failed to read sg.config.yaml. This command needs to be run in the `sourcegraph` repository") + } + + conn, err := connections.EnsureNewFrontendDB(observation.NewContext(logger), postgresdsn.New("", "", conf.GetEnv), "frontend") + if err != nil { + return err + } + + db := database.NewDB(logger, conn) + + const ( + username = "sourcegraph" + password = "sourcegraph" + ) + + return db.WithTransact(cmd.Context, func(tx database.DB) error { + user, err := tx.Users().Create(cmd.Context, database.NewUser{ + Username: username, + Email: "sourcegraph@sourcegraph.com", + EmailIsVerified: true, + Password: password, + }) + if err != nil && !database.IsUsernameExists(err) { + return err + } else if database.IsUsernameExists(err) { + user, err = tx.Users().GetByUsername(cmd.Context, username) + if err != nil { + return err + } + ttyOutput.WriteLine(output.Emojif(output.EmojiInfo, "User %q already exists, continuing...", username)) + } + + // Make the user site admin. + err = tx.Users().SetIsSiteAdmin(cmd.Context, user.ID, true) + if err != nil { + return err + } + + token := fmt.Sprintf("%s%s_%s", accesstoken.PersonalAccessTokenPrefix, accesstoken.LocalInstanceIdentifier, hex.EncodeToString(magicTokenSuffix[:])) + + if t, err := tx.AccessTokens().GetByToken(cmd.Context, token); t != nil || err != database.ErrAccessTokenNotFound { + if t != nil { + ttyOutput.WriteLine(output.Emojif(output.EmojiSuccess, "Default site-admin token already set for %q: %q", username, token)) + } + return err + } + + q := sqlf.Sprintf(`INSERT INTO access_tokens(subject_user_id, scopes, value_sha256, note, creator_user_id, expires_at, internal) + VALUES (%s, '{"user:all"}', %s, 'Default token for site-admin user created by sg', %s, NULL, false)`, user.ID, hashutil.ToSHA256Bytes(magicTokenSuffix[:]), user.ID) + if _, err = tx.ExecContext(cmd.Context, q.Query(sqlf.PostgresBindVar), q.Args()...); err != nil { + return err + } + + ttyOutput.WriteLine(output.Emojif(output.EmojiSuccess, "Default site-admin successfully created with username %q, password %q and token %q", username, password, token)) + + return nil + }) +} + func dbUpdateUserExternalAccount(cmd *cli.Context) error { logger := log.Scoped("dbUpdateUserExternalAccount") ctx := cmd.Context