Skip to content

Commit 17c5707

Browse files
committed
OIDC Authentication Support and Tests
1 parent 9b51758 commit 17c5707

File tree

4 files changed

+629
-9
lines changed

4 files changed

+629
-9
lines changed

pkg/kubernetes/configuration.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,26 @@ func (k *Kubernetes) ConfigurationView(minify bool) (string, error) {
8585
Server: k.cfg.Host,
8686
InsecureSkipTLSVerify: k.cfg.Insecure,
8787
}
88-
cfg.AuthInfos["user"] = &clientcmdapi.AuthInfo{
89-
Token: k.cfg.BearerToken,
88+
89+
// Create auth info with appropriate authentication method
90+
authInfo := &clientcmdapi.AuthInfo{}
91+
92+
// If using bearer token
93+
if k.cfg.BearerToken != "" {
94+
authInfo.Token = k.cfg.BearerToken
95+
}
96+
97+
// If using OIDC auth provider
98+
if k.cfg.AuthProvider != nil {
99+
authInfo.AuthProvider = k.cfg.AuthProvider
90100
}
101+
102+
// If using exec provider (for OIDC or other auth methods)
103+
if k.cfg.ExecProvider != nil {
104+
authInfo.Exec = k.cfg.ExecProvider
105+
}
106+
107+
cfg.AuthInfos["user"] = authInfo
91108
cfg.Contexts["context"] = &clientcmdapi.Context{
92109
Cluster: "cluster",
93110
AuthInfo: "user",

pkg/kubernetes/configuration_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package kubernetes
33
import (
44
"errors"
55
"k8s.io/client-go/rest"
6+
"k8s.io/client-go/tools/clientcmd/api"
7+
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
68
"os"
79
"path"
810
"runtime"
11+
"sigs.k8s.io/yaml"
912
"strings"
1013
"testing"
1114
)
@@ -136,3 +139,152 @@ users:
136139
}
137140
})
138141
}
142+
143+
func TestConfigurationViewWithOIDC(t *testing.T) {
144+
// Save the original InClusterConfig function
145+
originalInClusterConfig := InClusterConfig
146+
147+
// Mock InClusterConfig to return a config with OIDC auth provider
148+
InClusterConfig = func() (*rest.Config, error) {
149+
return &rest.Config{
150+
Host: "https://kubernetes.default.svc",
151+
AuthProvider: &api.AuthProviderConfig{
152+
Name: "oidc",
153+
Config: map[string]string{
154+
"client-id": "test-client",
155+
"client-secret": "test-secret",
156+
"id-token": "test-id-token",
157+
"refresh-token": "test-refresh-token",
158+
"idp-issuer-url": "https://example.org",
159+
},
160+
},
161+
}, nil
162+
}
163+
164+
// Restore the original function when the test completes
165+
defer func() {
166+
InClusterConfig = originalInClusterConfig
167+
}()
168+
169+
// Create a Kubernetes instance with empty kubeconfig to force in-cluster mode
170+
k := &Kubernetes{
171+
Kubeconfig: "",
172+
}
173+
174+
// Call ConfigurationView
175+
configYaml, err := k.ConfigurationView(true)
176+
if err != nil {
177+
t.Fatalf("ConfigurationView failed: %v", err)
178+
}
179+
180+
// Parse the YAML
181+
var config v1.Config
182+
if err := yaml.Unmarshal([]byte(configYaml), &config); err != nil {
183+
t.Fatalf("Failed to parse config YAML: %v", err)
184+
}
185+
186+
// Verify the auth provider is included
187+
if len(config.AuthInfos) != 1 {
188+
t.Fatalf("Expected 1 auth info, got %d", len(config.AuthInfos))
189+
}
190+
191+
authInfo := config.AuthInfos[0]
192+
if authInfo.Name != "user" {
193+
t.Errorf("Expected auth info name to be 'user', got '%s'", authInfo.Name)
194+
}
195+
196+
if authInfo.AuthInfo.AuthProvider == nil {
197+
t.Fatalf("Expected auth provider to be present, got nil")
198+
}
199+
200+
if authInfo.AuthInfo.AuthProvider.Name != "oidc" {
201+
t.Errorf("Expected auth provider name to be 'oidc', got '%s'", authInfo.AuthInfo.AuthProvider.Name)
202+
}
203+
204+
// Verify the auth provider config
205+
authProviderConfig := authInfo.AuthInfo.AuthProvider.Config
206+
expectedKeys := []string{"client-id", "client-secret", "id-token", "refresh-token", "idp-issuer-url"}
207+
for _, key := range expectedKeys {
208+
if value, exists := authProviderConfig[key]; !exists {
209+
t.Errorf("Expected auth provider config to have key '%s', but it was missing", key)
210+
} else if key == "client-id" && value != "test-client" {
211+
t.Errorf("Expected auth provider config key '%s' to have value 'test-client', got '%s'", key, value)
212+
}
213+
}
214+
}
215+
216+
func TestConfigurationViewWithExecProvider(t *testing.T) {
217+
// Save the original InClusterConfig function
218+
originalInClusterConfig := InClusterConfig
219+
220+
// Mock InClusterConfig to return a config with ExecProvider
221+
InClusterConfig = func() (*rest.Config, error) {
222+
return &rest.Config{
223+
Host: "https://kubernetes.default.svc",
224+
ExecProvider: &api.ExecConfig{
225+
Command: "aws",
226+
Args: []string{"eks", "get-token", "--cluster-name", "test-cluster"},
227+
Env: []api.ExecEnvVar{
228+
{
229+
Name: "AWS_PROFILE",
230+
Value: "test-profile",
231+
},
232+
},
233+
APIVersion: "client.authentication.k8s.io/v1beta1",
234+
},
235+
}, nil
236+
}
237+
238+
// Restore the original function when the test completes
239+
defer func() {
240+
InClusterConfig = originalInClusterConfig
241+
}()
242+
243+
// Create a Kubernetes instance with empty kubeconfig to force in-cluster mode
244+
k := &Kubernetes{
245+
Kubeconfig: "",
246+
}
247+
248+
// Call ConfigurationView
249+
configYaml, err := k.ConfigurationView(true)
250+
if err != nil {
251+
t.Fatalf("ConfigurationView failed: %v", err)
252+
}
253+
254+
// Parse the YAML
255+
var config v1.Config
256+
if err := yaml.Unmarshal([]byte(configYaml), &config); err != nil {
257+
t.Fatalf("Failed to parse config YAML: %v", err)
258+
}
259+
260+
// Verify the exec provider is included
261+
if len(config.AuthInfos) != 1 {
262+
t.Fatalf("Expected 1 auth info, got %d", len(config.AuthInfos))
263+
}
264+
265+
authInfo := config.AuthInfos[0]
266+
if authInfo.Name != "user" {
267+
t.Errorf("Expected auth info name to be 'user', got '%s'", authInfo.Name)
268+
}
269+
270+
if authInfo.AuthInfo.Exec == nil {
271+
t.Fatalf("Expected exec provider to be present, got nil")
272+
}
273+
274+
execConfig := authInfo.AuthInfo.Exec
275+
if execConfig.Command != "aws" {
276+
t.Errorf("Expected exec command to be 'aws', got '%s'", execConfig.Command)
277+
}
278+
279+
if len(execConfig.Args) != 4 || execConfig.Args[0] != "eks" || execConfig.Args[1] != "get-token" {
280+
t.Errorf("Expected exec args to be ['eks', 'get-token', '--cluster-name', 'test-cluster'], got %v", execConfig.Args)
281+
}
282+
283+
if len(execConfig.Env) != 1 || execConfig.Env[0].Name != "AWS_PROFILE" || execConfig.Env[0].Value != "test-profile" {
284+
t.Errorf("Expected exec env to have AWS_PROFILE=test-profile, got %v", execConfig.Env)
285+
}
286+
287+
if execConfig.APIVersion != "client.authentication.k8s.io/v1beta1" {
288+
t.Errorf("Expected exec APIVersion to be 'client.authentication.k8s.io/v1beta1', got '%s'", execConfig.APIVersion)
289+
}
290+
}

pkg/kubernetes/kubernetes.go

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

33
import (
44
"context"
5+
56
"github.com/fsnotify/fsnotify"
67
"github.com/manusa/kubernetes-mcp-server/pkg/helm"
78
v1 "k8s.io/api/core/v1"
@@ -131,13 +132,21 @@ func (k *Kubernetes) Derived(ctx context.Context) *Kubernetes {
131132
}
132133
klog.V(5).Infof("%s header found, using provided bearer token", AuthorizationBearerTokenHeader)
133134
derivedCfg := rest.CopyConfig(k.cfg)
134-
derivedCfg.BearerToken = bearerToken
135-
derivedCfg.BearerTokenFile = ""
136-
derivedCfg.Username = ""
137-
derivedCfg.Password = ""
138-
derivedCfg.AuthProvider = nil
139-
derivedCfg.AuthConfigPersister = nil
140-
derivedCfg.ExecProvider = nil
135+
136+
// If we have a bearer token, use it for authentication
137+
if bearerToken != "" {
138+
derivedCfg.BearerToken = bearerToken
139+
derivedCfg.BearerTokenFile = ""
140+
derivedCfg.Username = ""
141+
derivedCfg.Password = ""
142+
143+
// Only clear auth providers if we're using a bearer token
144+
// This preserves OIDC configuration when no token is provided
145+
derivedCfg.AuthProvider = nil
146+
derivedCfg.AuthConfigPersister = nil
147+
derivedCfg.ExecProvider = nil
148+
}
149+
141150
derivedCfg.Impersonate = rest.ImpersonationConfig{}
142151
clientCmdApiConfig, err := k.clientCmdConfig.RawConfig()
143152
if err != nil {

0 commit comments

Comments
 (0)