This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

XCP-ng

XCP-ng infrastructure

Terraform in tf manages a Xen Orchestra resource set for every entity in Vault’s approles group, following infra/pve/tf. Resource sets are the XO grouping and delegation mechanism; XCP-ng host pools represent physical clusters and are not created per AppRole.

resource_set_inventory assigns a pool, template, storage repository and network by name to each AppRole. Native provider lookups resolve their IDs and reject ambiguous matches. Forgejo and OpenHands have assignments by default; other sets remain empty. Subjects are the exact synchronized OIDC users selected by Vault issuer and immutable AppRole entity UUID. Bootstrap has synchronized the 18 AppRole users; new AppRoles need their first OIDC login before Terraform can bind their sets. resource_set_cpu_limit defaults to 32 CPUs per set. Creating a set does not allocate CPUs or storage.

The provider is pinned to vatesfr/xenorchestra 0.41.0. The single infra/xcp_ng/al.lua configuration supplies administrator authentication for XCP-ng infrastructure operations: its named xcp_ng Vault AppRole injects XOA_TOKEN from key xoa_token in secrets/alwaldend.com/vault1/approles/src_infra_xcp_ng/xoa. Forgejo instead uses its own Vault identity through the XO login plugin, which issues an invocation-scoped XO session and revokes it on shutdown. It retains its own Terraform backend and does not inherit the infrastructure administrator token. Token values are not Terraform inputs or outputs.

The default endpoint is wss://xoa.xcp-ng.alwaldend.com. DNS maps the XO appliance to 192.168.1.206 and the XCP-ng host to 192.168.1.213. TLS verification is enabled. xoa_insecure is an explicit temporary bootstrap override for an appliance whose certificate has not been replaced. The HTTP backend stores state under the AppRole’s tf_backend Vault path.

infra/vault/tf owns the src_infra_xcp_ng_provider OIDC client, the src_infra_xcp_ng_users and src_infra_xcp_ng_admins groups, and the src_infra_xcp_ng_pki_server ACME role. Apply those scoped prerequisites before the XO configuration. The callback is https://xoa.xcp-ng.alwaldend.com/signin/oidc/callback.

XO must have the auth-oidc plugin installed and trust Vault’s CA. The Terraform provider has no plugin resource, so terraform_data.oidc invokes the packaged Go JSON-RPC helper directly, without a shell. It configures Vault discovery, the client, and the user groups scopes, enables autoload, and verifies the plugin is loaded. The OIDC client secret remains sensitive and is stored in the Vault-protected Terraform state. The provisioner signs the XCP-ng AppRole in through Vault to create the synchronized OIDC groups. Plugin reconciliation runs when configuration or the helper binary changes, without native drift refresh. Use -replace=terraform_data.oidc to reapply after manual plugin drift.

Native xenorchestra_acl resources manage the administrators group’s pool permissions. An import block adopts matching existing ACLs using their discovered API IDs. The helper only reads these ACLs; it does not change them. The users group grants login eligibility, with membership refreshed at login. Per-AppRole resource-set membership and existing VM ownership use direct user subjects. The Forgejo user receives administration permission on its own VM; no shared pool-wide view grant is configured.

The helper remains for gaps in the pinned provider: OIDC plugin configuration and bootstrap, discovery of external identity subjects and synchronized groups, host registration, prepared VHD template import, and diagnostics. Resources, ACLs and named infrastructure lookups use the native provider.

The resource-set management page is Self Service. In the deployed XO source revision 961b505cfb74cbf24aaeb8d61c5613f97e78de2c, its menu entry requires a global administrator. The page also requires XOA_PLAN > 3, corresponding to Premium or Community. The runtime edition has not been verified. Pool-level administrator ACLs do not satisfy the page’s global-administrator requirement.

The certificate playbook uses Certbot with Vault EAB, installs XO’s Vault CA trust, and configures renewal. It requires appliance SSH access and inspected HTTPS certificate paths. Initial issuance and renewal briefly interrupt XO management access, not running guest VMs.

The XO server must be connected to an XCP-ng host before discovering templates or creating VMs. xo_inspect prints selected operational metadata without credentials or plugin configuration:

XOA_URL=wss://xoa.xcp-ng.alwaldend.com bazel_agent bazel run //infra/xcp_ng:xo_inspect
bazel_agent bazel run //infra/xcp_ng/tf:tf.plan
bazel_agent bazel run //infra/xcp_ng/tf:tf.apply
bazel_agent bazel test //infra/xcp_ng/tf:tf_tests.fmt_test //infra/xcp_ng/cmd/xo_config:xo_config_test

Formatting and helper tests run without live authentication. See Forgejo provisioning for VM prerequisites.

To register host1, store its xcpng_username and xcpng_password fields in secrets/alwaldend.com/vault1/approles/src_infra_xcp_ng/host1, then run:

XOA_URL=wss://xoa.xcp-ng.alwaldend.com XCPNG_HOST=192.168.1.213 bazel_agent bazel run //infra/xcp_ng:xo_register_host

The command uses the XOA token to register the host and supplies its separate host credentials through AL. It waits for a connected pool and leaves an already connected host unchanged. Duplicate registrations are rejected. XCPNG_INSECURE=true explicitly allows the host’s bootstrap certificate; XOA_INSECURE=true separately controls verification of XO’s certificate.

//infra/xcp_ng:xo_import_template imports the repository-pinned image from //third_party/org_fedora_cloud through XO’s disk-upload API. Supply XO_TEMPLATE_SHA256 from the image’s pinned checksum, XO_TEMPLATE_SR, XO_BASE_TEMPLATE_ID (a diskless Generic Linux BIOS template), and XO_TEMPLATE_NAME, together with XOA_URL. The host must support QCOW2 imports. The command verifies the image before creating anything, creates an unbooted template with only a boot disk at device 0, and verifies the final disk layout. It rejects incomplete or conflicting prior imports for inspection. This keeps cloud-init fresh for cloned VMs and prevents empty CD drives from shifting the Forgejo data disks to unexpected device names.

Older XO releases, including 5.192.1, require VHD rather than QCOW2 uploads. For those releases, convert the pinned image to dynamic VHD in task-private scratch, verify the decoded contents against the original with qemu-img compare, and record the converter version and converted file’s SHA256. Use //infra/xcp_ng:xo_import_prepared_template with XO_TEMPLATE_IMAGE set to the absolute converted path, XO_TEMPLATE_FORMAT=vhd, and XO_TEMPLATE_SHA256 set to the converted checksum. The other inputs are the same as for the pinned-image importer.

An interrupted import can resume on an inspected empty, halted task VM by setting XO_TEMPLATE_RESUME_ID, XO_TEMPLATE_PREVIOUS_FORMAT, and XO_TEMPLATE_PREVIOUS_SHA256 to its exact prior identity and image marker. Resume preserves that VM and refuses any attached disk or network interface.

1 -

XCP-ng infrastructure Specification

Specify Xen Orchestra resource delegation, scoped authentication, and certificate deployment owned by infra/xcp_ng. The baseline is checked-in source at revision 550d7e79b1f5fdbc2b6017b75178471d6914082f, observed on 2026-09-08. Existing operational observations in project documentation are historical context; this baseline does not verify current deployment, edition, identity synchronization, or service health.

Terraform SHALL declare one Xen Orchestra resource set per entity in Vault’s approles group. Each set SHALL contain its explicitly assigned template, storage repository, and network resolved by names within the selected pool; unassigned sets SHALL contain no inventory objects. Each set SHALL have a positive integer CPU quota, defaulting to 32. These resource sets SHALL represent delegation groups rather than new physical host pools.

Sources: resource sets, named inventory, and project contract.

  • WHEN an AppRole exists in Vault’s group but has no resource_set_inventory entry
  • THEN its resource set contains no assigned inventory objects and retains the configured CPU quota.

Resource-set membership SHALL select the exact synchronized OIDC user by Vault issuer and immutable AppRole entity UUID. AppRoles without synchronized users SHALL be reported in approles_pending_oidc_login and have no resource-set subjects. Existing-VM owner ACLs SHALL require exactly one VM matching the hostname and owning AppRole tag within its named pool, and SHALL grant admin to the owner’s synchronized user. Unknown owners, ambiguous VMs, or missing owner synchronization SHALL fail planning. Native Terraform ACL resources SHALL separately manage the synchronized administrators group’s pool grants.

Sources: identity contract, owner ACLs and preconditions, resource-set subjects, and administrator ACLs.

  • WHEN identity discovery cannot find its synchronized OIDC subject
  • THEN its resource set has no subjects and its name appears in the pending login output; an existing-VM ownership grant for it fails its precondition.
  • WHEN hostname and AppRole tag matching in the named pool returns zero or multiple VMs
  • THEN Terraform rejects the existing-VM ownership configuration.

The XO login plugin SHALL authenticate using the calling component’s Vault identity and export XOA_TOKEN, XOA_URL, and XOA_INSECURE=false to the invoked process. XO HTTPS verification SHALL be mandatory for this plugin. Session cookies and tokens SHALL remain in process memory, and normal plugin shutdown SHALL revoke only the XO token issued by that invocation. Tenant authentication SHALL remain separate from the infrastructure administrator token. Forced termination or a lost callback can leave a session until XO’s configured expiry and SHALL not be described as guaranteed revocation.

Sources: XO login contract and authentication ownership.

  • WHEN normal plugin shutdown follows a successful XO login
  • THEN the plugin revokes its issued session using token.deleteOwn without revoking unrelated sessions.

The XO appliance playbook SHALL use Certbot with Vault external account binding, retain the inspected listener certificate paths, install service-specific Vault CA trust, and configure twice-daily renewal checks. The host playbook SHALL use its packaged compatibility helper and the checksum-verified Lego release to install certificates through XAPI’s supported certificate-install command. Temporary EAB registration material SHALL be removed after use. Appliance renewal may interrupt XO management access; host renewal SHALL not stop XAPI or running VMs.

Sources: certificate deployment contract and packaged playbooks and runtime inputs.

  • WHEN the renewal helper obtains a certificate different from the installed certificate
  • THEN it uses xe host-server-certificate-install, with installation retry tracked independently of issuance, while preserving running VMs.

2 -

owned-dns Specification

Define XCP-ng infrastructure DNS management through the owner’s tf root, including canonical declarations, scoped execution, and offline source checks through preparation, adoption, and ongoing reconciliation.

The tf root SHALL consume this owner’s canonical dnsconfig.json through the shared DNS Terraform module, preserving declared record identities and views. DNS resources SHALL default to disabled until the authorized adoption revision and SHALL retain enabled ownership after existing records are imported into the owner’s state. Reconciliation against unchanged declarations and adopted state SHALL propose no record additions, changes, replacements, or deletions.

  • WHEN the checked-in root is evaluated with default inputs before its authorized adoption revision
  • THEN it reads this owner’s declaration and disables managed DNS records
  • AND the package contains the module and declaration inputs
  • WHEN the authorized adoption audits competing writers, controls those identified within its observed scope, and enables the owner’s record management
  • THEN the owning root imports existing records by exact provider ID into its state while preserving their declared identities and views
  • WHEN the adopted root uses its checked-in source defaults
  • THEN DNS ownership remains enabled and the package retains the module and canonical declaration inputs
  • WHEN the owning root plans against its adopted state and unchanged declarations
  • THEN it proposes no record additions, changes, replacements, or deletions

The DNS-only plan, show, and saved-plan apply commands SHALL select dns=1 through the repository AL flow, using src_infra_xcp_ng through its named xcp_ng Vault authentication. They SHALL target module.dns in the existing tf root and backend and keep secret values in injected variables. The ordinary service wrappers SHALL retain their existing xcp_ng_tf, xoa, and Vault environment labels and authentication behavior. Real DNS credentials and Vault policy grants SHALL be prerequisites for operational Terraform calls. RouterOS DNS credentials SHALL be isolated from unrelated RouterOS resources. The package SHALL expose a format test that does not authenticate to Vault or contact DNS providers. Successful DNS-only execution SHALL establish only the scoped DNS result, not full service health.

  • WHEN the package format test executes
  • THEN it checks the packaged Terraform configuration without live credentials
  • AND existing non-DNS authentication and backend paths remain unchanged
  • WHEN the DNS wrapper selects dns=1
  • THEN it selects DNS credential injection through the owner AppRole while preserving the existing root and backend without starting XO authentication
  • AND disabled DNS resources do not imply offline provider configuration
  • WHEN the operator reviews and applies a saved plan through the DNS-only plan, show, and apply commands
  • THEN the operation is limited to module.dns and its dependencies
  • AND its success does not establish the health of the other services in the root

3 - Terraform

AppRole resource sets and Vault-backed Xen Orchestra authentication

This configuration manages one Xen Orchestra resource set per Vault AppRole and configures the installed XO OIDC plugin through a packaged JSON-RPC helper. See the project documentation for requirements and checks.

Resource-set subjects reference exact synchronized OIDC users whose OIDC subject identifiers match Vault AppRole entity UUIDs. AppRole groups can include other service entities, so using those groups would grant access across deployments. Bootstrap XO’s OIDC configuration first, then log in through OIDC as each AppRole and run the full Terraform apply. Identity discovery runs at plan time and never creates users. AppRoles that have not logged in are listed in approles_pending_oidc_login; their sets remain without subjects until the next apply after login. Groups remain responsible for login entitlement and administrative access, separately from these per-AppRole grants.

resource_set_inventory supplies pool, template, storage and network names per AppRole. Native data sources resolve a unique pool and unique objects within that pool. Unassigned resource sets remain empty. These inputs have no UUID defaults; resolved IDs remain necessary for XO API operations.

existing_vms maps hostnames to an owning AppRole and pool name. Discovery requires exactly one VM with both the hostname and AppRole tag in that pool. The owner receives an explicit admin ACL. Resource-set membership permits provisioning and does not transfer existing VM ownership. Missing or ambiguous VMs, unknown AppRoles and unsynchronized OIDC users fail planning. ACL resource keys retain the AppRole and resolved VM ID, preserving their addresses when the same existing VMs are found through names.

Native xenorchestra_acl.pool_administrator resources manage the synchronized administrators group’s permissions on connected pools. The read-only helper identifies existing matching ACLs, and an import block adopts them without recreating access. Fresh installations must bootstrap terraform_data.oidc before the full plan so the synchronized groups exist. There is no shared pool-view grant and the helper performs no ACL writes.

The pinned hashicorp/external provider calls the packaged helper’s read-only oidc-identities command. XO exposes login names as email and authentication providers in authProviders; the helper accepts only OIDC-authenticated identities from the configured Vault issuer and indexes them by authProviders["oidc:" + issuer].id, the Vault entity UUID. Duplicate OIDC subjects are an error. Mutable login names and inherited group membership do not select the ACL subject. Group discovery selects only synchronized OIDC groups and rejects duplicate names. The provider has no group datasource and its user datasource does not expose immutable external subjects. API credentials remain in the injected environment.

Use this package’s dns.plan, dns.show, and dns.apply targets for the scoped DNS adoption workflow. These commands select dns=1 and target module.dns in this same root and backend, using the existing src_infra_xcp_ng AppRole through named xcp_ng Vault authentication. The ordinary service wrappers retain their XO authentication and labels. A successful DNS-only operation verifies its scoped DNS result; full service health requires the service’s own checks.

DNS ownership defaults to enabled after the completed record adoption. Keep that default enabled to retain the imported records.

4 - Xen Orchestra certificates

Vault ACME certificate issuance and renewal for the XOA appliance

This playbook installs Debian’s Certbot package and obtains a certificate for xoa.xcp-ng.alwaldend.com and host1.xoa.xcp-ng.alwaldend.com from the Vault src_infra_xcp_ng_pki_server ACME role. The XCP-ng AppRole creates the external account binding; its registration file is removed after issuance.

The target injects xoa_ssh_password from secrets/alwaldend.com/vault1/approles/src_infra_xcp_ng/xoa for the appliance’s xoa SSH account and sudo. Ansible uses its sshpass password mechanism; the controller must provide sshpass and a verified SSH host key. Passwords are runtime environment inputs and are not stored in inventory.

Run the read-only inspection target before deploying a different appliance:

bazel_agent bazel run //infra/xcp_ng/ansible:ansible.inspect
bazel_agent bazel run //infra/xcp_ng/ansible:ansible

The checked-in settings match the inspected appliance: xo-server runs as root and listens on HTTPS with /etc/ssl/cert.pem and /etc/ssl/key.pem. Native autoCert is disabled. The deployment preserves these listener paths. Apply the Vault PKI role before certificate issuance.

Certbot uses standalone HTTP-01 validation. Vault must resolve both names to the appliance and reach TCP port 80. Issuance and renewal briefly stop xo-server to free that port, interrupting management access but not guest VMs. The existing TLS files are preserved with .before-vault-acme suffixes before their paths become symlinks to Certbot’s renewable files. The dedicated xo-acme-renew.timer checks twice daily. Certbot is configured to renew the seven-day Vault certificates with two days remaining; stop/start hooks run when renewal is needed. Certificate keys and ACME account files remain root-only.

The playbook adds a service-specific Vault CA trust override using NODE_EXTRA_CA_CERTS so XO can also validate Vault’s OIDC endpoint. It verifies the appliance HTTPS endpoint against that CA after deployment.

The deployment target is //infra/xcp_ng/ansible:ansible. Building //infra/xcp_ng/ansible:ansible_bin packages the playbook without deploying it.

XO’s native ACME implementation does not pass EAB credentials to its ACME client. Certbot supports external account binding without changing Vault’s mandatory-EAB policy.

//infra/xcp_ng/ansible:ansible.host provisions a Vault ACME certificate for host1.xcp-ng.alwaldend.com and xcp-ng.alwaldend.com. Inspect a different host with //infra/xcp_ng/ansible:ansible.inspect_host before deployment. The host must serve /opt/xensource/www/.well-known/acme-challenge through HTTP or an HTTP-to-HTTPS redirect; the inspection verifies and cleans up a public probe file. This host supports that path without changing XAPI settings.

XCP-ng’s Python 3.6 cannot run the current Ansible modules. The playbook uses Ansible’s script transport for a Python 3.6-compatible helper, without adding packages to dom0. The helper downloads the immutable Lego release declared in third_party/com_github_go_acme_lego_bin/binary_toolchain.json, verifies its checksum, and installs it under root-only /etc/xcp-ng-acme.

The initial EAB is temporary. Only the ACME account remains for unattended renewal; no Vault token is retained on the host. The client requests RSA4096 keys to match the Vault role. xcp-ng-acme-renew.timer runs twice daily and uses XAPI’s supported xe host-server-certificate-install command when the issued certificate differs from the installed certificate. Installation is retried independently of issuance. Renewal does not stop XAPI or running VMs. The final deployment verifies HTTPS and enables the timer.

Set an absolute task-private TMPDIR for controller registration scratch. Ansible removes the temporary registration script after execution. The bootstrap tag updates the helper without ordering a certificate.

The XO playbook installs the official xo-server-auth-oidc 0.4.0 plugin with pinned, checksum-verified upstream files and isolated dependencies. This version supports Vault group synchronization while remaining compatible with the installed XO core. Ansible verifies the plugin before restarting XO; Terraform configures it and reconciles group ACLs through the API.

SSH connection reuse keeps deployment within the appliance’s existing UFW limit of six new SSH connections per 30 seconds. No firewall exception is required.