Add support for .pgpass file (#617)

* Add support for .pgpass file
* Support password lookup in /api/connect endpoint
* Restrore removed code for BuildStringFromOptions
* Restructure connection string test and add extra case for pgpass
* Add test for FormatURL method
This commit is contained in:
Dan Sosedoff
2022-12-14 13:37:49 -06:00
committed by GitHub
parent 40474d3990
commit e0a748812d
8 changed files with 368 additions and 187 deletions

View File

@@ -5,8 +5,10 @@ import (
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/jackc/pgpassfile"
"github.com/jessevdk/go-flags"
"github.com/sirupsen/logrus"
)
@@ -26,6 +28,7 @@ type Options struct {
Port int `long:"port" description:"Server port" default:"5432"`
User string `long:"user" description:"Database user"`
Pass string `long:"pass" description:"Password for user"`
Passfile string `long:"passfile" description:"Local passwords file location"`
DbName string `long:"db" description:"Database name"`
SSLMode string `long:"ssl" description:"SSL mode"`
SSLRootCert string `long:"ssl-rootcert" description:"SSL certificate authority file"`
@@ -79,6 +82,23 @@ func ParseOptions(args []string) (Options, error) {
opts.Prefix = getPrefixedEnvVar("URL_PREFIX")
}
if opts.Passfile == "" {
passfile := os.Getenv("PGPASSFILE")
if passfile == "" {
passfile = filepath.Join(os.Getenv("HOME"), ".pgpass")
}
_, err := os.Stat(passfile)
if err == nil {
_, err = pgpassfile.ReadPassfile(passfile)
if err == nil {
opts.Passfile = passfile
} else {
fmt.Printf("[WARN] Pgpass file unreadable: %s\n", err)
}
}
}
// Handle edge case where pgweb is started with a default host `localhost` and no user.
// When user is not set the `lib/pq` connection will fail and cause pgweb's termination.
if (opts.Host == "localhost" || opts.Host == "127.0.0.1") && opts.User == "" {

View File

@@ -1,47 +1,75 @@
package command
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseOptions(t *testing.T) {
// Test default behavior
opts, err := ParseOptions([]string{})
assert.NoError(t, err)
assert.Equal(t, false, opts.Sessions)
assert.Equal(t, "", opts.Prefix)
assert.Equal(t, "", opts.ConnectToken)
assert.Equal(t, "", opts.ConnectHeaders)
assert.Equal(t, false, opts.DisableSSH)
assert.Equal(t, false, opts.DisablePrettyJSON)
assert.Equal(t, false, opts.DisableConnectionIdleTimeout)
assert.Equal(t, 180, opts.ConnectionIdleTimeout)
assert.Equal(t, false, opts.Cors)
assert.Equal(t, "*", opts.CorsOrigin)
t.Run("defaults", func(t *testing.T) {
opts, err := ParseOptions([]string{})
assert.NoError(t, err)
assert.Equal(t, false, opts.Sessions)
assert.Equal(t, "", opts.Prefix)
assert.Equal(t, "", opts.ConnectToken)
assert.Equal(t, "", opts.ConnectHeaders)
assert.Equal(t, false, opts.DisableSSH)
assert.Equal(t, false, opts.DisablePrettyJSON)
assert.Equal(t, false, opts.DisableConnectionIdleTimeout)
assert.Equal(t, 180, opts.ConnectionIdleTimeout)
assert.Equal(t, false, opts.Cors)
assert.Equal(t, "*", opts.CorsOrigin)
assert.Equal(t, "", opts.Passfile)
})
// Test sessions
opts, err = ParseOptions([]string{"--sessions", "1"})
assert.NoError(t, err)
assert.Equal(t, true, opts.Sessions)
t.Run("sessions", func(t *testing.T) {
opts, err := ParseOptions([]string{"--sessions", "1"})
assert.NoError(t, err)
assert.Equal(t, true, opts.Sessions)
})
// Test url prefix
opts, err = ParseOptions([]string{"--prefix", "pgweb"})
assert.NoError(t, err)
assert.Equal(t, "pgweb/", opts.Prefix)
t.Run("url prefix", func(t *testing.T) {
opts, err := ParseOptions([]string{"--prefix", "pgweb"})
assert.NoError(t, err)
assert.Equal(t, "pgweb/", opts.Prefix)
opts, err = ParseOptions([]string{"--prefix", "pgweb/"})
assert.NoError(t, err)
assert.Equal(t, "pgweb/", opts.Prefix)
opts, err = ParseOptions([]string{"--prefix", "pgweb/"})
assert.NoError(t, err)
assert.Equal(t, "pgweb/", opts.Prefix)
})
// Test connect backend options
opts, err = ParseOptions([]string{"--connect-backend", "test"})
assert.EqualError(t, err, "--sessions flag must be set")
t.Run("connect backend", func(t *testing.T) {
_, err := ParseOptions([]string{"--connect-backend", "test"})
assert.EqualError(t, err, "--sessions flag must be set")
opts, err = ParseOptions([]string{"--connect-backend", "test", "--sessions"})
assert.EqualError(t, err, "--connect-token flag must be set")
_, err = ParseOptions([]string{"--connect-backend", "test", "--sessions"})
assert.EqualError(t, err, "--connect-token flag must be set")
opts, err = ParseOptions([]string{"--connect-backend", "test", "--sessions", "--connect-token", "token"})
assert.NoError(t, err)
_, err = ParseOptions([]string{"--connect-backend", "test", "--sessions", "--connect-token", "token"})
assert.NoError(t, err)
})
t.Run("passfile", func(t *testing.T) {
defer os.Unsetenv("PGPASSFILE")
// File does not exist
os.Setenv("PGPASSFILE", "/tmp/foo")
opts, err := ParseOptions([]string{})
assert.NoError(t, err)
assert.Equal(t, "", opts.Passfile)
// File exists and valid
os.Setenv("PGPASSFILE", "../../data/passfile")
opts, err = ParseOptions([]string{})
assert.NoError(t, err)
assert.Equal(t, "../../data/passfile", opts.Passfile)
// Set via flag
os.Unsetenv("PGPASSFILE")
opts, err = ParseOptions([]string{"--passfile", "../../data/passfile"})
assert.NoError(t, err)
assert.Equal(t, "../../data/passfile", opts.Passfile)
})
}