| 
 | 1 | +package kubernetes  | 
 | 2 | + | 
 | 3 | +import (  | 
 | 4 | +	"context"  | 
 | 5 | +	"os"  | 
 | 6 | +	"path/filepath"  | 
 | 7 | +	"testing"  | 
 | 8 | + | 
 | 9 | +	"github.com/containers/kubernetes-mcp-server/internal/test"  | 
 | 10 | +	"github.com/containers/kubernetes-mcp-server/pkg/config"  | 
 | 11 | +	"github.com/stretchr/testify/suite"  | 
 | 12 | +)  | 
 | 13 | + | 
 | 14 | +type DerivedTestSuite struct {  | 
 | 15 | +	suite.Suite  | 
 | 16 | +}  | 
 | 17 | + | 
 | 18 | +func (s *DerivedTestSuite) TestKubeConfig() {  | 
 | 19 | +	// Create a temporary kubeconfig file for testing  | 
 | 20 | +	tempDir := s.T().TempDir()  | 
 | 21 | +	kubeconfigPath := filepath.Join(tempDir, "config")  | 
 | 22 | +	kubeconfigContent := `  | 
 | 23 | +apiVersion: v1  | 
 | 24 | +kind: Config  | 
 | 25 | +clusters:  | 
 | 26 | +- cluster:  | 
 | 27 | +    server: https://test-cluster.example.com  | 
 | 28 | +  name: test-cluster  | 
 | 29 | +contexts:  | 
 | 30 | +- context:  | 
 | 31 | +    cluster: test-cluster  | 
 | 32 | +    user: test-user  | 
 | 33 | +  name: test-context  | 
 | 34 | +current-context: test-context  | 
 | 35 | +users:  | 
 | 36 | +- name: test-user  | 
 | 37 | +  user:  | 
 | 38 | +    username: test-username  | 
 | 39 | +    password: test-password  | 
 | 40 | +`  | 
 | 41 | +	err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0644)  | 
 | 42 | +	s.Require().NoError(err, "failed to create kubeconfig file")  | 
 | 43 | + | 
 | 44 | +	s.Run("with no RequireOAuth (default) config", func() {  | 
 | 45 | +		testStaticConfig := test.Must(config.ReadToml([]byte(`  | 
 | 46 | +			kubeconfig = "` + kubeconfigPath + `"  | 
 | 47 | +		`)))  | 
 | 48 | +		s.Run("without authorization header returns original manager", func() {  | 
 | 49 | +			testManager, err := NewManager(testStaticConfig)  | 
 | 50 | +			s.Require().NoErrorf(err, "failed to create test manager: %v", err)  | 
 | 51 | +			s.T().Cleanup(testManager.Close)  | 
 | 52 | + | 
 | 53 | +			derived, err := testManager.Derived(s.T().Context())  | 
 | 54 | +			s.Require().NoErrorf(err, "failed to create derived manager: %v", err)  | 
 | 55 | + | 
 | 56 | +			s.Equal(derived.manager, testManager, "expected original manager, got different manager")  | 
 | 57 | +		})  | 
 | 58 | + | 
 | 59 | +		s.Run("with invalid authorization header returns original manager", func() {  | 
 | 60 | +			testManager, err := NewManager(testStaticConfig)  | 
 | 61 | +			s.Require().NoErrorf(err, "failed to create test manager: %v", err)  | 
 | 62 | +			s.T().Cleanup(testManager.Close)  | 
 | 63 | + | 
 | 64 | +			ctx := context.WithValue(s.T().Context(), HeaderKey("Authorization"), "invalid-token")  | 
 | 65 | +			derived, err := testManager.Derived(ctx)  | 
 | 66 | +			s.Require().NoErrorf(err, "failed to create derived manager: %v", err)  | 
 | 67 | + | 
 | 68 | +			s.Equal(derived.manager, testManager, "expected original manager, got different manager")  | 
 | 69 | +		})  | 
 | 70 | + | 
 | 71 | +		s.Run("with valid bearer token creates derived manager with correct configuration", func() {  | 
 | 72 | +			testManager, err := NewManager(testStaticConfig)  | 
 | 73 | +			s.Require().NoErrorf(err, "failed to create test manager: %v", err)  | 
 | 74 | +			s.T().Cleanup(testManager.Close)  | 
 | 75 | + | 
 | 76 | +			ctx := context.WithValue(s.T().Context(), HeaderKey("Authorization"), "Bearer aiTana-julIA")  | 
 | 77 | +			derived, err := testManager.Derived(ctx)  | 
 | 78 | +			s.Require().NoErrorf(err, "failed to create derived manager: %v", err)  | 
 | 79 | + | 
 | 80 | +			s.NotEqual(derived.manager, testManager, "expected new derived manager, got original manager")  | 
 | 81 | +			s.Equal(derived.manager.staticConfig, testStaticConfig, "staticConfig not properly wired to derived manager")  | 
 | 82 | + | 
 | 83 | +			s.Run("RestConfig is correctly copied and sensitive fields are omitted", func() {  | 
 | 84 | +				derivedCfg := derived.manager.cfg  | 
 | 85 | +				s.Require().NotNil(derivedCfg, "derived config is nil")  | 
 | 86 | + | 
 | 87 | +				originalCfg := testManager.cfg  | 
 | 88 | +				s.Equalf(originalCfg.Host, derivedCfg.Host, "expected Host %s, got %s", originalCfg.Host, derivedCfg.Host)  | 
 | 89 | +				s.Equalf(originalCfg.APIPath, derivedCfg.APIPath, "expected APIPath %s, got %s", originalCfg.APIPath, derivedCfg.APIPath)  | 
 | 90 | +				s.Equalf(originalCfg.QPS, derivedCfg.QPS, "expected QPS %f, got %f", originalCfg.QPS, derivedCfg.QPS)  | 
 | 91 | +				s.Equalf(originalCfg.Burst, derivedCfg.Burst, "expected Burst %d, got %d", originalCfg.Burst, derivedCfg.Burst)  | 
 | 92 | +				s.Equalf(originalCfg.Timeout, derivedCfg.Timeout, "expected Timeout %v, got %v", originalCfg.Timeout, derivedCfg.Timeout)  | 
 | 93 | + | 
 | 94 | +				s.Equalf(originalCfg.Insecure, derivedCfg.Insecure, "expected TLS Insecure %v, got %v", originalCfg.Insecure, derivedCfg.Insecure)  | 
 | 95 | +				s.Equalf(originalCfg.ServerName, derivedCfg.ServerName, "expected TLS ServerName %s, got %s", originalCfg.ServerName, derivedCfg.ServerName)  | 
 | 96 | +				s.Equalf(originalCfg.CAFile, derivedCfg.CAFile, "expected TLS CAFile %s, got %s", originalCfg.CAFile, derivedCfg.CAFile)  | 
 | 97 | +				s.Equalf(string(originalCfg.CAData), string(derivedCfg.CAData), "expected TLS CAData %s, got %s", string(originalCfg.CAData), string(derivedCfg.CAData))  | 
 | 98 | + | 
 | 99 | +				s.Equalf("aiTana-julIA", derivedCfg.BearerToken, "expected BearerToken %s, got %s", "aiTana-julIA", derivedCfg.BearerToken)  | 
 | 100 | +				s.Equalf("kubernetes-mcp-server/bearer-token-auth", derivedCfg.UserAgent, "expected UserAgent \"kubernetes-mcp-server/bearer-token-auth\", got %s", derivedCfg.UserAgent)  | 
 | 101 | + | 
 | 102 | +				// Verify that sensitive fields are NOT copied to prevent credential leakage  | 
 | 103 | +				// The derived config should only use the bearer token from the Authorization header  | 
 | 104 | +				// and not inherit any authentication credentials from the original kubeconfig  | 
 | 105 | +				s.Emptyf(derivedCfg.CertFile, "expected TLS CertFile to be empty, got %s", derivedCfg.CertFile)  | 
 | 106 | +				s.Emptyf(derivedCfg.KeyFile, "expected TLS KeyFile to be empty, got %s", derivedCfg.KeyFile)  | 
 | 107 | +				s.Emptyf(len(derivedCfg.CertData), "expected TLS CertData to be empty, got %v", derivedCfg.CertData)  | 
 | 108 | +				s.Emptyf(len(derivedCfg.KeyData), "expected TLS KeyData to be empty, got %v", derivedCfg.KeyData)  | 
 | 109 | + | 
 | 110 | +				s.Emptyf(derivedCfg.Username, "expected Username to be empty, got %s", derivedCfg.Username)  | 
 | 111 | +				s.Emptyf(derivedCfg.Password, "expected Password to be empty, got %s", derivedCfg.Password)  | 
 | 112 | +				s.Nilf(derivedCfg.AuthProvider, "expected AuthProvider to be nil, got %v", derivedCfg.AuthProvider)  | 
 | 113 | +				s.Nilf(derivedCfg.ExecProvider, "expected ExecProvider to be nil, got %v", derivedCfg.ExecProvider)  | 
 | 114 | +				s.Emptyf(derivedCfg.BearerTokenFile, "expected BearerTokenFile to be empty, got %s", derivedCfg.BearerTokenFile)  | 
 | 115 | +				s.Emptyf(derivedCfg.Impersonate.UserName, "expected Impersonate.UserName to be empty, got %s", derivedCfg.Impersonate.UserName)  | 
 | 116 | + | 
 | 117 | +				// Verify that the original manager still has the sensitive data  | 
 | 118 | +				s.Falsef(originalCfg.Username == "" && originalCfg.Password == "", "original kubeconfig shouldn't be modified")  | 
 | 119 | + | 
 | 120 | +			})  | 
 | 121 | +			s.Run("derived manager has initialized clients", func() {  | 
 | 122 | +				// Verify that the derived manager has proper clients initialized  | 
 | 123 | +				s.NotNilf(derived.manager.accessControlClientSet, "expected accessControlClientSet to be initialized")  | 
 | 124 | +				s.Equalf(testStaticConfig, derived.manager.accessControlClientSet.staticConfig, "staticConfig not properly wired to derived manager")  | 
 | 125 | +				s.NotNilf(derived.manager.discoveryClient, "expected discoveryClient to be initialized")  | 
 | 126 | +				s.NotNilf(derived.manager.accessControlRESTMapper, "expected accessControlRESTMapper to be initialized")  | 
 | 127 | +				s.Equalf(testStaticConfig, derived.manager.accessControlRESTMapper.staticConfig, "staticConfig not properly wired to derived manager")  | 
 | 128 | +				s.NotNilf(derived.manager.dynamicClient, "expected dynamicClient to be initialized")  | 
 | 129 | +			})  | 
 | 130 | +		})  | 
 | 131 | +	})  | 
 | 132 | + | 
 | 133 | +	s.Run("with RequireOAuth=true", func() {  | 
 | 134 | +		testStaticConfig := test.Must(config.ReadToml([]byte(`  | 
 | 135 | +			kubeconfig = "` + kubeconfigPath + `"  | 
 | 136 | +			require_oauth = true  | 
 | 137 | +		`)))  | 
 | 138 | + | 
 | 139 | +		s.Run("with no authorization header returns oauth token required error", func() {  | 
 | 140 | +			testManager, err := NewManager(testStaticConfig)  | 
 | 141 | +			s.Require().NoErrorf(err, "failed to create test manager: %v", err)  | 
 | 142 | +			s.T().Cleanup(testManager.Close)  | 
 | 143 | + | 
 | 144 | +			derived, err := testManager.Derived(s.T().Context())  | 
 | 145 | +			s.Require().Error(err, "expected error for missing oauth token, got nil")  | 
 | 146 | +			s.EqualError(err, "oauth token required", "expected error 'oauth token required', got %s", err.Error())  | 
 | 147 | +			s.Nil(derived, "expected nil derived manager when oauth token required")  | 
 | 148 | +		})  | 
 | 149 | + | 
 | 150 | +		s.Run("with invalid authorization header returns oauth token required error", func() {  | 
 | 151 | +			testManager, err := NewManager(testStaticConfig)  | 
 | 152 | +			s.Require().NoErrorf(err, "failed to create test manager: %v", err)  | 
 | 153 | +			s.T().Cleanup(testManager.Close)  | 
 | 154 | + | 
 | 155 | +			ctx := context.WithValue(s.T().Context(), HeaderKey("Authorization"), "invalid-token")  | 
 | 156 | +			derived, err := testManager.Derived(ctx)  | 
 | 157 | +			s.Require().Error(err, "expected error for invalid oauth token, got nil")  | 
 | 158 | +			s.EqualError(err, "oauth token required", "expected error 'oauth token required', got %s", err.Error())  | 
 | 159 | +			s.Nil(derived, "expected nil derived manager when oauth token required")  | 
 | 160 | +		})  | 
 | 161 | + | 
 | 162 | +		s.Run("with valid bearer token creates derived manager", func() {  | 
 | 163 | +			testManager, err := NewManager(testStaticConfig)  | 
 | 164 | +			s.Require().NoErrorf(err, "failed to create test manager: %v", err)  | 
 | 165 | +			s.T().Cleanup(testManager.Close)  | 
 | 166 | + | 
 | 167 | +			ctx := context.WithValue(s.T().Context(), HeaderKey("Authorization"), "Bearer aiTana-julIA")  | 
 | 168 | +			derived, err := testManager.Derived(ctx)  | 
 | 169 | +			s.Require().NoErrorf(err, "failed to create derived manager: %v", err)  | 
 | 170 | + | 
 | 171 | +			s.NotEqual(derived.manager, testManager, "expected new derived manager, got original manager")  | 
 | 172 | +			s.Equal(derived.manager.staticConfig, testStaticConfig, "staticConfig not properly wired to derived manager")  | 
 | 173 | + | 
 | 174 | +			derivedCfg := derived.manager.cfg  | 
 | 175 | +			s.Require().NotNil(derivedCfg, "derived config is nil")  | 
 | 176 | + | 
 | 177 | +			s.Equalf("aiTana-julIA", derivedCfg.BearerToken, "expected BearerToken %s, got %s", "aiTana-julIA", derivedCfg.BearerToken)  | 
 | 178 | +		})  | 
 | 179 | +	})  | 
 | 180 | +}  | 
 | 181 | + | 
 | 182 | +func TestDerived(t *testing.T) {  | 
 | 183 | +	suite.Run(t, new(DerivedTestSuite))  | 
 | 184 | +}  | 
0 commit comments