From f4ceeb14be626f25f06d84604333e331fba822fa Mon Sep 17 00:00:00 2001 From: veceraj Date: Tue, 1 Jul 2025 18:06:59 +0200 Subject: [PATCH 1/6] add kinit package --- pbm/kinit/renewal.go | 96 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 pbm/kinit/renewal.go diff --git a/pbm/kinit/renewal.go b/pbm/kinit/renewal.go new file mode 100644 index 000000000..c1f0c7f57 --- /dev/null +++ b/pbm/kinit/renewal.go @@ -0,0 +1,96 @@ +package kinit + +import ( + "context" + "net/url" + "os/exec" + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/percona/percona-backup-mongodb/pbm/errors" + "github.com/percona/percona-backup-mongodb/pbm/log" +) + +type RenewalManager struct { + Keytab string + Principal string + Interval time.Duration +} + +func New(mongoURI, keytab string, interval time.Duration) (*RenewalManager, error) { + if _, err := exec.LookPath("kinit"); err != nil { + return nil, errors.New("kinit not found in PATH") + } + + principal, err := principalFromMongoUri(mongoURI) + if err != nil { + return nil, errors.Wrap(err, "parsing principal") + } + + return &RenewalManager{ + Keytab: keytab, + Principal: principal, + Interval: interval, + }, nil +} + +func (rm RenewalManager) Start(ctx context.Context) { + l := log.FromContext(ctx).NewEvent("", "", "", primitive.Timestamp{}) + + if err := rm.renew(ctx); err != nil { + l.Error("Kerberos ticket renewal failed: %v", err) + // TODO: determine if it should exit + } else { + l.Info("Kerberos ticket obtained successfully") + } + + tmr := time.NewTimer(rm.Interval) + go func() { + defer tmr.Stop() + for { + select { + case <-ctx.Done(): + return + case <-tmr.C: + if err := rm.renew(ctx); err != nil { + l.Error("Kerberos ticket renewal failed: %v", err) + } else { + l.Info("Kerberos ticket obtained successfully") + } + } + } + }() +} + +func (rm RenewalManager) renew(ctx context.Context) error { + args := []string{"-k"} + if rm.Keytab != "" { + args = append(args, "-t", rm.Keytab) + } + args = append(args, rm.Principal) + + cmd := exec.CommandContext(ctx, "kinit", args...) + if _, err := cmd.CombinedOutput(); err != nil { + return errors.Wrap(err, "kinit failed") + } + return nil +} + +func principalFromMongoUri(uri string) (string, error) { + parsed, err := url.Parse(uri) + if err != nil { + return "", errors.Wrap(err, "cannot parse URI") + } + + if parsed.User == nil { + return "", errors.New("no user info in URI") + } + + principal := parsed.User.Username() + if principal == "" { + return "", errors.New("empty principal in URI") + } + + return principal, nil +} From 11278b1687d773ef570cf19fe758df1db84528e8 Mon Sep 17 00:00:00 2001 From: veceraj Date: Wed, 2 Jul 2025 07:14:24 +0200 Subject: [PATCH 2/6] update renewal --- pbm/kinit/renewal.go | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/pbm/kinit/renewal.go b/pbm/kinit/renewal.go index c1f0c7f57..7d0828214 100644 --- a/pbm/kinit/renewal.go +++ b/pbm/kinit/renewal.go @@ -4,6 +4,7 @@ import ( "context" "net/url" "os/exec" + "strings" "time" "go.mongodb.org/mongo-driver/bson/primitive" @@ -12,48 +13,49 @@ import ( "github.com/percona/percona-backup-mongodb/pbm/log" ) -type RenewalManager struct { +// Renewal periodically refreshes a Kerberos TGT using the `kinit` command-line tool. +type Renewal struct { Keytab string Principal string Interval time.Duration } -func New(mongoURI, keytab string, interval time.Duration) (*RenewalManager, error) { +func New(mongoURI, keytab string, interval time.Duration) (*Renewal, error) { if _, err := exec.LookPath("kinit"); err != nil { return nil, errors.New("kinit not found in PATH") } - principal, err := principalFromMongoUri(mongoURI) + principal, err := principalFromMongoURI(mongoURI) if err != nil { return nil, errors.Wrap(err, "parsing principal") } - return &RenewalManager{ + return &Renewal{ Keytab: keytab, Principal: principal, Interval: interval, }, nil } -func (rm RenewalManager) Start(ctx context.Context) { +// Start performs an immediate renewal and then continues to renew the ticket every Interval. +func (r Renewal) Start(ctx context.Context) { l := log.FromContext(ctx).NewEvent("", "", "", primitive.Timestamp{}) - - if err := rm.renew(ctx); err != nil { + + if err := r.renew(ctx); err != nil { l.Error("Kerberos ticket renewal failed: %v", err) - // TODO: determine if it should exit } else { l.Info("Kerberos ticket obtained successfully") } - tmr := time.NewTimer(rm.Interval) + tkr := time.NewTicker(r.Interval) go func() { - defer tmr.Stop() + defer tkr.Stop() for { select { case <-ctx.Done(): return - case <-tmr.C: - if err := rm.renew(ctx); err != nil { + case <-tkr.C: + if err := r.renew(ctx); err != nil { l.Error("Kerberos ticket renewal failed: %v", err) } else { l.Info("Kerberos ticket obtained successfully") @@ -63,21 +65,22 @@ func (rm RenewalManager) Start(ctx context.Context) { }() } -func (rm RenewalManager) renew(ctx context.Context) error { +func (r Renewal) renew(ctx context.Context) error { args := []string{"-k"} - if rm.Keytab != "" { - args = append(args, "-t", rm.Keytab) + if r.Keytab != "" { + args = append(args, "-t", r.Keytab) } - args = append(args, rm.Principal) + args = append(args, r.Principal) cmd := exec.CommandContext(ctx, "kinit", args...) - if _, err := cmd.CombinedOutput(); err != nil { - return errors.Wrap(err, "kinit failed") + if out, err := cmd.CombinedOutput(); err != nil { + msg := strings.TrimSpace(string(out)) + return errors.New(msg) } return nil } -func principalFromMongoUri(uri string) (string, error) { +func principalFromMongoURI(uri string) (string, error) { parsed, err := url.Parse(uri) if err != nil { return "", errors.Wrap(err, "cannot parse URI") From e472dc0723dba65cb63926a44efef5c626fc0a0f Mon Sep 17 00:00:00 2001 From: veceraj Date: Wed, 2 Jul 2025 07:15:00 +0200 Subject: [PATCH 3/6] add kerberos renewal to cli --- cmd/pbm-agent/main.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/cmd/pbm-agent/main.go b/cmd/pbm-agent/main.go index f5f5dedae..73eb347e2 100644 --- a/cmd/pbm-agent/main.go +++ b/cmd/pbm-agent/main.go @@ -7,6 +7,7 @@ import ( "os/signal" "runtime" "strings" + "time" "github.com/fsnotify/fsnotify" mtLog "github.com/mongodb/mongo-tools/common/log" @@ -16,12 +17,14 @@ import ( "github.com/percona/percona-backup-mongodb/pbm/connect" "github.com/percona/percona-backup-mongodb/pbm/errors" + "github.com/percona/percona-backup-mongodb/pbm/kinit" "github.com/percona/percona-backup-mongodb/pbm/log" "github.com/percona/percona-backup-mongodb/pbm/util" "github.com/percona/percona-backup-mongodb/pbm/version" ) const mongoConnFlag = "mongodb-uri" +const kerberosRenewInterval = 4 * time.Hour func main() { rootCmd := rootCommand() @@ -123,6 +126,18 @@ func setRootFlags(rootCmd *cobra.Command) { _ = viper.BindPFlag("log.level", rootCmd.Flags().Lookup("log-level")) _ = viper.BindEnv("log.level", "LOG_LEVEL") viper.SetDefault("log.level", log.D) + + rootCmd.Flags().String("kerberos-keytab", "", + "Path to Kerberos keytab (if empty uses host keytab)") + _ = viper.BindPFlag("kerberos.keytab", + rootCmd.Flags().Lookup("kerberos-keytab")) + _ = viper.BindEnv("kerberos.keytab", "PBM_KERBEROS_KEYTAB") + + rootCmd.Flags().Duration("kerberos-renew-interval", 0, + "How often to renew Kerberos ticket (if 0 renewal is disabled)") + _ = viper.BindPFlag("kerberos.renew_interval", + rootCmd.Flags().Lookup("kerberos-renew-interval")) + _ = viper.BindEnv("kerberos.renew_interval", "PBM_KERBEROS_RENEW_INTERVAL") } func versionCommand() *cobra.Command { @@ -243,5 +258,24 @@ func runAgent( } go agent.HbStatus(ctx) + setupKerberosRenewal(ctx, mongoURI, logger) + return errors.Wrap(agent.Start(ctx), "listen the commands stream") } + +func setupKerberosRenewal(ctx context.Context, mongoURI string, l log.Logger) { + interval := viper.GetDuration("kerberos.renew_interval") + if interval <= 0 { // 0 or negative means disabled + return + } + + keytab := viper.GetString("kerberos.keytab") + + rm, err := kinit.New(mongoURI, keytab, interval) + if err != nil { + l.Printf("Kerberos renewal skipped: %s", err) + return + } + + rm.Start(ctx) +} From 9b8153f787fc17b9f854965724b41fc79e7bbecc Mon Sep 17 00:00:00 2001 From: veceraj Date: Wed, 2 Jul 2025 07:16:19 +0200 Subject: [PATCH 4/6] remove unused variable --- cmd/pbm-agent/main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/pbm-agent/main.go b/cmd/pbm-agent/main.go index 73eb347e2..99e66a040 100644 --- a/cmd/pbm-agent/main.go +++ b/cmd/pbm-agent/main.go @@ -7,7 +7,6 @@ import ( "os/signal" "runtime" "strings" - "time" "github.com/fsnotify/fsnotify" mtLog "github.com/mongodb/mongo-tools/common/log" @@ -24,7 +23,6 @@ import ( ) const mongoConnFlag = "mongodb-uri" -const kerberosRenewInterval = 4 * time.Hour func main() { rootCmd := rootCommand() From b30ae9d17f96951c5223b7532149b9cfc885f1e0 Mon Sep 17 00:00:00 2001 From: veceraj Date: Wed, 2 Jul 2025 15:13:54 +0200 Subject: [PATCH 5/6] fix convention --- cmd/pbm-agent/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/pbm-agent/main.go b/cmd/pbm-agent/main.go index 99e66a040..a452a4020 100644 --- a/cmd/pbm-agent/main.go +++ b/cmd/pbm-agent/main.go @@ -133,9 +133,9 @@ func setRootFlags(rootCmd *cobra.Command) { rootCmd.Flags().Duration("kerberos-renew-interval", 0, "How often to renew Kerberos ticket (if 0 renewal is disabled)") - _ = viper.BindPFlag("kerberos.renew_interval", + _ = viper.BindPFlag("kerberos.renew-interval", rootCmd.Flags().Lookup("kerberos-renew-interval")) - _ = viper.BindEnv("kerberos.renew_interval", "PBM_KERBEROS_RENEW_INTERVAL") + _ = viper.BindEnv("kerberos.renew-interval", "PBM_KERBEROS_RENEW_INTERVAL") } func versionCommand() *cobra.Command { From ec840e5b59eb5a9ff463a5c8728a41a616b7e146 Mon Sep 17 00:00:00 2001 From: veceraj Date: Wed, 2 Jul 2025 15:14:05 +0200 Subject: [PATCH 6/6] fix convention --- cmd/pbm-agent/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/pbm-agent/main.go b/cmd/pbm-agent/main.go index a452a4020..0c9dfcfbe 100644 --- a/cmd/pbm-agent/main.go +++ b/cmd/pbm-agent/main.go @@ -262,7 +262,7 @@ func runAgent( } func setupKerberosRenewal(ctx context.Context, mongoURI string, l log.Logger) { - interval := viper.GetDuration("kerberos.renew_interval") + interval := viper.GetDuration("kerberos.renew-interval") if interval <= 0 { // 0 or negative means disabled return }