> ## Documentation Index
> Fetch the complete documentation index at: https://docs.resolve.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Satellite on Azure AKS

Deploy the Resolve Satellite on Azure Kubernetes Service (AKS) using Helm and Microsoft Entra Workload ID. The satellite runs in one AKS cluster and can read that cluster and any number of other AKS clusters, without storing a Kubernetes token, client secret, or certificate.

> [!NOTE]
>
> For other Kubernetes clusters, see [Resolve Satellite](https://docs.resolve.ai/resolve-satellite). For AWS ECS Fargate, see [Satellite on AWS ECS](https://docs.resolve.ai/satellite-on-ecs.md).

## Architecture

* **Compute**: A satellite pod installed with Helm in one AKS cluster (the host cluster)
* **Identity**: A user-assigned managed identity, federated with the satellite's Kubernetes service account
* **Authentication**: [kubelogin](https://azure.github.io/kubelogin/), bundled in the satellite image, exchanges the pod's service account token for a short-lived Microsoft Entra token
* **Authorization**: A read-only ClusterRole bound to the managed identity on each cluster
* **Cluster access**: One Kubernetes integration per cluster, each with a kubeconfig that holds only the API server address and CA certificate

***

## Installation

### Step 1: Prepare Resources

Gather the following information before deployment. See [Resource Setup Reference](#resource-setup-reference) below for how to create each resource.

| Value | Description | Example |
| - | - | - |
| Ingest token secret | Kubernetes Secret holding the Resolve ingest token | `resolve-satellite-token` |
| Namespace | Namespace the satellite runs in | `resolve-satellite` |
| Service account | Service account the satellite runs as | `resolve-satellite` |
| `CLIENT_ID` | Client ID of the managed identity | `33839395-844b-4ffa-8c32-edac4bc28580` |
| API server URL | API server address of each cluster the satellite reads | `https://prod-eastus-dns-1a2b3c4d.hcp.eastus.azmk8s.io:443` |
| CA certificate | Base64 CA certificate of each cluster the satellite reads | `LS0tLS1CRUdJTi...` |

> [!NOTE]
>
> The namespace and service account are bound into the federated credential. Choose them before creating the identity, and install the satellite with the same values.

Get the API server URL and CA certificate for each cluster:

```bash theme={null}
kubectl config view --raw \
  --kubeconfig <(az aks get-credentials -g CLUSTER_RESOURCE_GROUP -n CLUSTER_NAME --file -) \
  -o jsonpath='{.clusters[0].cluster.server}{"\n"}{.clusters[0].cluster.certificate-authority-data}{"\n"}'
```

### Step 2: Create a Kubeconfig per Cluster

Each kubeconfig names one cluster and runs kubelogin to get a token. Give each cluster's kubeconfig a **different user name**: the satellite caches tokens by user name.

```yaml kubeconfig-prod-eastus.yaml theme={null}
apiVersion: v1
kind: Config
clusters:
  - name: prod-eastus
    cluster:
      server: https://prod-eastus-dns-1a2b3c4d.hcp.eastus.azmk8s.io:443
      certificate-authority-data: LS0tLS1CRUdJTi...
users:
  - name: resolve-wi-prod-eastus
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        command: kubelogin
        args:
          - get-token
          - --login
          - workloadidentity
          - --server-id
          # The AKS Microsoft Entra server application ID; the same for every AKS cluster.
          - 6dae42f8-4368-4678-94ff-3960e28e3630
        interactiveMode: Never
contexts:
  - name: prod-eastus
    context:
      cluster: prod-eastus
      user: resolve-wi-prod-eastus
current-context: prod-eastus
```

### Step 3: Deploy with Helm

Bind the satellite to the federated service account, turn on workload identity for its pod, and add one Kubernetes integration per cluster.

```yaml resolve-values.yaml theme={null}
ingest:
  tokenSecretName: resolve-satellite-token

clusterName: prod-eastus
environment: production

serviceAccount:
  create: true
  name: resolve-satellite          # must match the federated credential subject
  annotations:
    azure.workload.identity/client-id: CLIENT_ID

podLabels:
  azure.workload.identity/use: "true"

integrations:
  aks-prod-eastus:                 # the cluster the satellite runs in
    type: kubernetes
    create: true
    secret:
      kubeconfig: |
        # contents of kubeconfig-prod-eastus.yaml
  aks-prod-westus:                 # another cluster
    type: kubernetes
    create: true
    secret:
      kubeconfig: |
        # contents of kubeconfig-prod-westus.yaml
```

```bash theme={null}
helm upgrade --install resolve-satellite oci://registry-1.docker.io/resolveaihq/satellite-chart \
  -n resolve-satellite --create-namespace -f resolve-values.yaml
```

> [!NOTE]
>
> When you define a `kubernetes` integration, the chart skips its default in-cluster `k8s` integration. List the host cluster explicitly, as above, to read it with workload identity.

The kubeconfigs contain no credentials, so you can also create their Secrets with your usual configuration tooling and reference them with `secretName` instead of `secret`. See [Secret Management](https://docs.resolve.ai/secret-management.md).

### Step 4: Monitor Deployment

```bash theme={null}
# Check pod status
kubectl get pods -n resolve-satellite

# Follow the satellite logs
kubectl logs -n resolve-satellite resolve-satellite-0 -f
```

***

## Resource Setup Reference

The Helm chart does not create the following resources. They must exist before deployment.

> [!NOTE]
>
> The examples use the Azure CLI and `kubectl`. You can create the same resources with Terraform or another infrastructure-as-code tool: `azurerm_user_assigned_identity` for the managed identity, `azurerm_federated_identity_credential` for the federated credential (using the cluster's `oidc_issuer_url`), and `kubernetes_cluster_role_v1` and `kubernetes_cluster_role_binding_v1` for the RBAC objects.

### 1. AKS Clusters

* **Host cluster**: The OIDC issuer and workload identity must be enabled
* **Every cluster the satellite reads**: Microsoft Entra ID integration must be enabled. Local accounts can be disabled

```bash theme={null}
az aks update -g HOST_CLUSTER_RESOURCE_GROUP -n HOST_CLUSTER \
  --enable-oidc-issuer --enable-workload-identity
```

***

### 2. Managed Identity

Create a user-assigned managed identity for the satellite.

```bash theme={null}
az identity create -g RESOURCE_GROUP -n resolve-satellite -l LOCATION

CLIENT_ID=$(az identity show -g RESOURCE_GROUP -n resolve-satellite --query clientId -o tsv)
OBJECT_ID=$(az identity show -g RESOURCE_GROUP -n resolve-satellite --query principalId -o tsv)
```

`CLIENT_ID` goes into the Helm values. `OBJECT_ID` is the user name Kubernetes RBAC sees for this identity.

***

### 3. Federated Credential

Federate the identity with the satellite's service account on the host cluster. Only a pod running as this service account, in this namespace, on this cluster can use the identity.

```bash theme={null}
ISSUER=$(az aks show -g HOST_CLUSTER_RESOURCE_GROUP -n HOST_CLUSTER \
  --query oidcIssuerProfile.issuerUrl -o tsv)

az identity federated-credential create \
  -g RESOURCE_GROUP --identity-name resolve-satellite -n resolve-satellite \
  --issuer "$ISSUER" \
  --subject "system:serviceaccount:resolve-satellite:resolve-satellite" \
  --audiences api://AzureADTokenExchange
```

***

### 4. Kubernetes RBAC

Apply this ClusterRole and ClusterRoleBinding to **each** cluster the satellite reads, including the host cluster. Replace `OBJECT_ID` with the identity's object ID. The role grants read access only and does not include Secrets.

> [accordion] resolve-satellite-reader.yaml
>
> ```yaml theme={null}
> apiVersion: rbac.authorization.k8s.io/v1
> kind: ClusterRole
> metadata:
>   name: resolve-satellite-reader
> rules:
>   - apiGroups: [""]
>     resources: ["configmaps", "endpoints", "events", "namespaces", "nodes", "persistentvolumeclaims", "persistentvolumes", "pods", "pods/log", "services"]
>     verbs: ["get", "watch", "list"]
>   - apiGroups: ["discovery.k8s.io"]
>     resources: ["endpointslices"]
>     verbs: ["get", "watch", "list"]
>   - apiGroups: ["batch"]
>     resources: ["jobs", "cronjobs"]
>     verbs: ["get", "watch", "list"]
>   - apiGroups: ["apps"]
>     resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
>     verbs: ["get", "watch", "list"]
>   - apiGroups: ["storage.k8s.io"]
>     resources: ["storageclasses"]
>     verbs: ["get", "watch", "list"]
>   - apiGroups: ["autoscaling"]
>     resources: ["horizontalpodautoscalers"]
>     verbs: ["get", "list", "watch"]
>   - apiGroups: ["apiextensions.k8s.io"]
>     resources: ["customresourcedefinitions"]
>     verbs: ["get", "list", "watch"]
>   - apiGroups: ["argoproj.io"]
>     resources: ["rollouts"]
>     verbs: ["get", "list", "watch"]
>   - apiGroups: ["networking.istio.io"]
>     resources: ["*"]
>     verbs: ["get", "list", "watch"]
>   - apiGroups: ["keda.sh"]
>     resources: ["scaledobjects"]
>     verbs: ["get", "list", "watch"]
> ---
> apiVersion: rbac.authorization.k8s.io/v1
> kind: ClusterRoleBinding
> metadata:
>   name: resolve-satellite-reader
> roleRef:
>   apiGroup: rbac.authorization.k8s.io
>   kind: ClusterRole
>   name: resolve-satellite-reader
> subjects:
>   # AKS identifies a Microsoft Entra workload identity by its object ID.
>   - apiGroup: rbac.authorization.k8s.io
>     kind: User
>     name: OBJECT_ID
> ```

```bash theme={null}
kubectl apply -f resolve-satellite-reader.yaml
```

Use your usual admin access, or `az aks command invoke` for clusters you reach only through Azure.

> [!NOTE]
>
> The built-in **Azure Kubernetes Service RBAC Reader** role is not enough on its own. It excludes cluster-scoped resources the satellite reads, such as nodes, persistent volumes, storage classes, and custom resource definitions. AKS with Azure RBAC still evaluates Kubernetes RBAC, so this binding also works on clusters that use Azure RBAC.

***

### 5. Ingest Token

The ingest token authenticates your satellite with Resolve SaaS. Generate it from **Resolve UI > Admin > Tokens** and store it in a Kubernetes Secret, as described in [Secret Management](https://docs.resolve.ai/secret-management.md#ingest-token-with-kubernetes-secret).

***

### 6. Network

* **Outbound HTTPS**: Required from the host cluster to Resolve SaaS
* **API servers**: The satellite pod must reach the API server of every cluster it reads. For private clusters, provide a route to the private endpoint, for example through VNet peering and private DNS

***

## Configuration Reference

### Helm Values

| Value | Description |
| - | - |
| `serviceAccount.name` | Service account in the federated credential subject |
| `serviceAccount.annotations."azure.workload.identity/client-id"` | Client ID of the managed identity |
| `podLabels."azure.workload.identity/use"` | Set to `"true"` so the workload identity webhook configures the pod |
| `integrations.<name>.type` | `kubernetes`, one integration per cluster |
| `integrations.<name>.secret.kubeconfig` | Kubeconfig for that cluster. Use `secretName` instead to reference an existing Secret |

### Kubeconfig Fields

| Field | Value |
| - | - |
| `users[].name` | Unique per cluster |
| `users[].user.exec.command` | `kubelogin` |
| `users[].user.exec.args` | `get-token --login workloadidentity --server-id 6dae42f8-4368-4678-94ff-3960e28e3630` |
| `users[].user.exec.interactiveMode` | `Never` |

***

## Verification

### 1. Check Pod is Running

```bash theme={null}
kubectl get pods -n resolve-satellite
```

The satellite pod should be `Running` and ready.

### 2. Check Satellite Logs

```bash theme={null}
kubectl logs -n resolve-satellite resolve-satellite-0
```

**Expected log messages**: `Resolve Satellite Tool Request` entries with `"result":"success"` for each Kubernetes integration.

### 3. Check Satellite in Resolve UI

1. Log in to Resolve UI
2. Navigate to **Admin > Satellites**
3. Verify your satellite appears with status "Connected" and each Kubernetes integration is healthy

***

## Adding a Cluster

1. Apply the [Kubernetes RBAC](#4-kubernetes-rbac) ClusterRole and ClusterRoleBinding on the new cluster
2. Create a kubeconfig for it with a new user name (see [Step 2](#step-2-create-a-kubeconfig-per-cluster))
3. Add a `kubernetes` integration for it to `resolve-values.yaml` and run the `helm upgrade` command from [Step 3](#step-3-deploy-with-helm)

The federated credential does not change: it belongs to the host cluster.

***

## Deleting the Satellite

```bash theme={null}
helm uninstall resolve-satellite -n resolve-satellite
```

The managed identity, federated credential, and RBAC objects are not managed by the Helm chart. Delete them if no longer needed:

```bash theme={null}
# On each cluster the satellite read
kubectl delete clusterrolebinding,clusterrole resolve-satellite-reader

# Delete the identity and its federated credential
az identity delete -g RESOURCE_GROUP -n resolve-satellite
```

***

## Troubleshooting

### No `AZURE_` Variables in the Satellite Pod

kubelogin fails because the pod has no workload identity configuration.

```bash theme={null}
kubectl exec -n resolve-satellite resolve-satellite-0 -- env | grep '^AZURE_'
```

| Cause | Resolution |
| - | - |
| Missing pod label | Verify `podLabels` has `azure.workload.identity/use: "true"` |
| Missing service account annotation | Verify the service account has `azure.workload.identity/client-id` |
| Workload identity not enabled | Enable it on the host cluster (see [AKS Clusters](#1-aks-clusters)) |

Restart the pod after fixing the values.

### kubelogin Fails with a Federated Credential Error

The token exchange is rejected by Microsoft Entra ID.

| Cause | Resolution |
| - | - |
| Subject mismatch | The subject must be `system:serviceaccount:<namespace>:<service account>` for the namespace and service account the satellite runs as |
| Issuer mismatch | The issuer must be the OIDC issuer URL of the host cluster |

### Forbidden: Cannot List Resource Nodes

The identity authenticates but is not authorized. Check which user the cluster sees, using the kubeconfig of the failing integration:

```bash theme={null}
kubectl exec -n resolve-satellite resolve-satellite-0 -- \
  kubectl --kubeconfig /etc/secrets/aks-prod-westus/kubeconfig auth whoami
```

| Cause | Resolution |
| - | - |
| ClusterRoleBinding missing | Apply [Kubernetes RBAC](#4-kubernetes-rbac) on that cluster, with the identity's object ID as the subject |
| Only Azure RBAC Reader assigned | The built-in role excludes cluster-scoped resources. Apply the ClusterRoleBinding as well |
| New role assignment | Azure role assignments can take a few minutes to take effect |

### Timeouts Reaching Another Cluster

Requests to one cluster time out while others succeed.

* Verify the host cluster allows egress to that cluster's API server
* For private clusters, verify there is a route to the private endpoint, for example through VNet peering and private DNS
