Skip to content

Commit 8c00442

Browse files
yroblataskbot
authored andcommitted
reuse the same code for secrets in remote client and token exchange client (#2167)
* reuse the same code for secrets in remote client and token exchange client Closes: #2166 * use constant --------- Co-authored-by: taskbot <[email protected]>
1 parent d17dc08 commit 8c00442

File tree

2 files changed

+48
-40
lines changed

2 files changed

+48
-40
lines changed

cmd/thv/app/auth_flags.go

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,54 @@ import (
1414
"github.com/stacklok/toolhive/pkg/runner"
1515
)
1616

17+
const (
18+
// #nosec G101 - this is an environment variable name, not a credential
19+
envTokenExchangeClientSecret = "TOOLHIVE_TOKEN_EXCHANGE_CLIENT_SECRET"
20+
)
21+
1722
// readSecretFromFile reads a secret from a file, cleaning the path and trimming whitespace
18-
func readSecretFromFile(filePath, secretType string) (string, error) {
23+
func readSecretFromFile(filePath string) (string, error) {
1924
// Clean the file path to prevent path traversal
2025
cleanPath := filepath.Clean(filePath)
21-
logger.Debugf("Reading %s from file: %s", secretType, cleanPath)
26+
logger.Debugf("Reading secret from file: %s", cleanPath)
2227
// #nosec G304 - file path is cleaned above
2328
secretBytes, err := os.ReadFile(cleanPath)
2429
if err != nil {
25-
return "", fmt.Errorf("failed to read %s file %s: %w", secretType, cleanPath, err)
30+
return "", fmt.Errorf("failed to read secret file %s: %w", cleanPath, err)
2631
}
2732
secret := strings.TrimSpace(string(secretBytes))
2833
if secret == "" {
29-
return "", fmt.Errorf("%s file %s is empty", secretType, cleanPath)
34+
return "", fmt.Errorf("secret file %s is empty", cleanPath)
3035
}
3136
return secret, nil
3237
}
3338

39+
// resolveSecret resolves a secret from multiple sources following a standard priority order.
40+
// Priority: 1. Flag value, 2. File, 3. Environment variable
41+
// Returns empty string (not an error) if no secret is found - this is acceptable for public client/PKCE flows.
42+
func resolveSecret(flagValue, filePath, envVarName string) (string, error) {
43+
// 1. Check if provided directly via flag
44+
if flagValue != "" {
45+
logger.Debug("Using secret from command-line flag")
46+
return flagValue, nil
47+
}
48+
49+
// 2. Check if provided via file
50+
if filePath != "" {
51+
return readSecretFromFile(filePath)
52+
}
53+
54+
// 3. Check environment variable
55+
if secret := os.Getenv(envVarName); secret != "" {
56+
logger.Debugf("Using secret from %s environment variable", envVarName)
57+
return secret, nil
58+
}
59+
60+
// No secret found - this is acceptable for PKCE flows
61+
logger.Debug("No secret provided - using public client mode")
62+
return "", nil
63+
}
64+
3465
// RemoteAuthFlags holds the common remote authentication configuration
3566
type RemoteAuthFlags struct {
3667
EnableRemoteAuth bool
@@ -65,21 +96,14 @@ func (f *RemoteAuthFlags) BuildTokenExchangeConfig() (*tokenexchange.Config, err
6596
return nil, nil
6697
}
6798

68-
// Resolve token exchange client secret from flag or file only
69-
// Environment variable is handled by the middleware for Kubernetes deployments
70-
var clientSecret string
71-
if f.TokenExchangeClientSecret != "" {
72-
clientSecret = f.TokenExchangeClientSecret
73-
logger.Debug("Using token exchange client secret from command-line flag")
74-
} else if f.TokenExchangeClientSecretFile != "" {
75-
var err error
76-
clientSecret, err = readSecretFromFile(f.TokenExchangeClientSecretFile, "token exchange client secret")
77-
if err != nil {
78-
return nil, err
79-
}
80-
} else {
81-
// Empty client secret is acceptable for public client mode
82-
logger.Debug("No token exchange client secret provided - using public client mode")
99+
// Resolve token exchange client secret using the same mechanism as remote-auth-client-secret
100+
clientSecret, err := resolveSecret(
101+
f.TokenExchangeClientSecret,
102+
f.TokenExchangeClientSecretFile,
103+
envTokenExchangeClientSecret,
104+
)
105+
if err != nil {
106+
return nil, err
83107
}
84108

85109
// Determine header strategy based on whether custom header name is provided

cmd/thv/app/proxy.go

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"net/http"
77
"net/url"
8-
"os"
98
"os/signal"
109
"syscall"
1110
"time"
@@ -353,26 +352,11 @@ func handleOutgoingAuthentication(ctx context.Context) (*discovery.OAuthFlowResu
353352
// resolveClientSecret resolves the OAuth client secret from multiple sources
354353
// Priority: 1. Flag value, 2. File, 3. Environment variable
355354
func resolveClientSecret() (string, error) {
356-
// 1. Check if provided directly via flag
357-
if remoteAuthFlags.RemoteAuthClientSecret != "" {
358-
logger.Debug("Using client secret from command-line flag")
359-
return remoteAuthFlags.RemoteAuthClientSecret, nil
360-
}
361-
362-
// 2. Check if provided via file
363-
if remoteAuthFlags.RemoteAuthClientSecretFile != "" {
364-
return readSecretFromFile(remoteAuthFlags.RemoteAuthClientSecretFile, "client secret")
365-
}
366-
367-
// 3. Check environment variable
368-
if secret := os.Getenv(envOAuthClientSecret); secret != "" {
369-
logger.Debugf("Using client secret from %s environment variable", envOAuthClientSecret)
370-
return secret, nil
371-
}
372-
373-
// No client secret found - this is acceptable for PKCE flows
374-
logger.Debug("No client secret provided - using PKCE flow")
375-
return "", nil
355+
return resolveSecret(
356+
remoteAuthFlags.RemoteAuthClientSecret,
357+
remoteAuthFlags.RemoteAuthClientSecretFile,
358+
envOAuthClientSecret,
359+
)
376360
}
377361

378362
// createTokenInjectionMiddleware creates a middleware that injects the OAuth token into requests

0 commit comments

Comments
 (0)