Skip to content

feat(nutanix): support API key auth and custom HTTP headers#358

Open
andrew-sumner wants to merge 4 commits into
nutanix-cloud-native:mainfrom
andrew-sumner:issue/api-key-and-custom-headers
Open

feat(nutanix): support API key auth and custom HTTP headers#358
andrew-sumner wants to merge 4 commits into
nutanix-cloud-native:mainfrom
andrew-sumner:issue/api-key-and-custom-headers

Conversation

@andrew-sumner

@andrew-sumner andrew-sumner commented May 1, 2026

Copy link
Copy Markdown

Closes #357

What this PR does / why we need it:

Adds two new builder configuration fields to authenticate with Prism Central:

  • nutanix_api_key — when set, requests use the X-ntnx-api-key header in place of HTTP Basic auth. Falls back to the NUTANIX_API_KEY environment variable.
  • nutanix_custom_headers — extra HTTP headers attached to every Prism Central request (REST API, VNC console websocket, and Objects Lite S3 uploads), intended for environments fronted by a reverse proxy that requires additional auth headers (e.g. Cloudflare Access service tokens). Headers can also be supplied via NUTANIX_HEADER_* environment variables; the prefix is stripped, underscores become dashes, and each segment is title-cased (NUTANIX_HEADER_CF_ACCESS_CLIENT_ID -> Cf-Access-Client-Id). Config values take precedence over env vars.

Authentication validation now accepts either nutanix_api_key or the existing nutanix_username + nutanix_password pair. If both are provided, nutanix_api_key wins and a warning is emitted. The legacy service-account form documented today (nutanix_username = "X-ntnx-api-key", nutanix_password = <key>) keeps working for backwards compatibility; the docs now steer users to the new field.

Brings the plugin to parity with the equivalent changes in the sibling Nutanix projects:

Header propagation across all paths

The plugin communicates with Prism Central over three distinct transports. All three now honour nutanix_api_key and nutanix_custom_headers:

Path Purpose How headers are applied
REST API (v4) VM create, image metadata, task polling applyCustomHeaders on each SDK ApiClient via AddDefaultHeader
VNC console websocket boot_command keystroke injection step_vnc_connect.go copies CustomHeaders + APIKey onto the WSS upgrade http.Header
Objects Lite S3 source_image_path / cd_content image upload CreateImageFile builds the AWS HTTP client with a headerInjectingTransport that carries CustomHeaders

V3 API exclusion: The legacy V3 client is not wired with nutanix_api_key or nutanix_custom_headers — the V3 API is deprecated and will be removed in a future release. The only remaining V3 call is project lookup; this will not work through a proxy that requires custom headers.

Objects Lite caveat: Objects Lite always validates the AWS V4 signature against Prism Central's user table. nutanix_username/nutanix_password are required for uploads even when nutanix_api_key handles all other API calls. Users can avoid the Objects Lite path entirely by using source_image_name or source_image_uri instead of source_image_path.

Windows guest customization: windows_install_type

New config option windows_install_type controls the Nutanix V4 API InstallType field for Windows Sysprep guest customization (user_data with os_type = "Windows"):

  • PREPARED (default) — for sysprepped template/clone deployments. AHV delivers the unattend as Unattend.xml and may auto-restart the VM to apply customization.
  • FRESH — for ISO-based fresh installs. AHV delivers the unattend as Autounattend.xml (discoverable by Windows PE) and does not auto-restart the VM after shutdown.

Without this option, PREPARED was always used, which caused Nutanix to auto-restart VMs after sysprep /shutdown — preventing Packer from capturing the disk.

Shutdown command resilience (defensive)

step_shutdown_vm.go now tolerates communicator disconnects during shutdown_command execution and PowerOff() errors when the VM is already off. Previously, if the shutdown command triggered a VM power-off (e.g. sysprep /shutdown), the communicator drop was treated as a fatal error, halting the build before reaching the power-state polling loop. Now the error is logged as a warning and the polling loop proceeds normally.

This is a defensive improvement — with the recommended scheduled-task approach for sysprep, the shutdown command returns cleanly before sysprep starts and this code path is not triggered. But it prevents build failures if the timing is different or a different shutdown command is used.

Documentation updates

  • nutanix_custom_headers description lists all three propagation paths
  • user_data documented for both Linux (cloud-init) and Windows (Sysprep unattend.xml)
  • windows_install_type documented with FRESH/PREPARED options

Comment thread builder/nutanix/driver.go
// Objects Lite validates the AWS V4 signature against Prism Central's user
// table, so nutanix_username/nutanix_password are required even when the rest
// of the build authenticates with nutanix_api_key.
func (d *NutanixDriver) uploadImageObject(ctx context.Context, key, filePath string) error {

@adarshanand25 adarshanand25 May 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/nutanix-cloud-native/prism-go-client/blob/main/converged/v4/images.go#L170

it will be better if we use existing api of prism go client

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! We actually started with ImagesService.Upload but had to reimplement the S3 upload path because the upstream awsConfig() builds the AWS HTTP client internally with no way to inject custom headers or a custom transport.

This PR adds nutanix_custom_headers support — needed when Prism Central sits behind a service-token gateway like Cloudflare Access. Our uploadImageObject wraps the AWS HTTP client with a headerInjectingTransport so those headers reach the Objects Lite S3 endpoint. The upstream UploaduploadToObjectsawsConfig chain would silently drop them.

Specifically, awsConfig() only conditionally creates a custom http.Client for TLS skip-verify — there's no mechanism to pass in additional headers or wrap the transport.

Happy to contribute custom header / transport support upstream to prism-go-client as a follow-up so we can switch back to ImagesService.Upload — would that work?

@andrew-sumner

Copy link
Copy Markdown
Author

Update: Opened the upstream PR — nutanix-cloud-native/prism-go-client#354. It adds a WithExtraHeaders(http.Header) option to ImagesService.Upload (variadic, backward-compatible) so callers can inject headers onto the AWS HTTP client's transport.

Once that lands and a release is cut, the uploadImageObject + headerInjectingTransport reimplementation in builder/nutanix/driver.go here can be deleted and replaced with a direct client.Images.Upload(ctx, uuid, path, converged.WithExtraHeaders(headers)) call. Happy to do that as a follow-up PR.

Adds two new builder fields:

- nutanix_api_key — uses the X-ntnx-api-key header in place of Basic
  auth. Falls back to the NUTANIX_API_KEY environment variable.
- nutanix_custom_headers — extra HTTP headers attached to every Prism
  Central request (e.g. for Cloudflare Access service tokens). Headers
  can also be supplied via NUTANIX_HEADER_* env vars; config values
  take precedence.

Authentication accepts either nutanix_api_key or the existing
nutanix_username/nutanix_password pair; if both are set, the API key
wins and a warning is emitted. The legacy service-account form
(nutanix_username = "X-ntnx-api-key", nutanix_password = key) keeps
working for backwards compatibility.

Brings the plugin to parity with the equivalent changes in the
terraform-provider-nutanix and nutanix.ansible projects.
…Lite S3

Extends nutanix_api_key and nutanix_custom_headers support to the two
remaining paths that were not covered by the initial PR:

VNC console websocket (boot_command):
  step_vnc_connect.go now copies CustomHeaders into the WSS upgrade
  request and prefers the explicit APIKey field over the legacy
  Username == "X-ntnx-api-key" form. Without this, any service-token
  gateway in front of Prism Central (e.g. Cloudflare Access) rejects
  the websocket handshake and boot_command keystrokes never land.

Objects Lite S3 upload (source_image_path / cd_content):
  CreateImageFile is reimplemented to build the AWS HTTP client through
  a headerInjectingTransport that carries nutanix_custom_headers, then
  calls v4Client.Images.Create separately. The upstream
  prism-go-client ImagesService.Upload uses an unconfigured HTTP client
  that silently drops any service-token headers. Note: Objects Lite
  still validates AWS V4 signatures against Prism Central's user table,
  so nutanix_username/nutanix_password are required for upload even when
  nutanix_api_key handles all other API calls.

Documentation:
  - nutanix_custom_headers description updated to list all three paths
    (REST API, VNC websocket, Objects Lite S3)
  - user_data documented for both Linux (cloud-init) and Windows
    (Sysprep unattend.xml) — the code already handled both but the docs
    only mentioned Linux
  - Added caveat about Objects Lite requiring username/password
Remove NUTANIX_API_KEY env var fallback and NUTANIX_HEADER_* env var
scanning from plugin code. Users should use PKR_VAR_nutanix_api_key and
PKR_VAR_nutanix_custom_headers (or HCL env() function) instead, which
aligns with Packer best practices and existing plugin behaviour.

Updated docs with usage examples showing PKR_VAR_ approach.
- Clone request in headerInjectingTransport.RoundTrip() to comply with
  the http.RoundTripper contract (callers must not modify the request)
- Align example file field formatting for consistency
- Remove unused strings import from config.go
@andrew-sumner andrew-sumner force-pushed the issue/api-key-and-custom-headers branch from 2fbfa70 to e0dbe09 Compare June 7, 2026 23:49
andrew-sumner added a commit to andrew-sumner/packer-plugin-nutanix that referenced this pull request Jun 8, 2026
TestPrepareRejectsInvalidWindowsInstallType previously relied on the
default nutanix_username/nutanix_password set by minimalValidConfig.
Pass them explicitly so the test is self-contained and does not depend
on helper defaults — also avoids a behavioural conflict when this PR is
rebased against the API-key PR (nutanix-cloud-native#358), whose minimalValidConfig omits
default credentials so its auth-combination tests can work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support API key authentication and custom HTTP headers

2 participants