Skip to content
Draft
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
4 changes: 2 additions & 2 deletions docs/_docs/user-guide/tavern.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
124 changes: 65 additions & 59 deletions tavern/cli/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
2 changes: 2 additions & 0 deletions tavern/cli/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading