Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 BOOKMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Desired state of the system, organized by capability domain.
| [Runner](specs/agents/runner.spec.md) | agents | Runner subprocess lifecycle, bridges, gRPC/HTTP endpoints |
| [MCP Server](specs/integrations/mcp-server.spec.md) | integrations | MCP tool definitions, sidecar and public endpoint modes |
| [Security](specs/security/security.spec.md) | security | Identity boundaries, credential authorization, per-session isolation, design decisions |
| [OpenShell Sandbox](specs/security/openshell-sandbox.spec.md) | security | Agent subprocess sandbox: network namespace, Landlock, seccomp, TLS proxy, OPA policy |

Feature specs remain in numbered directories under `specs/` (e.g., `specs/001-*/spec.md`).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ func runKubeMode(ctx context.Context, cfg *config.ControlPlaneConfig) error {
ImagePullSecret: cfg.ImagePullSecret,
PlatformMode: cfg.PlatformMode,
MPPConfigNamespace: cfg.MPPConfigNamespace,
OpenShellEnabled: cfg.OpenShellEnabled,
OpenShellPolicyName: cfg.OpenShellPolicyName,
}

conn, err := grpc.NewClient(cfg.GRPCServerAddr, grpc.WithTransportCredentials(grpcCredentials(cfg.GRPCUseTLS)))
Expand Down
4 changes: 4 additions & 0 deletions components/ambient-control-plane/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ type ControlPlaneConfig struct {
HTTPSProxy string
NoProxy string
ImagePullSecret string
OpenShellEnabled bool
OpenShellPolicyName string
}

func Load() (*ControlPlaneConfig, error) {
Expand Down Expand Up @@ -91,6 +93,8 @@ func Load() (*ControlPlaneConfig, error) {
HTTPSProxy: os.Getenv("HTTPS_PROXY"),
NoProxy: os.Getenv("NO_PROXY"),
ImagePullSecret: os.Getenv("IMAGE_PULL_SECRET"),
OpenShellEnabled: os.Getenv("OPENSHELL_ENABLED") == "true",
OpenShellPolicyName: envOrDefault("OPENSHELL_POLICY_CONFIGMAP", "openshell-policy"),
}

if cfg.MCPAPIServerURL == "" {
Expand Down
14 changes: 14 additions & 0 deletions components/ambient-control-plane/internal/kubeclient/kubeclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ var NetworkPolicyGVR = schema.GroupVersionResource{
Resource: "networkpolicies",
}

var ConfigMapGVR = schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "configmaps",
}

type KubeClient struct {
dynamic dynamic.Interface
logger zerolog.Logger
Expand Down Expand Up @@ -327,6 +333,14 @@ func (kc *KubeClient) ListTenantNamespaces(ctx context.Context, namespace, label
return kc.dynamic.Resource(gvr).Namespace(namespace).List(ctx, opts)
}

func (kc *KubeClient) GetConfigMap(ctx context.Context, namespace, name string) (*unstructured.Unstructured, error) {
return kc.dynamic.Resource(ConfigMapGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
}

func (kc *KubeClient) CreateConfigMap(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
return kc.dynamic.Resource(ConfigMapGVR).Namespace(obj.GetNamespace()).Create(ctx, obj, metav1.CreateOptions{})
}

func (kc *KubeClient) GetResource(ctx context.Context, gvr schema.GroupVersionResource, namespace, name string) (*unstructured.Unstructured, error) {
return kc.dynamic.Resource(gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ type KubeReconcilerConfig struct {
ImagePullSecret string
PlatformMode string
MPPConfigNamespace string
OpenShellEnabled bool
OpenShellPolicyName string
}

type SimpleKubeReconciler struct {
Expand Down Expand Up @@ -178,6 +180,12 @@ func (r *SimpleKubeReconciler) provisionSession(ctx context.Context, session typ
}
}

if r.cfg.OpenShellEnabled {
if err := r.ensureOpenShellPolicy(ctx, namespace); err != nil {
return fmt.Errorf("ensuring openshell policy: %w", err)
}
}

if err := r.ensureServiceAccount(ctx, namespace, session, sessionLabel); err != nil {
return fmt.Errorf("ensuring service account: %w", err)
}
Expand Down Expand Up @@ -522,12 +530,7 @@ func (r *SimpleKubeReconciler) ensurePod(ctx context.Context, namespace string,
"memory": "4Gi",
},
},
"securityContext": map[string]interface{}{
"allowPrivilegeEscalation": false,
"capabilities": map[string]interface{}{
"drop": []interface{}{"ALL"},
},
},
"securityContext": r.buildRunnerSecurityContext(),
},
}

Expand Down Expand Up @@ -588,6 +591,14 @@ func (r *SimpleKubeReconciler) ensurePod(ctx context.Context, namespace string,
},
}

if r.cfg.OpenShellEnabled {
pod.Object["spec"].(map[string]interface{})["securityContext"] = map[string]interface{}{
"seccompProfile": map[string]interface{}{
"type": "Unconfined",
},
}
}

if r.cfg.ImagePullSecret != "" {
pod.Object["spec"].(map[string]interface{})["imagePullSecrets"] = []interface{}{
map[string]interface{}{"name": r.cfg.ImagePullSecret},
Expand All @@ -602,6 +613,25 @@ func (r *SimpleKubeReconciler) ensurePod(ctx context.Context, namespace string,
return nil
}

func (r *SimpleKubeReconciler) buildRunnerSecurityContext() map[string]interface{} {
sc := map[string]interface{}{
"allowPrivilegeEscalation": false,
"capabilities": map[string]interface{}{
"drop": []interface{}{"ALL"},
},
}
if r.cfg.OpenShellEnabled {
sc["allowPrivilegeEscalation"] = true
sc["runAsUser"] = int64(0)
sc["runAsNonRoot"] = false
sc["capabilities"] = map[string]interface{}{
"drop": []interface{}{"ALL"},
"add": []interface{}{"NET_ADMIN", "SYS_ADMIN", "SYS_PTRACE", "SETUID", "SETGID", "CHOWN", "DAC_OVERRIDE"},
}
}
return sc
}

func (r *SimpleKubeReconciler) buildVolumes(extraVolumes []interface{}) []interface{} {
vols := []interface{}{
map[string]interface{}{
Expand All @@ -624,6 +654,14 @@ func (r *SimpleKubeReconciler) buildVolumes(extraVolumes []interface{}) []interf
},
})
}
if r.cfg.OpenShellEnabled {
vols = append(vols, map[string]interface{}{
"name": "openshell-policy",
"configMap": map[string]interface{}{
"name": r.cfg.OpenShellPolicyName,
},
})
}
vols = append(vols, extraVolumes...)
return vols
}
Expand All @@ -648,6 +686,13 @@ func (r *SimpleKubeReconciler) buildVolumeMounts() []interface{} {
"readOnly": true,
})
}
if r.cfg.OpenShellEnabled {
mounts = append(mounts, map[string]interface{}{
"name": "openshell-policy",
"mountPath": "/etc/openshell",
"readOnly": true,
})
}
return mounts
}

Expand Down Expand Up @@ -688,6 +733,48 @@ func (r *SimpleKubeReconciler) ensureVertexSecret(ctx context.Context, namespace
return nil
}

func (r *SimpleKubeReconciler) ensureOpenShellPolicy(ctx context.Context, namespace string) error {
policyName := r.cfg.OpenShellPolicyName

if _, err := r.nsKube().GetConfigMap(ctx, namespace, policyName); err == nil {
return nil
}

src, err := r.nsKube().GetConfigMap(ctx, r.cfg.CPRuntimeNamespace, policyName)
if err != nil {
return fmt.Errorf("reading openshell policy configmap %s/%s: %w", r.cfg.CPRuntimeNamespace, policyName, err)
}

data, _, _ := unstructured.NestedStringMap(src.Object, "data")
dataIface := make(map[string]interface{}, len(data))
for k, v := range data {
dataIface[k] = v
}

dst := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": policyName,
"namespace": namespace,
"labels": map[string]interface{}{
LabelManaged: "true",
LabelManagedBy: "ambient-control-plane",
},
},
"data": dataIface,
},
}

if _, err := r.nsKube().CreateConfigMap(ctx, dst); err != nil && !k8serrors.IsAlreadyExists(err) {
return fmt.Errorf("copying openshell policy configmap to %s: %w", namespace, err)
}

r.logger.Debug().Str("namespace", namespace).Str("configmap", policyName).Msg("openshell policy configmap copied")
return nil
}

func (r *SimpleKubeReconciler) buildEnv(ctx context.Context, session types.Session, sdk *sdkclient.Client, useMCPSidecar bool, credentialIDs map[string]string) []interface{} {
useVertex := "0"
if r.cfg.VertexEnabled {
Expand Down Expand Up @@ -770,6 +857,14 @@ func (r *SimpleKubeReconciler) buildEnv(ctx context.Context, session types.Sessi
env = append(env, envVar("NO_PROXY", r.cfg.NoProxy))
}

if r.cfg.OpenShellEnabled {
env = append(env,
envVar("OPENSHELL_ENABLED", "true"),
envVar("OPENSHELL_POLICY_RULES", "/etc/openshell/policy.rego"),
envVar("OPENSHELL_POLICY_DATA", "/etc/openshell/policy.yaml"),
)
}

return env
}

Expand Down
Loading
Loading