diff --git a/main.go b/main.go index 95dbec7ad..5c4c3c13a 100644 --- a/main.go +++ b/main.go @@ -749,19 +749,6 @@ func main() { os.Exit(1) } - // Finish populating credentials. - for i := range *flCredentials { - cred := &(*flCredentials)[i] - if cred.PasswordFile != "" { - passwordFileBytes, err := os.ReadFile(cred.PasswordFile) - if err != nil { - log.Error(err, "can't read password file", "file", cred.PasswordFile) - os.Exit(1) - } - cred.Password = string(passwordFileBytes) - } - } - // If the --repo or any submodule uses SSH, we need to know which keys. if err := git.SetupGitSSH(*flSSHKnownHosts, *flSSHKeyFiles, *flSSHKnownHostsFile); err != nil { log.Error(err, "can't set up git SSH", "keyFiles", *flSSHKeyFiles, "useKnownHosts", *flSSHKnownHosts, "knownHostsFile", *flSSHKnownHostsFile) @@ -883,7 +870,20 @@ func main() { refreshCreds := func(ctx context.Context) error { // These should all be mutually-exclusive configs. for _, cred := range *flCredentials { - if err := git.StoreCredentials(ctx, cred.URL, cred.Username, cred.Password); err != nil { + password := cred.Password + + // If this credential has a password file, re-read it from disk + // to pick up token rotation + if cred.PasswordFile != "" { + passwordFileBytes, err := os.ReadFile(cred.PasswordFile) + if err != nil { + return fmt.Errorf("can't read password file %q: %w", cred.PasswordFile, err) + } + password = string(passwordFileBytes) + git.log.V(3).Info("read password from file", "file", cred.PasswordFile) + } + + if err := git.StoreCredentials(ctx, cred.URL, cred.Username, password); err != nil { return err } } @@ -2584,7 +2584,10 @@ OPTIONS --password-file , $GITSYNC_PASSWORD_FILE The file from which the password or personal access token (see github docs) to use for git authentication (see --username) will be - read. See also $GITSYNC_PASSWORD. + read. The file is re-read before each sync attempt, allowing + git-sync to pick up token rotations automatically (e.g. when using + dynamic credentials from an external secrets system). + See also $GITSYNC_PASSWORD. --period , $GITSYNC_PERIOD How long to wait between sync attempts. This must be at least diff --git a/test_e2e.sh b/test_e2e.sh index ab9629bd3..8966c79c5 100755 --- a/test_e2e.sh +++ b/test_e2e.sh @@ -2022,6 +2022,64 @@ function e2e::auth_http_password_file() { assert_file_eq "$ROOT/link/file" "${FUNCNAME[0]}" } +############################################## +# Test password-file reload on each sync +############################################## +function e2e::auth_password_file_reload() { + # Run a git-over-HTTP server. + local ctr + ctr=$(docker_run \ + -v "$REPO":/git/repo:ro \ + e2e/test/httpd) + local ip + ip=$(docker_ip "$ctr") + + # Create a password file with the correct password. + echo -n "testpass" > "$WORK/password-file" + + # First sync + echo "${FUNCNAME[0]} 1" > "$REPO/file" + git -C "$REPO" commit -qam "${FUNCNAME[0]} 1" + + GIT_SYNC \ + --period=1s \ + --repo="http://$ip/repo" \ + --root="$ROOT" \ + --link="link" \ + --username="testuser" \ + --password-file="$WORK/password-file" \ + --max-failures=100 \ + & + wait_for_sync "${MAXWAIT}" + assert_link_exists "$ROOT/link" + assert_file_exists "$ROOT/link/file" + assert_file_eq "$ROOT/link/file" "${FUNCNAME[0]} 1" + + # Change password file to wrong password + echo -n "wrong" > "$WORK/password-file.tmp" + mv "$WORK/password-file.tmp" "$WORK/password-file" + + # Commit a change to the repo + echo "${FUNCNAME[0]} 2" > "$REPO/file" + git -C "$REPO" commit -qam "${FUNCNAME[0]} 2" + + # Wait a bit and verify sync did NOT happen (still has old content) + sleep 3 + assert_link_exists "$ROOT/link" + assert_file_exists "$ROOT/link/file" + assert_file_eq "$ROOT/link/file" "${FUNCNAME[0]} 1" + + # Change password file back to correct password + echo -n "testpass" > "$WORK/password-file.tmp" + mv "$WORK/password-file.tmp" "$WORK/password-file" + + # Verify sync now picks up the new change + wait_for_sync "${MAXWAIT}" + assert_link_exists "$ROOT/link" + assert_file_exists "$ROOT/link/file" + assert_file_eq "$ROOT/link/file" "${FUNCNAME[0]} 2" +} + ############################################## # Test SSH (user@host:path syntax) ##############################################