diff --git a/.gitignore b/.gitignore index 45d815f0..7db64048 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ bin dist +docker-mcp .idea/ /.direnv/ /.vscode/ diff --git a/.golangci.yml b/.golangci.yml index 0d92b352..7f777854 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -48,6 +48,16 @@ linters: - path: cmd/docker-mcp/internal/gateway/dynamic_mcps.go linters: - gofumpt + - path: vendor/ + linters: + - gofmt + - gofumpt + - goimports + - path: examples/ + linters: + - gofmt + - gofumpt + - goimports formatters: enable: - gofmt diff --git a/cmd/docker-mcp/commands/gateway.go b/cmd/docker-mcp/commands/gateway.go index 5ea12579..04313625 100644 --- a/cmd/docker-mcp/commands/gateway.go +++ b/cmd/docker-mcp/commands/gateway.go @@ -169,6 +169,7 @@ func gatewayCommand(docker docker.Client, dockerCli command.Cli) *cobra.Command runCmd.Flags().BoolVar(&options.LogCalls, "log-calls", options.LogCalls, "Log calls to the tools") runCmd.Flags().BoolVar(&options.BlockSecrets, "block-secrets", options.BlockSecrets, "Block secrets from being/received sent to/from tools") runCmd.Flags().BoolVar(&options.BlockNetwork, "block-network", options.BlockNetwork, "Block tools from accessing forbidden network resources") + runCmd.Flags().BoolVar(&options.DisableNetwork, "disable-network", options.DisableNetwork, "Disable network access for all MCP servers (overrides individual server settings)") runCmd.Flags().BoolVar(&options.VerifySignatures, "verify-signatures", options.VerifySignatures, "Verify signatures of the server images") runCmd.Flags().BoolVar(&options.DryRun, "dry-run", options.DryRun, "Start the gateway but do not listen for connections (useful for testing the configuration)") runCmd.Flags().BoolVar(&options.Verbose, "verbose", options.Verbose, "Verbose output") diff --git a/pkg/gateway/clientpool.go b/pkg/gateway/clientpool.go index c3862b33..43112bcb 100644 --- a/pkg/gateway/clientpool.go +++ b/pkg/gateway/clientpool.go @@ -205,9 +205,14 @@ func (cp *clientPool) InvalidateOAuthClients(provider string) { func (cp *clientPool) runToolContainer(ctx context.Context, tool catalog.Tool, params *mcp.CallToolParams) (*mcp.CallToolResult, error) { args := cp.baseArgs(tool.Name) - // Attach the MCP servers to the same network as the gateway. - for _, network := range cp.networks { - args = append(args, "--network", network) + // Security options - respect global DisableNetwork flag + if cp.DisableNetwork { + args = append(args, "--network", "none") + } else { + // Attach the MCP servers to the same network as the gateway. + for _, network := range cp.networks { + args = append(args, "--network", network) + } } // Convert params.Arguments to map[string]any @@ -296,7 +301,8 @@ func (cp *clientPool) argsAndEnv(serverConfig *catalog.ServerConfig, readOnly *b var env []string // Security options - if serverConfig.Spec.DisableNetwork { + // Global DisableNetwork flag overrides individual server settings + if cp.DisableNetwork || serverConfig.Spec.DisableNetwork { args = append(args, "--network", "none") } else { // Attach the MCP servers to the same network as the gateway. diff --git a/pkg/gateway/clientpool_test.go b/pkg/gateway/clientpool_test.go index 36a5e577..f21f19c7 100644 --- a/pkg/gateway/clientpool_test.go +++ b/pkg/gateway/clientpool_test.go @@ -255,3 +255,90 @@ func TestStdioClientInitialization(t *testing.T) { t.Logf("Successfully initialized stdio client and retrieved %d tools", len(tools.Tools)) } + +func TestGlobalDisableNetwork(t *testing.T) { + catalogYAML := ` +command: + - --transport=stdio +` + + // Test that global DisableNetwork=true adds --network none + args, env := argsAndEnvWithGlobalDisableNetwork(t, catalogYAML, "", nil, nil, true) + + assert.Contains(t, args, "--network") + assert.Contains(t, args, "none") + assert.Empty(t, env) +} + +func TestGlobalDisableNetworkOverridesServerSetting(t *testing.T) { + // Server has disableNetwork: false, but global flag should override + catalogYAML := ` +command: + - --transport=stdio +disableNetwork: false +` + + // Test that global DisableNetwork=true overrides server setting + args, env := argsAndEnvWithGlobalDisableNetwork(t, catalogYAML, "", nil, nil, true) + + assert.Contains(t, args, "--network") + assert.Contains(t, args, "none") + assert.Empty(t, env) +} + +func TestServerDisableNetworkWhenGlobalFalse(t *testing.T) { + // Server has disableNetwork: true, global is false - should still disable network + catalogYAML := ` +command: + - --transport=stdio +disableNetwork: true +` + + // Test that server DisableNetwork=true works when global is false + args, env := argsAndEnvWithGlobalDisableNetwork(t, catalogYAML, "", nil, nil, false) + + assert.Contains(t, args, "--network") + assert.Contains(t, args, "none") + assert.Empty(t, env) +} + +func TestNoNetworkDisabledWhenBothFalse(t *testing.T) { + // Both server and global disable network are false - should enable networks + catalogYAML := ` +command: + - --transport=stdio +disableNetwork: false +` + + // Test that when both global and server DisableNetwork are false, networks are enabled + args, env := argsAndEnvWithGlobalDisableNetwork(t, catalogYAML, "", nil, nil, false) + + // Should not contain --network none + networkNoneFound := false + for i, arg := range args { + if arg == "--network" && i+1 < len(args) && args[i+1] == "none" { + networkNoneFound = true + break + } + } + assert.False(t, networkNoneFound, "Should not disable network when both global and server settings are false") + assert.Empty(t, env) +} + +func argsAndEnvWithGlobalDisableNetwork(t *testing.T, catalogYAML, configYAML string, secrets map[string]string, readOnly *bool, globalDisableNetwork bool) ([]string, []string) { + t.Helper() + + clientPool := &clientPool{ + Options: Options{ + Cpus: 1, + Memory: "2Gb", + DisableNetwork: globalDisableNetwork, + }, + } + return clientPool.argsAndEnv(&catalog.ServerConfig{ + Name: "test-server", + Spec: parseSpec(t, catalogYAML), + Config: parseConfig(t, configYAML), + Secrets: secrets, + }, readOnly, proxies.TargetConfig{}) +} diff --git a/pkg/gateway/config.go b/pkg/gateway/config.go index e1940191..17b45e91 100644 --- a/pkg/gateway/config.go +++ b/pkg/gateway/config.go @@ -25,6 +25,7 @@ type Options struct { LogCalls bool BlockSecrets bool BlockNetwork bool + DisableNetwork bool VerifySignatures bool DryRun bool Watch bool