Azure reference
Facts to look up while deploying and running Polaris on Azure. For why Azure works the way it does, see Polaris on Azure.
What Polaris offers on Azure today
| Capability | On Azure |
|---|---|
AMD SEV-SNP confidential VMs, DCas_v6 and ECas_v6 sizes | Available |
Intel TDX confidential VMs, DCes_v6 and ECes_v6 sizes | Available. The Policy Manager records the TDX launch measurement without checking it |
Confidential NVIDIA H100 on Standard_NCC40ads_H100_v5 | Only with --local-build and a VM image you bake, because the release manifest pins no GPU build of the proxy |
| Attestation of image, runtime and kernel, with the key's role assignment removed when a layer you enforce fails | Available. The tooling deploys every layer in advise, and promote moves layers to enforce over SSH through the workload VM |
| Status routes and incident records readable through the endpoint | Available |
| Limiting inbound HTTPS to chosen address ranges | Available, and on by default |
| Deployment from the Azure Marketplace | Available as a private plan |
| A route for the workload to use its attestation-gated key | Not yet available |
| Ending key access when the Policy Manager stops or hangs | Not yet available |
Keeping what the Policy Manager learned, and the promote settings, across a restart or replacement | Available, on the Policy Manager's state disk, while the baseline is unchanged |
Reading the Policy Manager's verdicts with status | Available, over SSH through the workload VM |
| Setting enforcement, or pinning the Policy Manager, from the deployment config | Not yet available |
| More than one workload VM | Not through the tooling. The Terraform module and the Marketplace offer accept more |
| Health telemetry from the Policy Manager over OpenTelemetry | Available, with the --otel-* flags |
Regions and VM sizes
Polaris runs in any Azure region that offers the confidential VM size you choose. The tooling does not check the region itself. The default is eastus.
The CPU TEE is set by tee.cpu, and the Terraform module checks the size family against it before it creates anything.
tee.cpu | Sizes the module accepts | Default for both VMs |
|---|---|---|
sev-snp | AMD sizes whose name has as_v or ads_v in it, such as Standard_DC2as_v6 or Standard_EC2as_v6 | Standard_DC2as_v6 |
tdx | Intel sizes whose name has es_v or eds_v in it, such as Standard_DC2es_v6 | Standard_DC2es_v6 |
With tee.gpu: nvidia-h100, the workload VM must be Standard_NCC40ads_H100_v5, tee.cpu must be sev-snp, and compute.cvm_image_id must name a VM image that carries the NVIDIA driver. The Policy Manager stays on a CPU size.
Use the v6 sizes. The Policy Manager refuses AMD firmware below the levels in AMD bulletin AMD-SB-3020, and the module notes that Azure's Milan DCadsv5 fleet reports a lower level. The Terraform module can lower the floor, but the deployment config has no key for it.
| VM | Config key |
|---|---|
| Workload VM | compute.cvm_machine_type |
| Policy Manager VM | compute.pm_machine_type |
What a deployment creates
Everything below is created in the resource group the deployment owns, <name>-rg by default, where <name> is the deployment name. destroy removes it, except where noted.
Network
- A virtual network,
<name>-vnet,10.0.0.0/16, with a workload subnet<name>-sub-workload,10.0.1.0/24, and a control subnet<name>-sub-control,10.0.2.0/24, for the Policy Manager. Both subnets have aMicrosoft.KeyVaultservice endpoint, so the VMs reach Key Vault through the virtual network. - A static public IP for the workload VM,
<name>-cvm-0-pip. - A NAT gateway,
<name>-nat, with its own public IP,<name>-nat-pip, for the Policy Manager's outbound traffic. The Policy Manager has no public IP. - Two network security groups,
<name>-nsg-workloadand<name>-nsg-control, attached to each VM's network interface. Their rules are in Where a deployment connects. - Two network interfaces,
<name>-cvm-0-nicand<name>-pm-nic. Both take a dynamic private address. The Policy Manager's address is somewhere in10.0.2.0/24.
Compute
- Two confidential VMs,
<name>-cvm-0and<name>-pm, each with vTPM and Secure Boot on, aPremium_LRSOS disk with the VM guest state encrypted, boot diagnostics in managed storage, and a system-assigned managed identity. The admin user isazureuser, with SSH key login only. - Both boot Canonical's Ubuntu 22.04 confidential VM image, offer
0001-com-ubuntu-confidential-vm-jammy, SKU22_04-lts-cvm. The workload VM boots the exact versionlatestnames whendeployruns, so a new version replaces it on the nextdeploy. The Policy Manager bootslatestas it is when the VM is created. Withcompute.cvm_image_idset, the workload VM boots that image instead. - A managed disk for the Policy Manager's state,
<name>-pm-state,StandardSSD_LRS, 8 GB, attached to the Policy Manager VM at LUN 0. It is a disk of its own, so it outlives the VM, and a replacement Policy Manager attaches the same disk. It holds the Policy Manager's issuer keys, its record of the workload VMs, what it learned and thepromotesettings.destroydeletes it with the resource group.
Key Vault
- A Key Vault named
<name>-kv-<6 random characters>, on thepremiumSKU, with Azure RBAC authorization, purge protection on and a 90-day soft-delete retention. Its firewall denies by default and admits the two subnets and the public address of the machine that runsdeploy. - The workload key,
<name>-workload-key, anRSA-HSMkey of 3072 bits, allowed toencrypt,decrypt,sign,verify,wrapKeyandunwrapKey. - Secrets that the VMs read at boot:
polaris-management-token,polaris-readonly-token,polaris-workload-token,polaris-sentinel-auth-tokenandpolaris-registry-password.
destroy deletes the vault, and Azure keeps it and the key soft-deleted for the 90-day retention window. Purge protection stops anyone purging them sooner.
Identity
- A custom role,
<name>-pm-vm-reader, that allows onlyMicrosoft.Compute/virtualMachines/read, assignable within the resource group. - The role assignments in Identities and role assignments.
Created, and kept by destroy
- An Azure Container Registry on the
BasicSKU, in a resource group of its own,<registry>-rg. Its name iscloud.azure.acr_name, orpolarisfollowed by the first 10 hex characters of the SHA-256 of the subscription ID, so every deployment in a subscription shares it unless you name another. The tooling turns on the registry's admin user and uses its password both to push images and for the VMs to pull them.
Identities and role assignments
| Who | Role | Scope | Made by |
|---|---|---|---|
The identity that runs deploy | Key Vault Crypto Officer | The vault | Terraform, so it can create the key |
The identity that runs deploy | Key Vault Secrets Officer | The vault | Terraform, so it can write the secrets |
| Workload VM identity | Key Vault Secrets User | The vault | Terraform, so the VM can read its tokens at boot |
| Policy Manager identity | Key Vault Secrets User | The vault | Terraform, for the same reason |
| Policy Manager identity | Role Based Access Control Administrator, with a condition | The workload key only | Terraform |
| Policy Manager identity | <name>-pm-vm-reader | The resource group | Terraform |
| Workload VM identity | Key Vault Crypto User | The workload key only | The Policy Manager, while the VM passes |
The two assignments to the deploying identity stay in place for the life of the deployment. Key Vault Crypto Officer lets that identity use the workload key directly.
The Policy Manager's access to the key
The Policy Manager holds Role Based Access Control Administrator on the workload key alone, not on the vault. A condition on that assignment lets it create and delete only assignments of one role, Key Vault Crypto User (role definition 12338af0-0e69-4776-bea7-57ae8d297424). A create must also name one of this deployment's workload VM identities, the same list the Policy Manager is started with in PM_AZURE_ALLOWED_PRINCIPAL_IDS. A delete is limited by role only, so when it starts the Policy Manager can delete a stray Polaris assignment whatever principal it names.
When a workload VM passes, the Policy Manager assigns Key Vault Crypto User on the key to that VM's managed identity. The assignment's name is derived from the VM's cvm_id, and its description is polaris:cvm=<cvm_id>. The Policy Manager deletes the assignment when a layer you enforce fails, or when the VM has not passed within the grant lifetime, attestation.grant_ttl. Azure gives a role assignment no expiry of its own, so the lifetime is kept by the Policy Manager. When it starts, the Policy Manager deletes Polaris assignments on the key that it is not tracking.
The Policy Manager's other roles
- Key Vault Secrets User on the vault, to read its tokens at boot. The workload key is a key, not a secret, so this role does not reach it.
<name>-pm-vm-readeron the resource group. The Policy Manager looks up which managed identities Azure assigned to the VM named in the hardware evidence, through Azure Resource Graph, and requires the identity token the VM presented to be one of them.
Where a deployment connects
| From | To | For |
|---|---|---|
The machine that runs deploy | Azure APIs in your subscription | Creating and checking the deployment |
The machine that runs deploy | api.ipify.org | Finding its own public address, which the Key Vault firewall and, by default, the workload endpoint admit |
The machine that runs deploy | The Key Vault data plane | Creating the key and writing the secrets |
The machine that runs deploy | ghcr.io, Fr0ntierX's container registry | Copying the Polaris images into your registry, pinned by digest |
| Workload VM | Any address on the internet, on ports 443 and 80 | Pulling images, package installs at first boot, and your workload's outbound traffic |
| Workload VM | The ranges in network.allowed_egress_cidrs, on any port | Your workload's other outbound traffic |
| Workload VM | The control subnet, on 443 and on 8443 | Reports to the Policy Manager, and the operator tunnel to it |
| Policy Manager | Any address on the internet, on ports 443 and 80, through the NAT gateway | Azure APIs, AMD's Key Distribution Service, Microsoft's token signing keys and the registry |
Inbound, the workload VM accepts HTTPS on port 443 from the ranges in network.workload_ingress_cidrs, or from the public address of the machine that ran deploy when that list is empty. It accepts SSH on port 22 from cloud.azure.operator_ssh_cidrs, and from nowhere while that list is empty. The Policy Manager accepts ports 443 and 8443 from the workload subnet and refuses them from the rest of the virtual network. SSH to it from inside the virtual network goes through Azure's default rule, which is how the tooling reaches it through the workload VM.
Every other destination on the internet is denied in both directions. Traffic inside the virtual network, Azure DNS and the instance metadata service are outside the rule that denies the internet.
Neither VM sends telemetry. The deployment creates no role assignment for anyone outside your tenant.
Permissions
The identity that deploys
The identity that runs deploy and destroy needs to create and delete, in your subscription:
| Area | What it creates | Used for |
|---|---|---|
| Resource groups | The deployment's resource group and the registry's | Holding everything else |
| Compute | Virtual machines and their disks, and reading their boot diagnostics | The two VMs, and reading each VM's SSH host keys from its console |
| Network | Virtual networks, subnets with service endpoints, network security groups and rules, public IPs, NAT gateways and network interfaces | The network |
| Key Vault | Vaults, and through the role assignments below, keys and secrets | The vault, the workload key and the tokens |
| Authorization | Role definitions and role assignments, so Microsoft.Authorization/roleDefinitions/write and Microsoft.Authorization/roleAssignments/write, and the matching deletes | The custom role and the six assignments in Identities and role assignments |
| Container Registry | Registries, turning on the admin user, reading its credentials, and pushing images | Copying the Polaris images in, and the VMs' pulls |
The deployment's resource group does not exist until deploy creates it, so the role assignments have to be allowed at the subscription. The module's notes call for Owner or User Access Administrator at subscription scope, and say that Contributor is not enough, because the deployment creates a role definition and role assignments.
An identity that can create role assignments can give any identity any role, so treat the deploying identity as an administrator of the subscription.
What the deployment does not need
The deployment creates its own Key Vault and key, so there is no existing key to grant access to, and no key policy to edit.
Deployment config keys
The keys specific to Azure, under cloud.azure:
| Key | Required | Default | Meaning |
|---|---|---|---|
subscription_id | Yes | The subscription to deploy into. The tooling switches the Azure CLI to it | |
tenant_id | No | from az account show | The Microsoft Entra tenant. The Policy Manager checks it in every identity token |
resource_group | No | <name>-rg | The resource group the deployment creates and owns |
location | No | eastus | The Azure region |
ssh_public_key_file | No | ~/.ssh/id_rsa.pub | The public key installed for azureuser on both VMs |
ssh_private_key | No | ssh_public_key_file without .pub | The private half, used to reach the Policy Manager through the workload VM |
operator_ssh_cidrs | No | empty | Address ranges allowed to SSH to the workload VM. While it is empty, SSH opens to nobody, deploy skips pinning the SSH host keys and capturing the Policy Manager's identity record, and status and promote cannot reach the Policy Manager |
acr_name | No | derived from the subscription ID | The container registry to create or reuse |
id_key_digests | No | the one Microsoft digest the tooling knows | The paravisor launch authorities the Policy Manager accepts on SEV-SNP, as 96 hex characters each. Append a new one rather than replacing the list |
The module rejects, in operator_ssh_cidrs, network.workload_ingress_cidrs and network.allowed_egress_cidrs, any entry that is not an address range with a prefix, and any /0 or /1 range.
Keys shared with the other clouds that apply on Azure:
| Key | Default | Meaning on Azure |
|---|---|---|
metadata.name | The deployment name. On Azure, a lowercase letter, then lowercase letters, digits or hyphens, 12 characters at most, because the Key Vault name is built from it | |
tee.cpu | sev-snp or tdx | |
tee.gpu | none | none or nvidia-h100 |
compute.cvm_machine_type | Standard_DC2as_v6, or Standard_DC2es_v6 for tdx | Workload VM size. See Regions and VM sizes |
compute.pm_machine_type | Standard_DC2as_v6, or Standard_DC2es_v6 for tdx | Policy Manager VM size |
compute.cvm_image_id | A Compute Gallery image or image version for the workload VM. Required with tee.gpu: nvidia-h100 | |
network.workload_ingress_cidrs | the deploying machine's public address | Ranges allowed to reach the endpoint on port 443 |
network.allowed_egress_cidrs | empty | Extra ranges both VMs may reach, on any port and protocol |
features.ebpf | false | Runs the Sentinel agent on the workload VM, which collects runtime and kernel evidence. Set it to true for runtime and kernel attestation |
features.ima | false | Has no effect on Azure. The Sentinel collects the kernel's measurements of every executable whenever it runs |
features.attestation_mode | full | Has no effect on the Terraform the Azure deploy writes, which always deploys full. Leave it at full |
workload.image | A prebuilt workload image. The VMs log in to the deployment's container registry, so an image there needs nothing else. An image elsewhere has to be public | |
workload.dir | In place of workload.image, a folder with a Dockerfile, relative to the top folder of the tooling. deploy builds it for linux/amd64 and pushes it to the deployment's registry | |
workload.port | 3000 | The port your workload listens on, over plain HTTP |
workload.env | empty | Environment variables for the workload. They are written into the VM's custom data, so keep secrets out |
workload.resources.cpus, workload.resources.memory_mb | no limit | Hard limits for the workload container |
workload.security.read_only | false | Runs the workload with a read-only filesystem and a small /tmp |
workload.security.capabilities | empty | Linux capabilities to add back to the workload, which otherwise runs with none |
license.file | A license file for a direct contract. See Licensing | |
polaris.docker_repo | polaris-docker | The path the Polaris images are copied to inside the registry |
attestation.report_interval | 30 | Seconds between evidence reports, 5 to 300 |
attestation.deadman_interval | 240 | Seconds without a fresh report before the Policy Manager deletes a VM's role assignment, 30 or more |
attestation.grant_ttl | 300 | Lifetime of a role assignment in seconds, 60 or more, renewed by each passing report. The module sets 300 because a new assignment can take up to about five minutes to take effect on the Key Vault data plane |
attestation.autolearn_window | 10m | How long the learners must watch before they can finish |
attestation.promotion_window | 1d | How long a layer must be clean under advise before the Policy Manager lets it enforce |
attestation.trust_domain | polaris.<name> | The name the Policy Manager writes into every certificate it issues. It is stored on the Policy Manager's state disk, so set it before the first deploy, if at all, and never change it afterwards |
attestation.deployment_id | derived from the trust domain | A UUID naming the deployment in the Policy Manager's record of its workload VMs. The Policy Manager refuses to start when its state disk holds a record under another ID, so never change it, or the trust domain it is derived from, once the deployment exists |
compute.cvm_count and kms.azure are accepted by the config schema and not read on Azure. A deployment has one workload VM, and the vault and key names come from the deployment name.
Commands
All but watch and set-workload take --config <file>.
| Command | What it does on Azure |
|---|---|
deploy | Creates the deployment, or updates it. A VM is replaced when its rendered boot configuration changes. A new workload VM has a new managed identity, which the Policy Manager's boot configuration names, so it replaces the Policy Manager too. A new version of Canonical's image replaces the workload VM, and so the Policy Manager, on a plain run. --force-replace recreates both VMs even when nothing changed |
status | Lists the VMs in the deployment's resource group whose names start with <name>-, with size, power state and addresses, then reads each workload VM's verdict, the license state and how far learning has got from the Policy Manager, over SSH through the workload VM. Without cloud.azure.operator_ssh_cidrs and the pinned host keys, it says why it did not read the Policy Manager |
promote | Moves enforcement layers between advise, enforce and off, in place, over SSH through the workload VM. Needs cloud.azure.operator_ssh_cidrs with your address and the pinned host keys. --layers takes a comma-separated list of l2, l3, l31, ima_boot, l3_syscalls, l3_binaries and l3_network, and defaults to l31. --mode defaults to enforce. It refuses to enforce a layer whose allowlist is still empty, and only warns for l3_binaries, unless you pass --force. The Policy Manager also refuses enforce for a layer that has not been clean under advise for the promotion window. promote reads the settings back to confirm them |
watch | Shows each layer's status and how far learning has got, through the endpoint. Takes --endpoint https://<address> and --token <token>, and works with the read-only token |
set-workload | Points the running deployment at a new workload image, pinned by digest, through the endpoint. Takes --endpoint, --token and --image |
destroy | Runs terraform destroy, which removes the resource group and everything in it |
deploy also takes --otel-endpoint, --otel-protocol, --otel-headers, --otel-temporality and --otel-egress-cidrs on Azure, which send the Policy Manager's health metrics to an OpenTelemetry collector. They are off by default, and nothing exports from the workload VM.
The Azure deployment leaves ima_boot unset, so it follows l2. The Policy Manager keeps the settings promote makes, and everything it learned, on its state disk while the baseline file stays the same. A deploy that changes the baseline, such as a new workload image digest, a new workload port or a new VM image, returns every layer to the settings in the baseline file and starts learning again.
Routes on the endpoint
The proxy on the workload VM serves these on port 443.
| Route | Token | Returns |
|---|---|---|
/polaris-container/health | none | {"status":"ok"} while the proxy runs |
/polaris-container/ready | none | Whether the proxy is serving workload traffic, as ready and gate |
/polaris-container/attestation | none | The hardware evidence, for a client to check |
/polaris-container/tls-info | none | The hash of the proxy's TLS public key |
/polaris-container/attestation-status | none | The latest verdict, with a status for each layer and checked_at |
/polaris-container/attestation-status | read-only | The same, with the reason for a failure and the alerts |
/management/status | read-only | The Policy Manager's status for each workload VM, including its cvm_id |
/management/profiles/learn, /management/ima/learn and /management/kernel/learn | read-only | Each learner's progress |
/management/incidents/<cvm_id> and /management/incidents/<cvm_id>/report | read-only | The VM's recent incidents, as JSON or as a plain-text report |
/management/checks/<cvm_id> | read-only | The latest checks, one by one |
/management/history/<cvm_id> | read-only | Recent verdicts |
/management/workload | workload or management | The workload image the deployment should run. set-workload posts to it |
The read-only token answers GET requests only. The management token answers every method on /management/.
The workload has no key routes on Azure. See Use the gated key from your workload.
Files the tooling keeps
Run every command from the copy of the tooling that deployed, since it holds these files. The state, the tfvars file and the token files carry the deployment's tokens in plain text, and the tfvars file also carries the registry's admin password, so keep the folder encrypted and access-controlled.
| File | Holds | Removed by destroy |
|---|---|---|
terraform/deploy/azure/terraform-<name>.tfstate | The Terraform state, including every token | No |
terraform/deploy/azure/terraform-<name>.tfvars | The deployment's resolved configuration, including the management and read-only tokens and the registry password | No |
terraform/modules/azure/terraform-<name>.token | The management token | Yes |
terraform/modules/azure/terraform-<name>.readonly.token | The read-only token, used by watch and the management routes | Yes |
terraform/deploy/azure/terraform-<name>.known_hosts | The pinned SSH host keys of both VMs, filed under each VM's vmUniqueId | No |
terraform/deploy/azure/pm-identity-<name>.json | The record Polaris Desktop checks the Policy Manager against | No |
The workload-swap token is generated by Terraform and is not written to a file. Read it from the state, or use the management token in its place:
cd terraform/deploy/azure
terraform output -state=terraform-<name>.tfstate -raw workload_token
The Azure Marketplace offer
Polaris is listed on the Azure Marketplace as an Azure Application, a solution template, on a private plan. Only the tenants and subscriptions Fr0ntierX adds to the plan's audience can deploy it, and the offer is not shown publicly. Billing is under your contract, not through the Marketplace.
The template creates the same resources as the Terraform module, into a resource group you choose, with these differences:
- It creates no resource group of its own.
- It creates the key and secrets through Azure Resource Manager, so the deploying identity gets no Key Vault Crypto Officer or Secrets Officer assignment, and the vault firewall admits only the two subnets and any addresses you list.
- The Policy Manager has a fixed private address,
10.0.2.4. - The VMs boot a Polaris image with the proxy, Policy Manager and Sentinel built in, so only your workload image is pulled at boot.
- The image carries no license. Paste the license file Fr0ntierX sent you into the form's License step. See Licensing.
- The form asks for the number of workload VMs, the CPU TEE and sizes, the client ranges allowed on 443, the SSH ranges, whether to run the Sentinel, an enforcement mode of alert or enforce, and an optional registry for a private workload image.
The identity that deploys needs to create a role definition and role assignments in the resource group, so Owner or User Access Administrator there.
After a deployment, the template's outputs include the endpoint and the az keyvault secret show commands for the tokens. To run them from outside the virtual network, your address has to be in the template's keyVaultAllowedIps, since the vault firewall refuses any other, and your identity needs a Key Vault role that can read secrets, since the template assigns none to you.