Skip to content

Commit e8efa1b

Browse files
authored
Merge commit from fork
* Do not persist secrets in the runconfig Previously we expanded secrets into environment variables before writing the run config into the state store. This led to the unfortunate situation where the run config contained both references to secrets as well as the raw secrets themselves. Since the state store is not encrypted, it would be possible for someone with access to the user's home folder to read the secrets without needing to know the decryption password. This code moves the point at which the secret expansion happens to after we persist the run config, and right before we start the MCP server. * Fix issue with detached processes * Configure transport after writing config * linting * use fmt.Println instead of empty log line
1 parent f4fc958 commit e8efa1b

File tree

8 files changed

+93
-100
lines changed

8 files changed

+93
-100
lines changed

cmd/thv/app/common.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,6 @@ func IsOIDCEnabled(cmd *cobra.Command) bool {
3636
return jwksURL != "" || issuer != ""
3737
}
3838

39-
// GetSecretsProviderType returns the secrets provider type from the command flags
40-
func GetSecretsProviderType(_ *cobra.Command) (secrets.ProviderType, error) {
41-
provider := config.GetConfig().Secrets.ProviderType
42-
switch provider {
43-
case string(secrets.EncryptedType):
44-
return secrets.EncryptedType, nil
45-
case string(secrets.OnePasswordType):
46-
return secrets.OnePasswordType, nil
47-
default:
48-
// TODO: auto-generate the set of valid values.
49-
return "", fmt.Errorf("invalid secrets provider type: %s (valid types: encrypted, 1password)", provider)
50-
}
51-
}
52-
5339
// SetSecretsProvider sets the secrets provider type in the configuration.
5440
// It validates the input and updates the configuration.
5541
// Choices are `encrypted` and `1password`.
@@ -83,13 +69,13 @@ func SetSecretsProvider(provider secrets.ProviderType) error {
8369
}
8470

8571
// NeedSecretsPassword returns true if the secrets provider requires a password.
86-
func NeedSecretsPassword(cmd *cobra.Command, secretOptions []string) bool {
72+
func NeedSecretsPassword(secretOptions []string) bool {
8773
// If the user did not ask for any secrets, then don't attempt to instantiate
8874
// the secrets manager.
8975
if len(secretOptions) == 0 {
9076
return false
9177
}
9278
// Ignore err - if the flag is not set, it's not needed.
93-
providerType, _ := GetSecretsProviderType(cmd)
79+
providerType, _ := config.GetConfig().Secrets.GetProviderType()
9480
return providerType == secrets.EncryptedType
9581
}

cmd/thv/app/restart.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func restartCmdFunc(cmd *cobra.Command, args []string) error {
9393

9494
// Run the tooling server
9595
logger.Infof("Starting tooling server %s...", containerName)
96-
return RunMCPServer(ctx, cmd, mcpRunner.Config, false)
96+
return RunMCPServer(ctx, mcpRunner.Config, false)
9797
}
9898

9999
// isProxyRunning checks if the proxy process is running

cmd/thv/app/run.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,12 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
218218
}
219219

220220
// Configure the RunConfig with transport, ports, permissions, etc.
221-
if err := configureRunConfig(cmd, config, runTransport, runPort, runTargetPort, runTargetHost, runEnv); err != nil {
221+
if err := configureRunConfig(config, runTransport, runPort, runTargetPort, runTargetHost, runEnv); err != nil {
222222
return err
223223
}
224224

225225
// Run the MCP server
226-
return RunMCPServer(ctx, cmd, config, runForeground)
226+
return RunMCPServer(ctx, config, runForeground)
227227
}
228228

229229
// pullImage pulls an image from a remote registry if it has the "latest" tag

cmd/thv/app/run_common.go

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"syscall"
1010

1111
"github.com/adrg/xdg"
12-
"github.com/spf13/cobra"
1312

1413
"github.com/stacklok/toolhive/pkg/authz"
1514
"github.com/stacklok/toolhive/pkg/logger"
@@ -23,10 +22,10 @@ import (
2322
// RunMCPServer runs an MCP server with the specified config
2423
//
2524
//nolint:gocyclo // This function is complex but manageable
26-
func RunMCPServer(ctx context.Context, cmd *cobra.Command, config *runner.RunConfig, foreground bool) error {
25+
func RunMCPServer(ctx context.Context, config *runner.RunConfig, foreground bool) error {
2726
// If not running in foreground mode, start a new detached process and exit
2827
if !foreground {
29-
return detachProcess(cmd, config)
28+
return detachProcess(config)
3029
}
3130

3231
// Create a Runner with the RunConfig
@@ -39,7 +38,7 @@ func RunMCPServer(ctx context.Context, cmd *cobra.Command, config *runner.RunCon
3938
// detachProcess starts a new detached process with the same command but with the --foreground flag
4039
//
4140
//nolint:gocyclo // This function is complex but manageable
42-
func detachProcess(cmd *cobra.Command, options *runner.RunConfig) error {
41+
func detachProcess(options *runner.RunConfig) error {
4342
// Get the current executable path
4443
execPath, err := os.Executable()
4544
if err != nil {
@@ -146,8 +145,8 @@ func detachProcess(cmd *cobra.Command, options *runner.RunConfig) error {
146145
// Set environment variables for the detached process
147146
detachedCmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", process.ToolHiveDetachedEnv, process.ToolHiveDetachedValue))
148147

149-
// If the process needs the decrypt password, pass it as an environment variable.
150-
if NeedSecretsPassword(cmd, options.Secrets) {
148+
// If we need the decrypt password, set it as an environment variable in the detached process.
149+
if NeedSecretsPassword(options.Secrets) {
151150
password, err := secrets.GetSecretsPassword()
152151
if err != nil {
153152
return fmt.Errorf("failed to get secrets password: %v", err)
@@ -200,7 +199,6 @@ func findEnvironmentVariableFromSecrets(secs []string, envVarName string) bool {
200199

201200
// configureRunConfig configures a RunConfig with transport, ports, permissions, etc.
202201
func configureRunConfig(
203-
cmd *cobra.Command,
204202
config *runner.RunConfig,
205203
transport string,
206204
port int,
@@ -215,24 +213,6 @@ func configureRunConfig(
215213
return fmt.Errorf("permission profile is required")
216214
}
217215

218-
// Process secrets if provided
219-
if len(config.Secrets) > 0 {
220-
providerType, err := GetSecretsProviderType(cmd)
221-
if err != nil {
222-
return fmt.Errorf("error determining secrets provider type: %w", err)
223-
}
224-
225-
secretManager, err := secrets.CreateSecretManager(providerType)
226-
if err != nil {
227-
return fmt.Errorf("error instantiating secret manager %v", err)
228-
}
229-
230-
// Process secrets
231-
if _, err = config.WithSecrets(secretManager); err != nil {
232-
return err
233-
}
234-
}
235-
236216
// Set transport
237217
if _, err = config.WithTransport(transport); err != nil {
238218
return err

cmd/thv/app/secret.go

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/spf13/cobra"
1111
"golang.org/x/term"
1212

13+
"github.com/stacklok/toolhive/pkg/config"
1314
"github.com/stacklok/toolhive/pkg/secrets"
1415
)
1516

@@ -68,7 +69,7 @@ Input Methods:
6869
6970
The secret will be stored securely using the configured secrets provider.`,
7071
Args: cobra.ExactArgs(1),
71-
Run: func(cmd *cobra.Command, args []string) {
72+
Run: func(_ *cobra.Command, args []string) {
7273
name := args[0]
7374

7475
// Validate input
@@ -114,13 +115,7 @@ The secret will be stored securely using the configured secrets provider.`,
114115
return
115116
}
116117

117-
providerType, err := GetSecretsProviderType(cmd)
118-
if err != nil {
119-
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
120-
os.Exit(1)
121-
}
122-
123-
manager, err := secrets.CreateSecretManager(providerType)
118+
manager, err := getSecretsManager()
124119
if err != nil {
125120
fmt.Fprintf(os.Stderr, "Failed to create secrets manager: %v\n", err)
126121
return
@@ -141,7 +136,7 @@ func newSecretGetCommand() *cobra.Command {
141136
Use: "get <name>",
142137
Short: "Get a secret",
143138
Args: cobra.ExactArgs(1),
144-
Run: func(cmd *cobra.Command, args []string) {
139+
Run: func(_ *cobra.Command, args []string) {
145140
name := args[0]
146141

147142
// Validate input
@@ -150,13 +145,7 @@ func newSecretGetCommand() *cobra.Command {
150145
return
151146
}
152147

153-
providerType, err := GetSecretsProviderType(cmd)
154-
if err != nil {
155-
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
156-
os.Exit(1)
157-
}
158-
159-
manager, err := secrets.CreateSecretManager(providerType)
148+
manager, err := getSecretsManager()
160149
if err != nil {
161150
fmt.Fprintf(os.Stderr, "Failed to create secrets manager: %v\n", err)
162151
return
@@ -177,7 +166,7 @@ func newSecretDeleteCommand() *cobra.Command {
177166
Use: "delete <name>",
178167
Short: "Delete a secret",
179168
Args: cobra.ExactArgs(1),
180-
Run: func(cmd *cobra.Command, args []string) {
169+
Run: func(_ *cobra.Command, args []string) {
181170
name := args[0]
182171

183172
// Validate input
@@ -186,13 +175,7 @@ func newSecretDeleteCommand() *cobra.Command {
186175
return
187176
}
188177

189-
providerType, err := GetSecretsProviderType(cmd)
190-
if err != nil {
191-
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
192-
os.Exit(1)
193-
}
194-
195-
manager, err := secrets.CreateSecretManager(providerType)
178+
manager, err := getSecretsManager()
196179
if err != nil {
197180
fmt.Fprintf(os.Stderr, "Failed to create secrets manager: %v\n", err)
198181
return
@@ -213,14 +196,8 @@ func newSecretListCommand() *cobra.Command {
213196
Use: "list",
214197
Short: "List all available secrets",
215198
Args: cobra.NoArgs,
216-
Run: func(cmd *cobra.Command, _ []string) {
217-
providerType, err := GetSecretsProviderType(cmd)
218-
if err != nil {
219-
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
220-
os.Exit(1)
221-
}
222-
223-
manager, err := secrets.CreateSecretManager(providerType)
199+
Run: func(_ *cobra.Command, _ []string) {
200+
manager, err := getSecretsManager()
224201
if err != nil {
225202
fmt.Fprintf(os.Stderr, "Failed to create secrets manager: %v\n", err)
226203
return
@@ -259,3 +236,17 @@ func newSecretResetKeyringCommand() *cobra.Command {
259236
},
260237
}
261238
}
239+
240+
func getSecretsManager() (secrets.Provider, error) {
241+
providerType, err := config.GetConfig().Secrets.GetProviderType()
242+
if err != nil {
243+
return nil, fmt.Errorf("failed to get secrets provider type: %w", err)
244+
}
245+
246+
manager, err := secrets.CreateSecretProvider(providerType)
247+
if err != nil {
248+
return nil, fmt.Errorf("failed to create secrets manager: %w", err)
249+
}
250+
251+
return manager, nil
252+
}

pkg/config/config.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ type Secrets struct {
3333
ProviderType string `yaml:"provider_type"`
3434
}
3535

36+
// GetProviderType returns the secrets provider type from the application config.
37+
func (s *Secrets) GetProviderType() (secrets.ProviderType, error) {
38+
provider := s.ProviderType
39+
switch provider {
40+
case string(secrets.EncryptedType):
41+
return secrets.EncryptedType, nil
42+
case string(secrets.OnePasswordType):
43+
return secrets.OnePasswordType, nil
44+
default:
45+
// TODO: auto-generate the set of valid values.
46+
return "", fmt.Errorf("invalid secrets provider type: %s (valid types: encrypted, 1password)", provider)
47+
}
48+
}
49+
3650
// Clients contains settings for client configuration.
3751
type Clients struct {
3852
AutoDiscovery bool `yaml:"auto_discovery"`

pkg/runner/runner.go

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111

1212
"github.com/stacklok/toolhive/pkg/auth"
1313
"github.com/stacklok/toolhive/pkg/client"
14+
"github.com/stacklok/toolhive/pkg/config"
1415
"github.com/stacklok/toolhive/pkg/logger"
1516
"github.com/stacklok/toolhive/pkg/process"
17+
"github.com/stacklok/toolhive/pkg/secrets"
1618
"github.com/stacklok/toolhive/pkg/transport"
1719
"github.com/stacklok/toolhive/pkg/transport/types"
1820
)
@@ -24,9 +26,9 @@ type Runner struct {
2426
}
2527

2628
// NewRunner creates a new Runner with the provided configuration
27-
func NewRunner(config *RunConfig) *Runner {
29+
func NewRunner(runConfig *RunConfig) *Runner {
2830
return &Runner{
29-
Config: config,
31+
Config: runConfig,
3032
}
3133
}
3234

@@ -83,6 +85,31 @@ func (r *Runner) Run(ctx context.Context) error {
8385
return fmt.Errorf("failed to create transport: %v", err)
8486
}
8587

88+
// Save the configuration to the state store
89+
if err := r.SaveState(ctx); err != nil {
90+
logger.Warnf("Warning: Failed to save run configuration: %v", err)
91+
}
92+
93+
// Process secrets if provided
94+
// NOTE: This MUST happen after we save the run config to avoid storing
95+
// the secrets in the state store.
96+
if len(r.Config.Secrets) > 0 {
97+
providerType, err := config.GetConfig().Secrets.GetProviderType()
98+
if err != nil {
99+
return fmt.Errorf("error determining secrets provider type: %w", err)
100+
}
101+
102+
secretManager, err := secrets.CreateSecretProvider(providerType)
103+
if err != nil {
104+
return fmt.Errorf("error instantiating secret manager %v", err)
105+
}
106+
107+
// Process secrets
108+
if _, err = r.Config.WithSecrets(secretManager); err != nil {
109+
return err
110+
}
111+
}
112+
86113
// Set up the transport
87114
logger.Infof("Setting up %s transport...", r.Config.Transport)
88115
if err := transportHandler.Setup(
@@ -100,11 +127,6 @@ func (r *Runner) Run(ctx context.Context) error {
100127

101128
logger.Infof("MCP server %s started successfully", r.Config.ContainerName)
102129

103-
// Save the configuration to the state store
104-
if err := r.SaveState(ctx); err != nil {
105-
logger.Warnf("Warning: Failed to save run configuration: %v", err)
106-
}
107-
108130
// Update client configurations with the MCP server URL.
109131
// Note that this function checks the configuration to determine which
110132
// clients should be updated, if any.
@@ -199,12 +221,12 @@ func (r *Runner) Run(ctx context.Context) error {
199221
// updateClientConfigurations updates client configuration files with the MCP server URL
200222
func updateClientConfigurations(containerName, host string, port int) error {
201223
// Find client configuration files
202-
configs, err := client.FindClientConfigs()
224+
clientConfigs, err := client.FindClientConfigs()
203225
if err != nil {
204226
return fmt.Errorf("failed to find client configurations: %w", err)
205227
}
206228

207-
if len(configs) == 0 {
229+
if len(clientConfigs) == 0 {
208230
logger.Infof("No client configuration files found")
209231
return nil
210232
}
@@ -213,15 +235,15 @@ func updateClientConfigurations(containerName, host string, port int) error {
213235
url := client.GenerateMCPServerURL(host, port, containerName)
214236

215237
// Update each configuration file
216-
for _, config := range configs {
217-
logger.Infof("Updating client configuration: %s", config.Path)
238+
for _, clientConfig := range clientConfigs {
239+
logger.Infof("Updating client configuration: %s", clientConfig.Path)
218240

219-
if err := client.Upsert(config, containerName, url); err != nil {
220-
fmt.Printf("Warning: Failed to update MCP server configuration in %s: %v\n", config.Path, err)
241+
if err := client.Upsert(clientConfig, containerName, url); err != nil {
242+
fmt.Printf("Warning: Failed to update MCP server configuration in %s: %v\n", clientConfig.Path, err)
221243
continue
222244
}
223245

224-
logger.Infof("Successfully updated client configuration: %s", config.Path)
246+
logger.Infof("Successfully updated client configuration: %s", clientConfig.Path)
225247
}
226248

227249
return nil

0 commit comments

Comments
 (0)