Skip to content

Comments

keylimectl: A replacement for keylime_tenant in rust#1068

Draft
ansasaki wants to merge 27 commits intokeylime:masterfrom
ansasaki:keylimectl
Draft

keylimectl: A replacement for keylime_tenant in rust#1068
ansasaki wants to merge 27 commits intokeylime:masterfrom
ansasaki:keylimectl

Conversation

@ansasaki
Copy link
Contributor

@ansasaki ansasaki commented Aug 4, 2025

Disclaimer: this is an AI generated rewrite. We should be careful reviewing it.

Adds a modern Rust replacement for keylime_tenant with full API compatibility and improved usability.

Features

  • Agent Management: add, remove, update, status, reactivate commands
  • Policy Management: runtime and measured boot policy CRUD operations
  • Resource Listing: agents, policies with detailed/basic views
  • Multi-format Output: JSON, table, YAML with configurable verbosity
  • Robust Error Handling: typed errors with context and retry logic
  • TLS Support: mutual authentication with certificate validation
  • Configuration: file-based config with CLI overrides

Implementation

  • 8,512 lines of documented Rust code
  • 158 comprehensive unit tests (100% pass rate)
  • 0 clippy warnings, full type safety
  • Modular architecture with proper abstractions
  • IPv6 support and exponential backoff retry

Usage

keylimectl agent add <uuid> --ip 192.168.1.100 --port 9002
keylimectl policy create web-policy --file policy.json
keylimectl list agents --detailed

Replaces Python keylime_tenant while maintaining backward compatibility.

@ansasaki ansasaki marked this pull request as draft August 4, 2025 12:04
@ansasaki ansasaki force-pushed the keylimectl branch 2 times, most recently from b55ee8e to da44cbc Compare August 4, 2025 15:43
@codecov
Copy link

codecov bot commented Aug 5, 2025

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 42.18%. Comparing base (bec5d94) to head (d1ff80a).

Files with missing lines Patch % Lines
keylime-push-model-agent/src/attestation.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
e2e-testsuite 42.18% <0.00%> (-16.10%) ⬇️
upstream-unit-tests 42.18% <0.00%> (-16.10%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
keylime-push-model-agent/src/struct_filler.rs 0.00% <ø> (-25.44%) ⬇️
keylime-push-model-agent/src/attestation.rs 0.00% <0.00%> (-44.76%) ⬇️

... and 53 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ansasaki ansasaki force-pushed the keylimectl branch 3 times, most recently from 7cd11bf to 9609be7 Compare August 7, 2025 10:19
@ansasaki ansasaki mentioned this pull request Aug 27, 2025
30 tasks
@ansasaki ansasaki mentioned this pull request Sep 24, 2025
30 tasks
@ansasaki ansasaki force-pushed the keylimectl branch 6 times, most recently from f15c57b to d1ff80a Compare October 6, 2025 17:19
@sarroutbi sarroutbi mentioned this pull request Nov 26, 2025
36 tasks
ansasaki and others added 9 commits February 17, 2026 12:21
This initial implementation covers most of the functionality from the
tenant, with some gaps.

The progress is tracked in the FEATURE_PARITY.md file and the plans in
ROADMAP.md file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
The generate_random_string() function used a time-seeded linear
congruential generator, making nonces predictable. Replace it with
generate_secure_nonce() backed by OpenSSL RAND_bytes (CSPRNG),
hex-encoding the output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
These functions were previously only available in the crypto::testing
module. Move them to the main crypto module as proper public API, with
CryptoError variants for structured error handling. The testing module
versions now delegate to the production implementations, preserving
backward compatibility.

The goal is to make them available to other crates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
Import the RSA-OAEP from the crypto module instead of importing from the
testing module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
Replace the validate_tpm_quote() stub with two cfg-gated
implementations controlled by the 'tpm-quote-validation' cargo feature.

The default path performs structural validation only (quote format, base64
decoding, data length) and clearly documents the security limitations
with SECURITY comments and a runtime warning.

The feature-enabled path implements full cryptographic validation
following tpm2_checkquote logic: parses the quote via
decode_quote_string(), verifies the signature against the registered AIK
using OpenSSL (RSA-SSA and RSA-PSS), checks the nonce from
TPMS_ATTEST.extraData, and validates the PCR digest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap U, V, and K key buffers in zeroize::Zeroizing so they are
securely cleared from memory when they go out of scope. This prevents
key material from lingering in process memory after use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…LS 1.2

Add accept_invalid_hostnames field to TlsConfig (default: true for
backward compatibility with Keylime auto-generated certificates).
Enforce TLS 1.2 as the minimum protocol version. Log a warning when
hostname verification is disabled so operators are aware of the
security trade-off.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mpleted Phase 1

Mark completed Phase 1 items:
- 1.1: CSPRNG nonce generation (replaces insecure LCG)
- 1.2: Production RSA-OAEP encryption (replaces crypto::testing)
- 1.3: Optional TPM quote validation behind feature flag
- 1.4: Secret key material zeroization on drop
- 1.5: Configurable TLS hostname verification, TLS 1.2 minimum

Update FEATURE_PARITY.md security items A and B as fixed,
TPM quote validation from Partial to Yes, and summary counts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dules

Implements Phase 2 Steps 2.1-2.3 of the ROADMAP:

Step 2.1 - Feature flag infrastructure:
- Add api-v2 and api-v3 features to Cargo.toml (both on by default)
- Add compile_error! guard requiring at least one feature enabled

Step 2.2 - Shared version constants:
- Create api_versions module as single source of truth for version
  constants (SUPPORTED_API_VERSIONS, DEFAULT_API_VERSION, is_v3)
- Version arrays are feature-conditional: v2 versions behind api-v2,
  v3 versions behind api-v3
- Replace duplicated constants in verifier, registrar, and agent clients

Step 2.3 - Gate v2-only modules:
- Gate client::agent module behind #[cfg(feature = "api-v2")]
- Gate commands::agent::attestation module behind api-v2
- Gate pull-model code paths in add.rs with #[cfg(feature = "api-v2")]
  and provide error returns when api-v2 is disabled
- Gate direct agent communication in status.rs behind api-v2
- Add #[cfg_attr] annotations for dead_code that's only used in v2
- Update tests to use DEFAULT_API_VERSION and feature-conditional
  version array assertions

All three feature combinations compile, pass tests, and pass clippy:
- default (api-v2 + api-v3): 302 tests
- api-v2 only: 302 tests
- api-v3 only: 290 tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ansasaki and others added 18 commits February 17, 2026 22:37
Gate all v3.0 private methods behind #[cfg(feature = "api-v3")]:
- test_api_version_v3, get_agent_v3, delete_agent_v3,
  reactivate_agent_v3, list_agents_v3, get_bulk_info_v3,
  add_runtime_policy_v3

Gate v2.x test method behind #[cfg(feature = "api-v2")]:
- test_api_version

Restructure 6 public methods (get_agent, delete_agent,
reactivate_agent, list_agents, get_bulk_info, add_runtime_policy)
with #[cfg] blocks:
- v3 branch: #[cfg(feature = "api-v3")] using is_v3() helper
- v2 fallback: #[cfg(feature = "api-v2")] block
- Error return when v2 fallback not available

Gate detect_api_version() internals:
- 410 Gone match arm behind #[cfg(feature = "api-v3")]
- Version probing uses cfg-conditional blocks for v3/v2 testing

Move is_v3() behind #[cfg(feature = "api-v3")] since it's only
used in v3 code paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add CI jobs to test all keylimectl feature flag combinations:
- api-v2,api-v3 (default): full functionality
- api-v2 only: pull-model without v3 endpoints
- api-v3 only: push-model without v2 endpoints

Each combination runs build, test, and clippy.

Also add a guard job verifying that building with no features
triggers the compile_error! as expected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mark completed Phase 2 items: feature flag infrastructure (2.1),
version constants module (2.2), verifier method gating (2.3),
command-level gating (2.4), version detection separation (2.5),
CI matrix and testing (2.6). Update architecture note from
"consideration" to "decision" explaining why #[cfg] was chosen
over trait-based approach. Mark related items in "What needs
attention" and Phase 3.1 sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply the same v3/v2 feature flag pattern to registrar's
detect_api_version() as was done for verifier: add 410 Gone handler
behind api-v3, split test_api_version into v2/v3 variants, and use
cfg-conditional version probing. Registrar public methods (get_agent,
delete_agent, list_agents) remain version-agnostic since they use
self.api_version in URL construction with the same endpoint pattern.

Verified: all 3 feature combinations build, pass tests (302, 301, 290),
and pass clippy. Binary symbol inspection confirms gated code is absent
when its feature is disabled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add negative tests for RSA encryption with attacker-controlled keys:
empty, garbage, truncated, and EC PEM keys; oversized plaintext.
Add structural validation tests for malformed TPM quote parsing
(base64 decode, quote format splitting edge cases).
Add zeroization verification tests for key material operations
(explicit .zeroize() clears data, XOR operations work through wrapper).
Add negative tests for pkey_pub_from_pem and rsa_oaep_encrypt in
the keylime crypto module (empty/garbage/truncated PEM, EC key,
oversized plaintext).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3 implementation: Push Model as Primary Mode.

- Auto-detect push model when verifier reports API >= 3.0, removing
  the need for explicit --push-model flag in most cases
- Add --pull-model flag to force legacy API 2.x behavior, with
  deprecation warning when used against v3.x verifiers
- Make IP/port optional in AddAgentRequest for push model (agent
  doesn't need direct contact in push model)
- Add --wait-for-attestation flag with configurable timeout to poll
  verifier until first attestation completes
- Improve error messages with actionable recovery guidance for
  registrar not-found, verifier enrollment, and key delivery failures
- Add push/pull model indicator to agent status response
- Replace port==0 heuristic in update command with API version-based
  push model detection
- Add 15 new unit tests for model auto-detection, attestation state
  machine, and optional field handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add --timeout CLI flag, no-argument behavior with config summary and
dynamic clap help, interactive configuration wizard (dialoguer), config
file path tracking, formalized search paths, and integration tests.

Key changes:
- --timeout global flag overrides client.timeout
- Running without subcommand shows config summary + clap help
- `configure` subcommand with interactive wizard (wizard feature flag)
  and --non-interactive mode for scripted configuration
- Config struct tracks loaded_from path, has_config_file() helper
- .keylimectl/config.toml added as highest-priority auto-discovery path
- 348 tests (340 unit + 8 integration), clippy clean across all feature
  combinations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the `keylimectl info` command with subcommands for inspecting
configuration, server status, agents, and TLS certificates. Info
commands work even with incomplete configuration (warn on validation
instead of exiting), making them useful for troubleshooting.

Subcommands:
- `info` (no subcommand): local diagnostics with version, features,
  config file search results, effective config with per-field source
  annotations (cli/env_var/config_file/default), KEYLIME_* env vars
- `info verifier`: connect to verifier, report API version, agent count
- `info registrar`: connect to registrar, report API version, agent count
- `info agent <ID>`: combined verifier+registrar view with summary
- `info tls`: validate cert files, check expiration, verify cert/key pair

Infrastructure:
- CliOverrides struct tracks which CLI args were provided for source
  annotation in the info command
- Config::config_search_paths() exposes file search paths
- RegistrarClient::api_version() public getter added
- Info command gets its own match arm in main() with validation bypass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…with new CLI subcommands

Convert commands/policy.rs into a directory module (commands/policy/)
with the existing CRUD operations moved to crud.rs. Add new CLI
subcommands for local policy tools:

- policy generate runtime/measured-boot/tpm
- policy sign (DSSE signing)
- policy verify-signature
- policy validate
- policy convert (legacy format conversion)
- verify evidence (top-level command for one-shot attestation)

Add new error types: PolicyGenerationError, DsseError, EvidenceError.
Create policy_tools module skeleton for shared policy logic.

All new subcommands return "not yet implemented" stubs. Existing CRUD
commands and all 372 unit tests continue to pass unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ypes

Add Rust types for policy schemas that match the Python implementation,
enabling serialization compatibility between Python and Rust codebases.

- RuntimePolicy: v1 schema with digests, excludes, keyrings, IMA config
- MeasuredBootPolicy: UEFI Secure Boot reference state types
- TpmPolicy: PCR mask and expected values with helper methods

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion

Add IMA measurement list parsing, flat-text and JSON allowlist parsing,
exclude list parsing, and file digest calculation for local runtime
policy generation via `keylimectl policy generate runtime`.

- ima_parser: parse IMA logs (ima, ima-ng, ima-sig, ima-buf templates)
- ima_parser: parse flat-text and JSON allowlists, exclude lists
- digest: calculate file digests using OpenSSL (sha1/256/384/512/sm3)
- generate: wire Runtime subcommand to parse inputs and build policy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add filesystem tree scanning for rootfs digest calculation and policy
merge utilities. Wire --rootfs and --skip-path CLI args to the runtime
policy generate command using tokio::spawn_blocking for CPU-bound work.

- filesystem: recursive directory walk with skip paths and symlink exclusion
- merge: union of digests, excludes, keyrings, and ima-buf entries
- runtime_policy: add deduplication to add_digest/add_keyring/add_ima_buf

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation

Add Dead Simple Signing Envelope (DSSE) support for policy signing and
verification with ECDSA P-256 and X.509 certificate backends. Implements
PAE (Pre-Authentication Encoding), envelope sign/verify protocol, key
generation/loading, and self-signed certificate creation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oot, and TPM policies

Add structural and content validation for all three policy types with
auto-detection. Validates digest formats, required fields, PCR mask
consistency, and schema compatibility. Supports optional DSSE signature
verification during validation via --signature-key.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add conversion from JSON and flat-text allowlists to v1 runtime policy
format. Supports auto-detection of input format, exclude list merging,
and verification key injection. Adds in-memory parsing helpers for
JSON and flat-text allowlists to ima_parser.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add verify evidence command that posts TPM or TEE attestation evidence
to the verifier's /verify/evidence endpoint. Reads quote, AK, EK files
as base64, sends with nonce and policies, parses verification results
including failure details.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TPM policy generation from PCR values file with index filtering
and mask calculation. Add measured boot policy generation from UEFI
event logs using the shared crate's UefiLogHandler, extracting S-CRTM,
platform firmware, and Secure Boot variable measurements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 23 integration tests in tests/policy_tools.rs covering:
- Help output for all new subcommands (generate, sign, validate, convert)
- Runtime policy generation from IMA logs, allowlists, with excludes
- TPM policy generation from PCR values files
- Policy validation for runtime and TPM policy types
- DSSE signing (ECDSA and X.509 backends) and verification
- Legacy allowlist conversion (flat-text and JSON formats)
- End-to-end pipeline: generate -> validate -> sign -> verify

Fix bugs found during integration testing:
- Remove duplicate stdout output (commands called output.success()
  AND main.rs dispatcher also called it, producing double JSON)
- Remove default_value on --ima-measurement-list to make it truly
  optional (previously always read /sys/kernel/security/ima even
  when only --allowlist was specified)
- Add PolicyAction::is_local_only() and match arm in main() so
  local-only policy commands (generate, sign, verify-signature,
  validate, convert) bypass strict TLS config validation

Update ROADMAP.md to mark Phase 6 items as complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

3 participants