From 49dbd651b1d669e30256a98dd195e05529806607 Mon Sep 17 00:00:00 2001 From: Max Twardowski Date: Fri, 6 Oct 2023 14:32:41 +0200 Subject: [PATCH 1/4] renew token in background --- cmd/vault-pusher/main.go | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/cmd/vault-pusher/main.go b/cmd/vault-pusher/main.go index d764601..a3319a0 100644 --- a/cmd/vault-pusher/main.go +++ b/cmd/vault-pusher/main.go @@ -75,6 +75,10 @@ func run(ctx context.Context) error { return errors.New("auth method did not return valid credentials") } + if err := manageTokenLifecycle(client, authInfo); err != nil { + return fmt.Errorf("unable to start managing token lifecycle: %w", err) + } + mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { @@ -159,3 +163,47 @@ func run(ctx context.Context) error { log.Println("Shutting down...") return nil } + +// Reference: https://github.com/hashicorp/vault-examples/blob/main/examples/token-renewal/go/example.go + +// Starts token lifecycle management. Returns only fatal errors as errors, +// otherwise returns nil so we can attempt login again. +func manageTokenLifecycle(client *vault.Client, token *vault.Secret) error { + renew := token.Auth.Renewable // You may notice a different top-level field called Renewable. That one is used for dynamic secrets renewal, not token renewal. + if !renew { + log.Printf("Token is not configured to be renewable. Re-attempting login.") + return nil + } + + watcher, err := client.NewLifetimeWatcher(&vault.LifetimeWatcherInput{ + Secret: token, + Increment: 60 * 60 * 24, // Learn more about this optional value in https://www.vaultproject.io/docs/concepts/lease#lease-durations-and-renewal + }) + if err != nil { + return fmt.Errorf("unable to initialize new lifetime watcher for renewing auth token: %w", err) + } + + go watcher.Start() + defer watcher.Stop() + + for { + select { + // `DoneCh` will return if renewal fails, or if the remaining lease + // duration is under a built-in threshold and either renewing is not + // extending it or renewing is disabled. In any case, the caller + // needs to attempt to log in again. + case err := <-watcher.DoneCh(): + if err != nil { + log.Printf("Failed to renew token: %v. Re-attempting login.", err) + return nil + } + // This occurs once the token has reached max TTL. + log.Printf("Token can no longer be renewed. Re-attempting login.") + return nil + + // Successfully completed renewal + case renewal := <-watcher.RenewCh(): + log.Printf("Successfully renewed: %#v", renewal) + } + } +} From 98dea51bb1251237ef635beb474aff61935c1757 Mon Sep 17 00:00:00 2001 From: Max Twardowski Date: Mon, 9 Oct 2023 10:14:01 +0200 Subject: [PATCH 2/4] proper fix --- cmd/vault-pusher/main.go | 117 +++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 48 deletions(-) diff --git a/cmd/vault-pusher/main.go b/cmd/vault-pusher/main.go index a3319a0..b6adca0 100644 --- a/cmd/vault-pusher/main.go +++ b/cmd/vault-pusher/main.go @@ -38,12 +38,17 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - if err := run(ctx); err != nil { + tokenRenewalDoneCh := make(chan struct{}) + err := run(ctx, tokenRenewalDoneCh) + log.Println("Stopping token renewal goroutine...") + <-tokenRenewalDoneCh + if err != nil { panic(err) } + log.Println("Exiting") } -func run(ctx context.Context) error { +func run(ctx context.Context, tokenRenewalDoneCh chan struct{}) error { // connect to vault config := vault.DefaultConfig() transport := config.HttpClient.Transport.(*http.Transport) @@ -67,16 +72,65 @@ func run(ctx context.Context) error { } // perform vault auth - authInfo, err := client.Auth().Login(ctx, appRoleAuth) + authInfo, err := login(ctx, client, appRoleAuth) if err != nil { - return fmt.Errorf("failed to log in using the approle method: %w", err) - } - if authInfo == nil { - return errors.New("auth method did not return valid credentials") + return err } - if err := manageTokenLifecycle(client, authInfo); err != nil { - return fmt.Errorf("unable to start managing token lifecycle: %w", err) + // And start token renewal lifecycle + // Reference: https://github.com/hashicorp/vault-examples/blob/main/examples/token-renewal/go/example.go + if authInfo.Auth.Renewable { + go func() { + defer func() { tokenRenewalDoneCh <- struct{}{} }() + log.Println("Starting auto renewal goroutine") + for { + select { + case <-ctx.Done(): + return + default: + } + + if authInfo == nil { + log.Println("Reattempting login") + authInfo, err = login(ctx, client, appRoleAuth) + if err != nil { + log.Printf("Failed to reattempt login: %v", err) + continue + } + } + + watcher, err := client.NewLifetimeWatcher(&vault.LifetimeWatcherInput{ + Secret: authInfo, + Increment: 60 * 60 * 24, + }) + if err != nil { + log.Printf("unable to initialize new lifetime watcher for renewing auth token: %v", err) + continue + } + + go watcher.Start() + defer watcher.Stop() + for { + select { + case <-ctx.Done(): + return + case err := <-watcher.DoneCh(): + if err != nil { + log.Printf("Failed to renew token: %v", err) + } else { + log.Printf("Token can no longer be renewed") + } + authInfo = nil + watcher.Stop() + break + case renewal := <-watcher.RenewCh(): + log.Printf("Successfully renewed: %#v", renewal) + } + } + } + }() + } else { + tokenRenewalDoneCh <- struct{}{} } mux := http.NewServeMux() @@ -164,46 +218,13 @@ func run(ctx context.Context) error { return nil } -// Reference: https://github.com/hashicorp/vault-examples/blob/main/examples/token-renewal/go/example.go - -// Starts token lifecycle management. Returns only fatal errors as errors, -// otherwise returns nil so we can attempt login again. -func manageTokenLifecycle(client *vault.Client, token *vault.Secret) error { - renew := token.Auth.Renewable // You may notice a different top-level field called Renewable. That one is used for dynamic secrets renewal, not token renewal. - if !renew { - log.Printf("Token is not configured to be renewable. Re-attempting login.") - return nil - } - - watcher, err := client.NewLifetimeWatcher(&vault.LifetimeWatcherInput{ - Secret: token, - Increment: 60 * 60 * 24, // Learn more about this optional value in https://www.vaultproject.io/docs/concepts/lease#lease-durations-and-renewal - }) +func login(ctx context.Context, client *vault.Client, appRoleAuth *auth.AppRoleAuth) (*vault.Secret, error) { + authInfo, err := client.Auth().Login(ctx, appRoleAuth) if err != nil { - return fmt.Errorf("unable to initialize new lifetime watcher for renewing auth token: %w", err) + return nil, fmt.Errorf("failed to log in using the approle method: %w", err) } - - go watcher.Start() - defer watcher.Stop() - - for { - select { - // `DoneCh` will return if renewal fails, or if the remaining lease - // duration is under a built-in threshold and either renewing is not - // extending it or renewing is disabled. In any case, the caller - // needs to attempt to log in again. - case err := <-watcher.DoneCh(): - if err != nil { - log.Printf("Failed to renew token: %v. Re-attempting login.", err) - return nil - } - // This occurs once the token has reached max TTL. - log.Printf("Token can no longer be renewed. Re-attempting login.") - return nil - - // Successfully completed renewal - case renewal := <-watcher.RenewCh(): - log.Printf("Successfully renewed: %#v", renewal) - } + if authInfo == nil { + return nil, errors.New("auth method did not return valid credentials") } + return authInfo, nil } From 995ca7f2ea7057cf72c884edcae65a2be562f35c Mon Sep 17 00:00:00 2001 From: Max Twardowski Date: Wed, 15 Nov 2023 10:06:57 +0100 Subject: [PATCH 3/4] fix renewal loop breaking --- cmd/vault-pusher/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/vault-pusher/main.go b/cmd/vault-pusher/main.go index b6adca0..3bb3d4f 100644 --- a/cmd/vault-pusher/main.go +++ b/cmd/vault-pusher/main.go @@ -110,6 +110,7 @@ func run(ctx context.Context, tokenRenewalDoneCh chan struct{}) error { go watcher.Start() defer watcher.Stop() + renewalLoop: for { select { case <-ctx.Done(): @@ -122,7 +123,7 @@ func run(ctx context.Context, tokenRenewalDoneCh chan struct{}) error { } authInfo = nil watcher.Stop() - break + break renewalLoop case renewal := <-watcher.RenewCh(): log.Printf("Successfully renewed: %#v", renewal) } From 75bac620ec67b02f03cc0c28b56f20683e772fc9 Mon Sep 17 00:00:00 2001 From: Max Twardowski Date: Wed, 15 Nov 2023 11:16:23 +0100 Subject: [PATCH 4/4] makefile docker repo adjustment --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e474246..d7de6c1 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: docker-build docker-push -export DOCKER_REPO=pyrsh/tbot-vault-bridge +export DOCKER_REPO=registry.pyr.sh/tbot-vault-bridge/tbot-vault-bridge export GIT_COMMIT=$(shell git rev-parse --short HEAD) docker-build: