Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
288d275
feat(specs): credential sidecar isolation architecture
May 20, 2026
1cc2dc6
feat(control-plane): add per-credential sidecar injection
May 21, 2026
f1f070a
feat(runner): SSE sidecar transport for credential MCP servers
May 21, 2026
b018062
feat(runner): MCP-tool-only git push prompts in sidecar mode
May 21, 2026
0ab4da9
feat: credential sidecar Dockerfiles and build targets
May 21, 2026
e373777
test(control-plane): credential sidecar injection unit tests
May 21, 2026
a29f2b1
feat: credential sidecar token refresh via CP token exchange
May 21, 2026
eb027eb
chore: cleanup built artifacts and gitignore
May 21, 2026
0a96c00
fix: credential sidecar security hardening and end-to-end validation
May 21, 2026
469ca40
fix: migration ordering for role_bindings and pr-test standard OpenSh…
May 21, 2026
8ed5d4d
fix: add AMBIENT_API_TOKEN to api-server in install-standard.sh
May 21, 2026
d0ecdc6
feat: support ANTHROPIC_API_KEY in install-standard.sh
May 21, 2026
fa13c83
feat: add Vertex AI support to install-standard.sh
May 21, 2026
0e29da7
fix: make credential project_id optional in API schema
May 21, 2026
68bea15
feat: add --scope-id shorthand flag for role-binding creation
May 21, 2026
0c7e419
fix: add project-scoped credential routes and fix RBAC path resolution
May 21, 2026
5ef445f
fix: increase SSE scanner buffer to 1MB to prevent stream errors
May 21, 2026
37fefc9
fix: credential sidecar security hardening and end-to-end validation
May 22, 2026
6d05767
fix: address review bot feedback on credential sidecar entrypoint and…
May 22, 2026
9184faf
fix: use PtrString for *string ProjectId field in credential integrat…
May 22, 2026
ee898e6
feat: add PodStatusSyncer to reflect pod phase back to session API
May 23, 2026
9173526
fix: adjust go module replace path in credential sidecar Dockerfiles
May 23, 2026
8823a9a
fix: base64-decode kubeconfig token from Credentials API
May 23, 2026
b18a2d5
fix: pass --kubeconfig flag to kubernetes-mcp-server
May 23, 2026
3de064e
fix: skip credential fetching for sidecar-handled providers
May 23, 2026
942edcc
fix: address PR #1599 security audit and code review findings
May 26, 2026
029554b
Merge branch 'main' into credential-mcp-sidecar
mergify[bot] May 26, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,4 @@ hack/

# Personal exports
*.csv
components/credential-sidecars/entrypoint/credential-entrypoint
31 changes: 31 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ STATE_SYNC_IMAGE ?= vteam_state_sync:$(IMAGE_TAG)
PUBLIC_API_IMAGE ?= vteam_public_api:$(IMAGE_TAG)
API_SERVER_IMAGE ?= vteam_api_server:$(IMAGE_TAG)
OBSERVABILITY_DASHBOARD_IMAGE ?= vteam_observability_dashboard:$(IMAGE_TAG)
GITHUB_MCP_IMAGE ?= vteam_credential_github:$(IMAGE_TAG)
JIRA_MCP_IMAGE ?= vteam_credential_jira:$(IMAGE_TAG)
K8S_MCP_IMAGE ?= vteam_credential_k8s:$(IMAGE_TAG)
GOOGLE_MCP_IMAGE ?= vteam_credential_google:$(IMAGE_TAG)

# kind-local overlay always references localhost/vteam_* images.
# Podman produces this prefix natively; for Docker we tag before loading.
Expand Down Expand Up @@ -221,6 +225,33 @@ build-observability-dashboard: ## Build observability dashboard image
-t $(OBSERVABILITY_DASHBOARD_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Observability dashboard built: $(OBSERVABILITY_DASHBOARD_IMAGE)"

build-credential-sidecars: build-credential-github build-credential-jira build-credential-k8s build-credential-google ## Build all credential sidecar images

build-credential-github: ## Build GitHub credential sidecar image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building GitHub credential sidecar with $(CONTAINER_ENGINE)..."
@$(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
-f components/credential-sidecars/github/Dockerfile \
-t $(GITHUB_MCP_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) GitHub credential sidecar built: $(GITHUB_MCP_IMAGE)"

build-credential-jira: ## Build Jira credential sidecar image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building Jira credential sidecar with $(CONTAINER_ENGINE)..."
@cd components/credential-sidecars/jira && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
-t $(JIRA_MCP_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Jira credential sidecar built: $(JIRA_MCP_IMAGE)"

build-credential-k8s: ## Build K8s credential sidecar image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building K8s credential sidecar with $(CONTAINER_ENGINE)..."
@cd components/credential-sidecars/k8s && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
-t $(K8S_MCP_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) K8s credential sidecar built: $(K8S_MCP_IMAGE)"

build-credential-google: ## Build Google credential sidecar image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building Google credential sidecar with $(CONTAINER_ENGINE)..."
@cd components/credential-sidecars/google && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
-t $(GOOGLE_MCP_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Google credential sidecar built: $(GOOGLE_MCP_IMAGE)"

Comment thread
coderabbitai[bot] marked this conversation as resolved.
build-cli: ## Build acpctl CLI binary
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building acpctl CLI..."
@cd components/ambient-cli && make build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ func runKubeMode(ctx context.Context, cfg *config.ControlPlaneConfig) error {
RunnerImageNamespace: cfg.RunnerImageNamespace,
MCPImage: cfg.MCPImage,
MCPAPIServerURL: cfg.MCPAPIServerURL,
GitHubMCPImage: cfg.GitHubMCPImage,
JiraMCPImage: cfg.JiraMCPImage,
K8sMCPImage: cfg.K8sMCPImage,
GoogleMCPImage: cfg.GoogleMCPImage,
RunnerLogLevel: cfg.RunnerLogLevel,
CPRuntimeNamespace: cfg.CPRuntimeNamespace,
CPTokenURL: cfg.CPTokenURL,
Expand Down
8 changes: 8 additions & 0 deletions components/ambient-control-plane/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type ControlPlaneConfig struct {
RunnerImageNamespace string
MCPImage string
MCPAPIServerURL string
GitHubMCPImage string
JiraMCPImage string
K8sMCPImage string
GoogleMCPImage string
RunnerLogLevel string
ProjectKubeTokenFile string
CPTokenListenAddr string
Expand Down Expand Up @@ -74,6 +78,10 @@ func Load() (*ControlPlaneConfig, error) {
RunnerImageNamespace: os.Getenv("RUNNER_IMAGE_NAMESPACE"),
MCPImage: os.Getenv("MCP_IMAGE"),
MCPAPIServerURL: envOrDefault("MCP_API_SERVER_URL", ""),
GitHubMCPImage: os.Getenv("GITHUB_MCP_IMAGE"),
JiraMCPImage: os.Getenv("JIRA_MCP_IMAGE"),
K8sMCPImage: os.Getenv("K8S_MCP_IMAGE"),
GoogleMCPImage: os.Getenv("GOOGLE_MCP_IMAGE"),
RunnerLogLevel: envOrDefault("RUNNER_LOG_LEVEL", "info"),
ProjectKubeTokenFile: os.Getenv("PROJECT_KUBE_TOKEN_FILE"),
CPTokenListenAddr: envOrDefault("CP_TOKEN_LISTEN_ADDR", ":8080"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,39 @@ const (
mcpSidecarURL = "http://localhost:8090"
)

type credentialSidecarSpec struct {
Name string
ImageField string
Port int64
ProviderEnvs map[string]string
}

var credentialSidecarRegistry = map[string]credentialSidecarSpec{
"github": {
Name: "credential-github",
ImageField: "GitHubMCPImage",
Port: 8091,
ProviderEnvs: map[string]string{
"GITHUB_PERSONAL_ACCESS_TOKEN": "__CREDENTIAL_TOKEN__",
},
},
"jira": {
Name: "credential-jira",
ImageField: "JiraMCPImage",
Port: 8092,
},
"kubeconfig": {
Name: "credential-k8s",
ImageField: "K8sMCPImage",
Port: 8093,
},
"google": {
Name: "credential-google",
ImageField: "GoogleMCPImage",
Port: 8094,
},
}

type KubeReconcilerConfig struct {
RunnerImage string
BackendURL string
Expand All @@ -36,6 +69,10 @@ type KubeReconcilerConfig struct {
RunnerImageNamespace string
MCPImage string
MCPAPIServerURL string
GitHubMCPImage string
JiraMCPImage string
K8sMCPImage string
GoogleMCPImage string
RunnerLogLevel string
CPRuntimeNamespace string
CPTokenURL string
Expand Down Expand Up @@ -451,6 +488,19 @@ func (r *SimpleKubeReconciler) ensurePod(ctx context.Context, namespace string,
r.logger.Info().Str("session_id", session.ID).Msg("MCP sidecar enabled for session")
}

credSidecars, credMCPURLs := r.buildCredentialSidecars(session.ID, credentialIDs)
containers = append(containers, credSidecars...)
if len(credMCPURLs) > 0 {
raw, err := json.Marshal(credMCPURLs)
if err == nil {
containers[0].(map[string]interface{})["env"] = append(
containers[0].(map[string]interface{})["env"].([]interface{}),
envVar("CREDENTIAL_MCP_URLS", string(raw)),
)
}
r.logger.Info().Int("count", len(credSidecars)).Str("session_id", session.ID).Msg("credential sidecars injected")
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

pod := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
Expand Down Expand Up @@ -827,6 +877,106 @@ func boolToStr(b bool) string {
return "false"
}

func (r *SimpleKubeReconciler) credentialSidecarImage(provider string) string {
switch provider {
case "github":
return r.cfg.GitHubMCPImage
case "jira":
return r.cfg.JiraMCPImage
case "kubeconfig":
return r.cfg.K8sMCPImage
case "google":
return r.cfg.GoogleMCPImage
default:
return ""
}
}

func (r *SimpleKubeReconciler) buildCredentialSidecars(sessionID string, credentialIDs map[string]string) ([]interface{}, map[string]string) {
var sidecars []interface{}
mcpURLs := map[string]string{}

for provider := range credentialIDs {
spec, ok := credentialSidecarRegistry[provider]
if !ok {
continue
}
image := r.credentialSidecarImage(provider)
if image == "" {
continue
}

imagePullPolicy := "Always"
if strings.HasPrefix(image, "localhost/") {
imagePullPolicy = "IfNotPresent"
}

env := []interface{}{
envVar("SESSION_ID", sessionID),
envVar("AMBIENT_API_URL", r.cfg.MCPAPIServerURL),
envVar("AMBIENT_CP_TOKEN_URL", r.cfg.CPTokenURL),
envVar("AMBIENT_CP_TOKEN_PUBLIC_KEY", r.cfg.CPTokenPublicKey),
envVar("SSL_CERT_FILE", "/etc/pki/ca-trust/extracted/pem/service-ca.crt"),
}
for k, v := range spec.ProviderEnvs {
env = append(env, envVar(k, v))
}
if r.cfg.HTTPProxy != "" {
env = append(env, envVar("HTTP_PROXY", r.cfg.HTTPProxy))
}
if r.cfg.HTTPSProxy != "" {
env = append(env, envVar("HTTPS_PROXY", r.cfg.HTTPSProxy))
}
if r.cfg.NoProxy != "" {
env = append(env, envVar("NO_PROXY", r.cfg.NoProxy))
}

sidecar := map[string]interface{}{
"name": spec.Name,
"image": image,
"imagePullPolicy": imagePullPolicy,
"ports": []interface{}{
map[string]interface{}{
"name": fmt.Sprintf("cred-%s", provider),
"containerPort": spec.Port,
"protocol": "TCP",
},
},
"env": env,
"volumeMounts": []interface{}{
map[string]interface{}{
"name": "service-ca",
"mountPath": "/etc/pki/ca-trust/extracted/pem/service-ca.crt",
"subPath": "service-ca.crt",
"readOnly": true,
},
},
"resources": map[string]interface{}{
"requests": map[string]interface{}{
"cpu": "100m",
"memory": "128Mi",
},
"limits": map[string]interface{}{
"cpu": "500m",
"memory": "256Mi",
},
},
"securityContext": map[string]interface{}{
"allowPrivilegeEscalation": false,
"capabilities": map[string]interface{}{
"drop": []interface{}{"ALL"},
},
},
}

sidecars = append(sidecars, sidecar)
mcpURLs[provider] = fmt.Sprintf("http://localhost:%d", spec.Port)
r.logger.Debug().Str("provider", provider).Str("image", image).Int64("port", spec.Port).Msg("credential sidecar configured")
}

return sidecars, mcpURLs
}

func (r *SimpleKubeReconciler) buildMCPSidecar(sessionID string) interface{} {
mcpImage := r.cfg.MCPImage
imagePullPolicy := "Always"
Expand Down
Loading
Loading