Kubernetes Deployment and Local Testing Guide
This guide explains how to deploy the sam-hub in a Kubernetes cluster and how to test it locally using kind and cloud-provider-kind.
[!TIP] This guide focuses on local development sandboxing. For production-grade Kubernetes deployments (GKE, EKS, AKS), see the Production Kubernetes Deployment guide.
This guide supports using either Google OIDC or a Mock OIDC Provider for authentication. The mock provider is recommended for quick local testing as it does not require creating external credentials.
1. Mock OIDC Provider Manifests (Optional)
The manifests for the mock OIDC provider are available in mock-oidc.yaml.
2. SAM Hub Manifests
The manifests for the SAM Hub are available in sam-hub.yaml.
3. Configuring Google OIDC (Optional)
To use Google as the OIDC provider instead of the mock provider:
- No Redirect URI required: Because
sam-nodeimplements RFC 8252 (dynamic loopback port selection for native apps), you don’t need to configure a specific Redirect URI when setting up a Desktop app. The authorization server will automatically allow loopback redirects. - Update Secret: Update the
sam-hub-secretinsam-hub.yamlwith your Google credentials:SAM_OIDC_ISSUER: "https://accounts.google.com" SAM_OIDC_ID: "<your-client-id>.apps.googleusercontent.com" SAM_OIDC_SECRET: "<your-client-secret>"
4. Local Testing with Kind
Step 1: Create a Kind Cluster
kind create cluster --name sam-test
Step 2: Run cloud-provider-kind
Run it in a separate terminal:
cloud-provider-kind
Step 3: Load Images into Kind
kind load docker-image sam-hub:local --name sam-test
kind load docker-image sam-node:local --name sam-test
Step 4: Apply Manifests
If using the Mock OIDC Provider:
kubectl apply -f mock-oidc.yaml
kubectl apply -f sam-hub.yaml
If using Google OIDC:
kubectl apply -f sam-hub.yaml
Step 5: Get the External IP
You can use the following command to extract the allocated IP into an environment variable:
HUB_IP=$(kubectl get svc sam-hub -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
5. Connecting an Agent
To connect a sam-node to the hub, you just need the hub’s external IP and port.
Enrolling the Agent
To connect a sam-node to the hub for the first time, you need to enroll it. The node needs to authenticate with the hub using a JWT token.
If you are using the Mock OIDC Provider, the node can fetch the token using OIDC Client Credentials flow:
Get the Mock OIDC Service IP:
MOCK_IP=$(kubectl get svc mock-oidc -o jsonpath='{.status.loadBalancer.ingress[0].ip}')Run the Node to enroll:
sam-node run \ --hub "http://$HUB_IP:9090" \ --oidc-issuer "http://$MOCK_IP:18080" \ --client-id "sam-mesh-audience" \ --client-secret "sam-e2e-secret"
If you are using Google OIDC, you must obtain a valid Google ID token for your user and pass it via the --jwt flag:
sam-node run \
--hub "http://$HUB_IP:9090" \
--jwt "<your-google-id-token>"
Once enrolled, the identity is stored in the local database (agent.db), and you can run subsequent times without OIDC credentials:
sam-node run
6. Automating Node Deployment
To automate the deployment of sam-nodes in Kubernetes and have them fetch the JWT token automatically, you can use a standard Kubernetes Deployment or StatefulSet.
Example Deployment
Here is a sample manifest that uses the in-cluster DNS to fetch the token from the mock provider:
apiVersion: apps/v1
kind: Deployment
metadata:
name: sam-node
spec:
replicas: 3
selector:
matchLabels:
app: sam-node
template:
metadata:
labels:
app: sam-node
spec:
containers:
- name: sam-node
image: sam-node:local
command: ["sam-node", "run"]
args:
- "--hub"
- "http://sam-hub:9090"
- "--oidc-issuer"
- "http://mock-oidc:18080"
- "--client-id"
- "sam-mesh-audience"
- "--client-secret"
- "sam-e2e-secret"
env:
- name: HOME
value: /data
volumeMounts:
- name: data-volume
mountPath: /data
volumes:
- name: data-volume
emptyDir: {}
Supported Authentication Flows
The SAM project supports three primary flows for acquiring a JWT token to enroll nodes, depending on the environment and security requirements:
1. Client Credentials Flow (Machine-to-Machine)
- Description: Defined in OAuth 2.0 RFC 6749, section 4.4. An application exchanges its application credentials (such as Client ID and Client Secret) for an access token.
- Use Case: For unattended services or deployments connecting to a production OIDC provider.
- How to use: Pass the
--oidc-issuer,--client-id, and--client-secretflags tosam-node run. - Example:
sam-node run \
--hub "http://hub.example.com:9090" \
--oidc-issuer "https://accounts.google.com" \
--client-id "$SAM_OIDC_ID" \
--client-secret "$SAM_OIDC_SECRET"
2. Native App Authorization Code Flow (Human Intervention)
- Description: For devices operated by humans, this uses the standard Authorization Code Flow with PKCE for native apps (RFC 8252). The human operator runs
sam-node jointo open a web browser (or get a verification code via--headless), completes the login, and obtains a Biscuit token which is stored in the local database (agent.db). - Use Case: When a human operator is enrolling a node manually via their local terminal.
- How to use: Run
sam-node join <hub-url>before running the node daemon. Alternatively, you can obtain a token yourself and pass it via the--jwtflag tosam-node run. - Example:
# First, join interactively:
sam-node join https://hub.example.com
# Then start the node daemon:
sam-node run
3. Workload Identity Federation (Secretless Kubernetes)
- Description: The current best practice in Kubernetes. It removes the need for static secrets entirely. The machine proves its identity based on where it is running by presenting a ServiceAccount token (a signed JWT issued by the K8s API).
- Use Case: Production Kubernetes deployments.
- How it works: The Pod has a ServiceAccount token mounted. The Pod presents this token to the
sam-hub. The hub verifies it by calling back to the Kubernetes OIDC discovery endpoint. - How to use: Pass the path to the mounted ServiceAccount token to the
--jwt-pathflag. - Example:
sam-node run \
--hub "http://hub.example.com:9090" \
--jwt-path "/var/run/secrets/kubernetes.io/serviceaccount/token"
[!NOTE] The
sam-hubmust be configured to trust the Kubernetes API server as an OIDC issuer for this flow to work.
7. Configuring Workload Identity in Kubernetes
Workload Identity allows sam-node pods to authenticate with the sam-hub using their Kubernetes ServiceAccount token, removing the need for static credentials.
Here are the exact steps to configure this:
Step 1: Ensure OIDC Discovery is enabled on your Cluster
Most managed Kubernetes services (GKE, EKS, AKS) and local tools like kind support ServiceAccount Issuer Discovery.
In kind, this is enabled by default. You can find the issuer URL by running:
kubectl get --raw /.well-known/openid-configuration | jq -r .issuer
(Or check your cloud provider’s documentation for the public issuer URL).
Step 2: Configure the Hub to trust the Kubernetes Issuer
Update the sam-hub deployment to include the Kubernetes issuer URL in the --issuer flag.
If you are using kind, the issuer URL is usually https://kubernetes.default.svc.cluster.local (internal) or the external URL mapped by kind.
Update sam-hub.yaml:
spec:
containers:
- name: sam-hub
args:
- "--issuer"
- "https://accounts.google.com,https://kubernetes.default.svc.cluster.local"
Step 3: Create a ServiceAccount for the Node
Create a ServiceAccount that the sam-node pods will use.
apiVersion: v1
kind: ServiceAccount
metadata:
name: sam-node-sa
Step 4: Deploy the Node with a Projected Volume
Deploy the sam-node and configure it to use the ServiceAccount. We use a Projected Volume to request a token with the specific audience expected by the hub (e.g., the mesh name or a specific client ID).
apiVersion: apps/v1
kind: Deployment
metadata:
name: sam-node
spec:
replicas: 3
selector:
matchLabels:
app: sam-node
template:
metadata:
labels:
app: sam-node
spec:
serviceAccountName: sam-node-sa
containers:
- name: sam-node
image: sam-node:local
command: ["sam-node", "run"]
args:
- "--hub"
- "http://sam-hub:9090"
- "--jwt-path"
- "/var/run/secrets/tokens/sam-token"
volumeMounts:
- name: sam-token
mountPath: /var/run/secrets/tokens
readOnly: true
volumes:
- name: sam-token
projected:
sources:
- serviceAccountToken:
path: sam-token
expirationSeconds: 3600
audience: "sam-hub-audience" # Match this with what the hub expects