diff --git a/docs/_docs/user-guide/tavern.md b/docs/_docs/user-guide/tavern.md index e0a75a58e..e30b1921d 100644 --- a/docs/_docs/user-guide/tavern.md +++ b/docs/_docs/user-guide/tavern.md @@ -22,6 +22,6 @@ The `TAVERN_API_TOKEN` is a separate token used for authenticating CLI tools and #### When to use TAVERN_API_TOKEN -You typically need to use the `TAVERN_API_TOKEN` in scenarios where you are running tools on a remote machine (like a Kali VM via SSH) and cannot perform the standard local browser-based authentication flow due to networking restrictions (e.g., you cannot define the auth redirection port for SSH port forwarding). +You typically need to use the `TAVERN_API_TOKEN` in scenarios where you are running tools on a remote machine (like a Kali VM via SSH) and cannot perform the standard Remote Device Authentication (RDA) flow. By default, CLI tools will use the RDA flow to authenticate. -In a standard local setup, CLI tools might pop open a browser window to authenticate. However, when you are SSH'd into a remote box, this isn't possible. The `TAVERN_API_TOKEN` provides a way to bypass this limitation. +To enable the legacy local browser-based OAuth flow, you can set the `TAVERN_USE_BROWSER_OAUTH=1` environment variable. In a standard local setup with this variable set, CLI tools might pop open a browser window to authenticate. However, when you are SSH'd into a remote box, this isn't possible, which is why the default is the RDA flow. The `TAVERN_API_TOKEN` provides a way to bypass these limitations altogether. diff --git a/tavern/cli/auth/auth.go b/tavern/cli/auth/auth.go index e57508954..2b377e626 100644 --- a/tavern/cli/auth/auth.go +++ b/tavern/cli/auth/auth.go @@ -78,72 +78,78 @@ func Authenticate(ctx context.Context, browser Browser, tavernURL string, opts . } } - // Create Listener - conn, err := net.Listen("tcp", ":0") - if err != nil { - return Token(""), fmt.Errorf("failed to start access_token http redirect handler: %w", err) - } - defer conn.Close() - - // Build Access Token Endpoint - accessTokenRedirURL, err := url.Parse(tavernURL) - if err != nil { - return Token(""), fmt.Errorf("%w: %v", ErrInvalidURL, err) - } - _, redirPort, err := net.SplitHostPort(conn.Addr().String()) - if err != nil { - return Token(""), fmt.Errorf("%w: %q: %v", ErrInvalidURL, conn.Addr().String(), err) - } - accessTokenRedirURL.RawQuery = url.Values{ - auth.ParamTokenRedirPort: []string{redirPort}, - }.Encode() - accessTokenRedirURL.Path = "/access_token/redirect" - - // Log TLS Warning - if accessTokenRedirURL.Scheme == "http" { - slog.WarnContext(ctx, "using insecure access token url (http), this may leak sensitive information") - } - // Create Channels tokenCh := make(chan Token, 1) errCh := make(chan error, 1) - // Configure Server - srv := &http.Server{ - Addr: ":0", - Handler: newTokenHandler(tokenCh, errCh), - } + // Use Browser OAuth Flow if Explicitly Enabled + if os.Getenv("TAVERN_USE_BROWSER_OAUTH") == "1" { + // Create Listener + conn, err := net.Listen("tcp", ":0") + if err != nil { + return Token(""), fmt.Errorf("failed to start access_token http redirect handler: %w", err) + } + defer conn.Close() - // Start HTTP Server - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - if err := srv.Serve(conn); err != nil { - errCh <- fmt.Errorf("http token redirect server failed: %w", err) + // Build Access Token Endpoint + accessTokenRedirURL, err := url.Parse(tavernURL) + if err != nil { + return Token(""), fmt.Errorf("%w: %v", ErrInvalidURL, err) + } + _, redirPort, err := net.SplitHostPort(conn.Addr().String()) + if err != nil { + return Token(""), fmt.Errorf("%w: %q: %v", ErrInvalidURL, conn.Addr().String(), err) + } + accessTokenRedirURL.RawQuery = url.Values{ + auth.ParamTokenRedirPort: []string{redirPort}, + }.Encode() + accessTokenRedirURL.Path = "/access_token/redirect" + + // Log TLS Warning + if accessTokenRedirURL.Scheme == "http" { + slog.WarnContext(ctx, "using insecure access token url (http), this may leak sensitive information") } - }() - - // Handle Cleanup - defer func() { - // Browsers keep open the connection, which means we must timeout the shutdown to - // prevent the http package from waiting indefinitely. - shutdownCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) - defer cancel() - srv.Shutdown(shutdownCtx) - wg.Wait() - }() - - // Try to open the browser - if browser != nil { - err = browser.OpenURL(accessTokenRedirURL.String()) - } else { - err = fmt.Errorf("no browser provided") - } - // Fallback to Remote Device Authentication if browser fails - if err != nil { - slog.WarnContext(ctx, "failed to open browser, falling back to remote device authentication", "error", err) + // Configure Server + srv := &http.Server{ + Addr: ":0", + Handler: newTokenHandler(tokenCh, errCh), + } + + // Start HTTP Server + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + if err := srv.Serve(conn); err != nil { + errCh <- fmt.Errorf("http token redirect server failed: %w", err) + } + }() + + // Handle Cleanup + defer func() { + // Browsers keep open the connection, which means we must timeout the shutdown to + // prevent the http package from waiting indefinitely. + shutdownCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) + defer cancel() + srv.Shutdown(shutdownCtx) + wg.Wait() + }() + + // Try to open the browser + if browser != nil { + err = browser.OpenURL(accessTokenRedirURL.String()) + } else { + err = fmt.Errorf("no browser provided") + } + + // Fallback to Remote Device Authentication if browser fails + if err != nil { + slog.WarnContext(ctx, "failed to open browser, falling back to remote device authentication", "error", err) + go AuthenticateRemoteDevice(ctx, tavernURL, tokenCh, errCh) + } + } else { + // Default to Remote Device Authentication go AuthenticateRemoteDevice(ctx, tavernURL, tokenCh, errCh) } diff --git a/tavern/cli/auth/auth_test.go b/tavern/cli/auth/auth_test.go index c6a8bd888..28769229b 100644 --- a/tavern/cli/auth/auth_test.go +++ b/tavern/cli/auth/auth_test.go @@ -88,6 +88,8 @@ func TestAuthenticate(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + t.Setenv("TAVERN_USE_BROWSER_OAUTH", "1") + // Setup Timeout deadline, ok := t.Deadline() if !ok {