Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: configurable superuser password #306

Merged
merged 6 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/clients-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ jobs:

- name: Start MyDuck Server
run: |
./myduckserver &
sleep 5
./myduckserver --flightsql-port 47470 &
sleep 10

- name: Run the Compatibility Test for FlightSQL
run: |
Expand Down
67 changes: 67 additions & 0 deletions .github/workflows/password-auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Password Auth Test

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'

- name: Install system packages
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: postgresql-client mysql-client
version: 1.0

- name: Install dependencies
run: |
go get .

pip3 install "sqlglot[rs]"

curl -LJO https://github.com/duckdb/duckdb/releases/latest/download/duckdb_cli-linux-amd64.zip
unzip duckdb_cli-linux-amd64.zip
chmod +x duckdb
sudo mv duckdb /usr/local/bin

- name: Build
run: go build -v

- name: Start MyDuck Server with password
run: |
./myduckserver --superuser-password=testpass123 &
sleep 5

- name: Test PostgreSQL auth
run: |
export PGPASSWORD=testpass123
# Basic connection test
psql -h 127.0.0.1 -U postgres -d postgres -c "SELECT 1 as test;"
# Create and query a table
psql -h 127.0.0.1 -U postgres -d postgres -c "CREATE TABLE test (id int); INSERT INTO test VALUES (42); SELECT * FROM test;"
# Test wrong password
! PGPASSWORD=wrongpass psql -h 127.0.0.1 -U postgres -d postgres -c "SELECT 1"

- name: Test MySQL auth
run: |
# Basic connection test
mysql -h127.0.0.1 -uroot -ptestpass123 -e "SELECT 1 as test;"
# Create and query a table
mysql -h127.0.0.1 -uroot -ptestpass123 -e "CREATE DATABASE IF NOT EXISTS test; USE test; CREATE TABLE t1 (id int); INSERT INTO t1 VALUES (42); SELECT * FROM t1;"
# Test wrong password
! mysql -h127.0.0.1 -uroot -pwrongpass -e "SELECT 1"
4 changes: 1 addition & 3 deletions .github/workflows/psql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,10 @@ jobs:

pip3 install "sqlglot[rs]" pyarrow pandas

curl -LJO https://github.com/duckdb/duckdb/releases/download/v1.1.3/duckdb_cli-linux-amd64.zip
curl -LJO https://github.com/duckdb/duckdb/releases/latest/download/duckdb_cli-linux-amd64.zip
unzip duckdb_cli-linux-amd64.zip
chmod +x duckdb
sudo mv duckdb /usr/local/bin
duckdb -c 'INSTALL json from core'
duckdb -c 'SELECT extension_name, loaded, install_path FROM duckdb_extensions() where installed'

- name: Build
run: go build -v
Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,18 +163,34 @@ With MyDuck's powerful analytics capabilities, you can create an hybrid transact
To rename the default database, pass the `DEFAULT_DB` environment variable to the Docker container:

```bash
docker run -p 13306:3306 -p 15432:5432 --env=DEFAULT_DB=mydbname apecloud/myduckserver:latest
docker run -d -p 13306:3306 -p 15432:5432 \
--env=DEFAULT_DB=mydbname \
apecloud/myduckserver:latest
```


To set the superuser password, pass the `SUPERUSER_PASSWORD` environment variable to the Docker container:

```bash
docker run -d -p 13306:3306 -p 15432:5432 \
--env=SUPERUSER_PASSWORD=mysecretpassword \
apecloud/myduckserver:latest
```


To initialize MyDuck Server with custom SQL statements, mount your `.sql` file to either `/docker-entrypoint-initdb.d/mysql/` or `/docker-entrypoint-initdb.d/postgres/` inside the Docker container, depending on the SQL dialect you're using.

For example:
```bash
# Execute `init.sql` via MySQL protocol
docker run -d -p 13306:3306 --name=myduck -v ./init.sql:/docker-entrypoint-initdb.d/mysql/init.sql apecloud/myduckserver:latest
docker run -d -p 13306:3306 --name=myduck \
-v ./init.sql:/docker-entrypoint-initdb.d/mysql/init.sql \
apecloud/myduckserver:latest

# Execute `init.sql` via PostgreSQL protocol
docker run -d -p 15432:5432 --name=myduck -v ./init.sql:/docker-entrypoint-initdb.d/postgres/init.sql apecloud/myduckserver:latest
docker run -d -p 15432:5432 --name=myduck \
-v ./init.sql:/docker-entrypoint-initdb.d/postgres/init.sql \
apecloud/myduckserver:latest
```

### Query Parquet Files
Expand Down
2 changes: 1 addition & 1 deletion devtools/replica-setup-postgres/replica_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ usage() {

MYDUCK_HOST=${MYDUCK_HOST:-127.0.0.1}
MYDUCK_PORT=${MYDUCK_PORT:-5432}
MYDUCK_USER=${MYDUCK_USER:-mysql}
MYDUCK_USER=${MYDUCK_USER:-postgres}
MYDUCK_PASSWORD=${MYDUCK_PASSWORD:-}
MYDUCK_SERVER_ID=${MYDUCK_SERVER_ID:-2}
MYDUCK_IN_DOCKER=${MYDUCK_IN_DOCKER:-false}
Expand Down
6 changes: 5 additions & 1 deletion docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ run_replica_setup() {

run_server_in_background() {
cd "$DATA_PATH" || { echo "Error: Could not change directory to ${DATA_PATH}"; exit 1; }
nohup myduckserver $DEFAULT_DB $LOG_LEVEL $PROFILER_PORT $RESTORE_FILE $RESTORE_ENDPOINT $RESTORE_ACCESS_KEY_ID $RESTORE_SECRET_ACCESS_KEY|tee -a "${LOG_PATH}/server.log" 2>&1 &
nohup myduckserver $DEFAULT_DB $SUPERUSER_PASSWORD $LOG_LEVEL $PROFILER_PORT $RESTORE_FILE $RESTORE_ENDPOINT $RESTORE_ACCESS_KEY_ID $RESTORE_SECRET_ACCESS_KEY | tee -a "${LOG_PATH}/server.log" 2>&1 &
echo "$!" > "${PID_FILE}"
}

Expand Down Expand Up @@ -207,6 +207,10 @@ setup() {
export DEFAULT_DB="--default-db=$DEFAULT_DB"
fi

if [ -n "$SUPERUSER_PASSWORD" ]; then
export SUPERUSER_PASSWORD="--superuser-password=$SUPERUSER_PASSWORD"
fi

if [ -n "$LOG_LEVEL" ]; then
export LOG_LEVEL="--loglevel=$LOG_LEVEL"
fi
Expand Down
10 changes: 8 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ var (

postgresPort = 5432

// Shared between the MySQL and Postgres servers.
superuserPassword = ""

defaultTimeZone = ""

// for Restore
Expand All @@ -68,7 +71,7 @@ var (
restoreSecretAccessKey = ""

flightsqlHost = "localhost"
flightsqlPort = 47470
flightsqlPort = -1 // Disabled by default
)

func init() {
Expand All @@ -81,6 +84,8 @@ func init() {
flag.StringVar(&defaultDb, "default-db", defaultDb, "The default database name to use.")
flag.IntVar(&logLevel, "loglevel", logLevel, "The log level to use.")

flag.StringVar(&superuserPassword, "superuser-password", superuserPassword, "The password for the superuser account.")

flag.StringVar(&replicaOptions.ReportHost, "report-host", replicaOptions.ReportHost, "The host name or IP address of the replica to be reported to the source during replica registration.")
flag.IntVar(&replicaOptions.ReportPort, "report-port", replicaOptions.ReportPort, "The TCP/IP port number for connecting to the replica, to be reported to the source during replica registration.")
flag.StringVar(&replicaOptions.ReportUser, "report-user", replicaOptions.ReportUser, "The account user name of the replica to be reported to the source during replica registration.")
Expand Down Expand Up @@ -154,7 +159,7 @@ func main() {
engine.Analyzer.Catalog.RegisterFunction(sql.NewContext(context.Background()), myfunc.ExtraBuiltIns...)
engine.Analyzer.Catalog.MySQLDb.SetPlugins(plugin.AuthPlugins)

if err := setPersister(provider, engine); err != nil {
if err := setPersister(provider, engine, "root", superuserPassword); err != nil {
logrus.Fatalln("Failed to set the persister:", err)
}

Expand Down Expand Up @@ -182,6 +187,7 @@ func main() {
pgServer, err := pgserver.NewServer(
provider, pool,
address, postgresPort,
superuserPassword,
func() *sql.Context {
session := backend.NewSession(memory.NewSession(sql.NewBaseSession(), provider), provider, pool)
return sql.NewContext(context.Background(), sql.WithSession(session))
Expand Down
8 changes: 4 additions & 4 deletions persist.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (m *MySQLPersister) Persist(ctx *sql.Context, data []byte) error {
}

// https://github.com/dolthub/go-mysql-server/blob/main/_example/users_example.go
func setPersister(provider sql.DatabaseProvider, engine *sqle.Engine) error {
func setPersister(provider sql.DatabaseProvider, engine *sqle.Engine, superuser, password string) error {
session := memory.NewSession(sql.NewBaseSession(), provider)
ctx := sql.NewContext(context.Background(), sql.WithSession(session))
ctx.SetCurrentDatabase("mysql")
Expand Down Expand Up @@ -67,16 +67,16 @@ func setPersister(provider sql.DatabaseProvider, engine *sqle.Engine) error {
}
}

addAccount := func(account string, address string) {
addAccount := func(account, password, address string) {
ed := mysqlDb.Editor()
defer ed.Close()
mysqlDb.AddSuperUser(ed, account, address, "")
mysqlDb.AddSuperUser(ed, account, address, password)
}

// Modify it to "%" to allow accepting connections outside when myduckserver runs in Docker
// TODO should add a config to decide this or some better way to support this
// addAccount("root", "localhost")
addAccount("root", "%")
addAccount(superuser, password, "%")

return nil
}
32 changes: 16 additions & 16 deletions pgserver/authentication_scram.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"encoding/base64"
"fmt"
"net"
"os"
"strings"

"github.com/dolthub/doltgresql/server/auth"
Expand All @@ -39,26 +38,27 @@ const (
)

// EnableAuthentication handles whether authentication is enabled. If enabled, it verifies that the given user exists,
// and checks that the encrypted password is derivable from the stored encrypted password. As the feature is still in
// development, it is disabled by default. It may be enabled by supplying the environment variable
// "DOLTGRES_ENABLE_AUTHENTICATION", or by simply setting this boolean to true.
var EnableAuthentication = false

func init() {
if _, ok := os.LookupEnv("DOLTGRES_ENABLE_AUTHENTICATION"); ok {
EnableAuthentication = true
}
// and checks that the encrypted password is derivable from the stored encrypted password.
var EnableAuthentication = true

func InitSuperuser(password string) {
auth.DropRole("doltgres")
auth.DropRole("postgres")

var err error
mysql := auth.CreateDefaultRole("mysql")
mysql.CanLogin = true
mysql.Password, err = auth.NewScramSha256Password("")
postgres := auth.CreateDefaultRole("postgres")
postgres.CanLogin = true
postgres.Password, err = auth.NewScramSha256Password(password)
if err != nil {
panic(err)
}
auth.SetRole(mysql)
auth.SetRole(postgres)

// Postgres does not allow empty passwords,
// so we disable authentication if the superuser password is empty.
if password == "" {
EnableAuthentication = false
}
}

// SASLBindingFlag are the flags for gs2-cbind-flag, used in SASL authentication.
Expand Down Expand Up @@ -111,15 +111,15 @@ func (h *ConnectionHandler) handleAuthentication(startupMessage *pgproto3.Startu
}
}
} else {
username = "doltgres" // TODO: should we use this, or the default "postgres" since programs may default to it?
username = "postgres"
host = "localhost"
}
h.mysqlConn.User = username
h.mysqlConn.UserData = sql.MysqlConnectionUser{
User: username,
Host: host,
}
// Since this is all still in development, we'll check if authentication is enabled.
// Currently, regression tests disable authentication, since we can't just replay the messages due to nonces.
if !EnableAuthentication {
return h.send(&pgproto3.AuthenticationOk{})
}
Expand Down
4 changes: 3 additions & 1 deletion pgserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pgserver

import (
"fmt"

"github.com/apecloud/myduckserver/backend"
"github.com/apecloud/myduckserver/catalog"
"github.com/dolthub/go-mysql-server/server"
Expand All @@ -16,7 +17,8 @@ type Server struct {
NewInternalCtx func() *sql.Context
}

func NewServer(provider *catalog.DatabaseProvider, connPool *backend.ConnectionPool, host string, port int, newCtx func() *sql.Context, options ...ListenerOpt) (*Server, error) {
func NewServer(provider *catalog.DatabaseProvider, connPool *backend.ConnectionPool, host string, port int, password string, newCtx func() *sql.Context, options ...ListenerOpt) (*Server, error) {
InitSuperuser(password)
addr := fmt.Sprintf("%s:%d", host, port)
l, err := server.NewListener("tcp", addr, "")
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion pgtest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func CreateTestServer(t *testing.T, port int) (ctx context.Context, pgServer *pg
pgServer, err = pgserver.NewServer(
provider, pool,
"127.0.0.1", port,
"",
func() *sql.Context {
session := backend.NewSession(memory.NewSession(sql.NewBaseSession(), provider), provider, pool)
return sql.NewContext(context.Background(), sql.WithSession(session))
Expand All @@ -79,7 +80,7 @@ func CreateTestServer(t *testing.T, port int) (ctx context.Context, pgServer *pg
}

// Since we use the in-memory DuckDB storage, we need to connect to the `memory` database
dsn := fmt.Sprintf("postgres://mysql:@127.0.0.1:%d/memory", port)
dsn := fmt.Sprintf("postgres://postgres:@127.0.0.1:%d/memory", port)
conn, err = pgx.Connect(ctx, dsn)
if err != nil {
close()
Expand Down
Loading