Skip to content

Commit b93c5cf

Browse files
authored
Merge pull request #408 from varshaprasad96/feat/mtls-defaults
Feat: default mTLS to permissive and enable card discovery
2 parents eed4d27 + ffdc5e5 commit b93c5cf

12 files changed

Lines changed: 102 additions & 37 deletions

File tree

charts/kagenti-operator/crds/agent.kagenti.dev_agentruntimes.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ spec:
118118
type: object
119119
type: object
120120
mtlsMode:
121+
default: permissive
121122
description: |-
122123
MTLSMode selects the mTLS posture between authbridge sidecars on
123124
the proxy-sidecar / lite paths. envoy-sidecar handles transport
@@ -127,8 +128,8 @@ spec:
127128
128129
Three valid values:
129130
130-
disabled Plaintext between sidecars (default).
131-
permissive Inbound: byte-peek listener accepts both TLS and
131+
disabled Plaintext between sidecars.
132+
permissive (default) Inbound: byte-peek listener accepts both TLS and
132133
plaintext on the same port. Outbound: tries TLS,
133134
falls back to plaintext on handshake failure (one-line
134135
WARN log per fallback). Use during rollout.
@@ -137,7 +138,7 @@ spec:
137138
completes.
138139
139140
Resolution: AgentRuntime CR > namespace authbridge-runtime-config
140-
mtls.mode > "disabled". Setting mtlsMode != disabled implicitly
141+
mtls.mode > "permissive". Setting mtlsMode != disabled implicitly
141142
requires SPIRE — the operator auto-enables spire for the workload.
142143
143144
CR-empty vs CR="disabled" are observably different in

kagenti-operator/api/v1alpha1/agentruntime_types.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ type AgentRuntimeSpec struct {
8686
//
8787
// Three valid values:
8888
//
89-
// disabled Plaintext between sidecars (default).
90-
// permissive Inbound: byte-peek listener accepts both TLS and
89+
// disabled Plaintext between sidecars.
90+
// permissive (default) Inbound: byte-peek listener accepts both TLS and
9191
// plaintext on the same port. Outbound: tries TLS,
9292
// falls back to plaintext on handshake failure (one-line
9393
// WARN log per fallback). Use during rollout.
@@ -96,7 +96,7 @@ type AgentRuntimeSpec struct {
9696
// completes.
9797
//
9898
// Resolution: AgentRuntime CR > namespace authbridge-runtime-config
99-
// mtls.mode > "disabled". Setting mtlsMode != disabled implicitly
99+
// mtls.mode > "permissive". Setting mtlsMode != disabled implicitly
100100
// requires SPIRE — the operator auto-enables spire for the workload.
101101
//
102102
// CR-empty vs CR="disabled" are observably different in
@@ -111,6 +111,7 @@ type AgentRuntimeSpec struct {
111111
// process start).
112112
//
113113
// +optional
114+
// +kubebuilder:default=permissive
114115
// +kubebuilder:validation:Enum=disabled;permissive;strict
115116
MTLSMode string `json:"mtlsMode,omitempty"`
116117
}

kagenti-operator/cmd/main.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ func main() {
171171
flag.StringVar(&mlflowCAFile, "mlflow-ca-file", "",
172172
"Path to PEM-encoded CA bundle for MLflow TLS verification (appended to system pool)")
173173

174-
flag.BoolVar(&enableCardDiscovery, "enable-card-discovery", false,
175-
"Enable automatic agent card discovery from AgentRuntime workloads into status.card")
176-
flag.BoolVar(&enableVerifiedFetch, "enable-verified-fetch", false,
177-
"Enable mTLS-authenticated fetch of agent cards via SPIFFE identity")
174+
flag.BoolVar(&enableCardDiscovery, "enable-card-discovery", true,
175+
"Enable automatic agent card discovery from AgentRuntime workloads into status.card (set to false to disable)")
176+
flag.BoolVar(&enableVerifiedFetch, "enable-verified-fetch", true,
177+
"Enable mTLS-authenticated fetch of agent cards via SPIFFE identity (set to false as kill switch)")
178178
flag.StringVar(&verifiedFetchSpiffeSocket, "verified-fetch-spiffe-socket",
179179
"unix:///spiffe-workload-api/spire-agent.sock",
180180
"SPIFFE Workload API socket path for verified fetch")
@@ -237,6 +237,30 @@ func main() {
237237

238238
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
239239

240+
// Startup info logs for defaults that changed in this release.
241+
if enableCardDiscovery {
242+
setupLog.Info("card discovery enabled by default; set --enable-card-discovery=false to disable")
243+
}
244+
if enableVerifiedFetch {
245+
setupLog.Info("verified fetch enabled by default; set --enable-verified-fetch=false to disable")
246+
}
247+
248+
// Deprecation warnings for legacy flags that are now superseded by
249+
// mTLS defaults (permissive mode auto-enables SPIRE and identity binding).
250+
for _, dep := range []struct {
251+
name string
252+
set bool
253+
}{
254+
{"require-a2a-signature", requireA2ASignature},
255+
{"signature-audit-mode", signatureAuditMode},
256+
{"enforce-network-policies", enforceNetworkPolicies},
257+
} {
258+
if dep.set {
259+
setupLog.Info("DEPRECATED: flag is superseded by mTLS permissive default; will be removed in a future release",
260+
"flag", dep.name)
261+
}
262+
}
263+
240264
ctx := ctrl.SetupSignalHandler()
241265

242266
// ========================================

kagenti-operator/config/crd/bases/agent.kagenti.dev_agentruntimes.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ spec:
118118
type: object
119119
type: object
120120
mtlsMode:
121+
default: permissive
121122
description: |-
122123
MTLSMode selects the mTLS posture between authbridge sidecars on
123124
the proxy-sidecar / lite paths. envoy-sidecar handles transport
@@ -127,8 +128,8 @@ spec:
127128
128129
Three valid values:
129130
130-
disabled Plaintext between sidecars (default).
131-
permissive Inbound: byte-peek listener accepts both TLS and
131+
disabled Plaintext between sidecars.
132+
permissive (default) Inbound: byte-peek listener accepts both TLS and
132133
plaintext on the same port. Outbound: tries TLS,
133134
falls back to plaintext on handshake failure (one-line
134135
WARN log per fallback). Use during rollout.
@@ -137,7 +138,7 @@ spec:
137138
completes.
138139
139140
Resolution: AgentRuntime CR > namespace authbridge-runtime-config
140-
mtls.mode > "disabled". Setting mtlsMode != disabled implicitly
141+
mtls.mode > "permissive". Setting mtlsMode != disabled implicitly
141142
requires SPIRE — the operator auto-enables spire for the workload.
142143
143144
CR-empty vs CR="disabled" are observably different in

kagenti-operator/internal/controller/agentruntime_controller.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ const (
6363
// Value is a JSON array of skill names, set by the kagenti backend or the user.
6464
AnnotationSkills = "kagenti.io/skills"
6565

66+
// AnnotationMTLSMode is the annotation applied to PodTemplateSpec to advertise the
67+
// resolved mTLS posture. Read by authbridge sidecars for observability.
68+
AnnotationMTLSMode = "kagenti.io/mtls-mode"
69+
6670
// AnnotationRestartPending marks a Sandbox that was scaled to 0 and needs
6771
// to be scaled back to 1 on the next reconcile cycle. Two-phase restart
6872
// avoids a race with the Sandbox controller's pod-name annotation.
@@ -72,6 +76,7 @@ const (
7276
ConditionTypeReady = "Ready"
7377
ConditionTypeTargetResolved = "TargetResolved"
7478
ConditionTypeConfigResolved = "ConfigResolved"
79+
ConditionTypeMTLSReady = "MTLSReady"
7580

7681
// AnnotationLastCardFetchHash stores the change-detection key used to skip
7782
// redundant card fetches when the workload's pod template has not changed.
@@ -333,6 +338,12 @@ func (r *AgentRuntimeReconciler) applyWorkloadConfig(ctx context.Context, rt *ag
333338

334339
key := types.NamespacedName{Name: ref.Name, Namespace: rt.Namespace}
335340

341+
// Resolve mTLS mode: CR value takes precedence, default to "permissive".
342+
mtlsMode := rt.Spec.MTLSMode
343+
if mtlsMode == "" {
344+
mtlsMode = "permissive"
345+
}
346+
336347
var configHashChanged bool
337348

338349
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
@@ -348,7 +359,8 @@ func (r *AgentRuntimeReconciler) applyWorkloadConfig(ctx context.Context, rt *ag
348359
alreadyConfigured := currentWorkloadLabels[LabelAgentType] == string(rt.Spec.Type) &&
349360
currentWorkloadLabels[LabelManagedBy] == LabelManagedByValue &&
350361
currentPodLabels[LabelAgentType] == string(rt.Spec.Type) &&
351-
currentPodAnnotations[AnnotationConfigHash] == configHash
362+
currentPodAnnotations[AnnotationConfigHash] == configHash &&
363+
currentPodAnnotations[AnnotationMTLSMode] == mtlsMode
352364

353365
if alreadyConfigured {
354366
return nil
@@ -375,19 +387,21 @@ func (r *AgentRuntimeReconciler) applyWorkloadConfig(ctx context.Context, rt *ag
375387
podLabels[LabelAgentType] = string(rt.Spec.Type)
376388
acc.setPodLabels(acc.obj, podLabels)
377389

378-
// Apply config-hash annotation to PodTemplateSpec
390+
// Apply config-hash and mtls-mode annotations to PodTemplateSpec
379391
podAnnotations := acc.getPodAnnotations(acc.obj)
380392
if podAnnotations == nil {
381393
podAnnotations = make(map[string]string)
382394
}
383395
podAnnotations[AnnotationConfigHash] = configHash
396+
podAnnotations[AnnotationMTLSMode] = mtlsMode
384397
acc.setPodAnnotations(acc.obj, podAnnotations)
385398

386399
logger.Info("Applying config to workload",
387400
"workload", ref.Name,
388401
"kind", ref.Kind,
389402
"type", string(rt.Spec.Type),
390-
"configHash", configHash[:12])
403+
"configHash", configHash[:12],
404+
"mtlsMode", mtlsMode)
391405

392406
return r.Update(ctx, acc.obj)
393407
})
@@ -725,11 +739,12 @@ func (r *AgentRuntimeReconciler) handleDeletion(ctx context.Context, rt *agentv1
725739
delete(podLabels, LabelAgentType)
726740
acc.setPodLabels(acc.obj, podLabels)
727741

728-
// Remove kagenti.io/config-hash from PodTemplateSpec pod annotations.
729-
// This triggers the rolling update that replaces existing injected pods,
730-
// and leaves the workload annotation-clean for any future AR.
742+
// Remove kagenti.io/config-hash and kagenti.io/mtls-mode from PodTemplateSpec
743+
// pod annotations. This triggers the rolling update that replaces existing
744+
// injected pods, and leaves the workload annotation-clean for any future AR.
731745
podAnnotations := acc.getPodAnnotations(acc.obj)
732746
delete(podAnnotations, AnnotationConfigHash)
747+
delete(podAnnotations, AnnotationMTLSMode)
733748
acc.setPodAnnotations(acc.obj, podAnnotations)
734749

735750
logger.Info("Removed kagenti labels and config-hash from workload on AgentRuntime deletion",

kagenti-operator/internal/webhook/injector/agentruntime_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ type AgentRuntimeOverrides struct {
5656

5757
// mTLS posture — from .spec.mtlsMode
5858
// Nil = no per-workload override; the namespace's
59-
// authbridge-runtime-config mtls.mode (if set) or "disabled"
59+
// authbridge-runtime-config mtls.mode (if set) or "permissive"
6060
// applies.
6161
MTLSMode *string
6262
}

kagenti-operator/internal/webhook/injector/envoy_template.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,20 @@ func RenderEnvoyConfig(cfg *ResolvedConfig) (string, error) {
7272
return cfg.EnvoyYAML, nil
7373
}
7474

75-
// MTLSEnabled checks both "" and MTLSModeDisabled because
76-
// ResolvedConfig leaves MTLSMode as "" when no source set it
77-
// (CR / namespace ConfigMap / default — see ResolveConfig). The
78-
// resolution chain only fills MTLSMode when something explicitly
79-
// asked for it, so "" means "no opinion → treat as disabled".
75+
// MTLSEnabled: empty string is treated as permissive (mTLS is on
76+
// by default). Only MTLSModeDisabled explicitly disables mTLS.
77+
effectiveMode := cfg.MTLSMode
78+
if effectiveMode == "" {
79+
effectiveMode = MTLSModePermissive
80+
}
8081
data := envoyTemplateData{
8182
AdminPort: cfg.Platform.Proxy.AdminPort,
8283
OutboundPort: cfg.Platform.Proxy.Port,
8384
InboundPort: cfg.Platform.Proxy.InboundProxyPort,
8485
ExtProcPort: defaultExtProcPort,
85-
MTLSEnabled: cfg.MTLSMode != "" && cfg.MTLSMode != MTLSModeDisabled,
86-
MTLSPermissive: cfg.MTLSMode == MTLSModePermissive,
87-
MTLSStrict: cfg.MTLSMode == MTLSModeStrict,
86+
MTLSEnabled: effectiveMode != MTLSModeDisabled,
87+
MTLSPermissive: effectiveMode == MTLSModePermissive,
88+
MTLSStrict: effectiveMode == MTLSModeStrict,
8889
}
8990

9091
var buf bytes.Buffer

kagenti-operator/internal/webhook/injector/envoy_template_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,9 @@ func TestRenderEnvoyConfig_TemplateRendering(t *testing.T) {
7070
}
7171

7272
func TestRenderEnvoyConfig_MTLSDisabled_NoTLSBlocks(t *testing.T) {
73-
// Default / disabled mode — no TLS blocks should render. Locks in
74-
// the existing plaintext shape so a future template edit can't
75-
// silently leak TLS config into pods that didn't ask for it.
76-
for _, mode := range []string{"", MTLSModeDisabled} {
73+
// Explicitly disabled mode — no TLS blocks should render.
74+
// Empty string is now treated as permissive (mTLS on by default).
75+
for _, mode := range []string{MTLSModeDisabled} {
7776
t.Run("mode="+mode, func(t *testing.T) {
7877
cfg := &ResolvedConfig{
7978
Platform: config.CompiledDefaults(),

kagenti-operator/internal/webhook/injector/pod_mutator.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ func (m *PodMutator) InjectAuthBridge(ctx context.Context, podSpec *corev1.PodSp
257257
}
258258
}
259259
if mtlsMode == "" {
260-
mtlsMode = MTLSModeDisabled
260+
mtlsMode = MTLSModePermissive
261261
mtlsSource = "default"
262262
}
263263
// Defense in depth: the CRD enum check rejects unknown values at
@@ -270,10 +270,10 @@ func (m *PodMutator) InjectAuthBridge(ctx context.Context, podSpec *corev1.PodSp
270270
case MTLSModeDisabled, MTLSModePermissive, MTLSModeStrict:
271271
// recognized, keep as-is
272272
default:
273-
mutatorLog.Info("WARN: unrecognized mtlsMode; defaulting to disabled",
273+
mutatorLog.Info("WARN: unrecognized mtlsMode; defaulting to permissive",
274274
"namespace", namespace, "crName", crName,
275275
"unrecognized", mtlsMode, "source", mtlsSource)
276-
mtlsMode = MTLSModeDisabled
276+
mtlsMode = MTLSModePermissive
277277
mtlsSource = "default-invalid-fallback"
278278
}
279279
mutatorLog.Info("resolved mTLS mode",
@@ -516,6 +516,15 @@ func (m *PodMutator) InjectAuthBridge(ctx context.Context, podSpec *corev1.PodSp
516516
))
517517
}
518518

519+
// Set MTLS_MODE env var on the authbridge container so it knows the
520+
// resolved mTLS posture at runtime.
521+
for i := range podSpec.Containers {
522+
if podSpec.Containers[i].Name == AuthBridgeProxyContainerName {
523+
setOrAddEnv(&podSpec.Containers[i], "MTLS_MODE", mtlsMode)
524+
break
525+
}
526+
}
527+
519528
// Inject HTTP_PROXY env vars into all existing app containers
520529
for i := range podSpec.Containers {
521530
c := &podSpec.Containers[i]
@@ -620,6 +629,14 @@ func (m *PodMutator) InjectAuthBridge(ctx context.Context, podSpec *corev1.PodSp
620629
podSpec.Containers = append(podSpec.Containers, builder.BuildEnvoyProxyContainerWithSpireOption(spireEnabled))
621630
}
622631

632+
// Set MTLS_MODE env var on the envoy-sidecar authbridge container.
633+
for i := range podSpec.Containers {
634+
if podSpec.Containers[i].Name == EnvoyProxyContainerName {
635+
setOrAddEnv(&podSpec.Containers[i], "MTLS_MODE", mtlsMode)
636+
break
637+
}
638+
}
639+
623640
if decision.ProxyInit.Inject && !containerExists(podSpec.InitContainers, ProxyInitContainerName) {
624641
outboundExclude := annotations[OutboundPortsExcludeAnnotation]
625642
inboundExclude := annotations[InboundPortsExcludeAnnotation]

kagenti-operator/internal/webhook/injector/pod_mutator_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,11 @@ func TestInjectAuthBridge_RespectsExistingServiceAccountName(t *testing.T) {
233233
func TestInjectAuthBridge_NoSACreationWhenSpiffeHelperDisabled(t *testing.T) {
234234
// Spiffe-helper is injected by default for agents. SA creation is skipped
235235
// when spiffe-helper is explicitly opted out via its per-sidecar label.
236-
m := newTestMutator(newAgentRuntime("test-ns", "my-agent"))
236+
// MTLSMode must be set to "disabled" because the default (permissive) would
237+
// auto-enable SPIRE, creating a ServiceAccount regardless of the spiffe-helper label.
238+
rt := newAgentRuntime("test-ns", "my-agent")
239+
rt.Spec.MTLSMode = "disabled"
240+
m := newTestMutator(rt)
237241
ctx := context.Background()
238242

239243
podSpec := &corev1.PodSpec{}

0 commit comments

Comments
 (0)