Phase 1: Detection & Guidance - Detect OS user vs DB user mismatch - Identify PostgreSQL authentication method (peer/ident/md5) - Show helpful error messages with 4 solutions: 1. sudo -u <user> (for peer auth) 2. ~/.pgpass file (recommended) 3. PGPASSWORD env variable 4. --password flag Phase 2: pgpass Support - Auto-load passwords from ~/.pgpass file - Support standard PostgreSQL pgpass format - Check file permissions (must be 0600) - Support wildcard matching (host:port:db:user:pass) Tested on CentOS Stream 10 with PostgreSQL 16
12 KiB
12 KiB
Database Authentication Enhancement Plan
Current Situation Analysis
PostgreSQL Authentication Methods (by Distribution)
Current System: CentOS Stream 10
-
Local (Unix socket):
peerauthentication- Requires OS username = PostgreSQL username
- Example:
sudo -u postgres ./dbbackup status --user postgres✅ - Fails:
./dbbackup status --user postgres❌ (peer auth failed)
-
TCP (localhost):
identauthentication- Uses identd protocol to verify OS username
- Similar to peer but over TCP
Common PostgreSQL Auth Methods Across Distributions
- peer - Unix socket only, OS user must match DB user
- ident - TCP/IP, uses identd to verify OS user
- md5/scram-sha-256 - Password-based (most common for remote)
- trust - No authentication (development only)
- cert - SSL certificate-based
- ldap/pam - Enterprise integration
MySQL/MariaDB Authentication
- Typically uses password-based authentication by default
- Can use unix_socket plugin (similar to peer)
- Less likely to have peer/ident issues
Problem Statement
When user runs:
./dbbackup status --user postgres
The tool attempts to connect as "postgres" user, but:
- Root user context: OS user is "root", PostgreSQL expects "postgres"
- Peer auth fails:
FATAL: Peer authentication failed for user "postgres" - User must know: Need
sudo -u postgresor provide password
Solution Strategy: Multi-Level Authentication
Level 1: Smart OS User Detection (Quick Win)
Goal: Detect when OS user ≠ DB user and provide helpful guidance
Implementation:
// Check if OS user matches requested DB user
currentOSUser := getCurrentUser()
requestedDBUser := cfg.User
if currentOSUser != requestedDBUser {
// Check authentication method
authMethod := detectPostgreSQLAuthMethod(cfg.Host, cfg.Port)
if authMethod == "peer" || authMethod == "ident" {
// Peer/ident requires OS user = DB user
if cfg.Password == "" {
// No password provided, suggest sudo
log.Warn("Authentication mismatch detected",
"os_user", currentOSUser,
"db_user", requestedDBUser,
"auth_method", authMethod)
fmt.Printf("\n⚠️ Authentication Note:\n")
fmt.Printf(" PostgreSQL is using '%s' authentication\n", authMethod)
fmt.Printf(" OS user '%s' cannot authenticate as DB user '%s'\n",
currentOSUser, requestedDBUser)
fmt.Printf("\n💡 Solutions:\n")
fmt.Printf(" 1. Run as matching user: sudo -u %s %s\n",
requestedDBUser, os.Args[0])
fmt.Printf(" 2. Provide password: %s --password <password>\n",
os.Args[0])
fmt.Printf(" 3. Set PGPASSWORD environment variable\n")
fmt.Printf(" 4. Configure ~/.pgpass file\n\n")
return fmt.Errorf("authentication method requires matching OS user")
}
}
}
Level 2: Auto-Sudo Wrapper (Medium Effort)
Goal: Automatically re-execute with sudo when needed
Implementation:
func autoSudoIfNeeded(cfg *Config) error {
currentUser := getCurrentUser()
// Check if we need sudo and aren't already using it
if currentUser != cfg.User && os.Getenv("DBBACKUP_SUDO_RETRY") == "" {
authMethod := detectAuthMethod(cfg)
if authMethod == "peer" || authMethod == "ident" {
if cfg.Password == "" {
fmt.Printf("🔄 Auto-retrying with sudo as user '%s'...\n", cfg.User)
// Re-execute with sudo
cmd := exec.Command("sudo", "-u", cfg.User, os.Args[0], os.Args[1:]...)
cmd.Env = append(os.Environ(), "DBBACKUP_SUDO_RETRY=1")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("sudo retry failed: %w", err)
}
os.Exit(cmd.ProcessState.ExitCode())
}
}
}
return nil
}
Level 3: pgpass Support (High Value)
Goal: Use ~/.pgpass file for password-less authentication
Implementation:
// Check ~/.pgpass and /var/lib/pgsql/.pgpass
func loadPasswordFromPgpass(cfg *Config) (string, bool) {
pgpassLocations := []string{
filepath.Join(os.Getenv("HOME"), ".pgpass"),
"/var/lib/pgsql/.pgpass",
filepath.Join("/home", cfg.User, ".pgpass"),
}
for _, pgpassPath := range pgpassLocations {
if password := parsePgpass(pgpassPath, cfg); password != "" {
return password, true
}
}
return "", false
}
// Format: hostname:port:database:username:password
func parsePgpass(path string, cfg *Config) string {
file, err := os.Open(path)
if err != nil {
return ""
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.Split(line, ":")
if len(parts) != 5 {
continue
}
host, port, db, user, pass := parts[0], parts[1], parts[2], parts[3], parts[4]
// Match hostname (* = wildcard)
if host != "*" && host != cfg.Host {
continue
}
// Match port (* = wildcard)
if port != "*" && port != strconv.Itoa(cfg.Port) {
continue
}
// Match database (* = wildcard)
if db != "*" && db != cfg.Database {
continue
}
// Match user (* = wildcard)
if user != "*" && user != cfg.User {
continue
}
return pass
}
return ""
}
Level 4: Smart Environment Detection (Advanced)
Goal: Detect distribution and suggest optimal configuration
Implementation:
type OSDistribution struct {
Name string
Family string // debian, redhat, arch, etc.
PostgreSQLVersion string
DefaultAuthMethod string
SocketLocation string
SuggestedUser string
}
func detectDistribution() *OSDistribution {
// Read /etc/os-release
data, err := os.ReadFile("/etc/os-release")
if err != nil {
return &OSDistribution{Name: "unknown"}
}
content := string(data)
dist := &OSDistribution{}
// Parse os-release
for _, line := range strings.Split(content, "\n") {
if strings.HasPrefix(line, "ID=") {
dist.Name = strings.Trim(strings.TrimPrefix(line, "ID="), "\"")
}
if strings.HasPrefix(line, "ID_LIKE=") {
dist.Family = strings.Trim(strings.TrimPrefix(line, "ID_LIKE="), "\"")
}
}
// Distribution-specific defaults
switch dist.Name {
case "centos", "rhel", "fedora":
dist.DefaultAuthMethod = "peer"
dist.SocketLocation = "/var/run/postgresql"
dist.SuggestedUser = "postgres"
case "debian", "ubuntu":
dist.DefaultAuthMethod = "peer"
dist.SocketLocation = "/var/run/postgresql"
dist.SuggestedUser = "postgres"
case "arch", "manjaro":
dist.DefaultAuthMethod = "peer"
dist.SocketLocation = "/run/postgresql"
dist.SuggestedUser = "postgres"
case "alpine":
dist.DefaultAuthMethod = "md5"
dist.SocketLocation = "/run/postgresql"
dist.SuggestedUser = "postgres"
}
return dist
}
Implementation Phases
Phase 1: Detection & Guidance (1-2 hours)
- ✅ Detect OS user vs DB user mismatch
- ✅ Detect PostgreSQL authentication method (peer/ident/md5)
- ✅ Provide helpful error messages with solutions
- ✅ Show example commands for current system
Files to modify:
internal/config/config.go- Add OS user detectioninternal/database/postgresql.go- Add auth method detectioncmd/root.go- Add pre-connection validation
Phase 2: pgpass Support (2-3 hours)
- ✅ Read and parse ~/.pgpass file
- ✅ Support wildcard matching
- ✅ Check multiple pgpass locations
- ✅ Fall back to password prompt if needed
Files to modify:
internal/config/config.go- Add pgpass loadinginternal/database/postgresql.go- Integrate pgpass passwords
Phase 3: Auto-Sudo (3-4 hours) - OPTIONAL
- ⚠️ Automatically detect when sudo is needed
- ⚠️ Re-execute command with sudo -u
- ⚠️ Preserve all arguments and flags
- ⚠️ Handle interactive prompts
Considerations:
- Security implications of auto-sudo
- May surprise users (implicit behavior change)
- Could interfere with scripting/automation
Phase 4: Distribution-Aware Setup (4-5 hours) - OPTIONAL
- Detect Linux distribution
- Provide distribution-specific guidance
- Auto-configure optimal settings
- Generate setup scripts for first-run
Recommended Approach: Phase 1 + Phase 2
Why this combination?
- Phase 1: Immediate value - users understand what's wrong
- Phase 2: Standard PostgreSQL solution - no surprises
- Skip Phase 3: Auto-sudo can be confusing/dangerous
- Skip Phase 4: Users know their own distribution
User Experience Flow:
# User runs without proper auth
$ ./dbbackup status --user postgres
⚠️ Authentication Note:
PostgreSQL is using 'peer' authentication
OS user 'root' cannot authenticate as DB user 'postgres'
💡 Solutions:
1. Run as matching user: sudo -u postgres ./dbbackup
2. Provide password: ./dbbackup --password <password>
3. Set PGPASSWORD environment variable
4. Configure ~/.pgpass file (recommended)
📝 To create ~/.pgpass file:
echo "localhost:5432:*:postgres:yourpassword" > ~/.pgpass
chmod 0600 ~/.pgpass
# User fixes authentication
$ sudo -u postgres ./dbbackup status --user postgres
✅ Connected successfully
Testing Matrix
PostgreSQL Authentication Methods
- peer (Unix socket) - CentOS/RHEL default
- ident (TCP/IP) - Some distributions
- md5 (Password) - Common for remote
- scram-sha-256 (Password) - Modern PostgreSQL
- trust (No auth) - Development only
Operating Systems
- CentOS Stream 10 (current system)
- Ubuntu 22.04/24.04 (most popular)
- Debian 12 (stable)
- Fedora 40 (cutting edge)
- Alpine Linux (containers)
Scenarios
- Root user connecting as postgres user
- Postgres user connecting as postgres user
- Regular user with pgpass file
- Regular user with PGPASSWORD env
- Regular user with --password flag
- TCP vs Unix socket connections
- Remote database connections
Security Considerations
- pgpass file permissions: Must be 0600 (owner read/write only)
- Password in command line: Discourage --password flag (visible in ps)
- PGPASSWORD env: Better than command line, but still visible
- Auto-sudo: Could be security risk if not carefully implemented
- Error messages: Don't expose sensitive connection details
Backward Compatibility
✅ No breaking changes - All existing workflows continue to work:
sudo -u postgres ./dbbackup✅PGPASSWORD=secret ./dbbackup✅./dbbackup --password secret✅- Current socket detection logic ✅
Conclusion
Recommended Implementation: Phase 1 + Phase 2
- Effort: 3-5 hours total
- Value: High - users can authenticate without sudo
- Risk: Low - using standard PostgreSQL mechanisms
- Complexity: Medium - well-defined scope
Skip: Phase 3 (Auto-sudo)
- Can surprise users with implicit behavior
- Security implications
- Not standard PostgreSQL practice
Defer: Phase 4 (Distribution detection)
- Nice-to-have but not essential
- Users generally know their own system
- Can be added later if needed