Skip to content
Merged
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
32 changes: 29 additions & 3 deletions k8s/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

> **⚠️ Experimental**: This deployment method is intended for **trying out NemoClaw on Kubernetes**, not for production use. It requires a **privileged pod** running **Docker-in-Docker (DinD)** to create isolated sandbox environments. Operational requirements (storage, runtime, security policies) vary by cluster configuration.

The sample manifest now uses a few safer defaults out of the box:

- disables Kubernetes service account token automounting
- disables service-link environment injection
- runs the workspace container with `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`, and `RuntimeDefault` seccomp
- applies NemoClaw's suggested policy presets instead of skipping policy setup
- downloads the installer to a local file with HTTPS-only curl flags before execution

Run [NemoClaw](https://github.com/NVIDIA/NemoClaw) on Kubernetes with GPU inference powered by [Dynamo](https://github.com/ai-dynamo/dynamo) or any OpenAI-compatible endpoint.

---
Expand All @@ -17,8 +25,16 @@ Run [NemoClaw](https://github.com/NVIDIA/NemoClaw) on Kubernetes with GPU infere

### 1. Deploy NemoClaw

If your compatible endpoint requires an API key, create the optional
`nemoclaw-compatible-api-key` Secret after creating the namespace and before
running `kubectl apply`. The same Secret-backed flow is described again in the
configuration section below.

```bash
kubectl create namespace nemoclaw
kubectl create secret generic nemoclaw-compatible-api-key \
-n nemoclaw \
--from-literal=api-key='<your-api-key>'
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/NemoClaw/main/k8s/nemoclaw-k8s.yaml
```

Expand Down Expand Up @@ -48,9 +64,21 @@ Edit the environment variables in `nemoclaw-k8s.yaml` before deploying:
|----------|----------|-------------|
| `DYNAMO_HOST` | Yes | Inference endpoint for socat proxy (e.g., `vllm-frontend.dynamo.svc:8000`) |
| `NEMOCLAW_ENDPOINT_URL` | Yes | URL the sandbox uses (usually `http://host.openshell.internal:8000/v1`) |
| `COMPATIBLE_API_KEY` | Yes | API key (use `dummy` for Dynamo/vLLM) |
| `COMPATIBLE_API_KEY` | No | Loaded from the optional `nemoclaw-compatible-api-key` Secret; defaults to `dummy` for Dynamo/vLLM when the Secret is absent |
| `NEMOCLAW_MODEL` | Yes | Model name (e.g., `meta-llama/Llama-3.1-8B-Instruct`) |
| `NEMOCLAW_SANDBOX_NAME` | No | Sandbox name (default: `my-assistant`) |
| `NEMOCLAW_POLICY_MODE` | No | Policy preset mode for non-interactive onboarding (default: `suggested`) |

### Optional: Store a Real API Key in a Secret

If your compatible endpoint requires authentication, create the Secret before
you apply the manifest in Step 1:

```bash
kubectl create secret generic nemoclaw-compatible-api-key \
-n nemoclaw \
--from-literal=api-key='<your-api-key>'
```

### Example: Custom Endpoint

Expand All @@ -60,8 +88,6 @@ env:
value: "my-vllm.my-namespace.svc.cluster.local:8000"
- name: NEMOCLAW_ENDPOINT_URL
value: "http://host.openshell.internal:8000/v1"
- name: COMPATIBLE_API_KEY
value: "dummy"
- name: NEMOCLAW_MODEL
value: "mistralai/Mistral-7B-Instruct-v0.3"
```
Expand Down
30 changes: 27 additions & 3 deletions k8s/nemoclaw-k8s.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ metadata:
labels:
app: nemoclaw
spec:
automountServiceAccountToken: false
enableServiceLinks: false
containers:
# Docker daemon (DinD)
- name: dind
Expand All @@ -34,6 +36,13 @@ spec:
# Workspace - runs official NemoClaw installer
- name: workspace
image: node:22
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
command:
- bash
- -c
Expand Down Expand Up @@ -61,9 +70,20 @@ spec:
docker info >/dev/null 2>&1 || { echo "Docker not ready"; exit 1; }
echo "Docker ready"

# Default to a dummy compatible API key for unauthenticated endpoints
# such as Dynamo/vLLM while still allowing a Secret-backed override.
export COMPATIBLE_API_KEY="${COMPATIBLE_API_KEY:-dummy}"

# Run official NemoClaw installer
echo "[4/4] Running NemoClaw installer..."
curl -fsSL https://nvidia.com/nemoclaw.sh | bash
umask 077
curl --proto '=https' --tlsv1.2 --fail --show-error --silent \
--location \
--output /tmp/nemoclaw-install.sh \
https://www.nvidia.com/nemoclaw.sh
chmod 700 /tmp/nemoclaw-install.sh
bash /tmp/nemoclaw-install.sh
rm -f /tmp/nemoclaw-install.sh

# Keep running after onboard
echo "Onboard complete. Container staying alive."
Expand All @@ -82,13 +102,17 @@ spec:
- name: NEMOCLAW_ENDPOINT_URL
value: "http://host.openshell.internal:8000/v1"
- name: COMPATIBLE_API_KEY
value: "dummy"
valueFrom:
secretKeyRef:
name: nemoclaw-compatible-api-key
key: api-key
optional: true
- name: NEMOCLAW_MODEL
value: "meta-llama/Llama-3.1-8B-Instruct"
- name: NEMOCLAW_SANDBOX_NAME
value: "my-assistant"
- name: NEMOCLAW_POLICY_MODE
value: "skip"
value: "suggested"
- name: NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE
value: "1"
volumeMounts:
Expand Down
40 changes: 40 additions & 0 deletions test/security-configuration-hardening.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, it, expect } from "vitest";
import fs from "node:fs";
import path from "node:path";

const ROOT = path.join(import.meta.dirname, "..");
const K8S_MANIFEST = path.join(ROOT, "k8s", "nemoclaw-k8s.yaml");

describe("security configuration hardening", () => {
it("hardens the Kubernetes sample manifest with safer defaults", () => {
const manifest = fs.readFileSync(K8S_MANIFEST, "utf8");
const workspaceMatch = manifest.match(
/- name: workspace[\s\S]*?(?=\n\s*-\s*name: |\n\s*initContainers:|\n\s*volumes:|$)/,
);
expect(workspaceMatch).not.toBeNull();
const workspaceSection = workspaceMatch[0];
expect(manifest).toMatch(/automountServiceAccountToken:\s*false/);
expect(manifest).toMatch(/enableServiceLinks:\s*false/);
expect(workspaceSection).toMatch(/allowPrivilegeEscalation:\s*false/);
expect(workspaceSection).toMatch(/capabilities:\s*[\r\n]+\s*drop:\s*[\r\n]+\s*-\s*ALL/);
expect(workspaceSection).toMatch(/seccompProfile:\s*[\r\n]+\s*type:\s*RuntimeDefault/);
expect(manifest).toMatch(/- name: NEMOCLAW_POLICY_MODE[\s\S]*value:\s*"suggested"/);
expect(manifest).toContain('export COMPATIBLE_API_KEY="${COMPATIBLE_API_KEY:-dummy}"');
const compatibleApiKeySection = manifest.match(
/- name: COMPATIBLE_API_KEY[\s\S]*?(?=\n\s*-\s*name: |\n\s*volumeMounts:|\n\s*command:|$)/,
)?.[0];
expect(compatibleApiKeySection).toBeTruthy();
expect(compatibleApiKeySection).toMatch(
/secretKeyRef:[\s\S]*name:\s*nemoclaw-compatible-api-key/,
);
expect(compatibleApiKeySection).toMatch(/optional:\s*true/);
expect(manifest).toContain("curl --proto '=https' --tlsv1.2 --fail --show-error --silent");
expect(manifest).toContain("--output /tmp/nemoclaw-install.sh");
expect(manifest).toContain("chmod 700 /tmp/nemoclaw-install.sh");
expect(manifest).toContain("bash /tmp/nemoclaw-install.sh");
expect(manifest).not.toMatch(/curl\b[^\n|]*\|\s*(?:ba|z|k)?sh\b/i);
});
});
Loading