Skip to content

Commit 114726f

Browse files
authored
test(config): add new test case to increase the test coverage of Derived Config (167)
Add new unit tests to check the values in Derived config --- Rely on kubeconfig in staticConfig instead of a separate but equal one
1 parent c5b2223 commit 114726f

File tree

5 files changed

+241
-17
lines changed

5 files changed

+241
-17
lines changed

pkg/kubernetes/configuration.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ var InClusterConfig = func() (*rest.Config, error) {
2424
func resolveKubernetesConfigurations(kubernetes *Manager) error {
2525
// Always set clientCmdConfig
2626
pathOptions := clientcmd.NewDefaultPathOptions()
27-
if kubernetes.Kubeconfig != "" {
28-
pathOptions.LoadingRules.ExplicitPath = kubernetes.Kubeconfig
27+
if kubernetes.staticConfig.KubeConfig != "" {
28+
pathOptions.LoadingRules.ExplicitPath = kubernetes.staticConfig.KubeConfig
2929
}
3030
kubernetes.clientCmdConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
3131
pathOptions.LoadingRules,
@@ -46,7 +46,7 @@ func resolveKubernetesConfigurations(kubernetes *Manager) error {
4646
}
4747

4848
func (m *Manager) IsInCluster() bool {
49-
if m.Kubeconfig != "" {
49+
if m.staticConfig.KubeConfig != "" {
5050
return false
5151
}
5252
cfg, err := InClusterConfig()

pkg/kubernetes/configuration_test.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@ package kubernetes
22

33
import (
44
"errors"
5-
"k8s.io/client-go/rest"
65
"os"
76
"path"
87
"runtime"
98
"strings"
109
"testing"
10+
11+
"k8s.io/client-go/rest"
12+
13+
"github.com/manusa/kubernetes-mcp-server/pkg/config"
1114
)
1215

1316
func TestKubernetes_IsInCluster(t *testing.T) {
1417
t.Run("with explicit kubeconfig", func(t *testing.T) {
1518
m := Manager{
16-
Kubeconfig: "kubeconfig",
19+
staticConfig: &config.StaticConfig{
20+
KubeConfig: "kubeconfig",
21+
},
1722
}
1823
if m.IsInCluster() {
1924
t.Errorf("expected not in cluster, got in cluster")
@@ -28,7 +33,9 @@ func TestKubernetes_IsInCluster(t *testing.T) {
2833
InClusterConfig = originalFunction
2934
}()
3035
m := Manager{
31-
Kubeconfig: "",
36+
staticConfig: &config.StaticConfig{
37+
KubeConfig: "",
38+
},
3239
}
3340
if !m.IsInCluster() {
3441
t.Errorf("expected in cluster, got not in cluster")
@@ -43,7 +50,9 @@ func TestKubernetes_IsInCluster(t *testing.T) {
4350
InClusterConfig = originalFunction
4451
}()
4552
m := Manager{
46-
Kubeconfig: "",
53+
staticConfig: &config.StaticConfig{
54+
KubeConfig: "",
55+
},
4756
}
4857
if m.IsInCluster() {
4958
t.Errorf("expected not in cluster, got in cluster")
@@ -58,7 +67,9 @@ func TestKubernetes_IsInCluster(t *testing.T) {
5867
InClusterConfig = originalFunction
5968
}()
6069
m := Manager{
61-
Kubeconfig: "",
70+
staticConfig: &config.StaticConfig{
71+
KubeConfig: "",
72+
},
6273
}
6374
if m.IsInCluster() {
6475
t.Errorf("expected not in cluster, got in cluster")
@@ -72,7 +83,9 @@ func TestKubernetes_ResolveKubernetesConfigurations_Explicit(t *testing.T) {
7283
t.Skip("Skipping test on non-linux platforms")
7384
}
7485
tempDir := t.TempDir()
75-
m := Manager{Kubeconfig: path.Join(tempDir, "config")}
86+
m := Manager{staticConfig: &config.StaticConfig{
87+
KubeConfig: path.Join(tempDir, "config"),
88+
}}
7689
err := resolveKubernetesConfigurations(&m)
7790
if err == nil {
7891
t.Errorf("expected error, got nil")
@@ -90,7 +103,9 @@ func TestKubernetes_ResolveKubernetesConfigurations_Explicit(t *testing.T) {
90103
if err := os.WriteFile(kubeconfigPath, []byte(""), 0644); err != nil {
91104
t.Fatalf("failed to create kubeconfig file: %v", err)
92105
}
93-
m := Manager{Kubeconfig: kubeconfigPath}
106+
m := Manager{staticConfig: &config.StaticConfig{
107+
KubeConfig: kubeconfigPath,
108+
}}
94109
err := resolveKubernetesConfigurations(&m)
95110
if err == nil {
96111
t.Errorf("expected error, got nil")
@@ -123,7 +138,9 @@ users:
123138
if err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0644); err != nil {
124139
t.Fatalf("failed to create kubeconfig file: %v", err)
125140
}
126-
m := Manager{Kubeconfig: kubeconfigPath}
141+
m := Manager{staticConfig: &config.StaticConfig{
142+
KubeConfig: kubeconfigPath,
143+
}}
127144
err := resolveKubernetesConfigurations(&m)
128145
if err != nil {
129146
t.Fatalf("expected no error, got %v", err)

pkg/kubernetes/kubernetes.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ type Kubernetes struct {
3939
}
4040

4141
type Manager struct {
42-
// Kubeconfig path override
43-
Kubeconfig string
4442
cfg *rest.Config
4543
clientCmdConfig clientcmd.ClientConfig
4644
discoveryClient discovery.CachedDiscoveryInterface
@@ -57,9 +55,8 @@ var ParameterCodec = runtime.NewParameterCodec(Scheme)
5755

5856
var _ helm.Kubernetes = &Manager{}
5957

60-
func NewManager(kubeconfig string, config *config.StaticConfig) (*Manager, error) {
58+
func NewManager(config *config.StaticConfig) (*Manager, error) {
6159
k8s := &Manager{
62-
Kubeconfig: kubeconfig,
6360
staticConfig: config,
6461
}
6562
if err := resolveKubernetesConfigurations(k8s); err != nil {
@@ -166,7 +163,6 @@ func (m *Manager) Derived(ctx context.Context) *Kubernetes {
166163
}
167164
clientCmdApiConfig.AuthInfos = make(map[string]*clientcmdapi.AuthInfo)
168165
derived := &Kubernetes{manager: &Manager{
169-
Kubeconfig: m.Kubeconfig,
170166
clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil),
171167
cfg: derivedCfg,
172168
staticConfig: m.staticConfig,

pkg/kubernetes/kubernetes_test.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package kubernetes
2+
3+
import (
4+
"context"
5+
"os"
6+
"path"
7+
"testing"
8+
9+
"github.com/manusa/kubernetes-mcp-server/pkg/config"
10+
)
11+
12+
func TestManager_Derived(t *testing.T) {
13+
// Create a temporary kubeconfig file for testing
14+
tempDir := t.TempDir()
15+
kubeconfigPath := path.Join(tempDir, "config")
16+
kubeconfigContent := `
17+
apiVersion: v1
18+
kind: Config
19+
clusters:
20+
- cluster:
21+
server: https://test-cluster.example.com
22+
name: test-cluster
23+
contexts:
24+
- context:
25+
cluster: test-cluster
26+
user: test-user
27+
name: test-context
28+
current-context: test-context
29+
users:
30+
- name: test-user
31+
user:
32+
username: test-username
33+
password: test-password
34+
`
35+
if err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0644); err != nil {
36+
t.Fatalf("failed to create kubeconfig file: %v", err)
37+
}
38+
39+
t.Run("without authorization header returns original manager", func(t *testing.T) {
40+
testStaticConfig := &config.StaticConfig{
41+
KubeConfig: kubeconfigPath,
42+
DisabledTools: []string{"configuration_view"},
43+
DeniedResources: []config.GroupVersionKind{
44+
{Group: "apps", Version: "v1", Kind: "Deployment"},
45+
},
46+
}
47+
48+
testManager, err := NewManager(testStaticConfig)
49+
if err != nil {
50+
t.Fatalf("failed to create manager: %v", err)
51+
}
52+
defer testManager.Close()
53+
ctx := context.Background()
54+
derived := testManager.Derived(ctx)
55+
56+
if derived.manager != testManager {
57+
t.Errorf("expected original manager, got different manager")
58+
}
59+
})
60+
61+
t.Run("with invalid authorization header returns original manager", func(t *testing.T) {
62+
testStaticConfig := &config.StaticConfig{
63+
KubeConfig: kubeconfigPath,
64+
DisabledTools: []string{"configuration_view"},
65+
DeniedResources: []config.GroupVersionKind{
66+
{Group: "apps", Version: "v1", Kind: "Deployment"},
67+
},
68+
}
69+
70+
testManager, err := NewManager(testStaticConfig)
71+
if err != nil {
72+
t.Fatalf("failed to create manager: %v", err)
73+
}
74+
defer testManager.Close()
75+
ctx := context.WithValue(context.Background(), OAuthAuthorizationHeader, "invalid-token")
76+
derived := testManager.Derived(ctx)
77+
78+
if derived.manager != testManager {
79+
t.Errorf("expected original manager, got different manager")
80+
}
81+
})
82+
83+
t.Run("with valid bearer token creates derived manager with correct configuration", func(t *testing.T) {
84+
testStaticConfig := &config.StaticConfig{
85+
KubeConfig: kubeconfigPath,
86+
DisabledTools: []string{"configuration_view"},
87+
DeniedResources: []config.GroupVersionKind{
88+
{Group: "apps", Version: "v1", Kind: "Deployment"},
89+
},
90+
}
91+
92+
testManager, err := NewManager(testStaticConfig)
93+
if err != nil {
94+
t.Fatalf("failed to create manager: %v", err)
95+
}
96+
defer testManager.Close()
97+
testBearerToken := "test-bearer-token-123"
98+
ctx := context.WithValue(context.Background(), OAuthAuthorizationHeader, "Bearer "+testBearerToken)
99+
derived := testManager.Derived(ctx)
100+
101+
if derived.manager == testManager {
102+
t.Errorf("expected new derived manager, got original manager")
103+
}
104+
105+
if derived.manager.staticConfig != testStaticConfig {
106+
t.Errorf("staticConfig not properly wired to derived manager")
107+
}
108+
109+
derivedCfg := derived.manager.cfg
110+
if derivedCfg == nil {
111+
t.Fatalf("derived config is nil")
112+
}
113+
114+
originalCfg := testManager.cfg
115+
if derivedCfg.Host != originalCfg.Host {
116+
t.Errorf("expected Host %s, got %s", originalCfg.Host, derivedCfg.Host)
117+
}
118+
if derivedCfg.APIPath != originalCfg.APIPath {
119+
t.Errorf("expected APIPath %s, got %s", originalCfg.APIPath, derivedCfg.APIPath)
120+
}
121+
if derivedCfg.QPS != originalCfg.QPS {
122+
t.Errorf("expected QPS %f, got %f", originalCfg.QPS, derivedCfg.QPS)
123+
}
124+
if derivedCfg.Burst != originalCfg.Burst {
125+
t.Errorf("expected Burst %d, got %d", originalCfg.Burst, derivedCfg.Burst)
126+
}
127+
if derivedCfg.Timeout != originalCfg.Timeout {
128+
t.Errorf("expected Timeout %v, got %v", originalCfg.Timeout, derivedCfg.Timeout)
129+
}
130+
131+
if derivedCfg.TLSClientConfig.Insecure != originalCfg.TLSClientConfig.Insecure {
132+
t.Errorf("expected TLS Insecure %v, got %v", originalCfg.TLSClientConfig.Insecure, derivedCfg.TLSClientConfig.Insecure)
133+
}
134+
if derivedCfg.TLSClientConfig.ServerName != originalCfg.TLSClientConfig.ServerName {
135+
t.Errorf("expected TLS ServerName %s, got %s", originalCfg.TLSClientConfig.ServerName, derivedCfg.TLSClientConfig.ServerName)
136+
}
137+
if derivedCfg.TLSClientConfig.CAFile != originalCfg.TLSClientConfig.CAFile {
138+
t.Errorf("expected TLS CAFile %s, got %s", originalCfg.TLSClientConfig.CAFile, derivedCfg.TLSClientConfig.CAFile)
139+
}
140+
if string(derivedCfg.TLSClientConfig.CAData) != string(originalCfg.TLSClientConfig.CAData) {
141+
t.Errorf("expected TLS CAData %s, got %s", string(originalCfg.TLSClientConfig.CAData), string(derivedCfg.TLSClientConfig.CAData))
142+
}
143+
144+
if derivedCfg.BearerToken != testBearerToken {
145+
t.Errorf("expected BearerToken %s, got %s", testBearerToken, derivedCfg.BearerToken)
146+
}
147+
if derivedCfg.UserAgent != CustomUserAgent {
148+
t.Errorf("expected UserAgent %s, got %s", CustomUserAgent, derivedCfg.UserAgent)
149+
}
150+
151+
// Verify that sensitive fields are NOT copied to prevent credential leakage
152+
// The derived config should only use the bearer token from the Authorization header
153+
// and not inherit any authentication credentials from the original kubeconfig
154+
if derivedCfg.TLSClientConfig.CertFile != "" {
155+
t.Errorf("expected TLS CertFile to be empty, got %s", derivedCfg.TLSClientConfig.CertFile)
156+
}
157+
if derivedCfg.TLSClientConfig.KeyFile != "" {
158+
t.Errorf("expected TLS KeyFile to be empty, got %s", derivedCfg.TLSClientConfig.KeyFile)
159+
}
160+
if len(derivedCfg.TLSClientConfig.CertData) != 0 {
161+
t.Errorf("expected TLS CertData to be empty, got %v", derivedCfg.TLSClientConfig.CertData)
162+
}
163+
if len(derivedCfg.TLSClientConfig.KeyData) != 0 {
164+
t.Errorf("expected TLS KeyData to be empty, got %v", derivedCfg.TLSClientConfig.KeyData)
165+
}
166+
167+
if derivedCfg.Username != "" {
168+
t.Errorf("expected Username to be empty, got %s", derivedCfg.Username)
169+
}
170+
if derivedCfg.Password != "" {
171+
t.Errorf("expected Password to be empty, got %s", derivedCfg.Password)
172+
}
173+
if derivedCfg.AuthProvider != nil {
174+
t.Errorf("expected AuthProvider to be nil, got %v", derivedCfg.AuthProvider)
175+
}
176+
if derivedCfg.ExecProvider != nil {
177+
t.Errorf("expected ExecProvider to be nil, got %v", derivedCfg.ExecProvider)
178+
}
179+
if derivedCfg.BearerTokenFile != "" {
180+
t.Errorf("expected BearerTokenFile to be empty, got %s", derivedCfg.BearerTokenFile)
181+
}
182+
if derivedCfg.Impersonate.UserName != "" {
183+
t.Errorf("expected Impersonate.UserName to be empty, got %s", derivedCfg.Impersonate.UserName)
184+
}
185+
186+
// Verify that the original manager still has the sensitive data
187+
if originalCfg.Username == "" && originalCfg.Password == "" {
188+
t.Logf("original kubeconfig shouldn't be modified")
189+
}
190+
191+
// Verify that the derived manager has proper clients initialized
192+
if derived.manager.accessControlClientSet == nil {
193+
t.Error("expected accessControlClientSet to be initialized")
194+
}
195+
if derived.manager.accessControlClientSet.staticConfig != testStaticConfig {
196+
t.Errorf("staticConfig not properly wired to derived manager")
197+
}
198+
if derived.manager.discoveryClient == nil {
199+
t.Error("expected discoveryClient to be initialized")
200+
}
201+
if derived.manager.accessControlRESTMapper == nil {
202+
t.Error("expected accessControlRESTMapper to be initialized")
203+
}
204+
if derived.manager.accessControlRESTMapper.staticConfig != testStaticConfig {
205+
t.Errorf("staticConfig not properly wired to derived manager")
206+
}
207+
if derived.manager.dynamicClient == nil {
208+
t.Error("expected dynamicClient to be initialized")
209+
}
210+
})
211+
}

pkg/mcp/mcp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func NewServer(configuration Configuration) (*Server, error) {
6565
}
6666

6767
func (s *Server) reloadKubernetesClient() error {
68-
k, err := internalk8s.NewManager(s.configuration.StaticConfig.KubeConfig, s.configuration.StaticConfig)
68+
k, err := internalk8s.NewManager(s.configuration.StaticConfig)
6969
if err != nil {
7070
return err
7171
}

0 commit comments

Comments
 (0)