Skip to content

Redact AppRole's Debug output #145

Description

@sehkone

Context

AppRole (src/bootroot_cmd.rs) holds the credential pair bootroot mints: role_id and secret_id. It derives Debug, so {:?} of an AppRole, or of anything that derives Debug over one, prints both values in clear text.

No deploy-core code formats an AppRole today. The hazard is downstream. bootler holds AppRoles in types that derive Debug. For example, RegistrationSteps (core/src/services.rs) derives Debug and holds service_add: Option<&AppRole>. A future tracing field, expect message or error string over any of them would print the secret id. bootler#617 redacted the records it owns, and it had to print the AppRole as "<redacted>" by hand because this type's own Debug leaks. Fixing the type here fixes every container at once.

deploy-core already redacts in the same way elsewhere: GenerationFile (src/generation.rs) has a hand-written Debug.

This is deploy-core's first release line (0.1.0, 2026-10-02). No compatibility shim is needed.

Scope

In src/bootroot_cmd.rs only:

  • Remove Debug from AppRole's derive list. Keep Clone, PartialEq, Eq, Serialize and Deserialize, and every field, its type and its visibility, as they are.
  • Add a hand-written impl std::fmt::Debug for AppRole that uses f.debug_struct("AppRole"), lists role_id and then secret_id, prints each as the &str "<redacted>", and ends with .finish(). Give it a one-line comment saying why it is hand-written.
  • Add a #[cfg(test)] mod tests to the file with the test in the Test plan.

In CHANGELOG.md, open an ## [Unreleased] section above ## [0.1.0] with a ### Changed entry: an AppRole's Debug output no longer shows its role id or secret id, and both print as <redacted>. Add the matching link reference [Unreleased]: https://github.com/aicers/deploy-core/compare/0.1.0...main above the [0.1.0] reference. A user of 0.1.0 can observe this change, so it gets an entry. The entry carries no issue or PR reference.

Acceptance criteria

  • AppRole no longer derives Debug, and its hand-written Debug prints AppRole { role_id: "<redacted>", secret_id: "<redacted>" }.
  • The test shows that format!("{approle:?}") and format!("{approle:#?}") contain neither sentinel, and do contain <redacted> and the field names role_id and secret_id.
  • The other derives and the serde attributes are unchanged, so serialized bytes and equality are unaffected. Every existing test passes unchanged.
  • The CHANGELOG.md entry and link reference are present, and the Markdown job passes.
  • CI passes: fmt, Clippy and Clippy with test-support, Docs, Test and Test with test-support, and the Platform jobs.

Constraints

  • Hand-written Debug only: no wrapper type, newtype, helper, macro or dependency, and no change to field types or visibility.
  • Use exactly the "<redacted>" literal and .finish(), not .finish_non_exhaustive(), so a field added later must be classified explicitly.
  • Never format a secret through its own Debug inside the impl.
  • Never compare secret values with assert_eq! or assert_ne!, which print both sides through Debug on failure. Use assert!(a == b).
  • First version: add no compatibility shim or feature flag.
  • Cut no release. Releasing is not this issue's.

Edge cases

  • role_id. It is redacted too. It is one half of the AppRole login, and bootler's own redactions (#617's SecretsRecord, BootstrapMaterial) already treat the whole pair as secret.
  • Pretty-printing. {:#?} goes through the same debug_struct and redacts the same way. The test covers both.

Test plan

  • app_role_debug_redacts_both_ids: build an AppRole with a distinct sentinel in role_id and in secret_id. Assert that format!("{approle:?}") and format!("{approle:#?}") contain neither sentinel, and contain <redacted>, role_id and secret_id.
  • cargo fmt --check, cargo clippy --all-targets -- -D warnings with and without --features test-support, cargo doc as the Docs job runs it, and cargo test with and without --features test-support.

Dependencies

  • None blocking.
  • bootler's issue "Redact Debug for bootroot's init capture" (aicers/bootler#NNN, filled in at filing) re-pins deploy-core to this issue's merge commit, so this merges first.

Out of scope

  • service_add_args passing the secret id on bootroot service add's argv. Its rustdoc already records that as an accepted trade-off.
  • Any other deploy-core type's Debug.
  • Zeroizing memory, or a secret wrapper type.

Pointers

  • src/bootroot_cmd.rs (AppRole); src/registration.rs (ServiceAddSpec::approle, service_add_args).
  • src/generation.rs (GenerationFile's hand-written Debug, the in-crate precedent).
  • bootler: core/src/secrets.rs (SecretsRecord's Debug, which prints the AppRole as "<redacted>"); core/src/services.rs (RegistrationSteps).

No activity

Activity on this issue will appear here.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions