Skip to content
18 changes: 18 additions & 0 deletions charts/kagenti-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,21 @@ defaults:
limits:
cpu: 300m
memory: 384Mi

# SPIFFE / spiffe-helper settings.
# Keep helperConfig in sync with DefaultSpiffeHelperConfig in internal/webhook/config/defaults.go.
spiffe:
trustDomain: cluster.local
socketPath: "unix:///spiffe-workload-api/spire-agent.sock"
helperConfig: |
agent_address = "/spiffe-workload-api/spire-agent.sock"
cmd = ""
cmd_args = ""
svid_file_name = "/opt/svid.pem"
svid_key_file_name = "/opt/svid_key.pem"
svid_bundle_file_name = "/opt/svid_bundle.pem"
cert_file_mode = 0644
key_file_mode = 0640
jwt_svids = [{jwt_audience="http://keycloak.localtest.me:8080/realms/kagenti", jwt_svid_file_name="/opt/jwt_svid.token"}]
jwt_svid_file_mode = 0644
include_federated_domains = true
1 change: 1 addition & 0 deletions kagenti-operator/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ func main() {
EnableCardDiscovery: enableCardDiscovery,
SpireTrustDomain: spireTrustDomain,
GetFeatureGates: featureGateLoader.Get,
GetPlatformConfig: configLoader.Get,
}
if enableCardDiscovery {
artReconciler.AgentFetcher = agentFetcher
Expand Down
24 changes: 0 additions & 24 deletions kagenti-operator/config/authbridge/spiffe-helper-config.yaml

This file was deleted.

1 change: 0 additions & 1 deletion kagenti-operator/config/openshift/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ resources:

patches:
- path: patches/authbridge-keycloak.yaml

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible regression on the OpenShift kustomize path. This PR also deletes patches/spiffe-helper-keycloak.yaml (and its reference here). That patch overrode the spiffe-helper jwt_audience from the public keycloak.localtest.me to the in-cluster keycloak-service.keycloak.svc:8080/realms/kagenti for OpenShift — mirroring the authbridge-keycloak.yaml patch that's kept right above this line.

The new single source of truth, kagenti-platform-config, is created only by the Helm chart (templates/manager/configmap-platform-defaults.yaml). The make deploy-openshift / kustomize build config/openshift path never creates it, so loader.go falls back to CompiledDefaults()DefaultSpiffeHelperConfig, which hardcodes the public keycloak.localtest.me:8080 audience. That won't resolve in-cluster on OpenShift.

If OpenShift is still installable via this kustomize overlay, the in-cluster audience override appears lost. Two ways to resolve:

  • add an OpenShift mechanism to set spiffe.helperConfig with the in-cluster audience (parallel to how authbridge-keycloak.yaml survives), or
  • if OpenShift is now Helm-only, remove/deprecate the config/openshift overlay so it isn't left in a broken state.

Could you confirm which install path OpenShift uses now?

- path: patches/spiffe-helper-keycloak.yaml

This file was deleted.

24 changes: 21 additions & 3 deletions kagenti-operator/internal/controller/agentruntime_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ const (
// ConfigMap are watched by AgentRuntimeReconciler so the resolved-config
// hash picks them up and rolls affected workloads.
AuthBridgeRuntimeConfigMapName = "authbridge-runtime-config"

// SpiffeHelperConfigMapName is the namespace-scoped ConfigMap holding
// the spiffe-helper helper.conf. Derived from PlatformConfig by the
// controller; included in the config hash for rolling updates.
SpiffeHelperConfigMapName = "spiffe-helper-config"
)

// ClusterDefaultsNamespace is the namespace where cluster-level ConfigMaps
Expand Down Expand Up @@ -84,6 +89,10 @@ type resolvedConfig struct {
// we want any byte change to roll the workload. Empty string when
// the ConfigMap doesn't exist in the namespace.
AuthBridgeRuntime string `json:"authBridgeRuntime,omitempty"`

// SpiffeHelperConfig captures the spiffe-helper-config CM content so
// changes to PlatformConfig's spiffe.helperConfig trigger rolling updates.
SpiffeHelperConfig string `json:"spiffeHelperConfig,omitempty"`
}

// ConfigResult holds the computed hash and any warnings from the config resolution.
Expand Down Expand Up @@ -130,10 +139,19 @@ func resolveConfig(ctx context.Context, c client.Reader, namespace string) (reso
abRuntime = data["config.yaml"]
}

// Layer 2c: spiffe-helper-config (helper.conf).
// Derived from PlatformConfig by the controller; included in hash
// so changes to spiffe.helperConfig trigger rolling updates.
spiffeHelper := ""
if data := readConfigMapData(ctx, c, namespace, SpiffeHelperConfigMapName); len(data) > 0 {
spiffeHelper = data["helper.conf"]
}

return resolvedConfig{
FeatureGates: featureGates,
Defaults: merged,
AuthBridgeRuntime: abRuntime,
FeatureGates: featureGates,
Defaults: merged,
AuthBridgeRuntime: abRuntime,
SpiffeHelperConfig: spiffeHelper,
}, warnings
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,32 @@ var _ = Describe("AgentRuntime Config", func() {
r2, _ := ComputeConfigHash(ctx, k8sClient, namespace)
Expect(r1.Hash).NotTo(Equal(r2.Hash))
})

It("should change when spiffe-helper-config content changes", func() {
const shHashNS = "sh-hash-ns"
shNS := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: shHashNS}}
_ = k8sClient.Create(ctx, shNS)

shCM := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "spiffe-helper-config",
Namespace: shHashNS,
},
Data: map[string]string{
"helper.conf": "agent_address = \"/old/socket\"",
},
}
Expect(k8sClient.Create(ctx, shCM)).To(Succeed())
defer func() { _ = k8sClient.Delete(ctx, shCM) }()

r1, _ := ComputeConfigHash(ctx, k8sClient, shHashNS)

shCM.Data["helper.conf"] = "agent_address = \"/new/socket\""
Expect(k8sClient.Update(ctx, shCM)).To(Succeed())

r2, _ := ComputeConfigHash(ctx, k8sClient, shHashNS)
Expect(r1.Hash).NotTo(Equal(r2.Hash))
})
})

Context("resolveConfig two-layer merge", func() {
Expand Down
85 changes: 83 additions & 2 deletions kagenti-operator/internal/controller/agentruntime_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ type AgentRuntimeReconciler struct {
EnableCardDiscovery bool
SpireTrustDomain string
GetFeatureGates func() *webhookconfig.FeatureGates
GetPlatformConfig func() *webhookconfig.PlatformConfig
}

func (r *AgentRuntimeReconciler) getFeatureGates() *webhookconfig.FeatureGates {
Expand All @@ -122,6 +123,15 @@ func (r *AgentRuntimeReconciler) getFeatureGates() *webhookconfig.FeatureGates {
return webhookconfig.DefaultFeatureGates()
}

func (r *AgentRuntimeReconciler) getPlatformConfig() *webhookconfig.PlatformConfig {
if r.GetPlatformConfig != nil {
if cfg := r.GetPlatformConfig(); cfg != nil {
return cfg
}
}
return webhookconfig.CompiledDefaults()
}

// +kubebuilder:rbac:groups=agent.kagenti.dev,resources=agentruntimes,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=agent.kagenti.dev,resources=agentruntimes/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=agent.kagenti.dev,resources=agentruntimes/finalizers,verbs=update
Expand Down Expand Up @@ -194,6 +204,19 @@ func (r *AgentRuntimeReconciler) Reconcile(ctx context.Context, req ctrl.Request
}
}

// 4.5b. Ensure spiffe-helper-config CM is derived from PlatformConfig.
// Unlike template CMs above, this always overwrites to keep PlatformConfig
// as the single source of truth.
if err := r.ensureSpiffeHelperConfigMap(ctx, rt.Namespace); err != nil {
logger.Error(err, "Failed to ensure spiffe-helper-config")
if r.Recorder != nil {
r.Recorder.Eventf(rt, nil, corev1.EventTypeWarning, "ConfigMapEnsureError",
"EnsureSpiffeHelperConfig", err.Error())
}
r.updateErrorStatus(ctx, req.NamespacedName, ConditionTypeReady, "SpiffeHelperConfigError", err.Error())
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

// 4.6. Ensure namespace has Istio ambient mesh labels for ztunnel mTLS.
istioLabeled, istioErr := r.ensureIstioMeshLabels(ctx, rt.Namespace)
switch {
Expand Down Expand Up @@ -235,7 +258,7 @@ func (r *AgentRuntimeReconciler) Reconcile(ctx context.Context, req ctrl.Request
}

// 5. Compute config hash from merged configuration (cluster → namespace)
configResult, err := ComputeConfigHash(ctx, r.Client, rt.Namespace)
configResult, err := ComputeConfigHash(ctx, r.uncachedReader(), rt.Namespace)
if err != nil {
logger.Error(err, "Failed to compute config hash")
r.updateErrorStatus(ctx, req.NamespacedName, ConditionTypeReady, "ConfigHashError", err.Error())
Expand Down Expand Up @@ -1045,7 +1068,6 @@ func computeCardContentHash(cardData *agentv1alpha1.AgentCardData) string {
var templateConfigMapNames = []string{
"authbridge-config",
"authbridge-runtime-config",
"spiffe-helper-config",
}

// ensureNamespaceConfigMaps copies template ConfigMaps from kagenti-system to the
Expand Down Expand Up @@ -1097,6 +1119,65 @@ func (r *AgentRuntimeReconciler) ensureNamespaceConfigMaps(ctx context.Context,
return nil
}

// ensureSpiffeHelperConfigMap creates or updates the spiffe-helper-config ConfigMap
// in the target namespace using content from PlatformConfig. Unlike template CMs
// which are create-if-not-exists, this always overwrites because PlatformConfig is
// the single source of truth.
func (r *AgentRuntimeReconciler) ensureSpiffeHelperConfigMap(ctx context.Context, namespace string) error {
logger := log.FromContext(ctx)
cfg := r.getPlatformConfig()

desired := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: SpiffeHelperConfigMapName,
Namespace: namespace,
Labels: map[string]string{
LabelManagedBy: LabelManagedByValue,
},
},
Data: map[string]string{
"helper.conf": cfg.Spiffe.HelperConfig,
},
}

existing := &corev1.ConfigMap{}
err := r.uncachedReader().Get(ctx, client.ObjectKey{Namespace: namespace, Name: SpiffeHelperConfigMapName}, existing)
if apierrors.IsNotFound(err) {
if err := r.Create(ctx, desired); err != nil {
if apierrors.IsAlreadyExists(err) {
return nil
}
return fmt.Errorf("failed to create spiffe-helper-config in %s: %w", namespace, err)
}
logger.Info("Created spiffe-helper-config from PlatformConfig", "namespace", namespace)
return nil
}
if err != nil {
return fmt.Errorf("failed to check spiffe-helper-config in %s: %w", namespace, err)
}

needsUpdate := existing.Data["helper.conf"] != cfg.Spiffe.HelperConfig

if existing.Labels == nil {
existing.Labels = make(map[string]string)
}
if existing.Labels[LabelManagedBy] != LabelManagedByValue {
existing.Labels[LabelManagedBy] = LabelManagedByValue
needsUpdate = true
}

if !needsUpdate {
return nil
}

existing.Data = desired.Data
if err := r.Update(ctx, existing); err != nil {
return fmt.Errorf("failed to update spiffe-helper-config in %s: %w", namespace, err)
}
logger.Info("Updated spiffe-helper-config from PlatformConfig", "namespace", namespace)
return nil
}

const (
sccClusterRoleName = "system:openshift:scc:kagenti-authbridge"
sccRoleBindingName = "agent-authbridge-scc"
Expand Down
Loading
Loading