Skip to content

Commit ff69330

Browse files
temblekingalecron
andauthored
feat: add support for skip TLS verification (#59)
Co-authored-by: Alejandro Magallón Soler <alejandro.magallon@sysdig.com>
1 parent fdf94bd commit ff69330

File tree

8 files changed

+165
-22
lines changed

8 files changed

+165
-22
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ The following environment variables are **required** for configuring the Sysdig
201201
You can also set the following variables to override the default configuration:
202202

203203
- `SYSDIG_MCP_TRANSPORT`: The transport protocol for the MCP Server (`stdio`, `streamable-http`, `sse`). Defaults to: `stdio`.
204+
- `SYSDIG_MCP_API_SKIP_TLS_VERIFICATION`: Whether to skip TLS verification for the Sysdig API connection (useful for self-signed certificates). Defaults to: `false`.
204205
- `SYSDIG_MCP_MOUNT_PATH`: The URL prefix for the streamable-http/sse deployment. Defaults to: `/sysdig-mcp-server`
205206
- `SYSDIG_MCP_LOGLEVEL`: Log Level of the application (`DEBUG`, `INFO`, `WARNING`, `ERROR`). Defaults to: `INFO`
206207
- `SYSDIG_MCP_LISTENING_PORT`: The port for the server when it is deployed using remote protocols (`streamable-http`, `sse`). Defaults to: `8080`

cmd/server/main.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"crypto/tls"
56
"fmt"
67
"log/slog"
78
"net/http"
@@ -82,13 +83,26 @@ func setupLogger(logLevel string) {
8283
}
8384

8485
func setupSysdigClient(cfg *config.Config) (sysdig.ExtendedClientWithResponsesInterface, error) {
85-
sysdigClient, err := sysdig.NewSysdigClient(
86+
sysdigClientOptions := []sysdig.IntoClientOption{
8687
sysdig.WithVersion(Version),
8788
sysdig.WithFallbackAuthentication(
8889
sysdig.WithHostAndTokenFromContext(),
8990
sysdig.WithFixedHostAndToken(cfg.APIHost, cfg.APIToken),
9091
),
91-
)
92+
}
93+
94+
if cfg.SkipTLSVerification {
95+
transport := http.DefaultTransport.(*http.Transport).Clone()
96+
if transport.TLSClientConfig == nil {
97+
transport.TLSClientConfig = &tls.Config{}
98+
}
99+
transport.TLSClientConfig.InsecureSkipVerify = true
100+
httpClient := &http.Client{Transport: transport}
101+
102+
sysdigClientOptions = append(sysdigClientOptions, sysdig.WithHTTPClient(httpClient))
103+
}
104+
105+
sysdigClient, err := sysdig.NewSysdigClient(sysdigClientOptions...)
92106
if err != nil {
93107
return nil, fmt.Errorf("error creating sysdig client: %w", err)
94108
}

docs/TROUBLESHOOTING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
**Problem**: "unable to authenticate with any method"
77
- **Solution**: For `stdio`, verify `SYSDIG_MCP_API_HOST` and `SYSDIG_MCP_API_TOKEN` env vars are set correctly. For remote transports, check `Authorization: Bearer <token>` header format.
88

9+
**Problem**: Connection failing with "certificate signed by unknown authority"
10+
- **Solution**: If using a self-signed certificate (e.g. on-prem), set `SYSDIG_MCP_API_SKIP_TLS_VERIFICATION=true`.
11+
912
**Problem**: Tests failing with "command not found"
1013
- **Solution**: Enter Nix shell with `nix develop` or `direnv allow`. All dev tools are provided by the flake.
1114

internal/config/config.go

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ package config
33
import (
44
"fmt"
55
"os"
6+
"strconv"
7+
"strings"
68
)
79

810
type Config struct {
9-
APIHost string
10-
APIToken string
11-
Transport string
12-
ListeningHost string
13-
ListeningPort string
14-
MountPath string
15-
LogLevel string
11+
APIHost string
12+
APIToken string
13+
SkipTLSVerification bool
14+
Transport string
15+
ListeningHost string
16+
ListeningPort string
17+
MountPath string
18+
LogLevel string
1619
}
1720

1821
func (c *Config) Validate() error {
@@ -27,13 +30,14 @@ func (c *Config) Validate() error {
2730

2831
func Load() (*Config, error) {
2932
cfg := &Config{
30-
APIHost: getEnv("SYSDIG_MCP_API_HOST", ""),
31-
APIToken: getEnv("SYSDIG_MCP_API_TOKEN", ""),
32-
Transport: getEnv("SYSDIG_MCP_TRANSPORT", "stdio"),
33-
ListeningHost: getEnv("SYSDIG_MCP_LISTENING_HOST", "localhost"),
34-
ListeningPort: getEnv("SYSDIG_MCP_LISTENING_PORT", "8080"),
35-
MountPath: getEnv("SYSDIG_MCP_MOUNT_PATH", "/sysdig-mcp-server"),
36-
LogLevel: getEnv("SYSDIG_MCP_LOGLEVEL", "INFO"),
33+
APIHost: getEnv("SYSDIG_MCP_API_HOST", ""),
34+
APIToken: getEnv("SYSDIG_MCP_API_TOKEN", ""),
35+
SkipTLSVerification: getEnv("SYSDIG_MCP_API_SKIP_TLS_VERIFICATION", false),
36+
Transport: getEnv("SYSDIG_MCP_TRANSPORT", "stdio"),
37+
ListeningHost: getEnv("SYSDIG_MCP_LISTENING_HOST", "localhost"),
38+
ListeningPort: getEnv("SYSDIG_MCP_LISTENING_PORT", "8080"),
39+
MountPath: getEnv("SYSDIG_MCP_MOUNT_PATH", "/sysdig-mcp-server"),
40+
LogLevel: getEnv("SYSDIG_MCP_LOGLEVEL", "INFO"),
3741
}
3842

3943
if err := cfg.Validate(); err != nil {
@@ -43,9 +47,32 @@ func Load() (*Config, error) {
4347
return cfg, nil
4448
}
4549

46-
func getEnv(key, fallback string) string {
47-
if value, ok := os.LookupEnv(key); ok {
48-
return value
50+
type envType interface {
51+
~string | ~bool
52+
}
53+
54+
func getEnv[T envType](key string, fallback T) T {
55+
value, ok := os.LookupEnv(key)
56+
if !ok {
57+
return fallback
4958
}
59+
60+
switch any(fallback).(type) {
61+
case string:
62+
return any(value).(T)
63+
64+
case bool:
65+
value = strings.TrimSpace(value)
66+
if value == "" {
67+
return fallback
68+
}
69+
70+
b, err := strconv.ParseBool(value)
71+
if err != nil {
72+
return fallback
73+
}
74+
return any(b).(T)
75+
}
76+
5077
return fallback
5178
}

internal/config/config_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ var _ = Describe("Config", func() {
8282
Expect(cfg.ListeningPort).To(Equal("8080"))
8383
Expect(cfg.MountPath).To(Equal("/sysdig-mcp-server"))
8484
Expect(cfg.LogLevel).To(Equal("INFO"))
85+
Expect(cfg.SkipTLSVerification).To(BeFalse())
8586
})
8687
})
8788

@@ -106,6 +107,7 @@ var _ = Describe("Config", func() {
106107
BeforeEach(func() {
107108
_ = os.Setenv("SYSDIG_MCP_API_HOST", "env-host")
108109
_ = os.Setenv("SYSDIG_MCP_API_TOKEN", "env-token")
110+
_ = os.Setenv("SYSDIG_MCP_API_SKIP_TLS_VERIFICATION", "true")
109111
_ = os.Setenv("SYSDIG_MCP_TRANSPORT", "http")
110112
_ = os.Setenv("SYSDIG_MCP_LISTENING_HOST", "0.0.0.0")
111113
_ = os.Setenv("SYSDIG_MCP_LISTENING_PORT", "9090")
@@ -118,6 +120,7 @@ var _ = Describe("Config", func() {
118120
Expect(err).NotTo(HaveOccurred())
119121
Expect(cfg.APIHost).To(Equal("env-host"))
120122
Expect(cfg.APIToken).To(Equal("env-token"))
123+
Expect(cfg.SkipTLSVerification).To(BeTrue())
121124
Expect(cfg.Transport).To(Equal("http"))
122125
Expect(cfg.ListeningHost).To(Equal("0.0.0.0"))
123126
Expect(cfg.ListeningPort).To(Equal("9090"))
@@ -126,6 +129,20 @@ var _ = Describe("Config", func() {
126129
})
127130
})
128131

132+
Context("with invalid boolean env var", func() {
133+
BeforeEach(func() {
134+
_ = os.Setenv("SYSDIG_MCP_API_HOST", "host")
135+
_ = os.Setenv("SYSDIG_MCP_API_TOKEN", "token")
136+
_ = os.Setenv("SYSDIG_MCP_API_SKIP_TLS_VERIFICATION", "invalid-bool")
137+
})
138+
139+
It("should fall back to default value", func() {
140+
cfg, err := config.Load()
141+
Expect(err).NotTo(HaveOccurred())
142+
Expect(cfg.SkipTLSVerification).To(BeFalse())
143+
})
144+
})
145+
129146
Context("without required env vars", func() {
130147
It("should return an error", func() {
131148
_, err := config.Load()

internal/infra/sysdig/client.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,22 @@ func WithVersion(version string) RequestEditorFn {
8888
}
8989
}
9090

91-
func NewSysdigClient(requestEditors ...RequestEditorFn) (ExtendedClientWithResponsesInterface, error) {
91+
type IntoClientOption interface {
92+
AsClientOption() ClientOption
93+
}
94+
95+
func (r RequestEditorFn) AsClientOption() ClientOption {
96+
return WithRequestEditorFn(r)
97+
}
98+
99+
func (c ClientOption) AsClientOption() ClientOption {
100+
return c
101+
}
102+
103+
func NewSysdigClient(requestEditors ...IntoClientOption) (ExtendedClientWithResponsesInterface, error) {
92104
editors := make([]ClientOption, len(requestEditors))
93105
for i, e := range requestEditors {
94-
editors[i] = WithRequestEditorFn(e)
106+
editors[i] = e.AsClientOption()
95107
}
96108

97109
return NewClientWithResponses("", editors...)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package sysdig_test
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"log"
7+
"net/http"
8+
"net/http/httptest"
9+
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
"github.com/sysdiglabs/sysdig-mcp-server/internal/infra/sysdig"
13+
)
14+
15+
var _ = Describe("Client TLS", func() {
16+
var ts *httptest.Server
17+
18+
BeforeEach(func() {
19+
// Start a TLS server with a self-signed certificate
20+
ts = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21+
if r.URL.Path == "/api/users/me/permissions" {
22+
w.Header().Set("Content-Type", "application/json")
23+
w.WriteHeader(http.StatusOK)
24+
_, _ = w.Write([]byte(`{"permissions":[]}`))
25+
return
26+
}
27+
w.WriteHeader(http.StatusNotFound)
28+
}))
29+
// Redirect server logs to GinkgoWriter to avoid noise in test output
30+
ts.Config.ErrorLog = log.New(GinkgoWriter, "", 0)
31+
})
32+
33+
AfterEach(func() {
34+
ts.Close()
35+
})
36+
37+
It("should fail request with self-signed cert by default", func() {
38+
// Create client pointing to the TLS server without custom transport
39+
client, err := sysdig.NewSysdigClient(
40+
sysdig.WithFixedHostAndToken(ts.URL, "dummy-token"),
41+
)
42+
Expect(err).NotTo(HaveOccurred())
43+
44+
// Attempt request
45+
_, err = client.GetMyPermissionsWithResponse(context.Background())
46+
Expect(err).To(HaveOccurred())
47+
// Verification that it failed due to certificate issues
48+
Expect(err.Error()).To(Or(ContainSubstring("certificate"), ContainSubstring("unknown authority")))
49+
})
50+
51+
It("should succeed request when using custom HTTP client with InsecureSkipVerify", func() {
52+
// Create custom HTTP client that skips verification
53+
transport := http.DefaultTransport.(*http.Transport).Clone()
54+
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
55+
httpClient := &http.Client{Transport: transport}
56+
57+
// Create client using the custom HTTP client
58+
client, err := sysdig.NewSysdigClient(
59+
sysdig.WithFixedHostAndToken(ts.URL, "dummy-token"),
60+
sysdig.WithHTTPClient(httpClient),
61+
)
62+
Expect(err).NotTo(HaveOccurred())
63+
64+
// Attempt request
65+
resp, err := client.GetMyPermissionsWithResponse(context.Background())
66+
Expect(err).NotTo(HaveOccurred())
67+
Expect(resp.HTTPResponse.StatusCode).To(Equal(http.StatusOK))
68+
})
69+
})

package.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{ buildGoModule, versionCheckHook }:
22
buildGoModule (finalAttrs: {
33
pname = "sysdig-mcp-server";
4-
version = "0.5.4";
4+
version = "0.6.0";
55
src = ./.;
66
# This hash is automatically re-calculated with `just rehash-package-nix`. This is automatically called as well by `just bump`.
77
vendorHash = "sha256-jf/px0p88XbfuSPMry/qZcfR0QPTF9IrPegg2CwAd6M=";

0 commit comments

Comments
 (0)