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

enabling client certs #870

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Binary file added .DS_Store
Binary file not shown.
187 changes: 123 additions & 64 deletions cmd/keeper/cmd/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,22 +96,27 @@ func (s *DBLocalState) DeepCopy() *DBLocalState {
type config struct {
cmd.CommonConfig

uid string
dataDir string
debug bool
pgListenAddress string
pgAdvertiseAddress string
pgPort string
pgAdvertisePort string
pgBinPath string
pgReplAuthMethod string
pgReplUsername string
pgReplPassword string
pgReplPasswordFile string
pgSUAuthMethod string
pgSUUsername string
pgSUPassword string
pgSUPasswordFile string
uid string
dataDir string
debug bool
pgListenAddress string
pgAdvertiseAddress string
pgPort string
pgAdvertisePort string
pgBinPath string
pgReplConnType string
pgReplAuthMethod string
pgReplLocalAuthMethod string
pgReplSslMode string
pgReplUsername string
pgReplPassword string
pgReplPasswordFile string
pgSUConnType string
pgSUAuthMethod string
pgSULocalAuthMethod string
pgSUUsername string
pgSUPassword string
pgSUPasswordFile string

canBeMaster bool
canBeSynchronousReplica bool
Expand All @@ -131,11 +136,16 @@ func init() {
CmdKeeper.PersistentFlags().StringVar(&cfg.pgPort, "pg-port", "5432", "postgresql instance listening port")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgAdvertisePort, "pg-advertise-port", "", "postgresql instance port from outside. Use it to expose port different than local port with a PAT networking config")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgBinPath, "pg-bin-path", "", "absolute path to postgresql binaries. If empty they will be searched in the current PATH")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgReplConnType, "pg-repl-connection-type", "host", "postgres replication user connection type. Default is host.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgReplAuthMethod, "pg-repl-auth-method", "md5", "postgres replication user auth method. Default is md5.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgReplLocalAuthMethod, "pg-repl-local-auth-method", "", "postgres replication user auth method. Default is same as pg-repl-auth-method.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgReplSslMode, "pg-repl-ssl-mode", "prefer", "postgres replication user ssl-mode. Default is prefer.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgReplUsername, "pg-repl-username", "", "postgres replication user name. Required. It'll be created on db initialization. Must be the same for all keepers.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgReplPassword, "pg-repl-password", "", "postgres replication user password. Only one of --pg-repl-password or --pg-repl-passwordfile must be provided. Must be the same for all keepers.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgReplPasswordFile, "pg-repl-passwordfile", "", "postgres replication user password file. Only one of --pg-repl-password or --pg-repl-passwordfile must be provided. Must be the same for all keepers.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgSUConnType, "pg-su-connection-type", "host", "postgres superuser connection type. Default is host.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgSUAuthMethod, "pg-su-auth-method", "md5", "postgres superuser auth method. Default is md5.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgSULocalAuthMethod, "pg-su-local-auth-method", "", "postgres superuser auth method. Default is same as pg-su-auth-method.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgSUUsername, "pg-su-username", "", "postgres superuser user name. Used for keeper managed instance access and pg_rewind based synchronization. It'll be created on db initialization. Defaults to the name of the effective user running stolon-keeper. Must be the same for all keepers.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgSUPassword, "pg-su-password", "", "postgres superuser password. Only one of --pg-su-password or --pg-su-passwordfile must be provided. Must be the same for all keepers.")
CmdKeeper.PersistentFlags().StringVar(&cfg.pgSUPasswordFile, "pg-su-passwordfile", "", "postgres superuser password file. Only one of --pg-su-password or --pg-su-passwordfile must be provided. Must be the same for all keepers)")
Expand Down Expand Up @@ -296,10 +306,12 @@ func (p *PostgresKeeper) getSUConnParams(db, followedDB *cluster.DB) pg.ConnPara
"port": followedDB.Status.Port,
"application_name": common.StolonName(db.UID),
"dbname": "postgres",
// prefer ssl if available (already the default for postgres libpq but not for golang lib pq)
"sslmode": "prefer",
// This is currently only used for pgRewind, which requires a SU (repluser might not be enough).
// Pgrewind is the only feature using SU over remote connection and with that the only type using SU with sslmode.
// Therefore we have skipped extra config option for sslmode for SU, and reuse config for sslmode for repl user instead.
"sslmode": p.pgReplSslMode,
}
if p.pgSUAuthMethod != "trust" {
if p.pgSUAuthMethod == "md5" {
cp.Set("password", p.pgSUPassword)
}
return cp
Expand All @@ -314,7 +326,7 @@ func (p *PostgresKeeper) getReplConnParams(db, followedDB *cluster.DB) pg.ConnPa
// prefer ssl if available (already the default for postgres libpq but not for golang lib pq)
"sslmode": "prefer",
}
if p.pgReplAuthMethod != "trust" {
if p.pgReplAuthMethod == "md5" {
cp.Set("password", p.pgReplPassword)
}
return cp
Expand All @@ -328,7 +340,7 @@ func (p *PostgresKeeper) getLocalConnParams() pg.ConnParams {
"dbname": "postgres",
// no sslmode defined since it's not needed and supported over unix sockets
}
if p.pgSUAuthMethod != "trust" {
if p.pgSUAuthMethod == "md5" {
cp.Set("password", p.pgSUPassword)
}
return cp
Expand All @@ -342,7 +354,7 @@ func (p *PostgresKeeper) getLocalReplConnParams() pg.ConnParams {
"port": p.pgPort,
// no sslmode defined since it's not needed and supported over unix sockets
}
if p.pgReplAuthMethod != "trust" {
if p.pgReplAuthMethod == "md5" {
cp.Set("password", p.pgReplPassword)
}
return cp
Expand Down Expand Up @@ -470,18 +482,23 @@ type PostgresKeeper struct {

bootUUID string

dataDir string
pgListenAddress string
pgAdvertiseAddress string
pgPort string
pgAdvertisePort string
pgBinPath string
pgReplAuthMethod string
pgReplUsername string
pgReplPassword string
pgSUAuthMethod string
pgSUUsername string
pgSUPassword string
dataDir string
pgListenAddress string
pgAdvertiseAddress string
pgPort string
pgAdvertisePort string
pgBinPath string
pgReplConnType string
pgReplAuthMethod string
pgReplLocalAuthMethod string
pgReplSslMode string
pgReplUsername string
pgReplPassword string
pgSUConnType string
pgSUAuthMethod string
pgSULocalAuthMethod string
pgSUUsername string
pgSUPassword string

sleepInterval time.Duration
requestTimeout time.Duration
Expand Down Expand Up @@ -523,17 +540,22 @@ func NewPostgresKeeper(cfg *config, end chan error) (*PostgresKeeper, error) {

dataDir: dataDir,

pgListenAddress: cfg.pgListenAddress,
pgAdvertiseAddress: cfg.pgAdvertiseAddress,
pgPort: cfg.pgPort,
pgAdvertisePort: cfg.pgAdvertisePort,
pgBinPath: cfg.pgBinPath,
pgReplAuthMethod: cfg.pgReplAuthMethod,
pgReplUsername: cfg.pgReplUsername,
pgReplPassword: cfg.pgReplPassword,
pgSUAuthMethod: cfg.pgSUAuthMethod,
pgSUUsername: cfg.pgSUUsername,
pgSUPassword: cfg.pgSUPassword,
pgListenAddress: cfg.pgListenAddress,
pgAdvertiseAddress: cfg.pgAdvertiseAddress,
pgPort: cfg.pgPort,
pgAdvertisePort: cfg.pgAdvertisePort,
pgBinPath: cfg.pgBinPath,
pgReplConnType: cfg.pgReplConnType,
pgReplAuthMethod: cfg.pgReplAuthMethod,
pgReplLocalAuthMethod: cfg.pgReplLocalAuthMethod,
pgReplSslMode: cfg.pgReplSslMode,
pgReplUsername: cfg.pgReplUsername,
pgReplPassword: cfg.pgReplPassword,
pgSUConnType: cfg.pgSUConnType,
pgSUAuthMethod: cfg.pgSUAuthMethod,
pgSULocalAuthMethod: cfg.pgSULocalAuthMethod,
pgSUUsername: cfg.pgSUUsername,
pgSUPassword: cfg.pgSUPassword,

sleepInterval: cluster.DefaultSleepInterval,
requestTimeout: cluster.DefaultRequestTimeout,
Expand Down Expand Up @@ -582,7 +604,7 @@ func (p *PostgresKeeper) dbLocalStateCopy() *DBLocalState {
}

func (p *PostgresKeeper) usePgrewind(db *cluster.DB) bool {
return p.pgSUUsername != "" && p.pgSUPassword != "" && db.Spec.UsePgrewind
return p.pgSUUsername != "" && (p.pgSUPassword != "" || p.pgSUAuthMethod == "cert") && db.Spec.UsePgrewind
}

func (p *PostgresKeeper) updateKeeperInfo() error {
Expand Down Expand Up @@ -1827,6 +1849,16 @@ func IsMaster(db *cluster.DB) bool {
}
}

// localAuthMethod returns the authentication method that works for local connections
// cert does not work for local connections, in which case we should fall back to peer authentication
func localAuthMethod(authMethod string) string {
if authMethod == "cert" {
log.Info("using peer instead of cert for local connection authentication method")
return "peer"
}
return authMethod
}

// generateHBA generates the instance hba entries depending on the value of
// DefaultSUReplAccessMode.
// When onlyInternal is true only rules needed for replication will be setup
Expand All @@ -1837,19 +1869,19 @@ func (p *PostgresKeeper) generateHBA(cd *cluster.ClusterData, db *cluster.DB, on
// Matched local connections are for postgres database and suUsername user with md5 auth
// Matched local replication connections are for replUsername user with md5 auth
computedHBA := []string{
fmt.Sprintf("local postgres %s %s", p.pgSUUsername, p.pgSUAuthMethod),
fmt.Sprintf("local replication %s %s", p.pgReplUsername, p.pgReplAuthMethod),
fmt.Sprintf("local postgres %s %s", p.pgSUUsername, localAuthMethod(p.pgSULocalAuthMethod)),
fmt.Sprintf("local replication %s %s", p.pgReplUsername, localAuthMethod(p.pgReplLocalAuthMethod)),
}

switch *cd.Cluster.DefSpec().DefaultSUReplAccessMode {
case cluster.SUReplAccessAll:
// all the keepers will accept connections from every host
computedHBA = append(
computedHBA,
fmt.Sprintf("host all %s %s %s", p.pgSUUsername, "0.0.0.0/0", p.pgSUAuthMethod),
fmt.Sprintf("host all %s %s %s", p.pgSUUsername, "::0/0", p.pgSUAuthMethod),
fmt.Sprintf("host replication %s %s %s", p.pgReplUsername, "0.0.0.0/0", p.pgReplAuthMethod),
fmt.Sprintf("host replication %s %s %s", p.pgReplUsername, "::0/0", p.pgReplAuthMethod),
fmt.Sprintf("%s all %s %s %s", p.pgSUConnType, p.pgSUUsername, "0.0.0.0/0", p.pgSUAuthMethod),
fmt.Sprintf("%s all %s %s %s", p.pgSUConnType, p.pgSUUsername, "::0/0", p.pgSUAuthMethod),
fmt.Sprintf("%s replication %s %s %s", p.pgReplConnType, p.pgReplUsername, "0.0.0.0/0", p.pgReplAuthMethod),
fmt.Sprintf("%s replication %s %s %s", p.pgReplConnType, p.pgReplUsername, "::0/0", p.pgReplAuthMethod),
)
case cluster.SUReplAccessStrict:
// only the master keeper (primary instance or standby of a remote primary when in standby cluster mode) will accept connections only from the other standby keepers IPs
Expand All @@ -1864,8 +1896,8 @@ func (p *PostgresKeeper) generateHBA(cd *cluster.ClusterData, db *cluster.DB, on
for _, address := range addresses {
computedHBA = append(
computedHBA,
fmt.Sprintf("host all %s %s/32 %s", p.pgSUUsername, address, p.pgReplAuthMethod),
fmt.Sprintf("host replication %s %s/32 %s", p.pgReplUsername, address, p.pgReplAuthMethod),
fmt.Sprintf("%s all %s %s/32 %s", p.pgSUConnType, p.pgSUUsername, address, p.pgReplAuthMethod),
fmt.Sprintf("%s replication %s %s/32 %s", p.pgReplConnType, p.pgReplUsername, address, p.pgReplAuthMethod),
)
}
}
Expand Down Expand Up @@ -1927,6 +1959,15 @@ func keeper(c *cobra.Command, args []string) {
validAuthMethods := make(map[string]struct{})
validAuthMethods["trust"] = struct{}{}
validAuthMethods["md5"] = struct{}{}
validAuthMethods["cert"] = struct{}{}
validAuthMethods["ident"] = struct{}{}
validAuthMethods["peer"] = struct{}{}
validConnectionTypes := make(map[string]struct{})
validConnectionTypes["host"] = struct{}{}
validConnectionTypes["hostssl"] = struct{}{}
validConnectionTypes["hostnossl"] = struct{}{}
validConnectionTypes["hostgssenc"] = struct{}{}
validConnectionTypes["hostnogssenc"] = struct{}{}
switch cfg.LogLevel {
case "error":
slog.SetLevel(zap.ErrorLevel)
Expand Down Expand Up @@ -1988,39 +2029,57 @@ func keeper(c *cobra.Command, args []string) {
}
}

if cfg.pgSULocalAuthMethod == "" {
cfg.pgSULocalAuthMethod = cfg.pgSUAuthMethod
}
if cfg.pgReplLocalAuthMethod == "" {
cfg.pgReplLocalAuthMethod = cfg.pgReplAuthMethod
}
if _, ok := validConnectionTypes[cfg.pgReplConnType]; !ok {
log.Fatalf("--pg-repl-connection-type must be one of: host, hostssl, hostnossl, hostgssenc, hostnogssenc")
}
if _, ok := validConnectionTypes[cfg.pgSUConnType]; !ok {
log.Fatalf("--pg-su-connection-type must be one of: host, hostssl, hostnossl, hostgssenc, hostnogssenc")
}
if _, ok := validAuthMethods[cfg.pgReplAuthMethod]; !ok {
log.Fatalf("--pg-repl-auth-method must be one of: md5, trust")
log.Fatalf("--pg-repl-auth-method must be one of: ident, md5, password, trust or cert")
}
if cfg.pgReplUsername == "" {
log.Fatalf("--pg-repl-username is required")
}
if _, ok := validAuthMethods[cfg.pgReplLocalAuthMethod]; !ok {
log.Fatalf("--pg-repl-local-auth-method must be one of: ident, md5, password, trust or cert")
}
if cfg.pgReplAuthMethod == "trust" {
log.Warn("not utilizing a password for replication between hosts is extremely dangerous")
if cfg.pgReplPassword != "" || cfg.pgReplPasswordFile != "" {
log.Fatalf("can not utilize --pg-repl-auth-method trust together with --pg-repl-password or --pg-repl-passwordfile")
}
}
if cfg.pgSUAuthMethod == "trust" {
if cfg.pgSUAuthMethod == "trust" || cfg.pgSULocalAuthMethod == "trust" {
log.Warn("not utilizing a password for superuser is extremely dangerous")
if cfg.pgSUPassword != "" || cfg.pgSUPasswordFile != "" {
log.Fatalf("can not utilize --pg-su-auth-method trust together with --pg-su-password or --pg-su-passwordfile")
if (cfg.pgSUAuthMethod == "trust" || cfg.pgSULocalAuthMethod == "trust") && (cfg.pgSUPassword != "" || cfg.pgSUPasswordFile != "") {
log.Fatalf("can not utilize --pg-su-auth-method trust and --pg-su-auth-method trust together with --pg-su-password or --pg-su-passwordfile")
}
}
if cfg.pgReplAuthMethod != "trust" && cfg.pgReplPassword == "" && cfg.pgReplPasswordFile == "" {
if cfg.pgReplAuthMethod == "md5" && cfg.pgReplPassword == "" && cfg.pgReplPasswordFile == "" {
log.Fatalf("one of --pg-repl-password or --pg-repl-passwordfile is required")
}
if cfg.pgReplAuthMethod != "trust" && cfg.pgReplPassword != "" && cfg.pgReplPasswordFile != "" {
if cfg.pgReplAuthMethod == "md5" && cfg.pgReplPassword != "" && cfg.pgReplPasswordFile != "" {
log.Fatalf("only one of --pg-repl-password or --pg-repl-passwordfile must be provided")
}
if _, ok := validAuthMethods[cfg.pgSUAuthMethod]; !ok {
log.Fatalf("--pg-su-auth-method must be one of: md5, password, trust")
log.Fatalf("--pg-su-auth-method must be one of: ident, md5, password, trust or cert")
}
if cfg.pgSUAuthMethod != "trust" && cfg.pgSUPassword == "" && cfg.pgSUPasswordFile == "" {
if cfg.pgSUAuthMethod == "md5" && cfg.pgSUPassword == "" && cfg.pgSUPasswordFile == "" {
log.Fatalf("one of --pg-su-password or --pg-su-passwordfile is required")
}
if cfg.pgSUAuthMethod != "trust" && cfg.pgSUPassword != "" && cfg.pgSUPasswordFile != "" {
if cfg.pgSUAuthMethod == "md5" && cfg.pgSUPassword != "" && cfg.pgSUPasswordFile != "" {
log.Fatalf("only one of --pg-su-password or --pg-su-passwordfile must be provided")
}
if _, ok := validAuthMethods[cfg.pgSULocalAuthMethod]; !ok {
log.Fatalf("--pg-su-local-auth-method must be one of: ident, md5, password, trust or cert")
}

if cfg.pgReplPasswordFile != "" {
cfg.pgReplPassword, err = readPasswordFromFile(cfg.pgReplPasswordFile)
Expand Down Expand Up @@ -2059,7 +2118,7 @@ func keeper(c *cobra.Command, args []string) {
if cfg.pgReplAuthMethod != cfg.pgSUAuthMethod {
log.Fatalf("do not support different auth methods when utilizing superuser for replication.")
}
if cfg.pgSUPassword != cfg.pgReplPassword && cfg.pgSUAuthMethod != "trust" && cfg.pgReplAuthMethod != "trust" {
if cfg.pgSUPassword != cfg.pgReplPassword && cfg.pgSUAuthMethod == "md5" && cfg.pgReplAuthMethod == "md5" {
log.Fatalf("provided superuser name and replication user name are the same but provided passwords are different")
}
}
Expand Down
1 change: 1 addition & 0 deletions internal/timer/timer_fallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !linux
// +build !linux

package timer
Expand Down
1 change: 1 addition & 0 deletions internal/timer/timer_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build linux
// +build linux

package timer
Expand Down