Skip to content

[SECURITY FEATURE]: Helm Chart - Enterprise Secrets Management Integration (Vault) #542

@crivetimihai

Description

@crivetimihai

🔐 FEATURE: Enterprise Secrets Management Integration

Summary: Integrate enterprise secrets management solutions (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) into the MCP Gateway Helm chart. This feature enables dynamic secret rotation, eliminates hardcoded secrets, and provides centralized secret lifecycle management for production deployments.

  1. Add configurable support to the helm chart
  2. Add optional Vault deployment.
  3. Optional: add support for Vault to docker-compose.yaml to support development & testing.

Implementation

1. Update values.yaml with Secrets Management Configuration

# In helm/mcpgateway/values.yaml

# ... existing configuration ...

# ===================================
# Enterprise Secrets Management
# ===================================
secretsManagement:
  # Enable external secrets management
  enabled: false
  
  # Secret provider: vault, aws, azure, kubernetes
  provider: "vault"
  
  # Secret refresh interval (seconds)
  refreshInterval: 300
  
  # Enable secret rotation
  autoRotation:
    enabled: true
    checkInterval: 3600  # 1 hour
  
  # HashiCorp Vault Configuration
  vault:
    enabled: false
    address: "https://vault.example.com:8200"
    authMethod: "kubernetes"  # kubernetes, token, approle
    role: "mcpgateway"
    mount: "kubernetes"
    namespace: "default"
    secretPath: "secret/data/mcpgateway"
    
    # TLS Configuration
    tls:
      enabled: true
      caCert: ""  # Base64 encoded CA certificate
      skipVerify: false
    
    # Token auth (if authMethod: token)
    token:
      secretName: "vault-token"
      secretKey: "token"
    
    # AppRole auth (if authMethod: approle)
    appRole:
      roleId: ""
      secretId:
        secretName: "vault-approle"
        secretKey: "secret-id"
  
  # AWS Secrets Manager Configuration
  aws:
    enabled: false
    region: "us-east-1"
    secretPrefix: "mcpgateway/"
    
    # Authentication via IRSA or explicit credentials
    auth:
      # Use IRSA (recommended)
      useIRSA: true
      serviceAccountAnnotations: {}
      
      # Or use explicit credentials
      accessKeyId:
        secretName: "aws-credentials"
        secretKey: "access-key-id"
      secretAccessKey:
        secretName: "aws-credentials"
        secretKey: "secret-access-key"
  
  # Azure Key Vault Configuration
  azure:
    enabled: false
    vaultName: "mcpgateway-keyvault"
    tenantId: ""
    
    # Authentication methods
    auth:
      # Use Managed Identity (recommended)
      useManagedIdentity: true
      clientId: ""  # For user-assigned managed identity
      
      # Or use Service Principal
      clientSecret:
        secretName: "azure-credentials"
        secretKey: "client-secret"
  
  # Secret Mappings - which secrets to fetch
  secrets:
    # Database credentials
    database:
      enabled: true
      source: "database/creds"  # Path in secret store
      target: "DATABASE_URL"     # Environment variable name
      format: "postgresql://{{username}}:{{password}}@{{host}}:{{port}}/{{database}}"
      
    # JWT Secret
    jwtSecret:
      enabled: true
      source: "jwt/secret"
      target: "JWT_SECRET"
      
    # OAuth Credentials
    oauth:
      google:
        enabled: false
        clientId:
          source: "oauth/google/client-id"
          target: "GOOGLE_CLIENT_ID"
        clientSecret:
          source: "oauth/google/client-secret"
          target: "GOOGLE_CLIENT_SECRET"
      
      github:
        enabled: false
        clientId:
          source: "oauth/github/client-id"
          target: "GITHUB_CLIENT_ID"
        clientSecret:
          source: "oauth/github/client-secret"
          target: "GITHUB_CLIENT_SECRET"
    
    # API Keys
    apiKeys:
      openai:
        enabled: false
        source: "api-keys/openai"
        target: "OPENAI_API_KEY"
      
      anthropic:
        enabled: false
        source: "api-keys/anthropic"
        target: "ANTHROPIC_API_KEY"
    
    # Custom secrets
    custom: []
    # - name: "custom-secret"
    #   source: "path/to/secret"
    #   target: "CUSTOM_ENV_VAR"
    #   format: "Bearer {{value}}"  # Optional formatting

# External Secrets Operator (if using)
externalSecrets:
  # Install External Secrets Operator as dependency
  enabled: false
  
  # External Secrets Operator configuration
  operator:
    installCRDs: true
    
  # Secret Store configuration
  secretStore:
    name: "mcpgateway-secret-store"
    
  # Refresh interval for ExternalSecret resources
  refreshInterval: "1m"

2. Create Secrets Provider Templates

# Create helm/mcpgateway/templates/secrets-provider.yaml

{{- if .Values.secretsManagement.enabled }}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "mcpgateway.fullname" . }}-secrets-config
  labels:
    {{- include "mcpgateway.labels" . | nindent 4 }}
data:
  provider: {{ .Values.secretsManagement.provider | quote }}
  refresh-interval: {{ .Values.secretsManagement.refreshInterval | quote }}
  auto-rotation-enabled: {{ .Values.secretsManagement.autoRotation.enabled | quote }}
  
  {{- if eq .Values.secretsManagement.provider "vault" }}
  vault-address: {{ .Values.secretsManagement.vault.address | quote }}
  vault-auth-method: {{ .Values.secretsManagement.vault.authMethod | quote }}
  vault-role: {{ .Values.secretsManagement.vault.role | quote }}
  vault-mount: {{ .Values.secretsManagement.vault.mount | quote }}
  vault-namespace: {{ .Values.secretsManagement.vault.namespace | quote }}
  vault-secret-path: {{ .Values.secretsManagement.vault.secretPath | quote }}
  {{- end }}
  
  {{- if eq .Values.secretsManagement.provider "aws" }}
  aws-region: {{ .Values.secretsManagement.aws.region | quote }}
  aws-secret-prefix: {{ .Values.secretsManagement.aws.secretPrefix | quote }}
  {{- end }}
  
  {{- if eq .Values.secretsManagement.provider "azure" }}
  azure-vault-name: {{ .Values.secretsManagement.azure.vaultName | quote }}
  azure-tenant-id: {{ .Values.secretsManagement.azure.tenantId | quote }}
  {{- end }}

---
{{- if .Values.externalSecrets.enabled }}
# External Secrets Operator SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: {{ include "mcpgateway.fullname" . }}-secret-store
  labels:
    {{- include "mcpgateway.labels" . | nindent 4 }}
spec:
  provider:
    {{- if eq .Values.secretsManagement.provider "vault" }}
    vault:
      server: {{ .Values.secretsManagement.vault.address | quote }}
      path: {{ .Values.secretsManagement.vault.secretPath | quote }}
      version: "v2"
      {{- if .Values.secretsManagement.vault.namespace }}
      namespace: {{ .Values.secretsManagement.vault.namespace | quote }}
      {{- end }}
      auth:
        {{- if eq .Values.secretsManagement.vault.authMethod "kubernetes" }}
        kubernetes:
          mountPath: {{ .Values.secretsManagement.vault.mount | quote }}
          role: {{ .Values.secretsManagement.vault.role | quote }}
          serviceAccountRef:
            name: {{ include "mcpgateway.serviceAccountName" . }}
        {{- else if eq .Values.secretsManagement.vault.authMethod "token" }}
        tokenSecretRef:
          name: {{ .Values.secretsManagement.vault.token.secretName | quote }}
          key: {{ .Values.secretsManagement.vault.token.secretKey | quote }}
        {{- else if eq .Values.secretsManagement.vault.authMethod "approle" }}
        appRole:
          path: "approle"
          roleId: {{ .Values.secretsManagement.vault.appRole.roleId | quote }}
          secretRef:
            name: {{ .Values.secretsManagement.vault.appRole.secretId.secretName | quote }}
            key: {{ .Values.secretsManagement.vault.appRole.secretId.secretKey | quote }}
        {{- end }}
      {{- if .Values.secretsManagement.vault.tls.enabled }}
      caBundle: {{ .Values.secretsManagement.vault.tls.caCert | quote }}
      {{- end }}
    {{- else if eq .Values.secretsManagement.provider "aws" }}
    aws:
      service: SecretsManager
      region: {{ .Values.secretsManagement.aws.region | quote }}
      {{- if not .Values.secretsManagement.aws.auth.useIRSA }}
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: {{ .Values.secretsManagement.aws.auth.accessKeyId.secretName | quote }}
            key: {{ .Values.secretsManagement.aws.auth.accessKeyId.secretKey | quote }}
          secretAccessKeySecretRef:
            name: {{ .Values.secretsManagement.aws.auth.secretAccessKey.secretName | quote }}
            key: {{ .Values.secretsManagement.aws.auth.secretAccessKey.secretKey | quote }}
      {{- end }}
    {{- else if eq .Values.secretsManagement.provider "azure" }}
    azurekv:
      vaultUrl: "https://{{ .Values.secretsManagement.azure.vaultName }}.vault.azure.net"
      tenantId: {{ .Values.secretsManagement.azure.tenantId | quote }}
      authType: {{ if .Values.secretsManagement.azure.auth.useManagedIdentity }}"ManagedIdentity"{{ else }}"ServicePrincipal"{{ end }}
      {{- if .Values.secretsManagement.azure.auth.useManagedIdentity }}
      identityId: {{ .Values.secretsManagement.azure.auth.clientId | quote }}
      {{- else }}
      authSecretRef:
        clientId:
          name: {{ .Values.secretsManagement.azure.auth.clientSecret.secretName | quote }}
          key: "client-id"
        clientSecret:
          name: {{ .Values.secretsManagement.azure.auth.clientSecret.secretName | quote }}
          key: {{ .Values.secretsManagement.azure.auth.clientSecret.secretKey | quote }}
      {{- end }}
    {{- end }}

---
# External Secret Resources
{{- if .Values.secretsManagement.secrets.database.enabled }}
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: {{ include "mcpgateway.fullname" . }}-database-secret
  labels:
    {{- include "mcpgateway.labels" . | nindent 4 }}
spec:
  refreshInterval: {{ .Values.externalSecrets.refreshInterval }}
  secretStoreRef:
    name: {{ include "mcpgateway.fullname" . }}-secret-store
    kind: SecretStore
  target:
    name: {{ include "mcpgateway.fullname" . }}-database-secret
    creationPolicy: Owner
  data:
  - secretKey: database-url
    remoteRef:
      key: {{ .Values.secretsManagement.secrets.database.source | quote }}
{{- end }}

{{- if .Values.secretsManagement.secrets.jwtSecret.enabled }}
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: {{ include "mcpgateway.fullname" . }}-jwt-secret
  labels:
    {{- include "mcpgateway.labels" . | nindent 4 }}
spec:
  refreshInterval: {{ .Values.externalSecrets.refreshInterval }}
  secretStoreRef:
    name: {{ include "mcpgateway.fullname" . }}-secret-store
    kind: SecretStore
  target:
    name: {{ include "mcpgateway.fullname" . }}-jwt-secret
    creationPolicy: Owner
  data:
  - secretKey: jwt-secret
    remoteRef:
      key: {{ .Values.secretsManagement.secrets.jwtSecret.source | quote }}
{{- end }}

{{- range .Values.secretsManagement.secrets.custom }}
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: {{ include "mcpgateway.fullname" $ }}-{{ .name }}
  labels:
    {{- include "mcpgateway.labels" $ | nindent 4 }}
spec:
  refreshInterval: {{ $.Values.externalSecrets.refreshInterval }}
  secretStoreRef:
    name: {{ include "mcpgateway.fullname" $ }}-secret-store
    kind: SecretStore
  target:
    name: {{ include "mcpgateway.fullname" $ }}-{{ .name }}
    creationPolicy: Owner
  data:
  - secretKey: value
    remoteRef:
      key: {{ .source | quote }}
{{- end }}

{{- else }}
# Native secret management init container will handle secrets
{{- end }}
{{- end }}

3. Create Init Container for Secret Injection

# Create helm/mcpgateway/templates/secret-injector-configmap.yaml

{{- if and .Values.secretsManagement.enabled (not .Values.externalSecrets.enabled) }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "mcpgateway.fullname" . }}-secret-injector
  labels:
    {{- include "mcpgateway.labels" . | nindent 4 }}
data:
  secret-injector.py: |
    #!/usr/bin/env python3
    import os
    import sys
    import json
    import time
    import base64
    from typing import Dict, Any
    
    # Provider implementations
    class VaultProvider:
        def __init__(self, config: Dict[str, str]):
            import hvac
            self.client = hvac.Client(
                url=config['vault-address'],
                namespace=config.get('vault-namespace')
            )
            
            auth_method = config['vault-auth-method']
            if auth_method == 'kubernetes':
                with open('/var/run/secrets/kubernetes.io/serviceaccount/token', 'r') as f:
                    jwt = f.read()
                self.client.auth.kubernetes.login(
                    role=config['vault-role'],
                    jwt=jwt,
                    mount_point=config['vault-mount']
                )
            elif auth_method == 'token':
                self.client.token = os.environ.get('VAULT_TOKEN')
        
        def get_secret(self, path: str) -> Dict[str, Any]:
            response = self.client.secrets.kv.v2.read_secret_version(
                path=path,
                mount_point=self.config['vault-secret-path'].split('/')[0]
            )
            return response['data']['data']
    
    class AWSProvider:
        def __init__(self, config: Dict[str, str]):
            import boto3
            self.client = boto3.client(
                'secretsmanager',
                region_name=config['aws-region']
            )
            self.prefix = config['aws-secret-prefix']
        
        def get_secret(self, name: str) -> str:
            secret_name = f"{self.prefix}{name}"
            response = self.client.get_secret_value(SecretId=secret_name)
            return json.loads(response['SecretString'])
    
    class AzureProvider:
        def __init__(self, config: Dict[str, str]):
            from azure.keyvault.secrets import SecretClient
            from azure.identity import DefaultAzureCredential
            
            vault_url = f"https://{config['azure-vault-name']}.vault.azure.net"
            credential = DefaultAzureCredential()
            self.client = SecretClient(vault_url=vault_url, credential=credential)
        
        def get_secret(self, name: str) -> str:
            secret = self.client.get_secret(name)
            return secret.value
    
    # Main execution
    def main():
        # Load configuration
        config = {}
        config_dir = '/etc/mcpgateway/secrets-config'
        for filename in os.listdir(config_dir):
            with open(os.path.join(config_dir, filename), 'r') as f:
                config[filename] = f.read().strip()
        
        # Initialize provider
        provider_name = config['provider']
        if provider_name == 'vault':
            provider = VaultProvider(config)
        elif provider_name == 'aws':
            provider = AWSProvider(config)
        elif provider_name == 'azure':
            provider = AzureProvider(config)
        else:
            raise ValueError(f"Unknown provider: {provider_name}")
        
        # Load secret mappings
        with open('/etc/mcpgateway/secret-mappings/mappings.json', 'r') as f:
            mappings = json.load(f)
        
        # Fetch and write secrets
        env_vars = {}
        for mapping in mappings:
            if mapping.get('enabled', True):
                try:
                    secret_value = provider.get_secret(mapping['source'])
                    
                    # Format if needed
                    if 'format' in mapping:
                        import re
                        formatted = mapping['format']
                        for key, value in secret_value.items():
                            formatted = formatted.replace(f"{{{{{key}}}}}", str(value))
                        env_vars[mapping['target']] = formatted
                    else:
                        env_vars[mapping['target']] = secret_value
                    
                    print(f"✓ Fetched secret: {mapping['source']}")
                except Exception as e:
                    print(f"✗ Failed to fetch {mapping['source']}: {e}")
                    if mapping.get('required', True):
                        sys.exit(1)
        
        # Write to shared volume
        with open('/shared/secrets/env', 'w') as f:
            for key, value in env_vars.items():
                f.write(f"export {key}='{value}'\n")
        
        print(f"Successfully injected {len(env_vars)} secrets")
    
    if __name__ == '__main__':
        main()
{{- end }}

4. Update Deployment to Use Secrets

# Update helm/mcpgateway/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "mcpgateway.fullname" . }}
  labels:
    {{- include "mcpgateway.labels" . | nindent 4 }}
spec:
  # ... existing spec ...
  template:
    metadata:
      annotations:
        {{- if .Values.secretsManagement.enabled }}
        checksum/secrets-config: {{ include (print $.Template.BasePath "/secrets-provider.yaml") . | sha256sum }}
        {{- end }}
    spec:
      {{- if .Values.secretsManagement.enabled }}
      serviceAccountName: {{ include "mcpgateway.serviceAccountName" . }}
      {{- end }}
      
      {{- if and .Values.secretsManagement.enabled (not .Values.externalSecrets.enabled) }}
      initContainers:
      - name: secret-injector
        image: python:3.11-slim
        command: ["/bin/bash", "-c"]
        args:
          - |
            pip install hvac boto3 azure-keyvault-secrets azure-identity
            python /scripts/secret-injector.py
        env:
          {{- if eq .Values.secretsManagement.provider "vault" }}
          {{- if eq .Values.secretsManagement.vault.authMethod "token" }}
          - name: VAULT_TOKEN
            valueFrom:
              secretKeyRef:
                name: {{ .Values.secretsManagement.vault.token.secretName }}
                key: {{ .Values.secretsManagement.vault.token.secretKey }}
          {{- end }}
          {{- end }}
        volumeMounts:
        - name: secret-injector-script
          mountPath: /scripts
        - name: secrets-config
          mountPath: /etc/mcpgateway/secrets-config
        - name: secret-mappings
          mountPath: /etc/mcpgateway/secret-mappings
        - name: shared-secrets
          mountPath: /shared/secrets
      {{- end }}
      
      containers:
      - name: {{ .Chart.Name }}
        # ... existing container spec ...
        
        {{- if .Values.secretsManagement.enabled }}
        {{- if .Values.externalSecrets.enabled }}
        # Use External Secrets
        envFrom:
        {{- if .Values.secretsManagement.secrets.database.enabled }}
        - secretRef:
            name: {{ include "mcpgateway.fullname" . }}-database-secret
        {{- end }}
        {{- if .Values.secretsManagement.secrets.jwtSecret.enabled }}
        - secretRef:
            name: {{ include "mcpgateway.fullname" . }}-jwt-secret
        {{- end }}
        {{- range .Values.secretsManagement.secrets.custom }}
        - secretRef:
            name: {{ include "mcpgateway.fullname" $ }}-{{ .name }}
        {{- end }}
        {{- else }}
        # Use init container injected secrets
        command: ["/bin/bash", "-c"]
        args:
          - |
            source /shared/secrets/env
            exec /app/mcpgateway
        volumeMounts:
        - name: shared-secrets
          mountPath: /shared/secrets
          readOnly: true
        {{- end }}
        {{- end }}
      
      volumes:
      {{- if and .Values.secretsManagement.enabled (not .Values.externalSecrets.enabled) }}
      - name: secret-injector-script
        configMap:
          name: {{ include "mcpgateway.fullname" . }}-secret-injector
          defaultMode: 0755
      - name: secrets-config
        configMap:
          name: {{ include "mcpgateway.fullname" . }}-secrets-config
      - name: secret-mappings
        configMap:
          name: {{ include "mcpgateway.fullname" . }}-secret-mappings
      - name: shared-secrets
        emptyDir: {}
      {{- end }}

5. Create ServiceAccount with Proper Permissions

# Update helm/mcpgateway/templates/serviceaccount.yaml

{{- if and .Values.serviceAccount.create .Values.secretsManagement.enabled }}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "mcpgateway.serviceAccountName" . }}
  labels:
    {{- include "mcpgateway.labels" . | nindent 4 }}
  {{- with .Values.serviceAccount.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
  {{- if and .Values.secretsManagement.enabled (eq .Values.secretsManagement.provider "aws") .Values.secretsManagement.aws.auth.useIRSA }}
  annotations:
    eks.amazonaws.com/role-arn: {{ .Values.secretsManagement.aws.auth.serviceAccountAnnotations.roleArn }}
  {{- end }}
  {{- if and .Values.secretsManagement.enabled (eq .Values.secretsManagement.provider "azure") .Values.secretsManagement.azure.auth.useManagedIdentity }}
  annotations:
    azure.workload.identity/client-id: {{ .Values.secretsManagement.azure.auth.clientId }}
  {{- end }}
automountServiceAccountToken: true
{{- end }}

6. Add Chart Dependencies

# Update helm/mcpgateway/Chart.yaml

apiVersion: v2
name: mcpgateway
description: Enterprise MCP Gateway with Secrets Management
type: application
version: 0.8.0
appVersion: "0.8.0"

dependencies:
  {{- if .Values.externalSecrets.enabled }}
  - name: external-secrets
    version: "0.9.x"
    repository: https://charts.external-secrets.io
    condition: externalSecrets.enabled
  {{- end }}

7. Create Examples Directory

# Create helm/mcpgateway/examples/secrets-management/vault-example.yaml

secretsManagement:
  enabled: true
  provider: "vault"
  refreshInterval: 300
  
  vault:
    enabled: true
    address: "https://vault.mycompany.com:8200"
    authMethod: "kubernetes"
    role: "mcpgateway-prod"
    mount: "kubernetes-prod"
    namespace: "production"
    secretPath: "secret/data/mcpgateway"
    
    tls:
      enabled: true
      caCert: |
        LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
  
  secrets:
    database:
      enabled: true
      source: "database/prod/creds"
      target: "DATABASE_URL"
      format: "postgresql://{{username}}:{{password}}@prod-db.internal:5432/mcpgateway"
    
    jwtSecret:
      enabled: true
      source: "security/jwt-secret"
      target: "JWT_SECRET"
# Create helm/mcpgateway/examples/secrets-management/aws-example.yaml

secretsManagement:
  enabled: true
  provider: "aws"
  
  aws:
    enabled: true
    region: "us-west-2"
    secretPrefix: "prod/mcpgateway/"
    
    auth:
      useIRSA: true
      serviceAccountAnnotations:
        roleArn: "arn:aws:iam::123456789012:role/mcpgateway-secrets-reader"
  
  secrets:
    database:
      enabled: true
      source: "rds-credentials"
      target: "DATABASE_URL"

8. Create Secret Mappings ConfigMap

# Create helm/mcpgateway/templates/secret-mappings-configmap.yaml

{{- if and .Values.secretsManagement.enabled (not .Values.externalSecrets.enabled) }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "mcpgateway.fullname" . }}-secret-mappings
  labels:
    {{- include "mcpgateway.labels" . | nindent 4 }}
data:
  mappings.json: |
    [
      {{- $first := true }}
      {{- range $key, $value := .Values.secretsManagement.secrets }}
      {{- if and (kindIs "map" $value) $value.enabled }}
      {{- if not $first }},{{- end }}
      {{- $first = false }}
      {
        "name": {{ $key | quote }},
        "source": {{ $value.source | quote }},
        "target": {{ $value.target | quote }}
        {{- if $value.format }},
        "format": {{ $value.format | quote }}
        {{- end }}
        {{- if hasKey $value "required" }},
        "required": {{ $value.required }}
        {{- end }}
      }
      {{- else if kindIs "map" $value }}
      {{- range $subKey, $subValue := $value }}
      {{- if and (kindIs "map" $subValue) $subValue.enabled }}
      {{- if not $first }},{{- end }}
      {{- $first = false }}
      {
        "name": {{ printf "%s-%s" $key $subKey | quote }},
        "source": {{ $subValue.source | quote }},
        "target": {{ $subValue.target | quote }}
        {{- if $subValue.format }},
        "format": {{ $subValue.format | quote }}
        {{- end }}
      }
      {{- end }}
      {{- end }}
      {{- end }}
      {{- end }}
      {{- range .Values.secretsManagement.secrets.custom }}
      {{- if not $first }},{{- end }}
      {{- $first = false }}
      {
        "name": {{ .name | quote }},
        "source": {{ .source | quote }},
        "target": {{ .target | quote }}
        {{- if .format }},
        "format": {{ .format | quote }}
        {{- end }}
      }
      {{- end }}
    ]
{{- end }}

Testing

1. Test with HashiCorp Vault

# Deploy with Vault integration
helm upgrade --install mcpgateway ./helm/mcpgateway \
  -f examples/secrets-management/vault-example.yaml \
  --namespace mcpgateway

# Verify secrets are injected
kubectl exec -n mcpgateway deployment/mcpgateway -- env | grep -E "(DATABASE_URL|JWT_SECRET)"

2. Test with AWS Secrets Manager

# Create IRSA role first
eksctl create iamserviceaccount \
  --cluster=my-cluster \
  --namespace=mcpgateway \
  --name=mcpgateway \
  --role-name=MCPGatewaySecretsReader \
  --attach-policy-arn=arn:aws:iam::aws:policy/SecretsManagerReadWrite \
  --approve

# Deploy with AWS integration
helm upgrade --install mcpgateway ./helm/mcpgateway \
  -f examples/secrets-management/aws-example.yaml

3. Test Secret Rotation

# Update secret in provider
vault kv put secret/mcpgateway/jwt-secret value="new-secret-value"

# Wait for refresh interval
sleep 300

# Verify pod restarted with new secret
kubectl get pods -n mcpgateway -w

Documentation

README Addition

## Enterprise Secrets Management

MCP Gateway supports integration with enterprise secret management solutions:

- **HashiCorp Vault**: Dynamic secrets with automatic rotation
- **AWS Secrets Manager**: Native AWS integration with IRSA support
- **Azure Key Vault**: Managed identity and service principal auth
- **External Secrets Operator**: Kubernetes-native secret synchronization

### Quick Start

1. **Enable secrets management**:
   ```yaml
   secretsManagement:
     enabled: true
     provider: "vault"  # or "aws", "azure"
  1. Configure your provider (see examples directory)

  2. Map your secrets:

    secrets:
      database:
        enabled: true
        source: "path/to/secret"
        target: "DATABASE_URL"

Benefits

  • No hardcoded secrets: All sensitive data stored in external systems
  • Automatic rotation: Secrets refreshed without pod restarts
  • Audit trail: Complete visibility of secret access
  • Compliance: Meet enterprise security requirements

## Security Benefits

1. **Centralized Management**: Single source of truth for all secrets
2. **Automatic Rotation**: Secrets updated without manual intervention
3. **Audit Compliance**: Full audit trail of secret access
4. **Zero Trust**: No secrets stored in Kubernetes or container images
5. **Fine-grained Access**: Role-based secret access policies
6. **Encryption at Rest**: Enterprise-grade encryption for stored secrets

Metadata

Metadata

Assignees

Labels

devopsDevOps activities (containers, automation, deployment, makefiles, etc)enhancementNew feature or requestsecurityImproves securitytriageIssues / Features awaiting triage

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions