Skip to content

Harden create-env agent passwords in cloud metadata - #742

Draft
Alphasite wants to merge 3 commits into
mainfrom
tnz-129825-hash-http-agent-password
Draft

Alphasite wants to merge 3 commits into
mainfrom
tnz-129825-hash-http-agent-password

Conversation

@Alphasite

@Alphasite Alphasite commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Why

create-env puts cloud_provider.properties.agent.mbus, which holds the agent's Basic Auth password, into IaaS VM metadata via the CPI.

What

  • If the stemcell's stemcell.MF has agent_features containing http-password-hmac-sha256, the CLI replaces the password in two places with a salted HMAC-SHA256 verifier from bosh-agent's agentpassword package:
    • cloud_provider.properties.agent.mbus
    • any resource pool env.bosh.mbus.urls
  • The CLI still uses the plaintext cloud_provider.mbus for its own connection to the agent; only the copy sent to the VM is hashed.
  • Hashing runs after the "no changes, skipping deploy" check. That check's manifest SHA is computed before hashing, so the random salt doesn't trigger a redeploy.
  • Stemcells without the feature behave exactly as before.

Dependency

Release ordering

  • The CLI can ship before or after the stemcell changes, because hashing only happens when the stemcell has the feature.
  • It must be built against a tagged agent release.

Tests

$ go test ./cmd/... ./stemcell/...
ok  github.com/cloudfoundry/bosh-cli/v7/cmd
ok  github.com/cloudfoundry/bosh-cli/v7/stemcell
$ bin/lint
0 issues.

New tests:

  • cmd/agent_password_test.go: the two hashing helpers, including absent paths and wrong-type errors.
  • cmd/create_env_test.go: with the feature, the CPI installer receives the hashed agent.mbus while the agent client gets the plaintext mbus URL; without the feature, nothing changes.

Note: unset any BOSH_* environment variables (e.g. BOSH_ALL_PROXY) before running cmd tests locally. Unrelated curl/session specs fail when they are set.

Related

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Alphasite Alphasite changed the title Hash create-env HTTP agent passwords for capable stemcells Address HTTPS agent credential exposure in create-env metadata Oct 6, 2026
@Alphasite Alphasite changed the title Address HTTPS agent credential exposure in create-env metadata Harden create-env agent passwords in cloud metadata Oct 6, 2026
@Alphasite
Alphasite force-pushed the tnz-129825-hash-http-agent-password branch from 21c2723 to 5587ef6 Compare October 8, 2026 18:51
Comment thread stemcell/stemcell.go Outdated
Comment thread cmd/deployment_preparer.go Outdated
}

if extractedStemcell.Manifest().SupportsFeature(agentpassword.Feature) {
c.ui.PrintLinef("Warning: agent password verifiers require cryptographically random passwords; hardcoded or predictable passwords remain vulnerable to offline guessing.")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this warning necessary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. Removed the unconditional warning. Random passwords remain a requirement recorded in the commit messages; warning on every run did not establish whether a password was generated securely. The separate warning now applies only when standard CPI settings cannot be identified and are left unchanged.

Comment thread agentpassword/password.go Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems like a lot of code to replace a string with a hashed string - can this be simplified?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. Simplified agentpassword/password.go to a 50-line URL verifier helper. Manifest handling now sits beside deployment preparation and uses one shared helper for scalar and overriding URL settings. Removed CPI-specific property traversal and preserved missing CPI defaults rather than rejecting the manifest.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. Removed the cloning in f74f37b82 after tracing ownership. The parsers recursively allocate fresh maps/lists, including for YAML aliases, while the CLI client URL remains a separate string. The manifest SHA is captured before transformation, and errors abort before CPI installation. The helper now updates these owned agent settings in place and returns only (bool, error). The full ownership rationale is in the commit message. CLI packages compile; existing assertions were adjusted but tests have not been rerun.

if err != nil {
return bosherr.WrapError(err, "Hashing CPI agent HTTP password")
}
for i := range deploymentManifest.ResourcePools {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are these resource_pools used for the cli to communicate with the agent, or by the agent on the stemcell? As far as I know, only part of the cloud_provider need to change: https://github.com/cloudfoundry/bosh-deployment/blob/951af8e335bd22648d1d65ddb5c35fc03c0ecce6/bosh.yml#L1-L6

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. These are settings delivered to the agent: the VM manager passes resourcePool.Env to CreateVM, and Settings.GetMbusURL prefers env.bosh.mbus.urls over the base mbus. The standard bosh-deployment manifest only supplies the TLS certificate there, so it remains unchanged. We still transform explicit URL overrides so they cannot leave a plaintext password in metadata. The client cloud_provider.mbus stays unchanged.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Inspecting a settings.json on a live create-env'd vm, and there's no resource_pool - I think the only thing we want to rewrite here is the installationManifest.Mbus

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Codex agent: we only care about the create-env case here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. This is specifically the create-env path. installationManifest.Mbus is passed unchanged to both the agent client and blob client; hashing it would make those clients send the verifier as their Basic Auth password and fail authentication. The metadata-side field is installationManifest.Properties["agent"]["mbus"]. There is no resource_pool key in settings.json because resource_pools is a manifest wrapper: its env is passed to CreateVM. For the standard manifest, only properties.agent.mbus changes. The environment helper does nothing unless URLs explicitly override that agent setting; it leaves the certificate unchanged.

@Alphasite
Alphasite force-pushed the tnz-129825-hash-http-agent-password branch from 5587ef6 to 4463e98 Compare October 8, 2026 21:12
Comment thread agentpassword/password.go Outdated
// hash.Hash.Write consumes all input and never returns an error.
_, _ = mac.Write([]byte(password))
key := mac.Sum(nil)
verifier := prefix + base64.RawStdEncoding.EncodeToString(salt) + "$" + base64.RawStdEncoding.EncodeToString(key)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not fmt.Sprintf?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. Updated verifier serialization to fmt.Sprintf.

Comment thread cmd/deployment_preparer.go Outdated
return bosherr.WrapError(err, "Hashing CPI agent HTTP password")
}
if !explicitMbus {
c.ui.PrintLinef("Warning: no explicit agent mbus URL found in supported CPI properties; CPI defaults and other credential locations are unchanged. Explicit env.bosh.mbus.urls overrides are still hashed.")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please remove this conditional and warning

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. Removed the conditional and warning. Missing explicit agent.mbus settings remain a no-op, and explicit env.bosh.mbus.urls overrides are still processed.

Comment thread agentpassword/password_test.go Outdated
"testing"
)

func TestHashURL(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please rewrite these as ginkgo/gomega tests like the rest in the codebase

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. Converted to Ginkgo v2/Gomega in 3217cee. The affected test packages compile; the suites have not been executed.

Comment thread cmd/agent_password_test.go Outdated
"github.com/cloudfoundry/bosh-utils/property"
)

func TestHashAgentPropertiesInPlace(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please rewrite these as ginkgo/gomega tests like the rest in the codebase

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. Converted to Ginkgo v2/Gomega in 3217cee. The affected test packages compile; the suites have not been executed.

Comment thread stemcell/agent_features_test.go Outdated
"gopkg.in/yaml.v2"
)

func TestAgentFeaturesRoundTrip(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please rewrite these as ginkgo/gomega tests like the rest in the codebase

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent here. Converted to Ginkgo v2/Gomega in 3217cee. The affected test packages compile; the suites have not been executed.

if err != nil {
return bosherr.WrapError(err, "Hashing CPI agent HTTP password")
}
for i := range deploymentManifest.ResourcePools {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent review — P2: Only transform the resource pool selected for the create-env job. GetStemcell fetches the stemcell for deploymentManifest.JobName(), and deployment/vm/manager.go passes only deploymentManifest.ResourcePool(jobName).Env to CreateVM. This loop additionally validates unused pools against that selected stemcell capability. An unused pool with an invalid/empty HTTPS credential or a differently shaped mbus override now aborts a deployment that previously ignored that env. Please resolve the selected pool and hash only its Env.

Comment thread agentpassword/password.go Outdated
if u.User == nil {
return "", fmt.Errorf("HTTPS agent mbus URL requires credentials")
}
password, present := u.User.Password()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Codex agent review — P2: Reject usernames containing a colon before emitting a verifier. For example, https://user%3Aextra:password@host:6868 parses successfully here, but the verifier branch in requestNotAuthorized uses request.BasicAuth(), which splits decoded credentials at the first colon. It reads username user, while the configured username is user:extra, so the original credentials always receive 401. The previous whole-header comparison accepted that same configuration. A sanitized validation error here would prevent creating an unreachable agent; cover this boundary in the Ginkgo specs.

@selzoc

selzoc commented Oct 8, 2026

Copy link
Copy Markdown
Member

Linting is failing, you need to run goimports most likely

@Alphasite

Copy link
Copy Markdown
Contributor Author

Codex agent here. Fixed the two goimports failures and pushed the correction. Used the repository-pinned formatter on every changed Go file in the CLI and agent PRs; its formatting check now reports no files.

create-env passes cloud_provider.properties.agent.mbus to the CPI. The
CPI writes it into IaaS VM metadata, so the Director agent's Basic
Auth password ends up readable by anyone with metadata access.

When the stemcell's stemcell.MF lists the http-password-hmac-sha256
agent feature, replace the password in agent.mbus, and in any
resource pool env.bosh.mbus.urls, with a salted HMAC-SHA256 verifier
from bosh-agent's agentpassword package before installing the CPI.
The CLI keeps using the plaintext cloud_provider.mbus to talk to the
agent, so only the copy that reaches the VM is hashed.

Hashing happens after the "no changes" check, and that check uses a
manifest SHA computed before hashing, so the random salt does not
force a redeploy.

Compatibility:
- New CLI, stemcell without the feature: nothing is hashed.
- Old CLI, stemcell with the feature: plaintext still works, because
  the agent accepts both forms.

ai-assisted=yes
TNZ-129825

Co-Authored-By: Chris Selzo <chris.selzo@broadcom.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The required tagged bosh-agent release is unavailable, and the pinned draft API has already changed upstream.

1 open finding
What changed in this PR

Hardens create-env by replacing agent passwords in VM metadata with feature-gated HMAC-SHA256 verifiers.

Changes:

  • Adds stemcell agent-feature detection.
  • Hashes CPI and resource-pool HTTPS mbus passwords while retaining plaintext client credentials.
  • Adds tests and vendors the bosh-agent hashing package.
File Description
cmd/​agent_password.go Implements metadata password hashing.
cmd/​agent_password_test.go Tests hashing helpers and errors.
cmd/​create_env_test.go Tests feature-gated create-env behavior.
cmd/​deployment_preparer.go Applies hashing after change detection.
stemcell/​stemcell.go Exposes advertised agent features.
stemcell/​agent_features_test.go Tests feature parsing and preservation.
go.mod Adds the bosh-agent pseudo-version dependency.
go.sum Updates dependency checksums.
vendor/​modules.txt Registers the vendored package.
vendor/​github.com/​cloudfoundry/​bosh-agent/​v2/​agentpassword/​agentpassword.go Vendors verifier generation logic.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread go.mod Outdated
code.cloudfoundry.org/workpool v0.0.0-20250911194158-1489753f182e
github.com/cheggaaa/pb/v3 v3.2.1
github.com/cloudfoundry/bosh-agent/v2 v2.898.0
github.com/cloudfoundry/bosh-agent/v2 v2.900.1-0.20261009041322-881bed56c8e2
Update the agent dependency and tracked vendor copy together with the parser references in the command specs. This brings the shared password-length validation into create-env. The pseudo-version is for draft integration only; before merge, replace it with the tagged agent release containing these changes and regenerate checksums and vendor metadata.
@Alphasite

Copy link
Copy Markdown
Contributor Author

Codex agent here. Re the dependency review comment: Updated the draft dependency to 50adbab21, regenerated go.sum/vendor, and changed the specs to ParseHashedPassword in 25cd17c. The affected specs compile. The tagged-release prerequisite remains outstanding before merge; this draft pin is only for reviewing integration, so I am leaving this thread open.

Use Ginkgo table entries so each metadata case reports separately. Detection and preservation during repacking are distinct behaviors and get separate specs. Clarify that absent capabilities return false; create-env then retains its existing plaintext credential behavior.
@Alphasite

Copy link
Copy Markdown
Contributor Author

Codex agent here. Feature specs now use separate Ginkgo tables for detection and serialization. Absent capabilities return false, so create-env skips hashing and keeps the original password. The specs compile. Posting here while the review is pending.

@mkocher

mkocher commented Oct 9, 2026

Copy link
Copy Markdown
Member

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

4 participants