Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
420 lines
13 KiB
Go
420 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/server/postgres"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
flag "github.com/seaweedfs/seaweedfs/weed/util/fla9"
|
|
)
|
|
|
|
const usageLine = "weed-db -port=5432 -master=<master_server>"
|
|
|
|
const longHelp = `Start a PostgreSQL wire protocol compatible database server that provides SQL query access to SeaweedFS.
|
|
|
|
This database server enables any PostgreSQL client, tool, or application to connect to SeaweedFS
|
|
and execute SQL queries against MQ topics. It implements the PostgreSQL wire protocol for maximum
|
|
compatibility with the existing PostgreSQL ecosystem.
|
|
|
|
Examples:
|
|
|
|
# Start database server on default port 5432
|
|
weed-db
|
|
|
|
# Start with MD5 authentication using JSON format (recommended)
|
|
weed-db -auth=md5 -users='{"admin":"secret","readonly":"view123"}'
|
|
|
|
# Start with complex passwords using JSON format
|
|
weed-db -auth=md5 -users='{"admin":"pass;with;semicolons","user":"password:with:colons"}'
|
|
|
|
# Start with credentials from JSON file (most secure)
|
|
weed-db -auth=md5 -users="@/etc/seaweedfs/users.json"
|
|
|
|
# Start with custom port and master
|
|
weed-db -port=5433 -master=master1:9333
|
|
|
|
# Allow connections from any host
|
|
weed-db -host=0.0.0.0 -port=5432
|
|
|
|
# Start with TLS encryption
|
|
weed-db -tls-cert=server.crt -tls-key=server.key
|
|
|
|
Client Connection Examples:
|
|
|
|
# psql command line client
|
|
psql "host=localhost port=5432 dbname=default user=seaweedfs"
|
|
psql -h localhost -p 5432 -U seaweedfs -d default
|
|
|
|
# With password
|
|
PGPASSWORD=secret psql -h localhost -p 5432 -U admin -d default
|
|
|
|
# Connection string
|
|
psql "postgresql://admin:secret@localhost:5432/default"
|
|
|
|
Programming Language Examples:
|
|
|
|
# Python (psycopg2)
|
|
import psycopg2
|
|
conn = psycopg2.connect(
|
|
host="localhost", port=5432,
|
|
user="seaweedfs", database="default"
|
|
)
|
|
|
|
# Java JDBC
|
|
String url = "jdbc:postgresql://localhost:5432/default";
|
|
Connection conn = DriverManager.getConnection(url, "seaweedfs", "");
|
|
|
|
# Go (lib/pq)
|
|
db, err := sql.Open("postgres", "host=localhost port=5432 user=seaweedfs dbname=default sslmode=disable")
|
|
|
|
# Node.js (pg)
|
|
const client = new Client({
|
|
host: 'localhost', port: 5432,
|
|
user: 'seaweedfs', database: 'default'
|
|
});
|
|
|
|
Supported SQL Operations:
|
|
- SELECT queries on MQ topics
|
|
- DESCRIBE/DESC table_name commands
|
|
- EXPLAIN query execution plans
|
|
- SHOW DATABASES/TABLES commands
|
|
- Aggregation functions (COUNT, SUM, AVG, MIN, MAX)
|
|
- WHERE clauses with filtering
|
|
- System columns (_timestamp_ns, _key, _source)
|
|
- Basic PostgreSQL system queries (version(), current_database(), current_user)
|
|
|
|
Authentication Methods:
|
|
- trust: No authentication required (default)
|
|
- password: Clear text password authentication
|
|
- md5: MD5 password authentication
|
|
|
|
User Credential Formats:
|
|
- JSON format: '{"user1":"pass1","user2":"pass2"}' (supports any special characters)
|
|
- File format: "@/path/to/users.json" (JSON file)
|
|
|
|
Note: JSON format supports passwords with semicolons, colons, and any other special characters.
|
|
File format is recommended for production to keep credentials secure.
|
|
|
|
Compatible Tools:
|
|
- psql (PostgreSQL command line client)
|
|
- Any PostgreSQL JDBC/ODBC compatible tool
|
|
|
|
Security Features:
|
|
- Multiple authentication methods
|
|
- TLS encryption support
|
|
- Read-only access (no data modification)
|
|
|
|
Performance Features:
|
|
- Fast path aggregation optimization (COUNT, MIN, MAX without WHERE clauses)
|
|
- Hybrid data scanning (parquet files + live logs)
|
|
- PostgreSQL wire protocol
|
|
- Query result streaming
|
|
|
|
`
|
|
|
|
type Options struct {
|
|
Host string
|
|
Port int
|
|
MasterAddr string
|
|
AuthMethod string
|
|
Users string
|
|
Database string
|
|
MaxConns int
|
|
IdleTimeout string
|
|
TLSCert string
|
|
TLSKey string
|
|
}
|
|
|
|
// Run executes the weed-db CLI.
|
|
func Run(args []string) int {
|
|
fs := flag.NewFlagSet("weed-db", flag.ContinueOnError)
|
|
usageWriter := io.Writer(os.Stderr)
|
|
fs.SetOutput(usageWriter)
|
|
|
|
var opts Options
|
|
fs.StringVar(&opts.Host, "host", "localhost", "Database server host")
|
|
fs.IntVar(&opts.Port, "port", 5432, "Database server port")
|
|
fs.StringVar(&opts.MasterAddr, "master", "localhost:9333", "SeaweedFS master server address")
|
|
fs.StringVar(&opts.AuthMethod, "auth", "trust", "Authentication method: trust, password, md5")
|
|
fs.StringVar(&opts.Users, "users", "", "User credentials for auth (JSON format '{\"user1\":\"pass1\",\"user2\":\"pass2\"}' or file '@/path/to/users.json')")
|
|
fs.StringVar(&opts.Database, "database", "default", "Default database name")
|
|
fs.IntVar(&opts.MaxConns, "max-connections", 100, "Maximum concurrent connections per server")
|
|
fs.StringVar(&opts.IdleTimeout, "idle-timeout", "1h", "Connection idle timeout")
|
|
fs.StringVar(&opts.TLSCert, "tls-cert", "", "TLS certificate file path")
|
|
fs.StringVar(&opts.TLSKey, "tls-key", "", "TLS private key file path")
|
|
|
|
fs.Usage = func() {
|
|
fmt.Fprintf(usageWriter, "Usage: %s\n\n%s\n", usageLine, longHelp)
|
|
fmt.Fprintln(usageWriter, "Default Parameters:")
|
|
fs.PrintDefaults()
|
|
}
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
return 2
|
|
}
|
|
|
|
if !runWithOptions(&opts) {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func runWithOptions(opts *Options) bool {
|
|
util.LoadConfiguration("security", false)
|
|
|
|
// Validate options.
|
|
if opts.MasterAddr == "" {
|
|
fmt.Fprintf(os.Stderr, "Error: master address is required\n")
|
|
return false
|
|
}
|
|
|
|
// Parse authentication method.
|
|
authMethod, err := parseAuthMethod(opts.AuthMethod)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
// Parse user credentials.
|
|
users, err := parseUsers(opts.Users, authMethod)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
// Parse idle timeout.
|
|
idleTimeout, err := time.ParseDuration(opts.IdleTimeout)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing idle timeout: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
// Validate port number.
|
|
if err := validatePortNumber(opts.Port); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
// Setup TLS if requested.
|
|
var tlsConfig *tls.Config
|
|
if opts.TLSCert != "" && opts.TLSKey != "" {
|
|
cert, err := tls.LoadX509KeyPair(opts.TLSCert, opts.TLSKey)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error loading TLS certificates: %v\n", err)
|
|
return false
|
|
}
|
|
tlsConfig = &tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
}
|
|
}
|
|
|
|
// Create server configuration.
|
|
config := &postgres.PostgreSQLServerConfig{
|
|
Host: opts.Host,
|
|
Port: opts.Port,
|
|
AuthMethod: authMethod,
|
|
Users: users,
|
|
Database: opts.Database,
|
|
MaxConns: opts.MaxConns,
|
|
IdleTimeout: idleTimeout,
|
|
TLSConfig: tlsConfig,
|
|
}
|
|
|
|
// Create database server.
|
|
dbServer, err := postgres.NewPostgreSQLServer(config, opts.MasterAddr)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error creating database server: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
// Print startup information.
|
|
fmt.Printf("Starting SeaweedFS Database Server...\n")
|
|
fmt.Printf("Host: %s\n", opts.Host)
|
|
fmt.Printf("Port: %d\n", opts.Port)
|
|
fmt.Printf("Master: %s\n", opts.MasterAddr)
|
|
fmt.Printf("Database: %s\n", opts.Database)
|
|
fmt.Printf("Auth Method: %s\n", opts.AuthMethod)
|
|
fmt.Printf("Max Connections: %d\n", opts.MaxConns)
|
|
fmt.Printf("Idle Timeout: %s\n", opts.IdleTimeout)
|
|
if tlsConfig != nil {
|
|
fmt.Printf("TLS: Enabled\n")
|
|
} else {
|
|
fmt.Printf("TLS: Disabled\n")
|
|
}
|
|
if len(users) > 0 {
|
|
fmt.Printf("Users: %d configured\n", len(users))
|
|
}
|
|
|
|
fmt.Printf("\nDatabase Connection Examples:\n")
|
|
fmt.Printf(" psql -h %s -p %d -U seaweedfs -d %s\n", opts.Host, opts.Port, opts.Database)
|
|
if len(users) > 0 {
|
|
// Show first user as example.
|
|
for username := range users {
|
|
fmt.Printf(" psql -h %s -p %d -U %s -d %s\n", opts.Host, opts.Port, username, opts.Database)
|
|
break
|
|
}
|
|
}
|
|
fmt.Printf(" postgresql://%s:%d/%s\n", opts.Host, opts.Port, opts.Database)
|
|
|
|
fmt.Printf("\nSupported Operations:\n")
|
|
fmt.Printf(" - SELECT queries on MQ topics\n")
|
|
fmt.Printf(" - DESCRIBE/DESC table_name\n")
|
|
fmt.Printf(" - EXPLAIN query execution plans\n")
|
|
fmt.Printf(" - SHOW DATABASES/TABLES\n")
|
|
fmt.Printf(" - Aggregations: COUNT, SUM, AVG, MIN, MAX\n")
|
|
fmt.Printf(" - System columns: _timestamp_ns, _key, _source\n")
|
|
fmt.Printf(" - Basic PostgreSQL system queries\n")
|
|
|
|
fmt.Printf("\nReady for database connections!\n\n")
|
|
|
|
// Start the server.
|
|
err = dbServer.Start()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error starting database server: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
// Set up signal handling for graceful shutdown.
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
// Wait for shutdown signal.
|
|
<-sigChan
|
|
fmt.Printf("\nReceived shutdown signal, stopping database server...\n")
|
|
|
|
// Create context with timeout for graceful shutdown.
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Stop the server with timeout.
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- dbServer.Stop()
|
|
}()
|
|
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error stopping database server: %v\n", err)
|
|
return false
|
|
}
|
|
fmt.Printf("Database server stopped successfully\n")
|
|
case <-ctx.Done():
|
|
fmt.Fprintf(os.Stderr, "Timeout waiting for database server to stop\n")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// parseAuthMethod parses the authentication method string.
|
|
func parseAuthMethod(method string) (postgres.AuthMethod, error) {
|
|
switch strings.ToLower(method) {
|
|
case "trust":
|
|
return postgres.AuthTrust, nil
|
|
case "password":
|
|
return postgres.AuthPassword, nil
|
|
case "md5":
|
|
return postgres.AuthMD5, nil
|
|
default:
|
|
return postgres.AuthTrust, fmt.Errorf("unsupported auth method '%s'. Supported: trust, password, md5", method)
|
|
}
|
|
}
|
|
|
|
// parseUsers parses the user credentials string with support for secure formats only.
|
|
// Supported formats:
|
|
// 1. JSON format: {"username":"password","username2":"password2"}
|
|
// 2. File format: /path/to/users.json or @/path/to/users.json
|
|
func parseUsers(usersStr string, authMethod postgres.AuthMethod) (map[string]string, error) {
|
|
users := make(map[string]string)
|
|
|
|
if usersStr == "" {
|
|
// No users specified.
|
|
if authMethod != postgres.AuthTrust {
|
|
return nil, fmt.Errorf("users must be specified when auth method is not 'trust'")
|
|
}
|
|
return users, nil
|
|
}
|
|
|
|
// Trim whitespace.
|
|
usersStr = strings.TrimSpace(usersStr)
|
|
|
|
// Determine format and parse accordingly.
|
|
if strings.HasPrefix(usersStr, "{") && strings.HasSuffix(usersStr, "}") {
|
|
// JSON format.
|
|
return parseUsersJSON(usersStr, authMethod)
|
|
}
|
|
|
|
// Check if it's a file path (with or without @ prefix) before declaring invalid format.
|
|
filePath := strings.TrimPrefix(usersStr, "@")
|
|
if _, err := os.Stat(filePath); err == nil {
|
|
// File format.
|
|
return parseUsersFile(usersStr, authMethod) // Pass original string to preserve @ handling.
|
|
}
|
|
|
|
// Invalid format.
|
|
return nil, fmt.Errorf("invalid user credentials format. Use JSON format '{\"user\":\"pass\"}' or file format '@/path/to/users.json' or 'path/to/users.json'. Legacy semicolon-separated format is no longer supported")
|
|
}
|
|
|
|
// parseUsersJSON parses user credentials from JSON format.
|
|
func parseUsersJSON(jsonStr string, authMethod postgres.AuthMethod) (map[string]string, error) {
|
|
var users map[string]string
|
|
if err := json.Unmarshal([]byte(jsonStr), &users); err != nil {
|
|
return nil, fmt.Errorf("invalid JSON format for users: %v", err)
|
|
}
|
|
|
|
// Validate users.
|
|
for username, password := range users {
|
|
if username == "" {
|
|
return nil, fmt.Errorf("empty username in JSON user specification")
|
|
}
|
|
if authMethod != postgres.AuthTrust && password == "" {
|
|
return nil, fmt.Errorf("empty password for user '%s' with auth method", username)
|
|
}
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
// parseUsersFile parses user credentials from a JSON file.
|
|
func parseUsersFile(filePath string, authMethod postgres.AuthMethod) (map[string]string, error) {
|
|
// Remove @ prefix if present.
|
|
filePath = strings.TrimPrefix(filePath, "@")
|
|
|
|
// Read file content.
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read users file '%s': %v", filePath, err)
|
|
}
|
|
|
|
contentStr := strings.TrimSpace(string(content))
|
|
|
|
// File must contain JSON format.
|
|
if !strings.HasPrefix(contentStr, "{") || !strings.HasSuffix(contentStr, "}") {
|
|
return nil, fmt.Errorf("users file '%s' must contain JSON format: {\"user\":\"pass\"}. Legacy formats are no longer supported", filePath)
|
|
}
|
|
|
|
// Parse as JSON.
|
|
return parseUsersJSON(contentStr, authMethod)
|
|
}
|
|
|
|
// validatePortNumber validates that the port number is reasonable.
|
|
func validatePortNumber(port int) error {
|
|
if port < 1 || port > 65535 {
|
|
return fmt.Errorf("port number must be between 1 and 65535, got %d", port)
|
|
}
|
|
if port < 1024 {
|
|
fmt.Fprintf(os.Stderr, "Warning: port number %d may require root privileges\n", port)
|
|
}
|
|
return nil
|
|
}
|