Skip to content

Support FIPS-compatible API signing#213

Open
andrijapanicsb wants to merge 1 commit into
mainfrom
cmk-fips-api-signing
Open

Support FIPS-compatible API signing#213
andrijapanicsb wants to merge 1 commit into
mainfrom
cmk-fips-api-signing

Conversation

@andrijapanicsb
Copy link
Copy Markdown

Support FIPS-compatible API request signing in CloudMonkey

Summary

This change adds support for CloudStack API key request signing with HmacSHA512, while preserving compatibility with older CloudStack deployments that still require or allow HmacSHA1.

CloudMonkey profiles now support a profile-level signaturealgorithm setting:

  • auto
  • HmacSHA512
  • HmacSHA1

New and migrated profiles default to auto. In auto mode, CloudMonkey probes the active endpoint with the read-only listApis API, trying HmacSHA512 first and falling back to HmacSHA1 only if the SHA-512 probe fails authentication/signature verification. Once a probe succeeds, the concrete algorithm is persisted to the active profile before the user-requested API command is executed.

This avoids retrying user-requested APIs directly and prevents duplicate execution of state-changing commands during signature algorithm detection.

Motivation

When legacy algorithms are disabled, CloudStack rejects API requests signed with HmacSHA1 and accepts modern HmacSHA512 signatures. CloudMonkey previously signed API key requests with HmacSHA1 only, so API key authentication failed against such deployments.

This change allows CloudMonkey to work with FIPS-compatible CloudStack environments while keeping existing legacy deployments usable without manual trial and error.

Behavior

New or migrated profiles

If signaturealgorithm is absent from an existing profile, CloudMonkey writes:

signaturealgorithm = auto

Auto detection

When API key and secret key authentication is used and the active profile is set to auto, CloudMonkey:

  1. Sends a read-only listApis listall=true probe signed with HmacSHA512.

  2. If that succeeds, persists:

    signaturealgorithm = HmacSHA512
  3. If SHA-512 fails authentication/signature verification, retries the same read-only probe with HmacSHA1.

  4. If SHA-1 succeeds, persists:

    signaturealgorithm = HmacSHA1
  5. Executes the original user-requested API only after detection has completed.

Explicit configuration

If a profile explicitly sets HmacSHA512 or HmacSHA1, CloudMonkey uses only that configured algorithm and does not silently fall back.

Prompt indicator

Profiles using HmacSHA512 show a -fips marker in the interactive prompt. For example:

(localcloud-fips) cmk >

The marker indicates that CloudMonkey is using the FIPS-compatible CloudStack API signing algorithm. It does not imply full end-to-end FIPS validation of the local environment.

Debug logging

Auto-detection probe attempts are logged only when debug mode is enabled. Normal interactive output is not noisy when fallback succeeds.

Debug output includes which signature algorithm probe is attempted, which probe fails, and which algorithm is selected.

Testing

Automated tests added:

  • HmacSHA1 signature generation against a fixed request string
  • HmacSHA512 signature generation against a fixed request string
  • default profile uses signaturealgorithm = auto
  • missing signaturealgorithm is migrated to auto
  • explicit HmacSHA512 does not silently try HmacSHA1
  • auto persists HmacSHA512 when the SHA-512 probe succeeds
  • auto falls back and persists HmacSHA1 when SHA-512 fails but SHA-1 succeeds
  • auto does not retry the user-requested API directly, avoiding duplicate execution risk for state-changing APIs
  • prompt shows -fips when the active profile uses HmacSHA512

Command run:

go test ./...

Lab verification

Verified against a CloudStack lab environment.

With legacy algorithms enabled:

api.legacy.algorithm.supported=true
  • auto selected and persisted HmacSHA512
  • read-only sync succeeded and discovered 878 APIs

With legacy algorithms disabled:

api.legacy.algorithm.supported=false

Control checks:

  • forced signaturealgorithm = HmacSHA1 failed with HTTP 401 signature verification error
  • forced signaturealgorithm = HmacSHA512 succeeded
  • signaturealgorithm = auto succeeded and persisted HmacSHA512

Observed result:

forced HmacSHA1:
HTTP 401: unable to verify user credentials and/or request signature

forced HmacSHA512:
Discovered 878 APIs

auto detection:
Discovered 878 APIs
signaturealgorithm = HmacSHA512

Performance note

I also compared SHA-1 and SHA-512 signing overhead during local validation.

Local signer-only measurement showed SHA-512 signing is slower than SHA-1 in isolation, but still on the order of microseconds per request. Lab API request latency was dominated by network and CloudStack server round-trip time, with no meaningful API-level bottleneck observed from using HmacSHA512.

Benchmark tooling and result artifacts are not included in this PR.

Compatibility

  • Existing profiles without signaturealgorithm migrate to auto.
  • Older CloudStack deployments that only support HmacSHA1 remain supported through auto fallback or explicit HmacSHA1 configuration.
  • CloudStack deployments with legacy algorithms disabled are supported through HmacSHA512.
  • Username/password session authentication is not changed.

Notes

The detection probe intentionally uses listApis before executing the user-requested API so that state-changing APIs are not retried as part of algorithm detection.

Signed-off-by: andrijapanicsb <andrija.panic@gmail.com>
@andrijapanicsb andrijapanicsb requested a review from shwstppr June 2, 2026 11:39
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

✅ Build complete for PR #213.

📦 Binary artifacts are available in the workflow run (expires on June 12, 2026).

Note: Download artifacts by clicking on the workflow run link above, then scroll to the "Artifacts" section.
Artifacts from PR builds are for testing only and may contain unreviewed, malicious code.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds FIPS-compatible CloudStack API request signing to CloudMonkey by introducing HmacSHA512 support with an auto mode that probes listApis to select and persist the working algorithm per profile.

Changes:

  • Add signaturealgorithm profile setting (auto, HmacSHA512, HmacSHA1) with normalization and migration to auto.
  • Implement request signing with HmacSHA512 and autodetection via listApis probe before executing user commands.
  • Update prompt to show a -fips marker when the active profile uses HmacSHA512; add unit tests covering config migration, signing, and autodetection behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
config/prompt.go Adds -fips prompt marker when the active profile is using HmacSHA512.
config/config.go Introduces signature algorithm constants, profile field, normalization, and migration/persistence behavior.
config/config_test.go Adds tests for default/migrated signaturealgorithm and prompt indicator behavior.
cmd/set.go Adds signaturealgorithm to set subcommands and updates validation behavior.
cmd/network.go Adds HmacSHA512 signing, autodetection probe flow, and refactors API request building/response parsing.
cmd/network_test.go Adds tests for SHA1/SHA512 signatures and autodetection/fallback/persistence semantics.
Comments suppressed due to low confidence (1)

cmd/set.go:72

  • The set command bypasses argument validation for signaturealgorithm, which means invalid values won’t cause the command to fail consistently (other subcommands return an error). Consider validating via NormalizeSignatureAlgorithm so case/whitespace are accepted, but invalid values still return an error and a non-zero exit status.
			validArgs := r.Command.SubCommands[subCommand]
			if len(validArgs) != 0 && subCommand != "timeout" && subCommand != "signaturealgorithm" {
				if !config.CheckIfValuePresent(validArgs, value) {
					return errors.New("Invalid value set for " + subCommand + ". Supported values: " + strings.Join(validArgs, ", "))
				}
			}
			r.Config.UpdateConfig(subCommand, value, true)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/network.go
Comment on lines +377 to +379
body, _ := ioutil.ReadAll(response.Body)
config.Debug("Signature algorithm probe response body:", string(body))
if _, err := parseAPIResponse(body); err == nil {
Comment thread cmd/network.go
Comment on lines +383 to +390
} else {
lastErr = err
if isAuthenticationFailure(response.StatusCode, err) {
config.Debug("API signature algorithm probe failed authentication for ", algorithm, ": ", err)
} else {
config.Debug("API signature algorithm probe failed with non-authentication error for ", algorithm, ": ", err)
}
}
Comment thread cmd/network.go
Comment on lines +536 to 538
func processAPIResponse(r *Request, response *http.Response, isAsync bool) (map[string]interface{}, error) {
body, _ := ioutil.ReadAll(response.Body)
config.Debug("NewAPIRequest response body:", string(body))
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.

2 participants