Skip to content

Commit bfe15ba

Browse files
Fix/securitycontext runasuser (#1171)
## Fix: Apply Security Context Fields from Agent Spec to Generated Pods Fixes #1083 ### Problem The kagent controller was not applying `runAsUser` and other security context fields from the Agent's `deployment.securityContext` and `deployment.podSecurityContext` to the generated pod specifications. This caused pods to fail with `CreateContainerConfigError` when container images have non-numeric users. **Current Behavior**: Only `runAsNonRoot` and `allowPrivilegeEscalation` were being applied to pods, while `runAsUser`, `runAsGroup`, `fsGroup`, and `capabilities` were ignored. **Expected Behavior**: All security context fields from the Agent spec should be properly propagated to the pod template. ### Solution This PR adds support for both pod-level and container-level security contexts in the Agent API and ensures they are properly propagated to generated pods and containers. ### Changes Made #### 1. API Changes (`go/api/v1alpha2/agent_types.go`) - Added `SecurityContext *corev1.SecurityContext` to `SharedDeploymentSpec` for container-level security context - Added `PodSecurityContext *corev1.PodSecurityContext` to `SharedDeploymentSpec` for pod-level security context #### 2. Internal Struct Updates (`go/internal/controller/translator/agent/adk_api_translator.go`) - Added `SecurityContext` and `PodSecurityContext` fields to the `resolvedDeployment` struct #### 3. Resolver Functions - **`resolveInlineDeployment`**: Now copies `SecurityContext` and `PodSecurityContext` from the Agent spec - **`resolveByoDeployment`**: Now copies `SecurityContext` and `PodSecurityContext` from the Agent spec #### 4. Manifest Building (`buildManifest` function) - **Pod-level security context**: `PodSecurityContext` from the Agent spec is applied to `PodSpec.securityContext`, which includes fields like: - `runAsUser`, `runAsGroup`, `runAsNonRoot` - `fsGroup`, `supplementalGroups` - `seLinuxOptions`, `seccompProfile` - **Container-level security context**: `SecurityContext` from the Agent spec is applied to container `SecurityContext`, which includes fields like: - `runAsUser`, `runAsGroup`, `runAsNonRoot` - `capabilities`, `allowPrivilegeEscalation` - `readOnlyRootFilesystem`, `privileged` - **Sandbox compatibility**: When `needSandbox` is `true` (for skills or code execution), the `Privileged` flag is set appropriately while preserving user-provided security context settings - **Init containers**: Security context is also applied to init containers (e.g., skills-init container) #### 5. Code Generation - Ran `make generate` to update the generated deepcopy methods for the new fields ### How It Works 1. **Pod-level security context**: The `podSecurityContext` field from the Agent spec is directly applied to `PodSpec.securityContext`, affecting all containers in the pod. 2. **Container-level security context**: The `securityContext` field from the Agent spec is applied to each container's `SecurityContext`. When sandbox mode is required (for skills or code execution), the `Privileged` flag is merged with user-provided settings. 3. **Priority**: User-provided security context settings take precedence, with sandbox requirements merged in when necessary. ### Testing **Unit Tests**: - Verified that security context fields are properly copied in resolver functions - Confirmed that security context is correctly applied to pod and container specs in manifest building **Manual Testing**: - Verified that pods are created successfully with `runAsUser` specified (e.g., `runAsUser: 1000`) - Confirmed that security context fields (`runAsUser`, `runAsGroup`, `fsGroup`, `capabilities`) are properly applied to both main containers and init containers - Tested sandbox mode compatibility (skills and code execution) with custom security contexts - Validated that `CreateContainerConfigError` is resolved when container images have non-numeric users - Verified that both `podSecurityContext` and `securityContext` from Agent spec are correctly propagated to pod template **Code Quality**: - Ran `make lint` to ensure code style compliance - All existing tests pass ### Documentation - API changes are self-documenting through the CRD schema - No additional documentation updates required as this fixes existing functionality ### Checklist - [x] Code follows project style guidelines (Go Code Review Comments) - [x] Ran `make lint` and fixed any issues - [x] Ran `make generate` to update generated code - [x] Changes are tested and verified - [x] All commits are signed off (DCO) ### Related Issues - Fixes #1083 - Controller not applying runAsUser from Agent securityContext to pod containers - Resolves `CreateContainerConfigError` when container images have non-numeric users or require specific security context configurations --------- Signed-off-by: Raghavendiran-2002 <raghavendiran46461@gmail.com>
1 parent 87eb468 commit bfe15ba

File tree

8 files changed

+2545
-46
lines changed

8 files changed

+2545
-46
lines changed

go/api/v1alpha2/agent_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ type SharedDeploymentSpec struct {
159159
Affinity *corev1.Affinity `json:"affinity,omitempty"`
160160
// +optional
161161
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
162+
// +optional
163+
SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`
164+
// +optional
165+
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
162166
}
163167

164168
// ToolProviderType represents the tool provider type

go/api/v1alpha2/zz_generated.deepcopy.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/config/crd/bases/kagent.dev_agents.yaml

Lines changed: 862 additions & 0 deletions
Large diffs are not rendered by default.

go/internal/controller/translator/agent/adk_api_translator.go

Lines changed: 66 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,10 @@ func (a *adkApiTranslator) buildManifest(
348348
if insecure {
349349
command = append(command, "--insecure")
350350
}
351+
initContainerSecurityContext := dep.SecurityContext
352+
if initContainerSecurityContext != nil {
353+
initContainerSecurityContext = initContainerSecurityContext.DeepCopy()
354+
}
351355
initContainers = append(initContainers, corev1.Container{
352356
Name: "skills-init",
353357
Image: dep.Image,
@@ -356,9 +360,8 @@ func (a *adkApiTranslator) buildManifest(
356360
VolumeMounts: []corev1.VolumeMount{
357361
{Name: "kagent-skills", MountPath: "/skills"},
358362
},
359-
Env: []corev1.EnvVar{
360-
skillsEnv,
361-
},
363+
Env: []corev1.EnvVar{skillsEnv},
364+
SecurityContext: initContainerSecurityContext,
362365
})
363366
volumes = append(volumes, corev1.Volume{
364367
Name: "kagent-skills",
@@ -409,12 +412,22 @@ func (a *adkApiTranslator) buildManifest(
409412
// Add hash annotations to pod template to force rollout on agent config or model config secret changes
410413
podTemplateAnnotations["kagent.dev/config-hash"] = fmt.Sprintf("%d", cfgHash)
411414

415+
// Merge container security context: start with user-provided, then apply sandbox requirements
412416
var securityContext *corev1.SecurityContext
413-
if needSandbox {
417+
if dep.SecurityContext != nil {
418+
// Deep copy the user-provided security context
419+
securityContext = dep.SecurityContext.DeepCopy()
420+
// If sandbox is needed, ensure Privileged is set (may override user setting)
421+
if needSandbox {
422+
securityContext.Privileged = ptr.To(true)
423+
}
424+
} else if needSandbox {
425+
// Only create security context if sandbox is needed
414426
securityContext = &corev1.SecurityContext{
415427
Privileged: ptr.To(true),
416428
}
417429
}
430+
// If neither user-provided securityContext nor sandbox is needed, securityContext remains nil
418431

419432
deployment := &appsv1.Deployment{
420433
TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"},
@@ -434,6 +447,7 @@ func (a *adkApiTranslator) buildManifest(
434447
Spec: corev1.PodSpec{
435448
ServiceAccountName: agent.Name,
436449
ImagePullSecrets: dep.ImagePullSecrets,
450+
SecurityContext: dep.PodSecurityContext,
437451
InitContainers: initContainers,
438452
Containers: []corev1.Container{{
439453
Name: "kagent",
@@ -1163,17 +1177,19 @@ type resolvedDeployment struct {
11631177
ImagePullPolicy corev1.PullPolicy
11641178

11651179
// SharedDeploymentSpec merged
1166-
Replicas *int32
1167-
ImagePullSecrets []corev1.LocalObjectReference
1168-
Volumes []corev1.Volume
1169-
VolumeMounts []corev1.VolumeMount
1170-
Labels map[string]string
1171-
Annotations map[string]string
1172-
Env []corev1.EnvVar
1173-
Resources corev1.ResourceRequirements
1174-
Tolerations []corev1.Toleration
1175-
Affinity *corev1.Affinity
1176-
NodeSelector map[string]string
1180+
Replicas *int32
1181+
ImagePullSecrets []corev1.LocalObjectReference
1182+
Volumes []corev1.Volume
1183+
VolumeMounts []corev1.VolumeMount
1184+
Labels map[string]string
1185+
Annotations map[string]string
1186+
Env []corev1.EnvVar
1187+
Resources corev1.ResourceRequirements
1188+
Tolerations []corev1.Toleration
1189+
Affinity *corev1.Affinity
1190+
NodeSelector map[string]string
1191+
SecurityContext *corev1.SecurityContext
1192+
PodSecurityContext *corev1.PodSecurityContext
11771193
}
11781194

11791195
// getDefaultResources sets default resource requirements if not specified
@@ -1241,21 +1257,23 @@ func (a *adkApiTranslator) resolveInlineDeployment(agent *v1alpha2.Agent, mdd *m
12411257
}
12421258

12431259
dep := &resolvedDeployment{
1244-
Image: image,
1245-
Args: args,
1246-
Port: port,
1247-
ImagePullPolicy: imagePullPolicy,
1248-
Replicas: spec.Replicas,
1249-
ImagePullSecrets: slices.Clone(spec.ImagePullSecrets),
1250-
Volumes: append(slices.Clone(spec.Volumes), mdd.Volumes...),
1251-
VolumeMounts: append(slices.Clone(spec.VolumeMounts), mdd.VolumeMounts...),
1252-
Labels: getDefaultLabels(agent.Name, spec.Labels),
1253-
Annotations: maps.Clone(spec.Annotations),
1254-
Env: append(slices.Clone(spec.Env), mdd.EnvVars...),
1255-
Resources: getDefaultResources(spec.Resources), // Set default resources if not specified
1256-
Tolerations: slices.Clone(spec.Tolerations),
1257-
Affinity: spec.Affinity,
1258-
NodeSelector: maps.Clone(spec.NodeSelector),
1260+
Image: image,
1261+
Args: args,
1262+
Port: port,
1263+
ImagePullPolicy: imagePullPolicy,
1264+
Replicas: spec.Replicas,
1265+
ImagePullSecrets: slices.Clone(spec.ImagePullSecrets),
1266+
Volumes: append(slices.Clone(spec.Volumes), mdd.Volumes...),
1267+
VolumeMounts: append(slices.Clone(spec.VolumeMounts), mdd.VolumeMounts...),
1268+
Labels: getDefaultLabels(agent.Name, spec.Labels),
1269+
Annotations: maps.Clone(spec.Annotations),
1270+
Env: append(slices.Clone(spec.Env), mdd.EnvVars...),
1271+
Resources: getDefaultResources(spec.Resources), // Set default resources if not specified
1272+
Tolerations: slices.Clone(spec.Tolerations),
1273+
Affinity: spec.Affinity,
1274+
NodeSelector: maps.Clone(spec.NodeSelector),
1275+
SecurityContext: spec.SecurityContext,
1276+
PodSecurityContext: spec.PodSecurityContext,
12591277
}
12601278

12611279
return dep, nil
@@ -1308,22 +1326,24 @@ func (a *adkApiTranslator) resolveByoDeployment(agent *v1alpha2.Agent) (*resolve
13081326
}
13091327

13101328
dep := &resolvedDeployment{
1311-
Image: image,
1312-
Cmd: cmd,
1313-
Args: args,
1314-
Port: port,
1315-
ImagePullPolicy: imagePullPolicy,
1316-
Replicas: replicas,
1317-
ImagePullSecrets: slices.Clone(spec.ImagePullSecrets),
1318-
Volumes: slices.Clone(spec.Volumes),
1319-
VolumeMounts: slices.Clone(spec.VolumeMounts),
1320-
Labels: getDefaultLabels(agent.Name, spec.Labels),
1321-
Annotations: maps.Clone(spec.Annotations),
1322-
Env: slices.Clone(spec.Env),
1323-
Resources: getDefaultResources(spec.Resources), // Set default resources if not specified
1324-
Tolerations: slices.Clone(spec.Tolerations),
1325-
Affinity: spec.Affinity,
1326-
NodeSelector: maps.Clone(spec.NodeSelector),
1329+
Image: image,
1330+
Cmd: cmd,
1331+
Args: args,
1332+
Port: port,
1333+
ImagePullPolicy: imagePullPolicy,
1334+
Replicas: replicas,
1335+
ImagePullSecrets: slices.Clone(spec.ImagePullSecrets),
1336+
Volumes: slices.Clone(spec.Volumes),
1337+
VolumeMounts: slices.Clone(spec.VolumeMounts),
1338+
Labels: getDefaultLabels(agent.Name, spec.Labels),
1339+
Annotations: maps.Clone(spec.Annotations),
1340+
Env: slices.Clone(spec.Env),
1341+
Resources: getDefaultResources(spec.Resources), // Set default resources if not specified
1342+
Tolerations: slices.Clone(spec.Tolerations),
1343+
Affinity: spec.Affinity,
1344+
NodeSelector: maps.Clone(spec.NodeSelector),
1345+
SecurityContext: spec.SecurityContext,
1346+
PodSecurityContext: spec.PodSecurityContext,
13271347
}
13281348

13291349
return dep, nil

0 commit comments

Comments
 (0)