Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ _output/
.vscode/
.docusaurus/
node_modules/
.DS_Store

config-dev.toml
.npmrc
kubernetes-mcp-server
!charts/kubernetes-mcp-server
Expand Down
177 changes: 177 additions & 0 deletions docs/AUTH_HEADERS_PROVIDER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Auth-Headers Provider

The `auth-headers` cluster provider strategy enables multi-tenant Kubernetes MCP server deployments where each request provides complete cluster connection details and authentication via HTTP headers or MCP tool parameters.

## Overview

This provider:
- **Requires cluster connection details per request** via custom headers (server URL, CA certificate)
- **Requires authentication per request** via bearer token OR client certificates
- **Does not use kubeconfig** - all configuration comes from request headers
- **Creates dynamic Kubernetes clients** per request using the provided credentials

## Use Cases

- **Multi-tenant SaaS deployments** - Single MCP server instance serving multiple users/clusters
- **Zero-trust architectures** - No stored credentials, complete authentication per request
- **Dynamic cluster access** - Connect to different clusters without server configuration
- **Auditing & compliance** - Each request uses the user's actual identity for Kubernetes RBAC
- **Temporary access** - Short-lived credentials without persistent configuration

## Configuration

### Basic Setup

```bash
kubernetes-mcp-server \
--port 8080 \
--cluster-provider-strategy auth-headers
```

The server will:
1. Accept requests with cluster connection details in headers
2. Create a Kubernetes client dynamically for each request
3. Reject any requests without required authentication headers

### TOML Configuration

```toml
cluster_provider_strategy = "auth-headers"
# No kubeconfig needed - all details come from request headers
```

### Required Headers

Each request must include the following custom headers:

**Required for all requests:**
- `kubernetes-server` - Kubernetes API server URL (e.g., `https://kubernetes.example.com:6443`)
- `kubernetes-certificate-authority-data` - Base64-encoded CA certificate

**Authentication (choose one):**

Option 1: Bearer Token
- `kubernetes-authorization` - Bearer token (e.g., `Bearer eyJhbGci...`)

Option 2: Client Certificate
- `kubernetes-client-certificate-data` - Base64-encoded client certificate
- `kubernetes-client-key-data` - Base64-encoded client key

**Optional:**
- `kubernetes-insecure-skip-tls-verify` - Set to `true` to skip TLS verification (not recommended for production)

## How It Works

### 1. Initialization

When the server starts:
```
Server starts with auth-headers provider
No kubeconfig or credentials loaded
Ready to accept requests with headers
```

### 2. Request Processing

For each MCP request:
```
HTTP Request with custom headers
Extract kubernetes-server, kubernetes-certificate-authority-data
Extract authentication (token OR client cert/key)
Create K8sAuthHeaders struct
Build rest.Config dynamically
Create new Kubernetes client
Execute Kubernetes operation
Discard client after request
```

### 3. Header Extraction

Headers can be provided in two ways:

**A. HTTP Request Headers** (standard way):
```
POST /mcp HTTP/1.1
kubernetes-server: https://k8s.example.com:6443
kubernetes-certificate-authority-data: LS0tLS1CRUdJ...
kubernetes-authorization: Bearer eyJhbGci...
```

**B. MCP Tool Parameters Meta** (advanced):
```json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "pods_list",
"arguments": {"namespace": "default"},
"_meta": {
"kubernetes-server": "https://k8s.example.com:6443",
"kubernetes-certificate-authority-data": "LS0tLS1CRUdJ...",
"kubernetes-authorization": "Bearer eyJhbGci..."
}
}
}
```

### 4. Security Model

```
┌──────────────────┐
│ MCP Client │
│ (Claude, etc) │
└────────┬─────────┘
│ All cluster info + auth in headers
┌──────────────────┐
│ MCP Server │
│ (auth-headers) │
│ NO CREDENTIALS │
│ STORED │
└────────┬─────────┘
│ Creates temporary client
┌──────────────────┐
│ Kubernetes API │
│ Server │
└──────────────────┘
RBAC enforced with
credentials from headers
```

## Client Usage

### Using the Go MCP Client

```go
import (
"encoding/base64"
"github.com/mark3labs/mcp-go/client/transport"
)

// Get cluster connection details
serverURL := "https://k8s.example.com:6443"
caCert := getCAcertificate() // PEM-encoded CA certificate
token := getUserKubernetesToken()

// Encode CA certificate to base64
caCertBase64 := base64.StdEncoding.EncodeToString(caCert)

client := NewMCPClient(
transport.WithHTTPHeaders(map[string]string{
"kubernetes-server": serverURL,
"kubernetes-certificate-authority-data": caCertBase64,
"kubernetes-authorization": "Bearer " + token,
})
)
```
7 changes: 4 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import (
)

const (
ClusterProviderKubeConfig = "kubeconfig"
ClusterProviderInCluster = "in-cluster"
ClusterProviderDisabled = "disabled"
ClusterProviderKubeConfig = "kubeconfig"
ClusterProviderInCluster = "in-cluster"
ClusterProviderAuthHeaders = "auth-headers"
ClusterProviderDisabled = "disabled"
)

// StaticConfig is the configuration for the server.
Expand Down
108 changes: 108 additions & 0 deletions pkg/kubernetes/auth_headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package kubernetes

import (
"encoding/base64"
"fmt"
"strings"
)

// AuthType represents the type of Kubernetes authentication.
type AuthType string
type ContextKey string

const (
// AuthHeadersContextKey is the context key for the Kubernetes authentication headers.
AuthHeadersContextKey ContextKey = "k8s_auth_headers"
)

// K8sAuthHeaders represents Kubernetes API authentication headers.
type K8sAuthHeaders struct {
// Server is the Kubernetes cluster URL.
Server string
// ClusterCertificateAuthorityData is the Certificate Authority data.
CertificateAuthorityData []byte
// AuthorizationToken is the optional bearer token for authentication.
AuthorizationToken string
// ClientCertificateData is the optional client certificate data.
ClientCertificateData []byte
// ClientKeyData is the optional client key data.
ClientKeyData []byte
// InsecureSkipTLSVerify is the optional flag to skip TLS verification.
InsecureSkipTLSVerify bool
}

// GetDecodedData decodes and returns the data.
func GetDecodedData(data string) ([]byte, error) {
return base64.StdEncoding.DecodeString(data)
}

// NewK8sAuthHeadersFromHeaders creates a new K8sAuthHeaders from the provided headers.
func NewK8sAuthHeadersFromHeaders(data map[string]any) (*K8sAuthHeaders, error) {
var ok bool
var err error

// Initialize auth headers with default values.
authHeaders := &K8sAuthHeaders{
InsecureSkipTLSVerify: false,
}

// Get cluster URL from headers.
authHeaders.Server, ok = data[string(CustomServerHeader)].(string)
if !ok || authHeaders.Server == "" {
return nil, fmt.Errorf("%s header is required", CustomServerHeader)
}

// Get certificate authority data from headers.
certificateAuthorityDataBase64, ok := data[string(CustomCertificateAuthorityDataHeader)].(string)
if !ok || certificateAuthorityDataBase64 == "" {
return nil, fmt.Errorf("%s header is required", CustomCertificateAuthorityDataHeader)
}
// Decode certificate authority data.
authHeaders.CertificateAuthorityData, err = GetDecodedData(certificateAuthorityDataBase64)
if err != nil {
return nil, fmt.Errorf("invalid certificate authority data: %w", err)
}

// Get insecure skip TLS verify flag from headers.
if data[string(CustomInsecureSkipTLSVerifyHeader)] != nil && strings.ToLower(data[string(CustomInsecureSkipTLSVerifyHeader)].(string)) == "true" {
authHeaders.InsecureSkipTLSVerify = true
}

// Get authorization token from headers.
authHeaders.AuthorizationToken, _ = data[string(CustomAuthorizationHeader)].(string)

// Get client certificate data from headers.
clientCertificateDataBase64, _ := data[string(CustomClientCertificateDataHeader)].(string)
if clientCertificateDataBase64 != "" {
authHeaders.ClientCertificateData, err = GetDecodedData(clientCertificateDataBase64)
if err != nil {
return nil, fmt.Errorf("invalid client certificate data: %w", err)
}
}
// Get client key data from headers.
clientKeyDataBase64, _ := data[string(CustomClientKeyDataHeader)].(string)
if clientKeyDataBase64 != "" {
authHeaders.ClientKeyData, err = GetDecodedData(clientKeyDataBase64)
if err != nil {
return nil, fmt.Errorf("invalid client key data: %w", err)
}
}

// Check if a valid authentication type is provided.
if !authHeaders.IsValid() {
return nil, fmt.Errorf("either %s header for token authentication or (%s and %s) headers for client certificate authentication required", CustomAuthorizationHeader, CustomClientCertificateDataHeader, CustomClientKeyDataHeader)
}

return authHeaders, nil
}

// IsValid checks if the authentication headers are valid.
func (h *K8sAuthHeaders) IsValid() bool {
if h.AuthorizationToken != "" {
return true
}
if len(h.ClientCertificateData) > 0 && len(h.ClientKeyData) > 0 {
return true
}
return false
}
Loading